| /* |
| * Copyright 2021 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/ContextUtils.h" |
| |
| #include <string> |
| #include "src/core/SkBlenderBase.h" |
| #include "src/gpu/BlendFormula.h" |
| #include "src/gpu/graphite/Caps.h" |
| #include "src/gpu/graphite/GraphicsPipelineDesc.h" |
| #include "src/gpu/graphite/KeyContext.h" |
| #include "src/gpu/graphite/PaintParams.h" |
| #include "src/gpu/graphite/PipelineData.h" |
| #include "src/gpu/graphite/RecorderPriv.h" |
| #include "src/gpu/graphite/RenderPassDesc.h" |
| #include "src/gpu/graphite/Renderer.h" |
| #include "src/gpu/graphite/ResourceProvider.h" |
| #include "src/gpu/graphite/ShaderCodeDictionary.h" |
| #include "src/gpu/graphite/UniformManager.h" |
| #include "src/gpu/graphite/UniquePaintParamsID.h" |
| #include "src/gpu/graphite/compute/ComputeStep.h" |
| #include "src/gpu/graphite/geom/Geometry.h" |
| #include "src/sksl/SkSLString.h" |
| #include "src/sksl/SkSLUtil.h" |
| |
| namespace skgpu::graphite { |
| |
| std::tuple<UniquePaintParamsID, UniformDataBlock, TextureDataBlock> ExtractPaintData( |
| Recorder* recorder, |
| PipelineDataGatherer* gatherer, |
| PaintParamsKeyBuilder* builder, |
| const Layout layout, |
| const SkM44& local2Dev, |
| const PaintParams& p, |
| const Geometry& geometry, |
| sk_sp<TextureProxy> dstTexture, |
| SkIPoint dstOffset, |
| const SkColorInfo& targetColorInfo) { |
| SkDEBUGCODE(builder->checkReset()); |
| |
| gatherer->resetWithNewLayout(layout); |
| |
| KeyContext keyContext(recorder, |
| local2Dev, |
| targetColorInfo, |
| geometry.isShape() || geometry.isEdgeAAQuad() |
| ? KeyContext::OptimizeSampling::kYes |
| : KeyContext::OptimizeSampling::kNo, |
| p.color(), |
| std::move(dstTexture), |
| dstOffset); |
| p.toKey(keyContext, builder, gatherer); |
| |
| UniquePaintParamsID paintID = recorder->priv().shaderCodeDictionary()->findOrCreate(builder); |
| UniformDataBlock uniforms; |
| TextureDataBlock textures; |
| if (paintID.isValid()) { |
| if (gatherer->hasUniforms()) { |
| UniformDataCache* uniformDataCache = recorder->priv().uniformDataCache(); |
| uniforms = uniformDataCache->insert(gatherer->finishUniformDataBlock()); |
| } |
| if (gatherer->hasTextures()) { |
| TextureDataCache* textureDataCache = recorder->priv().textureDataCache(); |
| textures = textureDataCache->insert(gatherer->textureDataBlock()); |
| } |
| } |
| |
| return { paintID, uniforms, textures }; |
| } |
| |
| std::tuple<UniformDataBlock, TextureDataBlock> ExtractRenderStepData( |
| UniformDataCache* uniformDataCache, |
| TextureDataCache* textureDataCache, |
| PipelineDataGatherer* gatherer, |
| const Layout layout, |
| const RenderStep* step, |
| const DrawParams& params) { |
| gatherer->resetWithNewLayout(layout); |
| step->writeUniformsAndTextures(params, gatherer); |
| |
| UniformDataBlock uniforms = |
| gatherer->hasUniforms() ? uniformDataCache->insert(gatherer->finishUniformDataBlock()) |
| : UniformDataBlock(); |
| TextureDataBlock textures = |
| gatherer->hasTextures() ? textureDataCache->insert(gatherer->textureDataBlock()) |
| : TextureDataBlock(); |
| |
| return { uniforms, textures }; |
| } |
| |
| DstReadRequirement GetDstReadRequirement(const Caps* caps, |
| std::optional<SkBlendMode> blendMode, |
| Coverage coverage) { |
| // If the blend mode is absent, this is assumed to be for a runtime blender, for which we always |
| // do a dst read. |
| // If the blend mode is plus, always do in-shader blending since we may be drawing to an |
| // unsaturated surface (e.g. F16) and we don't want to let the hardware clamp the color output |
| // in that case. We could check the draw dst properties to only do in-shader blending with plus |
| // when necessary, but we can't detect that during shader precompilation. |
| if (!blendMode || *blendMode > SkBlendMode::kLastCoeffMode || |
| *blendMode == SkBlendMode::kPlus) { |
| return caps->getDstReadRequirement(); |
| } |
| |
| const bool isLCD = coverage == Coverage::kLCD; |
| const bool hasCoverage = coverage != Coverage::kNone; |
| BlendFormula blendFormula = isLCD ? skgpu::GetLCDBlendFormula(*blendMode) |
| : skgpu::GetBlendFormula(false, hasCoverage, *blendMode); |
| if ((blendFormula.hasSecondaryOutput() && !caps->shaderCaps()->fDualSourceBlendingSupport) || |
| (coverage == Coverage::kLCD && blendMode != SkBlendMode::kSrcOver)) { |
| return caps->getDstReadRequirement(); |
| } |
| |
| return DstReadRequirement::kNone; |
| } |
| |
| namespace { |
| |
| std::string get_uniform_header(int bufferID, const char* name) { |
| std::string result; |
| |
| SkSL::String::appendf(&result, "layout (binding=%d) uniform %sUniforms {\n", bufferID, name); |
| |
| return result; |
| } |
| |
| std::string get_uniforms(UniformOffsetCalculator* offsetter, |
| SkSpan<const Uniform> uniforms, |
| int manglingSuffix, |
| bool* wrotePaintColor) { |
| std::string result; |
| std::string uniformName; |
| for (const Uniform& u : uniforms) { |
| uniformName = u.name(); |
| |
| if (u.isPaintColor() && wrotePaintColor) { |
| if (*wrotePaintColor) { |
| SkSL::String::appendf(&result, " // deduplicated %s\n", u.name()); |
| continue; |
| } |
| |
| *wrotePaintColor = true; |
| } else { |
| if (manglingSuffix >= 0) { |
| uniformName.append("_"); |
| uniformName.append(std::to_string(manglingSuffix)); |
| } |
| } |
| |
| SkSL::String::appendf(&result, |
| " layout(offset=%d) %s %s", |
| offsetter->advanceOffset(u.type(), u.count()), |
| SkSLTypeString(u.type()), |
| uniformName.c_str()); |
| if (u.count()) { |
| result.append("["); |
| result.append(std::to_string(u.count())); |
| result.append("]"); |
| } |
| result.append(";\n"); |
| } |
| |
| return result; |
| } |
| |
| std::string get_node_uniforms(UniformOffsetCalculator* offsetter, |
| const ShaderNode* node, |
| bool* wrotePaintColor) { |
| std::string result; |
| SkSpan<const Uniform> uniforms = node->entry()->fUniforms; |
| |
| if (!uniforms.empty()) { |
| if (node->entry()->fUniformStructName) { |
| auto substruct = UniformOffsetCalculator::ForStruct(offsetter->layout()); |
| for (const Uniform& u : uniforms) { |
| substruct.advanceOffset(u.type(), u.count()); |
| } |
| |
| const int structOffset = offsetter->advanceStruct(substruct); |
| SkSL::String::appendf(&result, |
| "layout(offset=%d) %s node_%d;", |
| structOffset, |
| node->entry()->fUniformStructName, |
| node->keyIndex()); |
| } else { |
| SkSL::String::appendf(&result, "// %d - %s uniforms\n", |
| node->keyIndex(), node->entry()->fName); |
| result += get_uniforms(offsetter, uniforms, node->keyIndex(), wrotePaintColor); |
| } |
| } |
| |
| for (const ShaderNode* child : node->children()) { |
| result += get_node_uniforms(offsetter, child, wrotePaintColor); |
| } |
| return result; |
| } |
| |
| std::string get_ssbo_fields(SkSpan<const Uniform> uniforms, |
| int manglingSuffix, |
| bool* wrotePaintColor) { |
| std::string result; |
| |
| std::string uniformName; |
| for (const Uniform& u : uniforms) { |
| uniformName = u.name(); |
| |
| if (u.isPaintColor() && wrotePaintColor) { |
| if (*wrotePaintColor) { |
| SkSL::String::appendf(&result, " // deduplicated %s\n", u.name()); |
| continue; |
| } |
| |
| *wrotePaintColor = true; |
| } else { |
| if (manglingSuffix >= 0) { |
| uniformName.append("_"); |
| uniformName.append(std::to_string(manglingSuffix)); |
| } |
| } |
| |
| SkSL::String::appendf(&result, " %s %s", SkSLTypeString(u.type()), uniformName.c_str()); |
| if (u.count()) { |
| SkSL::String::appendf(&result, "[%d]", u.count()); |
| } |
| result.append(";\n"); |
| } |
| |
| return result; |
| } |
| |
| std::string get_node_ssbo_fields(const ShaderNode* node, bool* wrotePaintColor) { |
| std::string result; |
| SkSpan<const Uniform> uniforms = node->entry()->fUniforms; |
| |
| if (!uniforms.empty()) { |
| if (node->entry()->fUniformStructName) { |
| SkSL::String::appendf(&result, "%s node_%d;", |
| node->entry()->fUniformStructName, node->keyIndex()); |
| } else { |
| SkSL::String::appendf(&result, "// %d - %s uniforms\n", |
| node->keyIndex(), node->entry()->fName); |
| |
| result += get_ssbo_fields(uniforms, node->keyIndex(), wrotePaintColor); |
| } |
| } |
| |
| for (const ShaderNode* child : node->children()) { |
| result += get_node_ssbo_fields(child, wrotePaintColor); |
| } |
| return result; |
| } |
| |
| std::string get_node_texture_samplers(const ResourceBindingRequirements& bindingReqs, |
| const ShaderNode* node, |
| int* binding, |
| skia_private::TArray<SamplerDesc>* outDescs) { |
| std::string result; |
| SkSpan<const TextureAndSampler> samplers = node->entry()->fTexturesAndSamplers; |
| |
| if (!samplers.empty()) { |
| SkSL::String::appendf(&result, "// %d - %s samplers\n", |
| node->keyIndex(), node->entry()->fName); |
| |
| // TODO(b/898301): If outDescs is a valid ptr, populate it appropriately. |
| |
| for (const TextureAndSampler& t : samplers) { |
| result += EmitSamplerLayout(bindingReqs, binding); |
| SkSL::String::appendf(&result, " sampler2D %s_%d;\n", |
| t.name(), node->keyIndex()); |
| } |
| } |
| |
| for (const ShaderNode* child : node->children()) { |
| result += get_node_texture_samplers(bindingReqs, child, binding, outDescs); |
| } |
| return result; |
| } |
| |
| static constexpr Uniform kIntrinsicUniforms[] = { {"viewport", SkSLType::kFloat4}, |
| {"dstCopyBounds", SkSLType::kFloat4} }; |
| |
| } // anonymous namespace |
| |
| void CollectIntrinsicUniforms(const Caps* caps, |
| SkIRect viewport, |
| SkIRect dstCopyBounds, |
| UniformManager* uniforms) { |
| SkDEBUGCODE(uniforms->setExpectedUniforms(kIntrinsicUniforms, /*isSubstruct=*/false);) |
| |
| // viewport |
| { |
| // The vertex shader needs to divide by the dimension and then multiply by 2, so do this |
| // once on the CPU. This is because viewport normalization wants to range from -1 to 1, and |
| // not 0 to 1. If any other user of the viewport uniform requires the true reciprocal or |
| // original dimensions, this can be adjusted. |
| SkASSERT(!viewport.isEmpty()); |
| float invTwoW = 2.f / viewport.width(); |
| float invTwoH = 2.f / viewport.height(); |
| |
| // If the NDC Y axis points up (opposite normal skia convention and the underlying view |
| // convention), upload the inverse height as a negative value. See BuildVertexSkSL |
| // for how this is used. |
| if (!caps->ndcYAxisPointsDown()) { |
| invTwoH *= -1.f; |
| } |
| uniforms->write(SkV4{(float) viewport.left(), (float) viewport.top(), invTwoW, invTwoH}); |
| } |
| |
| // dstCopyBounds |
| { |
| // Unlike viewport, dstCopyBounds can be empty so check for 0 dimensions and set the |
| // reciprocal to 0. It is also not doubled since its purpose is to normalize texture coords |
| // to 0 to 1, and not -1 to 1. |
| int width = dstCopyBounds.width(); |
| int height = dstCopyBounds.height(); |
| uniforms->write(SkV4{(float) dstCopyBounds.left(), (float) dstCopyBounds.top(), |
| width ? 1.f / width : 0.f, height ? 1.f / height : 0.f}); |
| } |
| |
| SkDEBUGCODE(uniforms->doneWithExpectedUniforms()); |
| } |
| |
| std::string EmitIntrinsicUniforms(int bufferID, Layout layout) { |
| auto offsetter = UniformOffsetCalculator::ForTopLevel(layout); |
| |
| std::string result = get_uniform_header(bufferID, "Intrinsic"); |
| result += get_uniforms(&offsetter, kIntrinsicUniforms, -1, /* wrotePaintColor= */ nullptr); |
| result.append("};\n\n"); |
| |
| SkASSERTF(result.find('[') == std::string::npos, |
| "Arrays are not supported in intrinsic uniforms"); |
| |
| return result; |
| } |
| |
| std::string EmitPaintParamsUniforms(int bufferID, |
| const Layout layout, |
| SkSpan<const ShaderNode*> nodes, |
| bool* hasUniforms, |
| bool* wrotePaintColor) { |
| auto offsetter = UniformOffsetCalculator::ForTopLevel(layout); |
| |
| std::string result = get_uniform_header(bufferID, "FS"); |
| for (const ShaderNode* n : nodes) { |
| result += get_node_uniforms(&offsetter, n, wrotePaintColor); |
| } |
| result.append("};\n\n"); |
| |
| *hasUniforms = offsetter.size() > 0; |
| if (!*hasUniforms) { |
| // No uniforms were added |
| return {}; |
| } |
| |
| return result; |
| } |
| |
| std::string EmitRenderStepUniforms(int bufferID, |
| const Layout layout, |
| SkSpan<const Uniform> uniforms) { |
| auto offsetter = UniformOffsetCalculator::ForTopLevel(layout); |
| |
| std::string result = get_uniform_header(bufferID, "Step"); |
| result += get_uniforms(&offsetter, uniforms, -1, /* wrotePaintColor= */ nullptr); |
| result.append("};\n\n"); |
| |
| return result; |
| } |
| |
| std::string EmitPaintParamsStorageBuffer( |
| int bufferID, |
| SkSpan<const ShaderNode*> nodes, |
| bool* hasUniforms, |
| bool* wrotePaintColor) { |
| *hasUniforms = false; |
| |
| std::string fields; |
| for (const ShaderNode* n : nodes) { |
| fields += get_node_ssbo_fields(n, wrotePaintColor); |
| } |
| |
| if (fields.empty()) { |
| // No uniforms were added |
| *hasUniforms = false; |
| return {}; |
| } |
| |
| *hasUniforms = true; |
| return SkSL::String::printf( |
| "struct FSUniformData {\n" |
| "%s\n" |
| "};\n\n" |
| "layout (binding=%d) readonly buffer FSUniforms {\n" |
| "FSUniformData fsUniformData[];\n" |
| "};\n", |
| fields.c_str(), |
| bufferID); |
| } |
| |
| std::string EmitRenderStepStorageBuffer( |
| int bufferID, |
| SkSpan<const Uniform> uniforms) { |
| SkASSERT(!uniforms.empty()); |
| std::string fields = get_ssbo_fields(uniforms, -1, /*wrotePaintColor=*/nullptr); |
| return SkSL::String::printf( |
| "struct StepUniformData {\n" |
| "%s\n" |
| "};\n\n" |
| "layout (binding=%d) readonly buffer StepUniforms {\n" |
| " StepUniformData stepUniformData[];\n" |
| "};\n", |
| fields.c_str(), |
| bufferID); |
| } |
| |
| std::string EmitUniformsFromStorageBuffer(const char* bufferNamePrefix, |
| const char* ssboIndex, |
| SkSpan<const Uniform> uniforms) { |
| std::string result; |
| |
| for (const Uniform& u : uniforms) { |
| SkSL::String::appendf(&result, "%s %s", SkSLTypeString(u.type()), u.name()); |
| if (u.count()) { |
| SkSL::String::appendf(&result, "[%d]", u.count()); |
| } |
| SkSL::String::appendf( |
| &result, " = %sUniformData[%s].%s;\n", bufferNamePrefix, ssboIndex, u.name()); |
| } |
| |
| return result; |
| } |
| |
| std::string EmitStorageBufferAccess(const char* bufferNamePrefix, |
| const char* ssboIndex, |
| const char* uniformName) { |
| return SkSL::String::printf("%sUniformData[%s].%s", bufferNamePrefix, ssboIndex, uniformName); |
| } |
| |
| std::string EmitTexturesAndSamplers(const ResourceBindingRequirements& bindingReqs, |
| SkSpan<const ShaderNode*> nodes, |
| int* binding, |
| skia_private::TArray<SamplerDesc>* outDescs) { |
| std::string result; |
| for (const ShaderNode* n : nodes) { |
| result += get_node_texture_samplers(bindingReqs, n, binding, outDescs); |
| } |
| return result; |
| } |
| |
| std::string EmitSamplerLayout(const ResourceBindingRequirements& bindingReqs, int* binding) { |
| std::string result; |
| |
| // If fDistinctIndexRanges is false, then texture and sampler indices may clash with other |
| // resource indices. Graphite assumes that they will be placed in descriptor set (Vulkan) and |
| // bind group (Dawn) index 1. |
| const char* distinctIndexRange = bindingReqs.fDistinctIndexRanges ? "" : "set=1, "; |
| |
| if (bindingReqs.fSeparateTextureAndSamplerBinding) { |
| int samplerIndex = (*binding)++; |
| int textureIndex = (*binding)++; |
| result = SkSL::String::printf("layout(webgpu, %ssampler=%d, texture=%d)", |
| distinctIndexRange, |
| samplerIndex, |
| textureIndex); |
| } else { |
| int samplerIndex = (*binding)++; |
| result = SkSL::String::printf("layout(%sbinding=%d)", |
| distinctIndexRange, |
| samplerIndex); |
| } |
| return result; |
| } |
| |
| namespace { |
| std::string emit_attributes(SkSpan<const Attribute> vertexAttrs, |
| SkSpan<const Attribute> instanceAttrs) { |
| std::string result; |
| |
| int attr = 0; |
| auto add_attrs = [&](SkSpan<const Attribute> attrs) { |
| for (auto a : attrs) { |
| SkSL::String::appendf(&result, " layout(location=%d) in ", attr++); |
| result.append(SkSLTypeString(a.gpuType())); |
| SkSL::String::appendf(&result, " %s;\n", a.name()); |
| } |
| }; |
| |
| if (!vertexAttrs.empty()) { |
| result.append("// vertex attrs\n"); |
| add_attrs(vertexAttrs); |
| } |
| if (!instanceAttrs.empty()) { |
| result.append("// instance attrs\n"); |
| add_attrs(instanceAttrs); |
| } |
| |
| return result; |
| } |
| } // anonymous namespace |
| |
| std::string EmitVaryings(const RenderStep* step, |
| const char* direction, |
| bool emitSsboIndicesVarying, |
| bool emitLocalCoordsVarying) { |
| std::string result; |
| int location = 0; |
| |
| auto appendVarying = [&](const Varying& v) { |
| const char* interpolation; |
| switch (v.interpolation()) { |
| case Interpolation::kPerspective: interpolation = ""; break; |
| case Interpolation::kLinear: interpolation = "noperspective "; break; |
| case Interpolation::kFlat: interpolation = "flat "; break; |
| } |
| SkSL::String::appendf(&result, "layout(location=%d) %s %s%s %s;\n", |
| location++, |
| direction, |
| interpolation, |
| SkSLTypeString(v.gpuType()), |
| v.name()); |
| }; |
| |
| if (emitSsboIndicesVarying) { |
| appendVarying({RenderStep::ssboIndicesVarying(), SkSLType::kUShort2}); |
| } |
| |
| if (emitLocalCoordsVarying) { |
| appendVarying({"localCoordsVar", SkSLType::kFloat2}); |
| } |
| |
| for (auto v : step->varyings()) { |
| appendVarying(v); |
| } |
| |
| return result; |
| } |
| |
| VertSkSLInfo BuildVertexSkSL(const ResourceBindingRequirements& bindingReqs, |
| const RenderStep* step, |
| bool useStorageBuffers, |
| bool defineLocalCoordsVarying) { |
| VertSkSLInfo result; |
| |
| const bool hasStepUniforms = step->numUniforms() > 0; |
| const bool useStepStorageBuffer = useStorageBuffers && hasStepUniforms; |
| const bool useShadingStorageBuffer = useStorageBuffers && step->performsShading(); |
| |
| // Fixed program header (intrinsics are always declared as an uniform interface block) |
| std::string sksl = EmitIntrinsicUniforms(bindingReqs.fIntrinsicBufferBinding, |
| bindingReqs.fUniformBufferLayout); |
| |
| if (step->numVertexAttributes() > 0 || step->numInstanceAttributes() > 0) { |
| sksl += emit_attributes(step->vertexAttributes(), step->instanceAttributes()); |
| } |
| |
| // Uniforms needed by RenderStep |
| // The uniforms are mangled by having their index in 'fEntries' as a suffix (i.e., "_%d") |
| if (hasStepUniforms) { |
| if (useStepStorageBuffer) { |
| sksl += EmitRenderStepStorageBuffer(bindingReqs.fRenderStepBufferBinding, |
| step->uniforms()); |
| } else { |
| sksl += EmitRenderStepUniforms(bindingReqs.fRenderStepBufferBinding, |
| bindingReqs.fUniformBufferLayout, |
| step->uniforms()); |
| } |
| } |
| |
| // Varyings needed by RenderStep |
| sksl += EmitVaryings(step, "out", useShadingStorageBuffer, defineLocalCoordsVarying); |
| |
| // Vertex shader function declaration |
| sksl += "void main() {"; |
| // Create stepLocalCoords which render steps can write to. |
| sksl += "float2 stepLocalCoords = float2(0);"; |
| // Vertex shader body |
| if (useStepStorageBuffer) { |
| // Extract out render step uniforms from SSBO, declaring local variables with the expected |
| // uniform names so that RenderStep SkSL is independent of storage choice. |
| SkSL::String::appendf( |
| &sksl, "uint stepSsboIndex = %s.x;\n", RenderStep::ssboIndicesAttribute()); |
| sksl += EmitUniformsFromStorageBuffer("step", "stepSsboIndex", step->uniforms()); |
| } |
| |
| sksl += step->vertexSkSL(); |
| |
| // We want to map the rectangle of logical device pixels from (0,0) to (viewWidth, viewHeight) |
| // to normalized device coordinates: (-1,-1) to (1,1) (actually -w to w since it's before |
| // homogenous division). |
| // |
| // For efficiency, this assumes viewport.zw holds the reciprocol of twice the viewport width and |
| // height. On some backends the NDC Y axis is flipped relative to the device and |
| // viewport coords (i.e. it points up instead of down). In those cases, it's also assumed that |
| // viewport.w holds a negative value. In that case the sign(viewport.zw) changes from |
| // subtracting w to adding w. |
| sksl += "sk_Position = float4(viewport.zw*devPosition.xy - sign(viewport.zw)*devPosition.ww," |
| "devPosition.zw);"; |
| |
| if (useShadingStorageBuffer) { |
| // Assign SSBO index values to the SSBO index varying. |
| SkSL::String::appendf(&sksl, |
| "%s = %s;", |
| RenderStep::ssboIndicesVarying(), |
| RenderStep::ssboIndicesAttribute()); |
| } |
| |
| if (defineLocalCoordsVarying) { |
| // Assign Render Step's stepLocalCoords to the localCoordsVar varying. |
| sksl += "localCoordsVar = stepLocalCoords;"; |
| } |
| sksl += "}"; |
| |
| result.fSkSL = std::move(sksl); |
| result.fLabel = step->name(); |
| if (defineLocalCoordsVarying) { |
| result.fLabel += " (w/ local coords)"; |
| } |
| result.fHasStepUniforms = hasStepUniforms; |
| |
| return result; |
| } |
| |
| FragSkSLInfo BuildFragmentSkSL(const Caps* caps, |
| const ShaderCodeDictionary* dict, |
| const RuntimeEffectDictionary* rteDict, |
| const RenderStep* step, |
| UniquePaintParamsID paintID, |
| bool useStorageBuffers, |
| skgpu::Swizzle writeSwizzle, |
| skia_private::TArray<SamplerDesc>* outDescs) { |
| FragSkSLInfo result; |
| if (!paintID.isValid()) { |
| // Depth-only draw so no fragment shader to compile |
| return {}; |
| } |
| |
| const char* shadingSsboIndex = |
| useStorageBuffers && step->performsShading() ? "shadingSsboIndex" : nullptr; |
| ShaderInfo shaderInfo(paintID, dict, rteDict, shadingSsboIndex); |
| |
| result.fSkSL = shaderInfo.toSkSL(caps, |
| step, |
| useStorageBuffers, |
| writeSwizzle, |
| &result.fNumTexturesAndSamplers, |
| &result.fHasPaintUniforms, |
| &result.fHasGradientBuffer, |
| outDescs); |
| |
| // Extract blend info after integrating the RenderStep into the final fragment shader in case |
| // that changes the HW blending choice to handle analytic coverage. |
| result.fBlendInfo = shaderInfo.blendInfo(); |
| result.fRequiresLocalCoords = shaderInfo.needsLocalCoords(); |
| result.fData = {shaderInfo.data()}; |
| result.fLabel = writeSwizzle.asString().c_str(); |
| result.fLabel += " + "; |
| result.fLabel = step->name(); |
| result.fLabel += " + "; |
| result.fLabel += dict->idToString(paintID).c_str(); |
| |
| return result; |
| } |
| |
| std::string GetPipelineLabel(const ShaderCodeDictionary* dict, |
| const RenderPassDesc& renderPassDesc, |
| const RenderStep* renderStep, |
| UniquePaintParamsID paintID) { |
| std::string label = renderPassDesc.toPipelineLabel().c_str(); // includes the write swizzle |
| label += " + "; |
| label += renderStep->name(); |
| label += " + "; |
| label += dict->idToString(paintID).c_str(); // will be "(empty)" for depth-only draws |
| return label; |
| } |
| |
| std::string BuildComputeSkSL(const Caps* caps, const ComputeStep* step) { |
| std::string sksl = |
| SkSL::String::printf("layout(local_size_x=%u, local_size_y=%u, local_size_z=%u) in;\n", |
| step->localDispatchSize().fWidth, |
| step->localDispatchSize().fHeight, |
| step->localDispatchSize().fDepth); |
| |
| const auto& bindingReqs = caps->resourceBindingRequirements(); |
| bool distinctRanges = bindingReqs.fDistinctIndexRanges; |
| bool separateSampler = bindingReqs.fSeparateTextureAndSamplerBinding; |
| |
| int index = 0; |
| int texIdx = 0; |
| // NOTE: SkSL Metal codegen always assigns the same binding index to a texture and its sampler. |
| // TODO: This could cause sampler indices to not be tightly packed if the sampler2D declaration |
| // comes after 1 or more storage texture declarations (which don't have samplers). An optional |
| // "layout(msl, sampler=T, texture=T)" syntax to count them separately (like we do for WGSL) |
| // could come in handy here but it's not supported in MSL codegen yet. |
| |
| for (const ComputeStep::ResourceDesc& r : step->resources()) { |
| using Type = ComputeStep::ResourceType; |
| switch (r.fType) { |
| case Type::kUniformBuffer: |
| SkSL::String::appendf(&sksl, "layout(binding=%d) uniform ", index++); |
| sksl += r.fSkSL; |
| break; |
| case Type::kStorageBuffer: |
| case Type::kIndirectBuffer: |
| SkSL::String::appendf(&sksl, "layout(binding=%d) buffer ", index++); |
| sksl += r.fSkSL; |
| break; |
| case Type::kReadOnlyStorageBuffer: |
| SkSL::String::appendf(&sksl, "layout(binding=%d) readonly buffer ", index++); |
| sksl += r.fSkSL; |
| break; |
| case Type::kWriteOnlyStorageTexture: |
| SkSL::String::appendf(&sksl, "layout(binding=%d, rgba8) writeonly texture2D ", |
| distinctRanges ? texIdx++ : index++); |
| sksl += r.fSkSL; |
| break; |
| case Type::kReadOnlyTexture: |
| SkSL::String::appendf(&sksl, "layout(binding=%d, rgba8) readonly texture2D ", |
| distinctRanges ? texIdx++ : index++); |
| sksl += r.fSkSL; |
| break; |
| case Type::kSampledTexture: |
| if (distinctRanges) { |
| SkSL::String::appendf(&sksl, "layout(metal, binding=%d) ", texIdx++); |
| } else if (separateSampler) { |
| SkSL::String::appendf( |
| &sksl, "layout(webgpu, sampler=%d, texture=%d) ", index, index + 1); |
| index += 2; |
| } else { |
| SkSL::String::appendf(&sksl, "layout(binding=%d) ", index++); |
| } |
| sksl += "sampler2D "; |
| sksl += r.fSkSL; |
| break; |
| } |
| sksl += ";\n"; |
| } |
| |
| sksl += step->computeSkSL(); |
| return sksl; |
| } |
| |
| } // namespace skgpu::graphite |