| // 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. | |
| #ifndef DAWNNATIVE_SUBRESOURCESTORAGE_H_ | |
| #define DAWNNATIVE_SUBRESOURCESTORAGE_H_ | |
| #include "common/Assert.h" | |
| #include "common/TypeTraits.h" | |
| #include "dawn_native/EnumMaskIterator.h" | |
| #include "dawn_native/Subresource.h" | |
| #include <array> | |
| #include <limits> | |
| #include <memory> | |
| #include <vector> | |
| namespace dawn_native { | |
| // SubresourceStorage<T> acts like a simple map from subresource (aspect, layer, level) to a | |
| // value of type T except that it tries to compress similar subresources so that algorithms | |
| // can act on a whole range of subresources at once if they have the same state. | |
| // | |
| // For example a very common case to optimize for is the tracking of the usage of texture | |
| // subresources inside a render pass: the vast majority of texture views will select the whole | |
| // texture while a small minority will select a sub-range. We want to optimize the common case | |
| // by setting and checking a single "usage" value when a full subresource is used but at the | |
| // same time allow per-subresource data when needed. | |
| // | |
| // Another example is barrier tracking per-subresource in the backends: it will often happen | |
| // that during texture upload each mip level will have a different "barrier state". However | |
| // when the texture is fully uploaded and after it is used for sampling (with a full view) for | |
| // the first time, the barrier state will likely be the same across all the subresources. | |
| // That's why some form of "recompression" of subresource state must be possibe. | |
| // | |
| // In order to keep the implementation details private and to avoid iterator-hell, this | |
| // container uses a more functional approach of calling a closure on the interesting ranges. | |
| // This is for example how to look at the state of all subresources. | |
| // | |
| // subresources.Iterate([](const SubresourceRange& range, const T& data) { | |
| // // Do something with the knowledge that all the subresources in `range` have value | |
| // // `data`. | |
| // }); | |
| // | |
| // SubresourceStorage internally tracks compression state per aspect and then per layer of each | |
| // aspect. This means that a 2-aspect texture can have the following compression state: | |
| // | |
| // - Aspect 0 is fully compressed. | |
| // - Aspect 1 is partially compressed: | |
| // - Aspect 1 layer 3 is decompressed. | |
| // - Aspect 1 layer 0-2 and 4-42 are compressed. | |
| // | |
| // A useful model to reason about SubresourceStorage is to represent is as a tree: | |
| // | |
| // - SubresourceStorage is the root. | |
| // |-> Nodes 1 deep represent each aspect. If an aspect is compressed, its node doesn't have | |
| // any children because the data is constant across all of the subtree. | |
| // |-> Nodes 2 deep represent layers (for uncompressed aspects). If a layer is compressed, | |
| // its node doesn't have any children because the data is constant across all of the | |
| // subtree. | |
| // |-> Nodes 3 deep represent individial mip levels (for uncompressed layers). | |
| // | |
| // The concept of recompression is the removal of all child nodes of a non-leaf node when the | |
| // data is constant across them. Decompression is the addition of child nodes to a leaf node | |
| // and copying of its data to all its children. | |
| // | |
| // The choice of having secondary compression for array layers is to optimize for the cases | |
| // where transfer operations are used to update specific layers of texture with render or | |
| // transfer operations, while the rest is untouched. It seems much less likely that there | |
| // would be operations that touch all Nth mips of a 2D array texture without touching the | |
| // others. | |
| // | |
| // There are several hot code paths that create new SubresourceStorage like the tracking of | |
| // resource usage per-pass. We don't want to allocate a container for the decompressed data | |
| // unless we have to because it would dramatically lower performance. Instead | |
| // SubresourceStorage contains an inline array that contains the per-aspect compressed data | |
| // and only allocates a per-subresource on aspect decompression. | |
| // | |
| // T must be a copyable type that supports equality comparison with ==. | |
| // | |
| // The implementation of functions in this file can have a lot of control flow and corner cases | |
| // so each modification should come with extensive tests and ensure 100% code coverage of the | |
| // modified functions. See instructions at | |
| // https://chromium.googlesource.com/chromium/src/+/master/docs/testing/code_coverage.md#local-coverage-script | |
| // to run the test with code coverage. A command line that worked in the past (with the right | |
| // GN args for the out/coverage directory in a Chromium checkout) is: | |
| // | |
| /* | |
| python tools/code_coverage/coverage.py dawn_unittests -b out/coverage -o out/report -c \ | |
| "out/coverage/dawn_unittests --gtest_filter=SubresourceStorage\*" -f \ | |
| third_party/dawn/src/dawn_native | |
| */ | |
| // | |
| // TODO(crbug.com/dawn/836): Make the recompression optional, the calling code should know | |
| // if recompression can happen or not in Update() and Merge() | |
| template <typename T> | |
| class SubresourceStorage { | |
| public: | |
| static_assert(std::is_copy_assignable<T>::value, "T must be copyable"); | |
| static_assert(HasEqualityOperator<T>::value, "T requires bool operator == (T, T)"); | |
| // Creates the storage with the given "dimensions" and all subresources starting with the | |
| // initial value. | |
| SubresourceStorage(Aspect aspects, | |
| uint32_t arrayLayerCount, | |
| uint32_t mipLevelCount, | |
| T initialValue = {}); | |
| // Returns the data for a single subresource. Note that the reference returned might be the | |
| // same for multiple subresources. | |
| const T& Get(Aspect aspect, uint32_t arrayLayer, uint32_t mipLevel) const; | |
| // Given an iterateFunc that's a function or function-like objet that can be called with | |
| // arguments of type (const SubresourceRange& range, const T& data) and returns void, | |
| // calls it with aggregate ranges if possible, such that each subresource is part of | |
| // exactly one of the ranges iterateFunc is called with (and obviously data is the value | |
| // stored for that subresource). For example: | |
| // | |
| // subresources.Iterate([&](const SubresourceRange& range, const T& data) { | |
| // // Do something with range and data. | |
| // }); | |
| template <typename F> | |
| void Iterate(F&& iterateFunc) const; | |
| // Given an updateFunc that's a function or function-like objet that can be called with | |
| // arguments of type (const SubresourceRange& range, T* data) and returns void, | |
| // calls it with ranges that in aggregate form `range` and pass for each of the | |
| // sub-ranges a pointer to modify the value for that sub-range. For example: | |
| // | |
| // subresources.Update(view->GetRange(), [](const SubresourceRange&, T* data) { | |
| // *data |= wgpu::TextureUsage::Stuff; | |
| // }); | |
| // | |
| // /!\ WARNING: updateFunc should never use range to compute the update to data otherwise | |
| // your code is likely to break when compression happens. Range should only be used for | |
| // side effects like using it to compute a Vulkan pipeline barrier. | |
| template <typename F> | |
| void Update(const SubresourceRange& range, F&& updateFunc); | |
| // Given a mergeFunc that's a function or a function-like object that can be called with | |
| // arguments of type (const SubresourceRange& range, T* data, const U& otherData) and | |
| // returns void, calls it with ranges that in aggregate form the full resources and pass | |
| // for each of the sub-ranges a pointer to modify the value for that sub-range and the | |
| // corresponding value from other for that sub-range. For example: | |
| // | |
| // subresources.Merge(otherUsages, | |
| // [](const SubresourceRange&, T* data, const T& otherData) { | |
| // *data |= otherData; | |
| // }); | |
| // | |
| // /!\ WARNING: mergeFunc should never use range to compute the update to data otherwise | |
| // your code is likely to break when compression happens. Range should only be used for | |
| // side effects like using it to compute a Vulkan pipeline barrier. | |
| template <typename U, typename F> | |
| void Merge(const SubresourceStorage<U>& other, F&& mergeFunc); | |
| // Other operations to consider: | |
| // | |
| // - UpdateTo(Range, T) that updates the range to a constant value. | |
| // Methods to query the internal state of SubresourceStorage for testing. | |
| Aspect GetAspectsForTesting() const; | |
| uint32_t GetArrayLayerCountForTesting() const; | |
| uint32_t GetMipLevelCountForTesting() const; | |
| bool IsAspectCompressedForTesting(Aspect aspect) const; | |
| bool IsLayerCompressedForTesting(Aspect aspect, uint32_t layer) const; | |
| private: | |
| template <typename U> | |
| friend class SubresourceStorage; | |
| void DecompressAspect(uint32_t aspectIndex); | |
| void RecompressAspect(uint32_t aspectIndex); | |
| void DecompressLayer(uint32_t aspectIndex, uint32_t layer); | |
| void RecompressLayer(uint32_t aspectIndex, uint32_t layer); | |
| SubresourceRange GetFullLayerRange(Aspect aspect, uint32_t layer) const; | |
| // LayerCompressed should never be called when the aspect is compressed otherwise it would | |
| // need to check that mLayerCompressed is not null before indexing it. | |
| bool& LayerCompressed(uint32_t aspectIndex, uint32_t layerIndex); | |
| bool LayerCompressed(uint32_t aspectIndex, uint32_t layerIndex) const; | |
| // Return references to the data for a compressed plane / layer or subresource. | |
| // Each variant should be called exactly under the correct compression level. | |
| T& DataInline(uint32_t aspectIndex); | |
| T& Data(uint32_t aspectIndex, uint32_t layer, uint32_t level = 0); | |
| const T& DataInline(uint32_t aspectIndex) const; | |
| const T& Data(uint32_t aspectIndex, uint32_t layer, uint32_t level = 0) const; | |
| Aspect mAspects; | |
| uint8_t mMipLevelCount; | |
| uint16_t mArrayLayerCount; | |
| // Invariant: if an aspect is marked compressed, then all it's layers are marked as | |
| // compressed. | |
| static constexpr size_t kMaxAspects = 2; | |
| std::array<bool, kMaxAspects> mAspectCompressed; | |
| std::array<T, kMaxAspects> mInlineAspectData; | |
| // Indexed as mLayerCompressed[aspectIndex * mArrayLayerCount + layer]. | |
| std::unique_ptr<bool[]> mLayerCompressed; | |
| // Indexed as mData[(aspectIndex * mArrayLayerCount + layer) * mMipLevelCount + level]. | |
| // The data for a compressed aspect is stored in the slot for (aspect, 0, 0). Similarly | |
| // the data for a compressed layer of aspect if in the slot for (aspect, layer, 0). | |
| std::unique_ptr<T[]> mData; | |
| }; | |
| template <typename T> | |
| SubresourceStorage<T>::SubresourceStorage(Aspect aspects, | |
| uint32_t arrayLayerCount, | |
| uint32_t mipLevelCount, | |
| T initialValue) | |
| : mAspects(aspects), mMipLevelCount(mipLevelCount), mArrayLayerCount(arrayLayerCount) { | |
| ASSERT(arrayLayerCount <= std::numeric_limits<decltype(mArrayLayerCount)>::max()); | |
| ASSERT(mipLevelCount <= std::numeric_limits<decltype(mMipLevelCount)>::max()); | |
| uint32_t aspectCount = GetAspectCount(aspects); | |
| ASSERT(aspectCount <= kMaxAspects); | |
| for (uint32_t aspectIndex = 0; aspectIndex < aspectCount; aspectIndex++) { | |
| mAspectCompressed[aspectIndex] = true; | |
| DataInline(aspectIndex) = initialValue; | |
| } | |
| } | |
| template <typename T> | |
| template <typename F> | |
| void SubresourceStorage<T>::Update(const SubresourceRange& range, F&& updateFunc) { | |
| bool fullLayers = range.baseMipLevel == 0 && range.levelCount == mMipLevelCount; | |
| bool fullAspects = | |
| range.baseArrayLayer == 0 && range.layerCount == mArrayLayerCount && fullLayers; | |
| for (Aspect aspect : IterateEnumMask(range.aspects)) { | |
| uint32_t aspectIndex = GetAspectIndex(aspect); | |
| // Call the updateFunc once for the whole aspect if possible or decompress and fallback | |
| // to per-layer handling. | |
| if (mAspectCompressed[aspectIndex]) { | |
| if (fullAspects) { | |
| SubresourceRange updateRange = | |
| SubresourceRange::MakeFull(aspect, mArrayLayerCount, mMipLevelCount); | |
| updateFunc(updateRange, &DataInline(aspectIndex)); | |
| continue; | |
| } | |
| DecompressAspect(aspectIndex); | |
| } | |
| uint32_t layerEnd = range.baseArrayLayer + range.layerCount; | |
| for (uint32_t layer = range.baseArrayLayer; layer < layerEnd; layer++) { | |
| // Call the updateFunc once for the whole layer if possible or decompress and | |
| // fallback to per-level handling. | |
| if (LayerCompressed(aspectIndex, layer)) { | |
| if (fullLayers) { | |
| SubresourceRange updateRange = GetFullLayerRange(aspect, layer); | |
| updateFunc(updateRange, &Data(aspectIndex, layer)); | |
| continue; | |
| } | |
| DecompressLayer(aspectIndex, layer); | |
| } | |
| // Worst case: call updateFunc per level. | |
| uint32_t levelEnd = range.baseMipLevel + range.levelCount; | |
| for (uint32_t level = range.baseMipLevel; level < levelEnd; level++) { | |
| SubresourceRange updateRange = | |
| SubresourceRange::MakeSingle(aspect, layer, level); | |
| updateFunc(updateRange, &Data(aspectIndex, layer, level)); | |
| } | |
| // If the range has fullLayers then it is likely we can recompress after the calls | |
| // to updateFunc (this branch is skipped if updateFunc was called for the whole | |
| // layer). | |
| if (fullLayers) { | |
| RecompressLayer(aspectIndex, layer); | |
| } | |
| } | |
| // If the range has fullAspects then it is likely we can recompress after the calls to | |
| // updateFunc (this branch is skipped if updateFunc was called for the whole aspect). | |
| if (fullAspects) { | |
| RecompressAspect(aspectIndex); | |
| } | |
| } | |
| } | |
| template <typename T> | |
| template <typename U, typename F> | |
| void SubresourceStorage<T>::Merge(const SubresourceStorage<U>& other, F&& mergeFunc) { | |
| ASSERT(mAspects == other.mAspects); | |
| ASSERT(mArrayLayerCount == other.mArrayLayerCount); | |
| ASSERT(mMipLevelCount == other.mMipLevelCount); | |
| for (Aspect aspect : IterateEnumMask(mAspects)) { | |
| uint32_t aspectIndex = GetAspectIndex(aspect); | |
| // If the other storage's aspect is compressed we don't need to decompress anything | |
| // in `this` and can just iterate through it, merging with `other`'s constant value for | |
| // the aspect. For code simplicity this can be done with a call to Update(). | |
| if (other.mAspectCompressed[aspectIndex]) { | |
| const U& otherData = other.DataInline(aspectIndex); | |
| Update(SubresourceRange::MakeFull(aspect, mArrayLayerCount, mMipLevelCount), | |
| [&](const SubresourceRange& subrange, T* data) { | |
| mergeFunc(subrange, data, otherData); | |
| }); | |
| continue; | |
| } | |
| // Other doesn't have the aspect compressed so we must do at least per-layer merging. | |
| if (mAspectCompressed[aspectIndex]) { | |
| DecompressAspect(aspectIndex); | |
| } | |
| for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { | |
| // Similarly to above, use a fast path if other's layer is compressed. | |
| if (other.LayerCompressed(aspectIndex, layer)) { | |
| const U& otherData = other.Data(aspectIndex, layer); | |
| Update(GetFullLayerRange(aspect, layer), | |
| [&](const SubresourceRange& subrange, T* data) { | |
| mergeFunc(subrange, data, otherData); | |
| }); | |
| continue; | |
| } | |
| // Sad case, other is decompressed for this layer, do per-level merging. | |
| if (LayerCompressed(aspectIndex, layer)) { | |
| DecompressLayer(aspectIndex, layer); | |
| } | |
| for (uint32_t level = 0; level < mMipLevelCount; level++) { | |
| SubresourceRange updateRange = | |
| SubresourceRange::MakeSingle(aspect, layer, level); | |
| mergeFunc(updateRange, &Data(aspectIndex, layer, level), | |
| other.Data(aspectIndex, layer, level)); | |
| } | |
| RecompressLayer(aspectIndex, layer); | |
| } | |
| RecompressAspect(aspectIndex); | |
| } | |
| } | |
| template <typename T> | |
| template <typename F> | |
| void SubresourceStorage<T>::Iterate(F&& iterateFunc) const { | |
| for (Aspect aspect : IterateEnumMask(mAspects)) { | |
| uint32_t aspectIndex = GetAspectIndex(aspect); | |
| // Fastest path, call iterateFunc on the whole aspect at once. | |
| if (mAspectCompressed[aspectIndex]) { | |
| SubresourceRange range = | |
| SubresourceRange::MakeFull(aspect, mArrayLayerCount, mMipLevelCount); | |
| iterateFunc(range, DataInline(aspectIndex)); | |
| continue; | |
| } | |
| for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { | |
| // Fast path, call iterateFunc on the whole array layer at once. | |
| if (LayerCompressed(aspectIndex, layer)) { | |
| SubresourceRange range = GetFullLayerRange(aspect, layer); | |
| iterateFunc(range, Data(aspectIndex, layer)); | |
| continue; | |
| } | |
| // Slow path, call iterateFunc for each mip level. | |
| for (uint32_t level = 0; level < mMipLevelCount; level++) { | |
| SubresourceRange range = SubresourceRange::MakeSingle(aspect, layer, level); | |
| iterateFunc(range, Data(aspectIndex, layer, level)); | |
| } | |
| } | |
| } | |
| } | |
| template <typename T> | |
| const T& SubresourceStorage<T>::Get(Aspect aspect, | |
| uint32_t arrayLayer, | |
| uint32_t mipLevel) const { | |
| uint32_t aspectIndex = GetAspectIndex(aspect); | |
| ASSERT(aspectIndex < GetAspectCount(mAspects)); | |
| ASSERT(arrayLayer < mArrayLayerCount); | |
| ASSERT(mipLevel < mMipLevelCount); | |
| // Fastest path, the aspect is compressed! | |
| if (mAspectCompressed[aspectIndex]) { | |
| return DataInline(aspectIndex); | |
| } | |
| // Fast path, the array layer is compressed. | |
| if (LayerCompressed(aspectIndex, arrayLayer)) { | |
| return Data(aspectIndex, arrayLayer); | |
| } | |
| return Data(aspectIndex, arrayLayer, mipLevel); | |
| } | |
| template <typename T> | |
| Aspect SubresourceStorage<T>::GetAspectsForTesting() const { | |
| return mAspects; | |
| } | |
| template <typename T> | |
| uint32_t SubresourceStorage<T>::GetArrayLayerCountForTesting() const { | |
| return mArrayLayerCount; | |
| } | |
| template <typename T> | |
| uint32_t SubresourceStorage<T>::GetMipLevelCountForTesting() const { | |
| return mMipLevelCount; | |
| } | |
| template <typename T> | |
| bool SubresourceStorage<T>::IsAspectCompressedForTesting(Aspect aspect) const { | |
| return mAspectCompressed[GetAspectIndex(aspect)]; | |
| } | |
| template <typename T> | |
| bool SubresourceStorage<T>::IsLayerCompressedForTesting(Aspect aspect, uint32_t layer) const { | |
| return mAspectCompressed[GetAspectIndex(aspect)] || | |
| mLayerCompressed[GetAspectIndex(aspect) * mArrayLayerCount + layer]; | |
| } | |
| template <typename T> | |
| void SubresourceStorage<T>::DecompressAspect(uint32_t aspectIndex) { | |
| ASSERT(mAspectCompressed[aspectIndex]); | |
| const T& aspectData = DataInline(aspectIndex); | |
| mAspectCompressed[aspectIndex] = false; | |
| // Extra allocations are only needed when aspects are decompressed. Create them lazily. | |
| if (mData == nullptr) { | |
| ASSERT(mLayerCompressed == nullptr); | |
| uint32_t aspectCount = GetAspectCount(mAspects); | |
| mLayerCompressed = std::make_unique<bool[]>(aspectCount * mArrayLayerCount); | |
| mData = std::make_unique<T[]>(aspectCount * mArrayLayerCount * mMipLevelCount); | |
| for (uint32_t layerIndex = 0; layerIndex < aspectCount * mArrayLayerCount; | |
| layerIndex++) { | |
| mLayerCompressed[layerIndex] = true; | |
| } | |
| } | |
| ASSERT(LayerCompressed(aspectIndex, 0)); | |
| for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { | |
| Data(aspectIndex, layer) = aspectData; | |
| ASSERT(LayerCompressed(aspectIndex, layer)); | |
| } | |
| } | |
| template <typename T> | |
| void SubresourceStorage<T>::RecompressAspect(uint32_t aspectIndex) { | |
| ASSERT(!mAspectCompressed[aspectIndex]); | |
| // All layers of the aspect must be compressed for the aspect to possibly recompress. | |
| for (uint32_t layer = 0; layer < mArrayLayerCount; layer++) { | |
| if (!LayerCompressed(aspectIndex, layer)) { | |
| return; | |
| } | |
| } | |
| T layer0Data = Data(aspectIndex, 0); | |
| for (uint32_t layer = 1; layer < mArrayLayerCount; layer++) { | |
| if (!(Data(aspectIndex, layer) == layer0Data)) { | |
| return; | |
| } | |
| } | |
| mAspectCompressed[aspectIndex] = true; | |
| DataInline(aspectIndex) = layer0Data; | |
| } | |
| template <typename T> | |
| void SubresourceStorage<T>::DecompressLayer(uint32_t aspectIndex, uint32_t layer) { | |
| ASSERT(LayerCompressed(aspectIndex, layer)); | |
| ASSERT(!mAspectCompressed[aspectIndex]); | |
| const T& layerData = Data(aspectIndex, layer); | |
| LayerCompressed(aspectIndex, layer) = false; | |
| // We assume that (aspect, layer, 0) is stored at the same place as (aspect, layer) which | |
| // allows starting the iteration at level 1. | |
| for (uint32_t level = 1; level < mMipLevelCount; level++) { | |
| Data(aspectIndex, layer, level) = layerData; | |
| } | |
| } | |
| template <typename T> | |
| void SubresourceStorage<T>::RecompressLayer(uint32_t aspectIndex, uint32_t layer) { | |
| ASSERT(!LayerCompressed(aspectIndex, layer)); | |
| ASSERT(!mAspectCompressed[aspectIndex]); | |
| const T& level0Data = Data(aspectIndex, layer, 0); | |
| for (uint32_t level = 1; level < mMipLevelCount; level++) { | |
| if (!(Data(aspectIndex, layer, level) == level0Data)) { | |
| return; | |
| } | |
| } | |
| LayerCompressed(aspectIndex, layer) = true; | |
| } | |
| template <typename T> | |
| SubresourceRange SubresourceStorage<T>::GetFullLayerRange(Aspect aspect, uint32_t layer) const { | |
| return {aspect, {layer, 1}, {0, mMipLevelCount}}; | |
| } | |
| template <typename T> | |
| bool& SubresourceStorage<T>::LayerCompressed(uint32_t aspectIndex, uint32_t layer) { | |
| ASSERT(!mAspectCompressed[aspectIndex]); | |
| return mLayerCompressed[aspectIndex * mArrayLayerCount + layer]; | |
| } | |
| template <typename T> | |
| bool SubresourceStorage<T>::LayerCompressed(uint32_t aspectIndex, uint32_t layer) const { | |
| ASSERT(!mAspectCompressed[aspectIndex]); | |
| return mLayerCompressed[aspectIndex * mArrayLayerCount + layer]; | |
| } | |
| template <typename T> | |
| T& SubresourceStorage<T>::DataInline(uint32_t aspectIndex) { | |
| ASSERT(mAspectCompressed[aspectIndex]); | |
| return mInlineAspectData[aspectIndex]; | |
| } | |
| template <typename T> | |
| T& SubresourceStorage<T>::Data(uint32_t aspectIndex, uint32_t layer, uint32_t level) { | |
| ASSERT(level == 0 || !LayerCompressed(aspectIndex, layer)); | |
| ASSERT(!mAspectCompressed[aspectIndex]); | |
| return mData[(aspectIndex * mArrayLayerCount + layer) * mMipLevelCount + level]; | |
| } | |
| template <typename T> | |
| const T& SubresourceStorage<T>::DataInline(uint32_t aspectIndex) const { | |
| ASSERT(mAspectCompressed[aspectIndex]); | |
| return mInlineAspectData[aspectIndex]; | |
| } | |
| template <typename T> | |
| const T& SubresourceStorage<T>::Data(uint32_t aspectIndex, | |
| uint32_t layer, | |
| uint32_t level) const { | |
| ASSERT(level == 0 || !LayerCompressed(aspectIndex, layer)); | |
| ASSERT(!mAspectCompressed[aspectIndex]); | |
| return mData[(aspectIndex * mArrayLayerCount + layer) * mMipLevelCount + level]; | |
| } | |
| } // namespace dawn_native | |
| #endif // DAWNNATIVE_SUBRESOURCESTORAGE_H_ |