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/PaintParams.h"
9
10 #include "include/core/SkColorSpace.h"
11 #include "include/core/SkShader.h"
12 #include "src/core/SkBlendModeBlender.h"
13 #include "src/core/SkBlenderBase.h"
14 #include "src/core/SkColorSpacePriv.h"
15 #include "src/effects/colorfilters/SkColorFilterBase.h"
16 #include "src/gpu/Blend.h"
17 #include "src/gpu/DitherUtils.h"
18 #include "src/gpu/graphite/KeyContext.h"
19 #include "src/gpu/graphite/KeyHelpers.h"
20 #include "src/gpu/graphite/Log.h"
21 #include "src/gpu/graphite/PaintParamsKey.h"
22 #include "src/gpu/graphite/PipelineData.h"
23 #include "src/gpu/graphite/RecorderPriv.h"
24 #include "src/gpu/graphite/Uniform.h"
25 #include "src/shaders/SkShaderBase.h"
26
27 namespace skgpu::graphite {
28
29 namespace {
30
31 // This should be kept in sync w/ SkPaintPriv::ShouldDither and PaintOption::shouldDither
should_dither(const PaintParams & p,SkColorType dstCT)32 bool should_dither(const PaintParams& p, SkColorType dstCT) {
33 // The paint dither flag can veto.
34 if (!p.dither()) {
35 return false;
36 }
37
38 if (dstCT == kUnknown_SkColorType) {
39 return false;
40 }
41
42 // We always dither 565 or 4444 when requested.
43 if (dstCT == kRGB_565_SkColorType || dstCT == kARGB_4444_SkColorType) {
44 return true;
45 }
46
47 // Otherwise, dither is only needed for non-const paints.
48 return p.shader() && !as_SB(p.shader())->isConstant();
49 }
50
51 } // anonymous namespace
52
PaintParams(const SkPaint & paint,sk_sp<SkBlender> primitiveBlender,const CircularRRectClip & analyticClip,sk_sp<SkShader> clipShader,DstReadRequirement dstReadReq,bool skipColorXform)53 PaintParams::PaintParams(const SkPaint& paint,
54 sk_sp<SkBlender> primitiveBlender,
55 const CircularRRectClip& analyticClip,
56 sk_sp<SkShader> clipShader,
57 DstReadRequirement dstReadReq,
58 bool skipColorXform)
59 : fColor(paint.getColor4f())
60 , fFinalBlender(paint.refBlender())
61 , fShader(paint.refShader())
62 , fColorFilter(paint.refColorFilter())
63 , fPrimitiveBlender(std::move(primitiveBlender))
64 , fAnalyticClip(analyticClip)
65 , fClipShader(std::move(clipShader))
66 , fDstReadReq(dstReadReq)
67 , fSkipColorXform(skipColorXform)
68 , fDither(paint.isDither()) {}
69
70 PaintParams::PaintParams(const PaintParams& other) = default;
71 PaintParams::~PaintParams() = default;
72 PaintParams& PaintParams::operator=(const PaintParams& other) = default;
73
asFinalBlendMode() const74 std::optional<SkBlendMode> PaintParams::asFinalBlendMode() const {
75 return fFinalBlender ? as_BB(fFinalBlender)->asBlendMode()
76 : SkBlendMode::kSrcOver;
77 }
78
refFinalBlender() const79 sk_sp<SkBlender> PaintParams::refFinalBlender() const { return fFinalBlender; }
80
refShader() const81 sk_sp<SkShader> PaintParams::refShader() const { return fShader; }
82
refColorFilter() const83 sk_sp<SkColorFilter> PaintParams::refColorFilter() const { return fColorFilter; }
84
refPrimitiveBlender() const85 sk_sp<SkBlender> PaintParams::refPrimitiveBlender() const { return fPrimitiveBlender; }
86
Color4fPrepForDst(SkColor4f srcColor,const SkColorInfo & dstColorInfo)87 SkColor4f PaintParams::Color4fPrepForDst(SkColor4f srcColor, const SkColorInfo& dstColorInfo) {
88 // xform from sRGB to the destination colorspace
89 SkColorSpaceXformSteps steps(sk_srgb_singleton(), kUnpremul_SkAlphaType,
90 dstColorInfo.colorSpace(), kUnpremul_SkAlphaType);
91
92 SkColor4f result = srcColor;
93 steps.apply(result.vec());
94 return result;
95 }
96
Blend(const KeyContext & keyContext,PaintParamsKeyBuilder * keyBuilder,PipelineDataGatherer * gatherer,AddToKeyFn addBlendToKey,AddToKeyFn addSrcToKey,AddToKeyFn addDstToKey)97 void Blend(const KeyContext& keyContext,
98 PaintParamsKeyBuilder* keyBuilder,
99 PipelineDataGatherer* gatherer,
100 AddToKeyFn addBlendToKey,
101 AddToKeyFn addSrcToKey,
102 AddToKeyFn addDstToKey) {
103 BlendComposeBlock::BeginBlock(keyContext, keyBuilder, gatherer);
104
105 addSrcToKey();
106
107 addDstToKey();
108
109 addBlendToKey();
110
111 keyBuilder->endBlock(); // BlendComposeBlock
112 }
113
Compose(const KeyContext & keyContext,PaintParamsKeyBuilder * keyBuilder,PipelineDataGatherer * gatherer,AddToKeyFn addInnerToKey,AddToKeyFn addOuterToKey)114 void Compose(const KeyContext& keyContext,
115 PaintParamsKeyBuilder* keyBuilder,
116 PipelineDataGatherer* gatherer,
117 AddToKeyFn addInnerToKey,
118 AddToKeyFn addOuterToKey) {
119 ComposeBlock::BeginBlock(keyContext, keyBuilder, gatherer);
120
121 addInnerToKey();
122
123 addOuterToKey();
124
125 keyBuilder->endBlock(); // ComposeBlock
126 }
127
AddFixedBlendMode(const KeyContext & keyContext,PaintParamsKeyBuilder * builder,PipelineDataGatherer * gatherer,SkBlendMode bm)128 void AddFixedBlendMode(const KeyContext& keyContext,
129 PaintParamsKeyBuilder* builder,
130 PipelineDataGatherer* gatherer,
131 SkBlendMode bm) {
132 SkASSERT(bm <= SkBlendMode::kLastMode);
133 BuiltInCodeSnippetID id = static_cast<BuiltInCodeSnippetID>(kFixedBlendIDOffset +
134 static_cast<int>(bm));
135 builder->addBlock(id);
136 }
137
AddBlendMode(const KeyContext & keyContext,PaintParamsKeyBuilder * builder,PipelineDataGatherer * gatherer,SkBlendMode bm)138 void AddBlendMode(const KeyContext& keyContext,
139 PaintParamsKeyBuilder* builder,
140 PipelineDataGatherer* gatherer,
141 SkBlendMode bm) {
142 // For non-fixed blends, coefficient blend modes are combined into the same shader snippet.
143 // The same goes for the HSLC advanced blends. The remaining advanced blends are fairly unique
144 // in their implementations. To avoid having to compile all of their SkSL, they are treated as
145 // fixed blend modes.
146 SkSpan<const float> coeffs = skgpu::GetPorterDuffBlendConstants(bm);
147 if (!coeffs.empty()) {
148 PorterDuffBlenderBlock::AddBlock(keyContext, builder, gatherer, coeffs);
149 } else if (bm >= SkBlendMode::kHue) {
150 ReducedBlendModeInfo blendInfo = GetReducedBlendModeInfo(bm);
151 HSLCBlenderBlock::AddBlock(keyContext, builder, gatherer, blendInfo.fUniformData);
152 } else {
153 AddFixedBlendMode(keyContext, builder, gatherer, bm);
154 }
155 }
156
AddDitherBlock(const KeyContext & keyContext,PaintParamsKeyBuilder * builder,PipelineDataGatherer * gatherer,SkColorType ct)157 void AddDitherBlock(const KeyContext& keyContext,
158 PaintParamsKeyBuilder* builder,
159 PipelineDataGatherer* gatherer,
160 SkColorType ct) {
161 static const SkBitmap gLUT = skgpu::MakeDitherLUT();
162
163 sk_sp<TextureProxy> proxy = RecorderPriv::CreateCachedProxy(keyContext.recorder(), gLUT,
164 "DitherLUT");
165 if (keyContext.recorder() && !proxy) {
166 SKGPU_LOG_W("Couldn't create dither shader's LUT");
167 builder->addBlock(BuiltInCodeSnippetID::kPriorOutput);
168 return;
169 }
170
171 DitherShaderBlock::DitherData data(skgpu::DitherRangeForConfig(ct), std::move(proxy));
172
173 DitherShaderBlock::AddBlock(keyContext, builder, gatherer, data);
174 }
175
addPaintColorToKey(const KeyContext & keyContext,PaintParamsKeyBuilder * keyBuilder,PipelineDataGatherer * gatherer) const176 void PaintParams::addPaintColorToKey(const KeyContext& keyContext,
177 PaintParamsKeyBuilder* keyBuilder,
178 PipelineDataGatherer* gatherer) const {
179 if (fShader) {
180 AddToKey(keyContext, keyBuilder, gatherer, fShader.get());
181 } else {
182 RGBPaintColorBlock::AddBlock(keyContext, keyBuilder, gatherer);
183 }
184 }
185
186 /**
187 * Primitive blend blocks are used to blend either the paint color or the output of another shader
188 * with a primitive color emitted by certain draw geometry calls (drawVertices, drawAtlas, etc.).
189 * Dst: primitiveColor Src: Paint color/shader output
190 */
handlePrimitiveColor(const KeyContext & keyContext,PaintParamsKeyBuilder * keyBuilder,PipelineDataGatherer * gatherer) const191 void PaintParams::handlePrimitiveColor(const KeyContext& keyContext,
192 PaintParamsKeyBuilder* keyBuilder,
193 PipelineDataGatherer* gatherer) const {
194 if (fPrimitiveBlender) {
195 Blend(keyContext, keyBuilder, gatherer,
196 /* addBlendToKey= */ [&] () -> void {
197 AddToKey(keyContext, keyBuilder, gatherer, fPrimitiveBlender.get());
198 },
199 /* addSrcToKey= */ [&]() -> void {
200 this->addPaintColorToKey(keyContext, keyBuilder, gatherer);
201 },
202 /* addDstToKey= */ [&]() -> void {
203 PrimitiveColorBlock::AddBlock(keyContext, keyBuilder, gatherer);
204 });
205 } else {
206 this->addPaintColorToKey(keyContext, keyBuilder, gatherer);
207 }
208 }
209
210 // Apply the paint's alpha value.
handlePaintAlpha(const KeyContext & keyContext,PaintParamsKeyBuilder * keyBuilder,PipelineDataGatherer * gatherer) const211 void PaintParams::handlePaintAlpha(const KeyContext& keyContext,
212 PaintParamsKeyBuilder* keyBuilder,
213 PipelineDataGatherer* gatherer) const {
214
215 if (!fShader && !fPrimitiveBlender) {
216 // If there is no shader and no primitive blending the input to the colorFilter stage
217 // is just the premultiplied paint color.
218 SkPMColor4f paintColor = PaintParams::Color4fPrepForDst(fColor,
219 keyContext.dstColorInfo()).premul();
220 SolidColorShaderBlock::AddBlock(keyContext, keyBuilder, gatherer, paintColor);
221 return;
222 }
223
224 if (fColor.fA != 1.0f) {
225 Blend(keyContext, keyBuilder, gatherer,
226 /* addBlendToKey= */ [&] () -> void {
227 AddFixedBlendMode(keyContext, keyBuilder, gatherer, SkBlendMode::kSrcIn);
228 },
229 /* addSrcToKey= */ [&]() -> void {
230 this->handlePrimitiveColor(keyContext, keyBuilder, gatherer);
231 },
232 /* addDstToKey= */ [&]() -> void {
233 AlphaOnlyPaintColorBlock::AddBlock(keyContext, keyBuilder, gatherer);
234 });
235 } else {
236 this->handlePrimitiveColor(keyContext, keyBuilder, gatherer);
237 }
238 }
239
handleColorFilter(const KeyContext & keyContext,PaintParamsKeyBuilder * builder,PipelineDataGatherer * gatherer) const240 void PaintParams::handleColorFilter(const KeyContext& keyContext,
241 PaintParamsKeyBuilder* builder,
242 PipelineDataGatherer* gatherer) const {
243 if (fColorFilter) {
244 Compose(keyContext, builder, gatherer,
245 /* addInnerToKey= */ [&]() -> void {
246 this->handlePaintAlpha(keyContext, builder, gatherer);
247 },
248 /* addOuterToKey= */ [&]() -> void {
249 AddToKey(keyContext, builder, gatherer, fColorFilter.get());
250 });
251 } else {
252 this->handlePaintAlpha(keyContext, builder, gatherer);
253 }
254 }
255
handleDithering(const KeyContext & keyContext,PaintParamsKeyBuilder * builder,PipelineDataGatherer * gatherer) const256 void PaintParams::handleDithering(const KeyContext& keyContext,
257 PaintParamsKeyBuilder* builder,
258 PipelineDataGatherer* gatherer) const {
259
260 #ifndef SK_IGNORE_GPU_DITHER
261 SkColorType ct = keyContext.dstColorInfo().colorType();
262 if (should_dither(*this, ct)) {
263 Compose(keyContext, builder, gatherer,
264 /* addInnerToKey= */ [&]() -> void {
265 this->handleColorFilter(keyContext, builder, gatherer);
266 },
267 /* addOuterToKey= */ [&]() -> void {
268 AddDitherBlock(keyContext, builder, gatherer, ct);
269 });
270 } else
271 #endif
272 {
273 this->handleColorFilter(keyContext, builder, gatherer);
274 }
275 }
276
handleClipping(const KeyContext & keyContext,PaintParamsKeyBuilder * builder,PipelineDataGatherer * gatherer) const277 void PaintParams::handleClipping(const KeyContext& keyContext,
278 PaintParamsKeyBuilder* builder,
279 PipelineDataGatherer* gatherer) const {
280 if (!fAnalyticClip.isEmpty()) {
281 float radius = fAnalyticClip.fRadius + 0.5f;
282 // N.B.: Because the clip data is normally used with depth-based clipping,
283 // the shape is inverted from its usual state. We re-invert here to
284 // match what the shader snippet expects.
285 SkPoint radiusPair = {(fAnalyticClip.fInverted) ? radius : -radius, 1.0f/radius};
286 CircularRRectClipBlock::CircularRRectClipData data(
287 fAnalyticClip.fBounds.makeOutset(0.5f).asSkRect(),
288 radiusPair,
289 fAnalyticClip.edgeSelectRect());
290 if (fClipShader) {
291 // For both an analytic clip and clip shader, we need to compose them together into
292 // a single clipping root node.
293 Blend(keyContext, builder, gatherer,
294 /* addBlendToKey= */ [&]() -> void {
295 AddFixedBlendMode(keyContext, builder, gatherer, SkBlendMode::kModulate);
296 },
297 /* addSrcToKey= */ [&]() -> void {
298 CircularRRectClipBlock::AddBlock(keyContext, builder, gatherer, data);
299 },
300 /* addDstToKey= */ [&]() -> void {
301 AddToKey(keyContext, builder, gatherer, fClipShader.get());
302 });
303 } else {
304 // Without a clip shader, the analytic clip can be the clipping root node.
305 CircularRRectClipBlock::AddBlock(keyContext, builder, gatherer, data);
306 }
307 } else if (fClipShader) {
308 // Since there's no analytic clip, the clipping root node can be fClipShader directly.
309 AddToKey(keyContext, builder, gatherer, fClipShader.get());
310 }
311 }
312
toKey(const KeyContext & keyContext,PaintParamsKeyBuilder * builder,PipelineDataGatherer * gatherer) const313 void PaintParams::toKey(const KeyContext& keyContext,
314 PaintParamsKeyBuilder* builder,
315 PipelineDataGatherer* gatherer) const {
316 // Root Node 0 is the source color, which is the output of all effects post dithering
317 this->handleDithering(keyContext, builder, gatherer);
318
319 // Root Node 1 is the final blender
320 std::optional<SkBlendMode> finalBlendMode = this->asFinalBlendMode();
321 if (finalBlendMode) {
322 if (fDstReadReq == DstReadRequirement::kNone) {
323 // With no shader blending, be as explicit as possible about the final blend
324 AddFixedBlendMode(keyContext, builder, gatherer, *finalBlendMode);
325 } else {
326 // With shader blending, use AddBlendMode() to select the more universal blend functions
327 // when possible. Technically we could always use a fixed blend mode but would then
328 // over-generate when encountering certain classes of blends. This is most problematic
329 // on devices that wouldn't support dual-source blending, so help them out by at least
330 // not requiring lots of pipelines.
331 AddBlendMode(keyContext, builder, gatherer, *finalBlendMode);
332 }
333 } else {
334 AddToKey(keyContext, builder, gatherer, fFinalBlender.get());
335 }
336
337 // Optional Root Node 2 is the clip
338 this->handleClipping(keyContext, builder, gatherer);
339 }
340
341 // TODO(b/330864257): Can be deleted once keys are determined by the Device draw.
notifyImagesInUse(Recorder * recorder,DrawContext * drawContext) const342 void PaintParams::notifyImagesInUse(Recorder* recorder,
343 DrawContext* drawContext) const {
344 if (fShader) {
345 NotifyImagesInUse(recorder, drawContext, fShader.get());
346 }
347 if (fPrimitiveBlender) {
348 NotifyImagesInUse(recorder, drawContext, fPrimitiveBlender.get());
349 }
350 if (fColorFilter) {
351 NotifyImagesInUse(recorder, drawContext, fColorFilter.get());
352 }
353 if (fFinalBlender) {
354 NotifyImagesInUse(recorder, drawContext, fFinalBlender.get());
355 }
356 if (fClipShader) {
357 NotifyImagesInUse(recorder, drawContext, fClipShader.get());
358 }
359 }
360
361 } // namespace skgpu::graphite
362