Update rive-cpp to 2.0 version
[platform/core/uifw/rive-tizen.git] / submodule / skia / src / gpu / graphite / DrawPass.cpp
1 /*
2  * Copyright 2021 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 #include "src/gpu/graphite/DrawPass.h"
9
10 #include "include/gpu/graphite/GraphiteTypes.h"
11 #include "include/gpu/graphite/Recorder.h"
12 #include "src/gpu/graphite/Buffer.h"
13 #include "src/gpu/graphite/ContextPriv.h"
14 #include "src/gpu/graphite/ContextUtils.h"
15 #include "src/gpu/graphite/DrawBufferManager.h"
16 #include "src/gpu/graphite/DrawContext.h"
17 #include "src/gpu/graphite/DrawList.h"
18 #include "src/gpu/graphite/DrawWriter.h"
19 #include "src/gpu/graphite/GlobalCache.h"
20 #include "src/gpu/graphite/GraphicsPipeline.h"
21 #include "src/gpu/graphite/GraphicsPipelineDesc.h"
22 #include "src/gpu/graphite/PipelineDataCache.h"
23 #include "src/gpu/graphite/RecorderPriv.h"
24 #include "src/gpu/graphite/Renderer.h"
25 #include "src/gpu/graphite/ResourceProvider.h"
26 #include "src/gpu/graphite/Sampler.h"
27 #include "src/gpu/graphite/Texture.h"
28 #include "src/gpu/graphite/TextureProxy.h"
29 #include "src/gpu/graphite/UniformManager.h"
30 #include "src/gpu/graphite/geom/BoundsManager.h"
31
32 #include "src/core/SkMathPriv.h"
33 #include "src/core/SkPaintParamsKey.h"
34 #include "src/core/SkPipelineData.h"
35 #include "src/core/SkTBlockList.h"
36
37 #include <algorithm>
38 #include <unordered_map>
39
40 namespace skgpu::graphite {
41
42 // Helper to manage packed fields within a uint64_t
43 template <uint64_t Bits, uint64_t Offset>
44 struct Bitfield {
45     static constexpr uint64_t kMask = ((uint64_t) 1 << Bits) - 1;
46     static constexpr uint64_t kOffset = Offset;
47     static constexpr uint64_t kBits = Bits;
48
49     static uint32_t get(uint64_t v) { return static_cast<uint32_t>((v >> kOffset) & kMask); }
50     static uint64_t set(uint32_t v) { return (v & kMask) << kOffset; }
51 };
52
53 /**
54  * Each Draw in a DrawList might be processed by multiple RenderSteps (determined by the Draw's
55  * Renderer), which can be sorted independently. Each (step, draw) pair produces its own SortKey.
56  *
57  * The goal of sorting draws for the DrawPass is to minimize pipeline transitions and dynamic binds
58  * within a pipeline, while still respecting the overall painter's order. This decreases the number
59  * of low-level draw commands in a command buffer and increases the size of those, allowing the GPU
60  * to operate more efficiently and have fewer bubbles within its own instruction stream.
61  *
62  * The Draw's CompresssedPaintersOrder and DisjointStencilINdex represent the most significant bits
63  * of the key, and are shared by all SortKeys produced by the same draw. Next, the pipeline
64  * description is encoded in two steps:
65  *  1. The index of the RenderStep packed in the high bits to ensure each step for a draw is
66  *     ordered correctly.
67  *  2. An index into a cache of pipeline descriptions is used to encode the identity of the
68  *     pipeline (SortKeys that differ in the bits from #1 necessarily would have different
69  *     descriptions, but then the specific ordering of the RenderSteps isn't enforced).
70  * Last, the SortKey encodes an index into the set of uniform bindings accumulated for a DrawPass.
71  * This allows the SortKey to cluster draw steps that have both a compatible pipeline and do not
72  * require rebinding uniform data or other state (e.g. scissor). Since the uniform data index and
73  * the pipeline description index are packed into indices and not actual pointers, a given SortKey
74  * is only valid for the a specific DrawList->DrawPass conversion.
75  */
76 class DrawPass::SortKey {
77 public:
78     SortKey(const DrawList::Draw* draw,
79             int renderStep,
80             uint32_t pipelineIndex,
81             UniformDataCache::Index geomUniformIndex,
82             UniformDataCache::Index shadingUniformIndex,
83             TextureDataCache::Index textureDataIndex)
84         : fPipelineKey(ColorDepthOrderField::set(draw->fGeometry.order().paintOrder().bits()) |
85                        StencilIndexField::set(draw->fGeometry.order().stencilIndex().bits())  |
86                        RenderStepField::set(static_cast<uint32_t>(renderStep))                |
87                        PipelineField::set(pipelineIndex))
88         , fUniformKey(GeometryUniformField::set(geomUniformIndex.asUInt())   |
89                       ShadingUniformField::set(shadingUniformIndex.asUInt()) |
90                       TextureBindingsField::set(textureDataIndex.asUInt()))
91         , fDraw(draw) {
92         SkASSERT(renderStep <= draw->fRenderer.numRenderSteps());
93     }
94
95     bool operator<(const SortKey& k) const {
96         return fPipelineKey < k.fPipelineKey ||
97                (fPipelineKey == k.fPipelineKey && fUniformKey < k.fUniformKey);
98     }
99
100     const RenderStep& renderStep() const {
101         return *fDraw->fRenderer.steps()[RenderStepField::get(fPipelineKey)];
102     }
103
104     const DrawList::Draw* draw() const { return fDraw; }
105
106     uint32_t pipeline() const { return PipelineField::get(fPipelineKey);       }
107     UniformDataCache::Index geometryUniforms() const {
108         return UniformDataCache::Index(GeometryUniformField::get(fUniformKey));
109     }
110     UniformDataCache::Index shadingUniforms() const {
111         return UniformDataCache::Index(ShadingUniformField::get(fUniformKey));
112     }
113     TextureDataCache::Index textureBindings() const {
114         return TextureDataCache::Index(TextureBindingsField::get(fUniformKey));
115     }
116
117 private:
118     // Fields are ordered from most-significant to least when sorting by 128-bit value.
119     // NOTE: We don't use bit fields because field ordering is implementation defined and we need
120     // to sort consistently.
121     using ColorDepthOrderField = Bitfield<16, 48>; // sizeof(CompressedPaintersOrder)
122     using StencilIndexField    = Bitfield<16, 32>; // sizeof(DisjointStencilIndex)
123     using RenderStepField      = Bitfield<2,  30>; // bits >= log2(Renderer::kMaxRenderSteps)
124     using PipelineField        = Bitfield<30, 0>;  // bits >= log2(max steps*DrawList::kMaxDraws)
125     uint64_t fPipelineKey;
126
127     using GeometryUniformField = Bitfield<22, 42>; // bits >= log2(max steps * max draw count)
128     using ShadingUniformField  = Bitfield<21, 21>; //  ""
129     using TextureBindingsField = Bitfield<21, 0>;  //  ""
130     uint64_t fUniformKey;
131
132     // Backpointer to the draw that produced the sort key
133     const DrawList::Draw* fDraw;
134
135     static_assert(ColorDepthOrderField::kBits >= sizeof(CompressedPaintersOrder));
136     static_assert(StencilIndexField::kBits    >= sizeof(DisjointStencilIndex));
137     static_assert(RenderStepField::kBits      >= SkNextLog2_portable(Renderer::kMaxRenderSteps));
138     static_assert(PipelineField::kBits        >=
139                           SkNextLog2_portable(Renderer::kMaxRenderSteps * DrawList::kMaxDraws));
140     static_assert(GeometryUniformField::kBits >=
141                           SkNextLog2_portable(Renderer::kMaxRenderSteps * DrawList::kMaxDraws));
142     static_assert(ShadingUniformField::kBits  >=
143                           SkNextLog2_portable(Renderer::kMaxRenderSteps * DrawList::kMaxDraws));
144     static_assert(TextureBindingsField::kBits >=
145                           SkNextLog2_portable(Renderer::kMaxRenderSteps * DrawList::kMaxDraws));
146 };
147
148 class DrawPass::Drawer final : public DrawDispatcher {
149 public:
150     Drawer(DrawPass* drawPass) : fPass(drawPass) {}
151     ~Drawer() override = default;
152
153     void bindDrawBuffers(BindBufferInfo vertexAttribs,
154                          BindBufferInfo instanceAttribs,
155                          BindBufferInfo indices) override {
156         fPass->fCommands.emplace_back(BindDrawBuffers{vertexAttribs, instanceAttribs, indices});
157     }
158
159     void draw(PrimitiveType type, unsigned int baseVertex, unsigned int vertexCount) override {
160         fPass->fCommands.emplace_back(Draw{type, baseVertex, vertexCount});
161     }
162
163     void drawIndexed(PrimitiveType type, unsigned int baseIndex,
164                      unsigned int indexCount, unsigned int baseVertex) override {
165         fPass->fCommands.emplace_back(DrawIndexed{type, baseIndex, indexCount, baseVertex});
166     }
167
168     void drawInstanced(PrimitiveType type,
169                        unsigned int baseVertex, unsigned int vertexCount,
170                        unsigned int baseInstance, unsigned int instanceCount) override {
171         fPass->fCommands.emplace_back(DrawInstanced{type, baseVertex, vertexCount,
172                                                     baseInstance, instanceCount});
173     }
174
175     void drawIndexedInstanced(PrimitiveType type,
176                               unsigned int baseIndex, unsigned int indexCount,
177                               unsigned int baseVertex, unsigned int baseInstance,
178                               unsigned int instanceCount) override {
179         fPass->fCommands.emplace_back(DrawIndexedInstanced{type, baseIndex, indexCount, baseVertex,
180                                                            baseInstance, instanceCount});
181     }
182
183 private:
184     DrawPass* fPass;
185 };
186
187 ///////////////////////////////////////////////////////////////////////////////////////////////////
188
189 namespace {
190
191 class UniformBindingCache {
192 public:
193     UniformBindingCache(DrawBufferManager* bufferMgr, UniformDataCache* uniformDataCache)
194             : fBufferMgr(bufferMgr)
195             , fUniformDataCache(uniformDataCache) {
196     }
197
198     UniformDataCache::Index addUniforms(UniformDataCache::Index uIndex) {
199         if (!uIndex.isValid()) {
200             return {};
201         }
202
203         const SkUniformDataBlock* udb = fUniformDataCache->lookup(uIndex);
204         SkASSERT(udb);
205
206         if (fBindings.find(uIndex.asUInt()) == fBindings.end()) {
207             // First time encountering this data, so upload to the GPU
208             SkASSERT(udb->size());
209             auto[writer, bufferInfo] = fBufferMgr->getUniformWriter(udb->size());
210             writer.write(udb->data(), udb->size());
211
212             fBindings.insert({uIndex.asUInt(), bufferInfo});
213         }
214
215         return uIndex;
216     }
217
218     BindBufferInfo getBinding(UniformDataCache::Index uniformDataIndex) {
219         auto lookup = fBindings.find(uniformDataIndex.asUInt());
220         SkASSERT(lookup != fBindings.end());
221         return lookup->second;
222     }
223
224 private:
225     DrawBufferManager* fBufferMgr;
226     UniformDataCache* fUniformDataCache;
227
228     std::unordered_map<uint32_t, BindBufferInfo> fBindings;
229 };
230
231 // std::unordered_map implementation for GraphicsPipelineDesc* that de-reference the pointers.
232 struct Hash {
233     size_t operator()(const GraphicsPipelineDesc* desc) const noexcept {
234         return GraphicsPipelineDesc::Hash()(*desc);
235     }
236 };
237
238 struct Eq {
239     bool operator()(const GraphicsPipelineDesc* a,
240                     const GraphicsPipelineDesc* b) const noexcept {
241         return *a == *b;
242     }
243 };
244
245 } // anonymous namespace
246
247 DrawPass::DrawPass(sk_sp<TextureProxy> target,
248                    std::pair<LoadOp, StoreOp> ops,
249                    std::array<float, 4> clearColor,
250                    int renderStepCount)
251         : fCommands(std::max(1, renderStepCount / 4), SkBlockAllocator::GrowthPolicy::kFibonacci)
252         , fTarget(std::move(target))
253         , fBounds(SkIRect::MakeEmpty())
254         , fOps(ops)
255         , fClearColor(clearColor) {
256     // TODO: Tune this estimate and the above "itemPerBlock" value for the command buffer sequence
257     // After merging, etc. one pipeline per recorded draw+step combo is likely unnecessary.
258     fPipelineDescs.reserve(renderStepCount);
259     fCommands.reserve(renderStepCount);
260 }
261
262 DrawPass::~DrawPass() = default;
263
264 std::unique_ptr<DrawPass> DrawPass::Make(Recorder* recorder,
265                                          std::unique_ptr<DrawList> draws,
266                                          sk_sp<TextureProxy> target,
267                                          std::pair<LoadOp, StoreOp> ops,
268                                          std::array<float, 4> clearColor,
269                                          const BoundsManager* occlusionCuller) {
270     // NOTE: This assert is here to ensure SortKey is as tightly packed as possible. Any change to
271     // its size should be done with care and good reason. The performance of sorting the keys is
272     // heavily tied to the total size.
273     //
274     // At 24 bytes (current), sorting is about 30% slower than if SortKey could be packed into just
275     // 16 bytes. There are several ways this could be done if necessary:
276     //  - Restricting the max draw count to 16k (14-bits) and only using a single index to refer to
277     //    the uniform data => 8 bytes of key, 8 bytes of pointer.
278     //  - Restrict the max draw count to 32k (15-bits), use a single uniform index, and steal the
279     //    4 low bits from the Draw* pointer since it's 16 byte aligned.
280     //  - Compact the Draw* to an index into the original collection, although that has extra
281     //    indirection and does not work as well with SkTBlockList.
282     // In pseudo tests, manipulating the pointer or having to mask out indices was about 15% slower
283     // than an 8 byte key and unmodified pointer.
284     static_assert(sizeof(DrawPass::SortKey) == 16 + sizeof(void*));
285
286     // The DrawList is converted directly into the DrawPass' data structures, but once the DrawPass
287     // is returned from Make(), it is considered immutable.
288     std::unique_ptr<DrawPass> drawPass(new DrawPass(std::move(target), ops, clearColor,
289                                                     draws->renderStepCount()));
290
291     Rect passBounds = Rect::InfiniteInverted();
292
293     DrawBufferManager* bufferMgr = recorder->priv().drawBufferManager();
294
295     // We don't expect the uniforms from the renderSteps to reappear multiple times across a
296     // recorder's lifetime so we only de-dupe them w/in a given DrawPass.
297     UniformDataCache geometryUniformDataCache;
298     UniformBindingCache geometryUniformBindings(bufferMgr, &geometryUniformDataCache);
299     UniformBindingCache shadingUniformBindings(bufferMgr, recorder->priv().uniformDataCache());
300     TextureDataCache* textureDataCache = recorder->priv().textureDataCache();
301
302     std::unordered_map<const GraphicsPipelineDesc*, uint32_t, Hash, Eq> pipelineDescToIndex;
303
304     std::vector<SortKey> keys;
305     keys.reserve(draws->renderStepCount()); // will not exceed but may use less with occluded draws
306
307     SkShaderCodeDictionary* dict = recorder->priv().resourceProvider()->shaderCodeDictionary();
308     SkPaintParamsKeyBuilder builder(dict, SkBackend::kGraphite);
309     SkPipelineDataGatherer gatherer(Layout::kMetal);  // TODO: get the layout from the recorder
310
311     for (const DrawList::Draw& draw : draws->fDraws.items()) {
312         if (occlusionCuller && occlusionCuller->isOccluded(draw.fGeometry.clip().drawBounds(),
313                                                            draw.fGeometry.order().depth())) {
314             continue;
315         }
316
317         // If we have two different descriptors, such that the uniforms from the PaintParams can be
318         // bound independently of those used by the rest of the RenderStep, then we can upload now
319         // and remember the location for re-use on any RenderStep that does shading.
320         SkUniquePaintParamsID shaderID;
321         UniformDataCache::Index shadingUniformIndex;
322         TextureDataCache::Index textureBindingIndex;
323         if (draw.fPaintParams.has_value()) {
324             UniformDataCache::Index uniformDataIndex;
325             std::tie(shaderID, uniformDataIndex, textureBindingIndex) =
326                     ExtractPaintData(recorder, &gatherer, &builder,
327                                      draw.fGeometry.transform().inverse(),
328                                      draw.fPaintParams.value());
329             shadingUniformIndex = shadingUniformBindings.addUniforms(uniformDataIndex);
330         } // else depth-only
331
332         for (int stepIndex = 0; stepIndex < draw.fRenderer.numRenderSteps(); ++stepIndex) {
333             const RenderStep* const step = draw.fRenderer.steps()[stepIndex];
334             const bool performsShading = draw.fPaintParams.has_value() && step->performsShading();
335
336             SkUniquePaintParamsID stepShaderID;
337             UniformDataCache::Index stepShadingUniformIndex;
338             TextureDataCache::Index stepTextureBindingIndex;
339             if (performsShading) {
340                 stepShaderID = shaderID;
341                 stepShadingUniformIndex = shadingUniformIndex;
342                 stepTextureBindingIndex = textureBindingIndex;
343             } // else depth-only draw or stencil-only step of renderer so no shading is needed
344
345             UniformDataCache::Index geometryUniformIndex;
346             if (step->numUniforms() > 0) {
347                 UniformDataCache::Index uniformDataIndex;
348                 uniformDataIndex = ExtractRenderStepData(&geometryUniformDataCache,
349                                                          &gatherer,
350                                                          step,
351                                                          draw.fGeometry);
352                 geometryUniformIndex = geometryUniformBindings.addUniforms(uniformDataIndex);
353             }
354
355             GraphicsPipelineDesc desc;
356             desc.setProgram(step, stepShaderID);
357             uint32_t pipelineIndex = 0;
358             auto pipelineLookup = pipelineDescToIndex.find(&desc);
359             if (pipelineLookup == pipelineDescToIndex.end()) {
360                 // Assign new index to first appearance of this pipeline description
361                 pipelineIndex = SkTo<uint32_t>(drawPass->fPipelineDescs.count());
362                 const GraphicsPipelineDesc& finalDesc = drawPass->fPipelineDescs.push_back(desc);
363                 pipelineDescToIndex.insert({&finalDesc, pipelineIndex});
364             } else {
365                 // Reuse the existing pipeline description for better batching after sorting
366                 pipelineIndex = pipelineLookup->second;
367             }
368
369             keys.push_back({&draw, stepIndex, pipelineIndex,
370                             geometryUniformIndex,
371                             stepShadingUniformIndex,
372                             stepTextureBindingIndex});
373         }
374
375         passBounds.join(draw.fGeometry.clip().drawBounds());
376         drawPass->fDepthStencilFlags |= draw.fRenderer.depthStencilFlags();
377         drawPass->fRequiresMSAA |= draw.fRenderer.requiresMSAA();
378     }
379
380     // TODO: Explore sorting algorithms; in all likelihood this will be mostly sorted already, so
381     // algorithms that approach O(n) in that condition may be favorable. Alternatively, could
382     // explore radix sort that is always O(n). Brief testing suggested std::sort was faster than
383     // std::stable_sort and SkTQSort on my [ml]'s Windows desktop. Also worth considering in-place
384     // vs. algorithms that require an extra O(n) storage.
385     // TODO: It's not strictly necessary, but would a stable sort be useful or just end up hiding
386     // bugs in the DrawOrder determination code?
387     std::sort(keys.begin(), keys.end());
388
389     // Used to record vertex/instance data, buffer binds, and draw calls
390     Drawer drawer(drawPass.get());
391     DrawWriter drawWriter(&drawer, bufferMgr);
392
393     // Used to track when a new pipeline or dynamic state needs recording between draw steps.
394     // Setting to # render steps ensures the very first time through the loop will bind a pipeline.
395     uint32_t lastPipeline = draws->renderStepCount();
396     UniformDataCache::Index lastShadingUniforms;
397     TextureDataCache::Index lastTextureBindings;
398     UniformDataCache::Index lastGeometryUniforms;
399     SkIRect lastScissor = SkIRect::MakeSize(drawPass->fTarget->dimensions());
400
401     for (const SortKey& key : keys) {
402         const DrawList::Draw& draw = *key.draw();
403         const RenderStep& renderStep = key.renderStep();
404
405         const bool geometryUniformChange = key.geometryUniforms().isValid() &&
406                                            key.geometryUniforms() != lastGeometryUniforms;
407         const bool shadingUniformChange = key.shadingUniforms().isValid() &&
408                                           key.shadingUniforms() != lastShadingUniforms;
409         const bool textureBindingsChange = key.textureBindings().isValid() &&
410                                            key.textureBindings() != lastTextureBindings;
411
412         const bool pipelineChange = key.pipeline() != lastPipeline;
413         const bool stateChange = geometryUniformChange ||
414                                  shadingUniformChange ||
415                                  textureBindingsChange ||
416                                  draw.fGeometry.clip().scissor() != lastScissor;
417
418         // Update DrawWriter *before* we actually change any state so that accumulated draws from
419         // the previous state use the proper state.
420         if (pipelineChange) {
421             drawWriter.newPipelineState(renderStep.primitiveType(),
422                                         renderStep.vertexStride(),
423                                         renderStep.instanceStride());
424         } else if (stateChange) {
425             drawWriter.newDynamicState();
426         }
427
428         // Make state changes before accumulating new draw data
429         if (pipelineChange) {
430             drawPass->fCommands.emplace_back(BindGraphicsPipeline{key.pipeline()});
431             lastPipeline = key.pipeline();
432         }
433         if (stateChange) {
434             if (geometryUniformChange) {
435                 auto binding = geometryUniformBindings.getBinding(key.geometryUniforms());
436                 drawPass->fCommands.emplace_back(
437                         BindUniformBuffer{binding, UniformSlot::kRenderStep});
438                 lastGeometryUniforms = key.geometryUniforms();
439             }
440             if (shadingUniformChange) {
441                 auto binding = shadingUniformBindings.getBinding(key.shadingUniforms());
442                 drawPass->fCommands.emplace_back(
443                         BindUniformBuffer{binding, UniformSlot::kPaint});
444                 lastShadingUniforms = key.shadingUniforms();
445             }
446             if (textureBindingsChange) {
447                 auto textureDataBlock = textureDataCache->lookup(key.textureBindings());
448                 drawPass->fCommands.emplace_back(BindTexturesAndSamplers{textureDataBlock});
449                 lastTextureBindings = key.textureBindings();
450             }
451             if (draw.fGeometry.clip().scissor() != lastScissor) {
452                 drawPass->fCommands.emplace_back(SetScissor{draw.fGeometry.clip().scissor()});
453                 lastScissor = draw.fGeometry.clip().scissor();
454             }
455         }
456
457         renderStep.writeVertices(&drawWriter, draw.fGeometry);
458     }
459     // Finish recording draw calls for any collected data at the end of the loop
460     drawWriter.flush();
461
462     passBounds.roundOut();
463     drawPass->fBounds = SkIRect::MakeLTRB((int) passBounds.left(), (int) passBounds.top(),
464                                           (int) passBounds.right(), (int) passBounds.bot());
465     return drawPass;
466 }
467
468 bool DrawPass::addCommands(ResourceProvider* resourceProvider,
469                            CommandBuffer* buffer,
470                            const RenderPassDesc& renderPassDesc) const {
471     // TODO: Validate RenderPass state against DrawPass's target and requirements?
472     // Generate actual GraphicsPipeline objects combining the target-level properties and each of
473     // the GraphicsPipelineDesc's referenced in this DrawPass.
474
475     // Use a vector instead of SkTBlockList for the full pipelines so that random access is fast.
476     std::vector<sk_sp<GraphicsPipeline>> fullPipelines;
477     fullPipelines.reserve(fPipelineDescs.count());
478     for (const GraphicsPipelineDesc& pipelineDesc : fPipelineDescs.items()) {
479         fullPipelines.push_back(resourceProvider->findOrCreateGraphicsPipeline(pipelineDesc,
480                                                                                renderPassDesc));
481     }
482
483     // Set viewport to the entire texture for now (eventually, we may have logically smaller bounds
484     // within an approx-sized texture). It is assumed that this also configures the sk_rtAdjust
485     // intrinsic for programs (however the backend chooses to do so).
486     buffer->setViewport(0, 0, fTarget->dimensions().width(), fTarget->dimensions().height());
487
488     for (const Command& c : fCommands.items()) {
489         switch(c.fType) {
490             case CommandType::kBindGraphicsPipeline: {
491                 auto& d = c.fBindGraphicsPipeline;
492                 buffer->bindGraphicsPipeline(fullPipelines[d.fPipelineIndex]);
493             } break;
494             case CommandType::kBindUniformBuffer: {
495                 auto& d = c.fBindUniformBuffer;
496                 buffer->bindUniformBuffer(d.fSlot, sk_ref_sp(d.fInfo.fBuffer), d.fInfo.fOffset);
497             } break;
498             case CommandType::kBindTexturesAndSamplers: {
499                 auto& d = c.fBindTexturesAndSamplers;
500
501                 for (int i = 0; i < d.fTextureBlock->numTextures(); ++i) {
502                     const auto &texture = d.fTextureBlock->texture(i);
503                     if (!texture.fProxy->texture()) {
504                         return false;
505                     }
506
507                     sk_sp<Sampler> sampler = resourceProvider->findOrCreateCompatibleSampler(
508                             texture.fSamplingOptions, texture.fTileModes[0], texture.fTileModes[1]);
509                     SkASSERT(sampler);
510
511                     buffer->bindTextureAndSampler(texture.fProxy->refTexture(),
512                                                   std::move(sampler),
513                                                   i);
514                 }
515
516             } break;
517             case CommandType::kBindDrawBuffers: {
518                 auto& d = c.fBindDrawBuffers;
519                 buffer->bindDrawBuffers(d.fVertices, d.fInstances, d.fIndices);
520                 break; }
521             case CommandType::kDraw: {
522                 auto& d = c.fDraw;
523                 buffer->draw(d.fType, d.fBaseVertex, d.fVertexCount);
524                 break; }
525             case CommandType::kDrawIndexed: {
526                 auto& d = c.fDrawIndexed;
527                 buffer->drawIndexed(d.fType, d.fBaseIndex, d.fIndexCount, d.fBaseVertex);
528                 break; }
529             case CommandType::kDrawInstanced: {
530                 auto& d = c.fDrawInstanced;
531                 buffer->drawInstanced(d.fType, d.fBaseVertex, d.fVertexCount,
532                                       d.fBaseInstance, d.fInstanceCount);
533                 break; }
534             case CommandType::kDrawIndexedInstanced: {
535                 auto& d = c.fDrawIndexedInstanced;
536                 buffer->drawIndexedInstanced(d.fType, d.fBaseIndex, d.fIndexCount, d.fBaseVertex,
537                                              d.fBaseInstance, d.fInstanceCount);
538                 break; }
539             case CommandType::kSetScissor: {
540                 auto& d = c.fSetScissor;
541                 buffer->setScissor(d.fScissor.fLeft, d.fScissor.fTop,
542                                    d.fScissor.width(), d.fScissor.height());
543                 break;
544             }
545         }
546     }
547
548     return true;
549 }
550
551 } // namespace skgpu::graphite