// 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. #ifndef SRC_DAWN_NATIVE_SUBRESOURCESTORAGE_H_ #define SRC_DAWN_NATIVE_SUBRESOURCESTORAGE_H_ #include <array> #include <limits> #include <memory> #include <type_traits> #include <vector> #include "dawn/common/Assert.h" #include "dawn/common/TypeTraits.h" #include "dawn/native/EnumMaskIterator.h" #include "dawn/native/Error.h" #include "dawn/native/Subresource.h" 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/+/main/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 { … }; template <typename T> SubresourceStorage<T>::SubresourceStorage(Aspect aspects, uint32_t arrayLayerCount, uint32_t mipLevelCount, const T& initialValue) : … { … } template <typename T> void SubresourceStorage<T>::Fill(const T& value) { … } template <typename T> template <typename F> void SubresourceStorage<T>::Update(const SubresourceRange& range, F&& updateFunc) { … } template <typename T> template <typename U, typename F> void SubresourceStorage<T>::Merge(const SubresourceStorage<U>& other, F&& mergeFunc) { … } template <typename T> template <typename F, typename R> R SubresourceStorage<T>::Iterate(F&& iterateFunc) const { … } template <typename T> const T& SubresourceStorage<T>::Get(Aspect aspect, uint32_t arrayLayer, uint32_t mipLevel) const { … } template <typename T> Aspect SubresourceStorage<T>::GetAspectsForTesting() const { … } template <typename T> uint32_t SubresourceStorage<T>::GetArrayLayerCountForTesting() const { … } template <typename T> uint32_t SubresourceStorage<T>::GetMipLevelCountForTesting() const { … } template <typename T> bool SubresourceStorage<T>::IsAspectCompressedForTesting(Aspect aspect) const { … } template <typename T> bool SubresourceStorage<T>::IsLayerCompressedForTesting(Aspect aspect, uint32_t layer) const { … } template <typename T> void SubresourceStorage<T>::DecompressAspect(uint32_t aspectIndex) { … } template <typename T> void SubresourceStorage<T>::RecompressAspect(uint32_t aspectIndex) { … } template <typename T> void SubresourceStorage<T>::DecompressLayer(uint32_t aspectIndex, uint32_t layer) { … } template <typename T> void SubresourceStorage<T>::RecompressLayer(uint32_t aspectIndex, uint32_t layer) { … } template <typename T> SubresourceRange SubresourceStorage<T>::GetFullLayerRange(Aspect aspect, uint32_t layer) const { … } template <typename T> bool& SubresourceStorage<T>::LayerCompressed(uint32_t aspectIndex, uint32_t layer) { … } template <typename T> bool SubresourceStorage<T>::LayerCompressed(uint32_t aspectIndex, uint32_t layer) const { … } template <typename T> T& SubresourceStorage<T>::DataInline(uint32_t aspectIndex) { … } template <typename T> T& SubresourceStorage<T>::Data(uint32_t aspectIndex, uint32_t layer, uint32_t level) { … } template <typename T> const T& SubresourceStorage<T>::DataInline(uint32_t aspectIndex) const { … } template <typename T> const T& SubresourceStorage<T>::Data(uint32_t aspectIndex, uint32_t layer, uint32_t level) const { … } } // namespace dawn::native #endif // SRC_DAWN_NATIVE_SUBRESOURCESTORAGE_H_