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 "include/private/base/SkTemplates.h"
12 #include "src/core/SkTraceEvent.h"
13 #include "src/gpu/SkSLToBackend.h"
14 #include "src/gpu/Swizzle.h"
15 #include "src/gpu/graphite/Attribute.h"
16 #include "src/gpu/graphite/ContextUtils.h"
17 #include "src/gpu/graphite/GraphicsPipelineDesc.h"
18 #include "src/gpu/graphite/Log.h"
19 #include "src/gpu/graphite/RenderPassDesc.h"
20 #include "src/gpu/graphite/RendererProvider.h"
21 #include "src/gpu/graphite/ShaderInfo.h"
22 #include "src/gpu/graphite/UniformManager.h"
23 #include "src/gpu/graphite/dawn/DawnCaps.h"
24 #include "src/gpu/graphite/dawn/DawnErrorChecker.h"
25 #include "src/gpu/graphite/dawn/DawnGraphiteTypesPriv.h"
26 #include "src/gpu/graphite/dawn/DawnGraphiteUtilsPriv.h"
27 #include "src/gpu/graphite/dawn/DawnResourceProvider.h"
28 #include "src/gpu/graphite/dawn/DawnSharedContext.h"
29 #include "src/gpu/graphite/dawn/DawnUtilsPriv.h"
30 #include "src/sksl/SkSLProgramSettings.h"
31 #include "src/sksl/SkSLUtil.h"
32 #include "src/sksl/ir/SkSLProgram.h"
33
34 #include <vector>
35
36 namespace skgpu::graphite {
37
38 namespace {
39
attribute_type_to_dawn(VertexAttribType type)40 inline wgpu::VertexFormat attribute_type_to_dawn(VertexAttribType type) {
41 switch (type) {
42 case VertexAttribType::kFloat:
43 return wgpu::VertexFormat::Float32;
44 case VertexAttribType::kFloat2:
45 return wgpu::VertexFormat::Float32x2;
46 case VertexAttribType::kFloat3:
47 return wgpu::VertexFormat::Float32x3;
48 case VertexAttribType::kFloat4:
49 return wgpu::VertexFormat::Float32x4;
50 case VertexAttribType::kHalf2:
51 return wgpu::VertexFormat::Float16x2;
52 case VertexAttribType::kHalf4:
53 return wgpu::VertexFormat::Float16x4;
54 case VertexAttribType::kInt2:
55 return wgpu::VertexFormat::Sint32x2;
56 case VertexAttribType::kInt3:
57 return wgpu::VertexFormat::Sint32x3;
58 case VertexAttribType::kInt4:
59 return wgpu::VertexFormat::Sint32x4;
60 case VertexAttribType::kUInt2:
61 return wgpu::VertexFormat::Uint32x2;
62 case VertexAttribType::kByte2:
63 return wgpu::VertexFormat::Sint8x2;
64 case VertexAttribType::kByte4:
65 return wgpu::VertexFormat::Sint8x4;
66 case VertexAttribType::kUByte2:
67 return wgpu::VertexFormat::Uint8x2;
68 case VertexAttribType::kUByte4:
69 return wgpu::VertexFormat::Uint8x4;
70 case VertexAttribType::kUByte4_norm:
71 return wgpu::VertexFormat::Unorm8x4;
72 case VertexAttribType::kShort2:
73 return wgpu::VertexFormat::Sint16x2;
74 case VertexAttribType::kShort4:
75 return wgpu::VertexFormat::Sint16x4;
76 case VertexAttribType::kUShort2:
77 return wgpu::VertexFormat::Uint16x2;
78 case VertexAttribType::kUShort2_norm:
79 return wgpu::VertexFormat::Unorm16x2;
80 case VertexAttribType::kInt:
81 return wgpu::VertexFormat::Sint32;
82 case VertexAttribType::kUInt:
83 return wgpu::VertexFormat::Uint32;
84 case VertexAttribType::kUShort4_norm:
85 return wgpu::VertexFormat::Unorm16x4;
86 case VertexAttribType::kHalf:
87 case VertexAttribType::kByte:
88 case VertexAttribType::kUByte:
89 case VertexAttribType::kUByte_norm:
90 case VertexAttribType::kUShort_norm:
91 // Not supported.
92 break;
93 }
94 SkUNREACHABLE;
95 }
96
compare_op_to_dawn(CompareOp op)97 wgpu::CompareFunction compare_op_to_dawn(CompareOp op) {
98 switch (op) {
99 case CompareOp::kAlways:
100 return wgpu::CompareFunction::Always;
101 case CompareOp::kNever:
102 return wgpu::CompareFunction::Never;
103 case CompareOp::kGreater:
104 return wgpu::CompareFunction::Greater;
105 case CompareOp::kGEqual:
106 return wgpu::CompareFunction::GreaterEqual;
107 case CompareOp::kLess:
108 return wgpu::CompareFunction::Less;
109 case CompareOp::kLEqual:
110 return wgpu::CompareFunction::LessEqual;
111 case CompareOp::kEqual:
112 return wgpu::CompareFunction::Equal;
113 case CompareOp::kNotEqual:
114 return wgpu::CompareFunction::NotEqual;
115 }
116 SkUNREACHABLE;
117 }
118
stencil_op_to_dawn(StencilOp op)119 wgpu::StencilOperation stencil_op_to_dawn(StencilOp op) {
120 switch (op) {
121 case StencilOp::kKeep:
122 return wgpu::StencilOperation::Keep;
123 case StencilOp::kZero:
124 return wgpu::StencilOperation::Zero;
125 case StencilOp::kReplace:
126 return wgpu::StencilOperation::Replace;
127 case StencilOp::kInvert:
128 return wgpu::StencilOperation::Invert;
129 case StencilOp::kIncWrap:
130 return wgpu::StencilOperation::IncrementWrap;
131 case StencilOp::kDecWrap:
132 return wgpu::StencilOperation::DecrementWrap;
133 case StencilOp::kIncClamp:
134 return wgpu::StencilOperation::IncrementClamp;
135 case StencilOp::kDecClamp:
136 return wgpu::StencilOperation::DecrementClamp;
137 }
138 SkUNREACHABLE;
139 }
140
stencil_face_to_dawn(DepthStencilSettings::Face face)141 wgpu::StencilFaceState stencil_face_to_dawn(DepthStencilSettings::Face face) {
142 wgpu::StencilFaceState state;
143 state.compare = compare_op_to_dawn(face.fCompareOp);
144 state.failOp = stencil_op_to_dawn(face.fStencilFailOp);
145 state.depthFailOp = stencil_op_to_dawn(face.fDepthFailOp);
146 state.passOp = stencil_op_to_dawn(face.fDepthStencilPassOp);
147 return state;
148 }
149
create_vertex_attributes(SkSpan<const Attribute> attrs,int shaderLocationOffset,std::vector<wgpu::VertexAttribute> * out)150 size_t create_vertex_attributes(SkSpan<const Attribute> attrs,
151 int shaderLocationOffset,
152 std::vector<wgpu::VertexAttribute>* out) {
153 SkASSERT(out && out->empty());
154 out->resize(attrs.size());
155 size_t vertexAttributeOffset = 0;
156 int attributeIndex = 0;
157 for (const auto& attr : attrs) {
158 wgpu::VertexAttribute& vertexAttribute = (*out)[attributeIndex];
159 vertexAttribute.format = attribute_type_to_dawn(attr.cpuType());
160 vertexAttribute.offset = vertexAttributeOffset;
161 vertexAttribute.shaderLocation = shaderLocationOffset + attributeIndex;
162 vertexAttributeOffset += attr.sizeAlign4();
163 attributeIndex++;
164 }
165 return vertexAttributeOffset;
166 }
167
168 // TODO: share this w/ Ganesh dawn backend?
blend_coeff_to_dawn_blend(const DawnCaps & caps,skgpu::BlendCoeff coeff)169 static wgpu::BlendFactor blend_coeff_to_dawn_blend(const DawnCaps& caps, skgpu::BlendCoeff coeff) {
170 #if defined(__EMSCRIPTEN__)
171 #define VALUE_IF_DSB_OR_ZERO(VALUE) wgpu::BlendFactor::Zero
172 #else
173 #define VALUE_IF_DSB_OR_ZERO(VALUE) \
174 ((caps.shaderCaps()->fDualSourceBlendingSupport) ? (VALUE) : wgpu::BlendFactor::Zero)
175 #endif
176 switch (coeff) {
177 case skgpu::BlendCoeff::kZero:
178 return wgpu::BlendFactor::Zero;
179 case skgpu::BlendCoeff::kOne:
180 return wgpu::BlendFactor::One;
181 case skgpu::BlendCoeff::kSC:
182 return wgpu::BlendFactor::Src;
183 case skgpu::BlendCoeff::kISC:
184 return wgpu::BlendFactor::OneMinusSrc;
185 case skgpu::BlendCoeff::kDC:
186 return wgpu::BlendFactor::Dst;
187 case skgpu::BlendCoeff::kIDC:
188 return wgpu::BlendFactor::OneMinusDst;
189 case skgpu::BlendCoeff::kSA:
190 return wgpu::BlendFactor::SrcAlpha;
191 case skgpu::BlendCoeff::kISA:
192 return wgpu::BlendFactor::OneMinusSrcAlpha;
193 case skgpu::BlendCoeff::kDA:
194 return wgpu::BlendFactor::DstAlpha;
195 case skgpu::BlendCoeff::kIDA:
196 return wgpu::BlendFactor::OneMinusDstAlpha;
197 case skgpu::BlendCoeff::kConstC:
198 return wgpu::BlendFactor::Constant;
199 case skgpu::BlendCoeff::kIConstC:
200 return wgpu::BlendFactor::OneMinusConstant;
201 case skgpu::BlendCoeff::kS2C:
202 return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::Src1);
203 case skgpu::BlendCoeff::kIS2C:
204 return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::OneMinusSrc1);
205 case skgpu::BlendCoeff::kS2A:
206 return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::Src1Alpha);
207 case skgpu::BlendCoeff::kIS2A:
208 return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::OneMinusSrc1Alpha);
209 case skgpu::BlendCoeff::kIllegal:
210 return wgpu::BlendFactor::Zero;
211 }
212 SkUNREACHABLE;
213 #undef VALUE_IF_DSB_OR_ZERO
214 }
215
blend_coeff_to_dawn_blend_for_alpha(const DawnCaps & caps,skgpu::BlendCoeff coeff)216 static wgpu::BlendFactor blend_coeff_to_dawn_blend_for_alpha(const DawnCaps& caps,
217 skgpu::BlendCoeff coeff) {
218 switch (coeff) {
219 // Force all srcColor used in alpha slot to alpha version.
220 case skgpu::BlendCoeff::kSC:
221 return wgpu::BlendFactor::SrcAlpha;
222 case skgpu::BlendCoeff::kISC:
223 return wgpu::BlendFactor::OneMinusSrcAlpha;
224 case skgpu::BlendCoeff::kDC:
225 return wgpu::BlendFactor::DstAlpha;
226 case skgpu::BlendCoeff::kIDC:
227 return wgpu::BlendFactor::OneMinusDstAlpha;
228 default:
229 return blend_coeff_to_dawn_blend(caps, coeff);
230 }
231 }
232
233 // TODO: share this w/ Ganesh Metal backend?
blend_equation_to_dawn_blend_op(skgpu::BlendEquation equation)234 static wgpu::BlendOperation blend_equation_to_dawn_blend_op(skgpu::BlendEquation equation) {
235 static const wgpu::BlendOperation gTable[] = {
236 wgpu::BlendOperation::Add, // skgpu::BlendEquation::kAdd
237 wgpu::BlendOperation::Subtract, // skgpu::BlendEquation::kSubtract
238 wgpu::BlendOperation::ReverseSubtract, // skgpu::BlendEquation::kReverseSubtract
239 };
240 static_assert(std::size(gTable) == (int)skgpu::BlendEquation::kFirstAdvanced);
241 static_assert(0 == (int)skgpu::BlendEquation::kAdd);
242 static_assert(1 == (int)skgpu::BlendEquation::kSubtract);
243 static_assert(2 == (int)skgpu::BlendEquation::kReverseSubtract);
244
245 SkASSERT((unsigned)equation < skgpu::kBlendEquationCnt);
246 return gTable[(int)equation];
247 }
248
249 struct AsyncPipelineCreationBase {
AsyncPipelineCreationBaseskgpu::graphite::__anonadbe7f3e0111::AsyncPipelineCreationBase250 AsyncPipelineCreationBase(const UniqueKey& key) : fKey(key) {}
251
252 wgpu::RenderPipeline fRenderPipeline;
253 bool fFinished = false;
254 UniqueKey fKey; // for logging the wait to resolve a Pipeline future in dawnRenderPipeline
255 #if SK_HISTOGRAMS_ENABLED
256 // We need these three for the Graphite.PipelineCreationTimes.* histograms (cf.
257 // log_pipeline_creation)
258 skgpu::StdSteadyClock::time_point fStartTime;
259 bool fFromPrecompile;
260 bool fAsynchronous = false;
261 #endif
262 };
263
log_pipeline_creation(const AsyncPipelineCreationBase * apcb)264 void log_pipeline_creation(const AsyncPipelineCreationBase* apcb) {
265 #if SK_HISTOGRAMS_ENABLED
266 [[maybe_unused]] static constexpr int kBucketCount = 100;
267 [[maybe_unused]] static constexpr int kOneSecInUS = 1000000;
268
269 SkASSERT(apcb->fFinished);
270
271 if (!apcb->fRenderPipeline) {
272 // A null fRenderPipeline means Pipeline creation failed
273 return; // TODO: log failures to their own UMA stat
274 }
275
276 [[maybe_unused]] auto micros_since = [](skgpu::StdSteadyClock::time_point start) {
277 skgpu::StdSteadyClock::duration elapsed = skgpu::StdSteadyClock::now() - start;
278 return std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
279 };
280
281 if (apcb->fFromPrecompile) {
282 SkASSERT(!apcb->fAsynchronous); // precompile is done synchronously on a thread
283 SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
284 "Graphite.PipelineCreationTimes.Precompile",
285 micros_since(apcb->fStartTime),
286 /* minUSec= */ 1,
287 /* maxUSec= */ kOneSecInUS,
288 kBucketCount);
289 } else if (apcb->fAsynchronous) {
290 SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
291 "Graphite.PipelineCreationTimes.Asynchronous",
292 micros_since(apcb->fStartTime),
293 /* minUSec= */ 1,
294 /* maxUSec= */ kOneSecInUS,
295 kBucketCount);
296 } else {
297 SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
298 "Graphite.PipelineCreationTimes.Synchronous",
299 micros_since(apcb->fStartTime),
300 /* minUSec= */ 1,
301 /* maxUSec= */ kOneSecInUS,
302 kBucketCount);
303 }
304 #endif // SK_HISTOGRAMS_ENABLED
305 }
306
307 } // anonymous namespace
308
309 #if defined(__EMSCRIPTEN__)
310 // For wasm, we don't use async compilation.
311 struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase {
AsyncPipelineCreationskgpu::graphite::DawnGraphicsPipeline::AsyncPipelineCreation312 AsyncPipelineCreation(const UniqueKey& key) : AsyncPipelineCreationBase(key) {}
313 };
314 #else
315 struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase {
AsyncPipelineCreationskgpu::graphite::DawnGraphicsPipeline::AsyncPipelineCreation316 AsyncPipelineCreation(const UniqueKey& key) : AsyncPipelineCreationBase(key) {}
317
318 wgpu::Future fFuture;
319 };
320 #endif
321
322 // static
Make(const DawnSharedContext * sharedContext,DawnResourceProvider * resourceProvider,const RuntimeEffectDictionary * runtimeDict,const UniqueKey & pipelineKey,const GraphicsPipelineDesc & pipelineDesc,const RenderPassDesc & renderPassDesc,SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,uint32_t compilationID)323 sk_sp<DawnGraphicsPipeline> DawnGraphicsPipeline::Make(
324 const DawnSharedContext* sharedContext,
325 DawnResourceProvider* resourceProvider,
326 const RuntimeEffectDictionary* runtimeDict,
327 const UniqueKey& pipelineKey,
328 const GraphicsPipelineDesc& pipelineDesc,
329 const RenderPassDesc& renderPassDesc,
330 SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags,
331 uint32_t compilationID) {
332 const DawnCaps& caps = *static_cast<const DawnCaps*>(sharedContext->caps());
333 const auto& device = sharedContext->device();
334
335 SkSL::Program::Interface vsInterface, fsInterface;
336
337 SkSL::ProgramSettings settings;
338 settings.fSharpenTextures = true;
339 settings.fForceNoRTFlip = true;
340
341 ShaderErrorHandler* errorHandler = caps.shaderErrorHandler();
342
343 const RenderStep* step = sharedContext->rendererProvider()->lookup(pipelineDesc.renderStepID());
344 const bool useStorageBuffers = caps.storageBufferSupport();
345
346 std::string vsCode, fsCode;
347 wgpu::ShaderModule fsModule, vsModule;
348
349 // Some steps just render depth buffer but not color buffer, so the fragment
350 // shader is null.
351 UniquePaintParamsID paintID = pipelineDesc.paintParamsID();
352
353 skia_private::TArray<SamplerDesc>* samplerDescArrPtr = nullptr;
354 #if !defined(__EMSCRIPTEN__)
355 skia_private::TArray<SamplerDesc> samplerDescArr {};
356 samplerDescArrPtr = &samplerDescArr;
357 #endif
358
359 std::unique_ptr<ShaderInfo> shaderInfo = ShaderInfo::Make(&caps,
360 sharedContext->shaderCodeDictionary(),
361 runtimeDict,
362 step,
363 paintID,
364 useStorageBuffers,
365 renderPassDesc.fWriteSwizzle,
366 samplerDescArrPtr);
367
368 const std::string& fsSkSL = shaderInfo->fragmentSkSL();
369 const BlendInfo& blendInfo = shaderInfo->blendInfo();
370 const int numTexturesAndSamplers = shaderInfo->numFragmentTexturesAndSamplers();
371
372 const bool hasFragmentSkSL = !fsSkSL.empty();
373 if (hasFragmentSkSL) {
374 if (!skgpu::SkSLToWGSL(caps.shaderCaps(),
375 fsSkSL,
376 SkSL::ProgramKind::kGraphiteFragment,
377 settings,
378 &fsCode,
379 &fsInterface,
380 errorHandler)) {
381 return {};
382 }
383 if (!DawnCompileWGSLShaderModule(sharedContext, shaderInfo->fsLabel().c_str(), fsCode,
384 &fsModule, errorHandler)) {
385 return {};
386 }
387 }
388
389 const std::string& vsSkSL = shaderInfo->vertexSkSL();
390 if (!skgpu::SkSLToWGSL(caps.shaderCaps(),
391 vsSkSL,
392 SkSL::ProgramKind::kGraphiteVertex,
393 settings,
394 &vsCode,
395 &vsInterface,
396 errorHandler)) {
397 return {};
398 }
399 if (!DawnCompileWGSLShaderModule(sharedContext, shaderInfo->vsLabel().c_str(), vsCode,
400 &vsModule, errorHandler)) {
401 return {};
402 }
403
404 std::string pipelineLabel =
405 GetPipelineLabel(sharedContext->shaderCodeDictionary(), renderPassDesc, step, paintID);
406 wgpu::RenderPipelineDescriptor descriptor;
407 // Always set the label for pipelines, dawn may need it for tracing.
408 descriptor.label = pipelineLabel.c_str();
409
410 // Fragment state
411 skgpu::BlendEquation equation = blendInfo.fEquation;
412 skgpu::BlendCoeff srcCoeff = blendInfo.fSrcBlend;
413 skgpu::BlendCoeff dstCoeff = blendInfo.fDstBlend;
414 bool blendOn = !skgpu::BlendShouldDisable(equation, srcCoeff, dstCoeff);
415
416 wgpu::BlendState blend;
417 if (blendOn) {
418 blend.color.operation = blend_equation_to_dawn_blend_op(equation);
419 blend.color.srcFactor = blend_coeff_to_dawn_blend(caps, srcCoeff);
420 blend.color.dstFactor = blend_coeff_to_dawn_blend(caps, dstCoeff);
421 blend.alpha.operation = blend_equation_to_dawn_blend_op(equation);
422 blend.alpha.srcFactor = blend_coeff_to_dawn_blend_for_alpha(caps, srcCoeff);
423 blend.alpha.dstFactor = blend_coeff_to_dawn_blend_for_alpha(caps, dstCoeff);
424 }
425
426 wgpu::ColorTargetState colorTarget;
427 colorTarget.format =
428 TextureInfos::GetDawnViewFormat(renderPassDesc.fColorAttachment.fTextureInfo);
429 colorTarget.blend = blendOn ? &blend : nullptr;
430 colorTarget.writeMask = blendInfo.fWritesColor && hasFragmentSkSL ? wgpu::ColorWriteMask::All
431 : wgpu::ColorWriteMask::None;
432
433 #if !defined(__EMSCRIPTEN__)
434 const bool loadMsaaFromResolve =
435 renderPassDesc.fColorResolveAttachment.fTextureInfo.isValid() &&
436 renderPassDesc.fColorResolveAttachment.fLoadOp == LoadOp::kLoad;
437 // Special case: a render pass loading resolve texture requires additional settings for the
438 // pipeline to make it compatible.
439 wgpu::ColorTargetStateExpandResolveTextureDawn pipelineMSAALoadResolveTextureDesc;
440 if (loadMsaaFromResolve && sharedContext->dawnCaps()->resolveTextureLoadOp().has_value()) {
441 SkASSERT(device.HasFeature(wgpu::FeatureName::DawnLoadResolveTexture));
442 colorTarget.nextInChain = &pipelineMSAALoadResolveTextureDesc;
443 pipelineMSAALoadResolveTextureDesc.enabled = true;
444 }
445 #endif
446
447 wgpu::FragmentState fragment;
448 // Dawn doesn't allow having a color attachment but without fragment shader, so have to use a
449 // noop fragment shader, if fragment shader is null.
450 fragment.module = hasFragmentSkSL ? std::move(fsModule) : sharedContext->noopFragment();
451 fragment.entryPoint = "main";
452 fragment.constantCount = 0;
453 fragment.constants = nullptr;
454 fragment.targetCount = 1;
455 fragment.targets = &colorTarget;
456 descriptor.fragment = &fragment;
457
458 // Depth stencil state
459 const auto& depthStencilSettings = step->depthStencilSettings();
460 SkASSERT(depthStencilSettings.fDepthTestEnabled ||
461 depthStencilSettings.fDepthCompareOp == CompareOp::kAlways);
462 wgpu::DepthStencilState depthStencil;
463 if (renderPassDesc.fDepthStencilAttachment.fTextureInfo.isValid()) {
464 wgpu::TextureFormat dsFormat = TextureInfos::GetDawnViewFormat(
465 renderPassDesc.fDepthStencilAttachment.fTextureInfo);
466 depthStencil.format =
467 DawnFormatIsDepthOrStencil(dsFormat) ? dsFormat : wgpu::TextureFormat::Undefined;
468 if (depthStencilSettings.fDepthTestEnabled) {
469 depthStencil.depthWriteEnabled = depthStencilSettings.fDepthWriteEnabled;
470 }
471 depthStencil.depthCompare = compare_op_to_dawn(depthStencilSettings.fDepthCompareOp);
472
473 // Dawn validation fails if the stencil state is non-default and the
474 // format doesn't have the stencil aspect.
475 if (DawnFormatIsStencil(dsFormat) && depthStencilSettings.fStencilTestEnabled) {
476 depthStencil.stencilFront = stencil_face_to_dawn(depthStencilSettings.fFrontStencil);
477 depthStencil.stencilBack = stencil_face_to_dawn(depthStencilSettings.fBackStencil);
478 depthStencil.stencilReadMask = depthStencilSettings.fFrontStencil.fReadMask;
479 depthStencil.stencilWriteMask = depthStencilSettings.fFrontStencil.fWriteMask;
480 }
481
482 descriptor.depthStencil = &depthStencil;
483 }
484
485 // Determine the BindGroupLayouts that will be used to make up the pipeline layout.
486 BindGroupLayouts groupLayouts;
487 // If immutable samplers are used with this pipeline, they must be included in the pipeline
488 // layout and passed in to the pipline constructor for lifetime management.
489 skia_private::TArray<sk_sp<DawnSampler>> immutableSamplers;
490 {
491 SkASSERT(resourceProvider);
492 groupLayouts[0] = resourceProvider->getOrCreateUniformBuffersBindGroupLayout();
493 if (!groupLayouts[0]) {
494 return {};
495 }
496
497 bool hasFragmentSamplers = hasFragmentSkSL && numTexturesAndSamplers > 0;
498 if (hasFragmentSamplers) {
499 // Check if we can optimize for the common case of a single texture + 1 dynamic sampler
500 if (numTexturesAndSamplers == 2 &&
501 !(samplerDescArrPtr && samplerDescArrPtr->at(0).isImmutable())) {
502 groupLayouts[1] =
503 resourceProvider->getOrCreateSingleTextureSamplerBindGroupLayout();
504 } else {
505 std::vector<wgpu::BindGroupLayoutEntry> entries(numTexturesAndSamplers);
506 #if !defined(__EMSCRIPTEN__)
507 // Static sampler layouts are passed into Dawn by address and therefore must stay
508 // alive until the BindGroupLayoutDescriptor is created. So, store them outside of
509 // the loop that iterates over each BindGroupLayoutEntry.
510 skia_private::TArray<wgpu::StaticSamplerBindingLayout> staticSamplerLayouts;
511
512 // Note that the number of samplers is equivalent to numTexturesAndSamplers / 2. So,
513 // a sampler's index within any container that only pertains to sampler information
514 // (as in, excludes textures) is equivalent to 1/2 of that sampler's binding index
515 // within the BindGroupLayout. Assert that we have analyzed the appropriate number
516 // of samplers by equating samplerDescArr size to sampler quantity.
517 SkASSERT(samplerDescArrPtr && samplerDescArr.size() == numTexturesAndSamplers / 2);
518 #endif
519
520 for (int i = 0; i < numTexturesAndSamplers;) {
521 entries[i].binding = i;
522 entries[i].visibility = wgpu::ShaderStage::Fragment;
523 #if !defined(__EMSCRIPTEN__)
524 // Index of sampler information = 1/2 of cumulative texture and sampler index.
525 // If we have a non-default-initialized SamplerDesc at that index,
526 // fetch an immutable sampler that matches that description to include in the
527 // pipeline layout.
528 const SamplerDesc& samplerDesc = samplerDescArr.at(i/2);
529 if (samplerDesc.isImmutable()) {
530 sk_sp<Sampler> immutableSampler =
531 resourceProvider->findOrCreateCompatibleSampler(samplerDesc);
532 if (!immutableSampler) {
533 SKGPU_LOG_E("Failed to find/create immutable sampler for pipeline");
534 return {};
535 }
536 sk_sp<DawnSampler> dawnImmutableSampler = sk_ref_sp<DawnSampler>(
537 static_cast<DawnSampler*>(immutableSampler.get()));
538 SkASSERT(dawnImmutableSampler);
539
540 wgpu::StaticSamplerBindingLayout& immutableSamplerBinding =
541 staticSamplerLayouts.emplace_back();
542 immutableSamplerBinding.sampler = dawnImmutableSampler->dawnSampler();
543 // Static samplers sample from the subsequent texture in the BindGroupLayout
544 immutableSamplerBinding.sampledTextureBinding = i + 1;
545
546 immutableSamplers.push_back(std::move(dawnImmutableSampler));
547 entries[i].nextInChain = &immutableSamplerBinding;
548 } else {
549 #endif
550 entries[i].sampler.type = wgpu::SamplerBindingType::Filtering;
551 #if !defined(__EMSCRIPTEN__)
552 }
553 #endif
554 ++i;
555
556 entries[i].binding = i;
557 entries[i].visibility = wgpu::ShaderStage::Fragment;
558 entries[i].texture.sampleType = wgpu::TextureSampleType::Float;
559 entries[i].texture.viewDimension = wgpu::TextureViewDimension::e2D;
560 entries[i].texture.multisampled = false;
561 ++i;
562 }
563
564 wgpu::BindGroupLayoutDescriptor groupLayoutDesc;
565 if (sharedContext->caps()->setBackendLabels()) {
566 groupLayoutDesc.label = shaderInfo->vsLabel().c_str();
567 }
568 groupLayoutDesc.entryCount = entries.size();
569 groupLayoutDesc.entries = entries.data();
570 groupLayouts[1] = device.CreateBindGroupLayout(&groupLayoutDesc);
571 }
572 if (!groupLayouts[1]) {
573 return {};
574 }
575 }
576
577 wgpu::PipelineLayoutDescriptor layoutDesc;
578 if (sharedContext->caps()->setBackendLabels()) {
579 layoutDesc.label = shaderInfo->fsLabel().c_str();
580 }
581 layoutDesc.bindGroupLayoutCount =
582 hasFragmentSamplers ? groupLayouts.size() : groupLayouts.size() - 1;
583 layoutDesc.bindGroupLayouts = groupLayouts.data();
584 auto layout = device.CreatePipelineLayout(&layoutDesc);
585 if (!layout) {
586 return {};
587 }
588 descriptor.layout = std::move(layout);
589 }
590
591 // Vertex state
592 std::array<wgpu::VertexBufferLayout, kNumVertexBuffers> vertexBufferLayouts;
593 // Vertex buffer layout
594 std::vector<wgpu::VertexAttribute> vertexAttributes;
595 {
596 auto arrayStride = create_vertex_attributes(step->vertexAttributes(),
597 0,
598 &vertexAttributes);
599 auto& layout = vertexBufferLayouts[kVertexBufferIndex];
600 if (arrayStride) {
601 layout.arrayStride = arrayStride;
602 layout.stepMode = wgpu::VertexStepMode::Vertex;
603 layout.attributeCount = vertexAttributes.size();
604 layout.attributes = vertexAttributes.data();
605 } else {
606 layout.arrayStride = 0;
607 #if defined(__EMSCRIPTEN__)
608 layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
609 #else
610 layout.stepMode = wgpu::VertexStepMode::Undefined;
611 #endif
612 layout.attributeCount = 0;
613 layout.attributes = nullptr;
614 }
615 }
616
617 // Instance buffer layout
618 std::vector<wgpu::VertexAttribute> instanceAttributes;
619 {
620 auto arrayStride = create_vertex_attributes(step->instanceAttributes(),
621 step->vertexAttributes().size(),
622 &instanceAttributes);
623 auto& layout = vertexBufferLayouts[kInstanceBufferIndex];
624 if (arrayStride) {
625 layout.arrayStride = arrayStride;
626 layout.stepMode = wgpu::VertexStepMode::Instance;
627 layout.attributeCount = instanceAttributes.size();
628 layout.attributes = instanceAttributes.data();
629 } else {
630 layout.arrayStride = 0;
631 #if defined(__EMSCRIPTEN__)
632 layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed;
633 #else
634 layout.stepMode = wgpu::VertexStepMode::Undefined;
635 #endif
636 layout.attributeCount = 0;
637 layout.attributes = nullptr;
638 }
639 }
640
641 auto& vertex = descriptor.vertex;
642 vertex.module = std::move(vsModule);
643 vertex.entryPoint = "main";
644 vertex.constantCount = 0;
645 vertex.constants = nullptr;
646 vertex.bufferCount = vertexBufferLayouts.size();
647 vertex.buffers = vertexBufferLayouts.data();
648
649 // Other state
650 descriptor.primitive.frontFace = wgpu::FrontFace::CCW;
651 descriptor.primitive.cullMode = wgpu::CullMode::None;
652 switch (step->primitiveType()) {
653 case PrimitiveType::kTriangles:
654 descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
655 break;
656 case PrimitiveType::kTriangleStrip:
657 descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip;
658 descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Uint16;
659 break;
660 case PrimitiveType::kPoints:
661 descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList;
662 break;
663 }
664
665 // Multisampled state
666 descriptor.multisample.count = renderPassDesc.fSampleCount;
667 descriptor.multisample.mask = 0xFFFFFFFF;
668 descriptor.multisample.alphaToCoverageEnabled = false;
669
670 const bool forPrecompilation =
671 SkToBool(pipelineCreationFlags & PipelineCreationFlags::kForPrecompilation);
672 // For Dawn, we want Precompilation to happen synchronously
673 const bool useAsync = caps.useAsyncPipelineCreation() && !forPrecompilation;
674
675 auto asyncCreation = std::make_unique<AsyncPipelineCreation>(pipelineKey);
676 #if SK_HISTOGRAMS_ENABLED
677 asyncCreation->fStartTime = skgpu::StdSteadyClock::now();
678 asyncCreation->fFromPrecompile = forPrecompilation;
679 asyncCreation->fAsynchronous = useAsync;
680 #endif
681
682 if (useAsync) {
683 #if defined(__EMSCRIPTEN__)
684 // We shouldn't use CreateRenderPipelineAsync in wasm.
685 SKGPU_LOG_F("CreateRenderPipelineAsync shouldn't be used in WASM");
686 #else
687 asyncCreation->fFuture = device.CreateRenderPipelineAsync(
688 &descriptor,
689 wgpu::CallbackMode::WaitAnyOnly,
690 [asyncCreationPtr = asyncCreation.get()](wgpu::CreatePipelineAsyncStatus status,
691 wgpu::RenderPipeline pipeline,
692 char const* message) {
693 if (status != wgpu::CreatePipelineAsyncStatus::Success) {
694 SKGPU_LOG_E("Failed to create render pipeline (%d): %s",
695 static_cast<int>(status),
696 message);
697 // invalidate AsyncPipelineCreation pointer to signal that this pipeline has
698 // failed.
699 asyncCreationPtr->fRenderPipeline = nullptr;
700 } else {
701 asyncCreationPtr->fRenderPipeline = std::move(pipeline);
702 }
703
704 asyncCreationPtr->fFinished = true;
705
706 log_pipeline_creation(asyncCreationPtr);
707 });
708 #endif
709 } else {
710 std::optional<DawnErrorChecker> errorChecker;
711 if (sharedContext->dawnCaps()->allowScopedErrorChecks()) {
712 errorChecker.emplace(sharedContext);
713 }
714 asyncCreation->fRenderPipeline = device.CreateRenderPipeline(&descriptor);
715 asyncCreation->fFinished = true;
716
717 if (errorChecker.has_value() && errorChecker->popErrorScopes() != DawnErrorType::kNoError) {
718 asyncCreation->fRenderPipeline = nullptr;
719 }
720
721 log_pipeline_creation(asyncCreation.get());
722 }
723
724 PipelineInfo pipelineInfo{ *shaderInfo, pipelineCreationFlags,
725 pipelineKey.hash(), compilationID };
726 #if defined(GPU_TEST_UTILS)
727 pipelineInfo.fNativeVertexShader = std::move(vsCode);
728 pipelineInfo.fNativeFragmentShader = std::move(fsCode);
729 #endif
730
731 return sk_sp<DawnGraphicsPipeline>(
732 new DawnGraphicsPipeline(sharedContext,
733 pipelineInfo,
734 std::move(asyncCreation),
735 std::move(groupLayouts),
736 step->primitiveType(),
737 depthStencilSettings.fStencilReferenceValue,
738 std::move(immutableSamplers)));
739 }
740
DawnGraphicsPipeline(const skgpu::graphite::SharedContext * sharedContext,const PipelineInfo & pipelineInfo,std::unique_ptr<AsyncPipelineCreation> asyncCreationInfo,BindGroupLayouts groupLayouts,PrimitiveType primitiveType,uint32_t refValue,skia_private::TArray<sk_sp<DawnSampler>> immutableSamplers)741 DawnGraphicsPipeline::DawnGraphicsPipeline(
742 const skgpu::graphite::SharedContext* sharedContext,
743 const PipelineInfo& pipelineInfo,
744 std::unique_ptr<AsyncPipelineCreation> asyncCreationInfo,
745 BindGroupLayouts groupLayouts,
746 PrimitiveType primitiveType,
747 uint32_t refValue,
748 skia_private::TArray<sk_sp<DawnSampler>> immutableSamplers)
749 : GraphicsPipeline(sharedContext, pipelineInfo)
750 , fAsyncPipelineCreation(std::move(asyncCreationInfo))
751 , fGroupLayouts(std::move(groupLayouts))
752 , fPrimitiveType(primitiveType)
753 , fStencilReferenceValue(refValue)
754 , fImmutableSamplers(std::move(immutableSamplers)) {}
755
~DawnGraphicsPipeline()756 DawnGraphicsPipeline::~DawnGraphicsPipeline() {
757 this->freeGpuData();
758 }
759
freeGpuData()760 void DawnGraphicsPipeline::freeGpuData() {
761 // Wait for async creation to finish before we can destroy this object.
762 (void)this->dawnRenderPipeline();
763 fAsyncPipelineCreation = nullptr;
764 }
765
dawnRenderPipeline() const766 const wgpu::RenderPipeline& DawnGraphicsPipeline::dawnRenderPipeline() const {
767 if (!fAsyncPipelineCreation) {
768 static const wgpu::RenderPipeline kNullPipeline = nullptr;
769 return kNullPipeline;
770 }
771 if (fAsyncPipelineCreation->fFinished) {
772 return fAsyncPipelineCreation->fRenderPipeline;
773 }
774 #if defined(__EMSCRIPTEN__)
775 // We shouldn't use CreateRenderPipelineAsync in wasm.
776 SKGPU_LOG_F("CreateRenderPipelineAsync shouldn't be used in WASM");
777 #else
778
779 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
780 TRACE_EVENT_INSTANT2("skia.gpu",
781 TRACE_STR_STATIC("WaitBeginN"),
782 TRACE_EVENT_SCOPE_THREAD,
783 "key", fAsyncPipelineCreation->fKey.hash(),
784 "compilationID", this->getPipelineInfo().fCompilationID);
785 #endif
786
787 wgpu::FutureWaitInfo waitInfo{};
788 waitInfo.future = fAsyncPipelineCreation->fFuture;
789 const auto& instance = static_cast<const DawnSharedContext*>(sharedContext())
790 ->device()
791 .GetAdapter()
792 .GetInstance();
793
794 [[maybe_unused]] auto status =
795 instance.WaitAny(1, &waitInfo, /*timeoutNS=*/std::numeric_limits<uint64_t>::max());
796 SkASSERT(status == wgpu::WaitStatus::Success);
797 SkASSERT(waitInfo.completed);
798
799 #if defined(SK_PIPELINE_LIFETIME_LOGGING)
800 TRACE_EVENT_INSTANT2("skia.gpu",
801 TRACE_STR_STATIC("WaitEndN"),
802 TRACE_EVENT_SCOPE_THREAD,
803 "key", fAsyncPipelineCreation->fKey.hash(),
804 "compilationID", this->getPipelineInfo().fCompilationID);
805 #endif
806
807 #endif
808
809 return fAsyncPipelineCreation->fRenderPipeline;
810 }
811
812 } // namespace skgpu::graphite
813