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