• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "src/gpu/graphite/dawn/DawnGraphicsPipeline.h"
9 
10 #include "include/gpu/graphite/TextureInfo.h"
11 #include "src/gpu/graphite/Attribute.h"
12 #include "src/gpu/graphite/ContextUtils.h"
13 #include "src/gpu/graphite/GraphicsPipelineDesc.h"
14 #include "src/gpu/graphite/Log.h"
15 #include "src/gpu/graphite/RendererProvider.h"
16 #include "src/gpu/graphite/UniformManager.h"
17 #include "src/gpu/graphite/dawn/DawnResourceProvider.h"
18 #include "src/gpu/graphite/dawn/DawnSharedContext.h"
19 #include "src/gpu/graphite/dawn/DawnUtilsPriv.h"
20 #include "src/sksl/SkSLProgramSettings.h"
21 #include "src/sksl/ir/SkSLProgram.h"
22 
23 #include <vector>
24 
25 namespace skgpu::graphite {
26 
27 namespace {
28 
attribute_type_to_dawn(VertexAttribType type)29 inline wgpu::VertexFormat attribute_type_to_dawn(VertexAttribType type) {
30     switch (type) {
31         case VertexAttribType::kFloat:
32             return wgpu::VertexFormat::Float32;
33         case VertexAttribType::kFloat2:
34             return wgpu::VertexFormat::Float32x2;
35         case VertexAttribType::kFloat3:
36             return wgpu::VertexFormat::Float32x3;
37         case VertexAttribType::kFloat4:
38             return wgpu::VertexFormat::Float32x4;
39         case VertexAttribType::kHalf:
40             return wgpu::VertexFormat::Undefined;
41         case VertexAttribType::kHalf2:
42             return wgpu::VertexFormat::Float16x2;
43         case VertexAttribType::kHalf4:
44             return wgpu::VertexFormat::Float16x4;
45         case VertexAttribType::kInt2:
46             return wgpu::VertexFormat::Sint32x2;
47         case VertexAttribType::kInt3:
48             return wgpu::VertexFormat::Sint32x3;
49         case VertexAttribType::kInt4:
50             return wgpu::VertexFormat::Sint32x4;
51         case VertexAttribType::kByte:
52             return wgpu::VertexFormat::Undefined;
53         case VertexAttribType::kByte2:
54             return wgpu::VertexFormat::Sint8x2;
55         case VertexAttribType::kByte4:
56             return wgpu::VertexFormat::Sint8x4;
57         case VertexAttribType::kUByte:
58             return wgpu::VertexFormat::Undefined;
59         case VertexAttribType::kUByte2:
60             return wgpu::VertexFormat::Uint8x2;
61         case VertexAttribType::kUByte4:
62             return wgpu::VertexFormat::Uint8x4;
63         case VertexAttribType::kUByte_norm:
64             return wgpu::VertexFormat::Undefined;
65         case VertexAttribType::kUByte4_norm:
66             return wgpu::VertexFormat::Unorm8x4;
67         case VertexAttribType::kShort2:
68             return wgpu::VertexFormat::Sint16x2;
69         case VertexAttribType::kShort4:
70             return wgpu::VertexFormat::Sint16x4;
71         case VertexAttribType::kUShort2:
72             return wgpu::VertexFormat::Uint16x2;
73         case VertexAttribType::kUShort2_norm:
74             return wgpu::VertexFormat::Unorm16x2;
75         case VertexAttribType::kInt:
76             return wgpu::VertexFormat::Sint32;
77         case VertexAttribType::kUInt:
78             return wgpu::VertexFormat::Uint32;
79         case VertexAttribType::kUShort_norm:
80             return wgpu::VertexFormat::Undefined;
81         case VertexAttribType::kUShort4_norm:
82             return wgpu::VertexFormat::Unorm16x4;
83     }
84     SkUNREACHABLE;
85 }
86 
compare_op_to_dawn(CompareOp op)87 wgpu::CompareFunction compare_op_to_dawn(CompareOp op) {
88     switch (op) {
89         case CompareOp::kAlways:
90             return wgpu::CompareFunction::Always;
91         case CompareOp::kNever:
92             return wgpu::CompareFunction::Never;
93         case CompareOp::kGreater:
94             return wgpu::CompareFunction::Greater;
95         case CompareOp::kGEqual:
96             return wgpu::CompareFunction::GreaterEqual;
97         case CompareOp::kLess:
98             return wgpu::CompareFunction::Less;
99         case CompareOp::kLEqual:
100             return wgpu::CompareFunction::LessEqual;
101         case CompareOp::kEqual:
102             return wgpu::CompareFunction::Equal;
103         case CompareOp::kNotEqual:
104             return wgpu::CompareFunction::NotEqual;
105     }
106     SkUNREACHABLE;
107 }
108 
stencil_op_to_dawn(StencilOp op)109 wgpu::StencilOperation stencil_op_to_dawn(StencilOp op) {
110     switch (op) {
111         case StencilOp::kKeep:
112             return wgpu::StencilOperation::Keep;
113         case StencilOp::kZero:
114             return wgpu::StencilOperation::Zero;
115         case StencilOp::kReplace:
116             return wgpu::StencilOperation::Replace;
117         case StencilOp::kInvert:
118             return wgpu::StencilOperation::Invert;
119         case StencilOp::kIncWrap:
120             return wgpu::StencilOperation::IncrementWrap;
121         case StencilOp::kDecWrap:
122             return wgpu::StencilOperation::DecrementWrap;
123         case StencilOp::kIncClamp:
124             return wgpu::StencilOperation::IncrementClamp;
125         case StencilOp::kDecClamp:
126             return wgpu::StencilOperation::DecrementClamp;
127     }
128     SkUNREACHABLE;
129 }
130 
stencil_face_to_dawn(DepthStencilSettings::Face face)131 wgpu::StencilFaceState stencil_face_to_dawn(DepthStencilSettings::Face face) {
132     wgpu::StencilFaceState state;
133     state.compare = compare_op_to_dawn(face.fCompareOp);
134     state.failOp = stencil_op_to_dawn(face.fStencilFailOp);
135     state.depthFailOp = stencil_op_to_dawn(face.fDepthFailOp);
136     state.passOp = stencil_op_to_dawn(face.fDepthStencilPassOp);
137     return state;
138 }
139 
create_vertex_attributes(SkSpan<const Attribute> attrs,int shaderLocationOffset,std::vector<wgpu::VertexAttribute> * out)140 size_t create_vertex_attributes(SkSpan<const Attribute> attrs,
141                                 int shaderLocationOffset,
142                                 std::vector<wgpu::VertexAttribute>* out) {
143     SkASSERT(out && out->empty());
144     out->resize(attrs.size());
145     size_t vertexAttributeOffset = 0;
146     int attributeIndex = 0;
147     for (const auto& attr : attrs) {
148         wgpu::VertexAttribute& vertexAttribute =  (*out)[attributeIndex];
149         vertexAttribute.format = attribute_type_to_dawn(attr.cpuType());
150         SkASSERT(vertexAttribute.format != wgpu::VertexFormat::Undefined);
151         vertexAttribute.offset = vertexAttributeOffset;
152         vertexAttribute.shaderLocation = shaderLocationOffset + attributeIndex;
153         vertexAttributeOffset += attr.sizeAlign4();
154         attributeIndex++;
155     }
156     return vertexAttributeOffset;
157 }
158 
159 // TODO: share this w/ Ganesh dawn backend?
blend_coeff_to_dawn_blend(skgpu::BlendCoeff coeff)160 static wgpu::BlendFactor blend_coeff_to_dawn_blend(skgpu::BlendCoeff coeff) {
161     switch (coeff) {
162         case skgpu::BlendCoeff::kZero:
163             return wgpu::BlendFactor::Zero;
164         case skgpu::BlendCoeff::kOne:
165             return wgpu::BlendFactor::One;
166         case skgpu::BlendCoeff::kSC:
167             return wgpu::BlendFactor::Src;
168         case skgpu::BlendCoeff::kISC:
169             return wgpu::BlendFactor::OneMinusSrc;
170         case skgpu::BlendCoeff::kDC:
171             return wgpu::BlendFactor::Dst;
172         case skgpu::BlendCoeff::kIDC:
173             return wgpu::BlendFactor::OneMinusDst;
174         case skgpu::BlendCoeff::kSA:
175             return wgpu::BlendFactor::SrcAlpha;
176         case skgpu::BlendCoeff::kISA:
177             return wgpu::BlendFactor::OneMinusSrcAlpha;
178         case skgpu::BlendCoeff::kDA:
179             return wgpu::BlendFactor::DstAlpha;
180         case skgpu::BlendCoeff::kIDA:
181             return wgpu::BlendFactor::OneMinusDstAlpha;
182         case skgpu::BlendCoeff::kConstC:
183             return wgpu::BlendFactor::Constant;
184         case skgpu::BlendCoeff::kIConstC:
185             return wgpu::BlendFactor::OneMinusConstant;
186         case skgpu::BlendCoeff::kS2C:
187         case skgpu::BlendCoeff::kIS2C:
188         case skgpu::BlendCoeff::kS2A:
189         case skgpu::BlendCoeff::kIS2A:
190         case skgpu::BlendCoeff::kIllegal:
191             return wgpu::BlendFactor::Zero;
192     }
193     SkUNREACHABLE;
194 }
195 
196 // TODO: share this w/ Ganesh Metal backend?
blend_equation_to_dawn_blend_op(skgpu::BlendEquation equation)197 static wgpu::BlendOperation blend_equation_to_dawn_blend_op(skgpu::BlendEquation equation) {
198     static const wgpu::BlendOperation gTable[] = {
199             wgpu::BlendOperation::Add,              // skgpu::BlendEquation::kAdd
200             wgpu::BlendOperation::Subtract,         // skgpu::BlendEquation::kSubtract
201             wgpu::BlendOperation::ReverseSubtract,  // skgpu::BlendEquation::kReverseSubtract
202     };
203     static_assert(std::size(gTable) == (int)skgpu::BlendEquation::kFirstAdvanced);
204     static_assert(0 == (int)skgpu::BlendEquation::kAdd);
205     static_assert(1 == (int)skgpu::BlendEquation::kSubtract);
206     static_assert(2 == (int)skgpu::BlendEquation::kReverseSubtract);
207 
208     SkASSERT((unsigned)equation < skgpu::kBlendEquationCnt);
209     return gTable[(int)equation];
210 }
211 
212 } // anonymous namespace
213 
214 // static
Make(const DawnSharedContext * sharedContext,SkSL::Compiler * compiler,const RuntimeEffectDictionary * runtimeDict,const GraphicsPipelineDesc & pipelineDesc,const RenderPassDesc & renderPassDesc)215 sk_sp<DawnGraphicsPipeline> DawnGraphicsPipeline::Make(const DawnSharedContext* sharedContext,
216                                                        SkSL::Compiler* compiler,
217                                                        const RuntimeEffectDictionary* runtimeDict,
218                                                        const GraphicsPipelineDesc& pipelineDesc,
219                                                        const RenderPassDesc& renderPassDesc) {
220     const auto& device = sharedContext->device();
221 
222     SkSL::Program::Inputs vsInputs, fsInputs;
223     SkSL::ProgramSettings settings;
224 
225     settings.fForceNoRTFlip = true;
226     settings.fSPIRVDawnCompatMode = true;
227 
228     ShaderErrorHandler* errorHandler = sharedContext->caps()->shaderErrorHandler();
229 
230     const RenderStep* step =
231             sharedContext->rendererProvider()->lookup(pipelineDesc.renderStepID());
232 
233     bool useShadingSsboIndex =
234             sharedContext->caps()->storageBufferPreferred() && step->performsShading();
235 
236     std::string vsSPIRV, fsSPIRV;
237     wgpu::ShaderModule fsModule, vsModule;
238 
239     // Some steps just render depth buffer but not color buffer, so the fragment
240     // shader is null.
241     const FragSkSLInfo fsSkSLInfo = GetSkSLFS(sharedContext->caps()->resourceBindingRequirements(),
242                                               sharedContext->shaderCodeDictionary(),
243                                               runtimeDict,
244                                               step,
245                                               pipelineDesc.paintParamsID(),
246                                               useShadingSsboIndex);
247     const std::string& fsSKSL = fsSkSLInfo.fSkSL;
248     const BlendInfo& blendInfo = fsSkSLInfo.fBlendInfo;
249     const bool localCoordsNeeded = fsSkSLInfo.fRequiresLocalCoords;
250     const int numTexturesAndSamplers = fsSkSLInfo.fNumTexturesAndSamplers;
251 
252     bool hasFragment = !fsSKSL.empty();
253     if (hasFragment) {
254         if (!SkSLToSPIRV(compiler,
255                          fsSKSL,
256                          SkSL::ProgramKind::kGraphiteFragment,
257                          settings,
258                          &fsSPIRV,
259                          &fsInputs,
260                          errorHandler)) {
261             return {};
262         }
263         fsModule = DawnCompileSPIRVShaderModule(sharedContext,
264                                                 fsSPIRV,
265                                                 errorHandler);
266         if (!fsModule) {
267             return {};
268         }
269     }
270 
271     if (!SkSLToSPIRV(compiler,
272                      GetSkSLVS(sharedContext->caps()->resourceBindingRequirements(),
273                                step,
274                                useShadingSsboIndex,
275                                localCoordsNeeded),
276                      SkSL::ProgramKind::kGraphiteVertex,
277                      settings,
278                      &vsSPIRV,
279                      &vsInputs,
280                      errorHandler)) {
281         return {};
282     }
283 
284     vsModule = DawnCompileSPIRVShaderModule(sharedContext, vsSPIRV, errorHandler);
285     if (!vsModule) {
286         return {};
287     }
288 
289     wgpu::RenderPipelineDescriptor descriptor;
290 #if defined(SK_DEBUG)
291     descriptor.label = step->name();
292 #endif
293 
294     // Fragment state
295     skgpu::BlendEquation equation = blendInfo.fEquation;
296     skgpu::BlendCoeff srcCoeff = blendInfo.fSrcBlend;
297     skgpu::BlendCoeff dstCoeff = blendInfo.fDstBlend;
298     bool blendOn = !skgpu::BlendShouldDisable(equation, srcCoeff, dstCoeff);
299 
300     wgpu::BlendState blend;
301     if (blendOn) {
302         blend.color.operation = blend_equation_to_dawn_blend_op(equation);
303         blend.color.srcFactor = blend_coeff_to_dawn_blend(srcCoeff);
304         blend.color.dstFactor = blend_coeff_to_dawn_blend(dstCoeff);
305         blend.alpha.operation = blend_equation_to_dawn_blend_op(equation);
306         blend.alpha.srcFactor = blend_coeff_to_dawn_blend(srcCoeff);
307         blend.alpha.dstFactor = blend_coeff_to_dawn_blend(dstCoeff);
308     }
309 
310     wgpu::ColorTargetState colorTarget;
311     colorTarget.format = renderPassDesc.fColorAttachment.fTextureInfo.dawnTextureSpec().fFormat;
312     colorTarget.blend = blendOn ? &blend : nullptr;
313     colorTarget.writeMask = blendInfo.fWritesColor && hasFragment ? wgpu::ColorWriteMask::All
314                                                                   : wgpu::ColorWriteMask::None;
315 
316     wgpu::FragmentState fragment;
317     // Dawn doesn't allow having a color attachment but without fragment shader, so have to use a
318     // noop fragment shader, if fragment shader is null.
319     fragment.module = hasFragment ? std::move(fsModule) : sharedContext->noopFragment();
320     fragment.entryPoint = "main";
321     fragment.targetCount = 1;
322     fragment.targets = &colorTarget;
323     descriptor.fragment = &fragment;
324 
325     // Depth stencil state
326     const auto& depthStencilSettings = step->depthStencilSettings();
327     SkASSERT(depthStencilSettings.fDepthTestEnabled ||
328              depthStencilSettings.fDepthCompareOp == CompareOp::kAlways);
329     wgpu::DepthStencilState depthStencil;
330     if (renderPassDesc.fDepthStencilAttachment.fTextureInfo.isValid()) {
331         wgpu::TextureFormat dsFormat =
332                 renderPassDesc.fDepthStencilAttachment.fTextureInfo.dawnTextureSpec().fFormat;
333         depthStencil.format =
334                 DawnFormatIsDepthOrStencil(dsFormat) ? dsFormat : wgpu::TextureFormat::Undefined;
335         if (depthStencilSettings.fDepthTestEnabled) {
336             depthStencil.depthWriteEnabled = depthStencilSettings.fDepthWriteEnabled;
337         }
338         depthStencil.depthCompare = compare_op_to_dawn(depthStencilSettings.fDepthCompareOp);
339         depthStencil.stencilFront = stencil_face_to_dawn(depthStencilSettings.fFrontStencil);
340         depthStencil.stencilBack = stencil_face_to_dawn(depthStencilSettings.fBackStencil);
341         depthStencil.stencilReadMask = depthStencilSettings.fFrontStencil.fReadMask;
342         depthStencil.stencilWriteMask = depthStencilSettings.fFrontStencil.fWriteMask;
343 
344         descriptor.depthStencil = &depthStencil;
345     }
346 
347     // Pipeline layout
348     {
349         std::array<wgpu::BindGroupLayout, 2> groupLayouts;
350         {
351             std::array<wgpu::BindGroupLayoutEntry, 3> entries;
352             entries[0].binding = kIntrinsicUniformBufferIndex;
353             entries[0].visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment;
354             entries[0].buffer.type = wgpu::BufferBindingType::Uniform;
355             entries[0].buffer.hasDynamicOffset = false;
356             entries[0].buffer.minBindingSize = 0;
357 
358             uint32_t numBuffers = 1;
359 
360             if (!step->uniforms().empty()) {
361                 entries[numBuffers].binding = kRenderStepUniformBufferIndex;
362                 entries[numBuffers].visibility =
363                         wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment;
364                 entries[numBuffers].buffer.type = wgpu::BufferBindingType::Uniform;
365                 entries[numBuffers].buffer.hasDynamicOffset = false;
366                 entries[numBuffers].buffer.minBindingSize = 0;
367                 ++numBuffers;
368             }
369 
370             if (hasFragment) {
371                 entries[numBuffers].binding = kPaintUniformBufferIndex;
372                 entries[numBuffers].visibility = wgpu::ShaderStage::Fragment;
373                 entries[numBuffers].buffer.type = wgpu::BufferBindingType::Uniform;
374                 entries[numBuffers].buffer.hasDynamicOffset = false;
375                 entries[numBuffers].buffer.minBindingSize = 0;
376                 ++numBuffers;
377             }
378 
379             wgpu::BindGroupLayoutDescriptor groupLayoutDesc;
380 #if defined(SK_DEBUG)
381             groupLayoutDesc.label = step->name();
382 #endif
383 
384             groupLayoutDesc.entryCount = numBuffers;
385             groupLayoutDesc.entries = entries.data();
386             groupLayouts[0] = device.CreateBindGroupLayout(&groupLayoutDesc);
387             if (!groupLayouts[0]) {
388                 return {};
389             }
390         }
391 
392         bool hasFragmentSamplers = hasFragment && numTexturesAndSamplers > 0;
393         if (hasFragmentSamplers) {
394             std::vector<wgpu::BindGroupLayoutEntry> entries(numTexturesAndSamplers);
395             for (int i = 0; i < numTexturesAndSamplers;) {
396                 entries[i].binding = static_cast<uint32_t>(i);
397                 entries[i].visibility = wgpu::ShaderStage::Fragment;
398                 entries[i].sampler.type = wgpu::SamplerBindingType::Filtering;
399                 ++i;
400                 entries[i].binding = i;
401                 entries[i].visibility = wgpu::ShaderStage::Fragment;
402                 entries[i].texture.sampleType = wgpu::TextureSampleType::Float;
403                 entries[i].texture.viewDimension = wgpu::TextureViewDimension::e2D;
404                 entries[i].texture.multisampled = false;
405                 ++i;
406             }
407 
408             wgpu::BindGroupLayoutDescriptor groupLayoutDesc;
409 #if defined(SK_DEBUG)
410             groupLayoutDesc.label = step->name();
411 #endif
412             groupLayoutDesc.entryCount = entries.size();
413             groupLayoutDesc.entries = entries.data();
414             groupLayouts[1] = device.CreateBindGroupLayout(&groupLayoutDesc);
415             if (!groupLayouts[1]) {
416                 return {};
417             }
418         }
419 
420         wgpu::PipelineLayoutDescriptor layoutDesc;
421 #if defined(SK_DEBUG)
422         layoutDesc.label = step->name();
423 #endif
424         layoutDesc.bindGroupLayoutCount =
425             hasFragmentSamplers ? groupLayouts.size() : groupLayouts.size() - 1;
426         layoutDesc.bindGroupLayouts = groupLayouts.data();
427         auto layout = device.CreatePipelineLayout(&layoutDesc);
428         if (!layout) {
429             return {};
430         }
431         descriptor.layout = std::move(layout);
432     }
433 
434     // Vertex state
435     std::array<wgpu::VertexBufferLayout, kNumVertexBuffers> vertexBufferLayouts;
436     // Vertex buffer layout
437     std::vector<wgpu::VertexAttribute> vertexAttributes;
438     {
439         auto arrayStride = create_vertex_attributes(step->vertexAttributes(),
440                                                     0,
441                                                     &vertexAttributes);
442         auto& layout = vertexBufferLayouts[kVertexBufferIndex];
443         if (arrayStride) {
444             layout.arrayStride = arrayStride;
445             layout.stepMode = wgpu::VertexStepMode::Vertex;
446             layout.attributeCount = vertexAttributes.size();
447             layout.attributes = vertexAttributes.data();
448         } else {
449             layout.arrayStride = 0;
450             layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
451             layout.attributeCount = 0;
452             layout.attributes = nullptr;
453         }
454     }
455 
456     // Instance buffer layout
457     std::vector<wgpu::VertexAttribute> instanceAttributes;
458     {
459         auto arrayStride = create_vertex_attributes(step->instanceAttributes(),
460                                                     step->vertexAttributes().size(),
461                                                     &instanceAttributes);
462         auto& layout = vertexBufferLayouts[kInstanceBufferIndex];
463         if (arrayStride) {
464             layout.arrayStride = arrayStride;
465             layout.stepMode = wgpu::VertexStepMode::Instance;
466             layout.attributeCount = instanceAttributes.size();
467             layout.attributes = instanceAttributes.data();
468         } else {
469             layout.arrayStride = 0;
470             layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
471             layout.attributeCount = 0;
472             layout.attributes = nullptr;
473         }
474     }
475 
476     auto& vertex = descriptor.vertex;
477     vertex.module = std::move(vsModule);
478     vertex.entryPoint = "main";
479     vertex.constantCount = 0;
480     vertex.constants = nullptr;
481     vertex.bufferCount = vertexBufferLayouts.size();
482     vertex.buffers = vertexBufferLayouts.data();
483 
484     // Other state
485     descriptor.primitive.frontFace = wgpu::FrontFace::CCW;
486     descriptor.primitive.cullMode = wgpu::CullMode::None;
487     switch(step->primitiveType()) {
488         case PrimitiveType::kTriangles:
489             descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
490             break;
491         case PrimitiveType::kTriangleStrip:
492             descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip;
493             break;
494         case PrimitiveType::kPoints:
495             descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
496             break;
497     }
498     descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Uint16;
499 
500     descriptor.multisample.count = renderPassDesc.fColorAttachment.fTextureInfo.numSamples();
501     descriptor.multisample.mask = 0xFFFFFFFF;
502     descriptor.multisample.alphaToCoverageEnabled = false;
503 
504     auto pipeline = device.CreateRenderPipeline(&descriptor);
505     if (!pipeline) {
506         return {};
507     }
508 
509     return sk_sp<DawnGraphicsPipeline>(
510             new DawnGraphicsPipeline(sharedContext,
511                                      std::move(pipeline),
512                                      step->primitiveType(),
513                                      depthStencilSettings.fStencilReferenceValue,
514                                      !step->uniforms().empty(),
515                                      hasFragment));
516 }
517 
freeGpuData()518 void DawnGraphicsPipeline::freeGpuData() {
519     fRenderPipeline = nullptr;
520 }
521 
dawnRenderPipeline() const522 const wgpu::RenderPipeline& DawnGraphicsPipeline::dawnRenderPipeline() const {
523     return fRenderPipeline;
524 }
525 
526 } // namespace skgpu::graphite
527