// Copyright 2020 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "dawn_native/SubresourceStorage.h" #include "common/Log.h" using namespace dawn_native; // A fake class that replicates the behavior of SubresourceStorage but without any compression and // is used to compare the results of operations on SubresourceStorage against the "ground truth" of // FakeStorage. template struct FakeStorage { FakeStorage(Aspect aspects, uint32_t arrayLayerCount, uint32_t mipLevelCount, T initialValue = {}) : mAspects(aspects), mArrayLayerCount(arrayLayerCount), mMipLevelCount(mipLevelCount), mData(GetAspectCount(aspects) * arrayLayerCount * mipLevelCount, initialValue) { } template void Update(const SubresourceRange& range, F&& updateFunc) { for (Aspect aspect : IterateEnumMask(range.aspects)) { for (uint32_t layer = range.baseArrayLayer; layer < range.baseArrayLayer + range.layerCount; layer++) { for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount; level++) { SubresourceRange range = SubresourceRange::MakeSingle(aspect, layer, level); updateFunc(range, &mData[GetDataIndex(aspect, layer, level)]); } } } } template void Merge(const SubresourceStorage& other, F&& mergeFunc) { for (Aspect aspect : IterateEnumMask(mAspects)) { for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { for (uint32_t level = 0; level < mMipLevelCount; level++) { SubresourceRange range = SubresourceRange::MakeSingle(aspect, layer, level); mergeFunc(range, &mData[GetDataIndex(aspect, layer, level)], other.Get(aspect, layer, level)); } } } } const T& Get(Aspect aspect, uint32_t arrayLayer, uint32_t mipLevel) const { return mData[GetDataIndex(aspect, arrayLayer, mipLevel)]; } size_t GetDataIndex(Aspect aspect, uint32_t layer, uint32_t level) const { uint32_t aspectIndex = GetAspectIndex(aspect); return level + mMipLevelCount * (layer + mArrayLayerCount * aspectIndex); } // Method that checks that this and real have exactly the same content. It does so via looping // on all subresources and calling Get() (hence testing Get()). It also calls Iterate() // checking that every subresource is mentioned exactly once and that its content is correct // (hence testing Iterate()). // Its implementation requires the RangeTracker below that itself needs FakeStorage so it // cannot be define inline with the other methods. void CheckSameAs(const SubresourceStorage& real); Aspect mAspects; uint32_t mArrayLayerCount; uint32_t mMipLevelCount; std::vector mData; }; // Track a set of ranges that have been seen and can assert that in aggregate they make exactly // a single range (and that each subresource was seen only once). struct RangeTracker { template RangeTracker(const SubresourceStorage& s) : mTracked(s.GetAspectsForTesting(), s.GetArrayLayerCountForTesting(), s.GetMipLevelCountForTesting(), 0) { } void Track(const SubresourceRange& range) { // Add +1 to the subresources tracked. mTracked.Update(range, [](const SubresourceRange&, uint32_t* counter) { ASSERT_EQ(*counter, 0u); *counter += 1; }); } void CheckTrackedExactly(const SubresourceRange& range) { // Check that all subresources in the range were tracked once and set the counter back to 0. mTracked.Update(range, [](const SubresourceRange&, uint32_t* counter) { ASSERT_EQ(*counter, 1u); *counter = 0; }); // Now all subresources should be at 0. for (int counter : mTracked.mData) { ASSERT_EQ(counter, 0); } } FakeStorage mTracked; }; template void FakeStorage::CheckSameAs(const SubresourceStorage& real) { EXPECT_EQ(real.GetAspectsForTesting(), mAspects); EXPECT_EQ(real.GetArrayLayerCountForTesting(), mArrayLayerCount); EXPECT_EQ(real.GetMipLevelCountForTesting(), mMipLevelCount); RangeTracker tracker(real); real.Iterate([&](const SubresourceRange& range, const T& data) { // Check that the range is sensical. EXPECT_TRUE(IsSubset(range.aspects, mAspects)); EXPECT_LT(range.baseArrayLayer, mArrayLayerCount); EXPECT_LE(range.baseArrayLayer + range.layerCount, mArrayLayerCount); EXPECT_LT(range.baseMipLevel, mMipLevelCount); EXPECT_LE(range.baseMipLevel + range.levelCount, mMipLevelCount); for (Aspect aspect : IterateEnumMask(range.aspects)) { for (uint32_t layer = range.baseArrayLayer; layer < range.baseArrayLayer + range.layerCount; layer++) { for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount; level++) { EXPECT_EQ(data, Get(aspect, layer, level)); EXPECT_EQ(data, real.Get(aspect, layer, level)); } } } tracker.Track(range); }); tracker.CheckTrackedExactly( SubresourceRange::MakeFull(mAspects, mArrayLayerCount, mMipLevelCount)); } template void CheckAspectCompressed(const SubresourceStorage& s, Aspect aspect, bool expected) { ASSERT(HasOneBit(aspect)); uint32_t levelCount = s.GetMipLevelCountForTesting(); uint32_t layerCount = s.GetArrayLayerCountForTesting(); bool seen = false; s.Iterate([&](const SubresourceRange& range, const T&) { if (range.aspects == aspect && range.layerCount == layerCount && range.levelCount == levelCount && range.baseArrayLayer == 0 && range.baseMipLevel == 0) { seen = true; } }); ASSERT_EQ(seen, expected); // Check that the internal state of SubresourceStorage matches what we expect. // If an aspect is compressed, all its layers should be internally tagged as compressed. ASSERT_EQ(s.IsAspectCompressedForTesting(aspect), expected); if (expected) { for (uint32_t layer = 0; layer < s.GetArrayLayerCountForTesting(); layer++) { ASSERT_TRUE(s.IsLayerCompressedForTesting(aspect, layer)); } } } template void CheckLayerCompressed(const SubresourceStorage& s, Aspect aspect, uint32_t layer, bool expected) { ASSERT(HasOneBit(aspect)); uint32_t levelCount = s.GetMipLevelCountForTesting(); bool seen = false; s.Iterate([&](const SubresourceRange& range, const T&) { if (range.aspects == aspect && range.layerCount == 1 && range.levelCount == levelCount && range.baseArrayLayer == layer && range.baseMipLevel == 0) { seen = true; } }); ASSERT_EQ(seen, expected); ASSERT_EQ(s.IsLayerCompressedForTesting(aspect, layer), expected); } struct SmallData { uint32_t value = 0xF00; }; bool operator==(const SmallData& a, const SmallData& b) { return a.value == b.value; } // Test that the default value is correctly set. TEST(SubresourceStorageTest, DefaultValue) { // Test setting no default value for a primitive type. { SubresourceStorage s(Aspect::Color, 3, 5); EXPECT_EQ(s.Get(Aspect::Color, 1, 2), 0); FakeStorage f(Aspect::Color, 3, 5); f.CheckSameAs(s); } // Test setting a default value for a primitive type. { SubresourceStorage s(Aspect::Color, 3, 5, 42); EXPECT_EQ(s.Get(Aspect::Color, 1, 2), 42); FakeStorage f(Aspect::Color, 3, 5, 42); f.CheckSameAs(s); } // Test setting no default value for a type with a default constructor. { SubresourceStorage s(Aspect::Color, 3, 5); EXPECT_EQ(s.Get(Aspect::Color, 1, 2).value, 0xF00u); FakeStorage f(Aspect::Color, 3, 5); f.CheckSameAs(s); } // Test setting a default value for a type with a default constructor. { SubresourceStorage s(Aspect::Color, 3, 5, {007u}); EXPECT_EQ(s.Get(Aspect::Color, 1, 2).value, 007u); FakeStorage f(Aspect::Color, 3, 5, {007u}); f.CheckSameAs(s); } } // The tests for Update() all follow the same pattern of setting up a real and a fake storage then // performing one or multiple Update()s on them and checking: // - They have the same content. // - The Update() range was correct. // - The aspects and layers have the expected "compressed" status. // Calls Update both on the read storage and the fake storage but intercepts the call to updateFunc // done by the real storage to check their ranges argument aggregate to exactly the update range. template void CallUpdateOnBoth(SubresourceStorage* s, FakeStorage* f, const SubresourceRange& range, F&& updateFunc) { RangeTracker tracker(*s); s->Update(range, [&](const SubresourceRange& range, T* data) { tracker.Track(range); updateFunc(range, data); }); f->Update(range, updateFunc); tracker.CheckTrackedExactly(range); f->CheckSameAs(*s); } // Test updating a single subresource on a single-aspect storage. TEST(SubresourceStorageTest, SingleSubresourceUpdateSingleAspect) { SubresourceStorage s(Aspect::Color, 5, 7); FakeStorage f(Aspect::Color, 5, 7); // Update a single subresource. SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Color, 3, 2); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 1; }); CheckAspectCompressed(s, Aspect::Color, false); CheckLayerCompressed(s, Aspect::Color, 2, true); CheckLayerCompressed(s, Aspect::Color, 3, false); CheckLayerCompressed(s, Aspect::Color, 4, true); } // Test updating a single subresource on a multi-aspect storage. TEST(SubresourceStorageTest, SingleSubresourceUpdateMultiAspect) { SubresourceStorage s(Aspect::Depth | Aspect::Stencil, 5, 3); FakeStorage f(Aspect::Depth | Aspect::Stencil, 5, 3); SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Stencil, 1, 2); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 1; }); CheckAspectCompressed(s, Aspect::Depth, true); CheckAspectCompressed(s, Aspect::Stencil, false); CheckLayerCompressed(s, Aspect::Stencil, 0, true); CheckLayerCompressed(s, Aspect::Stencil, 1, false); CheckLayerCompressed(s, Aspect::Stencil, 2, true); } // Test updating as a stipple pattern on one of two aspects then updating it completely. TEST(SubresourceStorageTest, UpdateStipple) { const uint32_t kLayers = 10; const uint32_t kLevels = 7; SubresourceStorage s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); FakeStorage f(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); // Update with a stipple. for (uint32_t layer = 0; layer < kLayers; layer++) { for (uint32_t level = 0; level < kLevels; level++) { if ((layer + level) % 2 == 0) { SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Depth, layer, level); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 17; }); } } } // The depth should be fully uncompressed while the stencil stayed compressed. CheckAspectCompressed(s, Aspect::Stencil, true); CheckAspectCompressed(s, Aspect::Depth, false); for (uint32_t layer = 0; layer < kLayers; layer++) { CheckLayerCompressed(s, Aspect::Depth, layer, false); } // Update completely with a single value. Recompression should happen! { SubresourceRange fullRange = SubresourceRange::MakeFull(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); CallUpdateOnBoth(&s, &f, fullRange, [](const SubresourceRange&, int* data) { *data = 31; }); } CheckAspectCompressed(s, Aspect::Depth, true); CheckAspectCompressed(s, Aspect::Stencil, true); } // Test updating as a crossing band pattern: // - The first band is full layers [2, 3] on both aspects // - The second band is full mips [5, 6] on one aspect. // Then updating completely. TEST(SubresourceStorageTest, UpdateTwoBand) { const uint32_t kLayers = 5; const uint32_t kLevels = 9; SubresourceStorage s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); FakeStorage f(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); // Update the two bands { SubresourceRange range(Aspect::Depth | Aspect::Stencil, {2, 2}, {0, kLevels}); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 3; }); } // The layers were fully updated so they should stay compressed. CheckLayerCompressed(s, Aspect::Depth, 2, true); CheckLayerCompressed(s, Aspect::Depth, 3, true); CheckLayerCompressed(s, Aspect::Stencil, 2, true); CheckLayerCompressed(s, Aspect::Stencil, 3, true); { SubresourceRange range(Aspect::Depth, {0, kLayers}, {5, 2}); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data *= 3; }); } // The layers had to be decompressed in depth CheckLayerCompressed(s, Aspect::Depth, 2, false); CheckLayerCompressed(s, Aspect::Depth, 3, false); CheckLayerCompressed(s, Aspect::Stencil, 2, true); CheckLayerCompressed(s, Aspect::Stencil, 3, true); // Update completely. Without a single value recompression shouldn't happen. { SubresourceRange fullRange = SubresourceRange::MakeFull(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); CallUpdateOnBoth(&s, &f, fullRange, [](const SubresourceRange&, int* data) { *data += 12; }); } CheckAspectCompressed(s, Aspect::Depth, false); CheckAspectCompressed(s, Aspect::Stencil, false); } // Test updating with extremal subresources // - Then half of the array layers in full. // - Then updating completely. TEST(SubresourceStorageTest, UpdateExtremas) { const uint32_t kLayers = 6; const uint32_t kLevels = 4; SubresourceStorage s(Aspect::Color, kLayers, kLevels); FakeStorage f(Aspect::Color, kLayers, kLevels); // Update the two extrema { SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Color, 0, kLevels - 1); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 3; }); } { SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Color, kLayers - 1, 0); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data *= 3; }); } CheckLayerCompressed(s, Aspect::Color, 0, false); CheckLayerCompressed(s, Aspect::Color, 1, true); CheckLayerCompressed(s, Aspect::Color, kLayers - 2, true); CheckLayerCompressed(s, Aspect::Color, kLayers - 1, false); // Update half of the layers in full with constant values. Some recompression should happen. { SubresourceRange range(Aspect::Color, {0, kLayers / 2}, {0, kLevels}); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data = 123; }); } CheckLayerCompressed(s, Aspect::Color, 0, true); CheckLayerCompressed(s, Aspect::Color, 1, true); CheckLayerCompressed(s, Aspect::Color, kLayers - 1, false); // Update completely. Recompression should happen! { SubresourceRange fullRange = SubresourceRange::MakeFull(Aspect::Color, kLayers, kLevels); CallUpdateOnBoth(&s, &f, fullRange, [](const SubresourceRange&, int* data) { *data = 35; }); } CheckAspectCompressed(s, Aspect::Color, true); } // A regression test for an issue found while reworking the implementation where RecompressAspect // didn't correctly check that each each layer was compressed but only that their 0th value was // the same. TEST(SubresourceStorageTest, UpdateLevel0sHappenToMatch) { SubresourceStorage s(Aspect::Color, 2, 2); FakeStorage f(Aspect::Color, 2, 2); // Update 0th mip levels to some value, it should decompress the aspect and both layers. { SubresourceRange range(Aspect::Color, {0, 2}, {0, 1}); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data = 17; }); } CheckAspectCompressed(s, Aspect::Color, false); CheckLayerCompressed(s, Aspect::Color, 0, false); CheckLayerCompressed(s, Aspect::Color, 1, false); // Update the whole resource by doing +1. The aspects and layers should stay decompressed. { SubresourceRange range = SubresourceRange::MakeFull(Aspect::Color, 2, 2); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 1; }); } CheckAspectCompressed(s, Aspect::Color, false); CheckLayerCompressed(s, Aspect::Color, 0, false); CheckLayerCompressed(s, Aspect::Color, 1, false); } // The tests for Merge() all follow the same as the Update() tests except that they use Update() // to set up the test storages. // Similar to CallUpdateOnBoth but for Merge template void CallMergeOnBoth(SubresourceStorage* s, FakeStorage* f, const SubresourceStorage& other, F&& mergeFunc) { RangeTracker tracker(*s); s->Merge(other, [&](const SubresourceRange& range, T* data, const U& otherData) { tracker.Track(range); mergeFunc(range, data, otherData); }); f->Merge(other, mergeFunc); tracker.CheckTrackedExactly( SubresourceRange::MakeFull(f->mAspects, f->mArrayLayerCount, f->mMipLevelCount)); f->CheckSameAs(*s); } // Test merging two fully compressed single-aspect resources. TEST(SubresourceStorageTest, MergeFullWithFullSingleAspect) { SubresourceStorage s(Aspect::Color, 4, 6); FakeStorage f(Aspect::Color, 4, 6); // Merge the whole resource in a single call. SubresourceStorage other(Aspect::Color, 4, 6, true); CallMergeOnBoth(&s, &f, other, [](const SubresourceRange&, int* data, bool other) { if (other) { *data = 13; } }); CheckAspectCompressed(s, Aspect::Color, true); } // Test merging two fully compressed multi-aspect resources. TEST(SubresourceStorageTest, MergeFullWithFullMultiAspect) { SubresourceStorage s(Aspect::Depth | Aspect::Stencil, 6, 7); FakeStorage f(Aspect::Depth | Aspect::Stencil, 6, 7); // Merge the whole resource in a single call. SubresourceStorage other(Aspect::Depth | Aspect::Stencil, 6, 7, true); CallMergeOnBoth(&s, &f, other, [](const SubresourceRange&, int* data, bool other) { if (other) { *data = 13; } }); CheckAspectCompressed(s, Aspect::Depth, true); CheckAspectCompressed(s, Aspect::Stencil, true); } // Test merging a fully compressed resource in a resource with the "cross band" pattern. // - The first band is full layers [2, 3] on both aspects // - The second band is full mips [5, 6] on one aspect. // This provides coverage of using a single piece of data from `other` to update all of `s` TEST(SubresourceStorageTest, MergeFullInTwoBand) { const uint32_t kLayers = 5; const uint32_t kLevels = 9; SubresourceStorage s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); FakeStorage f(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); // Update the two bands { SubresourceRange range(Aspect::Depth | Aspect::Stencil, {2, 2}, {0, kLevels}); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 3; }); } { SubresourceRange range(Aspect::Depth, {0, kLayers}, {5, 2}); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 5; }); } // Merge the fully compressed resource. SubresourceStorage other(Aspect::Depth | Aspect::Stencil, kLayers, kLevels, 17); CallMergeOnBoth(&s, &f, other, [](const SubresourceRange&, int* data, int other) { *data += other; }); // The layers traversed by the mip band are still uncompressed. CheckLayerCompressed(s, Aspect::Depth, 1, false); CheckLayerCompressed(s, Aspect::Depth, 2, false); CheckLayerCompressed(s, Aspect::Depth, 3, false); CheckLayerCompressed(s, Aspect::Depth, 4, false); // Stencil is decompressed but all its layers are still compressed because there wasn't the mip // band. CheckAspectCompressed(s, Aspect::Stencil, false); CheckLayerCompressed(s, Aspect::Stencil, 1, true); CheckLayerCompressed(s, Aspect::Stencil, 2, true); CheckLayerCompressed(s, Aspect::Stencil, 3, true); CheckLayerCompressed(s, Aspect::Stencil, 4, true); } // Test the reverse, mergign two-bands in a full resource. This provides coverage for decompressing // aspects / and partilly layers to match the compression of `other` TEST(SubresourceStorageTest, MergeTwoBandInFull) { const uint32_t kLayers = 5; const uint32_t kLevels = 9; SubresourceStorage s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels, 75); FakeStorage f(Aspect::Depth | Aspect::Stencil, kLayers, kLevels, 75); // Update the two bands SubresourceStorage other(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); { SubresourceRange range(Aspect::Depth | Aspect::Stencil, {2, 2}, {0, kLevels}); other.Update(range, [](const SubresourceRange&, int* data) { *data += 3; }); } { SubresourceRange range(Aspect::Depth, {0, kLayers}, {5, 2}); other.Update(range, [](const SubresourceRange&, int* data) { *data += 5; }); } // Merge the fully compressed resource. CallMergeOnBoth(&s, &f, other, [](const SubresourceRange&, int* data, int other) { *data += other; }); // The layers traversed by the mip band are still uncompressed. CheckLayerCompressed(s, Aspect::Depth, 1, false); CheckLayerCompressed(s, Aspect::Depth, 2, false); CheckLayerCompressed(s, Aspect::Depth, 3, false); CheckLayerCompressed(s, Aspect::Depth, 4, false); // Stencil is decompressed but all its layers are still compressed because there wasn't the mip // band. CheckAspectCompressed(s, Aspect::Stencil, false); CheckLayerCompressed(s, Aspect::Stencil, 1, true); CheckLayerCompressed(s, Aspect::Stencil, 2, true); CheckLayerCompressed(s, Aspect::Stencil, 3, true); CheckLayerCompressed(s, Aspect::Stencil, 4, true); } // Test merging storage with a layer band in a stipple patterned storage. This provide coverage // for the code path that uses the same layer data for other multiple times. TEST(SubresourceStorageTest, MergeLayerBandInStipple) { const uint32_t kLayers = 3; const uint32_t kLevels = 5; SubresourceStorage s(Aspect::Color, kLayers, kLevels); FakeStorage f(Aspect::Color, kLayers, kLevels); SubresourceStorage other(Aspect::Color, kLayers, kLevels); for (uint32_t layer = 0; layer < kLayers; layer++) { for (uint32_t level = 0; level < kLevels; level++) { if ((layer + level) % 2 == 0) { SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Color, layer, level); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 17; }); } } if (layer % 2 == 0) { other.Update({Aspect::Color, {layer, 1}, {0, kLevels}}, [](const SubresourceRange&, int* data) { *data += 8; }); } } // Merge the band in the stipple. CallMergeOnBoth(&s, &f, other, [](const SubresourceRange&, int* data, int other) { *data += other; }); // None of the resulting layers are compressed. CheckLayerCompressed(s, Aspect::Color, 0, false); CheckLayerCompressed(s, Aspect::Color, 1, false); CheckLayerCompressed(s, Aspect::Color, 2, false); } // Regression test for a missing check that layer 0 is compressed when recompressing. TEST(SubresourceStorageTest, Layer0NotCompressedBlocksAspectRecompression) { const uint32_t kLayers = 2; const uint32_t kLevels = 2; SubresourceStorage s(Aspect::Color, kLayers, kLevels); FakeStorage f(Aspect::Color, kLayers, kLevels); // Set up s with zeros except (0, 1) which is garbage. { SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Color, 0, 1); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 0xABC; }); } // Other is 2x2 of zeroes SubresourceStorage other(Aspect::Color, kLayers, kLevels); // Fake updating F with other which is fully compressed and will trigger recompression. CallMergeOnBoth(&s, &f, other, [](const SubresourceRange&, int*, int) {}); // The Color aspect should not have been recompressed. CheckAspectCompressed(s, Aspect::Color, false); CheckLayerCompressed(s, Aspect::Color, 0, false); } // Regression test for aspect decompression not copying to layer 0 TEST(SubresourceStorageTest, AspectDecompressionUpdatesLayer0) { const uint32_t kLayers = 2; const uint32_t kLevels = 2; SubresourceStorage s(Aspect::Color, kLayers, kLevels, 3); FakeStorage f(Aspect::Color, kLayers, kLevels, 3); // Cause decompression by writing to a single subresource. { SubresourceRange range = SubresourceRange::MakeSingle(Aspect::Color, 1, 1); CallUpdateOnBoth(&s, &f, range, [](const SubresourceRange&, int* data) { *data += 0xABC; }); } // Check that the aspect's value of 3 was correctly decompressed in layer 0. CheckLayerCompressed(s, Aspect::Color, 0, true); EXPECT_EQ(3, s.Get(Aspect::Color, 0, 0)); EXPECT_EQ(3, s.Get(Aspect::Color, 0, 1)); } // Bugs found while testing: // - mLayersCompressed not initialized to true. // - DecompressLayer setting Compressed to true instead of false. // - Get() checking for !compressed instead of compressed for the early exit. // - ASSERT in RecompressLayers was inverted. // - Two != being converted to == during a rework. // - (with ASSERT) that RecompressAspect didn't check that aspect 0 was compressed. // - Missing decompression of layer 0 after introducing mInlineAspectData.