| // Copyright 2020 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "dawn/common/Log.h" |
| #include "dawn/native/SubresourceStorage.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| namespace dawn::native { |
| |
| using ::testing::HasSubstr; |
| |
| // 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 <typename T> |
| 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 <typename F> |
| 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 updateRange = |
| SubresourceRange::MakeSingle(aspect, layer, level); |
| updateFunc(updateRange, &mData[GetDataIndex(aspect, layer, level)]); |
| } |
| } |
| } |
| } |
| |
| template <typename U, typename F> |
| void Merge(const SubresourceStorage<U>& 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<int> so it cannot be define inline with the other methods. |
| void CheckSameAs(const SubresourceStorage<T>& real); |
| |
| Aspect mAspects; |
| uint32_t mArrayLayerCount; |
| uint32_t mMipLevelCount; |
| |
| std::vector<T> 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 <typename T> |
| explicit RangeTracker(const SubresourceStorage<T>& 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<uint32_t> mTracked; |
| }; |
| |
| template <typename T> |
| void FakeStorage<T>::CheckSameAs(const SubresourceStorage<T>& 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 <typename T> |
| void CheckAspectCompressed(const SubresourceStorage<T>& s, Aspect aspect, bool expected) { |
| DAWN_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 <typename T> |
| void CheckLayerCompressed(const SubresourceStorage<T>& s, |
| Aspect aspect, |
| uint32_t layer, |
| bool expected) { |
| DAWN_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; |
| } |
| |
| // Tests that the MaybeError version of Iterate returns the first error that it encounters. |
| TEST(SubresourceStorageTest, IterateMaybeError) { |
| // Create a resource with multiple layers of different data so that we can ensure that the |
| // iterate function runs more than once. |
| constexpr uint32_t kLayers = 4; |
| SubresourceStorage<uint32_t> s(Aspect::Color, kLayers, 1); |
| for (uint32_t layer = 0; layer < kLayers; layer++) { |
| s.Update(SubresourceRange::MakeSingle(Aspect::Color, layer, 0), |
| [&](const SubresourceRange&, uint32_t* data) { *data = layer + 1; }); |
| } |
| |
| // Make sure that the first error is returned. |
| uint32_t errorLayer = 0; |
| MaybeError maybeError = |
| s.Iterate([&](const SubresourceRange& range, const uint32_t& layer) -> MaybeError { |
| if (!errorLayer) { |
| errorLayer = layer; |
| } |
| return DAWN_VALIDATION_ERROR("Errored at layer: %d", layer); |
| }); |
| ASSERT_TRUE(maybeError.IsError()); |
| std::unique_ptr<ErrorData> error = maybeError.AcquireError(); |
| EXPECT_THAT(error->GetFormattedMessage(), HasSubstr(std::to_string(errorLayer))); |
| } |
| |
| // Test that the default value is correctly set. |
| TEST(SubresourceStorageTest, DefaultValue) { |
| // Test setting no default value for a primitive type. |
| { |
| SubresourceStorage<int> s(Aspect::Color, 3, 5); |
| EXPECT_EQ(s.Get(Aspect::Color, 1, 2), 0); |
| |
| FakeStorage<int> f(Aspect::Color, 3, 5); |
| f.CheckSameAs(s); |
| } |
| |
| // Test setting a default value for a primitive type. |
| { |
| SubresourceStorage<int> s(Aspect::Color, 3, 5, 42); |
| EXPECT_EQ(s.Get(Aspect::Color, 1, 2), 42); |
| |
| FakeStorage<int> f(Aspect::Color, 3, 5, 42); |
| f.CheckSameAs(s); |
| } |
| |
| // Test setting no default value for a type with a default constructor. |
| { |
| SubresourceStorage<SmallData> s(Aspect::Color, 3, 5); |
| EXPECT_EQ(s.Get(Aspect::Color, 1, 2).value, 0xF00u); |
| |
| FakeStorage<SmallData> f(Aspect::Color, 3, 5); |
| f.CheckSameAs(s); |
| } |
| // Test setting a default value for a type with a default constructor. |
| { |
| SubresourceStorage<SmallData> s(Aspect::Color, 3, 5, {007u}); |
| EXPECT_EQ(s.Get(Aspect::Color, 1, 2).value, 007u); |
| |
| FakeStorage<SmallData> 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 <typename T, typename F> |
| void CallUpdateOnBoth(SubresourceStorage<T>* s, |
| FakeStorage<T>* 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<int> s(Aspect::Color, 5, 7); |
| FakeStorage<int> 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<int> s(Aspect::Depth | Aspect::Stencil, 5, 3); |
| FakeStorage<int> 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<int> s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); |
| FakeStorage<int> 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<int> s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); |
| FakeStorage<int> 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<int> s(Aspect::Color, kLayers, kLevels); |
| FakeStorage<int> 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<int> s(Aspect::Color, 2, 2); |
| FakeStorage<int> 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 <typename T, typename U, typename F> |
| void CallMergeOnBoth(SubresourceStorage<T>* s, |
| FakeStorage<T>* f, |
| const SubresourceStorage<U>& 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<int> s(Aspect::Color, 4, 6); |
| FakeStorage<int> f(Aspect::Color, 4, 6); |
| |
| // Merge the whole resource in a single call. |
| SubresourceStorage<bool> 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<int> s(Aspect::Depth | Aspect::Stencil, 6, 7); |
| FakeStorage<int> f(Aspect::Depth | Aspect::Stencil, 6, 7); |
| |
| // Merge the whole resource in a single call. |
| SubresourceStorage<bool> 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<int> s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels); |
| FakeStorage<int> 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<int> 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<int> s(Aspect::Depth | Aspect::Stencil, kLayers, kLevels, 75); |
| FakeStorage<int> f(Aspect::Depth | Aspect::Stencil, kLayers, kLevels, 75); |
| |
| // Update the two bands |
| SubresourceStorage<int> 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<int> s(Aspect::Color, kLayers, kLevels); |
| FakeStorage<int> f(Aspect::Color, kLayers, kLevels); |
| SubresourceStorage<int> 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<int> s(Aspect::Color, kLayers, kLevels); |
| FakeStorage<int> 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<int> 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<int> s(Aspect::Color, kLayers, kLevels, 3); |
| FakeStorage<int> 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. |
| // - DAWN_ASSERT in RecompressLayers was inverted. |
| // - Two != being converted to == during a rework. |
| // - (with DAWN_ASSERT) that RecompressAspect didn't check that aspect 0 was compressed. |
| // - Missing decompression of layer 0 after introducing mInlineAspectData. |
| |
| } // namespace dawn::native |