xref: /aosp_15_r20/external/skia/src/gpu/graphite/PipelineData.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef skgpu_graphite_PipelineData_DEFINED
9 #define skgpu_graphite_PipelineData_DEFINED
10 
11 #include "include/core/SkM44.h"
12 #include "include/core/SkPoint.h"
13 #include "include/core/SkRefCnt.h"
14 #include "include/core/SkSamplingOptions.h"
15 #include "include/core/SkSpan.h"
16 #include "include/core/SkTileMode.h"
17 #include "include/private/SkColorData.h"
18 #include "include/private/base/SkTArray.h"
19 #include "src/base/SkArenaAlloc.h"
20 #include "src/core/SkTHash.h"
21 #include "src/gpu/graphite/Caps.h"
22 #include "src/gpu/graphite/DrawList.h"
23 #include "src/gpu/graphite/DrawTypes.h"
24 #include "src/gpu/graphite/TextureProxy.h"
25 #include "src/gpu/graphite/UniformManager.h"
26 #include "src/shaders/gradients/SkGradientBaseShader.h"
27 
28 namespace skgpu::graphite {
29 
30 class Uniform;
31 
32 /**
33  * Wraps an SkSpan<const char> and provides ==/!= operators and a hash function based on the
34  * bit contents of the spans. It is assumed that the bytes are aligned to match some uniform
35  * interface declaration that will consume this data once it's copied to the GPU.
36  */
37 class UniformDataBlock {
38 public:
39     constexpr UniformDataBlock(const UniformDataBlock&) = default;
40     constexpr UniformDataBlock() = default;
41 
Make(UniformDataBlock toClone,SkArenaAlloc * arena)42     static UniformDataBlock Make(UniformDataBlock toClone, SkArenaAlloc* arena) {
43         const char* copy = arena->makeArrayCopy<char>(toClone.fData);
44         return UniformDataBlock(SkSpan(copy, toClone.size()));
45     }
46 
47     // Wraps the finished accumulated uniform data within the manager's underlying storage.
Wrap(UniformManager * uniforms)48     static UniformDataBlock Wrap(UniformManager* uniforms) {
49         return UniformDataBlock(uniforms->finish());
50     }
51 
52     constexpr UniformDataBlock& operator=(const UniformDataBlock&) = default;
53 
54     explicit operator bool() const { return !this->empty(); }
empty()55     bool empty() const { return fData.empty(); }
56 
data()57     const char* data() const { return fData.data(); }
size()58     size_t size() const { return fData.size(); }
59 
60     bool operator==(UniformDataBlock that) const {
61         return this->size() == that.size() &&
62                (this->data() == that.data() || // Shortcuts the memcmp if the spans are the same
63                 memcmp(this->data(), that.data(), this->size()) == 0);
64     }
65     bool operator!=(UniformDataBlock that) const { return !(*this == that); }
66 
67     struct Hash {
operatorHash68         uint32_t operator()(UniformDataBlock block) const {
69             return SkChecksum::Hash32(block.fData.data(), block.fData.size_bytes());
70         }
71     };
72 
73 private:
74     // To ensure that the underlying data is actually aligned properly, UniformDataBlocks can
75     // only be created publicly by copying an existing block or wrapping data accumulated by a
76     // UniformManager (or transitively a PipelineDataGatherer).
UniformDataBlock(SkSpan<const char> data)77     constexpr UniformDataBlock(SkSpan<const char> data) : fData(data) {}
78 
79     SkSpan<const char> fData;
80 };
81 
82 /**
83  * Wraps an SkSpan<const SampledTexture> (list of pairs of TextureProxy and SamplerDesc) and
84  * provides ==/!= operators and a hash function based on the proxy addresses and sampler desc
85  * bit representation.
86  */
87 class TextureDataBlock {
88 public:
89     using SampledTexture = std::pair<sk_sp<TextureProxy>, SamplerDesc>;
90 
91     constexpr TextureDataBlock(const TextureDataBlock&) = default;
92     constexpr TextureDataBlock() = default;
93 
Make(TextureDataBlock toClone,SkArenaAlloc * arena)94     static TextureDataBlock Make(TextureDataBlock toClone, SkArenaAlloc* arena) {
95         SampledTexture* copy = arena->makeArrayCopy<SampledTexture>(toClone.fTextures);
96         return TextureDataBlock(SkSpan(copy, toClone.numTextures()));
97     }
98 
99     // TODO(b/330864257): Once Device::drawCoverageMask() can keep its texture proxy alive without
100     // creating a temporary TextureDataBlock this constructor can go away.
TextureDataBlock(const SampledTexture & texture)101     explicit TextureDataBlock(const SampledTexture& texture) : fTextures(&texture, 1) {}
102 
103     constexpr TextureDataBlock& operator=(const TextureDataBlock&) = default;
104 
105     explicit operator bool() const { return !this->empty(); }
empty()106     bool empty() const { return fTextures.empty(); }
107 
numTextures()108     int numTextures() const { return SkTo<int>(fTextures.size()); }
texture(int index)109     const SampledTexture& texture(int index) const { return fTextures[index]; }
110 
111     bool operator==(TextureDataBlock other) const {
112         if (fTextures.size() != other.fTextures.size()) {
113             return false;
114         }
115         if (fTextures.data() == other.fTextures.data()) {
116             return true; // shortcut for the same span
117         }
118 
119         for (size_t i = 0; i < fTextures.size(); ++i) {
120             if (fTextures[i] != other.fTextures[i]) {
121                 return false;
122             }
123         }
124 
125         return true;
126     }
127     bool operator!=(TextureDataBlock other) const { return !(*this == other);  }
128 
129     struct Hash {
operatorHash130         uint32_t operator()(TextureDataBlock block) const {
131             uint32_t hash = 0;
132 
133             for (auto& d : block.fTextures) {
134                 SamplerDesc samplerKey = std::get<1>(d);
135                 hash = SkChecksum::Hash32(&samplerKey, sizeof(samplerKey), hash);
136 
137                 // Because the lifetime of the TextureDataCache is for just one Recording and the
138                 // TextureDataBlocks hold refs on their proxies, we can just use the proxy's pointer
139                 // for the hash here.
140                 uintptr_t proxy = reinterpret_cast<uintptr_t>(std::get<0>(d).get());
141                 hash = SkChecksum::Hash32(&proxy, sizeof(proxy), hash);
142             }
143 
144             return hash;
145         }
146     };
147 
148 private:
149     friend class PipelineDataGatherer;
150 
151     // Initial TextureDataBlocks must come from a PipelineDataGatherer
TextureDataBlock(SkSpan<const SampledTexture> textures)152     constexpr TextureDataBlock(SkSpan<const SampledTexture> textures) : fTextures(textures) {}
153 
154     SkSpan<const SampledTexture> fTextures;
155 };
156 
157 // Add a block of data to the cache and return a stable pointer to the contents (assuming that a
158 // resettable gatherer had accumulated the input data pointer).
159 //
160 // If an identical block of data is already in the cache, that existing pointer is returned, making
161 // pointer comparison suitable when comparing data blocks retrieved from the cache.
162 //
163 // T must define a Hash struct function, an operator==, and a static Make(T, SkArenaAlloc*)
164 // factory that's used to copy the data into an arena allocation owned by the PipelineDataCache.
165 template<typename T>
166 class PipelineDataCache {
167 public:
168     PipelineDataCache() = default;
169 
insert(T dataBlock)170     T insert(T dataBlock) {
171         const T* existing = fData.find(dataBlock);
172         if (existing) {
173             return *existing;
174         } else {
175             // Need to make a copy of dataBlock into the arena
176             T copy = T::Make(dataBlock, &fArena);
177             fData.add(copy);
178             return copy;
179         }
180     }
181 
182     // The number of unique T objects in the cache
count()183     int count() const {
184         return fData.count();
185     }
186 
187     // Call fn on every item in the set.  You may not mutate anything.
188     template <typename Fn>  // f(T), f(const T&)
foreach(Fn && fn)189     void foreach(Fn&& fn) const {
190         fData.foreach(fn);
191     }
192 
193 private:
194     skia_private::THashSet<T, typename T::Hash> fData;
195     // Holds the data that is pointed to by the span keys in fData
196     SkArenaAlloc fArena{0};
197 };
198 
199 // A TextureDataCache only lives for a single Recording. When a Recording is snapped it is pulled
200 // off of the Recorder and goes with the Recording as a record of the required Textures and
201 // Samplers.
202 using TextureDataCache = PipelineDataCache<TextureDataBlock>;
203 
204 // A UniformDataCache is used to deduplicate uniform data blocks uploaded to uniform / storage
205 // buffers for a DrawPass pipeline.
206 // TODO: This is just a combination of PipelineDataCache and DrawPass's DenseBiMap, ideally we can
207 // merge those two classes rather than defining this new class.
208 class UniformDataCache {
209 public:
210     using Index = uint32_t;
211     static constexpr Index kInvalidIndex{1 << SkNextLog2_portable(DrawList::kMaxRenderSteps)};
212 
213     // Tracks uniform data on the CPU and then its transition to storage in a GPU buffer (UBO or
214     // SSBO).
215     struct Entry {
216         UniformDataBlock fCpuData;
217         BindBufferInfo fBufferBinding;
218 
219         // Can only be initialized with CPU data.
EntryEntry220         Entry(UniformDataBlock cpuData) : fCpuData(cpuData) {}
221     };
222 
223     UniformDataCache() = default;
224 
insert(const UniformDataBlock & dataBlock)225     Index insert(const UniformDataBlock& dataBlock) {
226         Index* index = fDataToIndex.find(dataBlock);
227         if (!index) {
228             // Need to make a copy of dataBlock into the arena
229             UniformDataBlock copy = UniformDataBlock::Make(dataBlock, &fArena);
230             SkASSERT(SkToU32(fIndexToData.size()) < kInvalidIndex);
231             index = fDataToIndex.set(copy, static_cast<Index>(fIndexToData.size()));
232             fIndexToData.push_back(Entry{copy});
233         }
234         return *index;
235     }
236 
lookup(Index index)237     const Entry& lookup(Index index) const {
238         SkASSERT(index < kInvalidIndex);
239         return fIndexToData[index];
240     }
241 
lookup(Index index)242     Entry& lookup(Index index) {
243         SkASSERT(index < kInvalidIndex);
244         return fIndexToData[index];
245     }
246 
247 #if defined(GPU_TEST_UTILS)
count()248     int count() { return fIndexToData.size(); }
249 #endif
250 
251 private:
252     skia_private::THashMap<UniformDataBlock, Index, UniformDataBlock::Hash> fDataToIndex;
253     skia_private::TArray<Entry> fIndexToData;
254 
255     // Holds the de-duplicated data.
256     SkArenaAlloc fArena{0};
257 };
258 
259 // The PipelineDataGatherer is just used to collect information for a given PaintParams object.
260 //   The UniformData is added to a cache and uniquified. Only that unique ID is passed around.
261 //   The TextureData is also added to a cache and uniquified. Only that ID is passed around.
262 
263 // TODO: The current plan for fixing uniform padding is for the PipelineDataGatherer to hold a
264 // persistent uniformManager. A stretch goal for this system would be for this combination
265 // to accumulate all the uniforms and then rearrange them to minimize padding. This would,
266 // obviously, vastly complicate uniform accumulation.
267 class PipelineDataGatherer {
268 public:
PipelineDataGatherer(Layout layout)269     PipelineDataGatherer(Layout layout) : fUniformManager(layout) {}
270 
resetWithNewLayout(Layout layout)271     void resetWithNewLayout(Layout layout) {
272         fUniformManager.resetWithNewLayout(layout);
273         fTextures.clear();
274     }
275 
276 #if defined(SK_DEBUG)
277     // Check that the gatherer has been reset to its initial state prior to collecting new data.
checkReset()278     void checkReset() const {
279         SkASSERT(fTextures.empty());
280         SkASSERT(fUniformManager.isReset());
281     }
282 #endif // SK_DEBUG
283 
add(sk_sp<TextureProxy> proxy,const SamplerDesc & samplerDesc)284     void add(sk_sp<TextureProxy> proxy, const SamplerDesc& samplerDesc) {
285         fTextures.push_back({std::move(proxy), samplerDesc});
286     }
hasTextures()287     bool hasTextures() const { return !fTextures.empty(); }
288 
textureDataBlock()289     TextureDataBlock textureDataBlock() { return TextureDataBlock(SkSpan(fTextures)); }
290 
291     // Mimic the type-safe API available in UniformManager
write(const T & t)292     template <typename T> void write(const T& t) { fUniformManager.write(t); }
writeHalf(const T & t)293     template <typename T> void writeHalf(const T& t) { fUniformManager.writeHalf(t); }
writeArray(SkSpan<const T> t)294     template <typename T> void writeArray(SkSpan<const T> t) { fUniformManager.writeArray(t); }
writeHalfArray(SkSpan<const T> t)295     template <typename T> void writeHalfArray(SkSpan<const T> t) {
296         fUniformManager.writeHalfArray(t);
297     }
298 
write(const Uniform & u,const void * data)299     void write(const Uniform& u, const void* data) { fUniformManager.write(u, data); }
300 
writePaintColor(const SkPMColor4f & color)301     void writePaintColor(const SkPMColor4f& color) { fUniformManager.writePaintColor(color); }
302 
beginStruct(int baseAligment)303     void beginStruct(int baseAligment) { fUniformManager.beginStruct(baseAligment); }
endStruct()304     void endStruct() { fUniformManager.endStruct(); }
305 
hasUniforms()306     bool hasUniforms() const { return fUniformManager.size(); }
307 
hasGradientBufferData()308     bool hasGradientBufferData() const { return !fGradientStorage.empty(); }
309 
gradientBufferData()310     SkSpan<const float> gradientBufferData() const { return fGradientStorage; }
311 
312     // Returns the uniform data written so far. Will automatically pad the end of the data as needed
313     // to the overall required alignment, and so should only be called when all writing is done.
finishUniformDataBlock()314     UniformDataBlock finishUniformDataBlock() { return UniformDataBlock::Wrap(&fUniformManager); }
315 
316     // Checks if data already exists for the requested gradient shader, and returns a nullptr
317     // and the offset the data begins at. If it doesn't exist, it allocates the data for the
318     // required number of stops and caches the start index, returning the data pointer
319     // and index offset the data will begin at.
allocateGradientData(int numStops,const SkGradientBaseShader * shader)320     std::pair<float*, int> allocateGradientData(int numStops, const SkGradientBaseShader* shader) {
321         int* existingOfffset = fGradientOffsetCache.find(shader);
322         if (existingOfffset) {
323             return std::make_pair(nullptr, *existingOfffset);
324         }
325 
326         auto dataPair = this->allocateFloatData(numStops * 5);
327         fGradientOffsetCache.set(shader, dataPair.second);
328 
329         return dataPair;
330     }
331 
332 private:
333     // Allocates the data for the requested number of bytes and returns the
334     // pointer and buffer index offset the data will begin at.
allocateFloatData(int size)335     std::pair<float*, int> allocateFloatData(int size) {
336         int lastSize = fGradientStorage.size();
337         fGradientStorage.resize(lastSize + size);
338         float* startPtr = fGradientStorage.begin() + lastSize;
339 
340         return std::make_pair(startPtr, lastSize);
341     }
342 
343     SkDEBUGCODE(friend class UniformExpectationsValidator;)
344 
345     UniformManager fUniformManager;
346     skia_private::TArray<TextureDataBlock::SampledTexture> fTextures;
347 
348     SkTDArray<float>  fGradientStorage;
349     // Storing the address of the shader as a proxy for comparing
350     // the colors and offsets arrays to keep lookup fast.
351     skia_private::THashMap<const SkGradientBaseShader*, int> fGradientOffsetCache;
352 };
353 
354 #ifdef SK_DEBUG
355 class UniformExpectationsValidator {
356 public:
357     UniformExpectationsValidator(PipelineDataGatherer* gatherer,
358                                  SkSpan<const Uniform> expectedUniforms,
359                                  bool isSubstruct=false)
fGatherer(gatherer)360             : fGatherer(gatherer) {
361         fGatherer->fUniformManager.setExpectedUniforms(expectedUniforms, isSubstruct);
362     }
363 
~UniformExpectationsValidator()364     ~UniformExpectationsValidator() {
365         fGatherer->fUniformManager.doneWithExpectedUniforms();
366     }
367 
368 private:
369     PipelineDataGatherer* fGatherer;
370 
371     UniformExpectationsValidator(UniformExpectationsValidator &&) = delete;
372     UniformExpectationsValidator(const UniformExpectationsValidator &) = delete;
373     UniformExpectationsValidator &operator=(UniformExpectationsValidator &&) = delete;
374     UniformExpectationsValidator &operator=(const UniformExpectationsValidator &) = delete;
375 };
376 #endif // SK_DEBUG
377 
378 } // namespace skgpu::graphite
379 
380 #endif // skgpu_graphite_PipelineData_DEFINED
381