| /* | |
| * Copyright 2022 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/gpu/graphite/dawn/DawnGraphicsPipeline.h" | |
| #include "include/gpu/graphite/TextureInfo.h" | |
| #include "include/gpu/graphite/dawn/DawnGraphiteTypes.h" | |
| #include "include/private/SkAttributes.h" | |
| #include "include/private/SkLog.h" | |
| #include "include/private/SkTArray.h" | |
| #include "src/core/SkTBlockList.h" | |
| #include "src/core/SkTraceEvent.h" | |
| #include "src/gpu/SkSLToBackend.h" | |
| #include "src/gpu/Swizzle.h" | |
| #include "src/gpu/graphite/Attribute.h" | |
| #include "src/gpu/graphite/ContextUtils.h" | |
| #include "src/gpu/graphite/GraphicsPipelineDesc.h" | |
| #include "src/gpu/graphite/RenderPassDesc.h" | |
| #include "src/gpu/graphite/RendererProvider.h" | |
| #include "src/gpu/graphite/ShaderInfo.h" | |
| #include "src/gpu/graphite/TextureInfoPriv.h" | |
| #include "src/gpu/graphite/ThreadSafeResourceProvider.h" | |
| #include "src/gpu/graphite/UniformManager.h" | |
| #include "src/gpu/graphite/dawn/DawnCaps.h" | |
| #include "src/gpu/graphite/dawn/DawnErrorChecker.h" | |
| #include "src/gpu/graphite/dawn/DawnGraphiteUtils.h" | |
| #include "src/gpu/graphite/dawn/DawnResourceProvider.h" | |
| #include "src/gpu/graphite/dawn/DawnSampler.h" | |
| #include "src/gpu/graphite/dawn/DawnSharedContext.h" | |
| #include "src/sksl/SkSLProgramSettings.h" | |
| #include "src/sksl/SkSLUtil.h" | |
| #include "src/sksl/ir/SkSLProgram.h" | |
| #include <atomic> | |
| using namespace skia_private; | |
| namespace skgpu::graphite { | |
| namespace { | |
| inline wgpu::VertexFormat attribute_type_to_dawn(VertexAttribType type) { | |
| switch (type) { | |
| case VertexAttribType::kFloat: | |
| return wgpu::VertexFormat::Float32; | |
| case VertexAttribType::kFloat2: | |
| return wgpu::VertexFormat::Float32x2; | |
| case VertexAttribType::kFloat3: | |
| return wgpu::VertexFormat::Float32x3; | |
| case VertexAttribType::kFloat4: | |
| return wgpu::VertexFormat::Float32x4; | |
| case VertexAttribType::kHalf2: | |
| return wgpu::VertexFormat::Float16x2; | |
| case VertexAttribType::kHalf4: | |
| return wgpu::VertexFormat::Float16x4; | |
| case VertexAttribType::kInt2: | |
| return wgpu::VertexFormat::Sint32x2; | |
| case VertexAttribType::kInt3: | |
| return wgpu::VertexFormat::Sint32x3; | |
| case VertexAttribType::kInt4: | |
| return wgpu::VertexFormat::Sint32x4; | |
| case VertexAttribType::kUInt2: | |
| return wgpu::VertexFormat::Uint32x2; | |
| case VertexAttribType::kByte2: | |
| return wgpu::VertexFormat::Sint8x2; | |
| case VertexAttribType::kByte4: | |
| return wgpu::VertexFormat::Sint8x4; | |
| case VertexAttribType::kUByte2: | |
| return wgpu::VertexFormat::Uint8x2; | |
| case VertexAttribType::kUByte4: | |
| return wgpu::VertexFormat::Uint8x4; | |
| case VertexAttribType::kUByte4_norm: | |
| return wgpu::VertexFormat::Unorm8x4; | |
| case VertexAttribType::kShort2: | |
| return wgpu::VertexFormat::Sint16x2; | |
| case VertexAttribType::kShort4: | |
| return wgpu::VertexFormat::Sint16x4; | |
| case VertexAttribType::kUShort2: | |
| return wgpu::VertexFormat::Uint16x2; | |
| case VertexAttribType::kUShort2_norm: | |
| return wgpu::VertexFormat::Unorm16x2; | |
| case VertexAttribType::kInt: | |
| return wgpu::VertexFormat::Sint32; | |
| case VertexAttribType::kUInt: | |
| return wgpu::VertexFormat::Uint32; | |
| case VertexAttribType::kUShort4_norm: | |
| return wgpu::VertexFormat::Unorm16x4; | |
| case VertexAttribType::kHalf: | |
| case VertexAttribType::kByte: | |
| case VertexAttribType::kUByte: | |
| case VertexAttribType::kUByte_norm: | |
| case VertexAttribType::kUShort_norm: | |
| // Not supported. | |
| break; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| wgpu::CompareFunction compare_op_to_dawn(CompareOp op) { | |
| switch (op) { | |
| case CompareOp::kAlways: | |
| return wgpu::CompareFunction::Always; | |
| case CompareOp::kNever: | |
| return wgpu::CompareFunction::Never; | |
| case CompareOp::kGreater: | |
| return wgpu::CompareFunction::Greater; | |
| case CompareOp::kGEqual: | |
| return wgpu::CompareFunction::GreaterEqual; | |
| case CompareOp::kLess: | |
| return wgpu::CompareFunction::Less; | |
| case CompareOp::kLEqual: | |
| return wgpu::CompareFunction::LessEqual; | |
| case CompareOp::kEqual: | |
| return wgpu::CompareFunction::Equal; | |
| case CompareOp::kNotEqual: | |
| return wgpu::CompareFunction::NotEqual; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| wgpu::StencilOperation stencil_op_to_dawn(StencilOp op) { | |
| switch (op) { | |
| case StencilOp::kKeep: | |
| return wgpu::StencilOperation::Keep; | |
| case StencilOp::kZero: | |
| return wgpu::StencilOperation::Zero; | |
| case StencilOp::kReplace: | |
| return wgpu::StencilOperation::Replace; | |
| case StencilOp::kInvert: | |
| return wgpu::StencilOperation::Invert; | |
| case StencilOp::kIncWrap: | |
| return wgpu::StencilOperation::IncrementWrap; | |
| case StencilOp::kDecWrap: | |
| return wgpu::StencilOperation::DecrementWrap; | |
| case StencilOp::kIncClamp: | |
| return wgpu::StencilOperation::IncrementClamp; | |
| case StencilOp::kDecClamp: | |
| return wgpu::StencilOperation::DecrementClamp; | |
| } | |
| SkUNREACHABLE; | |
| } | |
| wgpu::StencilFaceState stencil_face_to_dawn(StencilSettings::Face face) { | |
| wgpu::StencilFaceState state; | |
| state.compare = compare_op_to_dawn(face.fCompareOp); | |
| state.failOp = stencil_op_to_dawn(face.fStencilFailOp); | |
| state.depthFailOp = stencil_op_to_dawn(face.fDepthFailOp); | |
| state.passOp = stencil_op_to_dawn(face.fDepthStencilPassOp); | |
| return state; | |
| } | |
| size_t create_vertex_attributes(SkSpan<const Attribute> attrs, | |
| int shaderLocationOffset, | |
| TArray<wgpu::VertexAttribute>* out) { | |
| SkASSERT(out && out->empty()); | |
| out->resize(attrs.size()); | |
| size_t vertexAttributeOffset = 0; | |
| int attributeIndex = 0; | |
| for (const auto& attr : attrs) { | |
| wgpu::VertexAttribute& vertexAttribute = (*out)[attributeIndex]; | |
| vertexAttribute.format = attribute_type_to_dawn(attr.cpuType()); | |
| vertexAttribute.offset = vertexAttributeOffset; | |
| vertexAttribute.shaderLocation = shaderLocationOffset + attributeIndex; | |
| vertexAttributeOffset += attr.sizeAlign4(); | |
| attributeIndex++; | |
| } | |
| return vertexAttributeOffset; | |
| } | |
| // TODO: share this w/ Ganesh dawn backend? | |
| static wgpu::BlendFactor blend_coeff_to_dawn_blend(const DawnCaps& caps, skgpu::BlendCoeff coeff) { | |
| #if defined(__EMSCRIPTEN__) | |
| #define VALUE_IF_DSB_OR_ZERO(VALUE) wgpu::BlendFactor::Zero | |
| #else | |
| #define VALUE_IF_DSB_OR_ZERO(VALUE) \ | |
| ((caps.shaderCaps()->fDualSourceBlendingSupport) ? (VALUE) : wgpu::BlendFactor::Zero) | |
| #endif | |
| switch (coeff) { | |
| case skgpu::BlendCoeff::kZero: | |
| return wgpu::BlendFactor::Zero; | |
| case skgpu::BlendCoeff::kOne: | |
| return wgpu::BlendFactor::One; | |
| case skgpu::BlendCoeff::kSC: | |
| return wgpu::BlendFactor::Src; | |
| case skgpu::BlendCoeff::kISC: | |
| return wgpu::BlendFactor::OneMinusSrc; | |
| case skgpu::BlendCoeff::kDC: | |
| return wgpu::BlendFactor::Dst; | |
| case skgpu::BlendCoeff::kIDC: | |
| return wgpu::BlendFactor::OneMinusDst; | |
| case skgpu::BlendCoeff::kSA: | |
| return wgpu::BlendFactor::SrcAlpha; | |
| case skgpu::BlendCoeff::kISA: | |
| return wgpu::BlendFactor::OneMinusSrcAlpha; | |
| case skgpu::BlendCoeff::kDA: | |
| return wgpu::BlendFactor::DstAlpha; | |
| case skgpu::BlendCoeff::kIDA: | |
| return wgpu::BlendFactor::OneMinusDstAlpha; | |
| case skgpu::BlendCoeff::kConstC: | |
| return wgpu::BlendFactor::Constant; | |
| case skgpu::BlendCoeff::kIConstC: | |
| return wgpu::BlendFactor::OneMinusConstant; | |
| case skgpu::BlendCoeff::kS2C: | |
| return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::Src1); | |
| case skgpu::BlendCoeff::kIS2C: | |
| return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::OneMinusSrc1); | |
| case skgpu::BlendCoeff::kS2A: | |
| return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::Src1Alpha); | |
| case skgpu::BlendCoeff::kIS2A: | |
| return VALUE_IF_DSB_OR_ZERO(wgpu::BlendFactor::OneMinusSrc1Alpha); | |
| case skgpu::BlendCoeff::kIllegal: | |
| return wgpu::BlendFactor::Zero; | |
| } | |
| SkUNREACHABLE; | |
| #undef VALUE_IF_DSB_OR_ZERO | |
| } | |
| static wgpu::BlendFactor blend_coeff_to_dawn_blend_for_alpha(const DawnCaps& caps, | |
| skgpu::BlendCoeff coeff) { | |
| switch (coeff) { | |
| // Force all srcColor used in alpha slot to alpha version. | |
| case skgpu::BlendCoeff::kSC: | |
| return wgpu::BlendFactor::SrcAlpha; | |
| case skgpu::BlendCoeff::kISC: | |
| return wgpu::BlendFactor::OneMinusSrcAlpha; | |
| case skgpu::BlendCoeff::kDC: | |
| return wgpu::BlendFactor::DstAlpha; | |
| case skgpu::BlendCoeff::kIDC: | |
| return wgpu::BlendFactor::OneMinusDstAlpha; | |
| default: | |
| return blend_coeff_to_dawn_blend(caps, coeff); | |
| } | |
| } | |
| // TODO: share this w/ Ganesh Metal backend? | |
| static wgpu::BlendOperation blend_equation_to_dawn_blend_op(skgpu::BlendEquation equation) { | |
| static const wgpu::BlendOperation gTable[] = { | |
| wgpu::BlendOperation::Add, // skgpu::BlendEquation::kAdd | |
| wgpu::BlendOperation::Subtract, // skgpu::BlendEquation::kSubtract | |
| wgpu::BlendOperation::ReverseSubtract, // skgpu::BlendEquation::kReverseSubtract | |
| }; | |
| static_assert(std::size(gTable) == (int)skgpu::BlendEquation::kFirstAdvanced); | |
| static_assert(0 == (int)skgpu::BlendEquation::kAdd); | |
| static_assert(1 == (int)skgpu::BlendEquation::kSubtract); | |
| static_assert(2 == (int)skgpu::BlendEquation::kReverseSubtract); | |
| SkASSERT((unsigned)equation < skgpu::kBlendEquationCnt); | |
| return SK_UNSAFE_TODO(gTable[(int)equation]); | |
| } | |
| struct AsyncPipelineCreationBase { | |
| AsyncPipelineCreationBase(const UniqueKey& key) : fKey(key) {} | |
| wgpu::RenderPipeline fRenderPipeline; | |
| std::string fErrorMessage; | |
| std::atomic<bool> fFinished = false; | |
| UniqueKey fKey; // for logging the wait to resolve a Pipeline future in dawnRenderPipeline | |
| #if SK_HISTOGRAMS_ENABLED | |
| // We need these three for the Graphite.PipelineCreationTimes.* histograms (cf. | |
| // log_pipeline_creation) | |
| skgpu::StdSteadyClock::time_point fStartTime; | |
| bool fFromPrecompile; | |
| bool fAsynchronous = false; | |
| #endif | |
| }; | |
| void log_pipeline_creation(const AsyncPipelineCreationBase* apcb) { | |
| #if SK_HISTOGRAMS_ENABLED | |
| [[maybe_unused]] static constexpr int kBucketCount = 100; | |
| [[maybe_unused]] static constexpr int kOneSecInUS = 1000000; | |
| SkASSERT(apcb->fFinished); | |
| if (!apcb->fRenderPipeline) { | |
| // A null fRenderPipeline means Pipeline creation failed | |
| return; // TODO: log failures to their own UMA stat | |
| } | |
| [[maybe_unused]] auto micros_since = [](skgpu::StdSteadyClock::time_point start) { | |
| skgpu::StdSteadyClock::duration elapsed = skgpu::StdSteadyClock::now() - start; | |
| return std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count(); | |
| }; | |
| if (apcb->fFromPrecompile) { | |
| SkASSERT(!apcb->fAsynchronous); // precompile is done synchronously on a thread | |
| SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( | |
| "Graphite.PipelineCreationTimes.Precompile", | |
| micros_since(apcb->fStartTime), | |
| /* minUSec= */ 1, | |
| /* maxUSec= */ kOneSecInUS, | |
| kBucketCount); | |
| } else if (apcb->fAsynchronous) { | |
| SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( | |
| "Graphite.PipelineCreationTimes.Asynchronous", | |
| micros_since(apcb->fStartTime), | |
| /* minUSec= */ 1, | |
| /* maxUSec= */ kOneSecInUS, | |
| kBucketCount); | |
| } else { | |
| SK_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( | |
| "Graphite.PipelineCreationTimes.Synchronous", | |
| micros_since(apcb->fStartTime), | |
| /* minUSec= */ 1, | |
| /* maxUSec= */ kOneSecInUS, | |
| kBucketCount); | |
| } | |
| #endif // SK_HISTOGRAMS_ENABLED | |
| } | |
| } // anonymous namespace | |
| #if defined(__EMSCRIPTEN__) | |
| // For wasm, we don't use async compilation. | |
| struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase { | |
| AsyncPipelineCreation(const UniqueKey& key) : AsyncPipelineCreationBase(key) {} | |
| }; | |
| #else | |
| struct DawnGraphicsPipeline::AsyncPipelineCreation : public AsyncPipelineCreationBase { | |
| AsyncPipelineCreation(const UniqueKey& key) : AsyncPipelineCreationBase(key) {} | |
| wgpu::Future fFuture; | |
| }; | |
| #endif | |
| // static | |
| sk_sp<DawnGraphicsPipeline> DawnGraphicsPipeline::Make( | |
| const DawnSharedContext* sharedContext, | |
| const RuntimeEffectDictionary* runtimeDict, | |
| const UniqueKey& pipelineKey, | |
| const GraphicsPipelineDesc& pipelineDesc, | |
| const RenderPassDesc& renderPassDesc, | |
| SkEnumBitMask<PipelineCreationFlags> pipelineCreationFlags, | |
| uint32_t compilationID) { | |
| const DawnCaps& caps = *static_cast<const DawnCaps*>(sharedContext->caps()); | |
| const auto& device = sharedContext->device(); | |
| SkSL::Program::Interface vsInterface, fsInterface; | |
| SkSL::ProgramSettings settings; | |
| settings.fSharpenTextures = true; | |
| settings.fForceNoRTFlip = true; | |
| settings.fForceHighPrecision = !caps.supportsHalfPrecision(); | |
| ShaderErrorHandler* errorHandler = caps.shaderErrorHandler(); | |
| const RenderStep* step = sharedContext->rendererProvider()->lookup(pipelineDesc.renderStepID()); | |
| SkSL::NativeShader vsCode, fsCode; | |
| wgpu::ShaderModule fsModule, vsModule; | |
| // Some steps just render depth buffer but not color buffer, so the fragment | |
| // shader is null. | |
| UniquePaintParamsID paintID = pipelineDesc.paintParamsID(); | |
| skia_private::TArray<SamplerDesc>* samplerDescArrPtr = nullptr; | |
| #if !defined(__EMSCRIPTEN__) | |
| skia_private::TArray<SamplerDesc> samplerDescArr {}; | |
| samplerDescArrPtr = &samplerDescArr; | |
| #endif | |
| std::unique_ptr<ShaderInfo> shaderInfo = | |
| ShaderInfo::Make(&caps, | |
| sharedContext->shaderCodeDictionary(), | |
| runtimeDict, | |
| renderPassDesc, | |
| step, | |
| paintID, | |
| samplerDescArrPtr); | |
| const std::string& fsSkSL = shaderInfo->fragmentSkSL(); | |
| const BlendInfo& blendInfo = shaderInfo->blendInfo(); | |
| const int numTexturesAndSamplers = shaderInfo->numFragmentTexturesAndSamplers(); | |
| const bool hasFragmentSkSL = !fsSkSL.empty(); | |
| if (hasFragmentSkSL) { | |
| if (!skgpu::SkSLToWGSL(caps.shaderCaps(), | |
| fsSkSL, | |
| SkSL::ProgramKind::kGraphiteFragment, | |
| settings, | |
| &fsCode, | |
| &fsInterface, | |
| errorHandler)) { | |
| return {}; | |
| } | |
| if (!DawnCompileWGSLShaderModule(sharedContext, shaderInfo->fsLabel().c_str(), fsCode, | |
| &fsModule, errorHandler)) { | |
| return {}; | |
| } | |
| } | |
| const std::string& vsSkSL = shaderInfo->vertexSkSL(); | |
| if (!skgpu::SkSLToWGSL(caps.shaderCaps(), | |
| vsSkSL, | |
| SkSL::ProgramKind::kGraphiteVertex, | |
| settings, | |
| &vsCode, | |
| &vsInterface, | |
| errorHandler)) { | |
| return {}; | |
| } | |
| if (!DawnCompileWGSLShaderModule(sharedContext, shaderInfo->vsLabel().c_str(), vsCode, | |
| &vsModule, errorHandler)) { | |
| return {}; | |
| } | |
| wgpu::RenderPipelineDescriptor descriptor; | |
| // Always set the label for pipelines, dawn may need it for tracing. | |
| descriptor.label = shaderInfo->pipelineLabel().c_str(); | |
| // Fragment state | |
| skgpu::BlendEquation equation = blendInfo.fEquation; | |
| skgpu::BlendCoeff srcCoeff = blendInfo.fSrcBlend; | |
| skgpu::BlendCoeff dstCoeff = blendInfo.fDstBlend; | |
| bool blendOn = !skgpu::BlendShouldDisable(equation, srcCoeff, dstCoeff); | |
| wgpu::BlendState blend; | |
| if (blendOn) { | |
| blend.color.operation = blend_equation_to_dawn_blend_op(equation); | |
| blend.color.srcFactor = blend_coeff_to_dawn_blend(caps, srcCoeff); | |
| blend.color.dstFactor = blend_coeff_to_dawn_blend(caps, dstCoeff); | |
| blend.alpha.operation = blend_equation_to_dawn_blend_op(equation); | |
| blend.alpha.srcFactor = blend_coeff_to_dawn_blend_for_alpha(caps, srcCoeff); | |
| blend.alpha.dstFactor = blend_coeff_to_dawn_blend_for_alpha(caps, dstCoeff); | |
| } | |
| wgpu::ColorTargetState colorTarget; | |
| colorTarget.format = TextureFormatToDawnFormat(renderPassDesc.fColorAttachment.fFormat); | |
| colorTarget.blend = blendOn ? &blend : nullptr; | |
| colorTarget.writeMask = blendInfo.fWritesColor && hasFragmentSkSL ? wgpu::ColorWriteMask::All | |
| : wgpu::ColorWriteMask::None; | |
| #if !defined(__EMSCRIPTEN__) | |
| const bool loadMsaaFromResolve = | |
| renderPassDesc.fColorResolveAttachment.fFormat != TextureFormat::kUnsupported && | |
| renderPassDesc.fColorResolveAttachment.fLoadOp == LoadOp::kLoad; | |
| // Special case: a render pass loading resolve texture requires additional settings for the | |
| // pipeline to make it compatible. | |
| wgpu::ColorTargetStateExpandResolveTextureDawn pipelineMSAALoadResolveTextureDesc; | |
| if (loadMsaaFromResolve && sharedContext->dawnCaps()->loadOpAffectsMSAAPipelines()) { | |
| SkASSERT(device.HasFeature(wgpu::FeatureName::DawnLoadResolveTexture)); | |
| colorTarget.nextInChain = &pipelineMSAALoadResolveTextureDesc; | |
| pipelineMSAALoadResolveTextureDesc.enabled = true; | |
| } | |
| #endif | |
| wgpu::FragmentState fragment; | |
| // Dawn doesn't allow having a color attachment but without fragment shader, so have to use a | |
| // noop fragment shader, if fragment shader is null. | |
| fragment.module = hasFragmentSkSL ? std::move(fsModule) : sharedContext->noopFragment(); | |
| fragment.entryPoint = "main"; | |
| fragment.constantCount = 0; | |
| fragment.constants = nullptr; | |
| fragment.targetCount = 1; | |
| fragment.targets = &colorTarget; | |
| descriptor.fragment = &fragment; | |
| // Depth stencil state | |
| const auto& [depthSettings, stencilSettings] = shaderInfo->depthStencilSettings(); | |
| TextureFormat dsFormat = renderPassDesc.fDepthStencilAttachment.fFormat; | |
| SkASSERT(dsFormat != TextureFormat::kUnsupported || !(depthSettings || stencilSettings)); | |
| wgpu::DepthStencilState depthStencil; | |
| if (dsFormat != TextureFormat::kUnsupported) { | |
| SkASSERT(TextureFormatIsDepthOrStencil(dsFormat)); | |
| depthStencil.format = TextureFormatToDawnFormat(dsFormat); | |
| if (depthSettings.enabled()) { | |
| SkASSERT(TextureFormatHasDepth(dsFormat)); | |
| depthStencil.depthWriteEnabled = depthSettings.fWriteEnabled; | |
| depthStencil.depthCompare = compare_op_to_dawn(depthSettings.fCompareOp); | |
| } | |
| if (stencilSettings.enabled()) { | |
| SkASSERT(TextureFormatHasStencil(dsFormat)); | |
| depthStencil.stencilFront = stencil_face_to_dawn(stencilSettings.fFrontFace); | |
| depthStencil.stencilBack = stencil_face_to_dawn(stencilSettings.fBackFace); | |
| depthStencil.stencilReadMask = stencilSettings.fReadMask; | |
| depthStencil.stencilWriteMask = stencilSettings.fWriteMask; | |
| // NOTE: fStencilReferenceValue is dynamic state and set on the render encoder | |
| } | |
| descriptor.depthStencil = &depthStencil; | |
| } | |
| // Determine the BindGroupLayouts that will be used to make up the pipeline layout. | |
| BindGroupLayouts groupLayouts; | |
| // If immutable samplers are used with this pipeline, they must be included in the pipeline | |
| // layout and passed in to the pipline constructor for lifetime management. | |
| skia_private::TArray<sk_sp<DawnSampler>> immutableSamplers; | |
| { | |
| wgpu::ShaderStage storageVisibility = wgpu::ShaderStage::None; | |
| if (shaderInfo->vsUsesStorage()) { | |
| storageVisibility |= wgpu::ShaderStage::Vertex; | |
| } | |
| if (shaderInfo->fsUsesStorage()) { | |
| storageVisibility |= wgpu::ShaderStage::Fragment; | |
| } | |
| groupLayouts[0] = sharedContext->getUniformBuffersBindGroupLayout(storageVisibility); | |
| if (!groupLayouts[0]) { | |
| return {}; | |
| } | |
| bool hasFragmentSamplers = hasFragmentSkSL && numTexturesAndSamplers > 0; | |
| if (hasFragmentSamplers) { | |
| // Check if we can optimize for the common case of a single texture + 1 dynamic sampler | |
| if (numTexturesAndSamplers == 2 && | |
| !(samplerDescArrPtr && samplerDescArrPtr->at(0).isImmutable())) { | |
| groupLayouts[1] = sharedContext->getSingleTextureSamplerBindGroupLayout(); | |
| } else { | |
| TArray<wgpu::BindGroupLayoutEntry> entries; | |
| entries.reset(numTexturesAndSamplers); | |
| #if !defined(__EMSCRIPTEN__) | |
| // Static sampler layouts are passed into Dawn by address and therefore must stay | |
| // alive until the BindGroupLayoutDescriptor is created. So, store them outside of | |
| // the loop that iterates over each BindGroupLayoutEntry. Use a TBlockList so that | |
| // any append StaticSamplerBindingLayouts have a stable address in case we have to | |
| // grow the collection. | |
| SkTBlockList<wgpu::StaticSamplerBindingLayout, 1> staticSamplerLayouts; | |
| // Note that the number of samplers is equivalent to numTexturesAndSamplers / 2. So, | |
| // a sampler's index within any container that only pertains to sampler information | |
| // (as in, excludes textures) is equivalent to 1/2 of that sampler's binding index | |
| // within the BindGroupLayout. Assert that we have analyzed the appropriate number | |
| // of samplers by equating samplerDescArr size to sampler quantity. | |
| SkASSERT(samplerDescArrPtr && samplerDescArr.size() == numTexturesAndSamplers / 2); | |
| immutableSamplers.reset(samplerDescArr.size()); | |
| #endif | |
| for (int i = 0; i < numTexturesAndSamplers;) { | |
| entries[i].binding = i; | |
| entries[i].visibility = wgpu::ShaderStage::Fragment; | |
| #if !defined(__EMSCRIPTEN__) | |
| // Index of sampler information = 1/2 of cumulative texture and sampler index. | |
| // If we have a non-default-initialized SamplerDesc at that index, | |
| // fetch an immutable sampler that matches that description to include in the | |
| // pipeline layout. | |
| const SamplerDesc& samplerDesc = samplerDescArr.at(i/2); | |
| if (samplerDesc.isImmutable()) { | |
| DawnThreadSafeResourceProvider* resourceProvider = | |
| sharedContext->threadSafeResourceProvider(); | |
| sk_sp<Sampler> immutableSampler = | |
| resourceProvider->findOrCreateCompatibleSampler(samplerDesc); | |
| if (!immutableSampler) { | |
| SKIA_LOG_E("Failed to find/create immutable sampler for pipeline"); | |
| return {}; | |
| } | |
| sk_sp<DawnSampler> dawnImmutableSampler = sk_ref_sp<DawnSampler>( | |
| static_cast<DawnSampler*>(immutableSampler.get())); | |
| SkASSERT(dawnImmutableSampler); | |
| wgpu::StaticSamplerBindingLayout& immutableSamplerBinding = | |
| staticSamplerLayouts.emplace_back(); | |
| immutableSamplerBinding.sampler = dawnImmutableSampler->dawnSampler(); | |
| // Static samplers sample from the subsequent texture in the BindGroupLayout | |
| immutableSamplerBinding.sampledTextureBinding = i + 1; | |
| immutableSamplers[i/2] = std::move(dawnImmutableSampler); | |
| entries[i].nextInChain = &immutableSamplerBinding; | |
| } else { | |
| #endif | |
| entries[i].sampler.type = wgpu::SamplerBindingType::Filtering; | |
| #if !defined(__EMSCRIPTEN__) | |
| } | |
| #endif | |
| ++i; | |
| entries[i].binding = i; | |
| entries[i].visibility = wgpu::ShaderStage::Fragment; | |
| entries[i].texture.sampleType = wgpu::TextureSampleType::Float; | |
| entries[i].texture.viewDimension = wgpu::TextureViewDimension::e2D; | |
| entries[i].texture.multisampled = false; | |
| ++i; | |
| } | |
| wgpu::BindGroupLayoutDescriptor groupLayoutDesc; | |
| if (sharedContext->caps()->setBackendLabels()) { | |
| groupLayoutDesc.label = shaderInfo->vsLabel().c_str(); | |
| } | |
| groupLayoutDesc.entryCount = entries.size(); | |
| groupLayoutDesc.entries = entries.data(); | |
| groupLayouts[1] = device.CreateBindGroupLayout(&groupLayoutDesc); | |
| } | |
| if (!groupLayouts[1]) { | |
| return {}; | |
| } | |
| } | |
| wgpu::PipelineLayoutDescriptor layoutDesc; | |
| if (sharedContext->caps()->setBackendLabels()) { | |
| layoutDesc.label = shaderInfo->fsLabel().c_str(); | |
| } | |
| layoutDesc.bindGroupLayoutCount = | |
| hasFragmentSamplers ? groupLayouts.size() : groupLayouts.size() - 1; | |
| layoutDesc.bindGroupLayouts = groupLayouts.data(); | |
| #if !defined(__EMSCRIPTEN__) | |
| if (sharedContext->caps() | |
| ->resourceBindingRequirements() | |
| .fUsePushConstantsForIntrinsicConstants) { | |
| layoutDesc.immediateSize = kIntrinsicUniformSize; | |
| } | |
| #endif | |
| auto layout = device.CreatePipelineLayout(&layoutDesc); | |
| if (!layout) { | |
| return {}; | |
| } | |
| descriptor.layout = std::move(layout); | |
| } | |
| // Vertex state | |
| std::array<wgpu::VertexBufferLayout, kNumVertexBuffers> vertexBufferLayouts; | |
| // Static data buffer layout | |
| TArray<wgpu::VertexAttribute> staticDataAttributes; | |
| { | |
| auto arrayStride = create_vertex_attributes(step->staticAttributes(), | |
| 0, | |
| &staticDataAttributes); | |
| auto& layout = vertexBufferLayouts[kStaticDataBufferIndex]; | |
| if (arrayStride) { | |
| layout.arrayStride = arrayStride; | |
| layout.stepMode = wgpu::VertexStepMode::Vertex; | |
| layout.attributeCount = staticDataAttributes.size(); | |
| layout.attributes = staticDataAttributes.data(); | |
| } else { | |
| layout.arrayStride = 0; | |
| #if defined(__EMSCRIPTEN__) | |
| layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed; | |
| #else | |
| layout.stepMode = wgpu::VertexStepMode::Undefined; | |
| #endif | |
| layout.attributeCount = 0; | |
| layout.attributes = nullptr; | |
| } | |
| } | |
| // Append data buffer layout | |
| TArray<wgpu::VertexAttribute> appendDataAttributes; | |
| { | |
| // Note: the shaderLocationOffset in this function call needs to be the staticAttributeSize | |
| auto arrayStride = create_vertex_attributes(shaderInfo->appendAttributes(), | |
| step->staticAttributes().size(), | |
| &appendDataAttributes); | |
| auto& layout = vertexBufferLayouts[kAppendDataBufferIndex]; | |
| if (arrayStride) { | |
| layout.arrayStride = arrayStride; | |
| layout.stepMode = step->appendsVertices() ? wgpu::VertexStepMode::Vertex : | |
| wgpu::VertexStepMode::Instance; | |
| layout.attributeCount = appendDataAttributes.size(); | |
| layout.attributes = appendDataAttributes.data(); | |
| } else { | |
| layout.arrayStride = 0; | |
| #if defined(__EMSCRIPTEN__) | |
| layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed; | |
| #else | |
| layout.stepMode = wgpu::VertexStepMode::Undefined; | |
| #endif | |
| layout.attributeCount = 0; | |
| layout.attributes = nullptr; | |
| } | |
| } | |
| auto& vertex = descriptor.vertex; | |
| vertex.module = std::move(vsModule); | |
| vertex.entryPoint = "main"; | |
| vertex.constantCount = 0; | |
| vertex.constants = nullptr; | |
| vertex.bufferCount = vertexBufferLayouts.size(); | |
| vertex.buffers = vertexBufferLayouts.data(); | |
| // Other state | |
| descriptor.primitive.frontFace = wgpu::FrontFace::CCW; | |
| descriptor.primitive.cullMode = wgpu::CullMode::None; | |
| switch (step->primitiveType()) { | |
| case PrimitiveType::kTriangles: | |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; | |
| break; | |
| case PrimitiveType::kTriangleStrip: | |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip; | |
| descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Uint16; | |
| break; | |
| case PrimitiveType::kPoints: | |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList; | |
| break; | |
| } | |
| // Multisampled state | |
| descriptor.multisample.count = (uint8_t) renderPassDesc.fSampleCount; | |
| descriptor.multisample.mask = 0xFFFFFFFF; | |
| descriptor.multisample.alphaToCoverageEnabled = false; | |
| const bool forPrecompilation = | |
| SkToBool(pipelineCreationFlags & PipelineCreationFlags::kForPrecompilation); | |
| // For Dawn, we want Precompilation to happen synchronously | |
| const bool useAsync = caps.useAsyncPipelineCreation() && !forPrecompilation; | |
| auto asyncCreation = std::make_unique<AsyncPipelineCreation>(pipelineKey); | |
| #if SK_HISTOGRAMS_ENABLED | |
| asyncCreation->fStartTime = skgpu::StdSteadyClock::now(); | |
| asyncCreation->fFromPrecompile = forPrecompilation; | |
| asyncCreation->fAsynchronous = useAsync; | |
| #endif | |
| if (useAsync) { | |
| #if defined(__EMSCRIPTEN__) | |
| // We shouldn't use CreateRenderPipelineAsync in wasm. | |
| SK_ABORT("CreateRenderPipelineAsync shouldn't be used in WASM"); | |
| #else | |
| asyncCreation->fFuture = device.CreateRenderPipelineAsync( | |
| &descriptor, | |
| wgpu::CallbackMode::WaitAnyOnly, | |
| [asyncCreationPtr = asyncCreation.get()](wgpu::CreatePipelineAsyncStatus status, | |
| wgpu::RenderPipeline pipeline, | |
| wgpu::StringView message) { | |
| if (status != wgpu::CreatePipelineAsyncStatus::Success) { | |
| asyncCreationPtr->fErrorMessage = std::string(message.data, message.length); | |
| SKIA_LOG_E("Failed to create render pipeline (%d): %s", | |
| static_cast<int>(status), | |
| asyncCreationPtr->fErrorMessage.c_str()); | |
| // invalidate AsyncPipelineCreation pointer to signal that this pipeline has | |
| // failed. | |
| asyncCreationPtr->fRenderPipeline = nullptr; | |
| } else { | |
| asyncCreationPtr->fRenderPipeline = std::move(pipeline); | |
| } | |
| asyncCreationPtr->fFinished = true; | |
| log_pipeline_creation(asyncCreationPtr); | |
| }); | |
| #endif | |
| } else { | |
| std::optional<DawnErrorChecker> errorChecker; | |
| if (sharedContext->dawnCaps()->allowScopedErrorChecks()) { | |
| errorChecker.emplace(sharedContext); | |
| } | |
| asyncCreation->fRenderPipeline = device.CreateRenderPipeline(&descriptor); | |
| asyncCreation->fFinished = true; | |
| if (errorChecker.has_value() && errorChecker->popErrorScopes() != DawnErrorType::kNoError) { | |
| asyncCreation->fRenderPipeline = nullptr; | |
| } | |
| // Fail ASAP for synchronous pipeline creation so it affects the Recording snap instead of | |
| // being detected later at insertRecording(). | |
| if (!asyncCreation->fRenderPipeline) { | |
| return nullptr; | |
| } | |
| log_pipeline_creation(asyncCreation.get()); | |
| } | |
| PipelineInfo pipelineInfo{ *shaderInfo, pipelineCreationFlags, | |
| pipelineKey.hash(), compilationID }; | |
| #if defined(GPU_TEST_UTILS) | |
| pipelineInfo.fNativeVertexShader = std::move(vsCode.fText); | |
| pipelineInfo.fNativeFragmentShader = std::move(fsCode.fText); | |
| #endif | |
| return sk_sp<DawnGraphicsPipeline>( | |
| new DawnGraphicsPipeline(sharedContext, | |
| pipelineInfo, | |
| shaderInfo->pipelineLabel(), | |
| std::move(asyncCreation), | |
| std::move(groupLayouts), | |
| step->primitiveType(), | |
| stencilSettings.fReferenceValue, | |
| std::move(immutableSamplers))); | |
| } | |
| DawnGraphicsPipeline::DawnGraphicsPipeline( | |
| const skgpu::graphite::SharedContext* sharedContext, | |
| const PipelineInfo& pipelineInfo, | |
| std::string_view pipelineLabel, | |
| std::unique_ptr<AsyncPipelineCreation> asyncCreationInfo, | |
| BindGroupLayouts groupLayouts, | |
| PrimitiveType primitiveType, | |
| uint32_t refValue, | |
| skia_private::TArray<sk_sp<DawnSampler>> immutableSamplers) | |
| : GraphicsPipeline(sharedContext, pipelineInfo, pipelineLabel) | |
| , fAsyncPipelineCreation(std::move(asyncCreationInfo)) | |
| , fGroupLayouts(std::move(groupLayouts)) | |
| , fPrimitiveType(primitiveType) | |
| , fStencilReferenceValue(refValue) | |
| , fImmutableSamplers(std::move(immutableSamplers)) { | |
| // Update the newly-created underlying GPU object's label to match the Resource's | |
| this->synchronizeBackendLabel(); | |
| } | |
| DawnGraphicsPipeline::~DawnGraphicsPipeline() { | |
| this->freeGpuData(); | |
| } | |
| void DawnGraphicsPipeline::freeGpuData() { | |
| // Wait for async creation to finish before we can destroy this object. | |
| (void)this->dawnRenderPipeline(); | |
| fAsyncPipelineCreation = nullptr; | |
| } | |
| std::optional<std::string> DawnGraphicsPipeline::didAsyncCompilationFail() const { | |
| if (fAsyncPipelineCreation && | |
| fAsyncPipelineCreation->fFinished && | |
| !fAsyncPipelineCreation->fRenderPipeline) { | |
| return fAsyncPipelineCreation->fErrorMessage.empty() | |
| ? "Unknown error" | |
| : fAsyncPipelineCreation->fErrorMessage; | |
| } | |
| return std::nullopt; | |
| } | |
| const wgpu::RenderPipeline& DawnGraphicsPipeline::dawnRenderPipeline() const { | |
| if (!fAsyncPipelineCreation) { | |
| static const wgpu::RenderPipeline kNullPipeline = nullptr; | |
| return kNullPipeline; | |
| } | |
| if (fAsyncPipelineCreation->fFinished) { | |
| return fAsyncPipelineCreation->fRenderPipeline; | |
| } | |
| #if defined(__EMSCRIPTEN__) | |
| // We shouldn't use CreateRenderPipelineAsync in wasm. | |
| SK_ABORT("CreateRenderPipelineAsync shouldn't be used in WASM"); | |
| #else | |
| #if defined(SK_PIPELINE_LIFETIME_LOGGING) | |
| TRACE_EVENT_INSTANT2("skia.gpu", | |
| TRACE_STR_STATIC("WaitBeginN"), | |
| TRACE_EVENT_SCOPE_THREAD, | |
| "key", fAsyncPipelineCreation->fKey.hash(), | |
| "compilationID", this->getPipelineInfo().fCompilationID); | |
| #endif | |
| wgpu::FutureWaitInfo waitInfo{}; | |
| waitInfo.future = fAsyncPipelineCreation->fFuture; | |
| const auto& instance = static_cast<const DawnSharedContext*>(sharedContext())->instance(); | |
| [[maybe_unused]] auto status = | |
| instance.WaitAny(1, &waitInfo, /*timeoutNS=*/std::numeric_limits<uint64_t>::max()); | |
| SkASSERT(status == wgpu::WaitStatus::Success); | |
| SkASSERT(waitInfo.completed); | |
| #if defined(SK_PIPELINE_LIFETIME_LOGGING) | |
| TRACE_EVENT_INSTANT2("skia.gpu", | |
| TRACE_STR_STATIC("WaitEndN"), | |
| TRACE_EVENT_SCOPE_THREAD, | |
| "key", fAsyncPipelineCreation->fKey.hash(), | |
| "compilationID", this->getPipelineInfo().fCompilationID); | |
| #endif | |
| #endif | |
| return fAsyncPipelineCreation->fRenderPipeline; | |
| } | |
| } // namespace skgpu::graphite |