/src/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp
Line | Count | Source (jump to first uncovered line) |
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/sksl/codegen/SkSLRasterPipelineCodeGenerator.h" |
9 | | |
10 | | #include "include/core/SkPoint.h" |
11 | | #include "include/core/SkSpan.h" |
12 | | #include "include/private/base/SkTArray.h" |
13 | | #include "include/private/base/SkTo.h" |
14 | | #include "src/base/SkEnumBitMask.h" |
15 | | #include "src/base/SkStringView.h" |
16 | | #include "src/base/SkUtils.h" |
17 | | #include "src/core/SkTHash.h" |
18 | | #include "src/sksl/SkSLAnalysis.h" |
19 | | #include "src/sksl/SkSLBuiltinTypes.h" |
20 | | #include "src/sksl/SkSLCompiler.h" |
21 | | #include "src/sksl/SkSLConstantFolder.h" |
22 | | #include "src/sksl/SkSLContext.h" |
23 | | #include "src/sksl/SkSLDefines.h" |
24 | | #include "src/sksl/SkSLIntrinsicList.h" |
25 | | #include "src/sksl/SkSLOperator.h" |
26 | | #include "src/sksl/SkSLPosition.h" |
27 | | #include "src/sksl/analysis/SkSLProgramUsage.h" |
28 | | #include "src/sksl/codegen/SkSLRasterPipelineBuilder.h" |
29 | | #include "src/sksl/ir/SkSLBinaryExpression.h" |
30 | | #include "src/sksl/ir/SkSLBlock.h" |
31 | | #include "src/sksl/ir/SkSLBreakStatement.h" |
32 | | #include "src/sksl/ir/SkSLChildCall.h" |
33 | | #include "src/sksl/ir/SkSLConstructor.h" |
34 | | #include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" |
35 | | #include "src/sksl/ir/SkSLConstructorMatrixResize.h" |
36 | | #include "src/sksl/ir/SkSLConstructorSplat.h" |
37 | | #include "src/sksl/ir/SkSLContinueStatement.h" |
38 | | #include "src/sksl/ir/SkSLDoStatement.h" |
39 | | #include "src/sksl/ir/SkSLExpression.h" |
40 | | #include "src/sksl/ir/SkSLExpressionStatement.h" |
41 | | #include "src/sksl/ir/SkSLFieldAccess.h" |
42 | | #include "src/sksl/ir/SkSLForStatement.h" |
43 | | #include "src/sksl/ir/SkSLFunctionCall.h" |
44 | | #include "src/sksl/ir/SkSLFunctionDeclaration.h" |
45 | | #include "src/sksl/ir/SkSLFunctionDefinition.h" |
46 | | #include "src/sksl/ir/SkSLIRNode.h" |
47 | | #include "src/sksl/ir/SkSLIfStatement.h" |
48 | | #include "src/sksl/ir/SkSLIndexExpression.h" |
49 | | #include "src/sksl/ir/SkSLLayout.h" |
50 | | #include "src/sksl/ir/SkSLLiteral.h" |
51 | | #include "src/sksl/ir/SkSLModifierFlags.h" |
52 | | #include "src/sksl/ir/SkSLPostfixExpression.h" |
53 | | #include "src/sksl/ir/SkSLPrefixExpression.h" |
54 | | #include "src/sksl/ir/SkSLProgram.h" |
55 | | #include "src/sksl/ir/SkSLProgramElement.h" |
56 | | #include "src/sksl/ir/SkSLReturnStatement.h" |
57 | | #include "src/sksl/ir/SkSLStatement.h" |
58 | | #include "src/sksl/ir/SkSLSwitchCase.h" |
59 | | #include "src/sksl/ir/SkSLSwitchStatement.h" |
60 | | #include "src/sksl/ir/SkSLSwizzle.h" |
61 | | #include "src/sksl/ir/SkSLTernaryExpression.h" |
62 | | #include "src/sksl/ir/SkSLType.h" |
63 | | #include "src/sksl/ir/SkSLVarDeclarations.h" |
64 | | #include "src/sksl/ir/SkSLVariable.h" |
65 | | #include "src/sksl/ir/SkSLVariableReference.h" |
66 | | #include "src/sksl/tracing/SkSLDebugTracePriv.h" |
67 | | #include "src/sksl/transform/SkSLTransform.h" |
68 | | |
69 | | #include <algorithm> |
70 | | #include <climits> |
71 | | #include <cstddef> |
72 | | #include <cstdint> |
73 | | #include <float.h> |
74 | | #include <iterator> |
75 | | #include <optional> |
76 | | #include <string> |
77 | | #include <string_view> |
78 | | #include <utility> |
79 | | #include <vector> |
80 | | |
81 | | using namespace skia_private; |
82 | | |
83 | | namespace SkSL { |
84 | | namespace RP { |
85 | | |
86 | 0 | static bool unsupported() { |
87 | | // If MakeRasterPipelineProgram returns false, set a breakpoint here for more information. |
88 | 0 | return false; |
89 | 0 | } |
90 | | |
91 | | class AutoContinueMask; |
92 | | class Generator; |
93 | | class LValue; |
94 | | |
95 | | class SlotManager { |
96 | | public: |
97 | 153 | SlotManager(std::vector<SlotDebugInfo>* i) : fSlotDebugInfo(i) {} |
98 | | |
99 | | /** Used by `createSlots` to add this variable to SlotDebugInfo inside the DebugTrace. */ |
100 | | void addSlotDebugInfoForGroup(const std::string& varName, |
101 | | const Type& type, |
102 | | Position pos, |
103 | | int* groupIndex, |
104 | | bool isFunctionReturnValue); |
105 | | void addSlotDebugInfo(const std::string& varName, |
106 | | const Type& type, |
107 | | Position pos, |
108 | | bool isFunctionReturnValue); |
109 | | |
110 | | /** Creates slots associated with an SkSL variable or return value. */ |
111 | | SlotRange createSlots(std::string name, |
112 | | const Type& type, |
113 | | Position pos, |
114 | | bool isFunctionReturnValue); |
115 | | |
116 | | /** |
117 | | * Associates previously-created slots with an SkSL variable; this can allow multiple variables |
118 | | * to share overlapping ranges. If the variable was already associated with a slot range, |
119 | | * returns the previously associated range. |
120 | | */ |
121 | | std::optional<SlotRange> mapVariableToSlots(const Variable& v, SlotRange range); |
122 | | |
123 | | /** |
124 | | * Deletes the existing mapping between a variable and its slots; a future call to |
125 | | * `getVariableSlots` will see this as a brand new variable and associate new slots. |
126 | | */ |
127 | | void unmapVariableSlots(const Variable& v); |
128 | | |
129 | | /** Looks up the slots associated with an SkSL variable; creates the slot if necessary. */ |
130 | | SlotRange getVariableSlots(const Variable& v); |
131 | | |
132 | | /** |
133 | | * Looks up the slots associated with an SkSL function's return value; creates the range if |
134 | | * necessary. Note that recursion is never supported, so we don't need to maintain return values |
135 | | * in a stack; we can just statically allocate one slot per function call-site. |
136 | | */ |
137 | | SlotRange getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f); |
138 | | |
139 | | /** Returns the total number of slots consumed. */ |
140 | 153 | int slotCount() const { return fSlotCount; } |
141 | | |
142 | | private: |
143 | | THashMap<const IRNode*, SlotRange> fSlotMap; |
144 | | int fSlotCount = 0; |
145 | | std::vector<SlotDebugInfo>* fSlotDebugInfo; |
146 | | }; |
147 | | |
148 | | class AutoStack { |
149 | | public: |
150 | | /** |
151 | | * Creates a temporary stack. The caller is responsible for discarding every entry on this |
152 | | * stack before ~AutoStack is reached. |
153 | | */ |
154 | | explicit AutoStack(Generator* g); |
155 | | ~AutoStack(); |
156 | | |
157 | | /** Activates the associated stack. */ |
158 | | void enter(); |
159 | | |
160 | | /** Undoes a call to `enter`, returning to the previously-active stack. */ |
161 | | void exit(); |
162 | | |
163 | | /** Returns the stack ID of this AutoStack. */ |
164 | 8 | int stackID() { return fStackID; } |
165 | | |
166 | | /** Clones values from this stack onto the top of the active stack. */ |
167 | | void pushClone(int slots); |
168 | | |
169 | | /** Clones values from a fixed range of this stack onto the top of the active stack. */ |
170 | | void pushClone(SlotRange range, int offsetFromStackTop); |
171 | | |
172 | | /** Clones values from a dynamic range of this stack onto the top of the active stack. */ |
173 | | void pushCloneIndirect(SlotRange range, int dynamicStackID, int offsetFromStackTop); |
174 | | |
175 | | private: |
176 | | Generator* fGenerator; |
177 | | int fStackID = 0; |
178 | | int fParentStackID = 0; |
179 | | }; |
180 | | |
181 | | class Generator { |
182 | | public: |
183 | | Generator(const SkSL::Program& program, DebugTracePriv* debugTrace, bool writeTraceOps) |
184 | | : fProgram(program) |
185 | | , fContext(fProgram.fContext->fTypes, *fProgram.fContext->fErrors) |
186 | | , fDebugTrace(debugTrace) |
187 | | , fWriteTraceOps(writeTraceOps) |
188 | | , fProgramSlots(debugTrace ? &debugTrace->fSlotInfo : nullptr) |
189 | | , fUniformSlots(debugTrace ? &debugTrace->fUniformInfo : nullptr) |
190 | 51 | , fImmutableSlots(nullptr) { |
191 | 51 | fContext.fConfig = fProgram.fConfig.get(); |
192 | 51 | fContext.fModule = fProgram.fContext->fModule; |
193 | 51 | } |
194 | | |
195 | 51 | ~Generator() { |
196 | | // ~AutoStack calls into the Generator, so we need to make sure the trace mask is reset |
197 | | // before the Generator is destroyed. |
198 | 51 | fTraceMask.reset(); |
199 | 51 | } |
200 | | |
201 | | /** Converts the SkSL main() function into a set of Instructions. */ |
202 | | bool writeProgram(const FunctionDefinition& function); |
203 | | |
204 | | /** Returns the generated program. */ |
205 | | std::unique_ptr<RP::Program> finish(); |
206 | | |
207 | | /** |
208 | | * Converts an SkSL function into a set of Instructions. Returns nullopt if the function |
209 | | * contained unsupported statements or expressions. |
210 | | */ |
211 | | std::optional<SlotRange> writeFunction(const IRNode& callSite, |
212 | | const FunctionDefinition& function, |
213 | | SkSpan<std::unique_ptr<Expression> const> arguments); |
214 | | |
215 | | /** |
216 | | * Returns the slot index of this function inside the FunctionDebugInfo array in DebugTracePriv. |
217 | | * The FunctionDebugInfo slot will be created if it doesn't already exist. |
218 | | */ |
219 | | int getFunctionDebugInfo(const FunctionDeclaration& decl); |
220 | | |
221 | | /** Returns true for variables with slots in fProgramSlots; immutables or uniforms are false. */ |
222 | 4 | bool hasVariableSlots(const Variable& v) { |
223 | 4 | return !IsUniform(v) && !fImmutableVariables.contains(&v); |
224 | 4 | } |
225 | | |
226 | | /** Looks up the slots associated with an SkSL variable; creates the slots if necessary. */ |
227 | 1.20k | SlotRange getVariableSlots(const Variable& v) { |
228 | 1.20k | SkASSERT(this->hasVariableSlots(v)); |
229 | 1.20k | return fProgramSlots.getVariableSlots(v); |
230 | 1.20k | } |
231 | | |
232 | | /** |
233 | | * Looks up the slots associated with an immutable variable; creates the slots if necessary. |
234 | | */ |
235 | 21 | SlotRange getImmutableSlots(const Variable& v) { |
236 | 21 | SkASSERT(!IsUniform(v)); |
237 | 21 | SkASSERT(fImmutableVariables.contains(&v)); |
238 | 21 | return fImmutableSlots.getVariableSlots(v); |
239 | 21 | } |
240 | | |
241 | | /** Looks up the slots associated with an SkSL uniform; creates the slots if necessary. */ |
242 | 405 | SlotRange getUniformSlots(const Variable& v) { |
243 | 405 | SkASSERT(IsUniform(v)); |
244 | 405 | SkASSERT(!fImmutableVariables.contains(&v)); |
245 | 405 | return fUniformSlots.getVariableSlots(v); |
246 | 405 | } |
247 | | |
248 | | /** |
249 | | * Looks up the slots associated with an SkSL function's return value; creates the range if |
250 | | * necessary. Note that recursion is never supported, so we don't need to maintain return values |
251 | | * in a stack; we can just statically allocate one slot per function call-site. |
252 | | */ |
253 | 55 | SlotRange getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f) { |
254 | 55 | return fProgramSlots.getFunctionSlots(callSite, f); |
255 | 55 | } |
256 | | |
257 | | /** |
258 | | * Creates an additional stack for the program to push values onto. The stack will not become |
259 | | * actively in-use until `setCurrentStack` is called. |
260 | | */ |
261 | | int createStack(); |
262 | | |
263 | | /** Frees a stack generated by `createStack`. The freed stack must be completely empty. */ |
264 | | void recycleStack(int stackID); |
265 | | |
266 | | /** Redirects builder ops to point to a different stack (created by `createStack`). */ |
267 | | void setCurrentStack(int stackID); |
268 | | |
269 | | /** Reports the currently active stack. */ |
270 | 64 | int currentStack() { |
271 | 64 | return fCurrentStack; |
272 | 64 | } |
273 | | |
274 | | /** |
275 | | * Returns an LValue for the passed-in expression; if the expression isn't supported as an |
276 | | * LValue, returns nullptr. |
277 | | */ |
278 | | std::unique_ptr<LValue> makeLValue(const Expression& e, bool allowScratch = false); |
279 | | |
280 | | /** Copies the top-of-stack value into this lvalue, without discarding it from the stack. */ |
281 | | [[nodiscard]] bool store(LValue& lvalue); |
282 | | |
283 | | /** Pushes the lvalue onto the top-of-stack. */ |
284 | | [[nodiscard]] bool push(LValue& lvalue); |
285 | | |
286 | | /** The Builder stitches our instructions together into Raster Pipeline code. */ |
287 | 191 | Builder* builder() { return &fBuilder; } |
288 | | |
289 | | /** Appends a statement to the program. */ |
290 | | [[nodiscard]] bool writeStatement(const Statement& s); |
291 | | [[nodiscard]] bool writeBlock(const Block& b); |
292 | | [[nodiscard]] bool writeBreakStatement(const BreakStatement& b); |
293 | | [[nodiscard]] bool writeContinueStatement(const ContinueStatement& b); |
294 | | [[nodiscard]] bool writeDoStatement(const DoStatement& d); |
295 | | [[nodiscard]] bool writeExpressionStatement(const ExpressionStatement& e); |
296 | | [[nodiscard]] bool writeMasklessForStatement(const ForStatement& f); |
297 | | [[nodiscard]] bool writeForStatement(const ForStatement& f); |
298 | | [[nodiscard]] bool writeGlobals(); |
299 | | [[nodiscard]] bool writeIfStatement(const IfStatement& i); |
300 | | [[nodiscard]] bool writeDynamicallyUniformIfStatement(const IfStatement& i); |
301 | | [[nodiscard]] bool writeReturnStatement(const ReturnStatement& r); |
302 | | [[nodiscard]] bool writeSwitchStatement(const SwitchStatement& s); |
303 | | [[nodiscard]] bool writeVarDeclaration(const VarDeclaration& v); |
304 | | [[nodiscard]] bool writeImmutableVarDeclaration(const VarDeclaration& d); |
305 | | |
306 | | /** Pushes an expression to the value stack. */ |
307 | | [[nodiscard]] bool pushBinaryExpression(const BinaryExpression& e); |
308 | | [[nodiscard]] bool pushBinaryExpression(const Expression& left, |
309 | | Operator op, |
310 | | const Expression& right); |
311 | | [[nodiscard]] bool pushChildCall(const ChildCall& c); |
312 | | [[nodiscard]] bool pushConstructorCast(const AnyConstructor& c); |
313 | | [[nodiscard]] bool pushConstructorCompound(const AnyConstructor& c); |
314 | | [[nodiscard]] bool pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c); |
315 | | [[nodiscard]] bool pushConstructorMatrixResize(const ConstructorMatrixResize& c); |
316 | | [[nodiscard]] bool pushConstructorSplat(const ConstructorSplat& c); |
317 | | [[nodiscard]] bool pushExpression(const Expression& e, bool usesResult = true); |
318 | | [[nodiscard]] bool pushFieldAccess(const FieldAccess& f); |
319 | | [[nodiscard]] bool pushFunctionCall(const FunctionCall& c); |
320 | | [[nodiscard]] bool pushIndexExpression(const IndexExpression& i); |
321 | | [[nodiscard]] bool pushIntrinsic(const FunctionCall& c); |
322 | | [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0); |
323 | | [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, |
324 | | const Expression& arg0, |
325 | | const Expression& arg1); |
326 | | [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, |
327 | | const Expression& arg0, |
328 | | const Expression& arg1, |
329 | | const Expression& arg2); |
330 | | [[nodiscard]] bool pushLiteral(const Literal& l); |
331 | | [[nodiscard]] bool pushPostfixExpression(const PostfixExpression& p, bool usesResult); |
332 | | [[nodiscard]] bool pushPrefixExpression(const PrefixExpression& p); |
333 | | [[nodiscard]] bool pushPrefixExpression(Operator op, const Expression& expr); |
334 | | [[nodiscard]] bool pushSwizzle(const Swizzle& s); |
335 | | [[nodiscard]] bool pushTernaryExpression(const TernaryExpression& t); |
336 | | [[nodiscard]] bool pushTernaryExpression(const Expression& test, |
337 | | const Expression& ifTrue, |
338 | | const Expression& ifFalse); |
339 | | [[nodiscard]] bool pushDynamicallyUniformTernaryExpression(const Expression& test, |
340 | | const Expression& ifTrue, |
341 | | const Expression& ifFalse); |
342 | | [[nodiscard]] bool pushVariableReference(const VariableReference& v); |
343 | | |
344 | | /** Support methods for immutable data, which trade more slots for smaller code size. */ |
345 | | using ImmutableBits = int32_t; |
346 | | |
347 | | [[nodiscard]] bool pushImmutableData(const Expression& e); |
348 | | [[nodiscard]] std::optional<SlotRange> findPreexistingImmutableData( |
349 | | const TArray<ImmutableBits>& immutableValues); |
350 | | [[nodiscard]] std::optional<ImmutableBits> getImmutableBitsForSlot(const Expression& expr, |
351 | | size_t slot); |
352 | | [[nodiscard]] bool getImmutableValueForExpression(const Expression& expr, |
353 | | TArray<ImmutableBits>* immutableValues); |
354 | | void storeImmutableValueToSlots(const TArray<ImmutableBits>& immutableValues, SlotRange slots); |
355 | | |
356 | | /** Pops an expression from the value stack and copies it into slots. */ |
357 | 12 | void popToSlotRange(SlotRange r) { |
358 | 12 | fBuilder.pop_slots(r); |
359 | 12 | if (this->shouldWriteTraceOps()) { |
360 | 0 | fBuilder.trace_var(fTraceMask->stackID(), r); |
361 | 0 | } |
362 | 12 | } |
363 | 216 | void popToSlotRangeUnmasked(SlotRange r) { |
364 | 216 | fBuilder.pop_slots_unmasked(r); |
365 | 216 | if (this->shouldWriteTraceOps()) { |
366 | 0 | fBuilder.trace_var(fTraceMask->stackID(), r); |
367 | 0 | } |
368 | 216 | } |
369 | | |
370 | | /** Pops an expression from the value stack and discards it. */ |
371 | 232 | void discardExpression(int slots) { fBuilder.discard_stack(slots); } |
372 | | |
373 | | /** Zeroes out a range of slots. */ |
374 | 8 | void zeroSlotRangeUnmasked(SlotRange r) { |
375 | 8 | fBuilder.zero_slots_unmasked(r); |
376 | 8 | if (this->shouldWriteTraceOps()) { |
377 | 0 | fBuilder.trace_var(fTraceMask->stackID(), r); |
378 | 0 | } |
379 | 8 | } |
380 | | |
381 | | /** |
382 | | * Emits a trace_line opcode. writeStatement does this, and statements that alter control flow |
383 | | * may need to explicitly add additional traces. |
384 | | */ |
385 | | void emitTraceLine(Position pos); |
386 | | |
387 | | /** |
388 | | * Emits a trace_scope opcode, which alters the SkSL variable-scope depth. |
389 | | * Unlike the other trace ops, trace_scope takes a dedicated mask instead of the trace-scope |
390 | | * mask. Call `pushTraceScopeMask` to synthesize this mask; discard it when you're done. |
391 | | */ |
392 | | void pushTraceScopeMask(); |
393 | | void discardTraceScopeMask(); |
394 | | void emitTraceScope(int delta); |
395 | | |
396 | | /** Prepares our position-to-line-offset conversion table (stored in `fLineOffsets`). */ |
397 | | void calculateLineOffsets(); |
398 | | |
399 | 1.63k | bool shouldWriteTraceOps() { return fDebugTrace && fWriteTraceOps; } |
400 | 0 | int traceMaskStackID() { return fTraceMask->stackID(); } |
401 | | |
402 | | /** Expression utilities. */ |
403 | | struct TypedOps { |
404 | | BuilderOp fFloatOp; |
405 | | BuilderOp fSignedOp; |
406 | | BuilderOp fUnsignedOp; |
407 | | BuilderOp fBooleanOp; |
408 | | }; |
409 | | |
410 | | static BuilderOp GetTypedOp(const SkSL::Type& type, const TypedOps& ops); |
411 | | |
412 | | [[nodiscard]] bool unaryOp(const SkSL::Type& type, const TypedOps& ops); |
413 | | [[nodiscard]] bool binaryOp(const SkSL::Type& type, const TypedOps& ops); |
414 | | [[nodiscard]] bool ternaryOp(const SkSL::Type& type, const TypedOps& ops); |
415 | | [[nodiscard]] bool pushIntrinsic(const TypedOps& ops, const Expression& arg0); |
416 | | [[nodiscard]] bool pushIntrinsic(const TypedOps& ops, |
417 | | const Expression& arg0, |
418 | | const Expression& arg1); |
419 | | [[nodiscard]] bool pushIntrinsic(BuilderOp builderOp, const Expression& arg0); |
420 | | [[nodiscard]] bool pushIntrinsic(BuilderOp builderOp, |
421 | | const Expression& arg0, |
422 | | const Expression& arg1); |
423 | | [[nodiscard]] bool pushAbsFloatIntrinsic(int slots); |
424 | | [[nodiscard]] bool pushLengthIntrinsic(int slotCount); |
425 | | [[nodiscard]] bool pushVectorizedExpression(const Expression& expr, const Type& vectorType); |
426 | | [[nodiscard]] bool pushVariableReferencePartial(const VariableReference& v, SlotRange subset); |
427 | | [[nodiscard]] bool pushLValueOrExpression(LValue* lvalue, const Expression& expr); |
428 | | [[nodiscard]] bool pushMatrixMultiply(LValue* lvalue, |
429 | | const Expression& left, |
430 | | const Expression& right, |
431 | | int leftColumns, int leftRows, |
432 | | int rightColumns, int rightRows); |
433 | | [[nodiscard]] bool pushStructuredComparison(LValue* left, |
434 | | Operator op, |
435 | | LValue* right, |
436 | | const Type& type); |
437 | | |
438 | | void foldWithMultiOp(BuilderOp op, int elements); |
439 | | void foldComparisonOp(Operator op, int elements); |
440 | | |
441 | | BuilderOp getTypedOp(const SkSL::Type& type, const TypedOps& ops) const; |
442 | | |
443 | 240 | Analysis::ReturnComplexity returnComplexity(const FunctionDefinition* func) { |
444 | 240 | Analysis::ReturnComplexity* complexity = fReturnComplexityMap.find(func); |
445 | 240 | if (!complexity) { |
446 | 55 | complexity = fReturnComplexityMap.set(fCurrentFunction, |
447 | 55 | Analysis::GetReturnComplexity(*func)); |
448 | 55 | } |
449 | 240 | return *complexity; |
450 | 240 | } |
451 | | |
452 | 122 | bool needsReturnMask(const FunctionDefinition* func) { |
453 | 122 | return this->returnComplexity(func) >= Analysis::ReturnComplexity::kEarlyReturns; |
454 | 122 | } |
455 | | |
456 | 118 | bool needsFunctionResultSlots(const FunctionDefinition* func) { |
457 | 118 | return this->shouldWriteTraceOps() || (this->returnComplexity(func) > |
458 | 118 | Analysis::ReturnComplexity::kSingleSafeReturn); |
459 | 118 | } |
460 | | |
461 | 1.39k | static bool IsUniform(const Variable& var) { |
462 | 1.39k | return var.modifierFlags().isUniform(); |
463 | 1.39k | } |
464 | | |
465 | 4 | static bool IsOutParameter(const Variable& var) { |
466 | 4 | return (var.modifierFlags() & (ModifierFlag::kIn | ModifierFlag::kOut)) == |
467 | 4 | ModifierFlag::kOut; |
468 | 4 | } |
469 | | |
470 | 4 | static bool IsInoutParameter(const Variable& var) { |
471 | 4 | return (var.modifierFlags() & (ModifierFlag::kIn | ModifierFlag::kOut)) == |
472 | 4 | (ModifierFlag::kIn | ModifierFlag::kOut); |
473 | 4 | } |
474 | | |
475 | | private: |
476 | | const SkSL::Program& fProgram; |
477 | | SkSL::Context fContext; |
478 | | Builder fBuilder; |
479 | | DebugTracePriv* fDebugTrace = nullptr; |
480 | | bool fWriteTraceOps = false; |
481 | | THashMap<const Variable*, int> fChildEffectMap; |
482 | | |
483 | | SlotManager fProgramSlots; |
484 | | SlotManager fUniformSlots; |
485 | | SlotManager fImmutableSlots; |
486 | | |
487 | | std::optional<AutoStack> fTraceMask; |
488 | | const FunctionDefinition* fCurrentFunction = nullptr; |
489 | | SlotRange fCurrentFunctionResult; |
490 | | AutoContinueMask* fCurrentContinueMask = nullptr; |
491 | | int fCurrentBreakTarget = -1; |
492 | | int fCurrentStack = 0; |
493 | | int fNextStackID = 0; |
494 | | TArray<int> fRecycledStacks; |
495 | | |
496 | | THashMap<const FunctionDefinition*, Analysis::ReturnComplexity> fReturnComplexityMap; |
497 | | |
498 | | THashMap<ImmutableBits, THashSet<Slot>> fImmutableSlotMap; |
499 | | THashSet<const Variable*> fImmutableVariables; |
500 | | |
501 | | // `fInsideCompoundStatement` will be nonzero if we are currently writing statements inside of a |
502 | | // compound-statement Block. (Conceptually those statements should all count as one.) |
503 | | int fInsideCompoundStatement = 0; |
504 | | |
505 | | // `fLineOffsets` contains the position of each newline in the source, plus a zero at the |
506 | | // beginning, and the total source length at the end, as sentinels. |
507 | | TArray<int> fLineOffsets; |
508 | | |
509 | | static constexpr auto kAddOps = TypedOps{BuilderOp::add_n_floats, |
510 | | BuilderOp::add_n_ints, |
511 | | BuilderOp::add_n_ints, |
512 | | BuilderOp::unsupported}; |
513 | | static constexpr auto kSubtractOps = TypedOps{BuilderOp::sub_n_floats, |
514 | | BuilderOp::sub_n_ints, |
515 | | BuilderOp::sub_n_ints, |
516 | | BuilderOp::unsupported}; |
517 | | static constexpr auto kMultiplyOps = TypedOps{BuilderOp::mul_n_floats, |
518 | | BuilderOp::mul_n_ints, |
519 | | BuilderOp::mul_n_ints, |
520 | | BuilderOp::unsupported}; |
521 | | static constexpr auto kDivideOps = TypedOps{BuilderOp::div_n_floats, |
522 | | BuilderOp::div_n_ints, |
523 | | BuilderOp::div_n_uints, |
524 | | BuilderOp::unsupported}; |
525 | | static constexpr auto kLessThanOps = TypedOps{BuilderOp::cmplt_n_floats, |
526 | | BuilderOp::cmplt_n_ints, |
527 | | BuilderOp::cmplt_n_uints, |
528 | | BuilderOp::unsupported}; |
529 | | static constexpr auto kLessThanEqualOps = TypedOps{BuilderOp::cmple_n_floats, |
530 | | BuilderOp::cmple_n_ints, |
531 | | BuilderOp::cmple_n_uints, |
532 | | BuilderOp::unsupported}; |
533 | | static constexpr auto kEqualOps = TypedOps{BuilderOp::cmpeq_n_floats, |
534 | | BuilderOp::cmpeq_n_ints, |
535 | | BuilderOp::cmpeq_n_ints, |
536 | | BuilderOp::cmpeq_n_ints}; |
537 | | static constexpr auto kNotEqualOps = TypedOps{BuilderOp::cmpne_n_floats, |
538 | | BuilderOp::cmpne_n_ints, |
539 | | BuilderOp::cmpne_n_ints, |
540 | | BuilderOp::cmpne_n_ints}; |
541 | | static constexpr auto kModOps = TypedOps{BuilderOp::mod_n_floats, |
542 | | BuilderOp::unsupported, |
543 | | BuilderOp::unsupported, |
544 | | BuilderOp::unsupported}; |
545 | | static constexpr auto kMinOps = TypedOps{BuilderOp::min_n_floats, |
546 | | BuilderOp::min_n_ints, |
547 | | BuilderOp::min_n_uints, |
548 | | BuilderOp::min_n_uints}; |
549 | | static constexpr auto kMaxOps = TypedOps{BuilderOp::max_n_floats, |
550 | | BuilderOp::max_n_ints, |
551 | | BuilderOp::max_n_uints, |
552 | | BuilderOp::max_n_uints}; |
553 | | static constexpr auto kMixOps = TypedOps{BuilderOp::mix_n_floats, |
554 | | BuilderOp::unsupported, |
555 | | BuilderOp::unsupported, |
556 | | BuilderOp::unsupported}; |
557 | | static constexpr auto kInverseSqrtOps = TypedOps{BuilderOp::invsqrt_float, |
558 | | BuilderOp::unsupported, |
559 | | BuilderOp::unsupported, |
560 | | BuilderOp::unsupported}; |
561 | | friend class AutoContinueMask; |
562 | | }; |
563 | | |
564 | | AutoStack::AutoStack(Generator* g) |
565 | | : fGenerator(g) |
566 | 24 | , fStackID(g->createStack()) {} |
567 | | |
568 | 24 | AutoStack::~AutoStack() { |
569 | 24 | fGenerator->recycleStack(fStackID); |
570 | 24 | } |
571 | | |
572 | 64 | void AutoStack::enter() { |
573 | 64 | fParentStackID = fGenerator->currentStack(); |
574 | 64 | fGenerator->setCurrentStack(fStackID); |
575 | 64 | } |
576 | | |
577 | 64 | void AutoStack::exit() { |
578 | 64 | SkASSERT(fGenerator->currentStack() == fStackID); |
579 | 64 | fGenerator->setCurrentStack(fParentStackID); |
580 | 64 | } |
581 | | |
582 | 0 | void AutoStack::pushClone(int slots) { |
583 | 0 | this->pushClone(SlotRange{0, slots}, /*offsetFromStackTop=*/slots); |
584 | 0 | } |
585 | | |
586 | 0 | void AutoStack::pushClone(SlotRange range, int offsetFromStackTop) { |
587 | 0 | fGenerator->builder()->push_clone_from_stack(range, fStackID, offsetFromStackTop); |
588 | 0 | } |
589 | | |
590 | 0 | void AutoStack::pushCloneIndirect(SlotRange range, int dynamicStackID, int offsetFromStackTop) { |
591 | 0 | fGenerator->builder()->push_clone_indirect_from_stack( |
592 | 0 | range, dynamicStackID, /*otherStackID=*/fStackID, offsetFromStackTop); |
593 | 0 | } |
594 | | |
595 | | class AutoContinueMask { |
596 | | public: |
597 | 13 | AutoContinueMask(Generator* gen) : fGenerator(gen) {} |
598 | | |
599 | 13 | ~AutoContinueMask() { |
600 | 13 | if (fPreviousContinueMask) { |
601 | 0 | fGenerator->fCurrentContinueMask = fPreviousContinueMask; |
602 | 0 | } |
603 | 13 | } |
604 | | |
605 | 0 | void enable() { |
606 | 0 | SkASSERT(!fContinueMaskStack.has_value()); |
607 | |
|
608 | 0 | fContinueMaskStack.emplace(fGenerator); |
609 | 0 | fPreviousContinueMask = fGenerator->fCurrentContinueMask; |
610 | 0 | fGenerator->fCurrentContinueMask = this; |
611 | 0 | } Unexecuted instantiation: SkSL::RP::AutoContinueMask::enable() Unexecuted instantiation: SkSL::RP::AutoContinueMask::enable() |
612 | | |
613 | 0 | void enter() { |
614 | 0 | SkASSERT(fContinueMaskStack.has_value()); |
615 | 0 | fContinueMaskStack->enter(); |
616 | 0 | } |
617 | | |
618 | 0 | void exit() { |
619 | 0 | SkASSERT(fContinueMaskStack.has_value()); |
620 | 0 | fContinueMaskStack->exit(); |
621 | 0 | } |
622 | | |
623 | 13 | void enterLoopBody() { |
624 | 13 | if (fContinueMaskStack.has_value()) { |
625 | 0 | fContinueMaskStack->enter(); |
626 | 0 | fGenerator->builder()->push_constant_i(0); |
627 | 0 | fContinueMaskStack->exit(); |
628 | 0 | } |
629 | 13 | } |
630 | | |
631 | 13 | void exitLoopBody() { |
632 | 13 | if (fContinueMaskStack.has_value()) { |
633 | 0 | fContinueMaskStack->enter(); |
634 | 0 | fGenerator->builder()->pop_and_reenable_loop_mask(); |
635 | 0 | fContinueMaskStack->exit(); |
636 | 0 | } |
637 | 13 | } |
638 | | |
639 | 0 | int stackID() { |
640 | 0 | SkASSERT(fContinueMaskStack.has_value()); |
641 | 0 | return fContinueMaskStack->stackID(); |
642 | 0 | } Unexecuted instantiation: SkSL::RP::AutoContinueMask::stackID() Unexecuted instantiation: SkSL::RP::AutoContinueMask::stackID() |
643 | | |
644 | | private: |
645 | | std::optional<AutoStack> fContinueMaskStack; |
646 | | Generator* fGenerator = nullptr; |
647 | | AutoContinueMask* fPreviousContinueMask = nullptr; |
648 | | }; |
649 | | |
650 | | class AutoLoopTarget { |
651 | | public: |
652 | 13 | AutoLoopTarget(Generator* gen, int* targetPtr) : fGenerator(gen), fLoopTargetPtr(targetPtr) { |
653 | 13 | fLabelID = fGenerator->builder()->nextLabelID(); |
654 | 13 | fPreviousLoopTarget = *fLoopTargetPtr; |
655 | 13 | *fLoopTargetPtr = fLabelID; |
656 | 13 | } |
657 | | |
658 | 13 | ~AutoLoopTarget() { |
659 | 13 | *fLoopTargetPtr = fPreviousLoopTarget; |
660 | 13 | } |
661 | | |
662 | 13 | int labelID() { |
663 | 13 | return fLabelID; |
664 | 13 | } |
665 | | |
666 | | private: |
667 | | Generator* fGenerator = nullptr; |
668 | | int* fLoopTargetPtr = nullptr; |
669 | | int fPreviousLoopTarget; |
670 | | int fLabelID; |
671 | | }; |
672 | | |
673 | | class LValue { |
674 | | public: |
675 | 179 | virtual ~LValue() = default; |
676 | | |
677 | | /** Returns true if this lvalue is actually writable--temporaries and uniforms are not. */ |
678 | | virtual bool isWritable() const = 0; |
679 | | |
680 | | /** |
681 | | * Returns the fixed slot range of the lvalue, after it is winnowed down to the selected |
682 | | * field/index. The range is calculated assuming every dynamic index will evaluate to zero. |
683 | | */ |
684 | | virtual SlotRange fixedSlotRange(Generator* gen) = 0; |
685 | | |
686 | | /** |
687 | | * Returns a stack which holds a single integer, representing the dynamic offset of the lvalue. |
688 | | * This value does not incorporate the fixed offset. If null is returned, the lvalue doesn't |
689 | | * have a dynamic offset. `evaluateDynamicIndices` must be called before this is used. |
690 | | */ |
691 | | virtual AutoStack* dynamicSlotRange() = 0; |
692 | | |
693 | | /** Returns the swizzle components of the lvalue, or an empty span for non-swizzle LValues. */ |
694 | 8 | virtual SkSpan<const int8_t> swizzle() { return {}; } |
695 | | |
696 | | /** Pushes values directly onto the stack. */ |
697 | | [[nodiscard]] virtual bool push(Generator* gen, |
698 | | SlotRange fixedOffset, |
699 | | AutoStack* dynamicOffset, |
700 | | SkSpan<const int8_t> swizzle) = 0; |
701 | | |
702 | | /** Stores topmost values from the stack directly into the lvalue. */ |
703 | | [[nodiscard]] virtual bool store(Generator* gen, |
704 | | SlotRange fixedOffset, |
705 | | AutoStack* dynamicOffset, |
706 | | SkSpan<const int8_t> swizzle) = 0; |
707 | | /** |
708 | | * Some lvalues refer to a temporary expression; these temps can be held in the |
709 | | * scratch-expression field to ensure that they exist for the lifetime of the lvalue. |
710 | | */ |
711 | | std::unique_ptr<Expression> fScratchExpression; |
712 | | }; |
713 | | |
714 | | class ScratchLValue final : public LValue { |
715 | | public: |
716 | | explicit ScratchLValue(const Expression& e) |
717 | | : fExpression(&e) |
718 | 0 | , fNumSlots(e.type().slotCount()) {} |
719 | | |
720 | 0 | ~ScratchLValue() override { |
721 | 0 | if (fGenerator && fDedicatedStack.has_value()) { |
722 | | // Jettison the scratch expression. |
723 | 0 | fDedicatedStack->enter(); |
724 | 0 | fGenerator->discardExpression(fNumSlots); |
725 | 0 | fDedicatedStack->exit(); |
726 | 0 | } |
727 | 0 | } |
728 | | |
729 | 0 | bool isWritable() const override { |
730 | 0 | return false; |
731 | 0 | } |
732 | | |
733 | 0 | SlotRange fixedSlotRange(Generator* gen) override { |
734 | 0 | return SlotRange{0, fNumSlots}; |
735 | 0 | } |
736 | | |
737 | 0 | AutoStack* dynamicSlotRange() override { |
738 | 0 | return nullptr; |
739 | 0 | } |
740 | | |
741 | | [[nodiscard]] bool push(Generator* gen, |
742 | | SlotRange fixedOffset, |
743 | | AutoStack* dynamicOffset, |
744 | 0 | SkSpan<const int8_t> swizzle) override { |
745 | 0 | if (!fDedicatedStack.has_value()) { |
746 | | // Push the scratch expression onto a dedicated stack. |
747 | 0 | fGenerator = gen; |
748 | 0 | fDedicatedStack.emplace(fGenerator); |
749 | 0 | fDedicatedStack->enter(); |
750 | 0 | if (!fGenerator->pushExpression(*fExpression)) { |
751 | 0 | return unsupported(); |
752 | 0 | } |
753 | 0 | fDedicatedStack->exit(); |
754 | 0 | } |
755 | | |
756 | 0 | if (dynamicOffset) { |
757 | 0 | fDedicatedStack->pushCloneIndirect(fixedOffset, dynamicOffset->stackID(), fNumSlots); |
758 | 0 | } else { |
759 | 0 | fDedicatedStack->pushClone(fixedOffset, fNumSlots); |
760 | 0 | } |
761 | 0 | if (!swizzle.empty()) { |
762 | 0 | gen->builder()->swizzle(fixedOffset.count, swizzle); |
763 | 0 | } |
764 | 0 | return true; |
765 | 0 | } |
766 | | |
767 | 0 | [[nodiscard]] bool store(Generator*, SlotRange, AutoStack*, SkSpan<const int8_t>) override { |
768 | 0 | SkDEBUGFAIL("scratch lvalues cannot be stored into"); |
769 | 0 | return unsupported(); |
770 | 0 | } Unexecuted instantiation: SkSL::RP::ScratchLValue::store(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) Unexecuted instantiation: SkSL::RP::ScratchLValue::store(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) |
771 | | |
772 | | private: |
773 | | Generator* fGenerator = nullptr; |
774 | | const Expression* fExpression = nullptr; |
775 | | std::optional<AutoStack> fDedicatedStack; |
776 | | int fNumSlots = 0; |
777 | | }; |
778 | | |
779 | | class VariableLValue final : public LValue { |
780 | | public: |
781 | 138 | explicit VariableLValue(const Variable* v) : fVariable(v) {} |
782 | | |
783 | 0 | bool isWritable() const override { |
784 | 0 | return !Generator::IsUniform(*fVariable); |
785 | 0 | } |
786 | | |
787 | 178 | SlotRange fixedSlotRange(Generator* gen) override { |
788 | 178 | return Generator::IsUniform(*fVariable) ? gen->getUniformSlots(*fVariable) |
789 | 178 | : gen->getVariableSlots(*fVariable); |
790 | 178 | } |
791 | | |
792 | 170 | AutoStack* dynamicSlotRange() override { |
793 | 170 | return nullptr; |
794 | 170 | } |
795 | | |
796 | | [[nodiscard]] bool push(Generator* gen, |
797 | | SlotRange fixedOffset, |
798 | | AutoStack* dynamicOffset, |
799 | 40 | SkSpan<const int8_t> swizzle) override { |
800 | 40 | if (Generator::IsUniform(*fVariable)) { |
801 | 4 | if (dynamicOffset) { |
802 | 4 | gen->builder()->push_uniform_indirect(fixedOffset, dynamicOffset->stackID(), |
803 | 4 | this->fixedSlotRange(gen)); |
804 | 4 | } else { |
805 | 0 | gen->builder()->push_uniform(fixedOffset); |
806 | 0 | } |
807 | 36 | } else { |
808 | 36 | if (dynamicOffset) { |
809 | 4 | gen->builder()->push_slots_indirect(fixedOffset, dynamicOffset->stackID(), |
810 | 4 | this->fixedSlotRange(gen)); |
811 | 32 | } else { |
812 | 32 | gen->builder()->push_slots(fixedOffset); |
813 | 32 | } |
814 | 36 | } |
815 | 40 | if (!swizzle.empty()) { |
816 | 0 | gen->builder()->swizzle(fixedOffset.count, swizzle); |
817 | 0 | } |
818 | 40 | return true; |
819 | 40 | } |
820 | | |
821 | | [[nodiscard]] bool store(Generator* gen, |
822 | | SlotRange fixedOffset, |
823 | | AutoStack* dynamicOffset, |
824 | 130 | SkSpan<const int8_t> swizzle) override { |
825 | 130 | SkASSERT(!Generator::IsUniform(*fVariable)); |
826 | | |
827 | 130 | if (swizzle.empty()) { |
828 | 130 | if (dynamicOffset) { |
829 | 0 | gen->builder()->copy_stack_to_slots_indirect(fixedOffset, dynamicOffset->stackID(), |
830 | 0 | this->fixedSlotRange(gen)); |
831 | 130 | } else { |
832 | 130 | gen->builder()->copy_stack_to_slots(fixedOffset); |
833 | 130 | } |
834 | 130 | } else { |
835 | 0 | if (dynamicOffset) { |
836 | 0 | gen->builder()->swizzle_copy_stack_to_slots_indirect(fixedOffset, |
837 | 0 | dynamicOffset->stackID(), |
838 | 0 | this->fixedSlotRange(gen), |
839 | 0 | swizzle, |
840 | 0 | swizzle.size()); |
841 | 0 | } else { |
842 | 0 | gen->builder()->swizzle_copy_stack_to_slots(fixedOffset, swizzle, swizzle.size()); |
843 | 0 | } |
844 | 0 | } |
845 | 130 | if (gen->shouldWriteTraceOps()) { |
846 | 0 | if (dynamicOffset) { |
847 | 0 | gen->builder()->trace_var_indirect(gen->traceMaskStackID(), |
848 | 0 | fixedOffset, |
849 | 0 | dynamicOffset->stackID(), |
850 | 0 | this->fixedSlotRange(gen)); |
851 | 0 | } else { |
852 | 0 | gen->builder()->trace_var(gen->traceMaskStackID(), fixedOffset); |
853 | 0 | } |
854 | 0 | } |
855 | 130 | return true; |
856 | 130 | } |
857 | | |
858 | | private: |
859 | | const Variable* fVariable; |
860 | | }; |
861 | | |
862 | | class ImmutableLValue final : public LValue { |
863 | | public: |
864 | 0 | explicit ImmutableLValue(const Variable* v) : fVariable(v) {} |
865 | | |
866 | 0 | bool isWritable() const override { |
867 | 0 | return false; |
868 | 0 | } |
869 | | |
870 | 0 | SlotRange fixedSlotRange(Generator* gen) override { |
871 | 0 | return gen->getImmutableSlots(*fVariable); |
872 | 0 | } |
873 | | |
874 | 0 | AutoStack* dynamicSlotRange() override { |
875 | 0 | return nullptr; |
876 | 0 | } |
877 | | |
878 | | [[nodiscard]] bool push(Generator* gen, |
879 | | SlotRange fixedOffset, |
880 | | AutoStack* dynamicOffset, |
881 | 0 | SkSpan<const int8_t> swizzle) override { |
882 | 0 | if (dynamicOffset) { |
883 | 0 | gen->builder()->push_immutable_indirect(fixedOffset, dynamicOffset->stackID(), |
884 | 0 | this->fixedSlotRange(gen)); |
885 | 0 | } else { |
886 | 0 | gen->builder()->push_immutable(fixedOffset); |
887 | 0 | } |
888 | 0 | if (!swizzle.empty()) { |
889 | 0 | gen->builder()->swizzle(fixedOffset.count, swizzle); |
890 | 0 | } |
891 | 0 | return true; |
892 | 0 | } |
893 | | |
894 | | [[nodiscard]] bool store(Generator* gen, |
895 | | SlotRange fixedOffset, |
896 | | AutoStack* dynamicOffset, |
897 | 0 | SkSpan<const int8_t> swizzle) override { |
898 | 0 | SkDEBUGFAIL("immutable values cannot be stored into"); |
899 | 0 | return unsupported(); |
900 | 0 | } Unexecuted instantiation: SkSL::RP::ImmutableLValue::store(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) Unexecuted instantiation: SkSL::RP::ImmutableLValue::store(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) |
901 | | |
902 | | private: |
903 | | const Variable* fVariable; |
904 | | }; |
905 | | |
906 | | class SwizzleLValue final : public LValue { |
907 | | public: |
908 | | explicit SwizzleLValue(std::unique_ptr<LValue> p, const ComponentArray& c) |
909 | | : fParent(std::move(p)) |
910 | 0 | , fComponents(c) { |
911 | 0 | SkASSERT(!fComponents.empty() && fComponents.size() <= 4); |
912 | 0 | } Unexecuted instantiation: SkSL::RP::SwizzleLValue::SwizzleLValue(std::__1::unique_ptr<SkSL::RP::LValue, std::__1::default_delete<SkSL::RP::LValue> >, skia_private::FixedArray<4, signed char> const&) Unexecuted instantiation: SkSL::RP::SwizzleLValue::SwizzleLValue(std::__1::unique_ptr<SkSL::RP::LValue, std::__1::default_delete<SkSL::RP::LValue> >, skia_private::FixedArray<4, signed char> const&) |
913 | | |
914 | 0 | bool isWritable() const override { |
915 | 0 | return fParent->isWritable(); |
916 | 0 | } |
917 | | |
918 | 0 | SlotRange fixedSlotRange(Generator* gen) override { |
919 | 0 | return fParent->fixedSlotRange(gen); |
920 | 0 | } |
921 | | |
922 | 0 | AutoStack* dynamicSlotRange() override { |
923 | 0 | return fParent->dynamicSlotRange(); |
924 | 0 | } |
925 | | |
926 | 0 | SkSpan<const int8_t> swizzle() override { |
927 | 0 | return fComponents; |
928 | 0 | } |
929 | | |
930 | | [[nodiscard]] bool push(Generator* gen, |
931 | | SlotRange fixedOffset, |
932 | | AutoStack* dynamicOffset, |
933 | 0 | SkSpan<const int8_t> swizzle) override { |
934 | 0 | if (!swizzle.empty()) { |
935 | 0 | SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end"); |
936 | 0 | return unsupported(); |
937 | 0 | } |
938 | 0 | return fParent->push(gen, fixedOffset, dynamicOffset, fComponents); |
939 | 0 | } Unexecuted instantiation: SkSL::RP::SwizzleLValue::push(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) Unexecuted instantiation: SkSL::RP::SwizzleLValue::push(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) |
940 | | |
941 | | [[nodiscard]] bool store(Generator* gen, |
942 | | SlotRange fixedOffset, |
943 | | AutoStack* dynamicOffset, |
944 | 0 | SkSpan<const int8_t> swizzle) override { |
945 | 0 | if (!swizzle.empty()) { |
946 | 0 | SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end"); |
947 | 0 | return unsupported(); |
948 | 0 | } |
949 | 0 | return fParent->store(gen, fixedOffset, dynamicOffset, fComponents); |
950 | 0 | } Unexecuted instantiation: SkSL::RP::SwizzleLValue::store(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) Unexecuted instantiation: SkSL::RP::SwizzleLValue::store(SkSL::RP::Generator*, SkSL::RP::SlotRange, SkSL::RP::AutoStack*, SkSpan<signed char const>) |
951 | | |
952 | | private: |
953 | | std::unique_ptr<LValue> fParent; |
954 | | const ComponentArray& fComponents; |
955 | | }; |
956 | | |
957 | | class UnownedLValueSlice : public LValue { |
958 | | public: |
959 | | explicit UnownedLValueSlice(LValue* p, int initialSlot, int numSlots) |
960 | | : fParent(p) |
961 | | , fInitialSlot(initialSlot) |
962 | 33 | , fNumSlots(numSlots) { |
963 | 33 | SkASSERT(fInitialSlot >= 0); |
964 | 33 | SkASSERT(fNumSlots > 0); |
965 | 33 | } |
966 | | |
967 | 0 | bool isWritable() const override { |
968 | 0 | return fParent->isWritable(); |
969 | 0 | } |
970 | | |
971 | 43 | SlotRange fixedSlotRange(Generator* gen) override { |
972 | 43 | SlotRange range = fParent->fixedSlotRange(gen); |
973 | 43 | SlotRange adjusted = range; |
974 | 43 | adjusted.index += fInitialSlot; |
975 | 43 | adjusted.count = fNumSlots; |
976 | 43 | SkASSERT((adjusted.index + adjusted.count) <= (range.index + range.count)); |
977 | 43 | return adjusted; |
978 | 43 | } |
979 | | |
980 | 43 | AutoStack* dynamicSlotRange() override { |
981 | 43 | return fParent->dynamicSlotRange(); |
982 | 43 | } |
983 | | |
984 | | [[nodiscard]] bool push(Generator* gen, |
985 | | SlotRange fixedOffset, |
986 | | AutoStack* dynamicOffset, |
987 | 10 | SkSpan<const int8_t> swizzle) override { |
988 | 10 | return fParent->push(gen, fixedOffset, dynamicOffset, swizzle); |
989 | 10 | } |
990 | | |
991 | | [[nodiscard]] bool store(Generator* gen, |
992 | | SlotRange fixedOffset, |
993 | | AutoStack* dynamicOffset, |
994 | 33 | SkSpan<const int8_t> swizzle) override { |
995 | 33 | return fParent->store(gen, fixedOffset, dynamicOffset, swizzle); |
996 | 33 | } |
997 | | |
998 | | protected: |
999 | | LValue* fParent; |
1000 | | |
1001 | | private: |
1002 | | int fInitialSlot = 0; |
1003 | | int fNumSlots = 0; |
1004 | | }; |
1005 | | |
1006 | | class LValueSlice final : public UnownedLValueSlice { |
1007 | | public: |
1008 | | explicit LValueSlice(std::unique_ptr<LValue> p, int initialSlot, int numSlots) |
1009 | 33 | : UnownedLValueSlice(p.release(), initialSlot, numSlots) {} |
1010 | | |
1011 | 33 | ~LValueSlice() override { |
1012 | 33 | delete fParent; |
1013 | 33 | } |
1014 | | }; |
1015 | | |
1016 | | class DynamicIndexLValue final : public LValue { |
1017 | | public: |
1018 | | explicit DynamicIndexLValue(std::unique_ptr<LValue> p, const IndexExpression& i) |
1019 | | : fParent(std::move(p)) |
1020 | 8 | , fIndexExpr(&i) { |
1021 | 8 | SkASSERT(fIndexExpr->index()->type().isInteger()); |
1022 | 8 | } |
1023 | | |
1024 | 8 | ~DynamicIndexLValue() override { |
1025 | 8 | if (fDedicatedStack.has_value()) { |
1026 | 8 | SkASSERT(fGenerator); |
1027 | | |
1028 | | // Jettison the index expression. |
1029 | 8 | fDedicatedStack->enter(); |
1030 | 8 | fGenerator->discardExpression(/*slots=*/1); |
1031 | 8 | fDedicatedStack->exit(); |
1032 | 8 | } |
1033 | 8 | } |
1034 | | |
1035 | 0 | bool isWritable() const override { |
1036 | 0 | return fParent->isWritable(); |
1037 | 0 | } |
1038 | | |
1039 | 8 | [[nodiscard]] bool evaluateDynamicIndices(Generator* gen) { |
1040 | | // The index must only be computed once; the index-expression could have side effects. |
1041 | | // Once it has been computed, the offset lives on `fDedicatedStack`. |
1042 | 8 | SkASSERT(!fDedicatedStack.has_value()); |
1043 | 8 | SkASSERT(!fGenerator); |
1044 | 8 | fGenerator = gen; |
1045 | 8 | fDedicatedStack.emplace(fGenerator); |
1046 | | |
1047 | 8 | if (!fParent->swizzle().empty()) { |
1048 | 0 | SkDEBUGFAIL("an indexed-swizzle should have been handled by RewriteIndexedSwizzle"); |
1049 | 0 | return unsupported(); |
1050 | 0 | } |
1051 | | |
1052 | | // Push the index expression onto the dedicated stack. |
1053 | 8 | fDedicatedStack->enter(); |
1054 | 8 | if (!fGenerator->pushExpression(*fIndexExpr->index())) { |
1055 | 0 | return unsupported(); |
1056 | 0 | } |
1057 | | |
1058 | | // Multiply the index-expression result by the per-value slot count. |
1059 | 8 | int slotCount = fIndexExpr->type().slotCount(); |
1060 | 8 | if (slotCount != 1) { |
1061 | 4 | fGenerator->builder()->push_constant_i(fIndexExpr->type().slotCount()); |
1062 | 4 | fGenerator->builder()->binary_op(BuilderOp::mul_n_ints, 1); |
1063 | 4 | } |
1064 | | |
1065 | | // Check to see if a parent LValue already has a dynamic index. If so, we need to |
1066 | | // incorporate its value into our own. |
1067 | 8 | if (AutoStack* parentDynamicIndexStack = fParent->dynamicSlotRange()) { |
1068 | 0 | parentDynamicIndexStack->pushClone(/*slots=*/1); |
1069 | 0 | fGenerator->builder()->binary_op(BuilderOp::add_n_ints, 1); |
1070 | 0 | } |
1071 | 8 | fDedicatedStack->exit(); |
1072 | 8 | return true; |
1073 | 8 | } SkSL::RP::DynamicIndexLValue::evaluateDynamicIndices(SkSL::RP::Generator*) Line | Count | Source | 1039 | 8 | [[nodiscard]] bool evaluateDynamicIndices(Generator* gen) { | 1040 | | // The index must only be computed once; the index-expression could have side effects. | 1041 | | // Once it has been computed, the offset lives on `fDedicatedStack`. | 1042 | 8 | SkASSERT(!fDedicatedStack.has_value()); | 1043 | 8 | SkASSERT(!fGenerator); | 1044 | 8 | fGenerator = gen; | 1045 | 8 | fDedicatedStack.emplace(fGenerator); | 1046 | | | 1047 | 8 | if (!fParent->swizzle().empty()) { | 1048 | 0 | SkDEBUGFAIL("an indexed-swizzle should have been handled by RewriteIndexedSwizzle"); | 1049 | 0 | return unsupported(); | 1050 | 0 | } | 1051 | | | 1052 | | // Push the index expression onto the dedicated stack. | 1053 | 8 | fDedicatedStack->enter(); | 1054 | 8 | if (!fGenerator->pushExpression(*fIndexExpr->index())) { | 1055 | 0 | return unsupported(); | 1056 | 0 | } | 1057 | | | 1058 | | // Multiply the index-expression result by the per-value slot count. | 1059 | 8 | int slotCount = fIndexExpr->type().slotCount(); | 1060 | 8 | if (slotCount != 1) { | 1061 | 4 | fGenerator->builder()->push_constant_i(fIndexExpr->type().slotCount()); | 1062 | 4 | fGenerator->builder()->binary_op(BuilderOp::mul_n_ints, 1); | 1063 | 4 | } | 1064 | | | 1065 | | // Check to see if a parent LValue already has a dynamic index. If so, we need to | 1066 | | // incorporate its value into our own. | 1067 | 8 | if (AutoStack* parentDynamicIndexStack = fParent->dynamicSlotRange()) { | 1068 | 0 | parentDynamicIndexStack->pushClone(/*slots=*/1); | 1069 | 0 | fGenerator->builder()->binary_op(BuilderOp::add_n_ints, 1); | 1070 | 0 | } | 1071 | 8 | fDedicatedStack->exit(); | 1072 | 8 | return true; | 1073 | 8 | } |
Unexecuted instantiation: SkSL::RP::DynamicIndexLValue::evaluateDynamicIndices(SkSL::RP::Generator*) |
1074 | | |
1075 | 8 | SlotRange fixedSlotRange(Generator* gen) override { |
1076 | | // Compute the fixed slot range as if we are indexing into position zero. |
1077 | 8 | SlotRange range = fParent->fixedSlotRange(gen); |
1078 | 8 | range.count = fIndexExpr->type().slotCount(); |
1079 | 8 | return range; |
1080 | 8 | } |
1081 | | |
1082 | 8 | AutoStack* dynamicSlotRange() override { |
1083 | | // We incorporated any parent dynamic offsets when `evaluateDynamicIndices` was called. |
1084 | 8 | SkASSERT(fDedicatedStack.has_value()); |
1085 | 8 | return &*fDedicatedStack; |
1086 | 8 | } |
1087 | | |
1088 | | [[nodiscard]] bool push(Generator* gen, |
1089 | | SlotRange fixedOffset, |
1090 | | AutoStack* dynamicOffset, |
1091 | 8 | SkSpan<const int8_t> swizzle) override { |
1092 | 8 | return fParent->push(gen, fixedOffset, dynamicOffset, swizzle); |
1093 | 8 | } |
1094 | | |
1095 | | [[nodiscard]] bool store(Generator* gen, |
1096 | | SlotRange fixedOffset, |
1097 | | AutoStack* dynamicOffset, |
1098 | 0 | SkSpan<const int8_t> swizzle) override { |
1099 | 0 | return fParent->store(gen, fixedOffset, dynamicOffset, swizzle); |
1100 | 0 | } |
1101 | | |
1102 | | private: |
1103 | | Generator* fGenerator = nullptr; |
1104 | | std::unique_ptr<LValue> fParent; |
1105 | | std::optional<AutoStack> fDedicatedStack; |
1106 | | const IndexExpression* fIndexExpr = nullptr; |
1107 | | }; |
1108 | | |
1109 | | void SlotManager::addSlotDebugInfoForGroup(const std::string& varName, |
1110 | | const Type& type, |
1111 | | Position pos, |
1112 | | int* groupIndex, |
1113 | 0 | bool isFunctionReturnValue) { |
1114 | 0 | SkASSERT(fSlotDebugInfo); |
1115 | 0 | switch (type.typeKind()) { |
1116 | 0 | case Type::TypeKind::kArray: { |
1117 | 0 | int nslots = type.columns(); |
1118 | 0 | const Type& elemType = type.componentType(); |
1119 | 0 | for (int slot = 0; slot < nslots; ++slot) { |
1120 | 0 | this->addSlotDebugInfoForGroup(varName + "[" + std::to_string(slot) + "]", elemType, |
1121 | 0 | pos, groupIndex, isFunctionReturnValue); |
1122 | 0 | } |
1123 | 0 | break; |
1124 | 0 | } |
1125 | 0 | case Type::TypeKind::kStruct: { |
1126 | 0 | for (const Field& field : type.fields()) { |
1127 | 0 | this->addSlotDebugInfoForGroup(varName + "." + std::string(field.fName), |
1128 | 0 | *field.fType, pos, groupIndex, |
1129 | 0 | isFunctionReturnValue); |
1130 | 0 | } |
1131 | 0 | break; |
1132 | 0 | } |
1133 | 0 | default: |
1134 | 0 | SkASSERTF(0, "unsupported slot type %d", (int)type.typeKind()); |
1135 | 0 | [[fallthrough]]; |
1136 | |
|
1137 | 0 | case Type::TypeKind::kScalar: |
1138 | 0 | case Type::TypeKind::kVector: |
1139 | 0 | case Type::TypeKind::kMatrix: { |
1140 | 0 | Type::NumberKind numberKind = type.componentType().numberKind(); |
1141 | 0 | int nslots = type.slotCount(); |
1142 | |
|
1143 | 0 | for (int slot = 0; slot < nslots; ++slot) { |
1144 | 0 | SlotDebugInfo slotInfo; |
1145 | 0 | slotInfo.name = varName; |
1146 | 0 | slotInfo.columns = type.columns(); |
1147 | 0 | slotInfo.rows = type.rows(); |
1148 | 0 | slotInfo.componentIndex = slot; |
1149 | 0 | slotInfo.groupIndex = (*groupIndex)++; |
1150 | 0 | slotInfo.numberKind = numberKind; |
1151 | 0 | slotInfo.pos = pos; |
1152 | 0 | slotInfo.fnReturnValue = isFunctionReturnValue ? 1 : -1; |
1153 | 0 | fSlotDebugInfo->push_back(std::move(slotInfo)); |
1154 | 0 | } |
1155 | 0 | break; |
1156 | 0 | } |
1157 | 0 | } |
1158 | 0 | } Unexecuted instantiation: SkSL::RP::SlotManager::addSlotDebugInfoForGroup(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, SkSL::Type const&, SkSL::Position, int*, bool) Unexecuted instantiation: SkSL::RP::SlotManager::addSlotDebugInfoForGroup(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, SkSL::Type const&, SkSL::Position, int*, bool) |
1159 | | |
1160 | | void SlotManager::addSlotDebugInfo(const std::string& varName, |
1161 | | const Type& type, |
1162 | | Position pos, |
1163 | 0 | bool isFunctionReturnValue) { |
1164 | 0 | int groupIndex = 0; |
1165 | 0 | this->addSlotDebugInfoForGroup(varName, type, pos, &groupIndex, isFunctionReturnValue); |
1166 | 0 | SkASSERT((size_t)groupIndex == type.slotCount()); |
1167 | 0 | } Unexecuted instantiation: SkSL::RP::SlotManager::addSlotDebugInfo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, SkSL::Type const&, SkSL::Position, bool) Unexecuted instantiation: SkSL::RP::SlotManager::addSlotDebugInfo(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, SkSL::Type const&, SkSL::Position, bool) |
1168 | | |
1169 | | SlotRange SlotManager::createSlots(std::string name, |
1170 | | const Type& type, |
1171 | | Position pos, |
1172 | 508 | bool isFunctionReturnValue) { |
1173 | 508 | size_t nslots = type.slotCount(); |
1174 | 508 | if (nslots == 0) { |
1175 | 0 | return {}; |
1176 | 0 | } |
1177 | 508 | if (fSlotDebugInfo) { |
1178 | | // Our debug slot-info table should have the same length as the actual slot table. |
1179 | 0 | SkASSERT(fSlotDebugInfo->size() == (size_t)fSlotCount); |
1180 | | |
1181 | | // Append slot names and types to our debug slot-info table. |
1182 | 0 | fSlotDebugInfo->reserve(fSlotCount + nslots); |
1183 | 0 | this->addSlotDebugInfo(name, type, pos, isFunctionReturnValue); |
1184 | | |
1185 | | // Confirm that we added the expected number of slots. |
1186 | 0 | SkASSERT(fSlotDebugInfo->size() == (size_t)(fSlotCount + nslots)); |
1187 | 0 | } |
1188 | | |
1189 | 508 | SlotRange result = {fSlotCount, (int)nslots}; |
1190 | 508 | fSlotCount += nslots; |
1191 | 508 | return result; |
1192 | 508 | } |
1193 | | |
1194 | 420 | std::optional<SlotRange> SlotManager::mapVariableToSlots(const Variable& v, SlotRange range) { |
1195 | 420 | SkASSERT(v.type().slotCount() == SkToSizeT(range.count)); |
1196 | 420 | const SlotRange* existingEntry = fSlotMap.find(&v); |
1197 | 420 | std::optional<SlotRange> originalRange = existingEntry ? std::optional(*existingEntry) |
1198 | 420 | : std::nullopt; |
1199 | 420 | fSlotMap.set(&v, range); |
1200 | 420 | return originalRange; |
1201 | 420 | } |
1202 | | |
1203 | 4 | void SlotManager::unmapVariableSlots(const Variable& v) { |
1204 | 4 | fSlotMap.remove(&v); |
1205 | 4 | } |
1206 | | |
1207 | 1.63k | SlotRange SlotManager::getVariableSlots(const Variable& v) { |
1208 | 1.63k | SlotRange* entry = fSlotMap.find(&v); |
1209 | 1.63k | if (entry != nullptr) { |
1210 | 1.21k | return *entry; |
1211 | 1.21k | } |
1212 | 416 | SlotRange range = this->createSlots(std::string(v.name()), |
1213 | 416 | v.type(), |
1214 | 416 | v.fPosition, |
1215 | 416 | /*isFunctionReturnValue=*/false); |
1216 | 416 | this->mapVariableToSlots(v, range); |
1217 | 416 | return range; |
1218 | 1.63k | } |
1219 | | |
1220 | 55 | SlotRange SlotManager::getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f) { |
1221 | 55 | SlotRange* entry = fSlotMap.find(&callSite); |
1222 | 55 | if (entry != nullptr) { |
1223 | 0 | return *entry; |
1224 | 0 | } |
1225 | 55 | SlotRange range = this->createSlots("[" + std::string(f.name()) + "].result", |
1226 | 55 | f.returnType(), |
1227 | 55 | f.fPosition, |
1228 | 55 | /*isFunctionReturnValue=*/true); |
1229 | 55 | fSlotMap.set(&callSite, range); |
1230 | 55 | return range; |
1231 | 55 | } |
1232 | | |
1233 | 512 | static bool is_sliceable_swizzle(SkSpan<const int8_t> components) { |
1234 | | // Determine if the swizzle rearranges its elements, or if it's a simple subset of its elements. |
1235 | | // (A simple subset would be a sequential non-repeating range of components, like `.xyz` or |
1236 | | // `.yzw` or `.z`, but not `.xx` or `.xz`, which can be accessed as a slice of the variable.) |
1237 | 724 | for (size_t index = 1; index < components.size(); ++index) { |
1238 | 220 | if (components[index] != int8_t(components[0] + index)) { |
1239 | 8 | return false; |
1240 | 8 | } |
1241 | 220 | } |
1242 | 504 | return true; |
1243 | 512 | } |
1244 | | |
1245 | 179 | std::unique_ptr<LValue> Generator::makeLValue(const Expression& e, bool allowScratch) { |
1246 | 179 | if (e.is<VariableReference>()) { |
1247 | 138 | const Variable* variable = e.as<VariableReference>().variable(); |
1248 | 138 | if (fImmutableVariables.contains(variable)) { |
1249 | 0 | return std::make_unique<ImmutableLValue>(variable); |
1250 | 0 | } |
1251 | 138 | return std::make_unique<VariableLValue>(variable); |
1252 | 138 | } |
1253 | 41 | if (e.is<Swizzle>()) { |
1254 | 33 | const Swizzle& swizzleExpr = e.as<Swizzle>(); |
1255 | 33 | if (std::unique_ptr<LValue> base = this->makeLValue(*swizzleExpr.base(), |
1256 | 33 | allowScratch)) { |
1257 | 33 | const ComponentArray& components = swizzleExpr.components(); |
1258 | 33 | if (is_sliceable_swizzle(components)) { |
1259 | | // If the swizzle is a contiguous subset, we can represent it with a fixed slice. |
1260 | 33 | return std::make_unique<LValueSlice>(std::move(base), components[0], |
1261 | 33 | components.size()); |
1262 | 33 | } |
1263 | 0 | return std::make_unique<SwizzleLValue>(std::move(base), components); |
1264 | 33 | } |
1265 | 0 | return nullptr; |
1266 | 33 | } |
1267 | 8 | if (e.is<FieldAccess>()) { |
1268 | 0 | const FieldAccess& fieldExpr = e.as<FieldAccess>(); |
1269 | 0 | if (std::unique_ptr<LValue> base = this->makeLValue(*fieldExpr.base(), |
1270 | 0 | allowScratch)) { |
1271 | | // Represent field access with a slice. |
1272 | 0 | return std::make_unique<LValueSlice>(std::move(base), fieldExpr.initialSlot(), |
1273 | 0 | fieldExpr.type().slotCount()); |
1274 | 0 | } |
1275 | 0 | return nullptr; |
1276 | 0 | } |
1277 | 8 | if (e.is<IndexExpression>()) { |
1278 | 8 | const IndexExpression& indexExpr = e.as<IndexExpression>(); |
1279 | | |
1280 | | // If the index base is swizzled (`vec.zyx[idx]`), rewrite it into an equivalent |
1281 | | // non-swizzled form (`vec[uint3(2,1,0)[idx]]`). |
1282 | 8 | if (std::unique_ptr<Expression> rewritten = Transform::RewriteIndexedSwizzle(fContext, |
1283 | 8 | indexExpr)) { |
1284 | | // Convert the rewritten expression into an lvalue. |
1285 | 0 | std::unique_ptr<LValue> lvalue = this->makeLValue(*rewritten, allowScratch); |
1286 | 0 | if (!lvalue) { |
1287 | 0 | return nullptr; |
1288 | 0 | } |
1289 | | // We need to hold onto the rewritten expression for the lifetime of the lvalue. |
1290 | 0 | lvalue->fScratchExpression = std::move(rewritten); |
1291 | 0 | return lvalue; |
1292 | 0 | } |
1293 | 8 | if (std::unique_ptr<LValue> base = this->makeLValue(*indexExpr.base(), |
1294 | 8 | allowScratch)) { |
1295 | | // If the index is a compile-time constant, we can represent it with a fixed slice. |
1296 | 8 | SKSL_INT indexValue; |
1297 | 8 | if (ConstantFolder::GetConstantInt(*indexExpr.index(), &indexValue)) { |
1298 | 0 | int numSlots = indexExpr.type().slotCount(); |
1299 | 0 | return std::make_unique<LValueSlice>(std::move(base), numSlots * indexValue, |
1300 | 0 | numSlots); |
1301 | 0 | } |
1302 | | |
1303 | | // Represent non-constant indexing via a dynamic index. |
1304 | 8 | auto dynLValue = std::make_unique<DynamicIndexLValue>(std::move(base), indexExpr); |
1305 | 8 | return dynLValue->evaluateDynamicIndices(this) ? std::move(dynLValue) |
1306 | 8 | : nullptr; |
1307 | 8 | } |
1308 | 0 | return nullptr; |
1309 | 8 | } |
1310 | 0 | if (allowScratch) { |
1311 | | // This path allows us to perform field- and index-accesses on an expression as if it were |
1312 | | // an lvalue, but is a temporary and shouldn't be written back to. |
1313 | 0 | return std::make_unique<ScratchLValue>(e); |
1314 | 0 | } |
1315 | 0 | return nullptr; |
1316 | 0 | } |
1317 | | |
1318 | 40 | bool Generator::push(LValue& lvalue) { |
1319 | 40 | return lvalue.push(this, |
1320 | 40 | lvalue.fixedSlotRange(this), |
1321 | 40 | lvalue.dynamicSlotRange(), |
1322 | 40 | /*swizzle=*/{}); |
1323 | 40 | } |
1324 | | |
1325 | 130 | bool Generator::store(LValue& lvalue) { |
1326 | 130 | SkASSERT(lvalue.isWritable()); |
1327 | 130 | return lvalue.store(this, |
1328 | 130 | lvalue.fixedSlotRange(this), |
1329 | 130 | lvalue.dynamicSlotRange(), |
1330 | 130 | /*swizzle=*/{}); |
1331 | 130 | } |
1332 | | |
1333 | 0 | int Generator::getFunctionDebugInfo(const FunctionDeclaration& decl) { |
1334 | 0 | SkASSERT(fDebugTrace); |
1335 | |
|
1336 | 0 | std::string name = decl.description(); |
1337 | | |
1338 | | // When generating the debug trace, we typically mark every function as `noinline`. This makes |
1339 | | // the trace more confusing, since this isn't in the source program, so remove it. |
1340 | 0 | static constexpr std::string_view kNoInline = "noinline "; |
1341 | 0 | if (skstd::starts_with(name, kNoInline)) { |
1342 | 0 | name = name.substr(kNoInline.size()); |
1343 | 0 | } |
1344 | | |
1345 | | // Look for a matching FunctionDebugInfo slot. |
1346 | 0 | for (size_t index = 0; index < fDebugTrace->fFuncInfo.size(); ++index) { |
1347 | 0 | if (fDebugTrace->fFuncInfo[index].name == name) { |
1348 | 0 | return index; |
1349 | 0 | } |
1350 | 0 | } |
1351 | | |
1352 | | // We've never called this function before; create a new slot to hold its information. |
1353 | 0 | int slot = (int)fDebugTrace->fFuncInfo.size(); |
1354 | 0 | fDebugTrace->fFuncInfo.push_back(FunctionDebugInfo{std::move(name)}); |
1355 | 0 | return slot; |
1356 | 0 | } Unexecuted instantiation: SkSL::RP::Generator::getFunctionDebugInfo(SkSL::FunctionDeclaration const&) Unexecuted instantiation: SkSL::RP::Generator::getFunctionDebugInfo(SkSL::FunctionDeclaration const&) |
1357 | | |
1358 | 24 | int Generator::createStack() { |
1359 | 24 | if (!fRecycledStacks.empty()) { |
1360 | 8 | int stackID = fRecycledStacks.back(); |
1361 | 8 | fRecycledStacks.pop_back(); |
1362 | 8 | return stackID; |
1363 | 8 | } |
1364 | 16 | return ++fNextStackID; |
1365 | 24 | } |
1366 | | |
1367 | 24 | void Generator::recycleStack(int stackID) { |
1368 | 24 | fRecycledStacks.push_back(stackID); |
1369 | 24 | } |
1370 | | |
1371 | 128 | void Generator::setCurrentStack(int stackID) { |
1372 | 128 | if (fCurrentStack != stackID) { |
1373 | 128 | fCurrentStack = stackID; |
1374 | 128 | fBuilder.set_current_stack(stackID); |
1375 | 128 | } |
1376 | 128 | } |
1377 | | |
1378 | | std::optional<SlotRange> Generator::writeFunction( |
1379 | | const IRNode& callSite, |
1380 | | const FunctionDefinition& function, |
1381 | 55 | SkSpan<std::unique_ptr<Expression> const> arguments) { |
1382 | | // Generate debug information and emit a trace-enter op. |
1383 | 55 | int funcIndex = -1; |
1384 | 55 | if (fDebugTrace) { |
1385 | 0 | funcIndex = this->getFunctionDebugInfo(function.declaration()); |
1386 | 0 | SkASSERT(funcIndex >= 0); |
1387 | 0 | if (this->shouldWriteTraceOps()) { |
1388 | 0 | fBuilder.trace_enter(fTraceMask->stackID(), funcIndex); |
1389 | 0 | } |
1390 | 0 | } |
1391 | | |
1392 | | // Handle parameter lvalues. |
1393 | 55 | struct RemappedSlotRange { |
1394 | 55 | const Variable* fVariable; |
1395 | 55 | std::optional<SlotRange> fSlotRange; |
1396 | 55 | }; |
1397 | 55 | SkSpan<Variable* const> parameters = function.declaration().parameters(); |
1398 | 55 | TArray<std::unique_ptr<LValue>> lvalues; |
1399 | 55 | TArray<RemappedSlotRange> remappedSlotRanges; |
1400 | | |
1401 | 55 | if (function.declaration().isMain()) { |
1402 | | // For main(), the parameter slots have already been populated by `writeProgram`, but we |
1403 | | // still need to explicitly emit trace ops for the variables in main(), since they are |
1404 | | // initialized before it is safe to use trace-var. (We can't invoke init-lane-masks until |
1405 | | // after we've copied the inputs from main into slots, because dst.rgba is used to pass in a |
1406 | | // blend-destination color, but we clobber it and put in the execution mask instead.) |
1407 | 51 | if (this->shouldWriteTraceOps()) { |
1408 | 0 | for (const Variable* var : parameters) { |
1409 | 0 | fBuilder.trace_var(fTraceMask->stackID(), this->getVariableSlots(*var)); |
1410 | 0 | } |
1411 | 0 | } |
1412 | 51 | } else { |
1413 | | // Write all the arguments into their parameter's variable slots. Because we never allow |
1414 | | // recursion, we don't need to worry about overwriting any existing values in those slots. |
1415 | | // (In fact, we don't even need to apply the write mask.) |
1416 | 4 | lvalues.resize(arguments.size()); |
1417 | | |
1418 | 8 | for (size_t index = 0; index < arguments.size(); ++index) { |
1419 | 4 | const Expression& arg = *arguments[index]; |
1420 | 4 | const Variable& param = *parameters[index]; |
1421 | | |
1422 | | // Use LValues for out-parameters and inout-parameters, so we can store back to them |
1423 | | // later. |
1424 | 4 | if (IsInoutParameter(param) || IsOutParameter(param)) { |
1425 | 0 | lvalues[index] = this->makeLValue(arg); |
1426 | 0 | if (!lvalues[index]) { |
1427 | 0 | return std::nullopt; |
1428 | 0 | } |
1429 | | // There are no guarantees on the starting value of an out-parameter, so we only |
1430 | | // need to store the lvalues associated with an inout parameter. |
1431 | 0 | if (IsInoutParameter(param)) { |
1432 | 0 | if (!this->push(*lvalues[index])) { |
1433 | 0 | return std::nullopt; |
1434 | 0 | } |
1435 | 0 | this->popToSlotRangeUnmasked(this->getVariableSlots(param)); |
1436 | 0 | } |
1437 | 0 | continue; |
1438 | 0 | } |
1439 | | |
1440 | | // If a parameter is never read by the function, we don't need to populate its slots. |
1441 | 4 | ProgramUsage::VariableCounts paramCounts = fProgram.fUsage->get(param); |
1442 | 4 | if (paramCounts.fRead == 0) { |
1443 | | // Honor the expression's side effects, if any. |
1444 | 0 | if (Analysis::HasSideEffects(arg)) { |
1445 | 0 | if (!this->pushExpression(arg, /*usesResult=*/false)) { |
1446 | 0 | return std::nullopt; |
1447 | 0 | } |
1448 | 0 | this->discardExpression(arg.type().slotCount()); |
1449 | 0 | } |
1450 | 0 | continue; |
1451 | 0 | } |
1452 | | |
1453 | | // If the expression is a plain variable and the parameter is never written to, we don't |
1454 | | // need to copy it; we can just share the slots from the existing variable. |
1455 | 4 | if (paramCounts.fWrite == 0 && arg.is<VariableReference>()) { |
1456 | 4 | const Variable& var = *arg.as<VariableReference>().variable(); |
1457 | 4 | if (this->hasVariableSlots(var)) { |
1458 | 4 | std::optional<SlotRange> originalRange = |
1459 | 4 | fProgramSlots.mapVariableToSlots(param, this->getVariableSlots(var)); |
1460 | 4 | remappedSlotRanges.push_back({¶m, originalRange}); |
1461 | 4 | continue; |
1462 | 4 | } |
1463 | 4 | } |
1464 | | |
1465 | | // Copy input arguments into their respective parameter slots. |
1466 | 0 | if (!this->pushExpression(arg)) { |
1467 | 0 | return std::nullopt; |
1468 | 0 | } |
1469 | 0 | this->popToSlotRangeUnmasked(this->getVariableSlots(param)); |
1470 | 0 | } |
1471 | 4 | } |
1472 | | |
1473 | | // Set up a slot range dedicated to this function's return value. |
1474 | 55 | SlotRange lastFunctionResult = fCurrentFunctionResult; |
1475 | 55 | fCurrentFunctionResult = this->getFunctionSlots(callSite, function.declaration()); |
1476 | | |
1477 | | // Save off the return mask. |
1478 | 55 | if (this->needsReturnMask(&function)) { |
1479 | 4 | fBuilder.enableExecutionMaskWrites(); |
1480 | 4 | if (!function.declaration().isMain()) { |
1481 | 4 | fBuilder.push_return_mask(); |
1482 | 4 | } |
1483 | 4 | } |
1484 | | |
1485 | | // Emit the function body. |
1486 | 55 | if (!this->writeStatement(*function.body())) { |
1487 | 0 | return std::nullopt; |
1488 | 0 | } |
1489 | | |
1490 | | // Restore the original return mask. |
1491 | 55 | if (this->needsReturnMask(&function)) { |
1492 | 4 | if (!function.declaration().isMain()) { |
1493 | 4 | fBuilder.pop_return_mask(); |
1494 | 4 | } |
1495 | 4 | fBuilder.disableExecutionMaskWrites(); |
1496 | 4 | } |
1497 | | |
1498 | | // Restore the function-result slot range. |
1499 | 55 | SlotRange functionResult = fCurrentFunctionResult; |
1500 | 55 | fCurrentFunctionResult = lastFunctionResult; |
1501 | | |
1502 | | // Emit a trace-exit op. |
1503 | 55 | if (fDebugTrace && fWriteTraceOps) { |
1504 | 0 | fBuilder.trace_exit(fTraceMask->stackID(), funcIndex); |
1505 | 0 | } |
1506 | | |
1507 | | // Copy out-parameters and inout-parameters back to their homes. |
1508 | 59 | for (int index = 0; index < lvalues.size(); ++index) { |
1509 | 4 | if (lvalues[index]) { |
1510 | | // Only out- and inout-parameters should have an associated lvalue. |
1511 | 0 | const Variable& param = *parameters[index]; |
1512 | 0 | SkASSERT(IsInoutParameter(param) || IsOutParameter(param)); |
1513 | | |
1514 | | // Copy the parameter's slots directly into the lvalue. |
1515 | 0 | fBuilder.push_slots(this->getVariableSlots(param)); |
1516 | 0 | if (!this->store(*lvalues[index])) { |
1517 | 0 | return std::nullopt; |
1518 | 0 | } |
1519 | 0 | this->discardExpression(param.type().slotCount()); |
1520 | 0 | } |
1521 | 4 | } |
1522 | | |
1523 | | // Restore any remapped parameter slot ranges to their original values. |
1524 | 55 | for (const RemappedSlotRange& remapped : remappedSlotRanges) { |
1525 | 4 | if (remapped.fSlotRange.has_value()) { |
1526 | 0 | fProgramSlots.mapVariableToSlots(*remapped.fVariable, *remapped.fSlotRange); |
1527 | 4 | } else { |
1528 | 4 | fProgramSlots.unmapVariableSlots(*remapped.fVariable); |
1529 | 4 | } |
1530 | 4 | } |
1531 | | |
1532 | 55 | return functionResult; |
1533 | 55 | } |
1534 | | |
1535 | 527 | void Generator::emitTraceLine(Position pos) { |
1536 | 527 | if (fDebugTrace && fWriteTraceOps && pos.valid() && fInsideCompoundStatement == 0) { |
1537 | | // Binary search within fLineOffets to convert the position into a line number. |
1538 | 0 | SkASSERT(fLineOffsets.size() >= 2); |
1539 | 0 | SkASSERT(fLineOffsets[0] == 0); |
1540 | 0 | SkASSERT(fLineOffsets.back() == (int)fProgram.fSource->length()); |
1541 | 0 | int lineNumber = std::distance( |
1542 | 0 | fLineOffsets.begin(), |
1543 | 0 | std::upper_bound(fLineOffsets.begin(), fLineOffsets.end(), pos.startOffset())); |
1544 | |
|
1545 | 0 | fBuilder.trace_line(fTraceMask->stackID(), lineNumber); |
1546 | 0 | } |
1547 | 527 | } |
1548 | | |
1549 | 187 | void Generator::pushTraceScopeMask() { |
1550 | 187 | if (this->shouldWriteTraceOps()) { |
1551 | | // Take the intersection of the trace mask and the execution mask. To do this, start with an |
1552 | | // all-zero mask, then use select to overwrite those zeros with the trace mask across all |
1553 | | // executing lanes. We'll get the trace mask in executing lanes, and zero in dead lanes. |
1554 | 0 | fBuilder.push_constant_i(0); |
1555 | 0 | fTraceMask->pushClone(/*slots=*/1); |
1556 | 0 | fBuilder.select(/*slots=*/1); |
1557 | 0 | } |
1558 | 187 | } |
1559 | | |
1560 | 187 | void Generator::discardTraceScopeMask() { |
1561 | 187 | if (this->shouldWriteTraceOps()) { |
1562 | 0 | this->discardExpression(/*slots=*/1); |
1563 | 0 | } |
1564 | 187 | } |
1565 | | |
1566 | 374 | void Generator::emitTraceScope(int delta) { |
1567 | 374 | if (this->shouldWriteTraceOps()) { |
1568 | 0 | fBuilder.trace_scope(this->currentStack(), delta); |
1569 | 0 | } |
1570 | 374 | } |
1571 | | |
1572 | 0 | void Generator::calculateLineOffsets() { |
1573 | 0 | SkASSERT(fLineOffsets.empty()); |
1574 | 0 | fLineOffsets.push_back(0); |
1575 | 0 | for (size_t i = 0; i < fProgram.fSource->length(); ++i) { |
1576 | 0 | if ((*fProgram.fSource)[i] == '\n') { |
1577 | 0 | fLineOffsets.push_back(i); |
1578 | 0 | } |
1579 | 0 | } |
1580 | 0 | fLineOffsets.push_back(fProgram.fSource->length()); |
1581 | 0 | } Unexecuted instantiation: SkSL::RP::Generator::calculateLineOffsets() Unexecuted instantiation: SkSL::RP::Generator::calculateLineOffsets() |
1582 | | |
1583 | 51 | bool Generator::writeGlobals() { |
1584 | 266 | for (const ProgramElement* e : fProgram.elements()) { |
1585 | 266 | if (e->is<GlobalVarDeclaration>()) { |
1586 | 183 | const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>(); |
1587 | 183 | const VarDeclaration& decl = gvd.varDeclaration(); |
1588 | 183 | const Variable* var = decl.var(); |
1589 | | |
1590 | 183 | if (var->type().isEffectChild()) { |
1591 | | // Associate each child effect variable with its numeric index. |
1592 | 50 | SkASSERT(!fChildEffectMap.find(var)); |
1593 | 50 | int childEffectIndex = fChildEffectMap.count(); |
1594 | 50 | fChildEffectMap[var] = childEffectIndex; |
1595 | 50 | continue; |
1596 | 50 | } |
1597 | | |
1598 | | // Opaque types include child processors and GL objects (samplers, textures, etc). |
1599 | | // Of those, only child processors are legal variables. |
1600 | 133 | SkASSERT(!var->type().isVoid()); |
1601 | 133 | SkASSERT(!var->type().isOpaque()); |
1602 | | |
1603 | | // Builtin variables are system-defined, with special semantics. |
1604 | 133 | if (int builtin = var->layout().fBuiltin; builtin >= 0) { |
1605 | 0 | if (builtin == SK_FRAGCOORD_BUILTIN) { |
1606 | 0 | fBuilder.store_device_xy01(this->getVariableSlots(*var)); |
1607 | 0 | continue; |
1608 | 0 | } |
1609 | | // The only builtin variable exposed to runtime effects is sk_FragCoord. |
1610 | 0 | return unsupported(); |
1611 | 0 | } |
1612 | | |
1613 | 133 | if (IsUniform(*var)) { |
1614 | | // Create the uniform slot map in first-to-last order. |
1615 | 116 | SlotRange uniformSlotRange = this->getUniformSlots(*var); |
1616 | | |
1617 | 116 | if (this->shouldWriteTraceOps()) { |
1618 | | // We expect uniform values to show up in the debug trace. To make this happen |
1619 | | // without updating the file format, we synthesize a value-slot range for the |
1620 | | // uniform here, and copy the uniform data into the value slots. This allows |
1621 | | // trace_var to work naturally. This wastes a bit of memory, but debug traces |
1622 | | // don't need to be hyper-efficient. |
1623 | 0 | SlotRange copyRange = fProgramSlots.getVariableSlots(*var); |
1624 | 0 | fBuilder.push_uniform(uniformSlotRange); |
1625 | 0 | this->popToSlotRangeUnmasked(copyRange); |
1626 | 0 | } |
1627 | | |
1628 | 116 | continue; |
1629 | 116 | } |
1630 | | |
1631 | | // Other globals are treated as normal variable declarations. |
1632 | 17 | if (!this->writeVarDeclaration(decl)) { |
1633 | 0 | return unsupported(); |
1634 | 0 | } |
1635 | 17 | } |
1636 | 266 | } |
1637 | | |
1638 | 51 | return true; |
1639 | 51 | } SkSL::RP::Generator::writeGlobals() Line | Count | Source | 1583 | 51 | bool Generator::writeGlobals() { | 1584 | 266 | for (const ProgramElement* e : fProgram.elements()) { | 1585 | 266 | if (e->is<GlobalVarDeclaration>()) { | 1586 | 183 | const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>(); | 1587 | 183 | const VarDeclaration& decl = gvd.varDeclaration(); | 1588 | 183 | const Variable* var = decl.var(); | 1589 | | | 1590 | 183 | if (var->type().isEffectChild()) { | 1591 | | // Associate each child effect variable with its numeric index. | 1592 | 50 | SkASSERT(!fChildEffectMap.find(var)); | 1593 | 50 | int childEffectIndex = fChildEffectMap.count(); | 1594 | 50 | fChildEffectMap[var] = childEffectIndex; | 1595 | 50 | continue; | 1596 | 50 | } | 1597 | | | 1598 | | // Opaque types include child processors and GL objects (samplers, textures, etc). | 1599 | | // Of those, only child processors are legal variables. | 1600 | 133 | SkASSERT(!var->type().isVoid()); | 1601 | 133 | SkASSERT(!var->type().isOpaque()); | 1602 | | | 1603 | | // Builtin variables are system-defined, with special semantics. | 1604 | 133 | if (int builtin = var->layout().fBuiltin; builtin >= 0) { | 1605 | 0 | if (builtin == SK_FRAGCOORD_BUILTIN) { | 1606 | 0 | fBuilder.store_device_xy01(this->getVariableSlots(*var)); | 1607 | 0 | continue; | 1608 | 0 | } | 1609 | | // The only builtin variable exposed to runtime effects is sk_FragCoord. | 1610 | 0 | return unsupported(); | 1611 | 0 | } | 1612 | | | 1613 | 133 | if (IsUniform(*var)) { | 1614 | | // Create the uniform slot map in first-to-last order. | 1615 | 116 | SlotRange uniformSlotRange = this->getUniformSlots(*var); | 1616 | | | 1617 | 116 | if (this->shouldWriteTraceOps()) { | 1618 | | // We expect uniform values to show up in the debug trace. To make this happen | 1619 | | // without updating the file format, we synthesize a value-slot range for the | 1620 | | // uniform here, and copy the uniform data into the value slots. This allows | 1621 | | // trace_var to work naturally. This wastes a bit of memory, but debug traces | 1622 | | // don't need to be hyper-efficient. | 1623 | 0 | SlotRange copyRange = fProgramSlots.getVariableSlots(*var); | 1624 | 0 | fBuilder.push_uniform(uniformSlotRange); | 1625 | 0 | this->popToSlotRangeUnmasked(copyRange); | 1626 | 0 | } | 1627 | | | 1628 | 116 | continue; | 1629 | 116 | } | 1630 | | | 1631 | | // Other globals are treated as normal variable declarations. | 1632 | 17 | if (!this->writeVarDeclaration(decl)) { | 1633 | 0 | return unsupported(); | 1634 | 0 | } | 1635 | 17 | } | 1636 | 266 | } | 1637 | | | 1638 | 51 | return true; | 1639 | 51 | } |
Unexecuted instantiation: SkSL::RP::Generator::writeGlobals() |
1640 | | |
1641 | 701 | bool Generator::writeStatement(const Statement& s) { |
1642 | 701 | switch (s.kind()) { |
1643 | 182 | case Statement::Kind::kBlock: |
1644 | | // The debugger will stop on statements inside Blocks; there's no need for an additional |
1645 | | // stop on the block's initial open-brace. |
1646 | 195 | case Statement::Kind::kFor: |
1647 | | // The debugger will stop on the init-statement of a for statement, so we don't need to |
1648 | | // stop on the outer for-statement itself as well. |
1649 | 195 | break; |
1650 | | |
1651 | 506 | default: |
1652 | | // The debugger should stop on other statements. |
1653 | 506 | this->emitTraceLine(s.fPosition); |
1654 | 506 | break; |
1655 | 701 | } |
1656 | | |
1657 | 701 | switch (s.kind()) { |
1658 | 182 | case Statement::Kind::kBlock: |
1659 | 182 | return this->writeBlock(s.as<Block>()); |
1660 | | |
1661 | 13 | case Statement::Kind::kBreak: |
1662 | 13 | return this->writeBreakStatement(s.as<BreakStatement>()); |
1663 | | |
1664 | 0 | case Statement::Kind::kContinue: |
1665 | 0 | return this->writeContinueStatement(s.as<ContinueStatement>()); |
1666 | | |
1667 | 0 | case Statement::Kind::kDo: |
1668 | 0 | return this->writeDoStatement(s.as<DoStatement>()); |
1669 | | |
1670 | 117 | case Statement::Kind::kExpression: |
1671 | 117 | return this->writeExpressionStatement(s.as<ExpressionStatement>()); |
1672 | | |
1673 | 13 | case Statement::Kind::kFor: |
1674 | 13 | return this->writeForStatement(s.as<ForStatement>()); |
1675 | | |
1676 | 65 | case Statement::Kind::kIf: |
1677 | 65 | return this->writeIfStatement(s.as<IfStatement>()); |
1678 | | |
1679 | 20 | case Statement::Kind::kNop: |
1680 | 20 | return true; |
1681 | | |
1682 | 63 | case Statement::Kind::kReturn: |
1683 | 63 | return this->writeReturnStatement(s.as<ReturnStatement>()); |
1684 | | |
1685 | 0 | case Statement::Kind::kSwitch: |
1686 | 0 | return this->writeSwitchStatement(s.as<SwitchStatement>()); |
1687 | | |
1688 | 228 | case Statement::Kind::kVarDeclaration: |
1689 | 228 | return this->writeVarDeclaration(s.as<VarDeclaration>()); |
1690 | | |
1691 | 0 | default: |
1692 | 0 | return unsupported(); |
1693 | 701 | } |
1694 | 701 | } |
1695 | | |
1696 | 182 | bool Generator::writeBlock(const Block& b) { |
1697 | 182 | if (b.blockKind() == Block::Kind::kCompoundStatement) { |
1698 | 8 | this->emitTraceLine(b.fPosition); |
1699 | 8 | ++fInsideCompoundStatement; |
1700 | 174 | } else { |
1701 | 174 | this->pushTraceScopeMask(); |
1702 | 174 | this->emitTraceScope(+1); |
1703 | 174 | } |
1704 | | |
1705 | 534 | for (const std::unique_ptr<Statement>& stmt : b.children()) { |
1706 | 534 | if (!this->writeStatement(*stmt)) { |
1707 | 0 | return unsupported(); |
1708 | 0 | } |
1709 | 534 | } |
1710 | | |
1711 | 182 | if (b.blockKind() == Block::Kind::kCompoundStatement) { |
1712 | 8 | --fInsideCompoundStatement; |
1713 | 174 | } else { |
1714 | 174 | this->emitTraceScope(-1); |
1715 | 174 | this->discardTraceScopeMask(); |
1716 | 174 | } |
1717 | | |
1718 | 182 | return true; |
1719 | 182 | } |
1720 | | |
1721 | 13 | bool Generator::writeBreakStatement(const BreakStatement&) { |
1722 | | // If all lanes have reached this break, we can just branch straight to the break target instead |
1723 | | // of updating masks. |
1724 | 13 | fBuilder.branch_if_all_lanes_active(fCurrentBreakTarget); |
1725 | 13 | fBuilder.mask_off_loop_mask(); |
1726 | 13 | return true; |
1727 | 13 | } |
1728 | | |
1729 | 0 | bool Generator::writeContinueStatement(const ContinueStatement&) { |
1730 | 0 | fBuilder.continue_op(fCurrentContinueMask->stackID()); |
1731 | 0 | return true; |
1732 | 0 | } |
1733 | | |
1734 | 0 | bool Generator::writeDoStatement(const DoStatement& d) { |
1735 | | // Set up a break target. |
1736 | 0 | AutoLoopTarget breakTarget(this, &fCurrentBreakTarget); |
1737 | | |
1738 | | // Save off the original loop mask. |
1739 | 0 | fBuilder.enableExecutionMaskWrites(); |
1740 | 0 | fBuilder.push_loop_mask(); |
1741 | | |
1742 | | // If `continue` is used in the loop... |
1743 | 0 | Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*d.statement()); |
1744 | 0 | AutoContinueMask autoContinueMask(this); |
1745 | 0 | if (loopInfo.fHasContinue) { |
1746 | | // ... create a temporary slot for continue-mask storage. |
1747 | 0 | autoContinueMask.enable(); |
1748 | 0 | } |
1749 | | |
1750 | | // Write the do-loop body. |
1751 | 0 | int labelID = fBuilder.nextLabelID(); |
1752 | 0 | fBuilder.label(labelID); |
1753 | |
|
1754 | 0 | autoContinueMask.enterLoopBody(); |
1755 | |
|
1756 | 0 | if (!this->writeStatement(*d.statement())) { |
1757 | 0 | return false; |
1758 | 0 | } |
1759 | | |
1760 | 0 | autoContinueMask.exitLoopBody(); |
1761 | | |
1762 | | // Point the debugger at the do-statement's test-expression before we run it. |
1763 | 0 | this->emitTraceLine(d.test()->fPosition); |
1764 | | |
1765 | | // Emit the test-expression, in order to combine it with the loop mask. |
1766 | 0 | if (!this->pushExpression(*d.test())) { |
1767 | 0 | return false; |
1768 | 0 | } |
1769 | | |
1770 | | // Mask off any lanes in the loop mask where the test-expression is false; this breaks the loop. |
1771 | | // We don't use the test expression for anything else, so jettison it. |
1772 | 0 | fBuilder.merge_loop_mask(); |
1773 | 0 | this->discardExpression(/*slots=*/1); |
1774 | | |
1775 | | // If any lanes are still running, go back to the top and run the loop body again. |
1776 | 0 | fBuilder.branch_if_any_lanes_active(labelID); |
1777 | | |
1778 | | // If we hit a break statement on all lanes, we will branch here to escape from the loop. |
1779 | 0 | fBuilder.label(breakTarget.labelID()); |
1780 | | |
1781 | | // Restore the loop mask. |
1782 | 0 | fBuilder.pop_loop_mask(); |
1783 | 0 | fBuilder.disableExecutionMaskWrites(); |
1784 | |
|
1785 | 0 | return true; |
1786 | 0 | } |
1787 | | |
1788 | 0 | bool Generator::writeMasklessForStatement(const ForStatement& f) { |
1789 | 0 | SkASSERT(f.unrollInfo()); |
1790 | 0 | SkASSERT(f.unrollInfo()->fCount > 0); |
1791 | 0 | SkASSERT(f.initializer()); |
1792 | 0 | SkASSERT(f.test()); |
1793 | 0 | SkASSERT(f.next()); |
1794 | | |
1795 | | // We want the loop index to disappear at the end of the loop, so wrap the for statement in a |
1796 | | // trace scope. |
1797 | 0 | this->pushTraceScopeMask(); |
1798 | 0 | this->emitTraceScope(+1); |
1799 | | |
1800 | | // If no lanes are active, skip over the loop entirely. This guards against looping forever; |
1801 | | // with no lanes active, we wouldn't be able to write the loop variable back to its slot, so |
1802 | | // we'd never make forward progress. |
1803 | 0 | int loopExitID = fBuilder.nextLabelID(); |
1804 | 0 | int loopBodyID = fBuilder.nextLabelID(); |
1805 | 0 | fBuilder.branch_if_no_lanes_active(loopExitID); |
1806 | | |
1807 | | // Run the loop initializer. |
1808 | 0 | if (!this->writeStatement(*f.initializer())) { |
1809 | 0 | return unsupported(); |
1810 | 0 | } |
1811 | | |
1812 | | // Write the for-loop body. We know the for-loop has a standard ES2 unrollable structure, and |
1813 | | // that it runs for at least one iteration, so we can plow straight ahead into the loop body |
1814 | | // instead of running the loop-test first. |
1815 | 0 | fBuilder.label(loopBodyID); |
1816 | |
|
1817 | 0 | if (!this->writeStatement(*f.statement())) { |
1818 | 0 | return unsupported(); |
1819 | 0 | } |
1820 | | |
1821 | | // Point the debugger at the for-statement's next-expression before we run it, or as close as we |
1822 | | // can reasonably get. |
1823 | 0 | if (f.next()) { |
1824 | 0 | this->emitTraceLine(f.next()->fPosition); |
1825 | 0 | } else if (f.test()) { |
1826 | 0 | this->emitTraceLine(f.test()->fPosition); |
1827 | 0 | } else { |
1828 | 0 | this->emitTraceLine(f.fPosition); |
1829 | 0 | } |
1830 | | |
1831 | | // If the loop only runs for a single iteration, we are already done. If not... |
1832 | 0 | if (f.unrollInfo()->fCount > 1) { |
1833 | | // ... run the next-expression, and immediately discard its result. |
1834 | 0 | if (!this->pushExpression(*f.next(), /*usesResult=*/false)) { |
1835 | 0 | return unsupported(); |
1836 | 0 | } |
1837 | 0 | this->discardExpression(f.next()->type().slotCount()); |
1838 | | |
1839 | | // Run the test-expression, and repeat the loop until the test-expression evaluates false. |
1840 | 0 | if (!this->pushExpression(*f.test())) { |
1841 | 0 | return unsupported(); |
1842 | 0 | } |
1843 | 0 | fBuilder.branch_if_no_active_lanes_on_stack_top_equal(0, loopBodyID); |
1844 | | |
1845 | | // Jettison the test-expression. |
1846 | 0 | this->discardExpression(/*slots=*/1); |
1847 | 0 | } |
1848 | | |
1849 | 0 | fBuilder.label(loopExitID); |
1850 | |
|
1851 | 0 | this->emitTraceScope(-1); |
1852 | 0 | this->discardTraceScopeMask(); |
1853 | 0 | return true; |
1854 | 0 | } Unexecuted instantiation: SkSL::RP::Generator::writeMasklessForStatement(SkSL::ForStatement const&) Unexecuted instantiation: SkSL::RP::Generator::writeMasklessForStatement(SkSL::ForStatement const&) |
1855 | | |
1856 | 13 | bool Generator::writeForStatement(const ForStatement& f) { |
1857 | | // If we've determined that the loop does not run, omit its code entirely. |
1858 | 13 | if (f.unrollInfo() && f.unrollInfo()->fCount == 0) { |
1859 | 0 | return true; |
1860 | 0 | } |
1861 | | |
1862 | | // If the loop doesn't escape early due to a `continue`, `break` or `return`, and the loop |
1863 | | // conforms to ES2 structure, we know that we will run the full number of iterations across all |
1864 | | // lanes and don't need to use a loop mask. |
1865 | 13 | Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*f.statement()); |
1866 | 13 | if (!loopInfo.fHasContinue && !loopInfo.fHasBreak && !loopInfo.fHasReturn && f.unrollInfo()) { |
1867 | 0 | return this->writeMasklessForStatement(f); |
1868 | 0 | } |
1869 | | |
1870 | | // We want the loop index to disappear at the end of the loop, so wrap the for statement in a |
1871 | | // trace scope. |
1872 | 13 | this->pushTraceScopeMask(); |
1873 | 13 | this->emitTraceScope(+1); |
1874 | | |
1875 | | // Set up a break target. |
1876 | 13 | AutoLoopTarget breakTarget(this, &fCurrentBreakTarget); |
1877 | | |
1878 | | // Run the loop initializer. |
1879 | 13 | if (f.initializer()) { |
1880 | 13 | if (!this->writeStatement(*f.initializer())) { |
1881 | 0 | return unsupported(); |
1882 | 0 | } |
1883 | 13 | } else { |
1884 | 0 | this->emitTraceLine(f.fPosition); |
1885 | 0 | } |
1886 | | |
1887 | 13 | AutoContinueMask autoContinueMask(this); |
1888 | 13 | if (loopInfo.fHasContinue) { |
1889 | | // Acquire a temporary slot for continue-mask storage. |
1890 | 0 | autoContinueMask.enable(); |
1891 | 0 | } |
1892 | | |
1893 | | // Save off the original loop mask. |
1894 | 13 | fBuilder.enableExecutionMaskWrites(); |
1895 | 13 | fBuilder.push_loop_mask(); |
1896 | | |
1897 | 13 | int loopTestID = fBuilder.nextLabelID(); |
1898 | 13 | int loopBodyID = fBuilder.nextLabelID(); |
1899 | | |
1900 | | // Jump down to the loop test so we can fall out of the loop immediately if it's zero-iteration. |
1901 | 13 | fBuilder.jump(loopTestID); |
1902 | | |
1903 | | // Write the for-loop body. |
1904 | 13 | fBuilder.label(loopBodyID); |
1905 | | |
1906 | 13 | autoContinueMask.enterLoopBody(); |
1907 | | |
1908 | 13 | if (!this->writeStatement(*f.statement())) { |
1909 | 0 | return unsupported(); |
1910 | 0 | } |
1911 | | |
1912 | 13 | autoContinueMask.exitLoopBody(); |
1913 | | |
1914 | | // Point the debugger at the for-statement's next-expression before we run it, or as close as we |
1915 | | // can reasonably get. |
1916 | 13 | if (f.next()) { |
1917 | 13 | this->emitTraceLine(f.next()->fPosition); |
1918 | 13 | } else if (f.test()) { |
1919 | 0 | this->emitTraceLine(f.test()->fPosition); |
1920 | 0 | } else { |
1921 | 0 | this->emitTraceLine(f.fPosition); |
1922 | 0 | } |
1923 | | |
1924 | | // Run the next-expression. Immediately discard its result. |
1925 | 13 | if (f.next()) { |
1926 | 13 | if (!this->pushExpression(*f.next(), /*usesResult=*/false)) { |
1927 | 0 | return unsupported(); |
1928 | 0 | } |
1929 | 13 | this->discardExpression(f.next()->type().slotCount()); |
1930 | 13 | } |
1931 | | |
1932 | 13 | fBuilder.label(loopTestID); |
1933 | 13 | if (f.test()) { |
1934 | | // Emit the test-expression, in order to combine it with the loop mask. |
1935 | 13 | if (!this->pushExpression(*f.test())) { |
1936 | 0 | return unsupported(); |
1937 | 0 | } |
1938 | | // Mask off any lanes in the loop mask where the test-expression is false; this breaks the |
1939 | | // loop. We don't use the test expression for anything else, so jettison it. |
1940 | 13 | fBuilder.merge_loop_mask(); |
1941 | 13 | this->discardExpression(/*slots=*/1); |
1942 | 13 | } |
1943 | | |
1944 | | // If any lanes are still running, go back to the top and run the loop body again. |
1945 | 13 | fBuilder.branch_if_any_lanes_active(loopBodyID); |
1946 | | |
1947 | | // If we hit a break statement on all lanes, we will branch here to escape from the loop. |
1948 | 13 | fBuilder.label(breakTarget.labelID()); |
1949 | | |
1950 | | // Restore the loop mask. |
1951 | 13 | fBuilder.pop_loop_mask(); |
1952 | 13 | fBuilder.disableExecutionMaskWrites(); |
1953 | | |
1954 | 13 | this->emitTraceScope(-1); |
1955 | 13 | this->discardTraceScopeMask(); |
1956 | 13 | return true; |
1957 | 13 | } |
1958 | | |
1959 | 117 | bool Generator::writeExpressionStatement(const ExpressionStatement& e) { |
1960 | 117 | if (!this->pushExpression(*e.expression(), /*usesResult=*/false)) { |
1961 | 0 | return unsupported(); |
1962 | 0 | } |
1963 | 117 | this->discardExpression(e.expression()->type().slotCount()); |
1964 | 117 | return true; |
1965 | 117 | } |
1966 | | |
1967 | 34 | bool Generator::writeDynamicallyUniformIfStatement(const IfStatement& i) { |
1968 | 34 | SkASSERT(Analysis::IsDynamicallyUniformExpression(*i.test())); |
1969 | | |
1970 | 34 | int falseLabelID = fBuilder.nextLabelID(); |
1971 | 34 | int exitLabelID = fBuilder.nextLabelID(); |
1972 | | |
1973 | 34 | if (!this->pushExpression(*i.test())) { |
1974 | 0 | return unsupported(); |
1975 | 0 | } |
1976 | | |
1977 | 34 | fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID); |
1978 | | |
1979 | 34 | if (!this->writeStatement(*i.ifTrue())) { |
1980 | 0 | return unsupported(); |
1981 | 0 | } |
1982 | | |
1983 | 34 | if (!i.ifFalse()) { |
1984 | | // We don't have an if-false condition at all. |
1985 | 17 | fBuilder.label(falseLabelID); |
1986 | 17 | } else { |
1987 | | // We do have an if-false condition. We've just completed the if-true block, so we need to |
1988 | | // jump past the if-false block to avoid executing it. |
1989 | 17 | fBuilder.jump(exitLabelID); |
1990 | | |
1991 | | // The if-false block starts here. |
1992 | 17 | fBuilder.label(falseLabelID); |
1993 | | |
1994 | 17 | if (!this->writeStatement(*i.ifFalse())) { |
1995 | 0 | return unsupported(); |
1996 | 0 | } |
1997 | | |
1998 | 17 | fBuilder.label(exitLabelID); |
1999 | 17 | } |
2000 | | |
2001 | | // Jettison the test-expression. |
2002 | 34 | this->discardExpression(/*slots=*/1); |
2003 | 34 | return true; |
2004 | 34 | } |
2005 | | |
2006 | 65 | bool Generator::writeIfStatement(const IfStatement& i) { |
2007 | | // If the test condition is known to be uniform, we can skip over the untrue portion entirely. |
2008 | 65 | if (Analysis::IsDynamicallyUniformExpression(*i.test())) { |
2009 | 34 | return this->writeDynamicallyUniformIfStatement(i); |
2010 | 34 | } |
2011 | | |
2012 | | // Save the current condition-mask. |
2013 | 31 | fBuilder.enableExecutionMaskWrites(); |
2014 | 31 | fBuilder.push_condition_mask(); |
2015 | | |
2016 | | // Push the test condition mask. |
2017 | 31 | if (!this->pushExpression(*i.test())) { |
2018 | 0 | return unsupported(); |
2019 | 0 | } |
2020 | | |
2021 | | // Merge the current condition-mask with the test condition, then run the if-true branch. |
2022 | 31 | fBuilder.merge_condition_mask(); |
2023 | 31 | if (!this->writeStatement(*i.ifTrue())) { |
2024 | 0 | return unsupported(); |
2025 | 0 | } |
2026 | | |
2027 | 31 | if (i.ifFalse()) { |
2028 | | // Apply the inverse condition-mask. Then run the if-false branch. |
2029 | 4 | fBuilder.merge_inv_condition_mask(); |
2030 | 4 | if (!this->writeStatement(*i.ifFalse())) { |
2031 | 0 | return unsupported(); |
2032 | 0 | } |
2033 | 4 | } |
2034 | | |
2035 | | // Jettison the test-expression, and restore the the condition-mask. |
2036 | 31 | this->discardExpression(/*slots=*/1); |
2037 | 31 | fBuilder.pop_condition_mask(); |
2038 | 31 | fBuilder.disableExecutionMaskWrites(); |
2039 | | |
2040 | 31 | return true; |
2041 | 31 | } |
2042 | | |
2043 | 63 | bool Generator::writeReturnStatement(const ReturnStatement& r) { |
2044 | 63 | if (r.expression()) { |
2045 | 63 | if (!this->pushExpression(*r.expression())) { |
2046 | 0 | return unsupported(); |
2047 | 0 | } |
2048 | 63 | if (this->needsFunctionResultSlots(fCurrentFunction)) { |
2049 | 12 | this->popToSlotRange(fCurrentFunctionResult); |
2050 | 12 | } |
2051 | 63 | } |
2052 | 63 | if (fBuilder.executionMaskWritesAreEnabled() && this->needsReturnMask(fCurrentFunction)) { |
2053 | 12 | fBuilder.mask_off_return_mask(); |
2054 | 12 | } |
2055 | 63 | return true; |
2056 | 63 | } |
2057 | | |
2058 | 0 | bool Generator::writeSwitchStatement(const SwitchStatement& s) { |
2059 | 0 | const StatementArray& cases = s.cases(); |
2060 | 0 | SkASSERT(std::all_of(cases.begin(), cases.end(), [](const std::unique_ptr<Statement>& stmt) { |
2061 | 0 | return stmt->is<SwitchCase>(); |
2062 | 0 | })); |
2063 | | |
2064 | | // Set up a break target. |
2065 | 0 | AutoLoopTarget breakTarget(this, &fCurrentBreakTarget); |
2066 | | |
2067 | | // Save off the original loop mask. |
2068 | 0 | fBuilder.enableExecutionMaskWrites(); |
2069 | 0 | fBuilder.push_loop_mask(); |
2070 | | |
2071 | | // Push the switch-case value, and write a default-mask that enables every lane which already |
2072 | | // has an active loop mask. As we match cases, the default mask will get pared down. |
2073 | 0 | if (!this->pushExpression(*s.value())) { |
2074 | 0 | return unsupported(); |
2075 | 0 | } |
2076 | 0 | fBuilder.push_loop_mask(); |
2077 | | |
2078 | | // Zero out the loop mask; each case op will re-enable it as we go. |
2079 | 0 | fBuilder.mask_off_loop_mask(); |
2080 | | |
2081 | | // Write each switch-case. |
2082 | 0 | bool foundDefaultCase = false; |
2083 | 0 | for (const std::unique_ptr<Statement>& stmt : cases) { |
2084 | 0 | int skipLabelID = fBuilder.nextLabelID(); |
2085 | |
|
2086 | 0 | const SwitchCase& sc = stmt->as<SwitchCase>(); |
2087 | 0 | if (sc.isDefault()) { |
2088 | 0 | foundDefaultCase = true; |
2089 | 0 | if (stmt.get() != cases.back().get()) { |
2090 | | // We only support a default case when it is the very last case. If that changes, |
2091 | | // this logic will need to be updated. |
2092 | 0 | return unsupported(); |
2093 | 0 | } |
2094 | | // Keep whatever lanes are executing now, and also enable any lanes in the default mask. |
2095 | 0 | fBuilder.pop_and_reenable_loop_mask(); |
2096 | | // Execute the switch-case block, if any lanes are alive to see it. |
2097 | 0 | fBuilder.branch_if_no_lanes_active(skipLabelID); |
2098 | 0 | if (!this->writeStatement(*sc.statement())) { |
2099 | 0 | return unsupported(); |
2100 | 0 | } |
2101 | 0 | } else { |
2102 | | // The case-op will enable the loop mask if the switch-value matches, and mask off lanes |
2103 | | // from the default-mask. |
2104 | 0 | fBuilder.case_op(sc.value()); |
2105 | | // Execute the switch-case block, if any lanes are alive to see it. |
2106 | 0 | fBuilder.branch_if_no_lanes_active(skipLabelID); |
2107 | 0 | if (!this->writeStatement(*sc.statement())) { |
2108 | 0 | return unsupported(); |
2109 | 0 | } |
2110 | 0 | } |
2111 | 0 | fBuilder.label(skipLabelID); |
2112 | 0 | } |
2113 | | |
2114 | | // Jettison the switch value, and the default case mask if it was never consumed above. |
2115 | 0 | this->discardExpression(/*slots=*/foundDefaultCase ? 1 : 2); |
2116 | | |
2117 | | // If we hit a break statement on all lanes, we will branch here to escape from the switch. |
2118 | 0 | fBuilder.label(breakTarget.labelID()); |
2119 | | |
2120 | | // Restore the loop mask. |
2121 | 0 | fBuilder.pop_loop_mask(); |
2122 | 0 | fBuilder.disableExecutionMaskWrites(); |
2123 | 0 | return true; |
2124 | 0 | } Unexecuted instantiation: SkSL::RP::Generator::writeSwitchStatement(SkSL::SwitchStatement const&) Unexecuted instantiation: SkSL::RP::Generator::writeSwitchStatement(SkSL::SwitchStatement const&) |
2125 | | |
2126 | 237 | bool Generator::writeImmutableVarDeclaration(const VarDeclaration& d) { |
2127 | | // In a debugging session, we expect debug traces for a variable declaration to appear, even if |
2128 | | // it's constant, so we don't use immutable slots for variables when tracing is on. |
2129 | 237 | if (this->shouldWriteTraceOps()) { |
2130 | 0 | return false; |
2131 | 0 | } |
2132 | | |
2133 | | // Find the constant value for this variable. |
2134 | 237 | const Expression* initialValue = ConstantFolder::GetConstantValueForVariable(*d.value()); |
2135 | 237 | SkASSERT(initialValue); |
2136 | | |
2137 | | // For a variable to be immutable, it cannot be written-to besides its initial declaration. |
2138 | 237 | ProgramUsage::VariableCounts counts = fProgram.fUsage->get(*d.var()); |
2139 | 237 | if (counts.fWrite != 1) { |
2140 | 66 | return false; |
2141 | 66 | } |
2142 | | |
2143 | 171 | STArray<16, ImmutableBits> immutableValues; |
2144 | 171 | if (!this->getImmutableValueForExpression(*initialValue, &immutableValues)) { |
2145 | 150 | return false; |
2146 | 150 | } |
2147 | | |
2148 | 21 | fImmutableVariables.add(d.var()); |
2149 | | |
2150 | 21 | std::optional<SlotRange> preexistingSlots = this->findPreexistingImmutableData(immutableValues); |
2151 | 21 | if (preexistingSlots.has_value()) { |
2152 | | // Associate this variable with a preexisting range of immutable data (no new data or code). |
2153 | 0 | fImmutableSlots.mapVariableToSlots(*d.var(), *preexistingSlots); |
2154 | 21 | } else { |
2155 | | // Write out the constant value back to immutable slots. (This generates data, but no |
2156 | | // runtime code.) |
2157 | 21 | SlotRange slots = this->getImmutableSlots(*d.var()); |
2158 | 21 | this->storeImmutableValueToSlots(immutableValues, slots); |
2159 | 21 | } |
2160 | | |
2161 | 21 | return true; |
2162 | 171 | } |
2163 | | |
2164 | 245 | bool Generator::writeVarDeclaration(const VarDeclaration& v) { |
2165 | 245 | if (v.value()) { |
2166 | | // If a variable never actually changes, we can make it immutable. |
2167 | 237 | if (this->writeImmutableVarDeclaration(v)) { |
2168 | 21 | return true; |
2169 | 21 | } |
2170 | | // This is a real variable which can change over the course of execution. |
2171 | 216 | if (!this->pushExpression(*v.value())) { |
2172 | 0 | return unsupported(); |
2173 | 0 | } |
2174 | 216 | this->popToSlotRangeUnmasked(this->getVariableSlots(*v.var())); |
2175 | 216 | } else { |
2176 | 8 | this->zeroSlotRangeUnmasked(this->getVariableSlots(*v.var())); |
2177 | 8 | } |
2178 | 224 | return true; |
2179 | 245 | } |
2180 | | |
2181 | 2.67k | bool Generator::pushExpression(const Expression& e, bool usesResult) { |
2182 | 2.67k | switch (e.kind()) { |
2183 | 621 | case Expression::Kind::kBinary: |
2184 | 621 | return this->pushBinaryExpression(e.as<BinaryExpression>()); |
2185 | | |
2186 | 94 | case Expression::Kind::kChildCall: |
2187 | 94 | return this->pushChildCall(e.as<ChildCall>()); |
2188 | | |
2189 | 0 | case Expression::Kind::kConstructorArray: |
2190 | 0 | case Expression::Kind::kConstructorArrayCast: |
2191 | 135 | case Expression::Kind::kConstructorCompound: |
2192 | 135 | case Expression::Kind::kConstructorStruct: |
2193 | 135 | return this->pushConstructorCompound(e.asAnyConstructor()); |
2194 | | |
2195 | 48 | case Expression::Kind::kConstructorCompoundCast: |
2196 | 57 | case Expression::Kind::kConstructorScalarCast: |
2197 | 57 | return this->pushConstructorCast(e.asAnyConstructor()); |
2198 | | |
2199 | 0 | case Expression::Kind::kConstructorDiagonalMatrix: |
2200 | 0 | return this->pushConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>()); |
2201 | | |
2202 | 0 | case Expression::Kind::kConstructorMatrixResize: |
2203 | 0 | return this->pushConstructorMatrixResize(e.as<ConstructorMatrixResize>()); |
2204 | | |
2205 | 26 | case Expression::Kind::kConstructorSplat: |
2206 | 26 | return this->pushConstructorSplat(e.as<ConstructorSplat>()); |
2207 | | |
2208 | 0 | case Expression::Kind::kEmpty: |
2209 | 0 | return true; |
2210 | | |
2211 | 0 | case Expression::Kind::kFieldAccess: |
2212 | 0 | return this->pushFieldAccess(e.as<FieldAccess>()); |
2213 | | |
2214 | 231 | case Expression::Kind::kFunctionCall: |
2215 | 231 | return this->pushFunctionCall(e.as<FunctionCall>()); |
2216 | | |
2217 | 8 | case Expression::Kind::kIndex: |
2218 | 8 | return this->pushIndexExpression(e.as<IndexExpression>()); |
2219 | | |
2220 | 344 | case Expression::Kind::kLiteral: |
2221 | 344 | return this->pushLiteral(e.as<Literal>()); |
2222 | | |
2223 | 17 | case Expression::Kind::kPrefix: |
2224 | 17 | return this->pushPrefixExpression(e.as<PrefixExpression>()); |
2225 | | |
2226 | 0 | case Expression::Kind::kPostfix: |
2227 | 0 | return this->pushPostfixExpression(e.as<PostfixExpression>(), usesResult); |
2228 | | |
2229 | 479 | case Expression::Kind::kSwizzle: |
2230 | 479 | return this->pushSwizzle(e.as<Swizzle>()); |
2231 | | |
2232 | 28 | case Expression::Kind::kTernary: |
2233 | 28 | return this->pushTernaryExpression(e.as<TernaryExpression>()); |
2234 | | |
2235 | 636 | case Expression::Kind::kVariableReference: |
2236 | 636 | return this->pushVariableReference(e.as<VariableReference>()); |
2237 | | |
2238 | 0 | default: |
2239 | 0 | return unsupported(); |
2240 | 2.67k | } |
2241 | 2.67k | } |
2242 | | |
2243 | 771 | BuilderOp Generator::GetTypedOp(const SkSL::Type& type, const TypedOps& ops) { |
2244 | 771 | switch (type.componentType().numberKind()) { |
2245 | 702 | case Type::NumberKind::kFloat: return ops.fFloatOp; |
2246 | 69 | case Type::NumberKind::kSigned: return ops.fSignedOp; |
2247 | 0 | case Type::NumberKind::kUnsigned: return ops.fUnsignedOp; |
2248 | 0 | case Type::NumberKind::kBoolean: return ops.fBooleanOp; |
2249 | 0 | default: return BuilderOp::unsupported; |
2250 | 771 | } |
2251 | 771 | } |
2252 | | |
2253 | 0 | bool Generator::unaryOp(const SkSL::Type& type, const TypedOps& ops) { |
2254 | 0 | BuilderOp op = GetTypedOp(type, ops); |
2255 | 0 | if (op == BuilderOp::unsupported) { |
2256 | 0 | return unsupported(); |
2257 | 0 | } |
2258 | 0 | fBuilder.unary_op(op, type.slotCount()); |
2259 | 0 | return true; |
2260 | 0 | } |
2261 | | |
2262 | 762 | bool Generator::binaryOp(const SkSL::Type& type, const TypedOps& ops) { |
2263 | 762 | BuilderOp op = GetTypedOp(type, ops); |
2264 | 762 | if (op == BuilderOp::unsupported) { |
2265 | 0 | return unsupported(); |
2266 | 0 | } |
2267 | 762 | fBuilder.binary_op(op, type.slotCount()); |
2268 | 762 | return true; |
2269 | 762 | } |
2270 | | |
2271 | 9 | bool Generator::ternaryOp(const SkSL::Type& type, const TypedOps& ops) { |
2272 | 9 | BuilderOp op = GetTypedOp(type, ops); |
2273 | 9 | if (op == BuilderOp::unsupported) { |
2274 | 0 | return unsupported(); |
2275 | 0 | } |
2276 | 9 | fBuilder.ternary_op(op, type.slotCount()); |
2277 | 9 | return true; |
2278 | 9 | } |
2279 | | |
2280 | 39 | void Generator::foldWithMultiOp(BuilderOp op, int elements) { |
2281 | | // Fold the top N elements on the stack using an op that supports multiple slots, e.g.: |
2282 | | // (A + B + C + D) -> add_2_floats $0..1 += $2..3 |
2283 | | // add_float $0 += $1 |
2284 | 39 | for (; elements >= 8; elements -= 4) { |
2285 | 0 | fBuilder.binary_op(op, /*slots=*/4); |
2286 | 0 | } |
2287 | 39 | for (; elements >= 6; elements -= 3) { |
2288 | 0 | fBuilder.binary_op(op, /*slots=*/3); |
2289 | 0 | } |
2290 | 39 | for (; elements >= 4; elements -= 2) { |
2291 | 0 | fBuilder.binary_op(op, /*slots=*/2); |
2292 | 0 | } |
2293 | 44 | for (; elements >= 2; elements -= 1) { |
2294 | 5 | fBuilder.binary_op(op, /*slots=*/1); |
2295 | 5 | } |
2296 | 39 | } |
2297 | | |
2298 | 536 | bool Generator::pushLValueOrExpression(LValue* lvalue, const Expression& expr) { |
2299 | 536 | return lvalue ? this->push(*lvalue) |
2300 | 536 | : this->pushExpression(expr); |
2301 | 536 | } |
2302 | | |
2303 | | bool Generator::pushMatrixMultiply(LValue* lvalue, |
2304 | | const Expression& left, |
2305 | | const Expression& right, |
2306 | | int leftColumns, |
2307 | | int leftRows, |
2308 | | int rightColumns, |
2309 | 0 | int rightRows) { |
2310 | 0 | SkASSERT(left.type().isMatrix() || left.type().isVector()); |
2311 | 0 | SkASSERT(right.type().isMatrix() || right.type().isVector()); |
2312 | | |
2313 | | // Insert padding space on the stack to hold the result. |
2314 | 0 | fBuilder.pad_stack(rightColumns * leftRows); |
2315 | | |
2316 | | // Push the left and right matrices onto the stack. |
2317 | 0 | if (!this->pushLValueOrExpression(lvalue, left) || !this->pushExpression(right)) { |
2318 | 0 | return unsupported(); |
2319 | 0 | } |
2320 | | |
2321 | 0 | fBuilder.matrix_multiply(leftColumns, leftRows, rightColumns, rightRows); |
2322 | | |
2323 | | // If this multiply was actually an assignment (via *=), write the result back to the lvalue. |
2324 | 0 | return lvalue ? this->store(*lvalue) |
2325 | 0 | : true; |
2326 | 0 | } Unexecuted instantiation: SkSL::RP::Generator::pushMatrixMultiply(SkSL::RP::LValue*, SkSL::Expression const&, SkSL::Expression const&, int, int, int, int) Unexecuted instantiation: SkSL::RP::Generator::pushMatrixMultiply(SkSL::RP::LValue*, SkSL::Expression const&, SkSL::Expression const&, int, int, int, int) |
2327 | | |
2328 | 39 | void Generator::foldComparisonOp(Operator op, int elements) { |
2329 | 39 | switch (op.kind()) { |
2330 | 39 | case OperatorKind::EQEQ: |
2331 | | // equal(x,y) returns a vector; use & to fold into a scalar. |
2332 | 39 | this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, elements); |
2333 | 39 | break; |
2334 | | |
2335 | 0 | case OperatorKind::NEQ: |
2336 | | // notEqual(x,y) returns a vector; use | to fold into a scalar. |
2337 | 0 | this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, elements); |
2338 | 0 | break; |
2339 | | |
2340 | 0 | default: |
2341 | 0 | SkDEBUGFAIL("comparison only allows == and !="); |
2342 | 0 | break; |
2343 | 39 | } |
2344 | 39 | } |
2345 | | |
2346 | | bool Generator::pushStructuredComparison(LValue* left, |
2347 | | Operator op, |
2348 | | LValue* right, |
2349 | 0 | const Type& type) { |
2350 | 0 | if (type.isStruct()) { |
2351 | | // Compare every field in the struct. |
2352 | 0 | SkSpan<const Field> fields = type.fields(); |
2353 | 0 | int currentSlot = 0; |
2354 | 0 | for (size_t index = 0; index < fields.size(); ++index) { |
2355 | 0 | const Type& fieldType = *fields[index].fType; |
2356 | 0 | const int fieldSlotCount = fieldType.slotCount(); |
2357 | 0 | UnownedLValueSlice fieldLeft {left, currentSlot, fieldSlotCount}; |
2358 | 0 | UnownedLValueSlice fieldRight{right, currentSlot, fieldSlotCount}; |
2359 | 0 | if (!this->pushStructuredComparison(&fieldLeft, op, &fieldRight, fieldType)) { |
2360 | 0 | return unsupported(); |
2361 | 0 | } |
2362 | 0 | currentSlot += fieldSlotCount; |
2363 | 0 | } |
2364 | | |
2365 | 0 | this->foldComparisonOp(op, fields.size()); |
2366 | 0 | return true; |
2367 | 0 | } |
2368 | | |
2369 | 0 | if (type.isArray()) { |
2370 | 0 | const Type& indexedType = type.componentType(); |
2371 | 0 | if (indexedType.numberKind() == Type::NumberKind::kNonnumeric) { |
2372 | | // Compare every element in the array. |
2373 | 0 | const int indexedSlotCount = indexedType.slotCount(); |
2374 | 0 | int currentSlot = 0; |
2375 | 0 | for (int index = 0; index < type.columns(); ++index) { |
2376 | 0 | UnownedLValueSlice indexedLeft {left, currentSlot, indexedSlotCount}; |
2377 | 0 | UnownedLValueSlice indexedRight{right, currentSlot, indexedSlotCount}; |
2378 | 0 | if (!this->pushStructuredComparison(&indexedLeft, op, &indexedRight, indexedType)) { |
2379 | 0 | return unsupported(); |
2380 | 0 | } |
2381 | 0 | currentSlot += indexedSlotCount; |
2382 | 0 | } |
2383 | | |
2384 | 0 | this->foldComparisonOp(op, type.columns()); |
2385 | 0 | return true; |
2386 | 0 | } |
2387 | 0 | } |
2388 | | |
2389 | | // We've winnowed down to a single element, or an array of homogeneous numeric elements. |
2390 | | // Push the elements onto the stack, then compare them. |
2391 | 0 | if (!this->push(*left) || !this->push(*right)) { |
2392 | 0 | return unsupported(); |
2393 | 0 | } |
2394 | 0 | switch (op.kind()) { |
2395 | 0 | case OperatorKind::EQEQ: |
2396 | 0 | if (!this->binaryOp(type, kEqualOps)) { |
2397 | 0 | return unsupported(); |
2398 | 0 | } |
2399 | 0 | break; |
2400 | | |
2401 | 0 | case OperatorKind::NEQ: |
2402 | 0 | if (!this->binaryOp(type, kNotEqualOps)) { |
2403 | 0 | return unsupported(); |
2404 | 0 | } |
2405 | 0 | break; |
2406 | | |
2407 | 0 | default: |
2408 | 0 | SkDEBUGFAIL("comparison only allows == and !="); |
2409 | 0 | break; |
2410 | 0 | } |
2411 | | |
2412 | 0 | this->foldComparisonOp(op, type.slotCount()); |
2413 | 0 | return true; |
2414 | 0 | } Unexecuted instantiation: SkSL::RP::Generator::pushStructuredComparison(SkSL::RP::LValue*, SkSL::Operator, SkSL::RP::LValue*, SkSL::Type const&) Unexecuted instantiation: SkSL::RP::Generator::pushStructuredComparison(SkSL::RP::LValue*, SkSL::Operator, SkSL::RP::LValue*, SkSL::Type const&) |
2415 | | |
2416 | 621 | bool Generator::pushBinaryExpression(const BinaryExpression& e) { |
2417 | 621 | return this->pushBinaryExpression(*e.left(), e.getOperator(), *e.right()); |
2418 | 621 | } |
2419 | | |
2420 | 680 | bool Generator::pushBinaryExpression(const Expression& left, Operator op, const Expression& right) { |
2421 | 680 | switch (op.kind()) { |
2422 | | // Rewrite greater-than ops as their less-than equivalents. |
2423 | 12 | case OperatorKind::GT: |
2424 | 12 | return this->pushBinaryExpression(right, OperatorKind::LT, left); |
2425 | | |
2426 | 26 | case OperatorKind::GTEQ: |
2427 | 26 | return this->pushBinaryExpression(right, OperatorKind::LTEQ, left); |
2428 | | |
2429 | | // Handle struct and array comparisons. |
2430 | 39 | case OperatorKind::EQEQ: |
2431 | 39 | case OperatorKind::NEQ: |
2432 | 39 | if (left.type().isStruct() || left.type().isArray()) { |
2433 | 0 | SkASSERT(left.type().matches(right.type())); |
2434 | 0 | std::unique_ptr<LValue> lvLeft = this->makeLValue(left, /*allowScratch=*/true); |
2435 | 0 | std::unique_ptr<LValue> lvRight = this->makeLValue(right, /*allowScratch=*/true); |
2436 | 0 | return this->pushStructuredComparison(lvLeft.get(), op, lvRight.get(), left.type()); |
2437 | 0 | } |
2438 | 39 | [[fallthrough]]; |
2439 | | |
2440 | | // Rewrite commutative ops so that the literal is on the right-hand side. This gives the |
2441 | | // Builder more opportunities to use immediate-mode ops. |
2442 | 146 | case OperatorKind::PLUS: |
2443 | 313 | case OperatorKind::STAR: |
2444 | 313 | case OperatorKind::BITWISEAND: |
2445 | 313 | case OperatorKind::BITWISEXOR: |
2446 | 313 | case OperatorKind::LOGICALXOR: { |
2447 | 313 | double unused; |
2448 | 313 | if (ConstantFolder::GetConstantValue(left, &unused) && |
2449 | 313 | !ConstantFolder::GetConstantValue(right, &unused)) { |
2450 | 8 | return this->pushBinaryExpression(right, op, left); |
2451 | 8 | } |
2452 | 305 | break; |
2453 | 313 | } |
2454 | | // Emit comma expressions. |
2455 | 305 | case OperatorKind::COMMA: |
2456 | 0 | if (Analysis::HasSideEffects(left)) { |
2457 | 0 | if (!this->pushExpression(left, /*usesResult=*/false)) { |
2458 | 0 | return unsupported(); |
2459 | 0 | } |
2460 | 0 | this->discardExpression(left.type().slotCount()); |
2461 | 0 | } |
2462 | 0 | return this->pushExpression(right); |
2463 | | |
2464 | 329 | default: |
2465 | 329 | break; |
2466 | 680 | } |
2467 | | |
2468 | | // Handle binary expressions with mismatched types. |
2469 | 634 | bool vectorizeLeft = false, vectorizeRight = false; |
2470 | 634 | if (!left.type().matches(right.type())) { |
2471 | 135 | if (left.type().componentType().numberKind() != right.type().componentType().numberKind()) { |
2472 | 0 | return unsupported(); |
2473 | 0 | } |
2474 | 135 | if (left.type().isScalar() && (right.type().isVector() || right.type().isMatrix())) { |
2475 | 68 | vectorizeLeft = true; |
2476 | 68 | } else if ((left.type().isVector() || left.type().isMatrix()) && right.type().isScalar()) { |
2477 | 67 | vectorizeRight = true; |
2478 | 67 | } |
2479 | 135 | } |
2480 | | |
2481 | 634 | const Type& type = vectorizeLeft ? right.type() : left.type(); |
2482 | | |
2483 | | // If this is an assignment... |
2484 | 634 | std::unique_ptr<LValue> lvalue; |
2485 | 634 | if (op.isAssignment()) { |
2486 | | // ... turn the left side into an lvalue. |
2487 | 130 | lvalue = this->makeLValue(left); |
2488 | 130 | if (!lvalue) { |
2489 | 0 | return unsupported(); |
2490 | 0 | } |
2491 | | |
2492 | | // Handle simple assignment (`var = expr`). |
2493 | 130 | if (op.kind() == OperatorKind::EQ) { |
2494 | 98 | return this->pushExpression(right) && |
2495 | 98 | this->store(*lvalue); |
2496 | 98 | } |
2497 | | |
2498 | | // Strip off the assignment from the op (turning += into +). |
2499 | 32 | op = op.removeAssignment(); |
2500 | 32 | } |
2501 | | |
2502 | | // Handle matrix multiplication (MxM/MxV/VxM). |
2503 | 536 | if (op.kind() == OperatorKind::STAR) { |
2504 | | // Matrix * matrix: |
2505 | 163 | if (left.type().isMatrix() && right.type().isMatrix()) { |
2506 | 0 | return this->pushMatrixMultiply(lvalue.get(), left, right, |
2507 | 0 | left.type().columns(), left.type().rows(), |
2508 | 0 | right.type().columns(), right.type().rows()); |
2509 | 0 | } |
2510 | | |
2511 | | // Vector * matrix: |
2512 | 163 | if (left.type().isVector() && right.type().isMatrix()) { |
2513 | 0 | return this->pushMatrixMultiply(lvalue.get(), left, right, |
2514 | 0 | left.type().columns(), 1, |
2515 | 0 | right.type().columns(), right.type().rows()); |
2516 | 0 | } |
2517 | | |
2518 | | // Matrix * vector: |
2519 | 163 | if (left.type().isMatrix() && right.type().isVector()) { |
2520 | 0 | return this->pushMatrixMultiply(lvalue.get(), left, right, |
2521 | 0 | left.type().columns(), left.type().rows(), |
2522 | 0 | 1, right.type().columns()); |
2523 | 0 | } |
2524 | 163 | } |
2525 | | |
2526 | 536 | if (!vectorizeLeft && !vectorizeRight && !type.matches(right.type())) { |
2527 | | // We have mismatched types but don't know how to handle them. |
2528 | 0 | return unsupported(); |
2529 | 0 | } |
2530 | | |
2531 | | // Handle binary ops which require short-circuiting. |
2532 | 536 | switch (op.kind()) { |
2533 | 8 | case OperatorKind::LOGICALAND: |
2534 | 8 | if (Analysis::HasSideEffects(right)) { |
2535 | | // If the RHS has side effects, we rewrite `a && b` as `a ? b : false`. This |
2536 | | // generates pretty solid code and gives us the required short-circuit behavior. |
2537 | 0 | SkASSERT(!op.isAssignment()); |
2538 | 0 | SkASSERT(type.componentType().isBoolean()); |
2539 | 0 | SkASSERT(type.slotCount() == 1); // operator&& only works with scalar types |
2540 | 0 | Literal falseLiteral{Position{}, 0.0, &right.type()}; |
2541 | 0 | return this->pushTernaryExpression(left, right, falseLiteral); |
2542 | 0 | } |
2543 | 8 | break; |
2544 | | |
2545 | 8 | case OperatorKind::LOGICALOR: |
2546 | 0 | if (Analysis::HasSideEffects(right)) { |
2547 | | // If the RHS has side effects, we rewrite `a || b` as `a ? true : b`. |
2548 | 0 | SkASSERT(!op.isAssignment()); |
2549 | 0 | SkASSERT(type.componentType().isBoolean()); |
2550 | 0 | SkASSERT(type.slotCount() == 1); // operator|| only works with scalar types |
2551 | 0 | Literal trueLiteral{Position{}, 1.0, &right.type()}; |
2552 | 0 | return this->pushTernaryExpression(left, trueLiteral, right); |
2553 | 0 | } |
2554 | 0 | break; |
2555 | | |
2556 | 528 | default: |
2557 | 528 | break; |
2558 | 536 | } |
2559 | | |
2560 | | // Push the left- and right-expressions onto the stack. |
2561 | 536 | if (!this->pushLValueOrExpression(lvalue.get(), left)) { |
2562 | 0 | return unsupported(); |
2563 | 0 | } |
2564 | 536 | if (vectorizeLeft) { |
2565 | 68 | fBuilder.push_duplicates(right.type().slotCount() - 1); |
2566 | 68 | } |
2567 | 536 | if (!this->pushExpression(right)) { |
2568 | 0 | return unsupported(); |
2569 | 0 | } |
2570 | 536 | if (vectorizeRight) { |
2571 | 67 | fBuilder.push_duplicates(left.type().slotCount() - 1); |
2572 | 67 | } |
2573 | | |
2574 | 536 | switch (op.kind()) { |
2575 | 135 | case OperatorKind::PLUS: |
2576 | 135 | if (!this->binaryOp(type, kAddOps)) { |
2577 | 0 | return unsupported(); |
2578 | 0 | } |
2579 | 135 | break; |
2580 | | |
2581 | 135 | case OperatorKind::MINUS: |
2582 | 100 | if (!this->binaryOp(type, kSubtractOps)) { |
2583 | 0 | return unsupported(); |
2584 | 0 | } |
2585 | 100 | break; |
2586 | | |
2587 | 163 | case OperatorKind::STAR: |
2588 | 163 | if (!this->binaryOp(type, kMultiplyOps)) { |
2589 | 0 | return unsupported(); |
2590 | 0 | } |
2591 | 163 | break; |
2592 | | |
2593 | 163 | case OperatorKind::SLASH: |
2594 | 16 | if (!this->binaryOp(type, kDivideOps)) { |
2595 | 0 | return unsupported(); |
2596 | 0 | } |
2597 | 16 | break; |
2598 | | |
2599 | 45 | case OperatorKind::LT: |
2600 | 45 | case OperatorKind::GT: |
2601 | 45 | if (!this->binaryOp(type, kLessThanOps)) { |
2602 | 0 | return unsupported(); |
2603 | 0 | } |
2604 | 45 | SkASSERT(type.slotCount() == 1); // operator< only works with scalar types |
2605 | 45 | break; |
2606 | | |
2607 | 30 | case OperatorKind::LTEQ: |
2608 | 30 | case OperatorKind::GTEQ: |
2609 | 30 | if (!this->binaryOp(type, kLessThanEqualOps)) { |
2610 | 0 | return unsupported(); |
2611 | 0 | } |
2612 | 30 | SkASSERT(type.slotCount() == 1); // operator<= only works with scalar types |
2613 | 30 | break; |
2614 | | |
2615 | 39 | case OperatorKind::EQEQ: |
2616 | 39 | if (!this->binaryOp(type, kEqualOps)) { |
2617 | 0 | return unsupported(); |
2618 | 0 | } |
2619 | 39 | this->foldComparisonOp(op, type.slotCount()); |
2620 | 39 | break; |
2621 | | |
2622 | 0 | case OperatorKind::NEQ: |
2623 | 0 | if (!this->binaryOp(type, kNotEqualOps)) { |
2624 | 0 | return unsupported(); |
2625 | 0 | } |
2626 | 0 | this->foldComparisonOp(op, type.slotCount()); |
2627 | 0 | break; |
2628 | | |
2629 | 8 | case OperatorKind::LOGICALAND: |
2630 | 8 | case OperatorKind::BITWISEAND: |
2631 | | // For logical-and, we verified above that the RHS does not have side effects, so we |
2632 | | // don't need to worry about short-circuiting side effects. |
2633 | 8 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, type.slotCount()); |
2634 | 8 | break; |
2635 | | |
2636 | 0 | case OperatorKind::LOGICALOR: |
2637 | 0 | case OperatorKind::BITWISEOR: |
2638 | | // For logical-or, we verified above that the RHS does not have side effects. |
2639 | 0 | fBuilder.binary_op(BuilderOp::bitwise_or_n_ints, type.slotCount()); |
2640 | 0 | break; |
2641 | | |
2642 | 0 | case OperatorKind::LOGICALXOR: |
2643 | 0 | case OperatorKind::BITWISEXOR: |
2644 | | // Logical-xor does not short circuit. |
2645 | 0 | fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, type.slotCount()); |
2646 | 0 | break; |
2647 | | |
2648 | 0 | default: |
2649 | 0 | return unsupported(); |
2650 | 536 | } |
2651 | | |
2652 | | // If we have an lvalue, we need to write the result back into it. |
2653 | 536 | return lvalue ? this->store(*lvalue) |
2654 | 536 | : true; |
2655 | 536 | } |
2656 | | |
2657 | | std::optional<Generator::ImmutableBits> Generator::getImmutableBitsForSlot(const Expression& expr, |
2658 | 308 | size_t slot) { |
2659 | | // Determine the constant-value of the slot; bail if it isn't constant. |
2660 | 308 | std::optional<double> v = expr.getConstantValue(slot); |
2661 | 308 | if (!v.has_value()) { |
2662 | 101 | return std::nullopt; |
2663 | 101 | } |
2664 | | // Determine the number-kind of the slot, and convert the value to its bit-representation. |
2665 | 207 | Type::NumberKind kind = expr.type().slotType(slot).numberKind(); |
2666 | 207 | double value = *v; |
2667 | 207 | switch (kind) { |
2668 | 198 | case Type::NumberKind::kFloat: |
2669 | 198 | return sk_bit_cast<ImmutableBits>((float)value); |
2670 | | |
2671 | 9 | case Type::NumberKind::kSigned: |
2672 | 9 | return sk_bit_cast<ImmutableBits>((int32_t)value); |
2673 | | |
2674 | 0 | case Type::NumberKind::kUnsigned: |
2675 | 0 | return sk_bit_cast<ImmutableBits>((uint32_t)value); |
2676 | | |
2677 | 0 | case Type::NumberKind::kBoolean: |
2678 | 0 | return value ? ~0 : 0; |
2679 | | |
2680 | 0 | default: |
2681 | 0 | return std::nullopt; |
2682 | 207 | } |
2683 | 207 | } |
2684 | | |
2685 | | bool Generator::getImmutableValueForExpression(const Expression& expr, |
2686 | 306 | TArray<ImmutableBits>* immutableValues) { |
2687 | 306 | if (!expr.supportsConstantValues()) { |
2688 | 123 | return false; |
2689 | 123 | } |
2690 | 183 | size_t numSlots = expr.type().slotCount(); |
2691 | 183 | immutableValues->reserve_exact(numSlots); |
2692 | 390 | for (size_t index = 0; index < numSlots; ++index) { |
2693 | 308 | std::optional<ImmutableBits> bits = this->getImmutableBitsForSlot(expr, index); |
2694 | 308 | if (!bits.has_value()) { |
2695 | 101 | return false; |
2696 | 101 | } |
2697 | 207 | immutableValues->push_back(*bits); |
2698 | 207 | } |
2699 | 82 | return true; |
2700 | 183 | } |
2701 | | |
2702 | | void Generator::storeImmutableValueToSlots(const TArray<ImmutableBits>& immutableValues, |
2703 | 58 | SlotRange slots) { |
2704 | 186 | for (int index = 0; index < slots.count; ++index) { |
2705 | | // Store the immutable value in its slot. |
2706 | 128 | const Slot slot = slots.index++; |
2707 | 128 | const ImmutableBits bits = immutableValues[index]; |
2708 | 128 | fBuilder.store_immutable_value_i(slot, bits); |
2709 | | |
2710 | | // Keep track of every stored immutable value for potential later reuse. |
2711 | 128 | fImmutableSlotMap[bits].add(slot); |
2712 | 128 | } |
2713 | 58 | } |
2714 | | |
2715 | | std::optional<SlotRange> Generator::findPreexistingImmutableData( |
2716 | 82 | const TArray<ImmutableBits>& immutableValues) { |
2717 | 82 | STArray<16, const THashSet<Slot>*> slotArray; |
2718 | 82 | slotArray.reserve_exact(immutableValues.size()); |
2719 | | |
2720 | | // Find all the slots associated with each immutable-value bit representation. |
2721 | | // If a given bit-pattern doesn't exist anywhere in our program yet, we can stop searching. |
2722 | 134 | for (const ImmutableBits& immutableValue : immutableValues) { |
2723 | 134 | const THashSet<Slot>* slotsForValue = fImmutableSlotMap.find(immutableValue); |
2724 | 134 | if (!slotsForValue) { |
2725 | 50 | return std::nullopt; |
2726 | 50 | } |
2727 | 84 | slotArray.push_back(slotsForValue); |
2728 | 84 | } |
2729 | | |
2730 | | // Look for the group with the fewest number of entries, since that can be searched in the |
2731 | | // least amount of effort. |
2732 | 32 | int leastSlotIndex = 0, leastSlotCount = INT_MAX; |
2733 | 112 | for (int index = 0; index < slotArray.size(); ++index) { |
2734 | 80 | int currentCount = slotArray[index]->count(); |
2735 | 80 | if (currentCount < leastSlotCount) { |
2736 | 52 | leastSlotIndex = index; |
2737 | 52 | leastSlotCount = currentCount; |
2738 | 52 | } |
2739 | 80 | } |
2740 | | |
2741 | | // See if we can reconstitute the value that we want with any of the data we've already got. |
2742 | 36 | for (int slot : *slotArray[leastSlotIndex]) { |
2743 | 36 | int firstSlot = slot - leastSlotIndex; |
2744 | 36 | bool found = true; |
2745 | 112 | for (int index = 0; index < slotArray.size(); ++index) { |
2746 | 88 | if (!slotArray[index]->contains(firstSlot + index)) { |
2747 | 12 | found = false; |
2748 | 12 | break; |
2749 | 12 | } |
2750 | 88 | } |
2751 | 36 | if (found) { |
2752 | | // We've found an exact match for the input value; return its slot-range. |
2753 | 24 | return SlotRange{firstSlot, slotArray.size()}; |
2754 | 24 | } |
2755 | 36 | } |
2756 | | |
2757 | | // We didn't find any reusable slot ranges. |
2758 | 8 | return std::nullopt; |
2759 | 32 | } |
2760 | | |
2761 | 135 | bool Generator::pushImmutableData(const Expression& e) { |
2762 | 135 | STArray<16, ImmutableBits> immutableValues; |
2763 | 135 | if (!this->getImmutableValueForExpression(e, &immutableValues)) { |
2764 | 74 | return false; |
2765 | 74 | } |
2766 | 61 | std::optional<SlotRange> preexistingData = this->findPreexistingImmutableData(immutableValues); |
2767 | 61 | if (preexistingData.has_value()) { |
2768 | 24 | fBuilder.push_immutable(*preexistingData); |
2769 | 24 | return true; |
2770 | 24 | } |
2771 | 37 | SlotRange range = fImmutableSlots.createSlots(e.description(), |
2772 | 37 | e.type(), |
2773 | 37 | e.fPosition, |
2774 | 37 | /*isFunctionReturnValue=*/false); |
2775 | 37 | this->storeImmutableValueToSlots(immutableValues, range); |
2776 | 37 | fBuilder.push_immutable(range); |
2777 | 37 | return true; |
2778 | 61 | } |
2779 | | |
2780 | 135 | bool Generator::pushConstructorCompound(const AnyConstructor& c) { |
2781 | 135 | if (c.type().slotCount() > 1 && this->pushImmutableData(c)) { |
2782 | 61 | return true; |
2783 | 61 | } |
2784 | 182 | for (const std::unique_ptr<Expression> &arg : c.argumentSpan()) { |
2785 | 182 | if (!this->pushExpression(*arg)) { |
2786 | 0 | return unsupported(); |
2787 | 0 | } |
2788 | 182 | } |
2789 | 74 | return true; |
2790 | 74 | } |
2791 | | |
2792 | 94 | bool Generator::pushChildCall(const ChildCall& c) { |
2793 | 94 | int* childIdx = fChildEffectMap.find(&c.child()); |
2794 | 94 | SkASSERT(childIdx != nullptr); |
2795 | 94 | SkASSERT(!c.arguments().empty()); |
2796 | | |
2797 | | // All child calls have at least one argument. |
2798 | 94 | const Expression* arg = c.arguments()[0].get(); |
2799 | 94 | if (!this->pushExpression(*arg)) { |
2800 | 0 | return unsupported(); |
2801 | 0 | } |
2802 | | |
2803 | | // Copy arguments from the stack into src/dst as required by this particular child-call. |
2804 | 94 | switch (c.child().type().typeKind()) { |
2805 | 88 | case Type::TypeKind::kShader: { |
2806 | | // The argument must be a float2. |
2807 | 88 | SkASSERT(c.arguments().size() == 1); |
2808 | 88 | SkASSERT(arg->type().matches(*fContext.fTypes.fFloat2)); |
2809 | | |
2810 | | // `exchange_src` will use the top four values on the stack, but we don't care what goes |
2811 | | // into the blue/alpha components. We inject padding here to balance the stack. |
2812 | 88 | fBuilder.pad_stack(2); |
2813 | | |
2814 | | // Move the argument into src.rgba while also preserving the execution mask. |
2815 | 88 | fBuilder.exchange_src(); |
2816 | 88 | fBuilder.invoke_shader(*childIdx); |
2817 | 88 | break; |
2818 | 0 | } |
2819 | 2 | case Type::TypeKind::kColorFilter: { |
2820 | | // The argument must be a half4/float4. |
2821 | 2 | SkASSERT(c.arguments().size() == 1); |
2822 | 2 | SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) || |
2823 | 2 | arg->type().matches(*fContext.fTypes.fFloat4)); |
2824 | | |
2825 | | // Move the argument into src.rgba while also preserving the execution mask. |
2826 | 2 | fBuilder.exchange_src(); |
2827 | 2 | fBuilder.invoke_color_filter(*childIdx); |
2828 | 2 | break; |
2829 | 0 | } |
2830 | 4 | case Type::TypeKind::kBlender: { |
2831 | | // Both arguments must be half4/float4. |
2832 | 4 | SkASSERT(c.arguments().size() == 2); |
2833 | 4 | SkASSERT(c.arguments()[0]->type().matches(*fContext.fTypes.fHalf4) || |
2834 | 4 | c.arguments()[0]->type().matches(*fContext.fTypes.fFloat4)); |
2835 | 4 | SkASSERT(c.arguments()[1]->type().matches(*fContext.fTypes.fHalf4) || |
2836 | 4 | c.arguments()[1]->type().matches(*fContext.fTypes.fFloat4)); |
2837 | | |
2838 | | // Move the second argument into dst.rgba, and the first argument into src.rgba, while |
2839 | | // simultaneously preserving the execution mask. |
2840 | 4 | if (!this->pushExpression(*c.arguments()[1])) { |
2841 | 0 | return unsupported(); |
2842 | 0 | } |
2843 | 4 | fBuilder.pop_dst_rgba(); |
2844 | 4 | fBuilder.exchange_src(); |
2845 | 4 | fBuilder.invoke_blender(*childIdx); |
2846 | 4 | break; |
2847 | 4 | } |
2848 | 0 | default: { |
2849 | 0 | SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str()); |
2850 | 0 | } |
2851 | 94 | } |
2852 | | |
2853 | | // The child call has returned the result color via src.rgba, and the SkRP execution mask is |
2854 | | // on top of the stack. Swapping the two puts the result color on top of the stack, and also |
2855 | | // restores our execution masks. |
2856 | 94 | fBuilder.exchange_src(); |
2857 | 94 | return true; |
2858 | 94 | } |
2859 | | |
2860 | 57 | bool Generator::pushConstructorCast(const AnyConstructor& c) { |
2861 | 57 | SkASSERT(c.argumentSpan().size() == 1); |
2862 | 57 | const Expression& inner = *c.argumentSpan().front(); |
2863 | 57 | SkASSERT(inner.type().slotCount() == c.type().slotCount()); |
2864 | | |
2865 | 57 | if (!this->pushExpression(inner)) { |
2866 | 0 | return unsupported(); |
2867 | 0 | } |
2868 | 57 | const Type::NumberKind innerKind = inner.type().componentType().numberKind(); |
2869 | 57 | const Type::NumberKind outerKind = c.type().componentType().numberKind(); |
2870 | | |
2871 | 57 | if (innerKind == outerKind) { |
2872 | | // Since we ignore type precision, this cast is effectively a no-op. |
2873 | 42 | return true; |
2874 | 42 | } |
2875 | | |
2876 | 15 | switch (innerKind) { |
2877 | 15 | case Type::NumberKind::kSigned: |
2878 | 15 | if (outerKind == Type::NumberKind::kUnsigned) { |
2879 | | // Treat uint(int) as a no-op. |
2880 | 0 | return true; |
2881 | 0 | } |
2882 | 15 | if (outerKind == Type::NumberKind::kFloat) { |
2883 | 15 | fBuilder.unary_op(BuilderOp::cast_to_float_from_int, c.type().slotCount()); |
2884 | 15 | return true; |
2885 | 15 | } |
2886 | 0 | break; |
2887 | | |
2888 | 0 | case Type::NumberKind::kUnsigned: |
2889 | 0 | if (outerKind == Type::NumberKind::kSigned) { |
2890 | | // Treat int(uint) as a no-op. |
2891 | 0 | return true; |
2892 | 0 | } |
2893 | 0 | if (outerKind == Type::NumberKind::kFloat) { |
2894 | 0 | fBuilder.unary_op(BuilderOp::cast_to_float_from_uint, c.type().slotCount()); |
2895 | 0 | return true; |
2896 | 0 | } |
2897 | 0 | break; |
2898 | | |
2899 | 0 | case Type::NumberKind::kBoolean: |
2900 | | // Converting boolean to int or float can be accomplished via bitwise-and. |
2901 | 0 | if (outerKind == Type::NumberKind::kFloat) { |
2902 | 0 | fBuilder.push_constant_f(1.0f); |
2903 | 0 | } else if (outerKind == Type::NumberKind::kSigned || |
2904 | 0 | outerKind == Type::NumberKind::kUnsigned) { |
2905 | 0 | fBuilder.push_constant_i(1); |
2906 | 0 | } else { |
2907 | 0 | SkDEBUGFAILF("unexpected cast from bool to %s", c.type().description().c_str()); |
2908 | 0 | return unsupported(); |
2909 | 0 | } |
2910 | 0 | fBuilder.push_duplicates(c.type().slotCount() - 1); |
2911 | 0 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, c.type().slotCount()); |
2912 | 0 | return true; |
2913 | | |
2914 | 0 | case Type::NumberKind::kFloat: |
2915 | 0 | if (outerKind == Type::NumberKind::kSigned) { |
2916 | 0 | fBuilder.unary_op(BuilderOp::cast_to_int_from_float, c.type().slotCount()); |
2917 | 0 | return true; |
2918 | 0 | } |
2919 | 0 | if (outerKind == Type::NumberKind::kUnsigned) { |
2920 | 0 | fBuilder.unary_op(BuilderOp::cast_to_uint_from_float, c.type().slotCount()); |
2921 | 0 | return true; |
2922 | 0 | } |
2923 | 0 | break; |
2924 | | |
2925 | 0 | case Type::NumberKind::kNonnumeric: |
2926 | 0 | break; |
2927 | 15 | } |
2928 | | |
2929 | 0 | if (outerKind == Type::NumberKind::kBoolean) { |
2930 | | // Converting int or float to boolean can be accomplished via `notEqual(x, 0)`. |
2931 | 0 | fBuilder.push_zeros(c.type().slotCount()); |
2932 | 0 | return this->binaryOp(inner.type(), kNotEqualOps); |
2933 | 0 | } |
2934 | | |
2935 | 0 | SkDEBUGFAILF("unexpected cast from %s to %s", |
2936 | 0 | c.type().description().c_str(), inner.type().description().c_str()); |
2937 | 0 | return unsupported(); |
2938 | 0 | } |
2939 | | |
2940 | 0 | bool Generator::pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c) { |
2941 | 0 | if (this->pushImmutableData(c)) { |
2942 | 0 | return true; |
2943 | 0 | } |
2944 | 0 | fBuilder.push_zeros(1); |
2945 | 0 | if (!this->pushExpression(*c.argument())) { |
2946 | 0 | return unsupported(); |
2947 | 0 | } |
2948 | 0 | fBuilder.diagonal_matrix(c.type().columns(), c.type().rows()); |
2949 | |
|
2950 | 0 | return true; |
2951 | 0 | } |
2952 | | |
2953 | 0 | bool Generator::pushConstructorMatrixResize(const ConstructorMatrixResize& c) { |
2954 | 0 | if (!this->pushExpression(*c.argument())) { |
2955 | 0 | return unsupported(); |
2956 | 0 | } |
2957 | 0 | fBuilder.matrix_resize(c.argument()->type().columns(), |
2958 | 0 | c.argument()->type().rows(), |
2959 | 0 | c.type().columns(), |
2960 | 0 | c.type().rows()); |
2961 | 0 | return true; |
2962 | 0 | } |
2963 | | |
2964 | 26 | bool Generator::pushConstructorSplat(const ConstructorSplat& c) { |
2965 | 26 | if (!this->pushExpression(*c.argument())) { |
2966 | 0 | return unsupported(); |
2967 | 0 | } |
2968 | 26 | fBuilder.push_duplicates(c.type().slotCount() - 1); |
2969 | 26 | return true; |
2970 | 26 | } |
2971 | | |
2972 | 0 | bool Generator::pushFieldAccess(const FieldAccess& f) { |
2973 | | // If possible, get direct field access via the lvalue. |
2974 | 0 | std::unique_ptr<LValue> lvalue = this->makeLValue(f, /*allowScratch=*/true); |
2975 | 0 | return lvalue && this->push(*lvalue); |
2976 | 0 | } |
2977 | | |
2978 | 231 | bool Generator::pushFunctionCall(const FunctionCall& c) { |
2979 | 231 | if (c.function().isIntrinsic()) { |
2980 | 227 | return this->pushIntrinsic(c); |
2981 | 227 | } |
2982 | | |
2983 | | // Keep track of the current function. |
2984 | 4 | const FunctionDefinition* lastFunction = fCurrentFunction; |
2985 | 4 | fCurrentFunction = c.function().definition(); |
2986 | | |
2987 | | // Skip over the function body entirely if there are no active lanes. |
2988 | | // (If the function call was trivial, it would likely have been inlined in the frontend, so we |
2989 | | // assume here that function calls generally represent a significant amount of work.) |
2990 | 4 | int skipLabelID = fBuilder.nextLabelID(); |
2991 | 4 | fBuilder.branch_if_no_lanes_active(skipLabelID); |
2992 | | |
2993 | | // Emit the function body. |
2994 | 4 | std::optional<SlotRange> r = this->writeFunction(c, *fCurrentFunction, c.arguments()); |
2995 | 4 | if (!r.has_value()) { |
2996 | 0 | return unsupported(); |
2997 | 0 | } |
2998 | | |
2999 | | // If the function uses result slots, move its result from slots onto the stack. |
3000 | 4 | if (this->needsFunctionResultSlots(fCurrentFunction)) { |
3001 | 4 | fBuilder.push_slots(*r); |
3002 | 4 | } |
3003 | | |
3004 | | // We've returned back to the last function. |
3005 | 4 | fCurrentFunction = lastFunction; |
3006 | | |
3007 | | // Copy the function result from its slots onto the stack. |
3008 | 4 | fBuilder.label(skipLabelID); |
3009 | 4 | return true; |
3010 | 4 | } |
3011 | | |
3012 | 8 | bool Generator::pushIndexExpression(const IndexExpression& i) { |
3013 | 8 | std::unique_ptr<LValue> lvalue = this->makeLValue(i, /*allowScratch=*/true); |
3014 | 8 | return lvalue && this->push(*lvalue); |
3015 | 8 | } |
3016 | | |
3017 | 227 | bool Generator::pushIntrinsic(const FunctionCall& c) { |
3018 | 227 | const ExpressionArray& args = c.arguments(); |
3019 | 227 | switch (args.size()) { |
3020 | 66 | case 1: |
3021 | 66 | return this->pushIntrinsic(c.function().intrinsicKind(), *args[0]); |
3022 | | |
3023 | 111 | case 2: |
3024 | 111 | return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1]); |
3025 | | |
3026 | 50 | case 3: |
3027 | 50 | return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1], *args[2]); |
3028 | | |
3029 | 0 | default: |
3030 | 0 | break; |
3031 | 227 | } |
3032 | | |
3033 | 0 | return unsupported(); |
3034 | 227 | } |
3035 | | |
3036 | 4 | bool Generator::pushLengthIntrinsic(int slotCount) { |
3037 | 4 | if (slotCount == 1) { |
3038 | | // `length(scalar)` is `sqrt(x^2)`, which is equivalent to `abs(x)`. |
3039 | 0 | return this->pushAbsFloatIntrinsic(/*slots=*/1); |
3040 | 0 | } |
3041 | | // Implement `length(vec)` as `sqrt(dot(x, x))`. |
3042 | 4 | fBuilder.push_clone(slotCount); |
3043 | 4 | fBuilder.dot_floats(slotCount); |
3044 | 4 | fBuilder.unary_op(BuilderOp::sqrt_float, 1); |
3045 | 4 | return true; |
3046 | 4 | } |
3047 | | |
3048 | 8 | bool Generator::pushAbsFloatIntrinsic(int slots) { |
3049 | | // Perform abs(float) by masking off the sign bit. |
3050 | 8 | fBuilder.push_constant_u(0x7FFFFFFF, slots); |
3051 | 8 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, slots); |
3052 | 8 | return true; |
3053 | 8 | } |
3054 | | |
3055 | 247 | bool Generator::pushVectorizedExpression(const Expression& expr, const Type& vectorType) { |
3056 | 247 | if (!this->pushExpression(expr)) { |
3057 | 0 | return unsupported(); |
3058 | 0 | } |
3059 | 247 | if (vectorType.slotCount() > expr.type().slotCount()) { |
3060 | 71 | SkASSERT(expr.type().slotCount() == 1); |
3061 | 71 | fBuilder.push_duplicates(vectorType.slotCount() - expr.type().slotCount()); |
3062 | 71 | } |
3063 | 247 | return true; |
3064 | 247 | } |
3065 | | |
3066 | 0 | bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0) { |
3067 | 0 | if (!this->pushExpression(arg0)) { |
3068 | 0 | return unsupported(); |
3069 | 0 | } |
3070 | 0 | return this->unaryOp(arg0.type(), ops); |
3071 | 0 | } |
3072 | | |
3073 | 0 | bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0) { |
3074 | 0 | if (!this->pushExpression(arg0)) { |
3075 | 0 | return unsupported(); |
3076 | 0 | } |
3077 | 0 | fBuilder.unary_op(builderOp, arg0.type().slotCount()); |
3078 | 0 | return true; |
3079 | 0 | } |
3080 | | |
3081 | 66 | bool Generator::pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0) { |
3082 | 66 | switch (intrinsic) { |
3083 | 8 | case IntrinsicKind::k_abs_IntrinsicKind: |
3084 | 8 | if (arg0.type().componentType().isFloat()) { |
3085 | | // Perform abs(float) by masking off the sign bit. |
3086 | 8 | if (!this->pushExpression(arg0)) { |
3087 | 0 | return unsupported(); |
3088 | 0 | } |
3089 | 8 | return this->pushAbsFloatIntrinsic(arg0.type().slotCount()); |
3090 | 8 | } |
3091 | | // We have a dedicated op for abs(int). |
3092 | 0 | return this->pushIntrinsic(BuilderOp::abs_int, arg0); |
3093 | | |
3094 | 0 | case IntrinsicKind::k_any_IntrinsicKind: |
3095 | 0 | if (!this->pushExpression(arg0)) { |
3096 | 0 | return unsupported(); |
3097 | 0 | } |
3098 | 0 | this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, arg0.type().slotCount()); |
3099 | 0 | return true; |
3100 | | |
3101 | 0 | case IntrinsicKind::k_all_IntrinsicKind: |
3102 | 0 | if (!this->pushExpression(arg0)) { |
3103 | 0 | return unsupported(); |
3104 | 0 | } |
3105 | 0 | this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, arg0.type().slotCount()); |
3106 | 0 | return true; |
3107 | | |
3108 | 0 | case IntrinsicKind::k_acos_IntrinsicKind: |
3109 | 0 | return this->pushIntrinsic(BuilderOp::acos_float, arg0); |
3110 | | |
3111 | 0 | case IntrinsicKind::k_asin_IntrinsicKind: |
3112 | 0 | return this->pushIntrinsic(BuilderOp::asin_float, arg0); |
3113 | | |
3114 | 0 | case IntrinsicKind::k_atan_IntrinsicKind: |
3115 | 0 | return this->pushIntrinsic(BuilderOp::atan_float, arg0); |
3116 | | |
3117 | 0 | case IntrinsicKind::k_ceil_IntrinsicKind: |
3118 | 0 | return this->pushIntrinsic(BuilderOp::ceil_float, arg0); |
3119 | | |
3120 | 0 | case IntrinsicKind::k_cos_IntrinsicKind: |
3121 | 0 | return this->pushIntrinsic(BuilderOp::cos_float, arg0); |
3122 | | |
3123 | 0 | case IntrinsicKind::k_degrees_IntrinsicKind: { |
3124 | 0 | Literal lit180OverPi{Position{}, 57.2957795131f, &arg0.type().componentType()}; |
3125 | 0 | return this->pushBinaryExpression(arg0, OperatorKind::STAR, lit180OverPi); |
3126 | 0 | } |
3127 | 0 | case IntrinsicKind::k_floatBitsToInt_IntrinsicKind: |
3128 | 0 | case IntrinsicKind::k_floatBitsToUint_IntrinsicKind: |
3129 | 0 | case IntrinsicKind::k_intBitsToFloat_IntrinsicKind: |
3130 | 0 | case IntrinsicKind::k_uintBitsToFloat_IntrinsicKind: |
3131 | 0 | return this->pushExpression(arg0); |
3132 | | |
3133 | 0 | case IntrinsicKind::k_exp_IntrinsicKind: |
3134 | 0 | return this->pushIntrinsic(BuilderOp::exp_float, arg0); |
3135 | | |
3136 | 0 | case IntrinsicKind::k_exp2_IntrinsicKind: |
3137 | 0 | return this->pushIntrinsic(BuilderOp::exp2_float, arg0); |
3138 | | |
3139 | 0 | case IntrinsicKind::k_floor_IntrinsicKind: |
3140 | 0 | return this->pushIntrinsic(BuilderOp::floor_float, arg0); |
3141 | | |
3142 | 4 | case IntrinsicKind::k_fract_IntrinsicKind: |
3143 | | // Implement fract as `x - floor(x)`. |
3144 | 4 | if (!this->pushExpression(arg0)) { |
3145 | 0 | return unsupported(); |
3146 | 0 | } |
3147 | 4 | fBuilder.push_clone(arg0.type().slotCount()); |
3148 | 4 | fBuilder.unary_op(BuilderOp::floor_float, arg0.type().slotCount()); |
3149 | 4 | return this->binaryOp(arg0.type(), kSubtractOps); |
3150 | | |
3151 | 0 | case IntrinsicKind::k_inverse_IntrinsicKind: |
3152 | 0 | SkASSERT(arg0.type().isMatrix()); |
3153 | 0 | SkASSERT(arg0.type().rows() == arg0.type().columns()); |
3154 | 0 | if (!this->pushExpression(arg0)) { |
3155 | 0 | return unsupported(); |
3156 | 0 | } |
3157 | 0 | fBuilder.inverse_matrix(arg0.type().rows()); |
3158 | 0 | return true; |
3159 | | |
3160 | 0 | case IntrinsicKind::k_inversesqrt_IntrinsicKind: |
3161 | 0 | return this->pushIntrinsic(kInverseSqrtOps, arg0); |
3162 | | |
3163 | 4 | case IntrinsicKind::k_length_IntrinsicKind: |
3164 | 4 | return this->pushExpression(arg0) && |
3165 | 4 | this->pushLengthIntrinsic(arg0.type().slotCount()); |
3166 | | |
3167 | 0 | case IntrinsicKind::k_log_IntrinsicKind: |
3168 | 0 | if (!this->pushExpression(arg0)) { |
3169 | 0 | return unsupported(); |
3170 | 0 | } |
3171 | 0 | fBuilder.unary_op(BuilderOp::log_float, arg0.type().slotCount()); |
3172 | 0 | return true; |
3173 | | |
3174 | 0 | case IntrinsicKind::k_log2_IntrinsicKind: |
3175 | 0 | if (!this->pushExpression(arg0)) { |
3176 | 0 | return unsupported(); |
3177 | 0 | } |
3178 | 0 | fBuilder.unary_op(BuilderOp::log2_float, arg0.type().slotCount()); |
3179 | 0 | return true; |
3180 | | |
3181 | 12 | case IntrinsicKind::k_normalize_IntrinsicKind: { |
3182 | | // Implement normalize as `x / length(x)`. First, push the expression. |
3183 | 12 | if (!this->pushExpression(arg0)) { |
3184 | 0 | return unsupported(); |
3185 | 0 | } |
3186 | 12 | int slotCount = arg0.type().slotCount(); |
3187 | 12 | if (slotCount > 1) { |
3188 | | #if defined(SK_USE_RSQRT_IN_RP_NORMALIZE) |
3189 | | // Instead of `x / sqrt(dot(x, x))`, we can get roughly the same result in less time |
3190 | | // by computing `x * invsqrt(dot(x, x))`. |
3191 | | fBuilder.push_clone(slotCount); |
3192 | | fBuilder.push_clone(slotCount); |
3193 | | fBuilder.dot_floats(slotCount); |
3194 | | |
3195 | | // Compute `vec(inversesqrt(dot(x, x)))`. |
3196 | | fBuilder.unary_op(BuilderOp::invsqrt_float, 1); |
3197 | | fBuilder.push_duplicates(slotCount - 1); |
3198 | | |
3199 | | // Return `x * vec(inversesqrt(dot(x, x)))`. |
3200 | | return this->binaryOp(arg0.type(), kMultiplyOps); |
3201 | | #else |
3202 | | // TODO: We can get roughly the same result in less time by using `invsqrt`, but |
3203 | | // that leads to more variance across architectures, which Chromium layout tests do |
3204 | | // not handle nicely. |
3205 | 12 | fBuilder.push_clone(slotCount); |
3206 | 12 | fBuilder.push_clone(slotCount); |
3207 | 12 | fBuilder.dot_floats(slotCount); |
3208 | | |
3209 | | // Compute `vec(sqrt(dot(x, x)))`. |
3210 | 12 | fBuilder.unary_op(BuilderOp::sqrt_float, 1); |
3211 | 12 | fBuilder.push_duplicates(slotCount - 1); |
3212 | | |
3213 | | // Return `x / vec(sqrt(dot(x, x)))`. |
3214 | 12 | return this->binaryOp(arg0.type(), kDivideOps); |
3215 | 12 | #endif |
3216 | 12 | } else { |
3217 | | // For single-slot normalization, we can simplify `sqrt(x * x)` into `abs(x)`. |
3218 | 0 | fBuilder.push_clone(slotCount); |
3219 | 0 | return this->pushAbsFloatIntrinsic(/*slots=*/1) && |
3220 | 0 | this->binaryOp(arg0.type(), kDivideOps); |
3221 | 0 | } |
3222 | 12 | } |
3223 | 0 | case IntrinsicKind::k_not_IntrinsicKind: |
3224 | 0 | return this->pushPrefixExpression(OperatorKind::LOGICALNOT, arg0); |
3225 | | |
3226 | 0 | case IntrinsicKind::k_radians_IntrinsicKind: { |
3227 | 0 | Literal litPiOver180{Position{}, 0.01745329251f, &arg0.type().componentType()}; |
3228 | 0 | return this->pushBinaryExpression(arg0, OperatorKind::STAR, litPiOver180); |
3229 | 12 | } |
3230 | 38 | case IntrinsicKind::k_saturate_IntrinsicKind: { |
3231 | | // Implement saturate as clamp(arg, 0, 1). |
3232 | 38 | Literal zeroLiteral{Position{}, 0.0, &arg0.type().componentType()}; |
3233 | 38 | Literal oneLiteral{Position{}, 1.0, &arg0.type().componentType()}; |
3234 | 38 | return this->pushIntrinsic(k_clamp_IntrinsicKind, arg0, zeroLiteral, oneLiteral); |
3235 | 12 | } |
3236 | 0 | case IntrinsicKind::k_sign_IntrinsicKind: { |
3237 | | // Implement floating-point sign() as `clamp(arg * FLT_MAX, -1, 1)`. |
3238 | | // FLT_MIN * FLT_MAX evaluates to 4, so multiplying any float value against FLT_MAX is |
3239 | | // sufficient to ensure that |value| is always 1 or greater (excluding zero and nan). |
3240 | | // Integer sign() doesn't need to worry about fractional values or nans, and can simply |
3241 | | // be `clamp(arg, -1, 1)`. |
3242 | 0 | if (!this->pushExpression(arg0)) { |
3243 | 0 | return unsupported(); |
3244 | 0 | } |
3245 | 0 | if (arg0.type().componentType().isFloat()) { |
3246 | 0 | Literal fltMaxLiteral{Position{}, FLT_MAX, &arg0.type().componentType()}; |
3247 | 0 | if (!this->pushVectorizedExpression(fltMaxLiteral, arg0.type())) { |
3248 | 0 | return unsupported(); |
3249 | 0 | } |
3250 | 0 | if (!this->binaryOp(arg0.type(), kMultiplyOps)) { |
3251 | 0 | return unsupported(); |
3252 | 0 | } |
3253 | 0 | } |
3254 | 0 | Literal neg1Literal{Position{}, -1.0, &arg0.type().componentType()}; |
3255 | 0 | if (!this->pushVectorizedExpression(neg1Literal, arg0.type())) { |
3256 | 0 | return unsupported(); |
3257 | 0 | } |
3258 | 0 | if (!this->binaryOp(arg0.type(), kMaxOps)) { |
3259 | 0 | return unsupported(); |
3260 | 0 | } |
3261 | 0 | Literal pos1Literal{Position{}, 1.0, &arg0.type().componentType()}; |
3262 | 0 | if (!this->pushVectorizedExpression(pos1Literal, arg0.type())) { |
3263 | 0 | return unsupported(); |
3264 | 0 | } |
3265 | 0 | return this->binaryOp(arg0.type(), kMinOps); |
3266 | 0 | } |
3267 | 0 | case IntrinsicKind::k_sin_IntrinsicKind: |
3268 | 0 | return this->pushIntrinsic(BuilderOp::sin_float, arg0); |
3269 | | |
3270 | 0 | case IntrinsicKind::k_sqrt_IntrinsicKind: |
3271 | 0 | return this->pushIntrinsic(BuilderOp::sqrt_float, arg0); |
3272 | | |
3273 | 0 | case IntrinsicKind::k_tan_IntrinsicKind: |
3274 | 0 | return this->pushIntrinsic(BuilderOp::tan_float, arg0); |
3275 | | |
3276 | 0 | case IntrinsicKind::k_transpose_IntrinsicKind: |
3277 | 0 | SkASSERT(arg0.type().isMatrix()); |
3278 | 0 | if (!this->pushExpression(arg0)) { |
3279 | 0 | return unsupported(); |
3280 | 0 | } |
3281 | 0 | fBuilder.transpose(arg0.type().columns(), arg0.type().rows()); |
3282 | 0 | return true; |
3283 | | |
3284 | 0 | case IntrinsicKind::k_trunc_IntrinsicKind: |
3285 | | // Implement trunc as `float(int(x))`, since float-to-int rounds toward zero. |
3286 | 0 | if (!this->pushExpression(arg0)) { |
3287 | 0 | return unsupported(); |
3288 | 0 | } |
3289 | 0 | fBuilder.unary_op(BuilderOp::cast_to_int_from_float, arg0.type().slotCount()); |
3290 | 0 | fBuilder.unary_op(BuilderOp::cast_to_float_from_int, arg0.type().slotCount()); |
3291 | 0 | return true; |
3292 | | |
3293 | 0 | case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind: |
3294 | 0 | case IntrinsicKind::k_toLinearSrgb_IntrinsicKind: |
3295 | | // The argument must be a half3. |
3296 | 0 | SkASSERT(arg0.type().matches(*fContext.fTypes.fHalf3)); |
3297 | 0 | if (!this->pushExpression(arg0)) { |
3298 | 0 | return unsupported(); |
3299 | 0 | } |
3300 | | |
3301 | 0 | if (intrinsic == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) { |
3302 | 0 | fBuilder.invoke_from_linear_srgb(); |
3303 | 0 | } else { |
3304 | 0 | fBuilder.invoke_to_linear_srgb(); |
3305 | 0 | } |
3306 | 0 | return true; |
3307 | | |
3308 | 0 | default: |
3309 | 0 | break; |
3310 | 66 | } |
3311 | 0 | return unsupported(); |
3312 | 66 | } |
3313 | | |
3314 | 60 | bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0, const Expression& arg1) { |
3315 | 60 | if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) { |
3316 | 0 | return unsupported(); |
3317 | 0 | } |
3318 | 60 | return this->binaryOp(arg0.type(), ops); |
3319 | 60 | } |
3320 | | |
3321 | 8 | bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0, const Expression& arg1) { |
3322 | 8 | if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) { |
3323 | 0 | return unsupported(); |
3324 | 0 | } |
3325 | 8 | fBuilder.binary_op(builderOp, arg0.type().slotCount()); |
3326 | 8 | return true; |
3327 | 8 | } |
3328 | | |
3329 | | bool Generator::pushIntrinsic(IntrinsicKind intrinsic, |
3330 | | const Expression& arg0, |
3331 | 111 | const Expression& arg1) { |
3332 | 111 | switch (intrinsic) { |
3333 | 0 | case IntrinsicKind::k_atan_IntrinsicKind: |
3334 | 0 | return this->pushIntrinsic(BuilderOp::atan2_n_floats, arg0, arg1); |
3335 | | |
3336 | 0 | case IntrinsicKind::k_cross_IntrinsicKind: { |
3337 | | // Implement cross as `arg0.yzx * arg1.zxy - arg0.zxy * arg1.yzx`. We use two stacks so |
3338 | | // that each subexpression can be multiplied separately. |
3339 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3340 | 0 | SkASSERT(arg0.type().slotCount() == 3); |
3341 | 0 | SkASSERT(arg1.type().slotCount() == 3); |
3342 | | |
3343 | | // Push `arg0.yzx` onto this stack and `arg0.zxy` onto a separate subexpression stack. |
3344 | 0 | AutoStack subexpressionStack(this); |
3345 | 0 | subexpressionStack.enter(); |
3346 | 0 | if (!this->pushExpression(arg0)) { |
3347 | 0 | return unsupported(); |
3348 | 0 | } |
3349 | 0 | subexpressionStack.exit(); |
3350 | 0 | subexpressionStack.pushClone(/*slots=*/3); |
3351 | |
|
3352 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0}); |
3353 | 0 | subexpressionStack.enter(); |
3354 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1}); |
3355 | 0 | subexpressionStack.exit(); |
3356 | | |
3357 | | // Push `arg1.zxy` onto this stack and `arg1.yzx` onto the next stack. Perform the |
3358 | | // multiply on each subexpression (`arg0.yzx * arg1.zxy` on the first stack, and |
3359 | | // `arg0.zxy * arg1.yzx` on the next). |
3360 | 0 | subexpressionStack.enter(); |
3361 | 0 | if (!this->pushExpression(arg1)) { |
3362 | 0 | return unsupported(); |
3363 | 0 | } |
3364 | 0 | subexpressionStack.exit(); |
3365 | 0 | subexpressionStack.pushClone(/*slots=*/3); |
3366 | |
|
3367 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1}); |
3368 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, 3); |
3369 | |
|
3370 | 0 | subexpressionStack.enter(); |
3371 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0}); |
3372 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, 3); |
3373 | 0 | subexpressionStack.exit(); |
3374 | | |
3375 | | // Migrate the result of the second subexpression (`arg0.zxy * arg1.yzx`) back onto the |
3376 | | // main stack and subtract it from the first subexpression (`arg0.yzx * arg1.zxy`). |
3377 | 0 | subexpressionStack.pushClone(/*slots=*/3); |
3378 | 0 | fBuilder.binary_op(BuilderOp::sub_n_floats, 3); |
3379 | | |
3380 | | // Now that the calculation is complete, discard the subexpression on the next stack. |
3381 | 0 | subexpressionStack.enter(); |
3382 | 0 | this->discardExpression(/*slots=*/3); |
3383 | 0 | subexpressionStack.exit(); |
3384 | 0 | return true; |
3385 | 0 | } |
3386 | 0 | case IntrinsicKind::k_distance_IntrinsicKind: |
3387 | | // Implement distance as `length(a - b)`. |
3388 | 0 | SkASSERT(arg0.type().slotCount() == arg1.type().slotCount()); |
3389 | 0 | return this->pushBinaryExpression(arg0, OperatorKind::MINUS, arg1) && |
3390 | 0 | this->pushLengthIntrinsic(arg0.type().slotCount()); |
3391 | | |
3392 | 43 | case IntrinsicKind::k_dot_IntrinsicKind: |
3393 | 43 | SkASSERT(arg0.type().matches(arg1.type())); |
3394 | 43 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { |
3395 | 0 | return unsupported(); |
3396 | 0 | } |
3397 | 43 | fBuilder.dot_floats(arg0.type().slotCount()); |
3398 | 43 | return true; |
3399 | | |
3400 | 0 | case IntrinsicKind::k_equal_IntrinsicKind: |
3401 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3402 | 0 | return this->pushIntrinsic(kEqualOps, arg0, arg1); |
3403 | | |
3404 | 0 | case IntrinsicKind::k_notEqual_IntrinsicKind: |
3405 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3406 | 0 | return this->pushIntrinsic(kNotEqualOps, arg0, arg1); |
3407 | | |
3408 | 0 | case IntrinsicKind::k_lessThan_IntrinsicKind: |
3409 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3410 | 0 | return this->pushIntrinsic(kLessThanOps, arg0, arg1); |
3411 | | |
3412 | 0 | case IntrinsicKind::k_greaterThan_IntrinsicKind: |
3413 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3414 | 0 | return this->pushIntrinsic(kLessThanOps, arg1, arg0); |
3415 | | |
3416 | 0 | case IntrinsicKind::k_lessThanEqual_IntrinsicKind: |
3417 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3418 | 0 | return this->pushIntrinsic(kLessThanEqualOps, arg0, arg1); |
3419 | | |
3420 | 0 | case IntrinsicKind::k_greaterThanEqual_IntrinsicKind: |
3421 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3422 | 0 | return this->pushIntrinsic(kLessThanEqualOps, arg1, arg0); |
3423 | | |
3424 | 20 | case IntrinsicKind::k_min_IntrinsicKind: |
3425 | 20 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); |
3426 | 20 | return this->pushIntrinsic(kMinOps, arg0, arg1); |
3427 | | |
3428 | 0 | case IntrinsicKind::k_matrixCompMult_IntrinsicKind: |
3429 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3430 | 0 | return this->pushIntrinsic(kMultiplyOps, arg0, arg1); |
3431 | | |
3432 | 40 | case IntrinsicKind::k_max_IntrinsicKind: |
3433 | 40 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); |
3434 | 40 | return this->pushIntrinsic(kMaxOps, arg0, arg1); |
3435 | | |
3436 | 0 | case IntrinsicKind::k_mod_IntrinsicKind: |
3437 | 0 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); |
3438 | 0 | return this->pushIntrinsic(kModOps, arg0, arg1); |
3439 | | |
3440 | 8 | case IntrinsicKind::k_pow_IntrinsicKind: |
3441 | 8 | SkASSERT(arg0.type().matches(arg1.type())); |
3442 | 8 | return this->pushIntrinsic(BuilderOp::pow_n_floats, arg0, arg1); |
3443 | | |
3444 | 0 | case IntrinsicKind::k_reflect_IntrinsicKind: { |
3445 | | // Implement reflect as `I - (N * dot(I,N) * 2)`. |
3446 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3447 | 0 | SkASSERT(arg0.type().slotCount() == arg1.type().slotCount()); |
3448 | 0 | SkASSERT(arg0.type().componentType().isFloat()); |
3449 | 0 | int slotCount = arg0.type().slotCount(); |
3450 | | |
3451 | | // Stack: I, N. |
3452 | 0 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { |
3453 | 0 | return unsupported(); |
3454 | 0 | } |
3455 | | // Stack: I, N, I, N. |
3456 | 0 | fBuilder.push_clone(2 * slotCount); |
3457 | | // Stack: I, N, dot(I,N) |
3458 | 0 | fBuilder.dot_floats(slotCount); |
3459 | | // Stack: I, N, dot(I,N), 2 |
3460 | 0 | fBuilder.push_constant_f(2.0); |
3461 | | // Stack: I, N, dot(I,N) * 2 |
3462 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, 1); |
3463 | | // Stack: I, N * dot(I,N) * 2 |
3464 | 0 | fBuilder.push_duplicates(slotCount - 1); |
3465 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, slotCount); |
3466 | | // Stack: I - (N * dot(I,N) * 2) |
3467 | 0 | fBuilder.binary_op(BuilderOp::sub_n_floats, slotCount); |
3468 | 0 | return true; |
3469 | 0 | } |
3470 | 0 | case IntrinsicKind::k_step_IntrinsicKind: { |
3471 | | // Compute step as `float(lessThanEqual(edge, x))`. We convert from boolean 0/~0 to |
3472 | | // floating point zero/one by using a bitwise-and against the bit-pattern of 1.0. |
3473 | 0 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); |
3474 | 0 | if (!this->pushVectorizedExpression(arg0, arg1.type()) || !this->pushExpression(arg1)) { |
3475 | 0 | return unsupported(); |
3476 | 0 | } |
3477 | 0 | if (!this->binaryOp(arg1.type(), kLessThanEqualOps)) { |
3478 | 0 | return unsupported(); |
3479 | 0 | } |
3480 | 0 | Literal pos1Literal{Position{}, 1.0, &arg1.type().componentType()}; |
3481 | 0 | if (!this->pushVectorizedExpression(pos1Literal, arg1.type())) { |
3482 | 0 | return unsupported(); |
3483 | 0 | } |
3484 | 0 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, arg1.type().slotCount()); |
3485 | 0 | return true; |
3486 | 0 | } |
3487 | | |
3488 | 0 | default: |
3489 | 0 | break; |
3490 | 111 | } |
3491 | 0 | return unsupported(); |
3492 | 111 | } SkSL::RP::Generator::pushIntrinsic(SkSL::IntrinsicKind, SkSL::Expression const&, SkSL::Expression const&) Line | Count | Source | 3331 | 111 | const Expression& arg1) { | 3332 | 111 | switch (intrinsic) { | 3333 | 0 | case IntrinsicKind::k_atan_IntrinsicKind: | 3334 | 0 | return this->pushIntrinsic(BuilderOp::atan2_n_floats, arg0, arg1); | 3335 | | | 3336 | 0 | case IntrinsicKind::k_cross_IntrinsicKind: { | 3337 | | // Implement cross as `arg0.yzx * arg1.zxy - arg0.zxy * arg1.yzx`. We use two stacks so | 3338 | | // that each subexpression can be multiplied separately. | 3339 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3340 | 0 | SkASSERT(arg0.type().slotCount() == 3); | 3341 | 0 | SkASSERT(arg1.type().slotCount() == 3); | 3342 | | | 3343 | | // Push `arg0.yzx` onto this stack and `arg0.zxy` onto a separate subexpression stack. | 3344 | 0 | AutoStack subexpressionStack(this); | 3345 | 0 | subexpressionStack.enter(); | 3346 | 0 | if (!this->pushExpression(arg0)) { | 3347 | 0 | return unsupported(); | 3348 | 0 | } | 3349 | 0 | subexpressionStack.exit(); | 3350 | 0 | subexpressionStack.pushClone(/*slots=*/3); | 3351 | |
| 3352 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0}); | 3353 | 0 | subexpressionStack.enter(); | 3354 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1}); | 3355 | 0 | subexpressionStack.exit(); | 3356 | | | 3357 | | // Push `arg1.zxy` onto this stack and `arg1.yzx` onto the next stack. Perform the | 3358 | | // multiply on each subexpression (`arg0.yzx * arg1.zxy` on the first stack, and | 3359 | | // `arg0.zxy * arg1.yzx` on the next). | 3360 | 0 | subexpressionStack.enter(); | 3361 | 0 | if (!this->pushExpression(arg1)) { | 3362 | 0 | return unsupported(); | 3363 | 0 | } | 3364 | 0 | subexpressionStack.exit(); | 3365 | 0 | subexpressionStack.pushClone(/*slots=*/3); | 3366 | |
| 3367 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1}); | 3368 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, 3); | 3369 | |
| 3370 | 0 | subexpressionStack.enter(); | 3371 | 0 | fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0}); | 3372 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, 3); | 3373 | 0 | subexpressionStack.exit(); | 3374 | | | 3375 | | // Migrate the result of the second subexpression (`arg0.zxy * arg1.yzx`) back onto the | 3376 | | // main stack and subtract it from the first subexpression (`arg0.yzx * arg1.zxy`). | 3377 | 0 | subexpressionStack.pushClone(/*slots=*/3); | 3378 | 0 | fBuilder.binary_op(BuilderOp::sub_n_floats, 3); | 3379 | | | 3380 | | // Now that the calculation is complete, discard the subexpression on the next stack. | 3381 | 0 | subexpressionStack.enter(); | 3382 | 0 | this->discardExpression(/*slots=*/3); | 3383 | 0 | subexpressionStack.exit(); | 3384 | 0 | return true; | 3385 | 0 | } | 3386 | 0 | case IntrinsicKind::k_distance_IntrinsicKind: | 3387 | | // Implement distance as `length(a - b)`. | 3388 | 0 | SkASSERT(arg0.type().slotCount() == arg1.type().slotCount()); | 3389 | 0 | return this->pushBinaryExpression(arg0, OperatorKind::MINUS, arg1) && | 3390 | 0 | this->pushLengthIntrinsic(arg0.type().slotCount()); | 3391 | | | 3392 | 43 | case IntrinsicKind::k_dot_IntrinsicKind: | 3393 | 43 | SkASSERT(arg0.type().matches(arg1.type())); | 3394 | 43 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { | 3395 | 0 | return unsupported(); | 3396 | 0 | } | 3397 | 43 | fBuilder.dot_floats(arg0.type().slotCount()); | 3398 | 43 | return true; | 3399 | | | 3400 | 0 | case IntrinsicKind::k_equal_IntrinsicKind: | 3401 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3402 | 0 | return this->pushIntrinsic(kEqualOps, arg0, arg1); | 3403 | | | 3404 | 0 | case IntrinsicKind::k_notEqual_IntrinsicKind: | 3405 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3406 | 0 | return this->pushIntrinsic(kNotEqualOps, arg0, arg1); | 3407 | | | 3408 | 0 | case IntrinsicKind::k_lessThan_IntrinsicKind: | 3409 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3410 | 0 | return this->pushIntrinsic(kLessThanOps, arg0, arg1); | 3411 | | | 3412 | 0 | case IntrinsicKind::k_greaterThan_IntrinsicKind: | 3413 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3414 | 0 | return this->pushIntrinsic(kLessThanOps, arg1, arg0); | 3415 | | | 3416 | 0 | case IntrinsicKind::k_lessThanEqual_IntrinsicKind: | 3417 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3418 | 0 | return this->pushIntrinsic(kLessThanEqualOps, arg0, arg1); | 3419 | | | 3420 | 0 | case IntrinsicKind::k_greaterThanEqual_IntrinsicKind: | 3421 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3422 | 0 | return this->pushIntrinsic(kLessThanEqualOps, arg1, arg0); | 3423 | | | 3424 | 20 | case IntrinsicKind::k_min_IntrinsicKind: | 3425 | 20 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); | 3426 | 20 | return this->pushIntrinsic(kMinOps, arg0, arg1); | 3427 | | | 3428 | 0 | case IntrinsicKind::k_matrixCompMult_IntrinsicKind: | 3429 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3430 | 0 | return this->pushIntrinsic(kMultiplyOps, arg0, arg1); | 3431 | | | 3432 | 40 | case IntrinsicKind::k_max_IntrinsicKind: | 3433 | 40 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); | 3434 | 40 | return this->pushIntrinsic(kMaxOps, arg0, arg1); | 3435 | | | 3436 | 0 | case IntrinsicKind::k_mod_IntrinsicKind: | 3437 | 0 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); | 3438 | 0 | return this->pushIntrinsic(kModOps, arg0, arg1); | 3439 | | | 3440 | 8 | case IntrinsicKind::k_pow_IntrinsicKind: | 3441 | 8 | SkASSERT(arg0.type().matches(arg1.type())); | 3442 | 8 | return this->pushIntrinsic(BuilderOp::pow_n_floats, arg0, arg1); | 3443 | | | 3444 | 0 | case IntrinsicKind::k_reflect_IntrinsicKind: { | 3445 | | // Implement reflect as `I - (N * dot(I,N) * 2)`. | 3446 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3447 | 0 | SkASSERT(arg0.type().slotCount() == arg1.type().slotCount()); | 3448 | 0 | SkASSERT(arg0.type().componentType().isFloat()); | 3449 | 0 | int slotCount = arg0.type().slotCount(); | 3450 | | | 3451 | | // Stack: I, N. | 3452 | 0 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { | 3453 | 0 | return unsupported(); | 3454 | 0 | } | 3455 | | // Stack: I, N, I, N. | 3456 | 0 | fBuilder.push_clone(2 * slotCount); | 3457 | | // Stack: I, N, dot(I,N) | 3458 | 0 | fBuilder.dot_floats(slotCount); | 3459 | | // Stack: I, N, dot(I,N), 2 | 3460 | 0 | fBuilder.push_constant_f(2.0); | 3461 | | // Stack: I, N, dot(I,N) * 2 | 3462 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, 1); | 3463 | | // Stack: I, N * dot(I,N) * 2 | 3464 | 0 | fBuilder.push_duplicates(slotCount - 1); | 3465 | 0 | fBuilder.binary_op(BuilderOp::mul_n_floats, slotCount); | 3466 | | // Stack: I - (N * dot(I,N) * 2) | 3467 | 0 | fBuilder.binary_op(BuilderOp::sub_n_floats, slotCount); | 3468 | 0 | return true; | 3469 | 0 | } | 3470 | 0 | case IntrinsicKind::k_step_IntrinsicKind: { | 3471 | | // Compute step as `float(lessThanEqual(edge, x))`. We convert from boolean 0/~0 to | 3472 | | // floating point zero/one by using a bitwise-and against the bit-pattern of 1.0. | 3473 | 0 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); | 3474 | 0 | if (!this->pushVectorizedExpression(arg0, arg1.type()) || !this->pushExpression(arg1)) { | 3475 | 0 | return unsupported(); | 3476 | 0 | } | 3477 | 0 | if (!this->binaryOp(arg1.type(), kLessThanEqualOps)) { | 3478 | 0 | return unsupported(); | 3479 | 0 | } | 3480 | 0 | Literal pos1Literal{Position{}, 1.0, &arg1.type().componentType()}; | 3481 | 0 | if (!this->pushVectorizedExpression(pos1Literal, arg1.type())) { | 3482 | 0 | return unsupported(); | 3483 | 0 | } | 3484 | 0 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, arg1.type().slotCount()); | 3485 | 0 | return true; | 3486 | 0 | } | 3487 | | | 3488 | 0 | default: | 3489 | 0 | break; | 3490 | 111 | } | 3491 | 0 | return unsupported(); | 3492 | 111 | } |
Unexecuted instantiation: SkSL::RP::Generator::pushIntrinsic(SkSL::IntrinsicKind, SkSL::Expression const&, SkSL::Expression const&) |
3493 | | |
3494 | | bool Generator::pushIntrinsic(IntrinsicKind intrinsic, |
3495 | | const Expression& arg0, |
3496 | | const Expression& arg1, |
3497 | 88 | const Expression& arg2) { |
3498 | 88 | switch (intrinsic) { |
3499 | 79 | case IntrinsicKind::k_clamp_IntrinsicKind: |
3500 | | // Implement clamp as min(max(arg, low), high). |
3501 | 79 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); |
3502 | 79 | SkASSERT(arg0.type().componentType().matches(arg2.type().componentType())); |
3503 | 79 | if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) { |
3504 | 0 | return unsupported(); |
3505 | 0 | } |
3506 | 79 | if (!this->binaryOp(arg0.type(), kMaxOps)) { |
3507 | 0 | return unsupported(); |
3508 | 0 | } |
3509 | 79 | if (!this->pushVectorizedExpression(arg2, arg0.type())) { |
3510 | 0 | return unsupported(); |
3511 | 0 | } |
3512 | 79 | if (!this->binaryOp(arg0.type(), kMinOps)) { |
3513 | 0 | return unsupported(); |
3514 | 0 | } |
3515 | 79 | return true; |
3516 | | |
3517 | 0 | case IntrinsicKind::k_faceforward_IntrinsicKind: { |
3518 | | // Implement faceforward as `N ^ ((0 <= dot(I, NRef)) & 0x80000000)`. |
3519 | | // In other words, flip the sign bit of N if `0 <= dot(I, NRef)`. |
3520 | 0 | SkASSERT(arg0.type().matches(arg1.type())); |
3521 | 0 | SkASSERT(arg0.type().matches(arg2.type())); |
3522 | 0 | int slotCount = arg0.type().slotCount(); |
3523 | | |
3524 | | // Stack: N, 0, I, Nref |
3525 | 0 | if (!this->pushExpression(arg0)) { |
3526 | 0 | return unsupported(); |
3527 | 0 | } |
3528 | 0 | fBuilder.push_constant_f(0.0); |
3529 | 0 | if (!this->pushExpression(arg1) || !this->pushExpression(arg2)) { |
3530 | 0 | return unsupported(); |
3531 | 0 | } |
3532 | | // Stack: N, 0, dot(I,NRef) |
3533 | 0 | fBuilder.dot_floats(slotCount); |
3534 | | // Stack: N, (0 <= dot(I,NRef)) |
3535 | 0 | fBuilder.binary_op(BuilderOp::cmple_n_floats, 1); |
3536 | | // Stack: N, (0 <= dot(I,NRef)), 0x80000000 |
3537 | 0 | fBuilder.push_constant_u(0x80000000); |
3538 | | // Stack: N, (0 <= dot(I,NRef)) & 0x80000000) |
3539 | 0 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1); |
3540 | | // Stack: N, vec(0 <= dot(I,NRef)) & 0x80000000) |
3541 | 0 | fBuilder.push_duplicates(slotCount - 1); |
3542 | | // Stack: N ^ vec((0 <= dot(I,NRef)) & 0x80000000) |
3543 | 0 | fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, slotCount); |
3544 | 0 | return true; |
3545 | 0 | } |
3546 | 9 | case IntrinsicKind::k_mix_IntrinsicKind: |
3547 | | // Note: our SkRP mix op takes the interpolation point first, not the interpolants. |
3548 | 9 | SkASSERT(arg0.type().matches(arg1.type())); |
3549 | 9 | if (arg2.type().componentType().isFloat()) { |
3550 | 9 | SkASSERT(arg0.type().componentType().matches(arg2.type().componentType())); |
3551 | 9 | if (!this->pushVectorizedExpression(arg2, arg0.type())) { |
3552 | 0 | return unsupported(); |
3553 | 0 | } |
3554 | 9 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { |
3555 | 0 | return unsupported(); |
3556 | 0 | } |
3557 | 9 | return this->ternaryOp(arg0.type(), kMixOps); |
3558 | 9 | } |
3559 | 0 | if (arg2.type().componentType().isBoolean()) { |
3560 | 0 | if (!this->pushExpression(arg2)) { |
3561 | 0 | return unsupported(); |
3562 | 0 | } |
3563 | 0 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { |
3564 | 0 | return unsupported(); |
3565 | 0 | } |
3566 | | // The `mix_int` op isn't doing a lerp; it uses the third argument to select values |
3567 | | // from the first and second arguments. It's safe for use with any type in arguments |
3568 | | // 0 and 1. |
3569 | 0 | fBuilder.ternary_op(BuilderOp::mix_n_ints, arg0.type().slotCount()); |
3570 | 0 | return true; |
3571 | 0 | } |
3572 | 0 | return unsupported(); |
3573 | | |
3574 | 0 | case IntrinsicKind::k_refract_IntrinsicKind: { |
3575 | | // We always calculate refraction using vec4s, so we pad out unused N/I slots with zero. |
3576 | 0 | int padding = 4 - arg0.type().slotCount(); |
3577 | 0 | if (!this->pushExpression(arg0)) { |
3578 | 0 | return unsupported(); |
3579 | 0 | } |
3580 | 0 | fBuilder.push_zeros(padding); |
3581 | |
|
3582 | 0 | if (!this->pushExpression(arg1)) { |
3583 | 0 | return unsupported(); |
3584 | 0 | } |
3585 | 0 | fBuilder.push_zeros(padding); |
3586 | | |
3587 | | // eta is always a scalar and doesn't need padding. |
3588 | 0 | if (!this->pushExpression(arg2)) { |
3589 | 0 | return unsupported(); |
3590 | 0 | } |
3591 | 0 | fBuilder.refract_floats(); |
3592 | | |
3593 | | // The result vector was returned as a vec4, so discard the extra columns. |
3594 | 0 | fBuilder.discard_stack(padding); |
3595 | 0 | return true; |
3596 | 0 | } |
3597 | 0 | case IntrinsicKind::k_smoothstep_IntrinsicKind: |
3598 | 0 | SkASSERT(arg0.type().componentType().isFloat()); |
3599 | 0 | SkASSERT(arg1.type().matches(arg0.type())); |
3600 | 0 | SkASSERT(arg2.type().componentType().isFloat()); |
3601 | |
|
3602 | 0 | if (!this->pushVectorizedExpression(arg0, arg2.type()) || |
3603 | 0 | !this->pushVectorizedExpression(arg1, arg2.type()) || |
3604 | 0 | !this->pushExpression(arg2)) { |
3605 | 0 | return unsupported(); |
3606 | 0 | } |
3607 | 0 | fBuilder.ternary_op(BuilderOp::smoothstep_n_floats, arg2.type().slotCount()); |
3608 | 0 | return true; |
3609 | | |
3610 | 0 | default: |
3611 | 0 | break; |
3612 | 88 | } |
3613 | 0 | return unsupported(); |
3614 | 88 | } SkSL::RP::Generator::pushIntrinsic(SkSL::IntrinsicKind, SkSL::Expression const&, SkSL::Expression const&, SkSL::Expression const&) Line | Count | Source | 3497 | 88 | const Expression& arg2) { | 3498 | 88 | switch (intrinsic) { | 3499 | 79 | case IntrinsicKind::k_clamp_IntrinsicKind: | 3500 | | // Implement clamp as min(max(arg, low), high). | 3501 | 79 | SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); | 3502 | 79 | SkASSERT(arg0.type().componentType().matches(arg2.type().componentType())); | 3503 | 79 | if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) { | 3504 | 0 | return unsupported(); | 3505 | 0 | } | 3506 | 79 | if (!this->binaryOp(arg0.type(), kMaxOps)) { | 3507 | 0 | return unsupported(); | 3508 | 0 | } | 3509 | 79 | if (!this->pushVectorizedExpression(arg2, arg0.type())) { | 3510 | 0 | return unsupported(); | 3511 | 0 | } | 3512 | 79 | if (!this->binaryOp(arg0.type(), kMinOps)) { | 3513 | 0 | return unsupported(); | 3514 | 0 | } | 3515 | 79 | return true; | 3516 | | | 3517 | 0 | case IntrinsicKind::k_faceforward_IntrinsicKind: { | 3518 | | // Implement faceforward as `N ^ ((0 <= dot(I, NRef)) & 0x80000000)`. | 3519 | | // In other words, flip the sign bit of N if `0 <= dot(I, NRef)`. | 3520 | 0 | SkASSERT(arg0.type().matches(arg1.type())); | 3521 | 0 | SkASSERT(arg0.type().matches(arg2.type())); | 3522 | 0 | int slotCount = arg0.type().slotCount(); | 3523 | | | 3524 | | // Stack: N, 0, I, Nref | 3525 | 0 | if (!this->pushExpression(arg0)) { | 3526 | 0 | return unsupported(); | 3527 | 0 | } | 3528 | 0 | fBuilder.push_constant_f(0.0); | 3529 | 0 | if (!this->pushExpression(arg1) || !this->pushExpression(arg2)) { | 3530 | 0 | return unsupported(); | 3531 | 0 | } | 3532 | | // Stack: N, 0, dot(I,NRef) | 3533 | 0 | fBuilder.dot_floats(slotCount); | 3534 | | // Stack: N, (0 <= dot(I,NRef)) | 3535 | 0 | fBuilder.binary_op(BuilderOp::cmple_n_floats, 1); | 3536 | | // Stack: N, (0 <= dot(I,NRef)), 0x80000000 | 3537 | 0 | fBuilder.push_constant_u(0x80000000); | 3538 | | // Stack: N, (0 <= dot(I,NRef)) & 0x80000000) | 3539 | 0 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1); | 3540 | | // Stack: N, vec(0 <= dot(I,NRef)) & 0x80000000) | 3541 | 0 | fBuilder.push_duplicates(slotCount - 1); | 3542 | | // Stack: N ^ vec((0 <= dot(I,NRef)) & 0x80000000) | 3543 | 0 | fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, slotCount); | 3544 | 0 | return true; | 3545 | 0 | } | 3546 | 9 | case IntrinsicKind::k_mix_IntrinsicKind: | 3547 | | // Note: our SkRP mix op takes the interpolation point first, not the interpolants. | 3548 | 9 | SkASSERT(arg0.type().matches(arg1.type())); | 3549 | 9 | if (arg2.type().componentType().isFloat()) { | 3550 | 9 | SkASSERT(arg0.type().componentType().matches(arg2.type().componentType())); | 3551 | 9 | if (!this->pushVectorizedExpression(arg2, arg0.type())) { | 3552 | 0 | return unsupported(); | 3553 | 0 | } | 3554 | 9 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { | 3555 | 0 | return unsupported(); | 3556 | 0 | } | 3557 | 9 | return this->ternaryOp(arg0.type(), kMixOps); | 3558 | 9 | } | 3559 | 0 | if (arg2.type().componentType().isBoolean()) { | 3560 | 0 | if (!this->pushExpression(arg2)) { | 3561 | 0 | return unsupported(); | 3562 | 0 | } | 3563 | 0 | if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { | 3564 | 0 | return unsupported(); | 3565 | 0 | } | 3566 | | // The `mix_int` op isn't doing a lerp; it uses the third argument to select values | 3567 | | // from the first and second arguments. It's safe for use with any type in arguments | 3568 | | // 0 and 1. | 3569 | 0 | fBuilder.ternary_op(BuilderOp::mix_n_ints, arg0.type().slotCount()); | 3570 | 0 | return true; | 3571 | 0 | } | 3572 | 0 | return unsupported(); | 3573 | | | 3574 | 0 | case IntrinsicKind::k_refract_IntrinsicKind: { | 3575 | | // We always calculate refraction using vec4s, so we pad out unused N/I slots with zero. | 3576 | 0 | int padding = 4 - arg0.type().slotCount(); | 3577 | 0 | if (!this->pushExpression(arg0)) { | 3578 | 0 | return unsupported(); | 3579 | 0 | } | 3580 | 0 | fBuilder.push_zeros(padding); | 3581 | |
| 3582 | 0 | if (!this->pushExpression(arg1)) { | 3583 | 0 | return unsupported(); | 3584 | 0 | } | 3585 | 0 | fBuilder.push_zeros(padding); | 3586 | | | 3587 | | // eta is always a scalar and doesn't need padding. | 3588 | 0 | if (!this->pushExpression(arg2)) { | 3589 | 0 | return unsupported(); | 3590 | 0 | } | 3591 | 0 | fBuilder.refract_floats(); | 3592 | | | 3593 | | // The result vector was returned as a vec4, so discard the extra columns. | 3594 | 0 | fBuilder.discard_stack(padding); | 3595 | 0 | return true; | 3596 | 0 | } | 3597 | 0 | case IntrinsicKind::k_smoothstep_IntrinsicKind: | 3598 | 0 | SkASSERT(arg0.type().componentType().isFloat()); | 3599 | 0 | SkASSERT(arg1.type().matches(arg0.type())); | 3600 | 0 | SkASSERT(arg2.type().componentType().isFloat()); | 3601 | |
| 3602 | 0 | if (!this->pushVectorizedExpression(arg0, arg2.type()) || | 3603 | 0 | !this->pushVectorizedExpression(arg1, arg2.type()) || | 3604 | 0 | !this->pushExpression(arg2)) { | 3605 | 0 | return unsupported(); | 3606 | 0 | } | 3607 | 0 | fBuilder.ternary_op(BuilderOp::smoothstep_n_floats, arg2.type().slotCount()); | 3608 | 0 | return true; | 3609 | | | 3610 | 0 | default: | 3611 | 0 | break; | 3612 | 88 | } | 3613 | 0 | return unsupported(); | 3614 | 88 | } |
Unexecuted instantiation: SkSL::RP::Generator::pushIntrinsic(SkSL::IntrinsicKind, SkSL::Expression const&, SkSL::Expression const&, SkSL::Expression const&) |
3615 | | |
3616 | 344 | bool Generator::pushLiteral(const Literal& l) { |
3617 | 344 | switch (l.type().numberKind()) { |
3618 | 275 | case Type::NumberKind::kFloat: |
3619 | 275 | fBuilder.push_constant_f(l.floatValue()); |
3620 | 275 | return true; |
3621 | | |
3622 | 69 | case Type::NumberKind::kSigned: |
3623 | 69 | fBuilder.push_constant_i(l.intValue()); |
3624 | 69 | return true; |
3625 | | |
3626 | 0 | case Type::NumberKind::kUnsigned: |
3627 | 0 | fBuilder.push_constant_u(l.intValue()); |
3628 | 0 | return true; |
3629 | | |
3630 | 0 | case Type::NumberKind::kBoolean: |
3631 | 0 | fBuilder.push_constant_i(l.boolValue() ? ~0 : 0); |
3632 | 0 | return true; |
3633 | | |
3634 | 0 | default: |
3635 | 0 | SkUNREACHABLE; |
3636 | 344 | } |
3637 | 344 | } |
3638 | | |
3639 | 0 | bool Generator::pushPostfixExpression(const PostfixExpression& p, bool usesResult) { |
3640 | | // If the result is ignored... |
3641 | 0 | if (!usesResult) { |
3642 | | // ... just emit a prefix expression instead. |
3643 | 0 | return this->pushPrefixExpression(p.getOperator(), *p.operand()); |
3644 | 0 | } |
3645 | | // Get the operand as an lvalue, and push it onto the stack as-is. |
3646 | 0 | std::unique_ptr<LValue> lvalue = this->makeLValue(*p.operand()); |
3647 | 0 | if (!lvalue || !this->push(*lvalue)) { |
3648 | 0 | return unsupported(); |
3649 | 0 | } |
3650 | | |
3651 | | // Push a scratch copy of the operand. |
3652 | 0 | fBuilder.push_clone(p.type().slotCount()); |
3653 | | |
3654 | | // Increment or decrement the scratch copy by one. |
3655 | 0 | Literal oneLiteral{Position{}, 1.0, &p.type().componentType()}; |
3656 | 0 | if (!this->pushVectorizedExpression(oneLiteral, p.type())) { |
3657 | 0 | return unsupported(); |
3658 | 0 | } |
3659 | | |
3660 | 0 | switch (p.getOperator().kind()) { |
3661 | 0 | case OperatorKind::PLUSPLUS: |
3662 | 0 | if (!this->binaryOp(p.type(), kAddOps)) { |
3663 | 0 | return unsupported(); |
3664 | 0 | } |
3665 | 0 | break; |
3666 | | |
3667 | 0 | case OperatorKind::MINUSMINUS: |
3668 | 0 | if (!this->binaryOp(p.type(), kSubtractOps)) { |
3669 | 0 | return unsupported(); |
3670 | 0 | } |
3671 | 0 | break; |
3672 | | |
3673 | 0 | default: |
3674 | 0 | SkUNREACHABLE; |
3675 | 0 | } |
3676 | | |
3677 | | // Write the new value back to the operand. |
3678 | 0 | if (!this->store(*lvalue)) { |
3679 | 0 | return unsupported(); |
3680 | 0 | } |
3681 | | |
3682 | | // Discard the scratch copy, leaving only the original value as-is. |
3683 | 0 | this->discardExpression(p.type().slotCount()); |
3684 | 0 | return true; |
3685 | 0 | } |
3686 | | |
3687 | 17 | bool Generator::pushPrefixExpression(const PrefixExpression& p) { |
3688 | 17 | return this->pushPrefixExpression(p.getOperator(), *p.operand()); |
3689 | 17 | } |
3690 | | |
3691 | 17 | bool Generator::pushPrefixExpression(Operator op, const Expression& expr) { |
3692 | 17 | switch (op.kind()) { |
3693 | 0 | case OperatorKind::BITWISENOT: |
3694 | 0 | case OperatorKind::LOGICALNOT: |
3695 | | // Handle operators ! and ~. |
3696 | 0 | if (!this->pushExpression(expr)) { |
3697 | 0 | return unsupported(); |
3698 | 0 | } |
3699 | 0 | fBuilder.push_constant_u(~0, expr.type().slotCount()); |
3700 | 0 | fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, expr.type().slotCount()); |
3701 | 0 | return true; |
3702 | | |
3703 | 4 | case OperatorKind::MINUS: { |
3704 | 4 | if (!this->pushExpression(expr)) { |
3705 | 0 | return unsupported(); |
3706 | 0 | } |
3707 | 4 | if (expr.type().componentType().isFloat()) { |
3708 | | // Handle float negation as an integer `x ^ 0x80000000`. This toggles the sign bit. |
3709 | 4 | fBuilder.push_constant_u(0x80000000, expr.type().slotCount()); |
3710 | 4 | fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, expr.type().slotCount()); |
3711 | 4 | } else { |
3712 | | // Handle integer negation as a componentwise `expr * -1`. |
3713 | 0 | fBuilder.push_constant_i(-1, expr.type().slotCount()); |
3714 | 0 | fBuilder.binary_op(BuilderOp::mul_n_ints, expr.type().slotCount()); |
3715 | 0 | } |
3716 | 4 | return true; |
3717 | 4 | } |
3718 | 13 | case OperatorKind::PLUSPLUS: { |
3719 | | // Rewrite as `expr += 1`. |
3720 | 13 | Literal oneLiteral{Position{}, 1.0, &expr.type().componentType()}; |
3721 | 13 | return this->pushBinaryExpression(expr, OperatorKind::PLUSEQ, oneLiteral); |
3722 | 4 | } |
3723 | 0 | case OperatorKind::MINUSMINUS: { |
3724 | | // Rewrite as `expr += -1`. |
3725 | 0 | Literal minusOneLiteral{expr.fPosition, -1.0, &expr.type().componentType()}; |
3726 | 0 | return this->pushBinaryExpression(expr, OperatorKind::PLUSEQ, minusOneLiteral); |
3727 | 4 | } |
3728 | 0 | default: |
3729 | 0 | break; |
3730 | 17 | } |
3731 | | |
3732 | 0 | return unsupported(); |
3733 | 17 | } |
3734 | | |
3735 | 479 | bool Generator::pushSwizzle(const Swizzle& s) { |
3736 | 479 | SkASSERT(!s.components().empty() && s.components().size() <= 4); |
3737 | | |
3738 | | // If this is a simple subset of a variable's slots... |
3739 | 479 | bool isSimpleSubset = is_sliceable_swizzle(s.components()); |
3740 | 479 | if (isSimpleSubset && s.base()->is<VariableReference>()) { |
3741 | | // ... we can just push part of the variable directly onto the stack, rather than pushing |
3742 | | // the whole expression and then immediately cutting it down. (Either way works, but this |
3743 | | // saves a step.) |
3744 | 434 | return this->pushVariableReferencePartial( |
3745 | 434 | s.base()->as<VariableReference>(), |
3746 | 434 | SlotRange{/*index=*/s.components()[0], /*count=*/s.components().size()}); |
3747 | 434 | } |
3748 | | // Push the base expression. |
3749 | 45 | if (!this->pushExpression(*s.base())) { |
3750 | 0 | return false; |
3751 | 0 | } |
3752 | | // An identity swizzle doesn't rearrange the data; it just (potentially) discards tail elements. |
3753 | 45 | if (isSimpleSubset && s.components()[0] == 0) { |
3754 | 0 | int discardedElements = s.base()->type().slotCount() - s.components().size(); |
3755 | 0 | SkASSERT(discardedElements >= 0); |
3756 | 0 | fBuilder.discard_stack(discardedElements); |
3757 | 0 | return true; |
3758 | 0 | } |
3759 | | // Perform the swizzle. |
3760 | 45 | fBuilder.swizzle(s.base()->type().slotCount(), s.components()); |
3761 | 45 | return true; |
3762 | 45 | } SkSL::RP::Generator::pushSwizzle(SkSL::Swizzle const&) Line | Count | Source | 3735 | 479 | bool Generator::pushSwizzle(const Swizzle& s) { | 3736 | 479 | SkASSERT(!s.components().empty() && s.components().size() <= 4); | 3737 | | | 3738 | | // If this is a simple subset of a variable's slots... | 3739 | 479 | bool isSimpleSubset = is_sliceable_swizzle(s.components()); | 3740 | 479 | if (isSimpleSubset && s.base()->is<VariableReference>()) { | 3741 | | // ... we can just push part of the variable directly onto the stack, rather than pushing | 3742 | | // the whole expression and then immediately cutting it down. (Either way works, but this | 3743 | | // saves a step.) | 3744 | 434 | return this->pushVariableReferencePartial( | 3745 | 434 | s.base()->as<VariableReference>(), | 3746 | 434 | SlotRange{/*index=*/s.components()[0], /*count=*/s.components().size()}); | 3747 | 434 | } | 3748 | | // Push the base expression. | 3749 | 45 | if (!this->pushExpression(*s.base())) { | 3750 | 0 | return false; | 3751 | 0 | } | 3752 | | // An identity swizzle doesn't rearrange the data; it just (potentially) discards tail elements. | 3753 | 45 | if (isSimpleSubset && s.components()[0] == 0) { | 3754 | 0 | int discardedElements = s.base()->type().slotCount() - s.components().size(); | 3755 | 0 | SkASSERT(discardedElements >= 0); | 3756 | 0 | fBuilder.discard_stack(discardedElements); | 3757 | 0 | return true; | 3758 | 0 | } | 3759 | | // Perform the swizzle. | 3760 | 45 | fBuilder.swizzle(s.base()->type().slotCount(), s.components()); | 3761 | 45 | return true; | 3762 | 45 | } |
Unexecuted instantiation: SkSL::RP::Generator::pushSwizzle(SkSL::Swizzle const&) |
3763 | | |
3764 | 28 | bool Generator::pushTernaryExpression(const TernaryExpression& t) { |
3765 | 28 | return this->pushTernaryExpression(*t.test(), *t.ifTrue(), *t.ifFalse()); |
3766 | 28 | } |
3767 | | |
3768 | | bool Generator::pushDynamicallyUniformTernaryExpression(const Expression& test, |
3769 | | const Expression& ifTrue, |
3770 | 0 | const Expression& ifFalse) { |
3771 | 0 | SkASSERT(Analysis::IsDynamicallyUniformExpression(test)); |
3772 | |
|
3773 | 0 | int falseLabelID = fBuilder.nextLabelID(); |
3774 | 0 | int exitLabelID = fBuilder.nextLabelID(); |
3775 | | |
3776 | | // First, push the test-expression into a separate stack. |
3777 | 0 | AutoStack testStack(this); |
3778 | 0 | testStack.enter(); |
3779 | 0 | if (!this->pushExpression(test)) { |
3780 | 0 | return unsupported(); |
3781 | 0 | } |
3782 | | |
3783 | | // Branch to the true- or false-expression based on the test-expression. We can skip the |
3784 | | // non-true path entirely since the test is known to be uniform. |
3785 | 0 | fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID); |
3786 | 0 | testStack.exit(); |
3787 | |
|
3788 | 0 | if (!this->pushExpression(ifTrue)) { |
3789 | 0 | return unsupported(); |
3790 | 0 | } |
3791 | | |
3792 | 0 | fBuilder.jump(exitLabelID); |
3793 | | |
3794 | | // The builder doesn't understand control flow, and assumes that every push moves the stack-top |
3795 | | // forwards. We need to manually balance out the `pushExpression` from the if-true path by |
3796 | | // moving the stack position backwards, so that the if-false path pushes its expression into the |
3797 | | // same as the if-true result. |
3798 | 0 | this->discardExpression(/*slots=*/ifTrue.type().slotCount()); |
3799 | |
|
3800 | 0 | fBuilder.label(falseLabelID); |
3801 | |
|
3802 | 0 | if (!this->pushExpression(ifFalse)) { |
3803 | 0 | return unsupported(); |
3804 | 0 | } |
3805 | | |
3806 | 0 | fBuilder.label(exitLabelID); |
3807 | | |
3808 | | // Jettison the text-expression from the separate stack. |
3809 | 0 | testStack.enter(); |
3810 | 0 | this->discardExpression(/*slots=*/1); |
3811 | 0 | testStack.exit(); |
3812 | 0 | return true; |
3813 | 0 | } Unexecuted instantiation: SkSL::RP::Generator::pushDynamicallyUniformTernaryExpression(SkSL::Expression const&, SkSL::Expression const&, SkSL::Expression const&) Unexecuted instantiation: SkSL::RP::Generator::pushDynamicallyUniformTernaryExpression(SkSL::Expression const&, SkSL::Expression const&, SkSL::Expression const&) |
3814 | | |
3815 | | bool Generator::pushTernaryExpression(const Expression& test, |
3816 | | const Expression& ifTrue, |
3817 | 28 | const Expression& ifFalse) { |
3818 | | // If the test-expression is dynamically-uniform, we can skip over the non-true expressions |
3819 | | // entirely, and not need to involve the condition mask. |
3820 | 28 | if (Analysis::IsDynamicallyUniformExpression(test)) { |
3821 | 0 | return this->pushDynamicallyUniformTernaryExpression(test, ifTrue, ifFalse); |
3822 | 0 | } |
3823 | | |
3824 | | // Analyze the ternary to see which corners we can safely cut. |
3825 | 28 | bool ifFalseHasSideEffects = Analysis::HasSideEffects(ifFalse); |
3826 | 28 | bool ifTrueHasSideEffects = Analysis::HasSideEffects(ifTrue); |
3827 | 28 | bool ifTrueIsTrivial = Analysis::IsTrivialExpression(ifTrue); |
3828 | 28 | int cleanupLabelID = fBuilder.nextLabelID(); |
3829 | | |
3830 | | // If the true- and false-expressions both lack side effects, we evaluate both of them safely |
3831 | | // without masking off their effects. In that case, we can emit both sides and use boolean mix |
3832 | | // to select the correct result without using the condition mask at all. |
3833 | 28 | if (!ifFalseHasSideEffects && !ifTrueHasSideEffects && ifTrueIsTrivial) { |
3834 | | // Push all of the arguments to mix. |
3835 | 12 | if (!this->pushVectorizedExpression(test, ifTrue.type())) { |
3836 | 0 | return unsupported(); |
3837 | 0 | } |
3838 | 12 | if (!this->pushExpression(ifFalse)) { |
3839 | 0 | return unsupported(); |
3840 | 0 | } |
3841 | 12 | if (!this->pushExpression(ifTrue)) { |
3842 | 0 | return unsupported(); |
3843 | 0 | } |
3844 | | // Use boolean mix to select the true- or false-expression via the test-expression. |
3845 | 12 | fBuilder.ternary_op(BuilderOp::mix_n_ints, ifTrue.type().slotCount()); |
3846 | 12 | return true; |
3847 | 12 | } |
3848 | | |
3849 | | // First, push the current condition-mask and the test-expression into a separate stack. |
3850 | 16 | fBuilder.enableExecutionMaskWrites(); |
3851 | 16 | AutoStack testStack(this); |
3852 | 16 | testStack.enter(); |
3853 | 16 | fBuilder.push_condition_mask(); |
3854 | 16 | if (!this->pushExpression(test)) { |
3855 | 0 | return unsupported(); |
3856 | 0 | } |
3857 | 16 | testStack.exit(); |
3858 | | |
3859 | | // We can take some shortcuts with condition-mask handling if the false-expression is entirely |
3860 | | // side-effect free. (We can evaluate it without masking off its effects.) We always handle the |
3861 | | // condition mask properly for the test-expression and true-expression properly. |
3862 | 16 | if (!ifFalseHasSideEffects) { |
3863 | | // Push the false-expression onto the primary stack. |
3864 | 16 | if (!this->pushExpression(ifFalse)) { |
3865 | 0 | return unsupported(); |
3866 | 0 | } |
3867 | | |
3868 | | // Next, merge the condition mask (on the separate stack) with the test expression. |
3869 | 16 | testStack.enter(); |
3870 | 16 | fBuilder.merge_condition_mask(); |
3871 | 16 | testStack.exit(); |
3872 | | |
3873 | | // If no lanes are active, we can skip the true-expression entirely. This isn't super likely |
3874 | | // to happen, so it's probably only a win for non-trivial true-expressions. |
3875 | 16 | if (!ifTrueIsTrivial) { |
3876 | 16 | fBuilder.branch_if_no_lanes_active(cleanupLabelID); |
3877 | 16 | } |
3878 | | |
3879 | | // Push the true-expression onto the primary stack, immediately after the false-expression. |
3880 | 16 | if (!this->pushExpression(ifTrue)) { |
3881 | 0 | return unsupported(); |
3882 | 0 | } |
3883 | | |
3884 | | // Use a select to conditionally mask-merge the true-expression and false-expression lanes. |
3885 | 16 | fBuilder.select(/*slots=*/ifTrue.type().slotCount()); |
3886 | 16 | fBuilder.label(cleanupLabelID); |
3887 | 16 | } else { |
3888 | | // Merge the condition mask (on the separate stack) with the test expression. |
3889 | 0 | testStack.enter(); |
3890 | 0 | fBuilder.merge_condition_mask(); |
3891 | 0 | testStack.exit(); |
3892 | | |
3893 | | // Push the true-expression onto the primary stack. |
3894 | 0 | if (!this->pushExpression(ifTrue)) { |
3895 | 0 | return unsupported(); |
3896 | 0 | } |
3897 | | |
3898 | | // Switch back to the test-expression stack and apply the inverted test condition. |
3899 | 0 | testStack.enter(); |
3900 | 0 | fBuilder.merge_inv_condition_mask(); |
3901 | 0 | testStack.exit(); |
3902 | | |
3903 | | // Push the false-expression onto the primary stack, immediately after the true-expression. |
3904 | 0 | if (!this->pushExpression(ifFalse)) { |
3905 | 0 | return unsupported(); |
3906 | 0 | } |
3907 | | |
3908 | | // Use a select to conditionally mask-merge the true-expression and false-expression lanes; |
3909 | | // the mask is already set up for this. |
3910 | 0 | fBuilder.select(/*slots=*/ifTrue.type().slotCount()); |
3911 | 0 | } |
3912 | | |
3913 | | // Restore the condition-mask to its original state and jettison the test-expression. |
3914 | 16 | testStack.enter(); |
3915 | 16 | this->discardExpression(/*slots=*/1); |
3916 | 16 | fBuilder.pop_condition_mask(); |
3917 | 16 | testStack.exit(); |
3918 | | |
3919 | 16 | fBuilder.disableExecutionMaskWrites(); |
3920 | 16 | return true; |
3921 | 16 | } |
3922 | | |
3923 | 636 | bool Generator::pushVariableReference(const VariableReference& var) { |
3924 | | // If we are pushing a constant-value variable, push the value directly; literal values are more |
3925 | | // amenable to optimization. |
3926 | 636 | if (var.type().isScalar() || var.type().isVector()) { |
3927 | 636 | if (const Expression* expr = ConstantFolder::GetConstantValueOrNull(var)) { |
3928 | 33 | return this->pushExpression(*expr); |
3929 | 33 | } |
3930 | 603 | if (fImmutableVariables.contains(var.variable())) { |
3931 | 0 | return this->pushExpression(*var.variable()->initialValue()); |
3932 | 0 | } |
3933 | 603 | } |
3934 | 603 | return this->pushVariableReferencePartial(var, SlotRange{0, (int)var.type().slotCount()}); |
3935 | 636 | } |
3936 | | |
3937 | 1.03k | bool Generator::pushVariableReferencePartial(const VariableReference& v, SlotRange subset) { |
3938 | 1.03k | const Variable& var = *v.variable(); |
3939 | 1.03k | SlotRange r; |
3940 | 1.03k | if (IsUniform(var)) { |
3941 | | // Push a uniform. |
3942 | 281 | r = this->getUniformSlots(var); |
3943 | 281 | SkASSERT(r.count == (int)var.type().slotCount()); |
3944 | 281 | r.index += subset.index; |
3945 | 281 | r.count = subset.count; |
3946 | 281 | fBuilder.push_uniform(r); |
3947 | 756 | } else if (fImmutableVariables.contains(&var)) { |
3948 | | // If we only need a single slot, we can push a constant. This saves a lookup, and can |
3949 | | // occasionally permit the use of an immediate-mode op. |
3950 | 0 | if (subset.count == 1) { |
3951 | 0 | const Expression& expr = *v.variable()->initialValue(); |
3952 | 0 | std::optional<ImmutableBits> bits = this->getImmutableBitsForSlot(expr, subset.index); |
3953 | 0 | if (bits.has_value()) { |
3954 | 0 | fBuilder.push_constant_i(*bits); |
3955 | 0 | return true; |
3956 | 0 | } |
3957 | 0 | } |
3958 | | // Push the immutable slot range. |
3959 | 0 | r = this->getImmutableSlots(var); |
3960 | 0 | SkASSERT(r.count == (int)var.type().slotCount()); |
3961 | 0 | r.index += subset.index; |
3962 | 0 | r.count = subset.count; |
3963 | 0 | fBuilder.push_immutable(r); |
3964 | 756 | } else { |
3965 | | // Push the variable. |
3966 | 756 | r = this->getVariableSlots(var); |
3967 | 756 | SkASSERT(r.count == (int)var.type().slotCount()); |
3968 | 756 | r.index += subset.index; |
3969 | 756 | r.count = subset.count; |
3970 | 756 | fBuilder.push_slots(r); |
3971 | 756 | } |
3972 | 1.03k | return true; |
3973 | 1.03k | } SkSL::RP::Generator::pushVariableReferencePartial(SkSL::VariableReference const&, SkSL::RP::SlotRange) Line | Count | Source | 3937 | 1.03k | bool Generator::pushVariableReferencePartial(const VariableReference& v, SlotRange subset) { | 3938 | 1.03k | const Variable& var = *v.variable(); | 3939 | 1.03k | SlotRange r; | 3940 | 1.03k | if (IsUniform(var)) { | 3941 | | // Push a uniform. | 3942 | 281 | r = this->getUniformSlots(var); | 3943 | 281 | SkASSERT(r.count == (int)var.type().slotCount()); | 3944 | 281 | r.index += subset.index; | 3945 | 281 | r.count = subset.count; | 3946 | 281 | fBuilder.push_uniform(r); | 3947 | 756 | } else if (fImmutableVariables.contains(&var)) { | 3948 | | // If we only need a single slot, we can push a constant. This saves a lookup, and can | 3949 | | // occasionally permit the use of an immediate-mode op. | 3950 | 0 | if (subset.count == 1) { | 3951 | 0 | const Expression& expr = *v.variable()->initialValue(); | 3952 | 0 | std::optional<ImmutableBits> bits = this->getImmutableBitsForSlot(expr, subset.index); | 3953 | 0 | if (bits.has_value()) { | 3954 | 0 | fBuilder.push_constant_i(*bits); | 3955 | 0 | return true; | 3956 | 0 | } | 3957 | 0 | } | 3958 | | // Push the immutable slot range. | 3959 | 0 | r = this->getImmutableSlots(var); | 3960 | 0 | SkASSERT(r.count == (int)var.type().slotCount()); | 3961 | 0 | r.index += subset.index; | 3962 | 0 | r.count = subset.count; | 3963 | 0 | fBuilder.push_immutable(r); | 3964 | 756 | } else { | 3965 | | // Push the variable. | 3966 | 756 | r = this->getVariableSlots(var); | 3967 | 756 | SkASSERT(r.count == (int)var.type().slotCount()); | 3968 | 756 | r.index += subset.index; | 3969 | 756 | r.count = subset.count; | 3970 | 756 | fBuilder.push_slots(r); | 3971 | 756 | } | 3972 | 1.03k | return true; | 3973 | 1.03k | } |
Unexecuted instantiation: SkSL::RP::Generator::pushVariableReferencePartial(SkSL::VariableReference const&, SkSL::RP::SlotRange) |
3974 | | |
3975 | 51 | bool Generator::writeProgram(const FunctionDefinition& function) { |
3976 | 51 | fCurrentFunction = &function; |
3977 | | |
3978 | 51 | if (fDebugTrace) { |
3979 | | // Copy the program source into the debug info so that it will be written in the trace file. |
3980 | 0 | fDebugTrace->setSource(*fProgram.fSource); |
3981 | |
|
3982 | 0 | if (fWriteTraceOps) { |
3983 | | // The Raster Pipeline blitter generates centered pixel coordinates. (0.5, 1.5, 2.5, |
3984 | | // etc.) Add 0.5 to the requested trace coordinate to match this, then compare against |
3985 | | // src.rg, which contains the shader's coordinates. We keep this result in a dedicated |
3986 | | // trace-mask stack. |
3987 | 0 | fTraceMask.emplace(this); |
3988 | 0 | fTraceMask->enter(); |
3989 | 0 | fBuilder.push_device_xy01(); |
3990 | 0 | fBuilder.discard_stack(2); |
3991 | 0 | fBuilder.push_constant_f(fDebugTrace->fTraceCoord.fX + 0.5f); |
3992 | 0 | fBuilder.push_constant_f(fDebugTrace->fTraceCoord.fY + 0.5f); |
3993 | 0 | fBuilder.binary_op(BuilderOp::cmpeq_n_floats, 2); |
3994 | 0 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1); |
3995 | 0 | fTraceMask->exit(); |
3996 | | |
3997 | | // Assemble a position-to-line-number mapping for the debugger. |
3998 | 0 | this->calculateLineOffsets(); |
3999 | 0 | } |
4000 | 0 | } |
4001 | | |
4002 | | // Assign slots to the parameters of main; copy src and dst into those slots as appropriate. |
4003 | 51 | const SkSL::Variable* mainCoordsParam = function.declaration().getMainCoordsParameter(); |
4004 | 51 | const SkSL::Variable* mainInputColorParam = function.declaration().getMainInputColorParameter(); |
4005 | 51 | const SkSL::Variable* mainDestColorParam = function.declaration().getMainDestColorParameter(); |
4006 | | |
4007 | 55 | for (const SkSL::Variable* param : function.declaration().parameters()) { |
4008 | 55 | if (param == mainCoordsParam) { |
4009 | | // Coordinates are passed via RG. |
4010 | 36 | SlotRange fragCoord = this->getVariableSlots(*param); |
4011 | 36 | SkASSERT(fragCoord.count == 2); |
4012 | 36 | fBuilder.store_src_rg(fragCoord); |
4013 | 36 | } else if (param == mainInputColorParam) { |
4014 | | // Input colors are passed via RGBA. |
4015 | 15 | SlotRange srcColor = this->getVariableSlots(*param); |
4016 | 15 | SkASSERT(srcColor.count == 4); |
4017 | 15 | fBuilder.store_src(srcColor); |
4018 | 15 | } else if (param == mainDestColorParam) { |
4019 | | // Dest colors are passed via dRGBA. |
4020 | 4 | SlotRange destColor = this->getVariableSlots(*param); |
4021 | 4 | SkASSERT(destColor.count == 4); |
4022 | 4 | fBuilder.store_dst(destColor); |
4023 | 4 | } else { |
4024 | 0 | SkDEBUGFAIL("Invalid parameter to main()"); |
4025 | 0 | return unsupported(); |
4026 | 0 | } |
4027 | 55 | } |
4028 | | |
4029 | | // Initialize the program. |
4030 | 51 | fBuilder.init_lane_masks(); |
4031 | | |
4032 | | // Emit global variables. |
4033 | 51 | if (!this->writeGlobals()) { |
4034 | 0 | return unsupported(); |
4035 | 0 | } |
4036 | | |
4037 | | // Invoke main(). |
4038 | 51 | std::optional<SlotRange> mainResult = this->writeFunction(function, function, /*arguments=*/{}); |
4039 | 51 | if (!mainResult.has_value()) { |
4040 | 0 | return unsupported(); |
4041 | 0 | } |
4042 | | |
4043 | | // Move the result of main() from slots into RGBA. |
4044 | 51 | SkASSERT(mainResult->count == 4); |
4045 | 51 | if (this->needsFunctionResultSlots(fCurrentFunction)) { |
4046 | 0 | fBuilder.load_src(*mainResult); |
4047 | 51 | } else { |
4048 | 51 | fBuilder.pop_src_rgba(); |
4049 | 51 | } |
4050 | | |
4051 | | // Discard the trace mask. |
4052 | 51 | if (fTraceMask.has_value()) { |
4053 | 0 | fTraceMask->enter(); |
4054 | 0 | fBuilder.discard_stack(1); |
4055 | 0 | fTraceMask->exit(); |
4056 | 0 | } |
4057 | | |
4058 | 51 | return true; |
4059 | 51 | } SkSL::RP::Generator::writeProgram(SkSL::FunctionDefinition const&) Line | Count | Source | 3975 | 51 | bool Generator::writeProgram(const FunctionDefinition& function) { | 3976 | 51 | fCurrentFunction = &function; | 3977 | | | 3978 | 51 | if (fDebugTrace) { | 3979 | | // Copy the program source into the debug info so that it will be written in the trace file. | 3980 | 0 | fDebugTrace->setSource(*fProgram.fSource); | 3981 | |
| 3982 | 0 | if (fWriteTraceOps) { | 3983 | | // The Raster Pipeline blitter generates centered pixel coordinates. (0.5, 1.5, 2.5, | 3984 | | // etc.) Add 0.5 to the requested trace coordinate to match this, then compare against | 3985 | | // src.rg, which contains the shader's coordinates. We keep this result in a dedicated | 3986 | | // trace-mask stack. | 3987 | 0 | fTraceMask.emplace(this); | 3988 | 0 | fTraceMask->enter(); | 3989 | 0 | fBuilder.push_device_xy01(); | 3990 | 0 | fBuilder.discard_stack(2); | 3991 | 0 | fBuilder.push_constant_f(fDebugTrace->fTraceCoord.fX + 0.5f); | 3992 | 0 | fBuilder.push_constant_f(fDebugTrace->fTraceCoord.fY + 0.5f); | 3993 | 0 | fBuilder.binary_op(BuilderOp::cmpeq_n_floats, 2); | 3994 | 0 | fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1); | 3995 | 0 | fTraceMask->exit(); | 3996 | | | 3997 | | // Assemble a position-to-line-number mapping for the debugger. | 3998 | 0 | this->calculateLineOffsets(); | 3999 | 0 | } | 4000 | 0 | } | 4001 | | | 4002 | | // Assign slots to the parameters of main; copy src and dst into those slots as appropriate. | 4003 | 51 | const SkSL::Variable* mainCoordsParam = function.declaration().getMainCoordsParameter(); | 4004 | 51 | const SkSL::Variable* mainInputColorParam = function.declaration().getMainInputColorParameter(); | 4005 | 51 | const SkSL::Variable* mainDestColorParam = function.declaration().getMainDestColorParameter(); | 4006 | | | 4007 | 55 | for (const SkSL::Variable* param : function.declaration().parameters()) { | 4008 | 55 | if (param == mainCoordsParam) { | 4009 | | // Coordinates are passed via RG. | 4010 | 36 | SlotRange fragCoord = this->getVariableSlots(*param); | 4011 | 36 | SkASSERT(fragCoord.count == 2); | 4012 | 36 | fBuilder.store_src_rg(fragCoord); | 4013 | 36 | } else if (param == mainInputColorParam) { | 4014 | | // Input colors are passed via RGBA. | 4015 | 15 | SlotRange srcColor = this->getVariableSlots(*param); | 4016 | 15 | SkASSERT(srcColor.count == 4); | 4017 | 15 | fBuilder.store_src(srcColor); | 4018 | 15 | } else if (param == mainDestColorParam) { | 4019 | | // Dest colors are passed via dRGBA. | 4020 | 4 | SlotRange destColor = this->getVariableSlots(*param); | 4021 | 4 | SkASSERT(destColor.count == 4); | 4022 | 4 | fBuilder.store_dst(destColor); | 4023 | 4 | } else { | 4024 | 0 | SkDEBUGFAIL("Invalid parameter to main()"); | 4025 | 0 | return unsupported(); | 4026 | 0 | } | 4027 | 55 | } | 4028 | | | 4029 | | // Initialize the program. | 4030 | 51 | fBuilder.init_lane_masks(); | 4031 | | | 4032 | | // Emit global variables. | 4033 | 51 | if (!this->writeGlobals()) { | 4034 | 0 | return unsupported(); | 4035 | 0 | } | 4036 | | | 4037 | | // Invoke main(). | 4038 | 51 | std::optional<SlotRange> mainResult = this->writeFunction(function, function, /*arguments=*/{}); | 4039 | 51 | if (!mainResult.has_value()) { | 4040 | 0 | return unsupported(); | 4041 | 0 | } | 4042 | | | 4043 | | // Move the result of main() from slots into RGBA. | 4044 | 51 | SkASSERT(mainResult->count == 4); | 4045 | 51 | if (this->needsFunctionResultSlots(fCurrentFunction)) { | 4046 | 0 | fBuilder.load_src(*mainResult); | 4047 | 51 | } else { | 4048 | 51 | fBuilder.pop_src_rgba(); | 4049 | 51 | } | 4050 | | | 4051 | | // Discard the trace mask. | 4052 | 51 | if (fTraceMask.has_value()) { | 4053 | 0 | fTraceMask->enter(); | 4054 | 0 | fBuilder.discard_stack(1); | 4055 | 0 | fTraceMask->exit(); | 4056 | 0 | } | 4057 | | | 4058 | 51 | return true; | 4059 | 51 | } |
Unexecuted instantiation: SkSL::RP::Generator::writeProgram(SkSL::FunctionDefinition const&) |
4060 | | |
4061 | 51 | std::unique_ptr<RP::Program> Generator::finish() { |
4062 | 51 | return fBuilder.finish(fProgramSlots.slotCount(), |
4063 | 51 | fUniformSlots.slotCount(), |
4064 | 51 | fImmutableSlots.slotCount(), |
4065 | 51 | fDebugTrace); |
4066 | 51 | } |
4067 | | |
4068 | | } // namespace RP |
4069 | | |
4070 | | std::unique_ptr<RP::Program> MakeRasterPipelineProgram(const SkSL::Program& program, |
4071 | | const FunctionDefinition& function, |
4072 | | DebugTracePriv* debugTrace, |
4073 | 51 | bool writeTraceOps) { |
4074 | 51 | RP::Generator generator(program, debugTrace, writeTraceOps); |
4075 | 51 | if (!generator.writeProgram(function)) { |
4076 | 0 | return nullptr; |
4077 | 0 | } |
4078 | 51 | return generator.finish(); |
4079 | 51 | } |
4080 | | |
4081 | | } // namespace SkSL |