| /* |
| * 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 "src/gpu/graphite/Attribute.h" |
| #include "src/gpu/graphite/ContextUtils.h" |
| #include "src/gpu/graphite/GraphicsPipelineDesc.h" |
| #include "src/gpu/graphite/Log.h" |
| #include "src/gpu/graphite/RendererProvider.h" |
| #include "src/gpu/graphite/UniformManager.h" |
| #include "src/gpu/graphite/dawn/DawnGraphiteUtilsPriv.h" |
| #include "src/gpu/graphite/dawn/DawnResourceProvider.h" |
| #include "src/gpu/graphite/dawn/DawnSharedContext.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| |
| #include <vector> |
| |
| 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::kHalf: |
| return wgpu::VertexFormat::Undefined; |
| 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::kByte: |
| return wgpu::VertexFormat::Undefined; |
| case VertexAttribType::kByte2: |
| return wgpu::VertexFormat::Sint8x2; |
| case VertexAttribType::kByte4: |
| return wgpu::VertexFormat::Sint8x4; |
| case VertexAttribType::kUByte: |
| return wgpu::VertexFormat::Undefined; |
| case VertexAttribType::kUByte2: |
| return wgpu::VertexFormat::Uint8x2; |
| case VertexAttribType::kUByte4: |
| return wgpu::VertexFormat::Uint8x4; |
| case VertexAttribType::kUByte_norm: |
| return wgpu::VertexFormat::Undefined; |
| 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::kUShort_norm: |
| return wgpu::VertexFormat::Undefined; |
| case VertexAttribType::kUShort4_norm: |
| return wgpu::VertexFormat::Unorm16x4; |
| } |
| 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(DepthStencilSettings::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, |
| std::vector<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()); |
| SkASSERT(vertexAttribute.format != wgpu::VertexFormat::Undefined); |
| 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(skgpu::BlendCoeff coeff) { |
| 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: |
| case skgpu::BlendCoeff::kIS2C: |
| case skgpu::BlendCoeff::kS2A: |
| case skgpu::BlendCoeff::kIS2A: |
| case skgpu::BlendCoeff::kIllegal: |
| return wgpu::BlendFactor::Zero; |
| } |
| SkUNREACHABLE; |
| } |
| |
| // 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 gTable[(int)equation]; |
| } |
| |
| } // anonymous namespace |
| |
| // static |
| sk_sp<DawnGraphicsPipeline> DawnGraphicsPipeline::Make(const DawnSharedContext* sharedContext, |
| SkSL::Compiler* compiler, |
| const RuntimeEffectDictionary* runtimeDict, |
| const GraphicsPipelineDesc& pipelineDesc, |
| const RenderPassDesc& renderPassDesc) { |
| const auto& device = sharedContext->device(); |
| |
| SkSL::Program::Inputs vsInputs, fsInputs; |
| SkSL::ProgramSettings settings; |
| |
| settings.fForceNoRTFlip = true; |
| settings.fSPIRVDawnCompatMode = true; |
| |
| ShaderErrorHandler* errorHandler = sharedContext->caps()->shaderErrorHandler(); |
| |
| const RenderStep* step = |
| sharedContext->rendererProvider()->lookup(pipelineDesc.renderStepID()); |
| |
| bool useShadingSsboIndex = |
| sharedContext->caps()->storageBufferPreferred() && step->performsShading(); |
| |
| std::string vsSPIRV, fsSPIRV; |
| wgpu::ShaderModule fsModule, vsModule; |
| |
| // Some steps just render depth buffer but not color buffer, so the fragment |
| // shader is null. |
| const FragSkSLInfo fsSkSLInfo = GetSkSLFS(sharedContext->caps()->resourceBindingRequirements(), |
| sharedContext->shaderCodeDictionary(), |
| runtimeDict, |
| step, |
| pipelineDesc.paintParamsID(), |
| useShadingSsboIndex); |
| const std::string& fsSKSL = fsSkSLInfo.fSkSL; |
| const BlendInfo& blendInfo = fsSkSLInfo.fBlendInfo; |
| const bool localCoordsNeeded = fsSkSLInfo.fRequiresLocalCoords; |
| const int numTexturesAndSamplers = fsSkSLInfo.fNumTexturesAndSamplers; |
| |
| bool hasFragment = !fsSKSL.empty(); |
| if (hasFragment) { |
| if (!SkSLToSPIRV(compiler, |
| fsSKSL, |
| SkSL::ProgramKind::kGraphiteFragment, |
| settings, |
| &fsSPIRV, |
| &fsInputs, |
| errorHandler)) { |
| return {}; |
| } |
| fsModule = DawnCompileSPIRVShaderModule(sharedContext, |
| fsSPIRV, |
| errorHandler); |
| if (!fsModule) { |
| return {}; |
| } |
| } |
| |
| if (!SkSLToSPIRV(compiler, |
| GetSkSLVS(sharedContext->caps()->resourceBindingRequirements(), |
| step, |
| useShadingSsboIndex, |
| localCoordsNeeded), |
| SkSL::ProgramKind::kGraphiteVertex, |
| settings, |
| &vsSPIRV, |
| &vsInputs, |
| errorHandler)) { |
| return {}; |
| } |
| |
| vsModule = DawnCompileSPIRVShaderModule(sharedContext, vsSPIRV, errorHandler); |
| if (!vsModule) { |
| return {}; |
| } |
| |
| wgpu::RenderPipelineDescriptor descriptor; |
| #if defined(SK_DEBUG) |
| descriptor.label = step->name(); |
| #endif |
| |
| // 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(srcCoeff); |
| blend.color.dstFactor = blend_coeff_to_dawn_blend(dstCoeff); |
| blend.alpha.operation = blend_equation_to_dawn_blend_op(equation); |
| blend.alpha.srcFactor = blend_coeff_to_dawn_blend(srcCoeff); |
| blend.alpha.dstFactor = blend_coeff_to_dawn_blend(dstCoeff); |
| } |
| |
| wgpu::ColorTargetState colorTarget; |
| colorTarget.format = renderPassDesc.fColorAttachment.fTextureInfo.dawnTextureSpec().fFormat; |
| colorTarget.blend = blendOn ? &blend : nullptr; |
| colorTarget.writeMask = blendInfo.fWritesColor && hasFragment ? wgpu::ColorWriteMask::All |
| : wgpu::ColorWriteMask::None; |
| |
| 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 = hasFragment ? std::move(fsModule) : sharedContext->noopFragment(); |
| fragment.entryPoint = "main"; |
| fragment.targetCount = 1; |
| fragment.targets = &colorTarget; |
| descriptor.fragment = &fragment; |
| |
| // Depth stencil state |
| const auto& depthStencilSettings = step->depthStencilSettings(); |
| SkASSERT(depthStencilSettings.fDepthTestEnabled || |
| depthStencilSettings.fDepthCompareOp == CompareOp::kAlways); |
| wgpu::DepthStencilState depthStencil; |
| if (renderPassDesc.fDepthStencilAttachment.fTextureInfo.isValid()) { |
| wgpu::TextureFormat dsFormat = |
| renderPassDesc.fDepthStencilAttachment.fTextureInfo.dawnTextureSpec().fFormat; |
| depthStencil.format = |
| DawnFormatIsDepthOrStencil(dsFormat) ? dsFormat : wgpu::TextureFormat::Undefined; |
| if (depthStencilSettings.fDepthTestEnabled) { |
| depthStencil.depthWriteEnabled = depthStencilSettings.fDepthWriteEnabled; |
| } |
| depthStencil.depthCompare = compare_op_to_dawn(depthStencilSettings.fDepthCompareOp); |
| depthStencil.stencilFront = stencil_face_to_dawn(depthStencilSettings.fFrontStencil); |
| depthStencil.stencilBack = stencil_face_to_dawn(depthStencilSettings.fBackStencil); |
| depthStencil.stencilReadMask = depthStencilSettings.fFrontStencil.fReadMask; |
| depthStencil.stencilWriteMask = depthStencilSettings.fFrontStencil.fWriteMask; |
| |
| descriptor.depthStencil = &depthStencil; |
| } |
| |
| // Pipeline layout |
| { |
| std::array<wgpu::BindGroupLayout, 2> groupLayouts; |
| { |
| std::array<wgpu::BindGroupLayoutEntry, 3> entries; |
| entries[0].binding = kIntrinsicUniformBufferIndex; |
| entries[0].visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment; |
| entries[0].buffer.type = wgpu::BufferBindingType::Uniform; |
| entries[0].buffer.hasDynamicOffset = false; |
| entries[0].buffer.minBindingSize = 0; |
| |
| uint32_t numBuffers = 1; |
| |
| if (!step->uniforms().empty()) { |
| entries[numBuffers].binding = kRenderStepUniformBufferIndex; |
| entries[numBuffers].visibility = |
| wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment; |
| entries[numBuffers].buffer.type = wgpu::BufferBindingType::Uniform; |
| entries[numBuffers].buffer.hasDynamicOffset = false; |
| entries[numBuffers].buffer.minBindingSize = 0; |
| ++numBuffers; |
| } |
| |
| if (hasFragment) { |
| entries[numBuffers].binding = kPaintUniformBufferIndex; |
| entries[numBuffers].visibility = wgpu::ShaderStage::Fragment; |
| entries[numBuffers].buffer.type = wgpu::BufferBindingType::Uniform; |
| entries[numBuffers].buffer.hasDynamicOffset = false; |
| entries[numBuffers].buffer.minBindingSize = 0; |
| ++numBuffers; |
| } |
| |
| wgpu::BindGroupLayoutDescriptor groupLayoutDesc; |
| #if defined(SK_DEBUG) |
| groupLayoutDesc.label = step->name(); |
| #endif |
| |
| groupLayoutDesc.entryCount = numBuffers; |
| groupLayoutDesc.entries = entries.data(); |
| groupLayouts[0] = device.CreateBindGroupLayout(&groupLayoutDesc); |
| if (!groupLayouts[0]) { |
| return {}; |
| } |
| } |
| |
| bool hasFragmentSamplers = hasFragment && numTexturesAndSamplers > 0; |
| if (hasFragmentSamplers) { |
| std::vector<wgpu::BindGroupLayoutEntry> entries(numTexturesAndSamplers); |
| for (int i = 0; i < numTexturesAndSamplers;) { |
| entries[i].binding = static_cast<uint32_t>(i); |
| entries[i].visibility = wgpu::ShaderStage::Fragment; |
| entries[i].sampler.type = wgpu::SamplerBindingType::Filtering; |
| ++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 defined(SK_DEBUG) |
| groupLayoutDesc.label = step->name(); |
| #endif |
| groupLayoutDesc.entryCount = entries.size(); |
| groupLayoutDesc.entries = entries.data(); |
| groupLayouts[1] = device.CreateBindGroupLayout(&groupLayoutDesc); |
| if (!groupLayouts[1]) { |
| return {}; |
| } |
| } |
| |
| wgpu::PipelineLayoutDescriptor layoutDesc; |
| #if defined(SK_DEBUG) |
| layoutDesc.label = step->name(); |
| #endif |
| layoutDesc.bindGroupLayoutCount = |
| hasFragmentSamplers ? groupLayouts.size() : groupLayouts.size() - 1; |
| layoutDesc.bindGroupLayouts = groupLayouts.data(); |
| auto layout = device.CreatePipelineLayout(&layoutDesc); |
| if (!layout) { |
| return {}; |
| } |
| descriptor.layout = std::move(layout); |
| } |
| |
| // Vertex state |
| std::array<wgpu::VertexBufferLayout, kNumVertexBuffers> vertexBufferLayouts; |
| // Vertex buffer layout |
| std::vector<wgpu::VertexAttribute> vertexAttributes; |
| { |
| auto arrayStride = create_vertex_attributes(step->vertexAttributes(), |
| 0, |
| &vertexAttributes); |
| auto& layout = vertexBufferLayouts[kVertexBufferIndex]; |
| if (arrayStride) { |
| layout.arrayStride = arrayStride; |
| layout.stepMode = wgpu::VertexStepMode::Vertex; |
| layout.attributeCount = vertexAttributes.size(); |
| layout.attributes = vertexAttributes.data(); |
| } else { |
| layout.arrayStride = 0; |
| layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed; |
| layout.attributeCount = 0; |
| layout.attributes = nullptr; |
| } |
| } |
| |
| // Instance buffer layout |
| std::vector<wgpu::VertexAttribute> instanceAttributes; |
| { |
| auto arrayStride = create_vertex_attributes(step->instanceAttributes(), |
| step->vertexAttributes().size(), |
| &instanceAttributes); |
| auto& layout = vertexBufferLayouts[kInstanceBufferIndex]; |
| if (arrayStride) { |
| layout.arrayStride = arrayStride; |
| layout.stepMode = wgpu::VertexStepMode::Instance; |
| layout.attributeCount = instanceAttributes.size(); |
| layout.attributes = instanceAttributes.data(); |
| } else { |
| layout.arrayStride = 0; |
| layout.stepMode = wgpu::VertexStepMode::VertexBufferNotUsed; |
| 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; |
| break; |
| case PrimitiveType::kPoints: |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::PointList; |
| break; |
| } |
| descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Uint16; |
| |
| descriptor.multisample.count = renderPassDesc.fColorAttachment.fTextureInfo.numSamples(); |
| descriptor.multisample.mask = 0xFFFFFFFF; |
| descriptor.multisample.alphaToCoverageEnabled = false; |
| |
| auto pipeline = device.CreateRenderPipeline(&descriptor); |
| if (!pipeline) { |
| return {}; |
| } |
| |
| return sk_sp<DawnGraphicsPipeline>( |
| new DawnGraphicsPipeline(sharedContext, |
| std::move(pipeline), |
| step->primitiveType(), |
| depthStencilSettings.fStencilReferenceValue, |
| !step->uniforms().empty(), |
| hasFragment)); |
| } |
| |
| void DawnGraphicsPipeline::freeGpuData() { |
| fRenderPipeline = nullptr; |
| } |
| |
| const wgpu::RenderPipeline& DawnGraphicsPipeline::dawnRenderPipeline() const { |
| return fRenderPipeline; |
| } |
| |
| } // namespace skgpu::graphite |