1 /*
2 * Copyright 2021 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/ContextUtils.h"
9
10 #include "src/gpu/BlendFormula.h"
11 #include "src/gpu/graphite/Caps.h"
12 #include "src/gpu/graphite/KeyContext.h"
13 #include "src/gpu/graphite/PaintParams.h"
14 #include "src/gpu/graphite/RecorderPriv.h"
15 #include "src/gpu/graphite/RenderPassDesc.h"
16 #include "src/gpu/graphite/Renderer.h"
17 #include "src/gpu/graphite/ShaderCodeDictionary.h"
18 #include "src/gpu/graphite/UniformManager.h"
19 #include "src/gpu/graphite/UniquePaintParamsID.h"
20 #include "src/gpu/graphite/compute/ComputeStep.h"
21 #include "src/gpu/graphite/geom/Geometry.h"
22 #include "src/sksl/SkSLString.h"
23 #include "src/sksl/SkSLUtil.h"
24
25 #include <string>
26
27 namespace skgpu::graphite {
28
ExtractPaintData(Recorder * recorder,PipelineDataGatherer * gatherer,PaintParamsKeyBuilder * builder,const Layout layout,const SkM44 & local2Dev,const PaintParams & p,const Geometry & geometry,const SkColorInfo & targetColorInfo)29 UniquePaintParamsID ExtractPaintData(Recorder* recorder,
30 PipelineDataGatherer* gatherer,
31 PaintParamsKeyBuilder* builder,
32 const Layout layout,
33 const SkM44& local2Dev,
34 const PaintParams& p,
35 const Geometry& geometry,
36 const SkColorInfo& targetColorInfo) {
37 SkDEBUGCODE(builder->checkReset());
38
39 gatherer->resetWithNewLayout(layout);
40
41 KeyContext keyContext(recorder,
42 local2Dev,
43 targetColorInfo,
44 geometry.isShape() || geometry.isEdgeAAQuad()
45 ? KeyContext::OptimizeSampling::kYes
46 : KeyContext::OptimizeSampling::kNo,
47 p.color());
48 p.toKey(keyContext, builder, gatherer);
49
50 return recorder->priv().shaderCodeDictionary()->findOrCreate(builder);
51 }
52
GetDstReadRequirement(const Caps * caps,std::optional<SkBlendMode> blendMode,Coverage coverage)53 DstReadRequirement GetDstReadRequirement(const Caps* caps,
54 std::optional<SkBlendMode> blendMode,
55 Coverage coverage) {
56 // If the blend mode is absent, this is assumed to be for a runtime blender, for which we always
57 // do a dst read.
58 // If the blend mode is plus, always do in-shader blending since we may be drawing to an
59 // unsaturated surface (e.g. F16) and we don't want to let the hardware clamp the color output
60 // in that case. We could check the draw dst properties to only do in-shader blending with plus
61 // when necessary, but we can't detect that during shader precompilation.
62 if (!blendMode || *blendMode > SkBlendMode::kLastCoeffMode ||
63 *blendMode == SkBlendMode::kPlus) {
64 return caps->getDstReadRequirement();
65 }
66
67 const bool isLCD = coverage == Coverage::kLCD;
68 const bool hasCoverage = coverage != Coverage::kNone;
69 BlendFormula blendFormula = isLCD ? skgpu::GetLCDBlendFormula(*blendMode)
70 : skgpu::GetBlendFormula(false, hasCoverage, *blendMode);
71 if ((blendFormula.hasSecondaryOutput() && !caps->shaderCaps()->fDualSourceBlendingSupport) ||
72 (coverage == Coverage::kLCD && blendMode != SkBlendMode::kSrcOver)) {
73 return caps->getDstReadRequirement();
74 }
75
76 return DstReadRequirement::kNone;
77 }
78
CollectIntrinsicUniforms(const Caps * caps,SkIRect viewport,SkIRect dstCopyBounds,UniformManager * uniforms)79 void CollectIntrinsicUniforms(const Caps* caps,
80 SkIRect viewport,
81 SkIRect dstCopyBounds,
82 UniformManager* uniforms) {
83 SkDEBUGCODE(uniforms->setExpectedUniforms(kIntrinsicUniforms, /*isSubstruct=*/false);)
84
85 // viewport
86 {
87 // The vertex shader needs to divide by the dimension and then multiply by 2, so do this
88 // once on the CPU. This is because viewport normalization wants to range from -1 to 1, and
89 // not 0 to 1. If any other user of the viewport uniform requires the true reciprocal or
90 // original dimensions, this can be adjusted.
91 SkASSERT(!viewport.isEmpty());
92 float invTwoW = 2.f / viewport.width();
93 float invTwoH = 2.f / viewport.height();
94
95 // If the NDC Y axis points up (opposite normal skia convention and the underlying view
96 // convention), upload the inverse height as a negative value. See ShaderInfo::Make
97 // for how this is used.
98 if (!caps->ndcYAxisPointsDown()) {
99 invTwoH *= -1.f;
100 }
101 uniforms->write(SkV4{(float) viewport.left(), (float) viewport.top(), invTwoW, invTwoH});
102 }
103
104 // dstCopyBounds
105 {
106 // Unlike viewport, dstCopyBounds can be empty so check for 0 dimensions and set the
107 // reciprocal to 0. It is also not doubled since its purpose is to normalize texture coords
108 // to 0 to 1, and not -1 to 1.
109 int width = dstCopyBounds.width();
110 int height = dstCopyBounds.height();
111 uniforms->write(SkV4{(float) dstCopyBounds.left(), (float) dstCopyBounds.top(),
112 width ? 1.f / width : 0.f, height ? 1.f / height : 0.f});
113 }
114
115 SkDEBUGCODE(uniforms->doneWithExpectedUniforms());
116 }
117
EmitSamplerLayout(const ResourceBindingRequirements & bindingReqs,int * binding)118 std::string EmitSamplerLayout(const ResourceBindingRequirements& bindingReqs, int* binding) {
119 std::string result;
120
121 // If fDistinctIndexRanges is false, then texture and sampler indices may clash with other
122 // resource indices. Graphite assumes that they will be placed in descriptor set (Vulkan) and
123 // bind group (Dawn) index 1.
124 const char* distinctIndexRange = bindingReqs.fDistinctIndexRanges ? "" : "set=1, ";
125
126 if (bindingReqs.fSeparateTextureAndSamplerBinding) {
127 int samplerIndex = (*binding)++;
128 int textureIndex = (*binding)++;
129 result = SkSL::String::printf("layout(webgpu, %ssampler=%d, texture=%d)",
130 distinctIndexRange,
131 samplerIndex,
132 textureIndex);
133 } else {
134 int samplerIndex = (*binding)++;
135 result = SkSL::String::printf("layout(%sbinding=%d)",
136 distinctIndexRange,
137 samplerIndex);
138 }
139 return result;
140 }
141
GetPipelineLabel(const ShaderCodeDictionary * dict,const RenderPassDesc & renderPassDesc,const RenderStep * renderStep,UniquePaintParamsID paintID)142 std::string GetPipelineLabel(const ShaderCodeDictionary* dict,
143 const RenderPassDesc& renderPassDesc,
144 const RenderStep* renderStep,
145 UniquePaintParamsID paintID) {
146 std::string label = renderPassDesc.toPipelineLabel().c_str(); // includes the write swizzle
147 label += " + ";
148 label += renderStep->name();
149 label += " + ";
150 label += dict->idToString(paintID).c_str(); // will be "(empty)" for depth-only draws
151 return label;
152 }
153
BuildComputeSkSL(const Caps * caps,const ComputeStep * step)154 std::string BuildComputeSkSL(const Caps* caps, const ComputeStep* step) {
155 std::string sksl =
156 SkSL::String::printf("layout(local_size_x=%u, local_size_y=%u, local_size_z=%u) in;\n",
157 step->localDispatchSize().fWidth,
158 step->localDispatchSize().fHeight,
159 step->localDispatchSize().fDepth);
160
161 const auto& bindingReqs = caps->resourceBindingRequirements();
162 bool distinctRanges = bindingReqs.fDistinctIndexRanges;
163 bool separateSampler = bindingReqs.fSeparateTextureAndSamplerBinding;
164
165 int index = 0;
166 int texIdx = 0;
167 // NOTE: SkSL Metal codegen always assigns the same binding index to a texture and its sampler.
168 // TODO: This could cause sampler indices to not be tightly packed if the sampler2D declaration
169 // comes after 1 or more storage texture declarations (which don't have samplers). An optional
170 // "layout(msl, sampler=T, texture=T)" syntax to count them separately (like we do for WGSL)
171 // could come in handy here but it's not supported in MSL codegen yet.
172
173 for (const ComputeStep::ResourceDesc& r : step->resources()) {
174 using Type = ComputeStep::ResourceType;
175 switch (r.fType) {
176 case Type::kUniformBuffer:
177 SkSL::String::appendf(&sksl, "layout(binding=%d) uniform ", index++);
178 sksl += r.fSkSL;
179 break;
180 case Type::kStorageBuffer:
181 case Type::kIndirectBuffer:
182 SkSL::String::appendf(&sksl, "layout(binding=%d) buffer ", index++);
183 sksl += r.fSkSL;
184 break;
185 case Type::kReadOnlyStorageBuffer:
186 SkSL::String::appendf(&sksl, "layout(binding=%d) readonly buffer ", index++);
187 sksl += r.fSkSL;
188 break;
189 case Type::kWriteOnlyStorageTexture:
190 SkSL::String::appendf(&sksl, "layout(binding=%d, rgba8) writeonly texture2D ",
191 distinctRanges ? texIdx++ : index++);
192 sksl += r.fSkSL;
193 break;
194 case Type::kReadOnlyTexture:
195 SkSL::String::appendf(&sksl, "layout(binding=%d, rgba8) readonly texture2D ",
196 distinctRanges ? texIdx++ : index++);
197 sksl += r.fSkSL;
198 break;
199 case Type::kSampledTexture:
200 if (distinctRanges) {
201 SkSL::String::appendf(&sksl, "layout(metal, binding=%d) ", texIdx++);
202 } else if (separateSampler) {
203 SkSL::String::appendf(
204 &sksl, "layout(webgpu, sampler=%d, texture=%d) ", index, index + 1);
205 index += 2;
206 } else {
207 SkSL::String::appendf(&sksl, "layout(binding=%d) ", index++);
208 }
209 sksl += "sampler2D ";
210 sksl += r.fSkSL;
211 break;
212 }
213 sksl += ";\n";
214 }
215
216 sksl += step->computeSkSL();
217 return sksl;
218 }
219
220 } // namespace skgpu::graphite
221