1 | /* |
2 | * Copyright 2019 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 "include/effects/SkRuntimeEffect.h" |
9 | |
10 | #include "include/core/SkAlphaType.h" |
11 | #include "include/core/SkBlender.h" |
12 | #include "include/core/SkCapabilities.h" |
13 | #include "include/core/SkColorFilter.h" |
14 | #include "include/core/SkData.h" |
15 | #include "include/private/SkSLDefines.h" |
16 | #include "include/private/base/SkAlign.h" |
17 | #include "include/private/base/SkDebug.h" |
18 | #include "include/private/base/SkMutex.h" |
19 | #include "include/private/base/SkOnce.h" |
20 | #include "include/private/base/SkTArray.h" |
21 | #include "src/base/SkArenaAlloc.h" |
22 | #include "src/base/SkEnumBitMask.h" |
23 | #include "src/base/SkNoDestructor.h" |
24 | #include "src/core/SkBlenderBase.h" |
25 | #include "src/core/SkChecksum.h" |
26 | #include "src/core/SkColorSpacePriv.h" |
27 | #include "src/core/SkColorSpaceXformSteps.h" |
28 | #include "src/core/SkEffectPriv.h" |
29 | #include "src/core/SkLRUCache.h" |
30 | #include "src/core/SkRasterPipeline.h" |
31 | #include "src/core/SkRasterPipelineOpList.h" |
32 | #include "src/core/SkReadBuffer.h" |
33 | #include "src/core/SkRuntimeBlender.h" |
34 | #include "src/core/SkRuntimeEffectPriv.h" |
35 | #include "src/core/SkStreamPriv.h" |
36 | #include "src/core/SkWriteBuffer.h" |
37 | #include "src/effects/colorfilters/SkColorFilterBase.h" |
38 | #include "src/effects/colorfilters/SkRuntimeColorFilter.h" |
39 | #include "src/shaders/SkLocalMatrixShader.h" |
40 | #include "src/shaders/SkRuntimeShader.h" |
41 | #include "src/shaders/SkShaderBase.h" |
42 | #include "src/sksl/SkSLAnalysis.h" |
43 | #include "src/sksl/SkSLBuiltinTypes.h" |
44 | #include "src/sksl/SkSLCompiler.h" |
45 | #include "src/sksl/SkSLContext.h" |
46 | #include "src/sksl/SkSLProgramKind.h" |
47 | #include "src/sksl/SkSLProgramSettings.h" |
48 | #include "src/sksl/SkSLUtil.h" |
49 | #include "src/sksl/analysis/SkSLProgramUsage.h" |
50 | #include "src/sksl/codegen/SkSLRasterPipelineBuilder.h" |
51 | #include "src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h" |
52 | #include "src/sksl/ir/SkSLFunctionDeclaration.h" |
53 | #include "src/sksl/ir/SkSLLayout.h" |
54 | #include "src/sksl/ir/SkSLModifierFlags.h" |
55 | #include "src/sksl/ir/SkSLProgram.h" |
56 | #include "src/sksl/ir/SkSLProgramElement.h" |
57 | #include "src/sksl/ir/SkSLStatement.h" |
58 | #include "src/sksl/ir/SkSLType.h" |
59 | #include "src/sksl/ir/SkSLVarDeclarations.h" |
60 | #include "src/sksl/ir/SkSLVariable.h" |
61 | #include "src/sksl/tracing/SkSLDebugTracePriv.h" |
62 | |
63 | #include <algorithm> |
64 | |
65 | using namespace skia_private; |
66 | |
67 | class SkColorSpace; |
68 | struct SkIPoint; |
69 | |
70 | constexpr bool kRPEnableLiveTrace = false; |
71 | |
72 | #if defined(SK_BUILD_FOR_DEBUGGER) |
73 | #define SK_LENIENT_SKSL_DESERIALIZATION 1 |
74 | #else |
75 | #define SK_LENIENT_SKSL_DESERIALIZATION 0 |
76 | #endif |
77 | |
78 | using ChildType = SkRuntimeEffect::ChildType; |
79 | |
80 | static bool init_uniform_type(const SkSL::Context& ctx, |
81 | const SkSL::Type* type, |
82 | SkRuntimeEffect::Uniform* v) { |
83 | using Type = SkRuntimeEffect::Uniform::Type; |
84 | if (type->matches(other: *ctx.fTypes.fFloat)) { v->type = Type::kFloat; return true; } |
85 | if (type->matches(other: *ctx.fTypes.fHalf)) { v->type = Type::kFloat; return true; } |
86 | if (type->matches(other: *ctx.fTypes.fFloat2)) { v->type = Type::kFloat2; return true; } |
87 | if (type->matches(other: *ctx.fTypes.fHalf2)) { v->type = Type::kFloat2; return true; } |
88 | if (type->matches(other: *ctx.fTypes.fFloat3)) { v->type = Type::kFloat3; return true; } |
89 | if (type->matches(other: *ctx.fTypes.fHalf3)) { v->type = Type::kFloat3; return true; } |
90 | if (type->matches(other: *ctx.fTypes.fFloat4)) { v->type = Type::kFloat4; return true; } |
91 | if (type->matches(other: *ctx.fTypes.fHalf4)) { v->type = Type::kFloat4; return true; } |
92 | if (type->matches(other: *ctx.fTypes.fFloat2x2)) { v->type = Type::kFloat2x2; return true; } |
93 | if (type->matches(other: *ctx.fTypes.fHalf2x2)) { v->type = Type::kFloat2x2; return true; } |
94 | if (type->matches(other: *ctx.fTypes.fFloat3x3)) { v->type = Type::kFloat3x3; return true; } |
95 | if (type->matches(other: *ctx.fTypes.fHalf3x3)) { v->type = Type::kFloat3x3; return true; } |
96 | if (type->matches(other: *ctx.fTypes.fFloat4x4)) { v->type = Type::kFloat4x4; return true; } |
97 | if (type->matches(other: *ctx.fTypes.fHalf4x4)) { v->type = Type::kFloat4x4; return true; } |
98 | |
99 | if (type->matches(other: *ctx.fTypes.fInt)) { v->type = Type::kInt; return true; } |
100 | if (type->matches(other: *ctx.fTypes.fInt2)) { v->type = Type::kInt2; return true; } |
101 | if (type->matches(other: *ctx.fTypes.fInt3)) { v->type = Type::kInt3; return true; } |
102 | if (type->matches(other: *ctx.fTypes.fInt4)) { v->type = Type::kInt4; return true; } |
103 | |
104 | return false; |
105 | } |
106 | |
107 | SkRuntimeEffect::Uniform SkRuntimeEffectPriv::VarAsUniform(const SkSL::Variable& var, |
108 | const SkSL::Context& context, |
109 | size_t* offset) { |
110 | using Uniform = SkRuntimeEffect::Uniform; |
111 | SkASSERT(var.modifierFlags().isUniform()); |
112 | Uniform uni; |
113 | uni.name = var.name(); |
114 | uni.flags = 0; |
115 | uni.count = 1; |
116 | |
117 | const SkSL::Type* type = &var.type(); |
118 | if (type->isArray()) { |
119 | uni.flags |= Uniform::kArray_Flag; |
120 | uni.count = type->columns(); |
121 | type = &type->componentType(); |
122 | } |
123 | |
124 | if (type->hasPrecision() && !type->highPrecision()) { |
125 | uni.flags |= Uniform::kHalfPrecision_Flag; |
126 | } |
127 | |
128 | SkAssertResult(init_uniform_type(context, type, &uni)); |
129 | if (var.layout().fFlags & SkSL::LayoutFlag::kColor) { |
130 | uni.flags |= Uniform::kColor_Flag; |
131 | } |
132 | |
133 | uni.offset = *offset; |
134 | *offset += uni.sizeInBytes(); |
135 | SkASSERT(SkIsAlign4(*offset)); |
136 | return uni; |
137 | } |
138 | |
139 | sk_sp<const SkData> SkRuntimeEffectPriv::TransformUniforms( |
140 | SkSpan<const SkRuntimeEffect::Uniform> uniforms, |
141 | sk_sp<const SkData> originalData, |
142 | const SkColorSpace* dstCS) { |
143 | if (!dstCS) { |
144 | // There's no destination color-space; we can early-out immediately. |
145 | return originalData; |
146 | } |
147 | SkColorSpaceXformSteps steps(sk_srgb_singleton(), kUnpremul_SkAlphaType, |
148 | dstCS, kUnpremul_SkAlphaType); |
149 | return TransformUniforms(uniforms, originalData: std::move(originalData), steps); |
150 | } |
151 | |
152 | sk_sp<const SkData> SkRuntimeEffectPriv::TransformUniforms( |
153 | SkSpan<const SkRuntimeEffect::Uniform> uniforms, |
154 | sk_sp<const SkData> originalData, |
155 | const SkColorSpaceXformSteps& steps) { |
156 | using Flags = SkRuntimeEffect::Uniform::Flags; |
157 | using Type = SkRuntimeEffect::Uniform::Type; |
158 | |
159 | sk_sp<SkData> data = nullptr; |
160 | auto writableData = [&]() { |
161 | if (!data) { |
162 | data = SkData::MakeWithCopy(data: originalData->data(), length: originalData->size()); |
163 | } |
164 | return data->writable_data(); |
165 | }; |
166 | |
167 | for (const auto& u : uniforms) { |
168 | if (u.flags & Flags::kColor_Flag) { |
169 | SkASSERT(u.type == Type::kFloat3 || u.type == Type::kFloat4); |
170 | if (steps.flags.mask()) { |
171 | float* color = SkTAddOffset<float>(ptr: writableData(), byteOffset: u.offset); |
172 | if (u.type == Type::kFloat4) { |
173 | // RGBA, easy case |
174 | for (int i = 0; i < u.count; ++i) { |
175 | steps.apply(rgba: color); |
176 | color += 4; |
177 | } |
178 | } else { |
179 | // RGB, need to pad out to include alpha. Technically, this isn't necessary, |
180 | // because steps shouldn't include unpremul or premul, and thus shouldn't |
181 | // read or write the fourth element. But let's be safe. |
182 | float rgba[4]; |
183 | for (int i = 0; i < u.count; ++i) { |
184 | memcpy(dest: rgba, src: color, n: 3 * sizeof(float)); |
185 | rgba[3] = 1.0f; |
186 | steps.apply(rgba); |
187 | memcpy(dest: color, src: rgba, n: 3 * sizeof(float)); |
188 | color += 3; |
189 | } |
190 | } |
191 | } |
192 | } |
193 | } |
194 | return data ? data : originalData; |
195 | } |
196 | |
197 | const SkSL::RP::Program* SkRuntimeEffect::getRPProgram(SkSL::DebugTracePriv* debugTrace) const { |
198 | // Lazily compile the program the first time `getRPProgram` is called. |
199 | // By using an SkOnce, we avoid thread hazards and behave in a conceptually const way, but we |
200 | // can avoid the cost of invoking the RP code generator until it's actually needed. |
201 | fCompileRPProgramOnce([&] { |
202 | // We generally do not run the inliner when an SkRuntimeEffect program is initially created, |
203 | // because the final compile to native shader code will do this. However, in SkRP, there's |
204 | // no additional compilation occurring, so we need to manually inline here if we want the |
205 | // performance boost of inlining. |
206 | if (!(fFlags & kDisableOptimization_Flag)) { |
207 | SkSL::Compiler compiler(SkSL::ShaderCapsFactory::Standalone()); |
208 | fBaseProgram->fConfig->fSettings.fInlineThreshold = SkSL::kDefaultInlineThreshold; |
209 | compiler.runInliner(program&: *fBaseProgram); |
210 | } |
211 | |
212 | SkSL::DebugTracePriv tempDebugTrace; |
213 | if (debugTrace) { |
214 | const_cast<SkRuntimeEffect*>(this)->fRPProgram = MakeRasterPipelineProgram( |
215 | program: *fBaseProgram, function: fMain, debugTrace, /*writeTraceOps=*/true); |
216 | } else if (kRPEnableLiveTrace) { |
217 | debugTrace = &tempDebugTrace; |
218 | const_cast<SkRuntimeEffect*>(this)->fRPProgram = MakeRasterPipelineProgram( |
219 | program: *fBaseProgram, function: fMain, debugTrace, /*writeTraceOps=*/false); |
220 | } else { |
221 | const_cast<SkRuntimeEffect*>(this)->fRPProgram = MakeRasterPipelineProgram( |
222 | program: *fBaseProgram, function: fMain, /*debugTrace=*/nullptr, /*writeTraceOps=*/false); |
223 | } |
224 | |
225 | if (kRPEnableLiveTrace) { |
226 | if (fRPProgram) { |
227 | SkDebugf(format: "-----\n\n" ); |
228 | SkDebugfStream stream; |
229 | fRPProgram->dump(out: &stream, /*writeInstructionCount=*/true); |
230 | SkDebugf(format: "\n-----\n\n" ); |
231 | } else { |
232 | SkDebugf(format: "----- RP unsupported -----\n\n" ); |
233 | } |
234 | } |
235 | }); |
236 | |
237 | return fRPProgram.get(); |
238 | } |
239 | |
240 | SkSpan<const float> SkRuntimeEffectPriv::UniformsAsSpan( |
241 | SkSpan<const SkRuntimeEffect::Uniform> uniforms, |
242 | sk_sp<const SkData> originalData, |
243 | bool alwaysCopyIntoAlloc, |
244 | const SkColorSpace* destColorSpace, |
245 | SkArenaAlloc* alloc) { |
246 | // Transform the uniforms into the destination colorspace. |
247 | sk_sp<const SkData> transformedData = SkRuntimeEffectPriv::TransformUniforms(uniforms, |
248 | originalData, |
249 | dstCS: destColorSpace); |
250 | if (alwaysCopyIntoAlloc || originalData != transformedData) { |
251 | // The transformed uniform data's lifetime is not long enough to reuse; instead, we copy the |
252 | // uniform data directly into the alloc. |
253 | int numBytes = transformedData->size(); |
254 | int numFloats = numBytes / sizeof(float); |
255 | float* uniformsInAlloc = alloc->makeArrayDefault<float>(count: numFloats); |
256 | memcpy(dest: uniformsInAlloc, src: transformedData->data(), n: numBytes); |
257 | return SkSpan{uniformsInAlloc, numFloats}; |
258 | } |
259 | // It's safe to return a pointer into existing data. |
260 | return SkSpan{static_cast<const float*>(originalData->data()), |
261 | originalData->size() / sizeof(float)}; |
262 | } |
263 | |
264 | bool RuntimeEffectRPCallbacks::appendShader(int index) { |
265 | if (SkShader* shader = fChildren[index].shader()) { |
266 | if (fSampleUsages[index].isPassThrough()) { |
267 | // Given a passthrough sample, the total-matrix is still as valid as before. |
268 | return as_SB(shader)->appendStages(fStage, fMatrix); |
269 | } |
270 | // For a non-passthrough sample, we need to explicitly mark the total-matrix as invalid. |
271 | SkShaders::MatrixRec nonPassthroughMatrix = fMatrix; |
272 | nonPassthroughMatrix.markTotalMatrixInvalid(); |
273 | return as_SB(shader)->appendStages(fStage, nonPassthroughMatrix); |
274 | } |
275 | // Return the paint color when a null child shader is evaluated. |
276 | fStage.fPipeline->append_constant_color(alloc: fStage.fAlloc, color: fStage.fPaintColor); |
277 | return true; |
278 | } |
279 | bool RuntimeEffectRPCallbacks::appendColorFilter(int index) { |
280 | if (SkColorFilter* colorFilter = fChildren[index].colorFilter()) { |
281 | return as_CFB(filter: colorFilter)->appendStages(rec: fStage, /*shaderIsOpaque=*/false); |
282 | } |
283 | // Return the original color as-is when a null child color filter is evaluated. |
284 | return true; |
285 | } |
286 | bool RuntimeEffectRPCallbacks::appendBlender(int index) { |
287 | if (SkBlender* blender = fChildren[index].blender()) { |
288 | return as_BB(blend: blender)->appendStages(rec: fStage); |
289 | } |
290 | // Return a source-over blend when a null blender is evaluated. |
291 | fStage.fPipeline->append(SkRasterPipelineOp::srcover); |
292 | return true; |
293 | } |
294 | |
295 | // TODO: If an effect calls these intrinsics more than once, we could cache and re-use the steps |
296 | // object(s), rather than re-creating them in the arena repeatedly. |
297 | void RuntimeEffectRPCallbacks::toLinearSrgb(const void* color) { |
298 | if (fStage.fDstCS) { |
299 | SkColorSpaceXformSteps xform{fStage.fDstCS, kUnpremul_SkAlphaType, |
300 | sk_srgb_linear_singleton(), kUnpremul_SkAlphaType}; |
301 | if (xform.flags.mask()) { |
302 | // We have a non-identity colorspace transform; apply it. |
303 | this->applyColorSpaceXform(tempXform: xform, color); |
304 | } |
305 | } |
306 | } |
307 | |
308 | void RuntimeEffectRPCallbacks::fromLinearSrgb(const void* color) { |
309 | if (fStage.fDstCS) { |
310 | SkColorSpaceXformSteps xform{sk_srgb_linear_singleton(), kUnpremul_SkAlphaType, |
311 | fStage.fDstCS, kUnpremul_SkAlphaType}; |
312 | if (xform.flags.mask()) { |
313 | // We have a non-identity colorspace transform; apply it. |
314 | this->applyColorSpaceXform(tempXform: xform, color); |
315 | } |
316 | } |
317 | } |
318 | |
319 | void RuntimeEffectRPCallbacks::applyColorSpaceXform(const SkColorSpaceXformSteps& tempXform, |
320 | const void* color) { |
321 | // Copy the transform steps into our alloc. |
322 | SkColorSpaceXformSteps* xform = fStage.fAlloc->make<SkColorSpaceXformSteps>(args: tempXform); |
323 | |
324 | // Put the color into src.rgba (and temporarily stash the execution mask there instead). |
325 | fStage.fPipeline->append(op: SkRasterPipelineOp::exchange_src, ctx: color); |
326 | // Add the color space transform to our raster pipeline. |
327 | xform->apply(fStage.fPipeline); |
328 | // Restore the execution mask, and move the color back into program data. |
329 | fStage.fPipeline->append(op: SkRasterPipelineOp::exchange_src, ctx: color); |
330 | } |
331 | |
332 | bool SkRuntimeEffectPriv::CanDraw(const SkCapabilities* caps, const SkSL::Program* program) { |
333 | SkASSERT(caps && program); |
334 | SkASSERT(program->fConfig->enforcesSkSLVersion()); |
335 | return program->fConfig->fRequiredSkSLVersion <= caps->skslVersion(); |
336 | } |
337 | |
338 | bool SkRuntimeEffectPriv::CanDraw(const SkCapabilities* caps, const SkRuntimeEffect* effect) { |
339 | SkASSERT(effect); |
340 | return CanDraw(caps, program: effect->fBaseProgram.get()); |
341 | } |
342 | |
343 | ////////////////////////////////////////////////////////////////////////////// |
344 | |
345 | static bool flattenable_is_valid_as_child(const SkFlattenable* f) { |
346 | if (!f) { return true; } |
347 | switch (f->getFlattenableType()) { |
348 | case SkFlattenable::kSkShader_Type: |
349 | case SkFlattenable::kSkColorFilter_Type: |
350 | case SkFlattenable::kSkBlender_Type: |
351 | return true; |
352 | default: |
353 | return false; |
354 | } |
355 | } |
356 | |
357 | SkRuntimeEffect::ChildPtr::ChildPtr(sk_sp<SkFlattenable> f) : fChild(std::move(f)) { |
358 | SkASSERT(flattenable_is_valid_as_child(fChild.get())); |
359 | } |
360 | |
361 | static ChildType child_type(const SkSL::Type& type) { |
362 | switch (type.typeKind()) { |
363 | case SkSL::Type::TypeKind::kBlender: return ChildType::kBlender; |
364 | case SkSL::Type::TypeKind::kColorFilter: return ChildType::kColorFilter; |
365 | case SkSL::Type::TypeKind::kShader: return ChildType::kShader; |
366 | default: SkUNREACHABLE; |
367 | } |
368 | } |
369 | |
370 | static bool verify_child_effects(const std::vector<SkRuntimeEffect::Child>& reflected, |
371 | SkSpan<const SkRuntimeEffect::ChildPtr> effectPtrs) { |
372 | // Verify that the number of passed-in child-effect pointers matches the SkSL code. |
373 | if (reflected.size() != effectPtrs.size()) { |
374 | return false; |
375 | } |
376 | |
377 | // Verify that each child object's type matches its declared type in the SkSL. |
378 | for (size_t i = 0; i < effectPtrs.size(); ++i) { |
379 | std::optional<ChildType> effectType = effectPtrs[i].type(); |
380 | if (effectType && effectType != reflected[i].type) { |
381 | return false; |
382 | } |
383 | } |
384 | return true; |
385 | } |
386 | |
387 | /** |
388 | * If `effect` is specified, then the number and type of child objects are validated against the |
389 | * children() of `effect`. If it's nullptr, this is skipped, allowing deserialization of children, |
390 | * even when the effect could not be constructed (ie, due to malformed SkSL). |
391 | */ |
392 | bool SkRuntimeEffectPriv::ReadChildEffects(SkReadBuffer& buffer, |
393 | const SkRuntimeEffect* effect, |
394 | TArray<SkRuntimeEffect::ChildPtr>* children) { |
395 | size_t childCount = buffer.read32(); |
396 | if (effect && !buffer.validate(isValid: childCount == effect->children().size())) { |
397 | return false; |
398 | } |
399 | |
400 | children->clear(); |
401 | children->reserve_exact(n: childCount); |
402 | |
403 | for (size_t i = 0; i < childCount; i++) { |
404 | sk_sp<SkFlattenable> obj(buffer.readRawFlattenable()); |
405 | if (!flattenable_is_valid_as_child(f: obj.get())) { |
406 | buffer.validate(isValid: false); |
407 | return false; |
408 | } |
409 | children->push_back(t: std::move(obj)); |
410 | } |
411 | |
412 | // If we are validating against an effect, make sure any (non-null) children are the right type |
413 | if (effect) { |
414 | auto childInfo = effect->children(); |
415 | SkASSERT(childInfo.size() == SkToSizeT(children->size())); |
416 | for (size_t i = 0; i < childCount; i++) { |
417 | std::optional<ChildType> ct = (*children)[i].type(); |
418 | if (ct.has_value() && (*ct) != childInfo[i].type) { |
419 | buffer.validate(isValid: false); |
420 | } |
421 | } |
422 | } |
423 | |
424 | return buffer.isValid(); |
425 | } |
426 | |
427 | void SkRuntimeEffectPriv::WriteChildEffects( |
428 | SkWriteBuffer& buffer, SkSpan<const SkRuntimeEffect::ChildPtr> children) { |
429 | buffer.write32(value: children.size()); |
430 | for (const auto& child : children) { |
431 | buffer.writeFlattenable(flattenable: child.flattenable()); |
432 | } |
433 | } |
434 | |
435 | SkSL::ProgramSettings SkRuntimeEffect::MakeSettings(const Options& options) { |
436 | SkSL::ProgramSettings settings; |
437 | settings.fInlineThreshold = 0; |
438 | settings.fForceNoInline = options.forceUnoptimized; |
439 | settings.fOptimize = !options.forceUnoptimized; |
440 | settings.fMaxVersionAllowed = options.maxVersionAllowed; |
441 | |
442 | // SkSL created by the GPU backend is typically parsed, converted to a backend format, |
443 | // and the IR is immediately discarded. In that situation, it makes sense to use node |
444 | // pools to accelerate the IR allocations. Here, SkRuntimeEffect instances are often |
445 | // long-lived (especially those created internally for runtime FPs). In this situation, |
446 | // we're willing to pay for a slightly longer compile so that we don't waste huge |
447 | // amounts of memory. |
448 | settings.fUseMemoryPool = false; |
449 | return settings; |
450 | } |
451 | |
452 | // TODO: Many errors aren't caught until we process the generated Program here. Catching those |
453 | // in the IR generator would provide better errors messages (with locations). |
454 | #define RETURN_FAILURE(...) return Result{nullptr, SkStringPrintf(__VA_ARGS__)} |
455 | |
456 | SkRuntimeEffect::Result SkRuntimeEffect::MakeFromSource(SkString sksl, |
457 | const Options& options, |
458 | SkSL::ProgramKind kind) { |
459 | SkSL::Compiler compiler(SkSL::ShaderCapsFactory::Standalone()); |
460 | SkSL::ProgramSettings settings = MakeSettings(options); |
461 | std::unique_ptr<SkSL::Program> program = |
462 | compiler.convertProgram(kind, text: std::string(sksl.c_str(), sksl.size()), settings); |
463 | |
464 | if (!program) { |
465 | RETURN_FAILURE("%s" , compiler.errorText().c_str()); |
466 | } |
467 | |
468 | return MakeInternal(program: std::move(program), options, kind); |
469 | } |
470 | |
471 | SkRuntimeEffect::Result SkRuntimeEffect::MakeInternal(std::unique_ptr<SkSL::Program> program, |
472 | const Options& options, |
473 | SkSL::ProgramKind kind) { |
474 | SkSL::Compiler compiler(SkSL::ShaderCapsFactory::Standalone()); |
475 | |
476 | uint32_t flags = 0; |
477 | switch (kind) { |
478 | case SkSL::ProgramKind::kPrivateRuntimeColorFilter: |
479 | case SkSL::ProgramKind::kRuntimeColorFilter: |
480 | // TODO(skia:11209): Figure out a way to run ES3+ color filters on the CPU. This doesn't |
481 | // need to be fast - it could just be direct IR evaluation. But without it, there's no |
482 | // way for us to fully implement the SkColorFilter API (eg, `filterColor`) |
483 | if (!SkRuntimeEffectPriv::CanDraw(caps: SkCapabilities::RasterBackend().get(), |
484 | program: program.get())) { |
485 | RETURN_FAILURE("SkSL color filters must target #version 100" ); |
486 | } |
487 | flags |= kAllowColorFilter_Flag; |
488 | break; |
489 | case SkSL::ProgramKind::kPrivateRuntimeShader: |
490 | case SkSL::ProgramKind::kRuntimeShader: |
491 | flags |= kAllowShader_Flag; |
492 | break; |
493 | case SkSL::ProgramKind::kPrivateRuntimeBlender: |
494 | case SkSL::ProgramKind::kRuntimeBlender: |
495 | flags |= kAllowBlender_Flag; |
496 | break; |
497 | default: |
498 | SkUNREACHABLE; |
499 | } |
500 | |
501 | if (options.forceUnoptimized) { |
502 | flags |= kDisableOptimization_Flag; |
503 | } |
504 | |
505 | // Find 'main', then locate the sample coords parameter. (It might not be present.) |
506 | const SkSL::FunctionDeclaration* main = program->getFunction(functionName: "main" ); |
507 | if (!main) { |
508 | RETURN_FAILURE("missing 'main' function" ); |
509 | } |
510 | const SkSL::Variable* coordsParam = main->getMainCoordsParameter(); |
511 | |
512 | const SkSL::ProgramUsage::VariableCounts sampleCoordsUsage = |
513 | coordsParam ? program->usage()->get(*coordsParam) |
514 | : SkSL::ProgramUsage::VariableCounts{}; |
515 | |
516 | if (sampleCoordsUsage.fRead || sampleCoordsUsage.fWrite) { |
517 | flags |= kUsesSampleCoords_Flag; |
518 | } |
519 | |
520 | // Color filters and blends are not allowed to depend on position (local or device) in any way. |
521 | // The signature of main, and the declarations in sksl_rt_colorfilter/sksl_rt_blend should |
522 | // guarantee this. |
523 | if (flags & (kAllowColorFilter_Flag | kAllowBlender_Flag)) { |
524 | SkASSERT(!(flags & kUsesSampleCoords_Flag)); |
525 | SkASSERT(!SkSL::Analysis::ReferencesFragCoords(*program)); |
526 | } |
527 | |
528 | if (SkSL::Analysis::CallsSampleOutsideMain(program: *program)) { |
529 | flags |= kSamplesOutsideMain_Flag; |
530 | } |
531 | |
532 | // Look for color filters that preserve the input alpha. This analysis is very conservative, and |
533 | // only returns true when the input alpha is returned as-is from main() with no intervening |
534 | // copies or arithmetic. |
535 | if (flags & kAllowColorFilter_Flag) { |
536 | if (SkSL::Analysis::ReturnsInputAlpha(function: *main->definition(), usage: *program->usage())) { |
537 | flags |= kAlphaUnchanged_Flag; |
538 | } |
539 | } |
540 | |
541 | // Determine if this effect uses of the color transform intrinsics. Effects need to know this |
542 | // so they can allocate color transform objects, etc. |
543 | if (SkSL::Analysis::CallsColorTransformIntrinsics(program: *program)) { |
544 | flags |= kUsesColorTransform_Flag; |
545 | } |
546 | |
547 | // Shaders are the only thing that cares about this, but it's inexpensive (and safe) to call. |
548 | if (SkSL::Analysis::ReturnsOpaqueColor(function: *main->definition())) { |
549 | flags |= kAlwaysOpaque_Flag; |
550 | } |
551 | |
552 | // Go through program elements, pulling out information that we need |
553 | size_t offset = 0; |
554 | std::vector<Uniform> uniforms; |
555 | std::vector<Child> children; |
556 | std::vector<SkSL::SampleUsage> sampleUsages; |
557 | int elidedSampleCoords = 0; |
558 | const SkSL::Context& ctx(compiler.context()); |
559 | |
560 | for (const SkSL::ProgramElement* elem : program->elements()) { |
561 | // Variables (uniform, etc.) |
562 | if (elem->is<SkSL::GlobalVarDeclaration>()) { |
563 | const SkSL::GlobalVarDeclaration& global = elem->as<SkSL::GlobalVarDeclaration>(); |
564 | const SkSL::VarDeclaration& varDecl = global.declaration()->as<SkSL::VarDeclaration>(); |
565 | |
566 | const SkSL::Variable& var = *varDecl.var(); |
567 | const SkSL::Type& varType = var.type(); |
568 | |
569 | // Child effects that can be sampled ('shader', 'colorFilter', 'blender') |
570 | if (varType.isEffectChild()) { |
571 | Child c; |
572 | c.name = var.name(); |
573 | c.type = child_type(type: varType); |
574 | c.index = children.size(); |
575 | children.push_back(x: c); |
576 | auto usage = SkSL::Analysis::GetSampleUsage( |
577 | program: *program, child: var, writesToSampleCoords: sampleCoordsUsage.fWrite != 0, elidedSampleCoordCount: &elidedSampleCoords); |
578 | // If the child is never sampled, we pretend that it's actually in PassThrough mode. |
579 | // Otherwise, the GP code for collecting transforms and emitting transform code gets |
580 | // very confused, leading to asserts and bad (backend) shaders. There's an implicit |
581 | // assumption that every FP is used by its parent. (skbug.com/12429) |
582 | sampleUsages.push_back(x: usage.isSampled() ? usage |
583 | : SkSL::SampleUsage::PassThrough()); |
584 | } |
585 | // 'uniform' variables |
586 | else if (var.modifierFlags().isUniform()) { |
587 | uniforms.push_back(x: SkRuntimeEffectPriv::VarAsUniform(var, context: ctx, offset: &offset)); |
588 | } |
589 | } |
590 | } |
591 | |
592 | // If the sample coords are never written to, then we will have converted sample calls that use |
593 | // them unmodified into "passthrough" sampling. If all references to the sample coords were of |
594 | // that form, then we don't actually "use" sample coords. We unset the flag to prevent creating |
595 | // an extra (unused) varying holding the coords. |
596 | if (elidedSampleCoords == sampleCoordsUsage.fRead && sampleCoordsUsage.fWrite == 0) { |
597 | flags &= ~kUsesSampleCoords_Flag; |
598 | } |
599 | |
600 | #undef RETURN_FAILURE |
601 | |
602 | sk_sp<SkRuntimeEffect> effect(new SkRuntimeEffect(std::move(program), |
603 | options, |
604 | *main->definition(), |
605 | std::move(uniforms), |
606 | std::move(children), |
607 | std::move(sampleUsages), |
608 | flags)); |
609 | return Result{.effect: std::move(effect), .errorText: SkString()}; |
610 | } |
611 | |
612 | sk_sp<SkRuntimeEffect> SkRuntimeEffect::makeUnoptimizedClone() { |
613 | // Compile with maximally-permissive options; any restrictions we need to enforce were already |
614 | // handled when the original SkRuntimeEffect was made. We don't keep around the Options struct |
615 | // from when it was initially made so we don't know what was originally requested. |
616 | Options options; |
617 | options.forceUnoptimized = true; |
618 | options.maxVersionAllowed = SkSL::Version::k300; |
619 | options.allowPrivateAccess = true; |
620 | |
621 | // We do know the original ProgramKind, so we don't need to re-derive it. |
622 | SkSL::ProgramKind kind = fBaseProgram->fConfig->fKind; |
623 | |
624 | // Attempt to recompile the program's source with optimizations off. This ensures that the |
625 | // Debugger shows results on every line, even for things that could be optimized away (static |
626 | // branches, unused variables, etc). If recompilation fails, we fall back to the original code. |
627 | SkSL::Compiler compiler(SkSL::ShaderCapsFactory::Standalone()); |
628 | SkSL::ProgramSettings settings = MakeSettings(options); |
629 | std::unique_ptr<SkSL::Program> program = |
630 | compiler.convertProgram(kind, text: *fBaseProgram->fSource, settings); |
631 | |
632 | if (!program) { |
633 | // Turning off compiler optimizations can theoretically expose a program error that |
634 | // had been optimized away (e.g. "all control paths return a value" might be found on a path |
635 | // that is completely eliminated in the optimized program). |
636 | // If this happens, the debugger will just have to show the optimized code. |
637 | return sk_ref_sp(obj: this); |
638 | } |
639 | |
640 | SkRuntimeEffect::Result result = MakeInternal(program: std::move(program), options, kind); |
641 | if (!result.effect) { |
642 | // Nothing in MakeInternal should change as a result of optimizations being toggled. |
643 | SkDEBUGFAILF("makeUnoptimizedClone: MakeInternal failed\n%s" , |
644 | result.errorText.c_str()); |
645 | return sk_ref_sp(obj: this); |
646 | } |
647 | |
648 | return result.effect; |
649 | } |
650 | |
651 | SkRuntimeEffect::Result SkRuntimeEffect::MakeForColorFilter(SkString sksl, const Options& options) { |
652 | auto programKind = options.allowPrivateAccess ? SkSL::ProgramKind::kPrivateRuntimeColorFilter |
653 | : SkSL::ProgramKind::kRuntimeColorFilter; |
654 | auto result = MakeFromSource(sksl: std::move(sksl), options, kind: programKind); |
655 | SkASSERT(!result.effect || result.effect->allowColorFilter()); |
656 | return result; |
657 | } |
658 | |
659 | SkRuntimeEffect::Result SkRuntimeEffect::MakeForShader(SkString sksl, const Options& options) { |
660 | auto programKind = options.allowPrivateAccess ? SkSL::ProgramKind::kPrivateRuntimeShader |
661 | : SkSL::ProgramKind::kRuntimeShader; |
662 | auto result = MakeFromSource(sksl: std::move(sksl), options, kind: programKind); |
663 | SkASSERT(!result.effect || result.effect->allowShader()); |
664 | return result; |
665 | } |
666 | |
667 | SkRuntimeEffect::Result SkRuntimeEffect::MakeForBlender(SkString sksl, const Options& options) { |
668 | auto programKind = options.allowPrivateAccess ? SkSL::ProgramKind::kPrivateRuntimeBlender |
669 | : SkSL::ProgramKind::kRuntimeBlender; |
670 | auto result = MakeFromSource(sksl: std::move(sksl), options, kind: programKind); |
671 | SkASSERT(!result.effect || result.effect->allowBlender()); |
672 | return result; |
673 | } |
674 | |
675 | sk_sp<SkRuntimeEffect> SkMakeCachedRuntimeEffect( |
676 | SkRuntimeEffect::Result (*make)(SkString sksl, const SkRuntimeEffect::Options&), |
677 | SkString sksl) { |
678 | static SkNoDestructor<SkMutex> mutex; |
679 | static SkNoDestructor<SkLRUCache<uint64_t, sk_sp<SkRuntimeEffect>>> cache(11 /*arbitrary*/); |
680 | |
681 | uint64_t key = SkChecksum::Hash64(data: sksl.c_str(), bytes: sksl.size()); |
682 | { |
683 | SkAutoMutexExclusive _(*mutex); |
684 | if (sk_sp<SkRuntimeEffect>* found = cache->find(key)) { |
685 | return *found; |
686 | } |
687 | } |
688 | |
689 | SkRuntimeEffect::Options options; |
690 | SkRuntimeEffectPriv::AllowPrivateAccess(options: &options); |
691 | |
692 | auto [effect, err] = make(std::move(sksl), options); |
693 | if (!effect) { |
694 | SkDEBUGFAILF("%s" , err.c_str()); |
695 | return nullptr; |
696 | } |
697 | SkASSERT(err.isEmpty()); |
698 | |
699 | { |
700 | SkAutoMutexExclusive _(*mutex); |
701 | cache->insert_or_update(key, value: effect); |
702 | } |
703 | return effect; |
704 | } |
705 | |
706 | static size_t uniform_element_size(SkRuntimeEffect::Uniform::Type type) { |
707 | switch (type) { |
708 | case SkRuntimeEffect::Uniform::Type::kFloat: return sizeof(float); |
709 | case SkRuntimeEffect::Uniform::Type::kFloat2: return sizeof(float) * 2; |
710 | case SkRuntimeEffect::Uniform::Type::kFloat3: return sizeof(float) * 3; |
711 | case SkRuntimeEffect::Uniform::Type::kFloat4: return sizeof(float) * 4; |
712 | |
713 | case SkRuntimeEffect::Uniform::Type::kFloat2x2: return sizeof(float) * 4; |
714 | case SkRuntimeEffect::Uniform::Type::kFloat3x3: return sizeof(float) * 9; |
715 | case SkRuntimeEffect::Uniform::Type::kFloat4x4: return sizeof(float) * 16; |
716 | |
717 | case SkRuntimeEffect::Uniform::Type::kInt: return sizeof(int); |
718 | case SkRuntimeEffect::Uniform::Type::kInt2: return sizeof(int) * 2; |
719 | case SkRuntimeEffect::Uniform::Type::kInt3: return sizeof(int) * 3; |
720 | case SkRuntimeEffect::Uniform::Type::kInt4: return sizeof(int) * 4; |
721 | default: SkUNREACHABLE; |
722 | } |
723 | } |
724 | |
725 | size_t SkRuntimeEffect::Uniform::sizeInBytes() const { |
726 | static_assert(sizeof(int) == sizeof(float)); |
727 | return uniform_element_size(type: this->type) * this->count; |
728 | } |
729 | |
730 | SkRuntimeEffect::SkRuntimeEffect(std::unique_ptr<SkSL::Program> baseProgram, |
731 | const Options& options, |
732 | const SkSL::FunctionDefinition& main, |
733 | std::vector<Uniform>&& uniforms, |
734 | std::vector<Child>&& children, |
735 | std::vector<SkSL::SampleUsage>&& sampleUsages, |
736 | uint32_t flags) |
737 | : fHash(SkChecksum::Hash32(data: baseProgram->fSource->c_str(), bytes: baseProgram->fSource->size())) |
738 | , fBaseProgram(std::move(baseProgram)) |
739 | , fMain(main) |
740 | , fUniforms(std::move(uniforms)) |
741 | , fChildren(std::move(children)) |
742 | , fSampleUsages(std::move(sampleUsages)) |
743 | , fFlags(flags) { |
744 | SkASSERT(fBaseProgram); |
745 | SkASSERT(fChildren.size() == fSampleUsages.size()); |
746 | |
747 | // Everything from SkRuntimeEffect::Options which could influence the compiled result needs to |
748 | // be accounted for in `fHash`. If you've added a new field to Options and caused the static- |
749 | // assert below to trigger, please incorporate your field into `fHash` and update KnownOptions |
750 | // to match the layout of Options. |
751 | struct KnownOptions { |
752 | bool forceUnoptimized, allowPrivateAccess; |
753 | SkSL::Version maxVersionAllowed; |
754 | }; |
755 | static_assert(sizeof(Options) == sizeof(KnownOptions)); |
756 | fHash = SkChecksum::Hash32(data: &options.forceUnoptimized, |
757 | bytes: sizeof(options.forceUnoptimized), seed: fHash); |
758 | fHash = SkChecksum::Hash32(data: &options.allowPrivateAccess, |
759 | bytes: sizeof(options.allowPrivateAccess), seed: fHash); |
760 | fHash = SkChecksum::Hash32(data: &options.maxVersionAllowed, |
761 | bytes: sizeof(options.maxVersionAllowed), seed: fHash); |
762 | } |
763 | |
764 | SkRuntimeEffect::~SkRuntimeEffect() = default; |
765 | |
766 | const std::string& SkRuntimeEffect::source() const { |
767 | return *fBaseProgram->fSource; |
768 | } |
769 | |
770 | size_t SkRuntimeEffect::uniformSize() const { |
771 | return fUniforms.empty() ? 0 |
772 | : SkAlign4(x: fUniforms.back().offset + fUniforms.back().sizeInBytes()); |
773 | } |
774 | |
775 | const SkRuntimeEffect::Uniform* SkRuntimeEffect::findUniform(std::string_view name) const { |
776 | auto iter = std::find_if(first: fUniforms.begin(), last: fUniforms.end(), pred: [name](const Uniform& u) { |
777 | return u.name == name; |
778 | }); |
779 | return iter == fUniforms.end() ? nullptr : &(*iter); |
780 | } |
781 | |
782 | const SkRuntimeEffect::Child* SkRuntimeEffect::findChild(std::string_view name) const { |
783 | auto iter = std::find_if(first: fChildren.begin(), last: fChildren.end(), pred: [name](const Child& c) { |
784 | return c.name == name; |
785 | }); |
786 | return iter == fChildren.end() ? nullptr : &(*iter); |
787 | } |
788 | |
789 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
790 | |
791 | sk_sp<SkShader> SkRuntimeEffectPriv::MakeDeferredShader( |
792 | const SkRuntimeEffect* effect, |
793 | UniformsCallback uniformsCallback, |
794 | SkSpan<const SkRuntimeEffect::ChildPtr> children, |
795 | const SkMatrix* localMatrix) { |
796 | if (!effect->allowShader()) { |
797 | return nullptr; |
798 | } |
799 | if (!verify_child_effects(reflected: effect->fChildren, effectPtrs: children)) { |
800 | return nullptr; |
801 | } |
802 | if (!uniformsCallback) { |
803 | return nullptr; |
804 | } |
805 | return SkLocalMatrixShader::MakeWrapped<SkRuntimeShader>(localMatrix, |
806 | args: sk_ref_sp(obj: effect), |
807 | /*debugTrace=*/args: nullptr, |
808 | args: std::move(uniformsCallback), |
809 | args&: children); |
810 | } |
811 | |
812 | sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<const SkData> uniforms, |
813 | sk_sp<SkShader> childShaders[], |
814 | size_t childCount, |
815 | const SkMatrix* localMatrix) const { |
816 | STArray<4, ChildPtr> children(childCount); |
817 | for (size_t i = 0; i < childCount; ++i) { |
818 | children.emplace_back(args&: childShaders[i]); |
819 | } |
820 | return this->makeShader(uniforms: std::move(uniforms), children: SkSpan(children), localMatrix); |
821 | } |
822 | |
823 | sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<const SkData> uniforms, |
824 | SkSpan<const ChildPtr> children, |
825 | const SkMatrix* localMatrix) const { |
826 | if (!this->allowShader()) { |
827 | return nullptr; |
828 | } |
829 | if (!verify_child_effects(reflected: fChildren, effectPtrs: children)) { |
830 | return nullptr; |
831 | } |
832 | if (!uniforms) { |
833 | uniforms = SkData::MakeEmpty(); |
834 | } |
835 | if (uniforms->size() != this->uniformSize()) { |
836 | return nullptr; |
837 | } |
838 | return SkLocalMatrixShader::MakeWrapped<SkRuntimeShader>(localMatrix, |
839 | args: sk_ref_sp(obj: this), |
840 | /*debugTrace=*/args: nullptr, |
841 | args: std::move(uniforms), |
842 | args&: children); |
843 | } |
844 | |
845 | sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<const SkData> uniforms, |
846 | sk_sp<SkColorFilter> childColorFilters[], |
847 | size_t childCount) const { |
848 | STArray<4, ChildPtr> children(childCount); |
849 | for (size_t i = 0; i < childCount; ++i) { |
850 | children.emplace_back(args&: childColorFilters[i]); |
851 | } |
852 | return this->makeColorFilter(uniforms: std::move(uniforms), children: SkSpan(children)); |
853 | } |
854 | |
855 | sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<const SkData> uniforms, |
856 | SkSpan<const ChildPtr> children) const { |
857 | if (!this->allowColorFilter()) { |
858 | return nullptr; |
859 | } |
860 | if (!verify_child_effects(reflected: fChildren, effectPtrs: children)) { |
861 | return nullptr; |
862 | } |
863 | if (!uniforms) { |
864 | uniforms = SkData::MakeEmpty(); |
865 | } |
866 | if (uniforms->size() != this->uniformSize()) { |
867 | return nullptr; |
868 | } |
869 | return sk_make_sp<SkRuntimeColorFilter>(args: sk_ref_sp(obj: this), args: std::move(uniforms), args&: children); |
870 | } |
871 | |
872 | sk_sp<SkColorFilter> SkRuntimeEffect::makeColorFilter(sk_sp<const SkData> uniforms) const { |
873 | return this->makeColorFilter(uniforms: std::move(uniforms), /*children=*/{}); |
874 | } |
875 | |
876 | sk_sp<SkBlender> SkRuntimeEffect::makeBlender(sk_sp<const SkData> uniforms, |
877 | SkSpan<const ChildPtr> children) const { |
878 | if (!this->allowBlender()) { |
879 | return nullptr; |
880 | } |
881 | if (!verify_child_effects(reflected: fChildren, effectPtrs: children)) { |
882 | return nullptr; |
883 | } |
884 | if (!uniforms) { |
885 | uniforms = SkData::MakeEmpty(); |
886 | } |
887 | if (uniforms->size() != this->uniformSize()) { |
888 | return nullptr; |
889 | } |
890 | return sk_make_sp<SkRuntimeBlender>(args: sk_ref_sp(obj: this), args: std::move(uniforms), args&: children); |
891 | } |
892 | |
893 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
894 | |
895 | SkRuntimeEffect::TracedShader SkRuntimeEffect::MakeTraced(sk_sp<SkShader> shader, |
896 | const SkIPoint& traceCoord) { |
897 | SkRuntimeEffect* effect = as_SB(shader)->asRuntimeEffect(); |
898 | if (!effect) { |
899 | return TracedShader{.shader: nullptr, .debugTrace: nullptr}; |
900 | } |
901 | // An SkShader with an attached SkRuntimeEffect must be an SkRuntimeShader. |
902 | SkRuntimeShader* rtShader = static_cast<SkRuntimeShader*>(shader.get()); |
903 | return rtShader->makeTracedClone(coord: traceCoord); |
904 | } |
905 | |
906 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
907 | |
908 | std::optional<ChildType> SkRuntimeEffect::ChildPtr::type() const { |
909 | if (fChild) { |
910 | switch (fChild->getFlattenableType()) { |
911 | case SkFlattenable::kSkShader_Type: |
912 | return ChildType::kShader; |
913 | case SkFlattenable::kSkColorFilter_Type: |
914 | return ChildType::kColorFilter; |
915 | case SkFlattenable::kSkBlender_Type: |
916 | return ChildType::kBlender; |
917 | default: |
918 | break; |
919 | } |
920 | } |
921 | return std::nullopt; |
922 | } |
923 | |
924 | SkShader* SkRuntimeEffect::ChildPtr::shader() const { |
925 | return (fChild && fChild->getFlattenableType() == SkFlattenable::kSkShader_Type) |
926 | ? static_cast<SkShader*>(fChild.get()) |
927 | : nullptr; |
928 | } |
929 | |
930 | SkColorFilter* SkRuntimeEffect::ChildPtr::colorFilter() const { |
931 | return (fChild && fChild->getFlattenableType() == SkFlattenable::kSkColorFilter_Type) |
932 | ? static_cast<SkColorFilter*>(fChild.get()) |
933 | : nullptr; |
934 | } |
935 | |
936 | SkBlender* SkRuntimeEffect::ChildPtr::blender() const { |
937 | return (fChild && fChild->getFlattenableType() == SkFlattenable::kSkBlender_Type) |
938 | ? static_cast<SkBlender*>(fChild.get()) |
939 | : nullptr; |
940 | } |
941 | |
942 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
943 | |
944 | void SkRuntimeEffect::RegisterFlattenables() { |
945 | SK_REGISTER_FLATTENABLE(SkRuntimeBlender); |
946 | SK_REGISTER_FLATTENABLE(SkRuntimeColorFilter); |
947 | SK_REGISTER_FLATTENABLE(SkRuntimeShader); |
948 | |
949 | // Previous name |
950 | SkFlattenable::Register(name: "SkRTShader" , SkRuntimeShader::CreateProc); |
951 | } |
952 | |
953 | SkRuntimeShaderBuilder::SkRuntimeShaderBuilder(sk_sp<SkRuntimeEffect> effect) |
954 | : SkRuntimeEffectBuilder(std::move(effect)) {} |
955 | |
956 | SkRuntimeShaderBuilder::~SkRuntimeShaderBuilder() = default; |
957 | |
958 | sk_sp<SkShader> SkRuntimeShaderBuilder::makeShader(const SkMatrix* localMatrix) const { |
959 | return this->effect()->makeShader(uniforms: this->uniforms(), children: this->children(), localMatrix); |
960 | } |
961 | |
962 | SkRuntimeBlendBuilder::SkRuntimeBlendBuilder(sk_sp<SkRuntimeEffect> effect) |
963 | : SkRuntimeEffectBuilder(std::move(effect)) {} |
964 | |
965 | SkRuntimeBlendBuilder::~SkRuntimeBlendBuilder() = default; |
966 | |
967 | sk_sp<SkBlender> SkRuntimeBlendBuilder::makeBlender() const { |
968 | return this->effect()->makeBlender(uniforms: this->uniforms(), children: this->children()); |
969 | } |
970 | |
971 | SkRuntimeColorFilterBuilder::SkRuntimeColorFilterBuilder(sk_sp<SkRuntimeEffect> effect) |
972 | : SkRuntimeEffectBuilder(std::move(effect)) {} |
973 | |
974 | SkRuntimeColorFilterBuilder::~SkRuntimeColorFilterBuilder() = default; |
975 | |
976 | sk_sp<SkColorFilter> SkRuntimeColorFilterBuilder::makeColorFilter() const { |
977 | return this->effect()->makeColorFilter(uniforms: this->uniforms(), children: this->children()); |
978 | } |
979 | |