Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkSLSPIRVCodeGenerator.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2016 Google Inc.
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
9
10#include "include/core/SkSpan.h"
15#include "src/core/SkChecksum.h"
16#include "src/core/SkTHash.h"
30#include "src/sksl/SkSLPool.h"
34#include "src/sksl/SkSLUtil.h"
82#include "src/sksl/spirv.h"
84#include "src/utils/SkBitSet.h"
85
86#include <cstdint>
87#include <cstring>
88#include <memory>
89#include <set>
90#include <string>
91#include <string_view>
92#include <tuple>
93#include <utility>
94#include <vector>
95
96#ifdef SK_ENABLE_SPIRV_VALIDATION
97#include "spirv-tools/libspirv.hpp"
98#endif
99
100using namespace skia_private;
101
102#define kLast_Capability SpvCapabilityMultiViewport
103
104constexpr int DEVICE_FRAGCOORDS_BUILTIN = -1000;
105constexpr int DEVICE_CLOCKWISE_BUILTIN = -1001;
107
108namespace SkSL {
109
110enum class ProgramKind : int8_t;
111
113public:
114 // We reserve an impossible SpvId as a sentinel. (NA meaning none, n/a, etc.)
115 static constexpr SpvId NA = (SpvId)-1;
116
117 class LValue {
118 public:
119 virtual ~LValue() {}
120
121 // returns a pointer to the lvalue, if possible. If the lvalue cannot be directly referenced
122 // by a pointer (e.g. vector swizzles), returns NA.
123 virtual SpvId getPointer() { return NA; }
124
125 // Returns true if a valid pointer returned by getPointer represents a memory object
126 // (see https://github.com/KhronosGroup/SPIRV-Tools/issues/2892). Has no meaning if
127 // getPointer() returns NA.
128 virtual bool isMemoryObjectPointer() const { return true; }
129
130 // Applies a swizzle to the components of the LValue, if possible. This is used to create
131 // LValues that are swizzes-of-swizzles. Non-swizzle LValues can just return false.
132 virtual bool applySwizzle(const ComponentArray& components, const Type& newType) {
133 return false;
134 }
135
136 // Returns the storage class of the lvalue.
137 virtual SpvStorageClass storageClass() const = 0;
138
139 virtual SpvId load(OutputStream& out) = 0;
140
141 virtual void store(SpvId value, OutputStream& out) = 0;
142 };
143
145 const ShaderCaps* caps,
146 const Program* program,
147 OutputStream* out)
148 : INHERITED(context, caps, program, out) {}
149
150 bool generateCode() override;
151
152private:
153 enum IntrinsicOpcodeKind {
154 kGLSL_STD_450_IntrinsicOpcodeKind,
155 kSPIRV_IntrinsicOpcodeKind,
156 kSpecial_IntrinsicOpcodeKind,
157 kInvalid_IntrinsicOpcodeKind,
158 };
159
160 enum SpecialIntrinsic {
161 kAtan_SpecialIntrinsic,
162 kClamp_SpecialIntrinsic,
163 kMatrixCompMult_SpecialIntrinsic,
164 kMax_SpecialIntrinsic,
165 kMin_SpecialIntrinsic,
166 kMix_SpecialIntrinsic,
167 kMod_SpecialIntrinsic,
168 kDFdy_SpecialIntrinsic,
169 kSaturate_SpecialIntrinsic,
170 kSampledImage_SpecialIntrinsic,
171 kSmoothStep_SpecialIntrinsic,
172 kStep_SpecialIntrinsic,
173 kSubpassLoad_SpecialIntrinsic,
174 kTexture_SpecialIntrinsic,
175 kTextureGrad_SpecialIntrinsic,
176 kTextureLod_SpecialIntrinsic,
177 kTextureRead_SpecialIntrinsic,
178 kTextureWrite_SpecialIntrinsic,
179 kTextureWidth_SpecialIntrinsic,
180 kTextureHeight_SpecialIntrinsic,
181 kAtomicAdd_SpecialIntrinsic,
182 kAtomicLoad_SpecialIntrinsic,
183 kAtomicStore_SpecialIntrinsic,
184 kStorageBarrier_SpecialIntrinsic,
185 kWorkgroupBarrier_SpecialIntrinsic,
186 };
187
188 enum class Precision {
189 kDefault,
190 kRelaxed,
191 };
192
193 struct TempVar {
194 SpvId spvId;
195 const Type* type;
196 std::unique_ptr<SPIRVCodeGenerator::LValue> lvalue;
197 };
198
199 /**
200 * Pass in the type to automatically add a RelaxedPrecision decoration for the id when
201 * appropriate, or null to never add one.
202 */
203 SpvId nextId(const Type* type);
204
205 SpvId nextId(Precision precision);
206
207 SpvId getType(const Type& type);
208
209 SpvId getType(const Type& type, const Layout& typeLayout, const MemoryLayout& memoryLayout);
210
211 SpvId getFunctionType(const FunctionDeclaration& function);
212
213 SpvId getFunctionParameterType(const Type& parameterType, const Layout& parameterLayout);
214
215 SpvId getPointerType(const Type& type, SpvStorageClass_ storageClass);
216
217 SpvId getPointerType(const Type& type,
218 const Layout& typeLayout,
219 const MemoryLayout& memoryLayout,
220 SpvStorageClass_ storageClass);
221
222 skia_private::TArray<SpvId> getAccessChain(const Expression& expr, OutputStream& out);
223
224 void writeLayout(const Layout& layout, SpvId target, Position pos);
225
226 void writeFieldLayout(const Layout& layout, SpvId target, int member);
227
228 SpvId writeStruct(const Type& type, const MemoryLayout& memoryLayout);
229
230 void writeProgramElement(const ProgramElement& pe, OutputStream& out);
231
232 SpvId writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip = true);
233
234 SpvId writeFunctionStart(const FunctionDeclaration& f, OutputStream& out);
235
236 SpvId writeFunctionDeclaration(const FunctionDeclaration& f, OutputStream& out);
237
238 SpvId writeFunction(const FunctionDefinition& f, OutputStream& out);
239
240 bool writeGlobalVarDeclaration(ProgramKind kind, const VarDeclaration& v);
241
242 SpvId writeGlobalVar(ProgramKind kind, SpvStorageClass_, const Variable& v);
243
244 void writeVarDeclaration(const VarDeclaration& var, OutputStream& out);
245
246 SpvId writeVariableReference(const VariableReference& ref, OutputStream& out);
247
248 int findUniformFieldIndex(const Variable& var) const;
249
250 std::unique_ptr<LValue> getLValue(const Expression& value, OutputStream& out);
251
252 SpvId writeExpression(const Expression& expr, OutputStream& out);
253
254 SpvId writeIntrinsicCall(const FunctionCall& c, OutputStream& out);
255
256 SpvId writeFunctionCallArgument(const FunctionCall& call,
257 int argIndex,
258 std::vector<TempVar>* tempVars,
259 OutputStream& out,
260 SpvId* outSynthesizedSamplerId = nullptr);
261
262 void copyBackTempVars(const std::vector<TempVar>& tempVars, OutputStream& out);
263
264 SpvId writeFunctionCall(const FunctionCall& c, OutputStream& out);
265
266
267 void writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst,
268 SpvId signedInst, SpvId unsignedInst,
269 const skia_private::TArray<SpvId>& args, OutputStream& out);
270
271 /**
272 * Promotes an expression to a vector. If the expression is already a vector with vectorSize
273 * columns, returns it unmodified. If the expression is a scalar, either promotes it to a
274 * vector (if vectorSize > 1) or returns it unmodified (if vectorSize == 1). Asserts if the
275 * expression is already a vector and it does not have vectorSize columns.
276 */
277 SpvId vectorize(const Expression& expr, int vectorSize, OutputStream& out);
278
279 /**
280 * Given a list of potentially mixed scalars and vectors, promotes the scalars to match the
281 * size of the vectors and returns the ids of the written expressions. e.g. given (float, vec2),
282 * returns (vec2(float), vec2). It is an error to use mismatched vector sizes, e.g. (float,
283 * vec2, vec3).
284 */
285 skia_private::TArray<SpvId> vectorize(const ExpressionArray& args, OutputStream& out);
286
287 SpvId writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind, OutputStream& out);
288 SpvId writeAtomicIntrinsic(const FunctionCall& c,
289 SpecialIntrinsic kind,
290 SpvId resultId,
291 OutputStream& out);
292
293 SpvId writeScalarToMatrixSplat(const Type& matrixType, SpvId scalarId, OutputStream& out);
294
295 SpvId castScalarToFloat(SpvId inputId, const Type& inputType, const Type& outputType,
296 OutputStream& out);
297
298 SpvId castScalarToSignedInt(SpvId inputId, const Type& inputType, const Type& outputType,
299 OutputStream& out);
300
301 SpvId castScalarToUnsignedInt(SpvId inputId, const Type& inputType, const Type& outputType,
302 OutputStream& out);
303
304 SpvId castScalarToBoolean(SpvId inputId, const Type& inputType, const Type& outputType,
305 OutputStream& out);
306
307 SpvId castScalarToType(SpvId inputExprId, const Type& inputType, const Type& outputType,
308 OutputStream& out);
309
310 /**
311 * Writes a potentially-different-sized copy of a matrix. Entries which do not exist in the
312 * source matrix are filled with zero; entries which do not exist in the destination matrix are
313 * ignored.
314 */
315 SpvId writeMatrixCopy(SpvId src, const Type& srcType, const Type& dstType, OutputStream& out);
316
317 void addColumnEntry(const Type& columnType,
318 skia_private::TArray<SpvId>* currentColumn,
320 int rows, SpvId entry, OutputStream& out);
321
322 SpvId writeConstructorCompound(const ConstructorCompound& c, OutputStream& out);
323
324 SpvId writeMatrixConstructor(const ConstructorCompound& c, OutputStream& out);
325
326 SpvId writeVectorConstructor(const ConstructorCompound& c, OutputStream& out);
327
328 SpvId writeCompositeConstructor(const AnyConstructor& c, OutputStream& out);
329
330 SpvId writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, OutputStream& out);
331
332 SpvId writeConstructorMatrixResize(const ConstructorMatrixResize& c, OutputStream& out);
333
334 SpvId writeConstructorScalarCast(const ConstructorScalarCast& c, OutputStream& out);
335
336 SpvId writeConstructorSplat(const ConstructorSplat& c, OutputStream& out);
337
338 SpvId writeConstructorCompoundCast(const ConstructorCompoundCast& c, OutputStream& out);
339
340 SpvId writeFieldAccess(const FieldAccess& f, OutputStream& out);
341
342 SpvId writeSwizzle(const Expression& baseExpr,
343 const ComponentArray& components,
344 OutputStream& out);
345
346 SpvId writeSwizzle(const Swizzle& swizzle, OutputStream& out);
347
348 /**
349 * Folds the potentially-vector result of a logical operation down to a single bool. If
350 * operandType is a vector type, assumes that the intermediate result in id is a bvec of the
351 * same dimensions, and applys all() to it to fold it down to a single bool value. Otherwise,
352 * returns the original id value.
353 */
354 SpvId foldToBool(SpvId id, const Type& operandType, SpvOp op, OutputStream& out);
355
356 SpvId writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs, SpvOp_ floatOperator,
357 SpvOp_ intOperator, SpvOp_ vectorMergeOperator,
358 SpvOp_ mergeOperator, OutputStream& out);
359
360 SpvId writeStructComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs,
361 OutputStream& out);
362
363 SpvId writeArrayComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs,
364 OutputStream& out);
365
366 // Used by writeStructComparison and writeArrayComparison to logically combine field-by-field
367 // comparisons into an overall comparison result.
368 // - `a.x == b.x` merged with `a.y == b.y` generates `(a.x == b.x) && (a.y == b.y)`
369 // - `a.x != b.x` merged with `a.y != b.y` generates `(a.x != b.x) || (a.y != b.y)`
370 SpvId mergeComparisons(SpvId comparison, SpvId allComparisons, Operator op, OutputStream& out);
371
372 // When the RewriteMatrixVectorMultiply caps bit is set, we manually decompose the M*V
373 // multiplication into a sum of vector-scalar products.
374 SpvId writeDecomposedMatrixVectorMultiply(const Type& leftType,
375 SpvId lhs,
376 const Type& rightType,
377 SpvId rhs,
378 const Type& resultType,
379 OutputStream& out);
380
381 SpvId writeComponentwiseMatrixUnary(const Type& operandType,
382 SpvId operand,
383 SpvOp_ op,
384 OutputStream& out);
385
386 SpvId writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, SpvId rhs,
387 SpvOp_ op, OutputStream& out);
388
389 SpvId writeBinaryOperation(const Type& resultType, const Type& operandType, SpvId lhs,
390 SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, SpvOp_ ifUInt,
391 SpvOp_ ifBool, OutputStream& out);
392
393 SpvId writeReciprocal(const Type& type, SpvId value, OutputStream& out);
394
395 SpvId writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op,
396 const Type& rightType, SpvId rhs, const Type& resultType,
397 OutputStream& out);
398
399 SpvId writeBinaryExpression(const BinaryExpression& b, OutputStream& out);
400
401 SpvId writeTernaryExpression(const TernaryExpression& t, OutputStream& out);
402
403 SpvId writeIndexExpression(const IndexExpression& expr, OutputStream& out);
404
405 SpvId writeLogicalAnd(const Expression& left, const Expression& right, OutputStream& out);
406
407 SpvId writeLogicalOr(const Expression& left, const Expression& right, OutputStream& out);
408
409 SpvId writePrefixExpression(const PrefixExpression& p, OutputStream& out);
410
411 SpvId writePostfixExpression(const PostfixExpression& p, OutputStream& out);
412
413 SpvId writeLiteral(const Literal& f);
414
415 SpvId writeLiteral(double value, const Type& type);
416
417 void writeStatement(const Statement& s, OutputStream& out);
418
419 void writeBlock(const Block& b, OutputStream& out);
420
421 void writeIfStatement(const IfStatement& stmt, OutputStream& out);
422
423 void writeForStatement(const ForStatement& f, OutputStream& out);
424
425 void writeDoStatement(const DoStatement& d, OutputStream& out);
426
427 void writeSwitchStatement(const SwitchStatement& s, OutputStream& out);
428
429 void writeReturnStatement(const ReturnStatement& r, OutputStream& out);
430
431 void writeCapabilities(OutputStream& out);
432
433 void writeInstructions(const Program& program, OutputStream& out);
434
435 void writeOpCode(SpvOp_ opCode, int length, OutputStream& out);
436
437 void writeWord(int32_t word, OutputStream& out);
438
439 void writeString(std::string_view s, OutputStream& out);
440
441 void writeInstruction(SpvOp_ opCode, OutputStream& out);
442
443 void writeInstruction(SpvOp_ opCode, std::string_view string, OutputStream& out);
444
445 void writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out);
446
447 void writeInstruction(SpvOp_ opCode, int32_t word1, std::string_view string,
448 OutputStream& out);
449
450 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, std::string_view string,
451 OutputStream& out);
452
453 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, OutputStream& out);
454
455 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3,
456 OutputStream& out);
457
458 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
459 OutputStream& out);
460
461 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
462 int32_t word5, OutputStream& out);
463
464 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
465 int32_t word5, int32_t word6, OutputStream& out);
466
467 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
468 int32_t word5, int32_t word6, int32_t word7, OutputStream& out);
469
470 void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
471 int32_t word5, int32_t word6, int32_t word7, int32_t word8,
472 OutputStream& out);
473
474 // This form of writeInstruction can deduplicate redundant ops.
475 struct Word;
476 // 8 Words is enough for nearly all instructions (except variable-length instructions like
477 // OpAccessChain or OpConstantComposite).
479 SpvId writeInstruction(
480 SpvOp_ opCode, const skia_private::TArray<Word, true>& words, OutputStream& out);
481
482 struct Instruction {
483 SpvId fOp;
484 int32_t fResultKind;
486
487 bool operator==(const Instruction& that) const;
488 struct Hash;
489 };
490
491 static Instruction BuildInstructionKey(SpvOp_ opCode,
493
494 // The writeOpXxxxx calls will simplify and deduplicate ops where possible.
495 SpvId writeOpConstantTrue(const Type& type);
496 SpvId writeOpConstantFalse(const Type& type);
497 SpvId writeOpConstant(const Type& type, int32_t valueBits);
498 SpvId writeOpConstantComposite(const Type& type, const skia_private::TArray<SpvId>& values);
499 SpvId writeOpCompositeConstruct(const Type& type, const skia_private::TArray<SpvId>&,
500 OutputStream& out);
501 SpvId writeOpCompositeExtract(const Type& type, SpvId base, int component, OutputStream& out);
502 SpvId writeOpCompositeExtract(const Type& type, SpvId base, int componentA, int componentB,
503 OutputStream& out);
504 SpvId writeOpLoad(SpvId type, Precision precision, SpvId pointer, OutputStream& out);
505 void writeOpStore(SpvStorageClass_ storageClass, SpvId pointer, SpvId value, OutputStream& out);
506
507 // Converts the provided SpvId(s) into an array of scalar OpConstants, if it can be done.
508 bool toConstants(SpvId value, skia_private::TArray<SpvId>* constants);
509 bool toConstants(SkSpan<const SpvId> values, skia_private::TArray<SpvId>* constants);
510
511 // Extracts the requested component SpvId from a composite instruction, if it can be done.
512 Instruction* resultTypeForInstruction(const Instruction& instr);
513 int numComponentsForVecInstruction(const Instruction& instr);
514 SpvId toComponent(SpvId id, int component);
515
516 struct ConditionalOpCounts {
517 int numReachableOps;
518 int numStoreOps;
519 };
520 ConditionalOpCounts getConditionalOpCounts();
521 void pruneConditionalOps(ConditionalOpCounts ops);
522
523 enum StraightLineLabelType {
524 // Use "BranchlessBlock" for blocks which are never explicitly branched-to at all. This
525 // happens at the start of a function, or when we find unreachable code.
526 kBranchlessBlock,
527
528 // Use "BranchIsOnPreviousLine" when writing a label that comes immediately after its
529 // associated branch. Example usage:
530 // - SPIR-V does not implicitly fall through from one block to the next, so you may need to
531 // use an OpBranch to explicitly jump to the next block, even when they are adjacent in
532 // the code.
533 // - The block immediately following an OpBranchConditional or OpSwitch.
534 kBranchIsOnPreviousLine,
535 };
536
537 enum BranchingLabelType {
538 // Use "BranchIsAbove" for labels which are referenced by OpBranch or OpBranchConditional
539 // ops that are above the label in the code--i.e., the branch skips forward in the code.
540 kBranchIsAbove,
541
542 // Use "BranchIsBelow" for labels which are referenced by OpBranch or OpBranchConditional
543 // ops below the label in the code--i.e., the branch jumps backward in the code.
544 kBranchIsBelow,
545
546 // Use "BranchesOnBothSides" for labels which have branches coming from both directions.
547 kBranchesOnBothSides,
548 };
549 void writeLabel(SpvId label, StraightLineLabelType type, OutputStream& out);
550 void writeLabel(SpvId label, BranchingLabelType type, ConditionalOpCounts ops,
551 OutputStream& out);
552
553 MemoryLayout memoryLayoutForStorageClass(SpvStorageClass_ storageClass);
554 MemoryLayout memoryLayoutForVariable(const Variable&) const;
555
556 struct EntrypointAdapter {
557 std::unique_ptr<FunctionDefinition> entrypointDef;
558 std::unique_ptr<FunctionDeclaration> entrypointDecl;
559 };
560
561 EntrypointAdapter writeEntrypointAdapter(const FunctionDeclaration& main);
562
563 struct UniformBuffer {
564 std::unique_ptr<InterfaceBlock> fInterfaceBlock;
565 std::unique_ptr<Variable> fInnerVariable;
566 std::unique_ptr<Type> fStruct;
567 };
568
569 void writeUniformBuffer(SymbolTable* topLevelSymbolTable);
570
571 void addRTFlipUniform(Position pos);
572
573 std::unique_ptr<Expression> identifier(std::string_view name);
574
575 std::tuple<const Variable*, const Variable*> synthesizeTextureAndSampler(
576 const Variable& combinedSampler);
577
578 const MemoryLayout fDefaultMemoryLayout{MemoryLayout::Standard::k140};
579
580 uint64_t fCapabilities = 0;
581 SpvId fIdCount = 1;
582 SpvId fGLSLExtendedInstructions;
583 struct Intrinsic {
584 IntrinsicOpcodeKind opKind;
585 int32_t floatOp;
586 int32_t signedOp;
587 int32_t unsignedOp;
588 int32_t boolOp;
589 };
590 Intrinsic getIntrinsic(IntrinsicKind) const;
594 StringStream fGlobalInitializersBuffer;
595 StringStream fConstantBuffer;
596 StringStream fVariableBuffer;
597 StringStream fNameBuffer;
598 StringStream fDecorationBuffer;
599
600 // Mapping from combined sampler declarations to synthesized texture/sampler variables.
601 // This is used when the sampler is declared as `layout(webgpu)` or `layout(direct3d)`.
602 bool fUseTextureSamplerPairs = false;
603 struct SynthesizedTextureSamplerPair {
604 // The names of the synthesized variables. The Variable objects themselves store string
605 // views referencing these strings. It is important for the std::string instances to have a
606 // fixed memory location after the string views get created, which is why
607 // `fSynthesizedSamplerMap` stores unique_ptr instead of values.
608 std::string fTextureName;
609 std::string fSamplerName;
610 std::unique_ptr<Variable> fTexture;
611 std::unique_ptr<Variable> fSampler;
612 };
614 fSynthesizedSamplerMap;
615
616 // These caches map SpvIds to Instructions, and vice-versa. This enables us to deduplicate code
617 // (by detecting an Instruction we've already issued and reusing the SpvId), and to introspect
618 // and simplify code we've already emitted (by taking a SpvId from an Instruction and following
619 // it back to its source).
620
621 // A map of instruction -> SpvId:
623 // A map of SpvId -> instruction:
625 // A map of SpvId -> value SpvId:
627
628 // "Reachable" ops are instructions which can safely be accessed from the current block.
629 // For instance, if our SPIR-V contains `%3 = OpFAdd %1 %2`, we would be able to access and
630 // reuse that computation on following lines. However, if that Add operation occurred inside an
631 // `if` block, then its SpvId becomes inaccessible once we complete the if statement (since
632 // depending on the if condition, we may or may not have actually done that computation). The
633 // same logic applies to other control-flow blocks as well. Once an instruction becomes
634 // unreachable, we remove it from both op-caches.
635 skia_private::TArray<SpvId> fReachableOps;
636
637 // The "store-ops" list contains a running list of all the pointers in the store cache. If a
638 // store occurs inside of a conditional block, once that block exits, we no longer know what is
639 // stored in that particular SpvId. At that point, we must remove any associated entry from the
640 // store cache.
642
643 // label of the current block, or 0 if we are not in a block
644 SpvId fCurrentBlock = 0;
645 skia_private::TArray<SpvId> fBreakTarget;
646 skia_private::TArray<SpvId> fContinueTarget;
647 bool fWroteRTFlip = false;
648 // holds variables synthesized during output, for lifetime purposes
649 SymbolTable fSynthetics{/*builtin=*/true};
650 // Holds a list of uniforms that were declared as globals at the top-level instead of in an
651 // interface block.
652 UniformBuffer fUniformBuffer;
653 std::vector<const VarDeclaration*> fTopLevelUniforms;
655 fTopLevelUniformMap; // <var, UniformBuffer field index>
656 SpvId fUniformBufferId = NA;
657
658 friend class PointerLValue;
659 friend class SwizzleLValue;
660
661 using INHERITED = CodeGenerator;
662};
663
664// Equality and hash operators for Instructions.
665bool SPIRVCodeGenerator::Instruction::operator==(const SPIRVCodeGenerator::Instruction& that) const {
666 return fOp == that.fOp &&
667 fResultKind == that.fResultKind &&
668 fWords == that.fWords;
669}
670
672 uint32_t operator()(const SPIRVCodeGenerator::Instruction& key) const {
673 uint32_t hash = key.fResultKind;
674 hash = SkChecksum::Hash32(&key.fOp, sizeof(key.fOp), hash);
675 hash = SkChecksum::Hash32(key.fWords.data(), key.fWords.size() * sizeof(int32_t), hash);
676 return hash;
677 }
678};
679
680// This class is used to pass values and result placeholder slots to writeInstruction.
682 enum Kind {
683 kNone, // intended for use as a sentinel, not part of any Instruction
690 };
691
693 Word(int32_t val, Kind kind) : fValue(val), fKind(kind) {}
694
695 static Word Number(int32_t val) {
696 return Word{val, Kind::kNumber};
697 }
698
699 static Word Result(const Type& type) {
700 return (type.hasPrecision() && !type.highPrecision()) ? RelaxedResult() : Result();
701 }
702
704 return Word{(int32_t)NA, kRelaxedPrecisionResult};
705 }
706
708 return Word{(int32_t)NA, kUniqueResult};
709 }
710
711 static Word Result() {
712 return Word{(int32_t)NA, kDefaultPrecisionResult};
713 }
714
715 // Unlike a Result (where the result ID is always deduplicated to its first instruction) or a
716 // UniqueResult (which always produces a new instruction), a KeyedResult allows an instruction
717 // to be deduplicated among those that share the same `key`.
718 static Word KeyedResult(int32_t key) { return Word{key, Kind::kKeyedResult}; }
719
720 bool isResult() const { return fKind >= Kind::kDefaultPrecisionResult; }
721
722 int32_t fValue;
724};
725
726// Skia's magic number is 31 and goes in the top 16 bits. We can use the lower bits to version the
727// sksl generator if we want.
728// https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/spir-v.xml#L84
729static const int32_t SKSL_MAGIC = 0x001F0000;
730
731SPIRVCodeGenerator::Intrinsic SPIRVCodeGenerator::getIntrinsic(IntrinsicKind ik) const {
732
733#define ALL_GLSL(x) Intrinsic{kGLSL_STD_450_IntrinsicOpcodeKind, GLSLstd450 ## x, \
734 GLSLstd450 ## x, GLSLstd450 ## x, GLSLstd450 ## x}
735#define BY_TYPE_GLSL(ifFloat, ifInt, ifUInt) Intrinsic{kGLSL_STD_450_IntrinsicOpcodeKind, \
736 GLSLstd450 ## ifFloat, \
737 GLSLstd450 ## ifInt, \
738 GLSLstd450 ## ifUInt, \
739 SpvOpUndef}
740#define ALL_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \
741 SpvOp ## x, SpvOp ## x, SpvOp ## x, SpvOp ## x}
742#define BOOL_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \
743 SpvOpUndef, SpvOpUndef, SpvOpUndef, SpvOp ## x}
744#define FLOAT_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \
745 SpvOp ## x, SpvOpUndef, SpvOpUndef, SpvOpUndef}
746#define SPECIAL(x) Intrinsic{kSpecial_IntrinsicOpcodeKind, k ## x ## _SpecialIntrinsic, \
747 k ## x ## _SpecialIntrinsic, k ## x ## _SpecialIntrinsic, \
748 k ## x ## _SpecialIntrinsic}
749
750 switch (ik) {
751 case k_round_IntrinsicKind: return ALL_GLSL(Round);
752 case k_roundEven_IntrinsicKind: return ALL_GLSL(RoundEven);
753 case k_trunc_IntrinsicKind: return ALL_GLSL(Trunc);
754 case k_abs_IntrinsicKind: return BY_TYPE_GLSL(FAbs, SAbs, SAbs);
755 case k_sign_IntrinsicKind: return BY_TYPE_GLSL(FSign, SSign, SSign);
756 case k_floor_IntrinsicKind: return ALL_GLSL(Floor);
757 case k_ceil_IntrinsicKind: return ALL_GLSL(Ceil);
758 case k_fract_IntrinsicKind: return ALL_GLSL(Fract);
759 case k_radians_IntrinsicKind: return ALL_GLSL(Radians);
760 case k_degrees_IntrinsicKind: return ALL_GLSL(Degrees);
761 case k_sin_IntrinsicKind: return ALL_GLSL(Sin);
762 case k_cos_IntrinsicKind: return ALL_GLSL(Cos);
763 case k_tan_IntrinsicKind: return ALL_GLSL(Tan);
764 case k_asin_IntrinsicKind: return ALL_GLSL(Asin);
765 case k_acos_IntrinsicKind: return ALL_GLSL(Acos);
766 case k_atan_IntrinsicKind: return SPECIAL(Atan);
767 case k_sinh_IntrinsicKind: return ALL_GLSL(Sinh);
768 case k_cosh_IntrinsicKind: return ALL_GLSL(Cosh);
769 case k_tanh_IntrinsicKind: return ALL_GLSL(Tanh);
770 case k_asinh_IntrinsicKind: return ALL_GLSL(Asinh);
771 case k_acosh_IntrinsicKind: return ALL_GLSL(Acosh);
772 case k_atanh_IntrinsicKind: return ALL_GLSL(Atanh);
773 case k_pow_IntrinsicKind: return ALL_GLSL(Pow);
774 case k_exp_IntrinsicKind: return ALL_GLSL(Exp);
775 case k_log_IntrinsicKind: return ALL_GLSL(Log);
776 case k_exp2_IntrinsicKind: return ALL_GLSL(Exp2);
777 case k_log2_IntrinsicKind: return ALL_GLSL(Log2);
778 case k_sqrt_IntrinsicKind: return ALL_GLSL(Sqrt);
779 case k_inverse_IntrinsicKind: return ALL_GLSL(MatrixInverse);
780 case k_outerProduct_IntrinsicKind: return ALL_SPIRV(OuterProduct);
781 case k_transpose_IntrinsicKind: return ALL_SPIRV(Transpose);
782 case k_isinf_IntrinsicKind: return ALL_SPIRV(IsInf);
783 case k_isnan_IntrinsicKind: return ALL_SPIRV(IsNan);
784 case k_inversesqrt_IntrinsicKind: return ALL_GLSL(InverseSqrt);
785 case k_determinant_IntrinsicKind: return ALL_GLSL(Determinant);
786 case k_matrixCompMult_IntrinsicKind: return SPECIAL(MatrixCompMult);
787 case k_matrixInverse_IntrinsicKind: return ALL_GLSL(MatrixInverse);
788 case k_mod_IntrinsicKind: return SPECIAL(Mod);
789 case k_modf_IntrinsicKind: return ALL_GLSL(Modf);
790 case k_min_IntrinsicKind: return SPECIAL(Min);
791 case k_max_IntrinsicKind: return SPECIAL(Max);
792 case k_clamp_IntrinsicKind: return SPECIAL(Clamp);
793 case k_saturate_IntrinsicKind: return SPECIAL(Saturate);
794 case k_dot_IntrinsicKind: return FLOAT_SPIRV(Dot);
795 case k_mix_IntrinsicKind: return SPECIAL(Mix);
796 case k_step_IntrinsicKind: return SPECIAL(Step);
797 case k_smoothstep_IntrinsicKind: return SPECIAL(SmoothStep);
798 case k_fma_IntrinsicKind: return ALL_GLSL(Fma);
799 case k_frexp_IntrinsicKind: return ALL_GLSL(Frexp);
800 case k_ldexp_IntrinsicKind: return ALL_GLSL(Ldexp);
801
802#define PACK(type) case k_pack##type##_IntrinsicKind: return ALL_GLSL(Pack##type); \
803 case k_unpack##type##_IntrinsicKind: return ALL_GLSL(Unpack##type)
804 PACK(Snorm4x8);
805 PACK(Unorm4x8);
806 PACK(Snorm2x16);
807 PACK(Unorm2x16);
808 PACK(Half2x16);
809#undef PACK
810
811 case k_length_IntrinsicKind: return ALL_GLSL(Length);
812 case k_distance_IntrinsicKind: return ALL_GLSL(Distance);
813 case k_cross_IntrinsicKind: return ALL_GLSL(Cross);
814 case k_normalize_IntrinsicKind: return ALL_GLSL(Normalize);
815 case k_faceforward_IntrinsicKind: return ALL_GLSL(FaceForward);
816 case k_reflect_IntrinsicKind: return ALL_GLSL(Reflect);
817 case k_refract_IntrinsicKind: return ALL_GLSL(Refract);
818 case k_bitCount_IntrinsicKind: return ALL_SPIRV(BitCount);
819 case k_findLSB_IntrinsicKind: return ALL_GLSL(FindILsb);
820 case k_findMSB_IntrinsicKind: return BY_TYPE_GLSL(FindSMsb, FindSMsb, FindUMsb);
821 case k_dFdx_IntrinsicKind: return FLOAT_SPIRV(DPdx);
822 case k_dFdy_IntrinsicKind: return SPECIAL(DFdy);
823 case k_fwidth_IntrinsicKind: return FLOAT_SPIRV(Fwidth);
824
825 case k_sample_IntrinsicKind: return SPECIAL(Texture);
826 case k_sampleGrad_IntrinsicKind: return SPECIAL(TextureGrad);
827 case k_sampleLod_IntrinsicKind: return SPECIAL(TextureLod);
828 case k_subpassLoad_IntrinsicKind: return SPECIAL(SubpassLoad);
829
830 case k_textureRead_IntrinsicKind: return SPECIAL(TextureRead);
831 case k_textureWrite_IntrinsicKind: return SPECIAL(TextureWrite);
832 case k_textureWidth_IntrinsicKind: return SPECIAL(TextureWidth);
833 case k_textureHeight_IntrinsicKind: return SPECIAL(TextureHeight);
834
835 case k_floatBitsToInt_IntrinsicKind: return ALL_SPIRV(Bitcast);
836 case k_floatBitsToUint_IntrinsicKind: return ALL_SPIRV(Bitcast);
837 case k_intBitsToFloat_IntrinsicKind: return ALL_SPIRV(Bitcast);
838 case k_uintBitsToFloat_IntrinsicKind: return ALL_SPIRV(Bitcast);
839
840 case k_any_IntrinsicKind: return BOOL_SPIRV(Any);
841 case k_all_IntrinsicKind: return BOOL_SPIRV(All);
842 case k_not_IntrinsicKind: return BOOL_SPIRV(LogicalNot);
843
844 case k_equal_IntrinsicKind:
845 return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
850 case k_notEqual_IntrinsicKind:
851 return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
856 case k_lessThan_IntrinsicKind:
857 return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
861 SpvOpUndef};
862 case k_lessThanEqual_IntrinsicKind:
863 return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
867 SpvOpUndef};
868 case k_greaterThan_IntrinsicKind:
869 return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
873 SpvOpUndef};
874 case k_greaterThanEqual_IntrinsicKind:
875 return Intrinsic{kSPIRV_IntrinsicOpcodeKind,
879 SpvOpUndef};
880
881 case k_atomicAdd_IntrinsicKind: return SPECIAL(AtomicAdd);
882 case k_atomicLoad_IntrinsicKind: return SPECIAL(AtomicLoad);
883 case k_atomicStore_IntrinsicKind: return SPECIAL(AtomicStore);
884
885 case k_storageBarrier_IntrinsicKind: return SPECIAL(StorageBarrier);
886 case k_workgroupBarrier_IntrinsicKind: return SPECIAL(WorkgroupBarrier);
887 default:
888 return Intrinsic{kInvalid_IntrinsicOpcodeKind, 0, 0, 0, 0};
889 }
890}
891
892void SPIRVCodeGenerator::writeWord(int32_t word, OutputStream& out) {
893 out.write((const char*) &word, sizeof(word));
894}
895
896static bool is_float(const Type& type) {
897 return (type.isScalar() || type.isVector() || type.isMatrix()) &&
898 type.componentType().isFloat();
899}
900
901static bool is_signed(const Type& type) {
902 return (type.isScalar() || type.isVector()) && type.componentType().isSigned();
903}
904
905static bool is_unsigned(const Type& type) {
906 return (type.isScalar() || type.isVector()) && type.componentType().isUnsigned();
907}
908
909static bool is_bool(const Type& type) {
910 return (type.isScalar() || type.isVector()) && type.componentType().isBoolean();
911}
912
913template <typename T>
914static T pick_by_type(const Type& type, T ifFloat, T ifInt, T ifUInt, T ifBool) {
915 if (is_float(type)) {
916 return ifFloat;
917 }
918 if (is_signed(type)) {
919 return ifInt;
920 }
921 if (is_unsigned(type)) {
922 return ifUInt;
923 }
924 if (is_bool(type)) {
925 return ifBool;
926 }
927 SkDEBUGFAIL("unrecognized type");
928 return ifFloat;
929}
930
931static bool is_out(ModifierFlags f) {
932 return SkToBool(f & ModifierFlag::kOut);
933}
934
935static bool is_in(ModifierFlags f) {
936 if (f & ModifierFlag::kIn) {
937 return true; // `in` and `inout` both count
938 }
939 // If neither in/out flag is set, the type is implicitly `in`.
940 return !SkToBool(f & ModifierFlag::kOut);
941}
942
943static bool is_control_flow_op(SpvOp_ op) {
944 switch (op) {
945 case SpvOpReturn:
946 case SpvOpReturnValue:
947 case SpvOpKill:
948 case SpvOpSwitch:
949 case SpvOpBranch:
951 return true;
952 default:
953 return false;
954 }
955}
956
958 switch (op) {
959 case SpvOpConstant:
963 case SpvOpTypeVoid:
964 case SpvOpTypeInt:
965 case SpvOpTypeFloat:
966 case SpvOpTypeBool:
967 case SpvOpTypeVector:
968 case SpvOpTypeMatrix:
969 case SpvOpTypeArray:
970 case SpvOpTypePointer:
973 case SpvOpTypeStruct:
974 case SpvOpTypeImage:
976 case SpvOpTypeSampler:
977 case SpvOpVariable:
978 case SpvOpFunction:
980 case SpvOpFunctionEnd:
982 case SpvOpMemoryModel:
983 case SpvOpCapability:
985 case SpvOpEntryPoint:
986 case SpvOpSource:
988 case SpvOpName:
989 case SpvOpMemberName:
990 case SpvOpDecorate:
992 return true;
993 default:
994 return false;
995 }
996}
997
998void SPIRVCodeGenerator::writeOpCode(SpvOp_ opCode, int length, OutputStream& out) {
999 SkASSERT(opCode != SpvOpLoad || &out != &fConstantBuffer);
1000 SkASSERT(opCode != SpvOpUndef);
1001 bool foundDeadCode = false;
1002 if (is_control_flow_op(opCode)) {
1003 // This instruction causes us to leave the current block.
1004 foundDeadCode = (fCurrentBlock == 0);
1005 fCurrentBlock = 0;
1006 } else if (!is_globally_reachable_op(opCode)) {
1007 foundDeadCode = (fCurrentBlock == 0);
1008 }
1009
1010 if (foundDeadCode) {
1011 // We just encountered dead code--an instruction that don't have an associated block.
1012 // Synthesize a label if this happens; this is necessary to satisfy the validator.
1013 this->writeLabel(this->nextId(nullptr), kBranchlessBlock, out);
1014 }
1015
1016 this->writeWord((length << 16) | opCode, out);
1017}
1018
1019void SPIRVCodeGenerator::writeLabel(SpvId label, StraightLineLabelType, OutputStream& out) {
1020 // The straight-line label type is not important; in any case, no caches are invalidated.
1021 SkASSERT(!fCurrentBlock);
1022 fCurrentBlock = label;
1023 this->writeInstruction(SpvOpLabel, label, out);
1024}
1025
1026void SPIRVCodeGenerator::writeLabel(SpvId label, BranchingLabelType type,
1027 ConditionalOpCounts ops, OutputStream& out) {
1028 switch (type) {
1029 case kBranchIsBelow:
1030 case kBranchesOnBothSides:
1031 // With a backward or bidirectional branch, we haven't seen the code between the label
1032 // and the branch yet, so any stored value is potentially suspect. Without scanning
1033 // ahead to check, the only safe option is to ditch the store cache entirely.
1034 fStoreCache.reset();
1035 [[fallthrough]];
1036
1037 case kBranchIsAbove:
1038 // With a forward branch, we can rely on stores that we had cached at the start of the
1039 // statement/expression, if they haven't been touched yet. Anything newer than that is
1040 // pruned.
1041 this->pruneConditionalOps(ops);
1042 break;
1043 }
1044
1045 // Emit the label.
1046 this->writeLabel(label, kBranchlessBlock, out);
1047}
1048
1049void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, OutputStream& out) {
1050 this->writeOpCode(opCode, 1, out);
1051}
1052
1053void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out) {
1054 this->writeOpCode(opCode, 2, out);
1055 this->writeWord(word1, out);
1056}
1057
1058void SPIRVCodeGenerator::writeString(std::string_view s, OutputStream& out) {
1059 out.write(s.data(), s.length());
1060 switch (s.length() % 4) {
1061 case 1:
1062 out.write8(0);
1063 [[fallthrough]];
1064 case 2:
1065 out.write8(0);
1066 [[fallthrough]];
1067 case 3:
1068 out.write8(0);
1069 break;
1070 default:
1071 this->writeWord(0, out);
1072 break;
1073 }
1074}
1075
1076void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, std::string_view string,
1077 OutputStream& out) {
1078 this->writeOpCode(opCode, 1 + (string.length() + 4) / 4, out);
1079 this->writeString(string, out);
1080}
1081
1082void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, std::string_view string,
1083 OutputStream& out) {
1084 this->writeOpCode(opCode, 2 + (string.length() + 4) / 4, out);
1085 this->writeWord(word1, out);
1086 this->writeString(string, out);
1087}
1088
1089void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1090 std::string_view string, OutputStream& out) {
1091 this->writeOpCode(opCode, 3 + (string.length() + 4) / 4, out);
1092 this->writeWord(word1, out);
1093 this->writeWord(word2, out);
1094 this->writeString(string, out);
1095}
1096
1097void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1098 OutputStream& out) {
1099 this->writeOpCode(opCode, 3, out);
1100 this->writeWord(word1, out);
1101 this->writeWord(word2, out);
1102}
1103
1104void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1105 int32_t word3, OutputStream& out) {
1106 this->writeOpCode(opCode, 4, out);
1107 this->writeWord(word1, out);
1108 this->writeWord(word2, out);
1109 this->writeWord(word3, out);
1110}
1111
1112void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1113 int32_t word3, int32_t word4, OutputStream& out) {
1114 this->writeOpCode(opCode, 5, out);
1115 this->writeWord(word1, out);
1116 this->writeWord(word2, out);
1117 this->writeWord(word3, out);
1118 this->writeWord(word4, out);
1119}
1120
1121void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1122 int32_t word3, int32_t word4, int32_t word5,
1123 OutputStream& out) {
1124 this->writeOpCode(opCode, 6, out);
1125 this->writeWord(word1, out);
1126 this->writeWord(word2, out);
1127 this->writeWord(word3, out);
1128 this->writeWord(word4, out);
1129 this->writeWord(word5, out);
1130}
1131
1132void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1133 int32_t word3, int32_t word4, int32_t word5,
1134 int32_t word6, OutputStream& out) {
1135 this->writeOpCode(opCode, 7, out);
1136 this->writeWord(word1, out);
1137 this->writeWord(word2, out);
1138 this->writeWord(word3, out);
1139 this->writeWord(word4, out);
1140 this->writeWord(word5, out);
1141 this->writeWord(word6, out);
1142}
1143
1144void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1145 int32_t word3, int32_t word4, int32_t word5,
1146 int32_t word6, int32_t word7, OutputStream& out) {
1147 this->writeOpCode(opCode, 8, out);
1148 this->writeWord(word1, out);
1149 this->writeWord(word2, out);
1150 this->writeWord(word3, out);
1151 this->writeWord(word4, out);
1152 this->writeWord(word5, out);
1153 this->writeWord(word6, out);
1154 this->writeWord(word7, out);
1155}
1156
1157void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2,
1158 int32_t word3, int32_t word4, int32_t word5,
1159 int32_t word6, int32_t word7, int32_t word8,
1160 OutputStream& out) {
1161 this->writeOpCode(opCode, 9, out);
1162 this->writeWord(word1, out);
1163 this->writeWord(word2, out);
1164 this->writeWord(word3, out);
1165 this->writeWord(word4, out);
1166 this->writeWord(word5, out);
1167 this->writeWord(word6, out);
1168 this->writeWord(word7, out);
1169 this->writeWord(word8, out);
1170}
1171
1172SPIRVCodeGenerator::Instruction SPIRVCodeGenerator::BuildInstructionKey(SpvOp_ opCode,
1173 const TArray<Word>& words) {
1174 // Assemble a cache key for this instruction.
1175 Instruction key;
1176 key.fOp = opCode;
1177 key.fWords.resize(words.size());
1178 key.fResultKind = Word::Kind::kNone;
1179
1180 for (int index = 0; index < words.size(); ++index) {
1181 const Word& word = words[index];
1182 key.fWords[index] = word.fValue;
1183 if (word.isResult()) {
1184 SkASSERT(key.fResultKind == Word::Kind::kNone);
1185 key.fResultKind = word.fKind;
1186 }
1187 }
1188
1189 return key;
1190}
1191
1192SpvId SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode,
1193 const TArray<Word>& words,
1194 OutputStream& out) {
1195 // writeOpLoad and writeOpStore have dedicated code.
1196 SkASSERT(opCode != SpvOpLoad);
1197 SkASSERT(opCode != SpvOpStore);
1198
1199 // If this instruction exists in our op cache, return the cached SpvId.
1200 Instruction key = BuildInstructionKey(opCode, words);
1201 if (SpvId* cachedOp = fOpCache.find(key)) {
1202 return *cachedOp;
1203 }
1204
1205 SpvId result = NA;
1206 Precision precision = Precision::kDefault;
1207
1208 switch (key.fResultKind) {
1210 // The instruction returns a SpvId, but we do not want deduplication.
1211 result = this->nextId(Precision::kDefault);
1212 fSpvIdCache.set(result, key);
1213 break;
1214
1215 case Word::Kind::kNone:
1216 // The instruction doesn't return a SpvId, but we can still cache and deduplicate it.
1217 fOpCache.set(key, result);
1218 break;
1219
1221 precision = Precision::kRelaxed;
1222 [[fallthrough]];
1223
1225 [[fallthrough]];
1226
1228 // Consume a new SpvId.
1229 result = this->nextId(precision);
1230 fOpCache.set(key, result);
1231 fSpvIdCache.set(result, key);
1232
1233 // Globally-reachable ops are not subject to the whims of flow control.
1234 if (!is_globally_reachable_op(opCode)) {
1235 fReachableOps.push_back(result);
1236 }
1237 break;
1238
1239 default:
1240 SkDEBUGFAIL("unexpected result kind");
1241 break;
1242 }
1243
1244 // Write the requested instruction.
1245 this->writeOpCode(opCode, words.size() + 1, out);
1246 for (const Word& word : words) {
1247 if (word.isResult()) {
1248 SkASSERT(result != NA);
1249 this->writeWord(result, out);
1250 } else {
1251 this->writeWord(word.fValue, out);
1252 }
1253 }
1254
1255 // Return the result.
1256 return result;
1257}
1258
1259SpvId SPIRVCodeGenerator::writeOpLoad(SpvId type,
1260 Precision precision,
1261 SpvId pointer,
1262 OutputStream& out) {
1263 // Look for this pointer in our load-cache.
1264 if (SpvId* cachedOp = fStoreCache.find(pointer)) {
1265 return *cachedOp;
1266 }
1267
1268 // Write the requested OpLoad instruction.
1269 SpvId result = this->nextId(precision);
1270 this->writeInstruction(SpvOpLoad, type, result, pointer, out);
1271 return result;
1272}
1273
1274void SPIRVCodeGenerator::writeOpStore(SpvStorageClass_ storageClass,
1275 SpvId pointer,
1276 SpvId value,
1277 OutputStream& out) {
1278 // Write the uncached SpvOpStore directly.
1279 this->writeInstruction(SpvOpStore, pointer, value, out);
1280
1281 if (storageClass == SpvStorageClassFunction) {
1282 // Insert a pointer-to-SpvId mapping into the load cache. A writeOpLoad to this pointer will
1283 // return the cached value as-is.
1284 fStoreCache.set(pointer, value);
1285 fStoreOps.push_back(pointer);
1286 }
1287}
1288
1289SpvId SPIRVCodeGenerator::writeOpConstantTrue(const Type& type) {
1290 return this->writeInstruction(SpvOpConstantTrue,
1291 Words{this->getType(type), Word::Result()},
1292 fConstantBuffer);
1293}
1294
1295SpvId SPIRVCodeGenerator::writeOpConstantFalse(const Type& type) {
1296 return this->writeInstruction(SpvOpConstantFalse,
1297 Words{this->getType(type), Word::Result()},
1298 fConstantBuffer);
1299}
1300
1301SpvId SPIRVCodeGenerator::writeOpConstant(const Type& type, int32_t valueBits) {
1302 return this->writeInstruction(
1304 Words{this->getType(type), Word::Result(), Word::Number(valueBits)},
1305 fConstantBuffer);
1306}
1307
1308SpvId SPIRVCodeGenerator::writeOpConstantComposite(const Type& type,
1309 const TArray<SpvId>& values) {
1310 SkASSERT(values.size() == (type.isStruct() ? (int)type.fields().size() : type.columns()));
1311
1312 Words words;
1313 words.push_back(this->getType(type));
1314 words.push_back(Word::Result());
1315 for (SpvId value : values) {
1316 words.push_back(value);
1317 }
1318 return this->writeInstruction(SpvOpConstantComposite, words, fConstantBuffer);
1319}
1320
1321bool SPIRVCodeGenerator::toConstants(SpvId value, TArray<SpvId>* constants) {
1322 Instruction* instr = fSpvIdCache.find(value);
1323 if (!instr) {
1324 return false;
1325 }
1326 switch (instr->fOp) {
1327 case SpvOpConstant:
1328 case SpvOpConstantTrue:
1329 case SpvOpConstantFalse:
1330 constants->push_back(value);
1331 return true;
1332
1333 case SpvOpConstantComposite: // OpConstantComposite ResultType ResultID Constituents...
1334 // Start at word 2 to skip past ResultType and ResultID.
1335 for (int i = 2; i < instr->fWords.size(); ++i) {
1336 if (!this->toConstants(instr->fWords[i], constants)) {
1337 return false;
1338 }
1339 }
1340 return true;
1341
1342 default:
1343 return false;
1344 }
1345}
1346
1347bool SPIRVCodeGenerator::toConstants(SkSpan<const SpvId> values, TArray<SpvId>* constants) {
1348 for (SpvId value : values) {
1349 if (!this->toConstants(value, constants)) {
1350 return false;
1351 }
1352 }
1353 return true;
1354}
1355
1356SpvId SPIRVCodeGenerator::writeOpCompositeConstruct(const Type& type,
1357 const TArray<SpvId>& values,
1358 OutputStream& out) {
1359 // If this is a vector composed entirely of literals, write a constant-composite instead.
1360 if (type.isVector()) {
1361 STArray<4, SpvId> constants;
1362 if (this->toConstants(SkSpan(values), &constants)) {
1363 // Create a vector from literals.
1364 return this->writeOpConstantComposite(type, constants);
1365 }
1366 }
1367
1368 // If this is a matrix composed entirely of literals, constant-composite them instead.
1369 if (type.isMatrix()) {
1370 STArray<16, SpvId> constants;
1371 if (this->toConstants(SkSpan(values), &constants)) {
1372 // Create each matrix column.
1373 SkASSERT(type.isMatrix());
1374 const Type& vecType = type.columnType(fContext);
1375 STArray<4, SpvId> columnIDs;
1376 for (int index=0; index < type.columns(); ++index) {
1377 STArray<4, SpvId> columnConstants(&constants[index * type.rows()],
1378 type.rows());
1379 columnIDs.push_back(this->writeOpConstantComposite(vecType, columnConstants));
1380 }
1381 // Compose the matrix from its columns.
1382 return this->writeOpConstantComposite(type, columnIDs);
1383 }
1384 }
1385
1386 Words words;
1387 words.push_back(this->getType(type));
1388 words.push_back(Word::Result(type));
1389 for (SpvId value : values) {
1390 words.push_back(value);
1391 }
1392
1393 return this->writeInstruction(SpvOpCompositeConstruct, words, out);
1394}
1395
1396SPIRVCodeGenerator::Instruction* SPIRVCodeGenerator::resultTypeForInstruction(
1397 const Instruction& instr) {
1398 // This list should contain every op that we cache that has a result and result-type.
1399 // (If one is missing, we will not find some optimization opportunities.)
1400 // Generally, the result type of an op is in the 0th word, but I'm not sure if this is
1401 // universally true, so it's configurable on a per-op basis.
1402 int resultTypeWord;
1403 switch (instr.fOp) {
1404 case SpvOpConstant:
1405 case SpvOpConstantTrue:
1406 case SpvOpConstantFalse:
1410 case SpvOpLoad:
1411 resultTypeWord = 0;
1412 break;
1413
1414 default:
1415 return nullptr;
1416 }
1417
1418 Instruction* typeInstr = fSpvIdCache.find(instr.fWords[resultTypeWord]);
1419 SkASSERT(typeInstr);
1420 return typeInstr;
1421}
1422
1423int SPIRVCodeGenerator::numComponentsForVecInstruction(const Instruction& instr) {
1424 // If an instruction is in the op cache, its type should be as well.
1425 Instruction* typeInstr = this->resultTypeForInstruction(instr);
1426 SkASSERT(typeInstr);
1427 SkASSERT(typeInstr->fOp == SpvOpTypeVector || typeInstr->fOp == SpvOpTypeFloat ||
1428 typeInstr->fOp == SpvOpTypeInt || typeInstr->fOp == SpvOpTypeBool);
1429
1430 // For vectors, extract their column count. Scalars have one component by definition.
1431 // SpvOpTypeVector ResultID ComponentType NumComponents
1432 return (typeInstr->fOp == SpvOpTypeVector) ? typeInstr->fWords[2]
1433 : 1;
1434}
1435
1436SpvId SPIRVCodeGenerator::toComponent(SpvId id, int component) {
1437 Instruction* instr = fSpvIdCache.find(id);
1438 if (!instr) {
1439 return NA;
1440 }
1441 if (instr->fOp == SpvOpConstantComposite) {
1442 // SpvOpConstantComposite ResultType ResultID [components...]
1443 // Add 2 to the component index to skip past ResultType and ResultID.
1444 return instr->fWords[2 + component];
1445 }
1446 if (instr->fOp == SpvOpCompositeConstruct) {
1447 // SpvOpCompositeConstruct ResultType ResultID [components...]
1448 // Vectors have special rules; check to see if we are composing a vector.
1449 Instruction* composedType = fSpvIdCache.find(instr->fWords[0]);
1450 SkASSERT(composedType);
1451
1452 // When composing a non-vector, each instruction word maps 1:1 to the component index.
1453 // We can just extract out the associated component directly.
1454 if (composedType->fOp != SpvOpTypeVector) {
1455 return instr->fWords[2 + component];
1456 }
1457
1458 // When composing a vector, components can be either scalars or vectors.
1459 // This means we need to check the op type on each component. (+2 to skip ResultType/Result)
1460 for (int index = 2; index < instr->fWords.size(); ++index) {
1461 int32_t currentWord = instr->fWords[index];
1462
1463 // Retrieve the sub-instruction pointed to by OpCompositeConstruct.
1464 Instruction* subinstr = fSpvIdCache.find(currentWord);
1465 if (!subinstr) {
1466 return NA;
1467 }
1468 // If this subinstruction contains the component we're looking for...
1469 int numComponents = this->numComponentsForVecInstruction(*subinstr);
1470 if (component < numComponents) {
1471 if (numComponents == 1) {
1472 // ... it's a scalar. Return it.
1473 SkASSERT(component == 0);
1474 return currentWord;
1475 } else {
1476 // ... it's a vector. Recurse into it.
1477 return this->toComponent(currentWord, component);
1478 }
1479 }
1480 // This sub-instruction doesn't contain our component. Keep walking forward.
1481 component -= numComponents;
1482 }
1483 SkDEBUGFAIL("component index goes past the end of this composite value");
1484 return NA;
1485 }
1486 return NA;
1487}
1488
1489SpvId SPIRVCodeGenerator::writeOpCompositeExtract(const Type& type,
1490 SpvId base,
1491 int component,
1492 OutputStream& out) {
1493 // If the base op is a composite, we can extract from it directly.
1494 SpvId result = this->toComponent(base, component);
1495 if (result != NA) {
1496 return result;
1497 }
1498 return this->writeInstruction(
1500 {this->getType(type), Word::Result(type), base, Word::Number(component)},
1501 out);
1502}
1503
1504SpvId SPIRVCodeGenerator::writeOpCompositeExtract(const Type& type,
1505 SpvId base,
1506 int componentA,
1507 int componentB,
1508 OutputStream& out) {
1509 // If the base op is a composite, we can extract from it directly.
1510 SpvId result = this->toComponent(base, componentA);
1511 if (result != NA) {
1512 return this->writeOpCompositeExtract(type, result, componentB, out);
1513 }
1514 return this->writeInstruction(SpvOpCompositeExtract,
1515 {this->getType(type),
1517 base,
1518 Word::Number(componentA),
1519 Word::Number(componentB)},
1520 out);
1521}
1522
1523void SPIRVCodeGenerator::writeCapabilities(OutputStream& out) {
1524 for (uint64_t i = 0, bit = 1; i <= kLast_Capability; i++, bit <<= 1) {
1525 if (fCapabilities & bit) {
1526 this->writeInstruction(SpvOpCapability, (SpvId) i, out);
1527 }
1528 }
1529 this->writeInstruction(SpvOpCapability, SpvCapabilityShader, out);
1530}
1531
1532SpvId SPIRVCodeGenerator::nextId(const Type* type) {
1533 return this->nextId(type && type->hasPrecision() && !type->highPrecision()
1534 ? Precision::kRelaxed
1535 : Precision::kDefault);
1536}
1537
1538SpvId SPIRVCodeGenerator::nextId(Precision precision) {
1539 if (precision == Precision::kRelaxed && !fProgram.fConfig->fSettings.fForceHighPrecision) {
1540 this->writeInstruction(SpvOpDecorate, fIdCount, SpvDecorationRelaxedPrecision,
1541 fDecorationBuffer);
1542 }
1543 return fIdCount++;
1544}
1545
1546SpvId SPIRVCodeGenerator::writeStruct(const Type& type, const MemoryLayout& memoryLayout) {
1547 // If we've already written out this struct, return its existing SpvId.
1548 if (SpvId* cachedStructId = fStructMap.find(&type)) {
1549 return *cachedStructId;
1550 }
1551
1552 // Write all of the field types first, so we don't inadvertently write them while we're in the
1553 // middle of writing the struct instruction.
1554 Words words;
1555 words.push_back(Word::UniqueResult());
1556 for (const auto& f : type.fields()) {
1557 words.push_back(this->getType(*f.fType, f.fLayout, memoryLayout));
1558 }
1559 SpvId resultId = this->writeInstruction(SpvOpTypeStruct, words, fConstantBuffer);
1560 this->writeInstruction(SpvOpName, resultId, type.name(), fNameBuffer);
1561 fStructMap.set(&type, resultId);
1562
1563 size_t offset = 0;
1564 for (int32_t i = 0; i < (int32_t) type.fields().size(); i++) {
1565 const Field& field = type.fields()[i];
1566 if (!memoryLayout.isSupported(*field.fType)) {
1567 fContext.fErrors->error(type.fPosition, "type '" + field.fType->displayName() +
1568 "' is not permitted here");
1569 return resultId;
1570 }
1571 size_t size = memoryLayout.size(*field.fType);
1572 size_t alignment = memoryLayout.alignment(*field.fType);
1573 const Layout& fieldLayout = field.fLayout;
1574 if (fieldLayout.fOffset >= 0) {
1575 if (fieldLayout.fOffset < (int) offset) {
1576 fContext.fErrors->error(field.fPosition, "offset of field '" +
1577 std::string(field.fName) + "' must be at least " + std::to_string(offset));
1578 }
1579 if (fieldLayout.fOffset % alignment) {
1580 fContext.fErrors->error(field.fPosition,
1581 "offset of field '" + std::string(field.fName) +
1582 "' must be a multiple of " + std::to_string(alignment));
1583 }
1584 offset = fieldLayout.fOffset;
1585 } else {
1586 size_t mod = offset % alignment;
1587 if (mod) {
1588 offset += alignment - mod;
1589 }
1590 }
1591 this->writeInstruction(SpvOpMemberName, resultId, i, field.fName, fNameBuffer);
1592 this->writeFieldLayout(fieldLayout, resultId, i);
1593 if (field.fLayout.fBuiltin < 0) {
1594 this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i, SpvDecorationOffset,
1595 (SpvId) offset, fDecorationBuffer);
1596 }
1597 if (field.fType->isMatrix()) {
1598 this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationColMajor,
1599 fDecorationBuffer);
1600 this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationMatrixStride,
1601 (SpvId) memoryLayout.stride(*field.fType),
1602 fDecorationBuffer);
1603 }
1604 if (!field.fType->highPrecision()) {
1605 this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i,
1606 SpvDecorationRelaxedPrecision, fDecorationBuffer);
1607 }
1608 offset += size;
1609 if ((field.fType->isArray() || field.fType->isStruct()) && offset % alignment != 0) {
1610 offset += alignment - offset % alignment;
1611 }
1612 }
1613
1614 return resultId;
1615}
1616
1617SpvId SPIRVCodeGenerator::getType(const Type& type) {
1618 return this->getType(type, kDefaultTypeLayout, fDefaultMemoryLayout);
1619}
1620
1623 switch (flags.value()) {
1624 case (int)LayoutFlag::kRGBA8:
1625 return SpvImageFormatRgba8;
1626
1627 case (int)LayoutFlag::kRGBA32F:
1628 return SpvImageFormatRgba32f;
1629
1630 case (int)LayoutFlag::kR32F:
1631 return SpvImageFormatR32f;
1632
1633 default:
1634 return SpvImageFormatUnknown;
1635 }
1636
1638}
1639
1640SpvId SPIRVCodeGenerator::getType(const Type& rawType,
1641 const Layout& typeLayout,
1642 const MemoryLayout& memoryLayout) {
1643 const Type* type = &rawType;
1644
1645 switch (type->typeKind()) {
1646 case Type::TypeKind::kVoid: {
1647 return this->writeInstruction(SpvOpTypeVoid, Words{Word::Result()}, fConstantBuffer);
1648 }
1651 if (type->isBoolean()) {
1652 return this->writeInstruction(SpvOpTypeBool, {Word::Result()}, fConstantBuffer);
1653 }
1654 if (type->isSigned()) {
1655 return this->writeInstruction(
1657 Words{Word::Result(), Word::Number(32), Word::Number(1)},
1658 fConstantBuffer);
1659 }
1660 if (type->isUnsigned()) {
1661 return this->writeInstruction(
1663 Words{Word::Result(), Word::Number(32), Word::Number(0)},
1664 fConstantBuffer);
1665 }
1666 if (type->isFloat()) {
1667 return this->writeInstruction(
1669 Words{Word::Result(), Word::Number(32)},
1670 fConstantBuffer);
1671 }
1672 SkDEBUGFAILF("unrecognized scalar type '%s'", type->description().c_str());
1673 return NA;
1674 }
1676 SpvId scalarTypeId = this->getType(type->componentType(), typeLayout, memoryLayout);
1677 return this->writeInstruction(
1679 Words{Word::Result(), scalarTypeId, Word::Number(type->columns())},
1680 fConstantBuffer);
1681 }
1683 SpvId vectorTypeId = this->getType(IndexExpression::IndexType(fContext, *type),
1684 typeLayout,
1685 memoryLayout);
1686 return this->writeInstruction(
1688 Words{Word::Result(), vectorTypeId, Word::Number(type->columns())},
1689 fConstantBuffer);
1690 }
1692 if (!memoryLayout.isSupported(*type)) {
1693 fContext.fErrors->error(type->fPosition, "type '" + type->displayName() +
1694 "' is not permitted here");
1695 return NA;
1696 }
1697 size_t stride = memoryLayout.stride(*type);
1698 SpvId typeId = this->getType(type->componentType(), typeLayout, memoryLayout);
1699 SpvId result = NA;
1700 if (type->isUnsizedArray()) {
1701 result = this->writeInstruction(SpvOpTypeRuntimeArray,
1702 Words{Word::KeyedResult(stride), typeId},
1703 fConstantBuffer);
1704 } else {
1705 SpvId countId = this->writeLiteral(type->columns(), *fContext.fTypes.fInt);
1706 result = this->writeInstruction(SpvOpTypeArray,
1707 Words{Word::KeyedResult(stride), typeId, countId},
1708 fConstantBuffer);
1709 }
1710 this->writeInstruction(SpvOpDecorate,
1712 fDecorationBuffer);
1713 return result;
1714 }
1716 return this->writeStruct(*type, memoryLayout);
1717 }
1719 return this->writeInstruction(SpvOpTypeSampler, Words{Word::Result()}, fConstantBuffer);
1720 }
1722 if (SpvDimBuffer == type->dimensions()) {
1723 fCapabilities |= 1ULL << SpvCapabilitySampledBuffer;
1724 }
1725 SpvId imageTypeId = this->getType(type->textureType(), typeLayout, memoryLayout);
1726 return this->writeInstruction(SpvOpTypeSampledImage,
1727 Words{Word::Result(), imageTypeId},
1728 fConstantBuffer);
1729 }
1731 SpvId floatTypeId = this->getType(*fContext.fTypes.fFloat,
1733 memoryLayout);
1734
1735 bool sampled = (type->textureAccess() == Type::TextureAccess::kSample);
1736 SpvImageFormat format = (!sampled && type->dimensions() != SpvDimSubpassData)
1737 ? layout_flags_to_image_format(typeLayout.fFlags)
1739
1740 return this->writeInstruction(SpvOpTypeImage,
1741 Words{Word::Result(),
1742 floatTypeId,
1743 Word::Number(type->dimensions()),
1744 Word::Number(type->isDepth()),
1745 Word::Number(type->isArrayedTexture()),
1746 Word::Number(type->isMultisampled()),
1747 Word::Number(sampled ? 1 : 2),
1748 format},
1749 fConstantBuffer);
1750 }
1752 // SkSL currently only supports the atomicUint type.
1754 // SPIR-V doesn't have atomic types. Rather, it allows atomic operations on primitive
1755 // types. The SPIR-V type of an SkSL atomic is simply the underlying type.
1756 return this->writeInstruction(SpvOpTypeInt,
1757 Words{Word::Result(), Word::Number(32), Word::Number(0)},
1758 fConstantBuffer);
1759 }
1760 default: {
1761 SkDEBUGFAILF("invalid type: %s", type->description().c_str());
1762 return NA;
1763 }
1764 }
1765}
1766
1767SpvId SPIRVCodeGenerator::getFunctionType(const FunctionDeclaration& function) {
1768 Words words;
1769 words.push_back(Word::Result());
1770 words.push_back(this->getType(function.returnType()));
1771 for (const Variable* parameter : function.parameters()) {
1772 if (fUseTextureSamplerPairs && parameter->type().isSampler()) {
1773 words.push_back(this->getFunctionParameterType(parameter->type().textureType(),
1774 parameter->layout()));
1775 words.push_back(this->getFunctionParameterType(*fContext.fTypes.fSampler,
1777 } else {
1778 words.push_back(this->getFunctionParameterType(parameter->type(), parameter->layout()));
1779 }
1780 }
1781 return this->writeInstruction(SpvOpTypeFunction, words, fConstantBuffer);
1782}
1783
1784SpvId SPIRVCodeGenerator::getFunctionParameterType(const Type& parameterType,
1785 const Layout& parameterLayout) {
1786 // glslang treats all function arguments as pointers whether they need to be or
1787 // not. I was initially puzzled by this until I ran bizarre failures with certain
1788 // patterns of function calls and control constructs, as exemplified by this minimal
1789 // failure case:
1790 //
1791 // void sphere(float x) {
1792 // }
1793 //
1794 // void map() {
1795 // sphere(1.0);
1796 // }
1797 //
1798 // void main() {
1799 // for (int i = 0; i < 1; i++) {
1800 // map();
1801 // }
1802 // }
1803 //
1804 // As of this writing, compiling this in the "obvious" way (with sphere taking a float)
1805 // crashes. Making it take a float* and storing the argument in a temporary variable,
1806 // as glslang does, fixes it.
1807 //
1808 // The consensus among shader compiler authors seems to be that GPU driver generally don't
1809 // handle value-based parameters consistently. It is highly likely that they fit their
1810 // implementations to conform to glslang. We take care to do so ourselves.
1811 //
1812 // Our implementation first stores every parameter value into a function storage-class pointer
1813 // before calling a function. The exception is for opaque handle types (samplers and textures)
1814 // which must be stored in a pointer with UniformConstant storage-class. This prevents
1815 // unnecessary temporaries (becuase opaque handles are always rooted in a pointer variable),
1816 // matches glslang's behavior, and translates into WGSL more easily when targeting Dawn.
1817 SpvStorageClass_ storageClass;
1818 if (parameterType.typeKind() == Type::TypeKind::kSampler ||
1819 parameterType.typeKind() == Type::TypeKind::kSeparateSampler ||
1820 parameterType.typeKind() == Type::TypeKind::kTexture) {
1821 storageClass = SpvStorageClassUniformConstant;
1822 } else {
1823 storageClass = SpvStorageClassFunction;
1824 }
1825 return this->getPointerType(parameterType,
1826 parameterLayout,
1827 this->memoryLayoutForStorageClass(storageClass),
1828 storageClass);
1829}
1830
1831SpvId SPIRVCodeGenerator::getPointerType(const Type& type, SpvStorageClass_ storageClass) {
1832 return this->getPointerType(type,
1834 this->memoryLayoutForStorageClass(storageClass),
1835 storageClass);
1836}
1837
1838SpvId SPIRVCodeGenerator::getPointerType(const Type& type,
1839 const Layout& typeLayout,
1840 const MemoryLayout& memoryLayout,
1841 SpvStorageClass_ storageClass) {
1842 return this->writeInstruction(SpvOpTypePointer,
1843 Words{Word::Result(),
1844 Word::Number(storageClass),
1845 this->getType(type, typeLayout, memoryLayout)},
1846 fConstantBuffer);
1847}
1848
1849SpvId SPIRVCodeGenerator::writeExpression(const Expression& expr, OutputStream& out) {
1850 switch (expr.kind()) {
1851 case Expression::Kind::kBinary:
1852 return this->writeBinaryExpression(expr.as<BinaryExpression>(), out);
1853 case Expression::Kind::kConstructorArrayCast:
1854 return this->writeExpression(*expr.as<ConstructorArrayCast>().argument(), out);
1855 case Expression::Kind::kConstructorArray:
1856 case Expression::Kind::kConstructorStruct:
1857 return this->writeCompositeConstructor(expr.asAnyConstructor(), out);
1858 case Expression::Kind::kConstructorDiagonalMatrix:
1859 return this->writeConstructorDiagonalMatrix(expr.as<ConstructorDiagonalMatrix>(), out);
1860 case Expression::Kind::kConstructorMatrixResize:
1861 return this->writeConstructorMatrixResize(expr.as<ConstructorMatrixResize>(), out);
1862 case Expression::Kind::kConstructorScalarCast:
1863 return this->writeConstructorScalarCast(expr.as<ConstructorScalarCast>(), out);
1864 case Expression::Kind::kConstructorSplat:
1865 return this->writeConstructorSplat(expr.as<ConstructorSplat>(), out);
1866 case Expression::Kind::kConstructorCompound:
1867 return this->writeConstructorCompound(expr.as<ConstructorCompound>(), out);
1868 case Expression::Kind::kConstructorCompoundCast:
1869 return this->writeConstructorCompoundCast(expr.as<ConstructorCompoundCast>(), out);
1870 case Expression::Kind::kEmpty:
1871 return NA;
1872 case Expression::Kind::kFieldAccess:
1873 return this->writeFieldAccess(expr.as<FieldAccess>(), out);
1874 case Expression::Kind::kFunctionCall:
1875 return this->writeFunctionCall(expr.as<FunctionCall>(), out);
1876 case Expression::Kind::kLiteral:
1877 return this->writeLiteral(expr.as<Literal>());
1878 case Expression::Kind::kPrefix:
1879 return this->writePrefixExpression(expr.as<PrefixExpression>(), out);
1880 case Expression::Kind::kPostfix:
1881 return this->writePostfixExpression(expr.as<PostfixExpression>(), out);
1882 case Expression::Kind::kSwizzle:
1883 return this->writeSwizzle(expr.as<Swizzle>(), out);
1884 case Expression::Kind::kVariableReference:
1885 return this->writeVariableReference(expr.as<VariableReference>(), out);
1886 case Expression::Kind::kTernary:
1887 return this->writeTernaryExpression(expr.as<TernaryExpression>(), out);
1888 case Expression::Kind::kIndex:
1889 return this->writeIndexExpression(expr.as<IndexExpression>(), out);
1890 case Expression::Kind::kSetting:
1891 return this->writeExpression(*expr.as<Setting>().toLiteral(fCaps), out);
1892 default:
1893 SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str());
1894 break;
1895 }
1896 return NA;
1897}
1898
1899SpvId SPIRVCodeGenerator::writeIntrinsicCall(const FunctionCall& c, OutputStream& out) {
1900 const FunctionDeclaration& function = c.function();
1901 Intrinsic intrinsic = this->getIntrinsic(function.intrinsicKind());
1902 if (intrinsic.opKind == kInvalid_IntrinsicOpcodeKind) {
1903 fContext.fErrors->error(c.fPosition, "unsupported intrinsic '" + function.description() +
1904 "'");
1905 return NA;
1906 }
1907 const ExpressionArray& arguments = c.arguments();
1908 int32_t intrinsicId = intrinsic.floatOp;
1909 if (!arguments.empty()) {
1910 const Type& type = arguments[0]->type();
1911 if (intrinsic.opKind == kSpecial_IntrinsicOpcodeKind) {
1912 // Keep the default float op.
1913 } else {
1914 intrinsicId = pick_by_type(type, intrinsic.floatOp, intrinsic.signedOp,
1915 intrinsic.unsignedOp, intrinsic.boolOp);
1916 }
1917 }
1918 switch (intrinsic.opKind) {
1919 case kGLSL_STD_450_IntrinsicOpcodeKind: {
1920 SpvId result = this->nextId(&c.type());
1921 TArray<SpvId> argumentIds;
1922 argumentIds.reserve_exact(arguments.size());
1923 std::vector<TempVar> tempVars;
1924 for (int i = 0; i < arguments.size(); i++) {
1925 argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out));
1926 }
1927 this->writeOpCode(SpvOpExtInst, 5 + (int32_t) argumentIds.size(), out);
1928 this->writeWord(this->getType(c.type()), out);
1929 this->writeWord(result, out);
1930 this->writeWord(fGLSLExtendedInstructions, out);
1931 this->writeWord(intrinsicId, out);
1932 for (SpvId id : argumentIds) {
1933 this->writeWord(id, out);
1934 }
1935 this->copyBackTempVars(tempVars, out);
1936 return result;
1937 }
1938 case kSPIRV_IntrinsicOpcodeKind: {
1939 // GLSL supports dot(float, float), but SPIR-V does not. Convert it to FMul
1940 if (intrinsicId == SpvOpDot && arguments[0]->type().isScalar()) {
1941 intrinsicId = SpvOpFMul;
1942 }
1943 SpvId result = this->nextId(&c.type());
1944 TArray<SpvId> argumentIds;
1945 argumentIds.reserve_exact(arguments.size());
1946 std::vector<TempVar> tempVars;
1947 for (int i = 0; i < arguments.size(); i++) {
1948 argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out));
1949 }
1950 if (!c.type().isVoid()) {
1951 this->writeOpCode((SpvOp_) intrinsicId, 3 + (int32_t) arguments.size(), out);
1952 this->writeWord(this->getType(c.type()), out);
1953 this->writeWord(result, out);
1954 } else {
1955 this->writeOpCode((SpvOp_) intrinsicId, 1 + (int32_t) arguments.size(), out);
1956 }
1957 for (SpvId id : argumentIds) {
1958 this->writeWord(id, out);
1959 }
1960 this->copyBackTempVars(tempVars, out);
1961 return result;
1962 }
1963 case kSpecial_IntrinsicOpcodeKind:
1964 return this->writeSpecialIntrinsic(c, (SpecialIntrinsic) intrinsicId, out);
1965 default:
1966 fContext.fErrors->error(c.fPosition, "unsupported intrinsic '" +
1967 function.description() + "'");
1968 return NA;
1969 }
1970}
1971
1972SpvId SPIRVCodeGenerator::vectorize(const Expression& arg, int vectorSize, OutputStream& out) {
1973 SkASSERT(vectorSize >= 1 && vectorSize <= 4);
1974 const Type& argType = arg.type();
1975 if (argType.isScalar() && vectorSize > 1) {
1976 ConstructorSplat splat{arg.fPosition,
1977 argType.toCompound(fContext, vectorSize, /*rows=*/1),
1978 arg.clone()};
1979 return this->writeConstructorSplat(splat, out);
1980 }
1981
1982 SkASSERT(vectorSize == argType.columns());
1983 return this->writeExpression(arg, out);
1984}
1985
1986TArray<SpvId> SPIRVCodeGenerator::vectorize(const ExpressionArray& args, OutputStream& out) {
1987 int vectorSize = 1;
1988 for (const auto& a : args) {
1989 if (a->type().isVector()) {
1990 if (vectorSize > 1) {
1991 SkASSERT(a->type().columns() == vectorSize);
1992 } else {
1993 vectorSize = a->type().columns();
1994 }
1995 }
1996 }
1998 result.reserve_exact(args.size());
1999 for (const auto& arg : args) {
2000 result.push_back(this->vectorize(*arg, vectorSize, out));
2001 }
2002 return result;
2003}
2004
2005void SPIRVCodeGenerator::writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst,
2006 SpvId signedInst, SpvId unsignedInst,
2007 const TArray<SpvId>& args,
2008 OutputStream& out) {
2009 this->writeOpCode(SpvOpExtInst, 5 + args.size(), out);
2010 this->writeWord(this->getType(type), out);
2011 this->writeWord(id, out);
2012 this->writeWord(fGLSLExtendedInstructions, out);
2013 this->writeWord(pick_by_type(type, floatInst, signedInst, unsignedInst, NA), out);
2014 for (SpvId a : args) {
2015 this->writeWord(a, out);
2016 }
2017}
2018
2019SpvId SPIRVCodeGenerator::writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind,
2020 OutputStream& out) {
2021 const ExpressionArray& arguments = c.arguments();
2022 const Type& callType = c.type();
2023 SpvId result = this->nextId(nullptr);
2024 switch (kind) {
2025 case kAtan_SpecialIntrinsic: {
2026 STArray<2, SpvId> argumentIds;
2027 for (const std::unique_ptr<Expression>& arg : arguments) {
2028 argumentIds.push_back(this->writeExpression(*arg, out));
2029 }
2030 this->writeOpCode(SpvOpExtInst, 5 + (int32_t) argumentIds.size(), out);
2031 this->writeWord(this->getType(callType), out);
2032 this->writeWord(result, out);
2033 this->writeWord(fGLSLExtendedInstructions, out);
2034 this->writeWord(argumentIds.size() == 2 ? GLSLstd450Atan2 : GLSLstd450Atan, out);
2035 for (SpvId id : argumentIds) {
2036 this->writeWord(id, out);
2037 }
2038 break;
2039 }
2040 case kSampledImage_SpecialIntrinsic: {
2041 SkASSERT(arguments.size() == 2);
2042 SpvId img = this->writeExpression(*arguments[0], out);
2043 SpvId sampler = this->writeExpression(*arguments[1], out);
2044 this->writeInstruction(SpvOpSampledImage,
2045 this->getType(callType),
2046 result,
2047 img,
2048 sampler,
2049 out);
2050 break;
2051 }
2052 case kSubpassLoad_SpecialIntrinsic: {
2053 SpvId img = this->writeExpression(*arguments[0], out);
2054 ExpressionArray args;
2055 args.reserve_exact(2);
2056 args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0));
2057 args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0));
2058 ConstructorCompound ctor(Position(), *fContext.fTypes.fInt2, std::move(args));
2059 SpvId coords = this->writeExpression(ctor, out);
2060 if (arguments.size() == 1) {
2061 this->writeInstruction(SpvOpImageRead,
2062 this->getType(callType),
2063 result,
2064 img,
2065 coords,
2066 out);
2067 } else {
2068 SkASSERT(arguments.size() == 2);
2069 SpvId sample = this->writeExpression(*arguments[1], out);
2070 this->writeInstruction(SpvOpImageRead,
2071 this->getType(callType),
2072 result,
2073 img,
2074 coords,
2076 sample,
2077 out);
2078 }
2079 break;
2080 }
2081 case kTexture_SpecialIntrinsic: {
2083 const Type& arg1Type = arguments[1]->type();
2084 switch (arguments[0]->type().dimensions()) {
2085 case SpvDim1D:
2086 if (arg1Type.matches(*fContext.fTypes.fFloat2)) {
2088 } else {
2089 SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat));
2090 }
2091 break;
2092 case SpvDim2D:
2093 if (arg1Type.matches(*fContext.fTypes.fFloat3)) {
2095 } else {
2096 SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2));
2097 }
2098 break;
2099 case SpvDim3D:
2100 if (arg1Type.matches(*fContext.fTypes.fFloat4)) {
2102 } else {
2103 SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat3));
2104 }
2105 break;
2106 case SpvDimCube: // fall through
2107 case SpvDimRect: // fall through
2108 case SpvDimBuffer: // fall through
2109 case SpvDimSubpassData:
2110 break;
2111 }
2112 SpvId type = this->getType(callType);
2113 SpvId sampler = this->writeExpression(*arguments[0], out);
2114 SpvId uv = this->writeExpression(*arguments[1], out);
2115 if (arguments.size() == 3) {
2116 this->writeInstruction(op, type, result, sampler, uv,
2118 this->writeExpression(*arguments[2], out),
2119 out);
2120 } else {
2121 SkASSERT(arguments.size() == 2);
2122 if (fProgram.fConfig->fSettings.fSharpenTextures) {
2123 SpvId lodBias = this->writeLiteral(kSharpenTexturesBias,
2125 this->writeInstruction(op, type, result, sampler, uv,
2126 SpvImageOperandsBiasMask, lodBias, out);
2127 } else {
2128 this->writeInstruction(op, type, result, sampler, uv,
2129 out);
2130 }
2131 }
2132 break;
2133 }
2134 case kTextureGrad_SpecialIntrinsic: {
2136 SkASSERT(arguments.size() == 4);
2137 SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
2138 SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fFloat2));
2139 SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat2));
2140 SkASSERT(arguments[3]->type().matches(*fContext.fTypes.fFloat2));
2141 SpvId type = this->getType(callType);
2142 SpvId sampler = this->writeExpression(*arguments[0], out);
2143 SpvId uv = this->writeExpression(*arguments[1], out);
2144 SpvId dPdx = this->writeExpression(*arguments[2], out);
2145 SpvId dPdy = this->writeExpression(*arguments[3], out);
2146 this->writeInstruction(op, type, result, sampler, uv, SpvImageOperandsGradMask,
2147 dPdx, dPdy, out);
2148 break;
2149 }
2150 case kTextureLod_SpecialIntrinsic: {
2152 SkASSERT(arguments.size() == 3);
2153 SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
2154 SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat));
2155 const Type& arg1Type = arguments[1]->type();
2156 if (arg1Type.matches(*fContext.fTypes.fFloat3)) {
2158 } else {
2159 SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2));
2160 }
2161 SpvId type = this->getType(callType);
2162 SpvId sampler = this->writeExpression(*arguments[0], out);
2163 SpvId uv = this->writeExpression(*arguments[1], out);
2164 this->writeInstruction(op, type, result, sampler, uv,
2166 this->writeExpression(*arguments[2], out),
2167 out);
2168 break;
2169 }
2170 case kTextureRead_SpecialIntrinsic: {
2171 SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
2172 SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fUInt2));
2173
2174 SpvId type = this->getType(callType);
2175 SpvId image = this->writeExpression(*arguments[0], out);
2176 SpvId coord = this->writeExpression(*arguments[1], out);
2177
2178 const Type& arg0Type = arguments[0]->type();
2179 SkASSERT(arg0Type.typeKind() == Type::TypeKind::kTexture);
2180
2181 switch (arg0Type.textureAccess()) {
2183 this->writeInstruction(SpvOpImageFetch, type, result, image, coord,
2185 this->writeOpConstant(*fContext.fTypes.fInt, 0),
2186 out);
2187 break;
2190 this->writeInstruction(SpvOpImageRead, type, result, image, coord, out);
2191 break;
2193 default:
2194 SkDEBUGFAIL("'textureRead' called on writeonly texture type");
2195 break;
2196 }
2197
2198 break;
2199 }
2200 case kTextureWrite_SpecialIntrinsic: {
2201 SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
2202 SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fUInt2));
2203 SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fHalf4));
2204
2205 SpvId image = this->writeExpression(*arguments[0], out);
2206 SpvId coord = this->writeExpression(*arguments[1], out);
2207 SpvId texel = this->writeExpression(*arguments[2], out);
2208
2209 this->writeInstruction(SpvOpImageWrite, image, coord, texel, out);
2210 break;
2211 }
2212 case kTextureWidth_SpecialIntrinsic:
2213 case kTextureHeight_SpecialIntrinsic: {
2214 SkASSERT(arguments[0]->type().dimensions() == SpvDim2D);
2215 fCapabilities |= 1ULL << SpvCapabilityImageQuery;
2216
2217 SpvId dimsType = this->getType(*fContext.fTypes.fUInt2);
2218 SpvId dims = this->nextId(nullptr);
2219 SpvId image = this->writeExpression(*arguments[0], out);
2220 this->writeInstruction(SpvOpImageQuerySize, dimsType, dims, image, out);
2221
2222 SpvId type = this->getType(callType);
2223 int32_t index = (kind == kTextureWidth_SpecialIntrinsic) ? 0 : 1;
2224 this->writeInstruction(SpvOpCompositeExtract, type, result, dims, index, out);
2225 break;
2226 }
2227 case kMod_SpecialIntrinsic: {
2228 TArray<SpvId> args = this->vectorize(arguments, out);
2229 SkASSERT(args.size() == 2);
2230 const Type& operandType = arguments[0]->type();
2232 SkASSERT(op != SpvOpUndef);
2233 this->writeOpCode(op, 5, out);
2234 this->writeWord(this->getType(operandType), out);
2235 this->writeWord(result, out);
2236 this->writeWord(args[0], out);
2237 this->writeWord(args[1], out);
2238 break;
2239 }
2240 case kDFdy_SpecialIntrinsic: {
2241 SpvId fn = this->writeExpression(*arguments[0], out);
2242 this->writeOpCode(SpvOpDPdy, 4, out);
2243 this->writeWord(this->getType(callType), out);
2244 this->writeWord(result, out);
2245 this->writeWord(fn, out);
2246 if (!fProgram.fConfig->fSettings.fForceNoRTFlip) {
2247 this->addRTFlipUniform(c.fPosition);
2248 ComponentArray componentArray;
2249 for (int index = 0; index < callType.columns(); ++index) {
2250 componentArray.push_back(SwizzleComponent::Y);
2251 }
2252 SpvId rtFlipY = this->writeSwizzle(*this->identifier(SKSL_RTFLIP_NAME),
2253 componentArray, out);
2254 SpvId flipped = this->nextId(&callType);
2255 this->writeInstruction(SpvOpFMul, this->getType(callType), flipped, result,
2256 rtFlipY, out);
2257 result = flipped;
2258 }
2259 break;
2260 }
2261 case kClamp_SpecialIntrinsic: {
2262 TArray<SpvId> args = this->vectorize(arguments, out);
2263 SkASSERT(args.size() == 3);
2264 this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp,
2265 GLSLstd450UClamp, args, out);
2266 break;
2267 }
2268 case kMax_SpecialIntrinsic: {
2269 TArray<SpvId> args = this->vectorize(arguments, out);
2270 SkASSERT(args.size() == 2);
2271 this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMax, GLSLstd450SMax,
2272 GLSLstd450UMax, args, out);
2273 break;
2274 }
2275 case kMin_SpecialIntrinsic: {
2276 TArray<SpvId> args = this->vectorize(arguments, out);
2277 SkASSERT(args.size() == 2);
2278 this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMin, GLSLstd450SMin,
2279 GLSLstd450UMin, args, out);
2280 break;
2281 }
2282 case kMix_SpecialIntrinsic: {
2283 TArray<SpvId> args = this->vectorize(arguments, out);
2284 SkASSERT(args.size() == 3);
2285 if (arguments[2]->type().componentType().isBoolean()) {
2286 // Use OpSelect to implement Boolean mix().
2287 SpvId falseId = this->writeExpression(*arguments[0], out);
2288 SpvId trueId = this->writeExpression(*arguments[1], out);
2289 SpvId conditionId = this->writeExpression(*arguments[2], out);
2290 this->writeInstruction(SpvOpSelect, this->getType(arguments[0]->type()), result,
2291 conditionId, trueId, falseId, out);
2292 } else {
2293 this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMix, SpvOpUndef,
2294 SpvOpUndef, args, out);
2295 }
2296 break;
2297 }
2298 case kSaturate_SpecialIntrinsic: {
2299 SkASSERT(arguments.size() == 1);
2300 ExpressionArray finalArgs;
2301 finalArgs.reserve_exact(3);
2302 finalArgs.push_back(arguments[0]->clone());
2303 finalArgs.push_back(Literal::MakeFloat(fContext, Position(), /*value=*/0));
2304 finalArgs.push_back(Literal::MakeFloat(fContext, Position(), /*value=*/1));
2305 TArray<SpvId> spvArgs = this->vectorize(finalArgs, out);
2306 this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp,
2307 GLSLstd450UClamp, spvArgs, out);
2308 break;
2309 }
2310 case kSmoothStep_SpecialIntrinsic: {
2311 TArray<SpvId> args = this->vectorize(arguments, out);
2312 SkASSERT(args.size() == 3);
2313 this->writeGLSLExtendedInstruction(callType, result, GLSLstd450SmoothStep, SpvOpUndef,
2314 SpvOpUndef, args, out);
2315 break;
2316 }
2317 case kStep_SpecialIntrinsic: {
2318 TArray<SpvId> args = this->vectorize(arguments, out);
2319 SkASSERT(args.size() == 2);
2320 this->writeGLSLExtendedInstruction(callType, result, GLSLstd450Step, SpvOpUndef,
2321 SpvOpUndef, args, out);
2322 break;
2323 }
2324 case kMatrixCompMult_SpecialIntrinsic: {
2325 SkASSERT(arguments.size() == 2);
2326 SpvId lhs = this->writeExpression(*arguments[0], out);
2327 SpvId rhs = this->writeExpression(*arguments[1], out);
2328 result = this->writeComponentwiseMatrixBinary(callType, lhs, rhs, SpvOpFMul, out);
2329 break;
2330 }
2331 case kAtomicAdd_SpecialIntrinsic:
2332 case kAtomicLoad_SpecialIntrinsic:
2333 case kAtomicStore_SpecialIntrinsic:
2334 result = this->writeAtomicIntrinsic(c, kind, result, out);
2335 break;
2336 case kStorageBarrier_SpecialIntrinsic:
2337 case kWorkgroupBarrier_SpecialIntrinsic: {
2338 // Both barrier types operate in the workgroup execution and memory scope and differ
2339 // only in memory semantics. storageBarrier() is not a device-scope barrier.
2340 SpvId scopeId =
2341 this->writeOpConstant(*fContext.fTypes.fUInt, (int32_t)SpvScopeWorkgroup);
2342 int32_t memSemMask = (kind == kStorageBarrier_SpecialIntrinsic)
2347 SpvId memorySemanticsId = this->writeOpConstant(*fContext.fTypes.fUInt, memSemMask);
2348 this->writeInstruction(SpvOpControlBarrier,
2349 scopeId, // execution scope
2350 scopeId, // memory scope
2351 memorySemanticsId,
2352 out);
2353 break;
2354 }
2355 }
2356 return result;
2357}
2358
2359SpvId SPIRVCodeGenerator::writeAtomicIntrinsic(const FunctionCall& c,
2360 SpecialIntrinsic kind,
2361 SpvId resultId,
2362 OutputStream& out) {
2363 const ExpressionArray& arguments = c.arguments();
2364 SkASSERT(!arguments.empty());
2365
2366 std::unique_ptr<LValue> atomicPtr = this->getLValue(*arguments[0], out);
2367 SpvId atomicPtrId = atomicPtr->getPointer();
2368 if (atomicPtrId == NA) {
2369 SkDEBUGFAILF("atomic intrinsic expected a pointer argument: %s",
2370 arguments[0]->description().c_str());
2371 return NA;
2372 }
2373
2374 SpvId memoryScopeId = NA;
2375 {
2376 // In SkSL, the atomicUint type can only be declared as a workgroup variable or SSBO block
2377 // member. The two memory scopes that these map to are "workgroup" and "device",
2378 // respectively.
2379 SpvScope memoryScope;
2380 switch (atomicPtr->storageClass()) {
2382 // We encode storage buffers in the uniform address space (with the BufferBlock
2383 // decorator).
2384 memoryScope = SpvScopeDevice;
2385 break;
2387 memoryScope = SpvScopeWorkgroup;
2388 break;
2389 default:
2390 SkDEBUGFAILF("atomic argument has invalid storage class: %d",
2391 atomicPtr->storageClass());
2392 return NA;
2393 }
2394 memoryScopeId = this->writeOpConstant(*fContext.fTypes.fUInt, (int32_t)memoryScope);
2395 }
2396
2397 SpvId relaxedMemoryOrderId =
2398 this->writeOpConstant(*fContext.fTypes.fUInt, (int32_t)SpvMemorySemanticsMaskNone);
2399
2400 switch (kind) {
2401 case kAtomicAdd_SpecialIntrinsic:
2402 SkASSERT(arguments.size() == 2);
2403 this->writeInstruction(SpvOpAtomicIAdd,
2404 this->getType(c.type()),
2405 resultId,
2406 atomicPtrId,
2407 memoryScopeId,
2408 relaxedMemoryOrderId,
2409 this->writeExpression(*arguments[1], out),
2410 out);
2411 break;
2412 case kAtomicLoad_SpecialIntrinsic:
2413 SkASSERT(arguments.size() == 1);
2414 this->writeInstruction(SpvOpAtomicLoad,
2415 this->getType(c.type()),
2416 resultId,
2417 atomicPtrId,
2418 memoryScopeId,
2419 relaxedMemoryOrderId,
2420 out);
2421 break;
2422 case kAtomicStore_SpecialIntrinsic:
2423 SkASSERT(arguments.size() == 2);
2424 this->writeInstruction(SpvOpAtomicStore,
2425 atomicPtrId,
2426 memoryScopeId,
2427 relaxedMemoryOrderId,
2428 this->writeExpression(*arguments[1], out),
2429 out);
2430 break;
2431 default:
2433 }
2434
2435 return resultId;
2436}
2437
2438SpvId SPIRVCodeGenerator::writeFunctionCallArgument(const FunctionCall& call,
2439 int argIndex,
2440 std::vector<TempVar>* tempVars,
2441 OutputStream& out,
2442 SpvId* outSynthesizedSamplerId) {
2443 const FunctionDeclaration& funcDecl = call.function();
2444 const Expression& arg = *call.arguments()[argIndex];
2445 ModifierFlags paramFlags = funcDecl.parameters()[argIndex]->modifierFlags();
2446
2447 // ID of temporary variable that we will use to hold this argument, or 0 if it is being
2448 // passed directly
2449 SpvId tmpVar;
2450 // if we need a temporary var to store this argument, this is the value to store in the var
2451 SpvId tmpValueId = NA;
2452
2453 if (is_out(paramFlags)) {
2454 std::unique_ptr<LValue> lv = this->getLValue(arg, out);
2455 // We handle out params with a temp var that we copy back to the original variable at the
2456 // end of the call. GLSL guarantees that the original variable will be unchanged until the
2457 // end of the call, and also that out params are written back to their original variables in
2458 // a specific order (left-to-right), so it's unsafe to pass a pointer to the original value.
2459 if (is_in(paramFlags)) {
2460 tmpValueId = lv->load(out);
2461 }
2462 tmpVar = this->nextId(&arg.type());
2463 tempVars->push_back(TempVar{tmpVar, &arg.type(), std::move(lv)});
2464 } else if (funcDecl.isIntrinsic()) {
2465 // Unlike user function calls, non-out intrinsic arguments don't need pointer parameters.
2466 return this->writeExpression(arg, out);
2467 } else if (arg.is<VariableReference>() &&
2468 (arg.type().typeKind() == Type::TypeKind::kSampler ||
2469 arg.type().typeKind() == Type::TypeKind::kSeparateSampler ||
2470 arg.type().typeKind() == Type::TypeKind::kTexture)) {
2471 // Opaque handle (sampler/texture) arguments are always declared as pointers but never
2472 // stored in intermediates when calling user-defined functions.
2473 //
2474 // The case for intrinsics (which take opaque arguments by value) is handled above just like
2475 // regular pointers.
2476 //
2477 // See getFunctionParameterType for further explanation.
2478 const Variable* var = arg.as<VariableReference>().variable();
2479
2480 // In Dawn-mode the texture and sampler arguments are forwarded to the helper function.
2481 if (fUseTextureSamplerPairs && var->type().isSampler()) {
2482 if (const auto* p = fSynthesizedSamplerMap.find(var)) {
2483 SkASSERT(outSynthesizedSamplerId);
2484
2485 SpvId* img = fVariableMap.find((*p)->fTexture.get());
2486 SpvId* sampler = fVariableMap.find((*p)->fSampler.get());
2487 SkASSERT(img);
2488 SkASSERT(sampler);
2489
2490 *outSynthesizedSamplerId = *sampler;
2491 return *img;
2492 }
2493 SkDEBUGFAIL("sampler missing from fSynthesizedSamplerMap");
2494 }
2495
2496 SpvId* entry = fVariableMap.find(var);
2497 SkASSERTF(entry, "%s", arg.description().c_str());
2498 return *entry;
2499 } else {
2500 // We always use pointer parameters when calling user functions.
2501 // See getFunctionParameterType for further explanation.
2502 tmpValueId = this->writeExpression(arg, out);
2503 tmpVar = this->nextId(nullptr);
2504 }
2505 this->writeInstruction(SpvOpVariable,
2506 this->getPointerType(arg.type(), SpvStorageClassFunction),
2507 tmpVar,
2509 fVariableBuffer);
2510 if (tmpValueId != NA) {
2511 this->writeOpStore(SpvStorageClassFunction, tmpVar, tmpValueId, out);
2512 }
2513 return tmpVar;
2514}
2515
2516void SPIRVCodeGenerator::copyBackTempVars(const std::vector<TempVar>& tempVars, OutputStream& out) {
2517 for (const TempVar& tempVar : tempVars) {
2518 SpvId load = this->nextId(tempVar.type);
2519 this->writeInstruction(SpvOpLoad, this->getType(*tempVar.type), load, tempVar.spvId, out);
2520 tempVar.lvalue->store(load, out);
2521 }
2522}
2523
2524SpvId SPIRVCodeGenerator::writeFunctionCall(const FunctionCall& c, OutputStream& out) {
2525 const FunctionDeclaration& function = c.function();
2526 if (function.isIntrinsic() && !function.definition()) {
2527 return this->writeIntrinsicCall(c, out);
2528 }
2529 const ExpressionArray& arguments = c.arguments();
2530 SpvId* entry = fFunctionMap.find(&function);
2531 if (!entry) {
2532 fContext.fErrors->error(c.fPosition, "function '" + function.description() +
2533 "' is not defined");
2534 return NA;
2535 }
2536 // Temp variables are used to write back out-parameters after the function call is complete.
2537 std::vector<TempVar> tempVars;
2538 TArray<SpvId> argumentIds;
2539 argumentIds.reserve_exact(arguments.size());
2540 for (int i = 0; i < arguments.size(); i++) {
2541 SpvId samplerId = NA;
2542 argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out, &samplerId));
2543 if (samplerId != NA) {
2544 argumentIds.push_back(samplerId);
2545 }
2546 }
2547 SpvId result = this->nextId(nullptr);
2548 this->writeOpCode(SpvOpFunctionCall, 4 + (int32_t)argumentIds.size(), out);
2549 this->writeWord(this->getType(c.type()), out);
2550 this->writeWord(result, out);
2551 this->writeWord(*entry, out);
2552 for (SpvId id : argumentIds) {
2553 this->writeWord(id, out);
2554 }
2555 // Now that the call is complete, we copy temp out-variables back to their real lvalues.
2556 this->copyBackTempVars(tempVars, out);
2557 return result;
2558}
2559
2560SpvId SPIRVCodeGenerator::castScalarToType(SpvId inputExprId,
2561 const Type& inputType,
2562 const Type& outputType,
2563 OutputStream& out) {
2564 if (outputType.isFloat()) {
2565 return this->castScalarToFloat(inputExprId, inputType, outputType, out);
2566 }
2567 if (outputType.isSigned()) {
2568 return this->castScalarToSignedInt(inputExprId, inputType, outputType, out);
2569 }
2570 if (outputType.isUnsigned()) {
2571 return this->castScalarToUnsignedInt(inputExprId, inputType, outputType, out);
2572 }
2573 if (outputType.isBoolean()) {
2574 return this->castScalarToBoolean(inputExprId, inputType, outputType, out);
2575 }
2576
2577 fContext.fErrors->error(Position(), "unsupported cast: " + inputType.description() + " to " +
2578 outputType.description());
2579 return inputExprId;
2580}
2581
2582SpvId SPIRVCodeGenerator::castScalarToFloat(SpvId inputId, const Type& inputType,
2583 const Type& outputType, OutputStream& out) {
2584 // Casting a float to float is a no-op.
2585 if (inputType.isFloat()) {
2586 return inputId;
2587 }
2588
2589 // Given the input type, generate the appropriate instruction to cast to float.
2590 SpvId result = this->nextId(&outputType);
2591 if (inputType.isBoolean()) {
2592 // Use OpSelect to convert the boolean argument to a literal 1.0 or 0.0.
2593 const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fFloat);
2594 const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fFloat);
2595 this->writeInstruction(SpvOpSelect, this->getType(outputType), result,
2596 inputId, oneID, zeroID, out);
2597 } else if (inputType.isSigned()) {
2598 this->writeInstruction(SpvOpConvertSToF, this->getType(outputType), result, inputId, out);
2599 } else if (inputType.isUnsigned()) {
2600 this->writeInstruction(SpvOpConvertUToF, this->getType(outputType), result, inputId, out);
2601 } else {
2602 SkDEBUGFAILF("unsupported type for float typecast: %s", inputType.description().c_str());
2603 return NA;
2604 }
2605 return result;
2606}
2607
2608SpvId SPIRVCodeGenerator::castScalarToSignedInt(SpvId inputId, const Type& inputType,
2609 const Type& outputType, OutputStream& out) {
2610 // Casting a signed int to signed int is a no-op.
2611 if (inputType.isSigned()) {
2612 return inputId;
2613 }
2614
2615 // Given the input type, generate the appropriate instruction to cast to signed int.
2616 SpvId result = this->nextId(&outputType);
2617 if (inputType.isBoolean()) {
2618 // Use OpSelect to convert the boolean argument to a literal 1 or 0.
2619 const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fInt);
2620 const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fInt);
2621 this->writeInstruction(SpvOpSelect, this->getType(outputType), result,
2622 inputId, oneID, zeroID, out);
2623 } else if (inputType.isFloat()) {
2624 this->writeInstruction(SpvOpConvertFToS, this->getType(outputType), result, inputId, out);
2625 } else if (inputType.isUnsigned()) {
2626 this->writeInstruction(SpvOpBitcast, this->getType(outputType), result, inputId, out);
2627 } else {
2628 SkDEBUGFAILF("unsupported type for signed int typecast: %s",
2629 inputType.description().c_str());
2630 return NA;
2631 }
2632 return result;
2633}
2634
2635SpvId SPIRVCodeGenerator::castScalarToUnsignedInt(SpvId inputId, const Type& inputType,
2636 const Type& outputType, OutputStream& out) {
2637 // Casting an unsigned int to unsigned int is a no-op.
2638 if (inputType.isUnsigned()) {
2639 return inputId;
2640 }
2641
2642 // Given the input type, generate the appropriate instruction to cast to unsigned int.
2643 SpvId result = this->nextId(&outputType);
2644 if (inputType.isBoolean()) {
2645 // Use OpSelect to convert the boolean argument to a literal 1u or 0u.
2646 const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fUInt);
2647 const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fUInt);
2648 this->writeInstruction(SpvOpSelect, this->getType(outputType), result,
2649 inputId, oneID, zeroID, out);
2650 } else if (inputType.isFloat()) {
2651 this->writeInstruction(SpvOpConvertFToU, this->getType(outputType), result, inputId, out);
2652 } else if (inputType.isSigned()) {
2653 this->writeInstruction(SpvOpBitcast, this->getType(outputType), result, inputId, out);
2654 } else {
2655 SkDEBUGFAILF("unsupported type for unsigned int typecast: %s",
2656 inputType.description().c_str());
2657 return NA;
2658 }
2659 return result;
2660}
2661
2662SpvId SPIRVCodeGenerator::castScalarToBoolean(SpvId inputId, const Type& inputType,
2663 const Type& outputType, OutputStream& out) {
2664 // Casting a bool to bool is a no-op.
2665 if (inputType.isBoolean()) {
2666 return inputId;
2667 }
2668
2669 // Given the input type, generate the appropriate instruction to cast to bool.
2670 SpvId result = this->nextId(nullptr);
2671 if (inputType.isSigned()) {
2672 // Synthesize a boolean result by comparing the input against a signed zero literal.
2673 const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fInt);
2674 this->writeInstruction(SpvOpINotEqual, this->getType(outputType), result,
2675 inputId, zeroID, out);
2676 } else if (inputType.isUnsigned()) {
2677 // Synthesize a boolean result by comparing the input against an unsigned zero literal.
2678 const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fUInt);
2679 this->writeInstruction(SpvOpINotEqual, this->getType(outputType), result,
2680 inputId, zeroID, out);
2681 } else if (inputType.isFloat()) {
2682 // Synthesize a boolean result by comparing the input against a floating-point zero literal.
2683 const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fFloat);
2684 this->writeInstruction(SpvOpFUnordNotEqual, this->getType(outputType), result,
2685 inputId, zeroID, out);
2686 } else {
2687 SkDEBUGFAILF("unsupported type for boolean typecast: %s", inputType.description().c_str());
2688 return NA;
2689 }
2690 return result;
2691}
2692
2693SpvId SPIRVCodeGenerator::writeMatrixCopy(SpvId src, const Type& srcType, const Type& dstType,
2694 OutputStream& out) {
2695 SkASSERT(srcType.isMatrix());
2696 SkASSERT(dstType.isMatrix());
2697 SkASSERT(srcType.componentType().matches(dstType.componentType()));
2698 const Type& srcColumnType = srcType.componentType().toCompound(fContext, srcType.rows(), 1);
2699 const Type& dstColumnType = dstType.componentType().toCompound(fContext, dstType.rows(), 1);
2700 SkASSERT(dstType.componentType().isFloat());
2701 SpvId dstColumnTypeId = this->getType(dstColumnType);
2702 const SpvId zeroId = this->writeLiteral(0.0, dstType.componentType());
2703 const SpvId oneId = this->writeLiteral(1.0, dstType.componentType());
2704
2705 STArray<4, SpvId> columns;
2706 for (int i = 0; i < dstType.columns(); i++) {
2707 if (i < srcType.columns()) {
2708 // we're still inside the src matrix, copy the column
2709 SpvId srcColumn = this->writeOpCompositeExtract(srcColumnType, src, i, out);
2710 SpvId dstColumn;
2711 if (srcType.rows() == dstType.rows()) {
2712 // columns are equal size, don't need to do anything
2713 dstColumn = srcColumn;
2714 }
2715 else if (dstType.rows() > srcType.rows()) {
2716 // dst column is bigger, need to zero-pad it
2718 values.push_back(srcColumn);
2719 for (int j = srcType.rows(); j < dstType.rows(); ++j) {
2720 values.push_back((i == j) ? oneId : zeroId);
2721 }
2722 dstColumn = this->writeOpCompositeConstruct(dstColumnType, values, out);
2723 }
2724 else {
2725 // dst column is smaller, need to swizzle the src column
2726 dstColumn = this->nextId(&dstType);
2727 this->writeOpCode(SpvOpVectorShuffle, 5 + dstType.rows(), out);
2728 this->writeWord(dstColumnTypeId, out);
2729 this->writeWord(dstColumn, out);
2730 this->writeWord(srcColumn, out);
2731 this->writeWord(srcColumn, out);
2732 for (int j = 0; j < dstType.rows(); j++) {
2733 this->writeWord(j, out);
2734 }
2735 }
2736 columns.push_back(dstColumn);
2737 } else {
2738 // we're past the end of the src matrix, need to synthesize an identity-matrix column
2740 for (int j = 0; j < dstType.rows(); ++j) {
2741 values.push_back((i == j) ? oneId : zeroId);
2742 }
2743 columns.push_back(this->writeOpCompositeConstruct(dstColumnType, values, out));
2744 }
2745 }
2746
2747 return this->writeOpCompositeConstruct(dstType, columns, out);
2748}
2749
2750void SPIRVCodeGenerator::addColumnEntry(const Type& columnType,
2751 TArray<SpvId>* currentColumn,
2752 TArray<SpvId>* columnIds,
2753 int rows,
2754 SpvId entry,
2755 OutputStream& out) {
2756 SkASSERT(currentColumn->size() < rows);
2757 currentColumn->push_back(entry);
2758 if (currentColumn->size() == rows) {
2759 // Synthesize this column into a vector.
2760 SpvId columnId = this->writeOpCompositeConstruct(columnType, *currentColumn, out);
2761 columnIds->push_back(columnId);
2762 currentColumn->clear();
2763 }
2764}
2765
2766SpvId SPIRVCodeGenerator::writeMatrixConstructor(const ConstructorCompound& c, OutputStream& out) {
2767 const Type& type = c.type();
2768 SkASSERT(type.isMatrix());
2769 SkASSERT(!c.arguments().empty());
2770 const Type& arg0Type = c.arguments()[0]->type();
2771 // go ahead and write the arguments so we don't try to write new instructions in the middle of
2772 // an instruction
2773 STArray<16, SpvId> arguments;
2774 for (const std::unique_ptr<Expression>& arg : c.arguments()) {
2775 arguments.push_back(this->writeExpression(*arg, out));
2776 }
2777
2778 if (arguments.size() == 1 && arg0Type.isVector()) {
2779 // Special-case handling of float4 -> mat2x2.
2780 SkASSERT(type.rows() == 2 && type.columns() == 2);
2781 SkASSERT(arg0Type.columns() == 4);
2782 SpvId v[4];
2783 for (int i = 0; i < 4; ++i) {
2784 v[i] = this->writeOpCompositeExtract(type.componentType(), arguments[0], i, out);
2785 }
2786 const Type& vecType = type.columnType(fContext);
2787 SpvId v0v1 = this->writeOpCompositeConstruct(vecType, {v[0], v[1]}, out);
2788 SpvId v2v3 = this->writeOpCompositeConstruct(vecType, {v[2], v[3]}, out);
2789 return this->writeOpCompositeConstruct(type, {v0v1, v2v3}, out);
2790 }
2791
2792 int rows = type.rows();
2793 const Type& columnType = type.columnType(fContext);
2794 // SpvIds of completed columns of the matrix.
2795 STArray<4, SpvId> columnIds;
2796 // SpvIds of scalars we have written to the current column so far.
2797 STArray<4, SpvId> currentColumn;
2798 for (int i = 0; i < arguments.size(); i++) {
2799 const Type& argType = c.arguments()[i]->type();
2800 if (currentColumn.empty() && argType.isVector() && argType.columns() == rows) {
2801 // This vector is a complete matrix column by itself and can be used as-is.
2802 columnIds.push_back(arguments[i]);
2803 } else if (argType.columns() == 1) {
2804 // This argument is a lone scalar and can be added to the current column as-is.
2805 this->addColumnEntry(columnType, &currentColumn, &columnIds, rows, arguments[i], out);
2806 } else {
2807 // This argument needs to be decomposed into its constituent scalars.
2808 for (int j = 0; j < argType.columns(); ++j) {
2809 SpvId swizzle = this->writeOpCompositeExtract(argType.componentType(),
2810 arguments[i], j, out);
2811 this->addColumnEntry(columnType, &currentColumn, &columnIds, rows, swizzle, out);
2812 }
2813 }
2814 }
2815 SkASSERT(columnIds.size() == type.columns());
2816 return this->writeOpCompositeConstruct(type, columnIds, out);
2817}
2818
2819SpvId SPIRVCodeGenerator::writeConstructorCompound(const ConstructorCompound& c,
2820 OutputStream& out) {
2821 return c.type().isMatrix() ? this->writeMatrixConstructor(c, out)
2822 : this->writeVectorConstructor(c, out);
2823}
2824
2825SpvId SPIRVCodeGenerator::writeVectorConstructor(const ConstructorCompound& c, OutputStream& out) {
2826 const Type& type = c.type();
2827 const Type& componentType = type.componentType();
2828 SkASSERT(type.isVector());
2829
2830 STArray<4, SpvId> arguments;
2831 for (int i = 0; i < c.arguments().size(); i++) {
2832 const Type& argType = c.arguments()[i]->type();
2833 SkASSERT(componentType.numberKind() == argType.componentType().numberKind());
2834
2835 SpvId arg = this->writeExpression(*c.arguments()[i], out);
2836 if (argType.isMatrix()) {
2837 // CompositeConstruct cannot take a 2x2 matrix as an input, so we need to extract out
2838 // each scalar separately.
2839 SkASSERT(argType.rows() == 2);
2840 SkASSERT(argType.columns() == 2);
2841 for (int j = 0; j < 4; ++j) {
2842 arguments.push_back(this->writeOpCompositeExtract(componentType, arg,
2843 j / 2, j % 2, out));
2844 }
2845 } else if (argType.isVector()) {
2846 // There's a bug in the Intel Vulkan driver where OpCompositeConstruct doesn't handle
2847 // vector arguments at all, so we always extract each vector component and pass them
2848 // into OpCompositeConstruct individually.
2849 for (int j = 0; j < argType.columns(); j++) {
2850 arguments.push_back(this->writeOpCompositeExtract(componentType, arg, j, out));
2851 }
2852 } else {
2853 arguments.push_back(arg);
2854 }
2855 }
2856
2857 return this->writeOpCompositeConstruct(type, arguments, out);
2858}
2859
2860SpvId SPIRVCodeGenerator::writeConstructorSplat(const ConstructorSplat& c, OutputStream& out) {
2861 // Write the splat argument.
2862 SpvId argument = this->writeExpression(*c.argument(), out);
2863
2864 // Generate a OpCompositeConstruct which repeats the argument N times.
2866 values.push_back_n(/*n=*/c.type().columns(), /*t=*/argument);
2867 return this->writeOpCompositeConstruct(c.type(), values, out);
2868}
2869
2870SpvId SPIRVCodeGenerator::writeCompositeConstructor(const AnyConstructor& c, OutputStream& out) {
2871 SkASSERT(c.type().isArray() || c.type().isStruct());
2872 auto ctorArgs = c.argumentSpan();
2873
2874 STArray<4, SpvId> arguments;
2875 for (const std::unique_ptr<Expression>& arg : ctorArgs) {
2876 arguments.push_back(this->writeExpression(*arg, out));
2877 }
2878
2879 return this->writeOpCompositeConstruct(c.type(), arguments, out);
2880}
2881
2882SpvId SPIRVCodeGenerator::writeConstructorScalarCast(const ConstructorScalarCast& c,
2883 OutputStream& out) {
2884 const Type& type = c.type();
2885 if (type.componentType().numberKind() == c.argument()->type().componentType().numberKind()) {
2886 return this->writeExpression(*c.argument(), out);
2887 }
2888
2889 const Expression& ctorExpr = *c.argument();
2890 SpvId expressionId = this->writeExpression(ctorExpr, out);
2891 return this->castScalarToType(expressionId, ctorExpr.type(), type, out);
2892}
2893
2894SpvId SPIRVCodeGenerator::writeConstructorCompoundCast(const ConstructorCompoundCast& c,
2895 OutputStream& out) {
2896 const Type& ctorType = c.type();
2897 const Type& argType = c.argument()->type();
2898 SkASSERT(ctorType.isVector() || ctorType.isMatrix());
2899
2900 // Write the composite that we are casting. If the actual type matches, we are done.
2901 SpvId compositeId = this->writeExpression(*c.argument(), out);
2902 if (ctorType.componentType().numberKind() == argType.componentType().numberKind()) {
2903 return compositeId;
2904 }
2905
2906 // writeMatrixCopy can cast matrices to a different type.
2907 if (ctorType.isMatrix()) {
2908 return this->writeMatrixCopy(compositeId, argType, ctorType, out);
2909 }
2910
2911 // SPIR-V doesn't support vector(vector-of-different-type) directly, so we need to extract the
2912 // components and convert each one manually.
2913 const Type& srcType = argType.componentType();
2914 const Type& dstType = ctorType.componentType();
2915
2916 STArray<4, SpvId> arguments;
2917 for (int index = 0; index < argType.columns(); ++index) {
2918 SpvId componentId = this->writeOpCompositeExtract(srcType, compositeId, index, out);
2919 arguments.push_back(this->castScalarToType(componentId, srcType, dstType, out));
2920 }
2921
2922 return this->writeOpCompositeConstruct(ctorType, arguments, out);
2923}
2924
2925SpvId SPIRVCodeGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c,
2926 OutputStream& out) {
2927 const Type& type = c.type();
2928 SkASSERT(type.isMatrix());
2929 SkASSERT(c.argument()->type().isScalar());
2930
2931 // Write out the scalar argument.
2932 SpvId diagonal = this->writeExpression(*c.argument(), out);
2933
2934 // Build the diagonal matrix.
2935 SpvId zeroId = this->writeLiteral(0.0, *fContext.fTypes.fFloat);
2936
2937 const Type& vecType = type.columnType(fContext);
2938 STArray<4, SpvId> columnIds;
2939 STArray<4, SpvId> arguments;
2940 arguments.resize(type.rows());
2941 for (int column = 0; column < type.columns(); column++) {
2942 for (int row = 0; row < type.rows(); row++) {
2943 arguments[row] = (row == column) ? diagonal : zeroId;
2944 }
2945 columnIds.push_back(this->writeOpCompositeConstruct(vecType, arguments, out));
2946 }
2947 return this->writeOpCompositeConstruct(type, columnIds, out);
2948}
2949
2950SpvId SPIRVCodeGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& c,
2951 OutputStream& out) {
2952 // Write the input matrix.
2953 SpvId argument = this->writeExpression(*c.argument(), out);
2954
2955 // Use matrix-copy to resize the input matrix to its new size.
2956 return this->writeMatrixCopy(argument, c.argument()->type(), c.type(), out);
2957}
2958
2960 const Variable& var, SpvStorageClass_ fallbackStorageClass) {
2961 SkASSERT(var.storage() == Variable::Storage::kGlobal);
2962
2963 if (var.type().typeKind() == Type::TypeKind::kSampler ||
2967 }
2968
2969 const Layout& layout = var.layout();
2971 if (flags & ModifierFlag::kIn) {
2973 return SpvStorageClassInput;
2974 }
2975 if (flags & ModifierFlag::kOut) {
2977 return SpvStorageClassOutput;
2978 }
2979 if (flags.isUniform()) {
2980 if (layout.fFlags & LayoutFlag::kPushConstant) {
2982 }
2984 }
2985 if (flags.isBuffer()) {
2986 // Note: In SPIR-V 1.3, a storage buffer can be declared with the "StorageBuffer"
2987 // storage class and the "Block" decoration and the <1.3 approach we use here ("Uniform"
2988 // storage class and the "BufferBlock" decoration) is deprecated. Since we target SPIR-V
2989 // 1.0, we have to use the deprecated approach which is well supported in Vulkan and
2990 // addresses SkSL use cases (notably SkSL currently doesn't support pointer features that
2991 // would benefit from SPV_KHR_variable_pointers capabilities).
2993 }
2994 if (flags.isWorkgroup()) {
2996 }
2997 return fallbackStorageClass;
2998}
2999
3001 switch (expr.kind()) {
3002 case Expression::Kind::kVariableReference: {
3003 const Variable& var = *expr.as<VariableReference>().variable();
3004 if (var.storage() != Variable::Storage::kGlobal) {
3006 }
3008 }
3009 case Expression::Kind::kFieldAccess:
3010 return get_storage_class(*expr.as<FieldAccess>().base());
3011 case Expression::Kind::kIndex:
3012 return get_storage_class(*expr.as<IndexExpression>().base());
3013 default:
3015 }
3016}
3017
3018TArray<SpvId> SPIRVCodeGenerator::getAccessChain(const Expression& expr, OutputStream& out) {
3019 switch (expr.kind()) {
3020 case Expression::Kind::kIndex: {
3021 const IndexExpression& indexExpr = expr.as<IndexExpression>();
3022 if (indexExpr.base()->is<Swizzle>()) {
3023 // Access chains don't directly support dynamically indexing into a swizzle, but we
3024 // can rewrite them into a supported form.
3025 return this->getAccessChain(*Transform::RewriteIndexedSwizzle(fContext, indexExpr),
3026 out);
3027 }
3028 // All other index-expressions can be represented as typical access chains.
3029 TArray<SpvId> chain = this->getAccessChain(*indexExpr.base(), out);
3030 chain.push_back(this->writeExpression(*indexExpr.index(), out));
3031 return chain;
3032 }
3033 case Expression::Kind::kFieldAccess: {
3034 const FieldAccess& fieldExpr = expr.as<FieldAccess>();
3035 TArray<SpvId> chain = this->getAccessChain(*fieldExpr.base(), out);
3036 chain.push_back(this->writeLiteral(fieldExpr.fieldIndex(), *fContext.fTypes.fInt));
3037 return chain;
3038 }
3039 default: {
3040 SpvId id = this->getLValue(expr, out)->getPointer();
3041 SkASSERT(id != NA);
3042 return TArray<SpvId>{id};
3043 }
3044 }
3046}
3047
3049public:
3050 PointerLValue(SPIRVCodeGenerator& gen, SpvId pointer, bool isMemoryObject, SpvId type,
3051 SPIRVCodeGenerator::Precision precision, SpvStorageClass_ storageClass)
3052 : fGen(gen)
3053 , fPointer(pointer)
3054 , fIsMemoryObject(isMemoryObject)
3055 , fType(type)
3056 , fPrecision(precision)
3057 , fStorageClass(storageClass) {}
3058
3059 SpvId getPointer() override {
3060 return fPointer;
3061 }
3062
3063 bool isMemoryObjectPointer() const override {
3064 return fIsMemoryObject;
3065 }
3066
3068 return fStorageClass;
3069 }
3070
3071 SpvId load(OutputStream& out) override {
3072 return fGen.writeOpLoad(fType, fPrecision, fPointer, out);
3073 }
3074
3075 void store(SpvId value, OutputStream& out) override {
3076 if (!fIsMemoryObject) {
3077 // We are going to write into an access chain; this could represent one component of a
3078 // vector, or one element of an array. This has the potential to invalidate other,
3079 // *unknown* elements of our store cache. (e.g. if the store cache holds `%50 = myVec4`,
3080 // and we store `%60 = myVec4.z`, this invalidates the cached value for %50.) To avoid
3081 // relying on stale data, reset the store cache entirely when this happens.
3082 fGen.fStoreCache.reset();
3083 }
3084
3085 fGen.writeOpStore(fStorageClass, fPointer, value, out);
3086 }
3087
3088private:
3089 SPIRVCodeGenerator& fGen;
3090 const SpvId fPointer;
3091 const bool fIsMemoryObject;
3092 const SpvId fType;
3093 const SPIRVCodeGenerator::Precision fPrecision;
3094 const SpvStorageClass_ fStorageClass;
3095};
3096
3098public:
3100 const Type& baseType, const Type& swizzleType, SpvStorageClass_ storageClass)
3101 : fGen(gen)
3102 , fVecPointer(vecPointer)
3103 , fComponents(components)
3104 , fBaseType(&baseType)
3105 , fSwizzleType(&swizzleType)
3106 , fStorageClass(storageClass) {}
3107
3108 bool applySwizzle(const ComponentArray& components, const Type& newType) override {
3109 ComponentArray updatedSwizzle;
3110 for (int8_t component : components) {
3111 if (component < 0 || component >= fComponents.size()) {
3112 SkDEBUGFAILF("swizzle accessed nonexistent component %d", (int)component);
3113 return false;
3114 }
3115 updatedSwizzle.push_back(fComponents[component]);
3116 }
3117 fComponents = updatedSwizzle;
3118 fSwizzleType = &newType;
3119 return true;
3120 }
3121
3123 return fStorageClass;
3124 }
3125
3126 SpvId load(OutputStream& out) override {
3127 SpvId base = fGen.nextId(fBaseType);
3128 fGen.writeInstruction(SpvOpLoad, fGen.getType(*fBaseType), base, fVecPointer, out);
3129 SpvId result = fGen.nextId(fBaseType);
3130 fGen.writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) fComponents.size(), out);
3131 fGen.writeWord(fGen.getType(*fSwizzleType), out);
3132 fGen.writeWord(result, out);
3133 fGen.writeWord(base, out);
3134 fGen.writeWord(base, out);
3135 for (int component : fComponents) {
3136 fGen.writeWord(component, out);
3137 }
3138 return result;
3139 }
3140
3141 void store(SpvId value, OutputStream& out) override {
3142 // use OpVectorShuffle to mix and match the vector components. We effectively create
3143 // a virtual vector out of the concatenation of the left and right vectors, and then
3144 // select components from this virtual vector to make the result vector. For
3145 // instance, given:
3146 // float3L = ...;
3147 // float3R = ...;
3148 // L.xz = R.xy;
3149 // we end up with the virtual vector (L.x, L.y, L.z, R.x, R.y, R.z). Then we want
3150 // our result vector to look like (R.x, L.y, R.y), so we need to select indices
3151 // (3, 1, 4).
3152 SpvId base = fGen.nextId(fBaseType);
3153 fGen.writeInstruction(SpvOpLoad, fGen.getType(*fBaseType), base, fVecPointer, out);
3154 SpvId shuffle = fGen.nextId(fBaseType);
3155 fGen.writeOpCode(SpvOpVectorShuffle, 5 + fBaseType->columns(), out);
3156 fGen.writeWord(fGen.getType(*fBaseType), out);
3157 fGen.writeWord(shuffle, out);
3158 fGen.writeWord(base, out);
3159 fGen.writeWord(value, out);
3160 for (int i = 0; i < fBaseType->columns(); i++) {
3161 // current offset into the virtual vector, defaults to pulling the unmodified
3162 // value from the left side
3163 int offset = i;
3164 // check to see if we are writing this component
3165 for (int j = 0; j < fComponents.size(); j++) {
3166 if (fComponents[j] == i) {
3167 // we're writing to this component, so adjust the offset to pull from
3168 // the correct component of the right side instead of preserving the
3169 // value from the left
3170 offset = (int) (j + fBaseType->columns());
3171 break;
3172 }
3173 }
3174 fGen.writeWord(offset, out);
3175 }
3176 fGen.writeOpStore(fStorageClass, fVecPointer, shuffle, out);
3177 }
3178
3179private:
3180 SPIRVCodeGenerator& fGen;
3181 const SpvId fVecPointer;
3182 ComponentArray fComponents;
3183 const Type* fBaseType;
3184 const Type* fSwizzleType;
3185 const SpvStorageClass_ fStorageClass;
3186};
3187
3188int SPIRVCodeGenerator::findUniformFieldIndex(const Variable& var) const {
3189 int* fieldIndex = fTopLevelUniformMap.find(&var);
3190 return fieldIndex ? *fieldIndex : -1;
3191}
3192
3193std::unique_ptr<SPIRVCodeGenerator::LValue> SPIRVCodeGenerator::getLValue(const Expression& expr,
3194 OutputStream& out) {
3195 const Type& type = expr.type();
3196 Precision precision = type.highPrecision() ? Precision::kDefault : Precision::kRelaxed;
3197 switch (expr.kind()) {
3198 case Expression::Kind::kVariableReference: {
3199 const Variable& var = *expr.as<VariableReference>().variable();
3200 int uniformIdx = this->findUniformFieldIndex(var);
3201 if (uniformIdx >= 0) {
3202 // Access uniforms via an AccessChain into the uniform-buffer struct.
3203 SpvId memberId = this->nextId(nullptr);
3204 SpvId typeId = this->getPointerType(type, SpvStorageClassUniform);
3205 SpvId uniformIdxId = this->writeLiteral((double)uniformIdx, *fContext.fTypes.fInt);
3206 this->writeInstruction(SpvOpAccessChain, typeId, memberId, fUniformBufferId,
3207 uniformIdxId, out);
3208 return std::make_unique<PointerLValue>(
3209 *this,
3210 memberId,
3211 /*isMemoryObjectPointer=*/true,
3212 this->getType(type, kDefaultTypeLayout, this->memoryLayoutForVariable(var)),
3213 precision,
3215 }
3216
3217 SpvId* entry = fVariableMap.find(&var);
3218 SkASSERTF(entry, "%s", expr.description().c_str());
3219
3220 if (var.layout().fBuiltin == SK_SAMPLEMASKIN_BUILTIN ||
3221 var.layout().fBuiltin == SK_SAMPLEMASK_BUILTIN) {
3222 // Access sk_SampleMask and sk_SampleMaskIn via an array access, since Vulkan
3223 // represents sample masks as an array of uints.
3224 SpvStorageClass_ storageClass =
3226 SkASSERT(storageClass != SpvStorageClassPrivate);
3227 SkASSERT(type.matches(*fContext.fTypes.fUInt));
3228
3229 SpvId accessId = this->nextId(nullptr);
3230 SpvId typeId = this->getPointerType(type, storageClass);
3231 SpvId indexId = this->writeLiteral(0.0, *fContext.fTypes.fInt);
3232 this->writeInstruction(SpvOpAccessChain, typeId, accessId, *entry, indexId, out);
3233 return std::make_unique<PointerLValue>(*this,
3234 accessId,
3235 /*isMemoryObjectPointer=*/true,
3236 this->getType(type),
3237 precision,
3238 storageClass);
3239 }
3240
3241 SpvId typeId = this->getType(type, var.layout(), this->memoryLayoutForVariable(var));
3242 return std::make_unique<PointerLValue>(*this, *entry,
3243 /*isMemoryObjectPointer=*/true,
3244 typeId, precision, get_storage_class(expr));
3245 }
3246 case Expression::Kind::kIndex: // fall through
3247 case Expression::Kind::kFieldAccess: {
3248 TArray<SpvId> chain = this->getAccessChain(expr, out);
3249 SpvId member = this->nextId(nullptr);
3250 SpvStorageClass_ storageClass = get_storage_class(expr);
3251 this->writeOpCode(SpvOpAccessChain, (SpvId) (3 + chain.size()), out);
3252 this->writeWord(this->getPointerType(type, storageClass), out);
3253 this->writeWord(member, out);
3254 for (SpvId idx : chain) {
3255 this->writeWord(idx, out);
3256 }
3257 return std::make_unique<PointerLValue>(
3258 *this,
3259 member,
3260 /*isMemoryObjectPointer=*/false,
3261 this->getType(type,
3263 this->memoryLayoutForStorageClass(storageClass)),
3264 precision,
3265 storageClass);
3266 }
3267 case Expression::Kind::kSwizzle: {
3268 const Swizzle& swizzle = expr.as<Swizzle>();
3269 std::unique_ptr<LValue> lvalue = this->getLValue(*swizzle.base(), out);
3270 if (lvalue->applySwizzle(swizzle.components(), type)) {
3271 return lvalue;
3272 }
3273 SpvId base = lvalue->getPointer();
3274 if (base == NA) {
3275 fContext.fErrors->error(swizzle.fPosition,
3276 "unable to retrieve lvalue from swizzle");
3277 }
3278 SpvStorageClass_ storageClass = get_storage_class(*swizzle.base());
3279 if (swizzle.components().size() == 1) {
3280 SpvId member = this->nextId(nullptr);
3281 SpvId typeId = this->getPointerType(type, storageClass);
3282 SpvId indexId = this->writeLiteral(swizzle.components()[0], *fContext.fTypes.fInt);
3283 this->writeInstruction(SpvOpAccessChain, typeId, member, base, indexId, out);
3284 return std::make_unique<PointerLValue>(*this, member,
3285 /*isMemoryObjectPointer=*/false,
3286 this->getType(type),
3287 precision, storageClass);
3288 } else {
3289 return std::make_unique<SwizzleLValue>(*this, base, swizzle.components(),
3290 swizzle.base()->type(), type, storageClass);
3291 }
3292 }
3293 default: {
3294 // expr isn't actually an lvalue, create a placeholder variable for it. This case
3295 // happens due to the need to store values in temporary variables during function
3296 // calls (see comments in getFunctionParameterType); erroneous uses of rvalues as
3297 // lvalues should have been caught before code generation.
3298 //
3299 // This is with the exception of opaque handle types (textures/samplers) which are
3300 // always defined as UniformConstant pointers and don't need to be explicitly stored
3301 // into a temporary (which is handled explicitly in writeFunctionCallArgument).
3302 SpvId result = this->nextId(nullptr);
3303 SpvId pointerType = this->getPointerType(type, SpvStorageClassFunction);
3304 this->writeInstruction(SpvOpVariable, pointerType, result, SpvStorageClassFunction,
3305 fVariableBuffer);
3306 this->writeOpStore(SpvStorageClassFunction, result, this->writeExpression(expr, out),
3307 out);
3308 return std::make_unique<PointerLValue>(*this, result, /*isMemoryObjectPointer=*/true,
3309 this->getType(type), precision,
3311 }
3312 }
3313}
3314
3315std::unique_ptr<Expression> SPIRVCodeGenerator::identifier(std::string_view name) {
3316 std::unique_ptr<Expression> expr =
3317 fProgram.fSymbols->instantiateSymbolRef(fContext, name, Position());
3318 return expr ? std::move(expr)
3319 : Poison::Make(Position(), fContext);
3320}
3321
3322SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, OutputStream& out) {
3323 const Variable* variable = ref.variable();
3324 switch (variable->layout().fBuiltin) {
3326 // Down below, we rewrite raw references to sk_FragCoord with expressions that reference
3327 // DEVICE_FRAGCOORDS_BUILTIN. This is a fake variable that means we need to directly
3328 // access the fragcoord; do so now.
3329 return this->getLValue(*this->identifier("sk_FragCoord"), out)->load(out);
3330 }
3332 // Down below, we rewrite raw references to sk_Clockwise with expressions that reference
3333 // DEVICE_CLOCKWISE_BUILTIN. This is a fake variable that means we need to directly
3334 // access front facing; do so now.
3335 return this->getLValue(*this->identifier("sk_Clockwise"), out)->load(out);
3336 }
3338 // sk_SecondaryFragColor corresponds to gl_SecondaryFragColorEXT, which isn't supposed
3339 // to appear in a SPIR-V program (it's only valid in ES2). Report an error.
3340 fContext.fErrors->error(ref.fPosition,
3341 "sk_SecondaryFragColor is not allowed in SPIR-V");
3342 return NA;
3343 }
3344 case SK_FRAGCOORD_BUILTIN: {
3345 if (fProgram.fConfig->fSettings.fForceNoRTFlip) {
3346 return this->getLValue(*this->identifier("sk_FragCoord"), out)->load(out);
3347 }
3348
3349 // Handle inserting use of uniform to flip y when referencing sk_FragCoord.
3350 this->addRTFlipUniform(ref.fPosition);
3351 // Use sk_RTAdjust to compute the flipped coordinate
3352 // Use a uniform to flip the Y coordinate. The new expression will be written in
3353 // terms of $device_FragCoords, which is a fake variable that means "access the
3354 // underlying fragcoords directly without flipping it".
3355 static constexpr char DEVICE_COORDS_NAME[] = "$device_FragCoords";
3356 if (!fProgram.fSymbols->find(DEVICE_COORDS_NAME)) {
3357 AutoAttachPoolToThread attach(fProgram.fPool.get());
3358 Layout layout;
3359 layout.fBuiltin = DEVICE_FRAGCOORDS_BUILTIN;
3360 auto coordsVar = Variable::Make(/*pos=*/Position(),
3361 /*modifiersPosition=*/Position(),
3362 layout,
3364 fContext.fTypes.fFloat4.get(),
3365 DEVICE_COORDS_NAME,
3366 /*mangledName=*/"",
3367 /*builtin=*/true,
3368 Variable::Storage::kGlobal);
3369 fProgram.fSymbols->add(fContext, std::move(coordsVar));
3370 }
3371 std::unique_ptr<Expression> deviceCoord = this->identifier(DEVICE_COORDS_NAME);
3372 std::unique_ptr<Expression> rtFlip = this->identifier(SKSL_RTFLIP_NAME);
3373 SpvId rtFlipX = this->writeSwizzle(*rtFlip, {SwizzleComponent::X}, out);
3374 SpvId rtFlipY = this->writeSwizzle(*rtFlip, {SwizzleComponent::Y}, out);
3375 SpvId deviceCoordX = this->writeSwizzle(*deviceCoord, {SwizzleComponent::X}, out);
3376 SpvId deviceCoordY = this->writeSwizzle(*deviceCoord, {SwizzleComponent::Y}, out);
3377 SpvId deviceCoordZW = this->writeSwizzle(*deviceCoord, {SwizzleComponent::Z,
3379 // Compute `flippedY = u_RTFlip.y * $device_FragCoords.y`.
3380 SpvId flippedY = this->writeBinaryExpression(
3382 *fContext.fTypes.fFloat, deviceCoordY,
3383 *fContext.fTypes.fFloat, out);
3384
3385 // Compute `flippedY = u_RTFlip.x + flippedY`.
3386 flippedY = this->writeBinaryExpression(
3388 *fContext.fTypes.fFloat, flippedY,
3389 *fContext.fTypes.fFloat, out);
3390
3391 // Return `float4(deviceCoord.x, flippedY, deviceCoord.zw)`.
3392 return this->writeOpCompositeConstruct(*fContext.fTypes.fFloat4,
3393 {deviceCoordX, flippedY, deviceCoordZW},
3394 out);
3395 }
3396 case SK_CLOCKWISE_BUILTIN: {
3397 if (fProgram.fConfig->fSettings.fForceNoRTFlip) {
3398 return this->getLValue(*this->identifier("sk_Clockwise"), out)->load(out);
3399 }
3400
3401 // Apply RTFlip to sk_Clockwise.
3402 this->addRTFlipUniform(ref.fPosition);
3403 // Use a uniform to flip the Y coordinate. The new expression will be written in
3404 // terms of $device_Clockwise, which is a fake variable that means "access the
3405 // underlying FrontFacing directly".
3406 static constexpr char DEVICE_CLOCKWISE_NAME[] = "$device_Clockwise";
3407 if (!fProgram.fSymbols->find(DEVICE_CLOCKWISE_NAME)) {
3408 AutoAttachPoolToThread attach(fProgram.fPool.get());
3409 Layout layout;
3410 layout.fBuiltin = DEVICE_CLOCKWISE_BUILTIN;
3411 auto clockwiseVar = Variable::Make(/*pos=*/Position(),
3412 /*modifiersPosition=*/Position(),
3413 layout,
3415 fContext.fTypes.fBool.get(),
3416 DEVICE_CLOCKWISE_NAME,
3417 /*mangledName=*/"",
3418 /*builtin=*/true,
3419 Variable::Storage::kGlobal);
3420 fProgram.fSymbols->add(fContext, std::move(clockwiseVar));
3421 }
3422 // FrontFacing in Vulkan is defined in terms of a top-down render target. In Skia,
3423 // we use the default convention of "counter-clockwise face is front".
3424
3425 // Compute `positiveRTFlip = (rtFlip.y > 0)`.
3426 std::unique_ptr<Expression> rtFlip = this->identifier(SKSL_RTFLIP_NAME);
3427 SpvId rtFlipY = this->writeSwizzle(*rtFlip, {SwizzleComponent::Y}, out);
3428 SpvId zero = this->writeLiteral(0.0, *fContext.fTypes.fFloat);
3429 SpvId positiveRTFlip = this->writeBinaryExpression(
3431 *fContext.fTypes.fFloat, zero,
3432 *fContext.fTypes.fBool, out);
3433
3434 // Compute `positiveRTFlip ^^ $device_Clockwise`.
3435 std::unique_ptr<Expression> deviceClockwise = this->identifier(DEVICE_CLOCKWISE_NAME);
3436 SpvId deviceClockwiseID = this->writeExpression(*deviceClockwise, out);
3437 return this->writeBinaryExpression(
3439 *fContext.fTypes.fBool, deviceClockwiseID,
3440 *fContext.fTypes.fBool, out);
3441 }
3442 default: {
3443 // Constant-propagate variables that have a known compile-time value.
3444 if (const Expression* expr = ConstantFolder::GetConstantValueOrNull(ref)) {
3445 return this->writeExpression(*expr, out);
3446 }
3447
3448 // A reference to a sampler variable at global scope with synthesized texture/sampler
3449 // backing should construct a function-scope combined image-sampler from the synthesized
3450 // constituents. This is the case in which a sample intrinsic was invoked.
3451 //
3452 // Variable references to opaque handles (texture/sampler) that appear as the argument
3453 // of a user-defined function call are explicitly handled in writeFunctionCallArgument.
3454 if (fUseTextureSamplerPairs && variable->type().isSampler()) {
3455 if (const auto* p = fSynthesizedSamplerMap.find(variable)) {
3456 SpvId* imgPtr = fVariableMap.find((*p)->fTexture.get());
3457 SpvId* samplerPtr = fVariableMap.find((*p)->fSampler.get());
3458 SkASSERT(imgPtr);
3459 SkASSERT(samplerPtr);
3460
3461 SpvId img = this->writeOpLoad(this->getType((*p)->fTexture->type()),
3462 Precision::kDefault, *imgPtr, out);
3463 SpvId sampler = this->writeOpLoad(this->getType((*p)->fSampler->type()),
3464 Precision::kDefault,
3465 *samplerPtr,
3466 out);
3467 SpvId result = this->nextId(nullptr);
3468 this->writeInstruction(SpvOpSampledImage,
3469 this->getType(variable->type()),
3470 result,
3471 img,
3472 sampler,
3473 out);
3474 return result;
3475 }
3476 SkDEBUGFAIL("sampler missing from fSynthesizedSamplerMap");
3477 }
3478 return this->getLValue(ref, out)->load(out);
3479 }
3480 }
3481}
3482
3483SpvId SPIRVCodeGenerator::writeIndexExpression(const IndexExpression& expr, OutputStream& out) {
3484 if (expr.base()->type().isVector()) {
3485 SpvId base = this->writeExpression(*expr.base(), out);
3486 SpvId index = this->writeExpression(*expr.index(), out);
3487 SpvId result = this->nextId(nullptr);
3488 this->writeInstruction(SpvOpVectorExtractDynamic, this->getType(expr.type()), result, base,
3489 index, out);
3490 return result;
3491 }
3492 return getLValue(expr, out)->load(out);
3493}
3494
3495SpvId SPIRVCodeGenerator::writeFieldAccess(const FieldAccess& f, OutputStream& out) {
3496 return getLValue(f, out)->load(out);
3497}
3498
3499SpvId SPIRVCodeGenerator::writeSwizzle(const Expression& baseExpr,
3500 const ComponentArray& components,
3501 OutputStream& out) {
3502 size_t count = components.size();
3503 const Type& type = baseExpr.type().componentType().toCompound(fContext, count, /*rows=*/1);
3504 SpvId base = this->writeExpression(baseExpr, out);
3505 if (count == 1) {
3506 return this->writeOpCompositeExtract(type, base, components[0], out);
3507 }
3508
3509 SpvId result = this->nextId(&type);
3510 this->writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) count, out);
3511 this->writeWord(this->getType(type), out);
3512 this->writeWord(result, out);
3513 this->writeWord(base, out);
3514 this->writeWord(base, out);
3515 for (int component : components) {
3516 this->writeWord(component, out);
3517 }
3518 return result;
3519}
3520
3521SpvId SPIRVCodeGenerator::writeSwizzle(const Swizzle& swizzle, OutputStream& out) {
3522 return this->writeSwizzle(*swizzle.base(), swizzle.components(), out);
3523}
3524
3525SpvId SPIRVCodeGenerator::writeBinaryOperation(const Type& resultType,
3526 const Type& operandType, SpvId lhs,
3527 SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt,
3528 SpvOp_ ifUInt, SpvOp_ ifBool, OutputStream& out) {
3529 SpvId result = this->nextId(&resultType);
3530 SpvOp_ op = pick_by_type(operandType, ifFloat, ifInt, ifUInt, ifBool);
3531 if (op == SpvOpUndef) {
3532 fContext.fErrors->error(operandType.fPosition,
3533 "unsupported operand for binary expression: " + operandType.description());
3534 return NA;
3535 }
3536 this->writeInstruction(op, this->getType(resultType), result, lhs, rhs, out);
3537 return result;
3538}
3539
3540SpvId SPIRVCodeGenerator::foldToBool(SpvId id, const Type& operandType, SpvOp op,
3541 OutputStream& out) {
3542 if (operandType.isVector()) {
3543 SpvId result = this->nextId(nullptr);
3544 this->writeInstruction(op, this->getType(*fContext.fTypes.fBool), result, id, out);
3545 return result;
3546 }
3547 return id;
3548}
3549
3550SpvId SPIRVCodeGenerator::writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs,
3551 SpvOp_ floatOperator, SpvOp_ intOperator,
3552 SpvOp_ vectorMergeOperator, SpvOp_ mergeOperator,
3553 OutputStream& out) {
3554 SpvOp_ compareOp = is_float(operandType) ? floatOperator : intOperator;
3555 SkASSERT(operandType.isMatrix());
3556 const Type& columnType = operandType.componentType().toCompound(fContext,
3557 operandType.rows(),
3558 1);
3559 SpvId bvecType = this->getType(fContext.fTypes.fBool->toCompound(fContext,
3560 operandType.rows(),
3561 1));
3562 SpvId boolType = this->getType(*fContext.fTypes.fBool);
3563 SpvId result = 0;
3564 for (int i = 0; i < operandType.columns(); i++) {
3565 SpvId columnL = this->writeOpCompositeExtract(columnType, lhs, i, out);
3566 SpvId columnR = this->writeOpCompositeExtract(columnType, rhs, i, out);
3567 SpvId compare = this->nextId(&operandType);
3568 this->writeInstruction(compareOp, bvecType, compare, columnL, columnR, out);
3569 SpvId merge = this->nextId(nullptr);
3570 this->writeInstruction(vectorMergeOperator, boolType, merge, compare, out);
3571 if (result != 0) {
3572 SpvId next = this->nextId(nullptr);
3573 this->writeInstruction(mergeOperator, boolType, next, result, merge, out);
3574 result = next;
3575 } else {
3576 result = merge;
3577 }
3578 }
3579 return result;
3580}
3581
3582SpvId SPIRVCodeGenerator::writeComponentwiseMatrixUnary(const Type& operandType,
3583 SpvId operand,
3584 SpvOp_ op,
3585 OutputStream& out) {
3586 SkASSERT(operandType.isMatrix());
3587 const Type& columnType = operandType.columnType(fContext);
3588 SpvId columnTypeId = this->getType(columnType);
3589
3590 STArray<4, SpvId> columns;
3591 for (int i = 0; i < operandType.columns(); i++) {
3592 SpvId srcColumn = this->writeOpCompositeExtract(columnType, operand, i, out);
3593 SpvId dstColumn = this->nextId(&operandType);
3594 this->writeInstruction(op, columnTypeId, dstColumn, srcColumn, out);
3595 columns.push_back(dstColumn);
3596 }
3597
3598 return this->writeOpCompositeConstruct(operandType, columns, out);
3599}
3600
3601SpvId SPIRVCodeGenerator::writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs,
3602 SpvId rhs, SpvOp_ op, OutputStream& out) {
3603 SkASSERT(operandType.isMatrix());
3604 const Type& columnType = operandType.columnType(fContext);
3605 SpvId columnTypeId = this->getType(columnType);
3606
3607 STArray<4, SpvId> columns;
3608 for (int i = 0; i < operandType.columns(); i++) {
3609 SpvId columnL = this->writeOpCompositeExtract(columnType, lhs, i, out);
3610 SpvId columnR = this->writeOpCompositeExtract(columnType, rhs, i, out);
3611 columns.push_back(this->nextId(&operandType));
3612 this->writeInstruction(op, columnTypeId, columns[i], columnL, columnR, out);
3613 }
3614 return this->writeOpCompositeConstruct(operandType, columns, out);
3615}
3616
3617SpvId SPIRVCodeGenerator::writeReciprocal(const Type& type, SpvId value, OutputStream& out) {
3618 SkASSERT(type.isFloat());
3619 SpvId one = this->writeLiteral(1.0, type);
3620 SpvId reciprocal = this->nextId(&type);
3621 this->writeInstruction(SpvOpFDiv, this->getType(type), reciprocal, one, value, out);
3622 return reciprocal;
3623}
3624
3625SpvId SPIRVCodeGenerator::writeScalarToMatrixSplat(const Type& matrixType,
3626 SpvId scalarId,
3627 OutputStream& out) {
3628 // Splat the scalar into a vector.
3629 const Type& vectorType = matrixType.columnType(fContext);
3630 STArray<4, SpvId> vecArguments;
3631 vecArguments.push_back_n(/*n=*/matrixType.rows(), /*t=*/scalarId);
3632 SpvId vectorId = this->writeOpCompositeConstruct(vectorType, vecArguments, out);
3633
3634 // Splat the vector into a matrix.
3635 STArray<4, SpvId> matArguments;
3636 matArguments.push_back_n(/*n=*/matrixType.columns(), /*t=*/vectorId);
3637 return this->writeOpCompositeConstruct(matrixType, matArguments, out);
3638}
3639
3640static bool types_match(const Type& a, const Type& b) {
3641 if (a.matches(b)) {
3642 return true;
3643 }
3644 return (a.typeKind() == b.typeKind()) &&
3645 (a.isScalar() || a.isVector() || a.isMatrix()) &&
3646 (a.columns() == b.columns() && a.rows() == b.rows()) &&
3647 a.componentType().numberKind() == b.componentType().numberKind();
3648}
3649
3650SpvId SPIRVCodeGenerator::writeDecomposedMatrixVectorMultiply(const Type& leftType,
3651 SpvId lhs,
3652 const Type& rightType,
3653 SpvId rhs,
3654 const Type& resultType,
3655 OutputStream& out) {
3656 SpvId sum = NA;
3657 const Type& columnType = leftType.columnType(fContext);
3658 const Type& scalarType = rightType.componentType();
3659
3660 for (int n = 0; n < leftType.rows(); ++n) {
3661 // Extract mat[N] from the matrix.
3662 SpvId matN = this->writeOpCompositeExtract(columnType, lhs, n, out);
3663
3664 // Extract vec[N] from the vector.
3665 SpvId vecN = this->writeOpCompositeExtract(scalarType, rhs, n, out);
3666
3667 // Multiply them together.
3668 SpvId product = this->writeBinaryExpression(columnType, matN, OperatorKind::STAR,
3669 scalarType, vecN,
3670 columnType, out);
3671
3672 // Sum all the components together.
3673 if (sum == NA) {
3674 sum = product;
3675 } else {
3676 sum = this->writeBinaryExpression(columnType, sum, OperatorKind::PLUS,
3677 columnType, product,
3678 columnType, out);
3679 }
3680 }
3681
3682 return sum;
3683}
3684
3685SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op,
3686 const Type& rightType, SpvId rhs,
3687 const Type& resultType, OutputStream& out) {
3688 // The comma operator ignores the type of the left-hand side entirely.
3689 if (op.kind() == Operator::Kind::COMMA) {
3690 return rhs;
3691 }
3692 // overall type we are operating on: float2, int, uint4...
3693 const Type* operandType;
3694 if (types_match(leftType, rightType)) {
3695 operandType = &leftType;
3696 } else {
3697 // IR allows mismatched types in expressions (e.g. float2 * float), but they need special
3698 // handling in SPIR-V
3699 if (leftType.isVector() && rightType.isNumber()) {
3700 if (resultType.componentType().isFloat()) {
3701 switch (op.kind()) {
3702 case Operator::Kind::SLASH: {
3703 rhs = this->writeReciprocal(rightType, rhs, out);
3704 [[fallthrough]];
3705 }
3706 case Operator::Kind::STAR: {
3707 SpvId result = this->nextId(&resultType);
3708 this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType),
3709 result, lhs, rhs, out);
3710 return result;
3711 }
3712 default:
3713 break;
3714 }
3715 }
3716 // Vectorize the right-hand side.
3717 STArray<4, SpvId> arguments;
3718 arguments.push_back_n(/*n=*/leftType.columns(), /*t=*/rhs);
3719 rhs = this->writeOpCompositeConstruct(leftType, arguments, out);
3720 operandType = &leftType;
3721 } else if (rightType.isVector() && leftType.isNumber()) {
3722 if (resultType.componentType().isFloat()) {
3723 if (op.kind() == Operator::Kind::STAR) {
3724 SpvId result = this->nextId(&resultType);
3725 this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType),
3726 result, rhs, lhs, out);
3727 return result;
3728 }
3729 }
3730 // Vectorize the left-hand side.
3731 STArray<4, SpvId> arguments;
3732 arguments.push_back_n(/*n=*/rightType.columns(), /*t=*/lhs);
3733 lhs = this->writeOpCompositeConstruct(rightType, arguments, out);
3734 operandType = &rightType;
3735 } else if (leftType.isMatrix()) {
3736 if (op.kind() == Operator::Kind::STAR) {
3737 // When the rewriteMatrixVectorMultiply bit is set, we rewrite medium-precision
3738 // matrix * vector multiplication as (mat[0]*vec[0] + ... + mat[N]*vec[N]).
3740 rightType.isVector() &&
3741 !resultType.highPrecision()) {
3742 return this->writeDecomposedMatrixVectorMultiply(leftType, lhs, rightType, rhs,
3743 resultType, out);
3744 }
3745
3746 // Matrix-times-vector and matrix-times-scalar have dedicated ops in SPIR-V.
3747 SpvOp_ spvop;
3748 if (rightType.isMatrix()) {
3749 spvop = SpvOpMatrixTimesMatrix;
3750 } else if (rightType.isVector()) {
3751 spvop = SpvOpMatrixTimesVector;
3752 } else {
3753 SkASSERT(rightType.isScalar());
3754 spvop = SpvOpMatrixTimesScalar;
3755 }
3756 SpvId result = this->nextId(&resultType);
3757 this->writeInstruction(spvop, this->getType(resultType), result, lhs, rhs, out);
3758 return result;
3759 } else {
3760 // Matrix-op-vector is not supported in GLSL/SkSL for non-multiplication ops; we
3761 // expect to have a scalar here.
3762 SkASSERT(rightType.isScalar());
3763
3764 // Splat rhs across an entire matrix so we can reuse the matrix-op-matrix path.
3765 SpvId rhsMatrix = this->writeScalarToMatrixSplat(leftType, rhs, out);
3766
3767 // Perform this operation as matrix-op-matrix.
3768 return this->writeBinaryExpression(leftType, lhs, op, leftType, rhsMatrix,
3769 resultType, out);
3770 }
3771 } else if (rightType.isMatrix()) {
3772 if (op.kind() == Operator::Kind::STAR) {
3773 // Matrix-times-vector and matrix-times-scalar have dedicated ops in SPIR-V.
3774 SpvId result = this->nextId(&resultType);
3775 if (leftType.isVector()) {
3776 this->writeInstruction(SpvOpVectorTimesMatrix, this->getType(resultType),
3777 result, lhs, rhs, out);
3778 } else {
3779 SkASSERT(leftType.isScalar());
3780 this->writeInstruction(SpvOpMatrixTimesScalar, this->getType(resultType),
3781 result, rhs, lhs, out);
3782 }
3783 return result;
3784 } else {
3785 // Vector-op-matrix is not supported in GLSL/SkSL for non-multiplication ops; we
3786 // expect to have a scalar here.
3787 SkASSERT(leftType.isScalar());
3788
3789 // Splat lhs across an entire matrix so we can reuse the matrix-op-matrix path.
3790 SpvId lhsMatrix = this->writeScalarToMatrixSplat(rightType, lhs, out);
3791
3792 // Perform this operation as matrix-op-matrix.
3793 return this->writeBinaryExpression(rightType, lhsMatrix, op, rightType, rhs,
3794 resultType, out);
3795 }
3796 } else {
3797 fContext.fErrors->error(leftType.fPosition, "unsupported mixed-type expression");
3798 return NA;
3799 }
3800 }
3801
3802 switch (op.kind()) {
3803 case Operator::Kind::EQEQ: {
3804 if (operandType->isMatrix()) {
3805 return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdEqual,
3807 }
3808 if (operandType->isStruct()) {
3809 return this->writeStructComparison(*operandType, lhs, op, rhs, out);
3810 }
3811 if (operandType->isArray()) {
3812 return this->writeArrayComparison(*operandType, lhs, op, rhs, out);
3813 }
3814 SkASSERT(resultType.isBoolean());
3815 const Type* tmpType;
3816 if (operandType->isVector()) {
3817 tmpType = &fContext.fTypes.fBool->toCompound(fContext,
3818 operandType->columns(),
3819 operandType->rows());
3820 } else {
3821 tmpType = &resultType;
3822 }
3823 if (lhs == rhs) {
3824 // This ignores the effects of NaN.
3825 return this->writeOpConstantTrue(*fContext.fTypes.fBool);
3826 }
3827 return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs,
3830 *operandType, SpvOpAll, out);
3831 }
3832 case Operator::Kind::NEQ:
3833 if (operandType->isMatrix()) {
3834 return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFUnordNotEqual,
3836 }
3837 if (operandType->isStruct()) {
3838 return this->writeStructComparison(*operandType, lhs, op, rhs, out);
3839 }
3840 if (operandType->isArray()) {
3841 return this->writeArrayComparison(*operandType, lhs, op, rhs, out);
3842 }
3843 [[fallthrough]];
3844 case Operator::Kind::LOGICALXOR:
3845 SkASSERT(resultType.isBoolean());
3846 const Type* tmpType;
3847 if (operandType->isVector()) {
3848 tmpType = &fContext.fTypes.fBool->toCompound(fContext,
3849 operandType->columns(),
3850 operandType->rows());
3851 } else {
3852 tmpType = &resultType;
3853 }
3854 if (lhs == rhs) {
3855 // This ignores the effects of NaN.
3856 return this->writeOpConstantFalse(*fContext.fTypes.fBool);
3857 }
3858 return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs,
3861 out),
3862 *operandType, SpvOpAny, out);
3863 case Operator::Kind::GT:
3864 SkASSERT(resultType.isBoolean());
3865 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs,
3868 case Operator::Kind::LT:
3869 SkASSERT(resultType.isBoolean());
3870 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFOrdLessThan,
3872 case Operator::Kind::GTEQ:
3873 SkASSERT(resultType.isBoolean());
3874 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs,
3877 case Operator::Kind::LTEQ:
3878 SkASSERT(resultType.isBoolean());
3879 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs,
3882 case Operator::Kind::PLUS:
3883 if (leftType.isMatrix() && rightType.isMatrix()) {
3884 SkASSERT(leftType.matches(rightType));
3885 return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFAdd, out);
3886 }
3887 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFAdd,
3889 case Operator::Kind::MINUS:
3890 if (leftType.isMatrix() && rightType.isMatrix()) {
3891 SkASSERT(leftType.matches(rightType));
3892 return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFSub, out);
3893 }
3894 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFSub,
3896 case Operator::Kind::STAR:
3897 if (leftType.isMatrix() && rightType.isMatrix()) {
3898 // matrix multiply
3899 SpvId result = this->nextId(&resultType);
3900 this->writeInstruction(SpvOpMatrixTimesMatrix, this->getType(resultType), result,
3901 lhs, rhs, out);
3902 return result;
3903 }
3904 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMul,
3906 case Operator::Kind::SLASH:
3907 if (leftType.isMatrix() && rightType.isMatrix()) {
3908 SkASSERT(leftType.matches(rightType));
3909 return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFDiv, out);
3910 }
3911 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFDiv,
3913 case Operator::Kind::PERCENT:
3914 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMod,
3916 case Operator::Kind::SHL:
3917 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
3919 SpvOpUndef, out);
3920 case Operator::Kind::SHR:
3921 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
3923 SpvOpUndef, out);
3924 case Operator::Kind::BITWISEAND:
3925 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
3927 case Operator::Kind::BITWISEOR:
3928 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
3930 case Operator::Kind::BITWISEXOR:
3931 return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef,
3933 default:
3934 fContext.fErrors->error(Position(), "unsupported token");
3935 return NA;
3936 }
3937}
3938
3939SpvId SPIRVCodeGenerator::writeArrayComparison(const Type& arrayType, SpvId lhs, Operator op,
3940 SpvId rhs, OutputStream& out) {
3941 // The inputs must be arrays, and the op must be == or !=.
3942 SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ);
3943 SkASSERT(arrayType.isArray());
3944 const Type& componentType = arrayType.componentType();
3945 const int arraySize = arrayType.columns();
3946 SkASSERT(arraySize > 0);
3947
3948 // Synthesize equality checks for each item in the array.
3949 const Type& boolType = *fContext.fTypes.fBool;
3950 SpvId allComparisons = NA;
3951 for (int index = 0; index < arraySize; ++index) {
3952 // Get the left and right item in the array.
3953 SpvId itemL = this->writeOpCompositeExtract(componentType, lhs, index, out);
3954 SpvId itemR = this->writeOpCompositeExtract(componentType, rhs, index, out);
3955 // Use `writeBinaryExpression` with the requested == or != operator on these items.
3956 SpvId comparison = this->writeBinaryExpression(componentType, itemL, op,
3957 componentType, itemR, boolType, out);
3958 // Merge this comparison result with all the other comparisons we've done.
3959 allComparisons = this->mergeComparisons(comparison, allComparisons, op, out);
3960 }
3961 return allComparisons;
3962}
3963
3964SpvId SPIRVCodeGenerator::writeStructComparison(const Type& structType, SpvId lhs, Operator op,
3965 SpvId rhs, OutputStream& out) {
3966 // The inputs must be structs containing fields, and the op must be == or !=.
3967 SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ);
3968 SkASSERT(structType.isStruct());
3969 SkSpan<const Field> fields = structType.fields();
3970 SkASSERT(!fields.empty());
3971
3972 // Synthesize equality checks for each field in the struct.
3973 const Type& boolType = *fContext.fTypes.fBool;
3974 SpvId allComparisons = NA;
3975 for (int index = 0; index < (int)fields.size(); ++index) {
3976 // Get the left and right versions of this field.
3977 const Type& fieldType = *fields[index].fType;
3978
3979 SpvId fieldL = this->writeOpCompositeExtract(fieldType, lhs, index, out);
3980 SpvId fieldR = this->writeOpCompositeExtract(fieldType, rhs, index, out);
3981 // Use `writeBinaryExpression` with the requested == or != operator on these fields.
3982 SpvId comparison = this->writeBinaryExpression(fieldType, fieldL, op, fieldType, fieldR,
3983 boolType, out);
3984 // Merge this comparison result with all the other comparisons we've done.
3985 allComparisons = this->mergeComparisons(comparison, allComparisons, op, out);
3986 }
3987 return allComparisons;
3988}
3989
3990SpvId SPIRVCodeGenerator::mergeComparisons(SpvId comparison, SpvId allComparisons, Operator op,
3991 OutputStream& out) {
3992 // If this is the first entry, we don't need to merge comparison results with anything.
3993 if (allComparisons == NA) {
3994 return comparison;
3995 }
3996 // Use LogicalAnd or LogicalOr to combine the comparison with all the other comparisons.
3997 const Type& boolType = *fContext.fTypes.fBool;
3998 SpvId boolTypeId = this->getType(boolType);
3999 SpvId logicalOp = this->nextId(&boolType);
4000 switch (op.kind()) {
4001 case Operator::Kind::EQEQ:
4002 this->writeInstruction(SpvOpLogicalAnd, boolTypeId, logicalOp,
4003 comparison, allComparisons, out);
4004 break;
4005 case Operator::Kind::NEQ:
4006 this->writeInstruction(SpvOpLogicalOr, boolTypeId, logicalOp,
4007 comparison, allComparisons, out);
4008 break;
4009 default:
4010 SkDEBUGFAILF("mergeComparisons only supports == and !=, not %s", op.operatorName());
4011 return NA;
4012 }
4013 return logicalOp;
4014}
4015
4016SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, OutputStream& out) {
4017 const Expression* left = b.left().get();
4018 const Expression* right = b.right().get();
4019 Operator op = b.getOperator();
4020
4021 switch (op.kind()) {
4022 case Operator::Kind::EQ: {
4023 // Handles assignment.
4024 SpvId rhs = this->writeExpression(*right, out);
4025 this->getLValue(*left, out)->store(rhs, out);
4026 return rhs;
4027 }
4028 case Operator::Kind::LOGICALAND:
4029 // Handles short-circuiting; we don't necessarily evaluate both LHS and RHS.
4030 return this->writeLogicalAnd(*b.left(), *b.right(), out);
4031
4032 case Operator::Kind::LOGICALOR:
4033 // Handles short-circuiting; we don't necessarily evaluate both LHS and RHS.
4034 return this->writeLogicalOr(*b.left(), *b.right(), out);
4035
4036 default:
4037 break;
4038 }
4039
4040 std::unique_ptr<LValue> lvalue;
4041 SpvId lhs;
4042 if (op.isAssignment()) {
4043 lvalue = this->getLValue(*left, out);
4044 lhs = lvalue->load(out);
4045 } else {
4046 lvalue = nullptr;
4047 lhs = this->writeExpression(*left, out);
4048 }
4049
4050 SpvId rhs = this->writeExpression(*right, out);
4051 SpvId result = this->writeBinaryExpression(left->type(), lhs, op.removeAssignment(),
4052 right->type(), rhs, b.type(), out);
4053 if (lvalue) {
4054 lvalue->store(result, out);
4055 }
4056 return result;
4057}
4058
4059SpvId SPIRVCodeGenerator::writeLogicalAnd(const Expression& left, const Expression& right,
4060 OutputStream& out) {
4061 SpvId falseConstant = this->writeLiteral(0.0, *fContext.fTypes.fBool);
4062 SpvId lhs = this->writeExpression(left, out);
4063
4064 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4065
4066 SpvId rhsLabel = this->nextId(nullptr);
4067 SpvId end = this->nextId(nullptr);
4068 SpvId lhsBlock = fCurrentBlock;
4069 this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
4070 this->writeInstruction(SpvOpBranchConditional, lhs, rhsLabel, end, out);
4071 this->writeLabel(rhsLabel, kBranchIsOnPreviousLine, out);
4072 SpvId rhs = this->writeExpression(right, out);
4073 SpvId rhsBlock = fCurrentBlock;
4074 this->writeInstruction(SpvOpBranch, end, out);
4075 this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
4076 SpvId result = this->nextId(nullptr);
4077 this->writeInstruction(SpvOpPhi, this->getType(*fContext.fTypes.fBool), result, falseConstant,
4078 lhsBlock, rhs, rhsBlock, out);
4079
4080 return result;
4081}
4082
4083SpvId SPIRVCodeGenerator::writeLogicalOr(const Expression& left, const Expression& right,
4084 OutputStream& out) {
4085 SpvId trueConstant = this->writeLiteral(1.0, *fContext.fTypes.fBool);
4086 SpvId lhs = this->writeExpression(left, out);
4087
4088 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4089
4090 SpvId rhsLabel = this->nextId(nullptr);
4091 SpvId end = this->nextId(nullptr);
4092 SpvId lhsBlock = fCurrentBlock;
4093 this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
4094 this->writeInstruction(SpvOpBranchConditional, lhs, end, rhsLabel, out);
4095 this->writeLabel(rhsLabel, kBranchIsOnPreviousLine, out);
4096 SpvId rhs = this->writeExpression(right, out);
4097 SpvId rhsBlock = fCurrentBlock;
4098 this->writeInstruction(SpvOpBranch, end, out);
4099 this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
4100 SpvId result = this->nextId(nullptr);
4101 this->writeInstruction(SpvOpPhi, this->getType(*fContext.fTypes.fBool), result, trueConstant,
4102 lhsBlock, rhs, rhsBlock, out);
4103
4104 return result;
4105}
4106
4107SpvId SPIRVCodeGenerator::writeTernaryExpression(const TernaryExpression& t, OutputStream& out) {
4108 const Type& type = t.type();
4109 SpvId test = this->writeExpression(*t.test(), out);
4110 if (t.ifTrue()->type().columns() == 1 &&
4111 Analysis::IsCompileTimeConstant(*t.ifTrue()) &&
4112 Analysis::IsCompileTimeConstant(*t.ifFalse())) {
4113 // both true and false are constants, can just use OpSelect
4114 SpvId result = this->nextId(nullptr);
4115 SpvId trueId = this->writeExpression(*t.ifTrue(), out);
4116 SpvId falseId = this->writeExpression(*t.ifFalse(), out);
4117 this->writeInstruction(SpvOpSelect, this->getType(type), result, test, trueId, falseId,
4118 out);
4119 return result;
4120 }
4121
4122 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4123
4124 // was originally using OpPhi to choose the result, but for some reason that is crashing on
4125 // Adreno. Switched to storing the result in a temp variable as glslang does.
4126 SpvId var = this->nextId(nullptr);
4127 this->writeInstruction(SpvOpVariable, this->getPointerType(type, SpvStorageClassFunction),
4128 var, SpvStorageClassFunction, fVariableBuffer);
4129 SpvId trueLabel = this->nextId(nullptr);
4130 SpvId falseLabel = this->nextId(nullptr);
4131 SpvId end = this->nextId(nullptr);
4132 this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
4133 this->writeInstruction(SpvOpBranchConditional, test, trueLabel, falseLabel, out);
4134 this->writeLabel(trueLabel, kBranchIsOnPreviousLine, out);
4135 this->writeOpStore(SpvStorageClassFunction, var, this->writeExpression(*t.ifTrue(), out), out);
4136 this->writeInstruction(SpvOpBranch, end, out);
4137 this->writeLabel(falseLabel, kBranchIsAbove, conditionalOps, out);
4138 this->writeOpStore(SpvStorageClassFunction, var, this->writeExpression(*t.ifFalse(), out), out);
4139 this->writeInstruction(SpvOpBranch, end, out);
4140 this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
4141 SpvId result = this->nextId(&type);
4142 this->writeInstruction(SpvOpLoad, this->getType(type), result, var, out);
4143
4144 return result;
4145}
4146
4147SpvId SPIRVCodeGenerator::writePrefixExpression(const PrefixExpression& p, OutputStream& out) {
4148 const Type& type = p.type();
4149 if (p.getOperator().kind() == Operator::Kind::MINUS) {
4151 SkASSERT(negateOp != SpvOpUndef);
4152 SpvId expr = this->writeExpression(*p.operand(), out);
4153 if (type.isMatrix()) {
4154 return this->writeComponentwiseMatrixUnary(type, expr, negateOp, out);
4155 }
4156 SpvId result = this->nextId(&type);
4157 SpvId typeId = this->getType(type);
4158 this->writeInstruction(negateOp, typeId, result, expr, out);
4159 return result;
4160 }
4161 switch (p.getOperator().kind()) {
4162 case Operator::Kind::PLUS:
4163 return this->writeExpression(*p.operand(), out);
4164 case Operator::Kind::PLUSPLUS: {
4165 std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out);
4166 SpvId one = this->writeLiteral(1.0, type);
4167 SpvId result = this->writeBinaryOperation(type, type, lv->load(out), one,
4169 out);
4170 lv->store(result, out);
4171 return result;
4172 }
4173 case Operator::Kind::MINUSMINUS: {
4174 std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out);
4175 SpvId one = this->writeLiteral(1.0, type);
4176 SpvId result = this->writeBinaryOperation(type, type, lv->load(out), one, SpvOpFSub,
4178 lv->store(result, out);
4179 return result;
4180 }
4181 case Operator::Kind::LOGICALNOT: {
4182 SkASSERT(p.operand()->type().isBoolean());
4183 SpvId result = this->nextId(nullptr);
4184 this->writeInstruction(SpvOpLogicalNot, this->getType(type), result,
4185 this->writeExpression(*p.operand(), out), out);
4186 return result;
4187 }
4188 case Operator::Kind::BITWISENOT: {
4189 SpvId result = this->nextId(nullptr);
4190 this->writeInstruction(SpvOpNot, this->getType(type), result,
4191 this->writeExpression(*p.operand(), out), out);
4192 return result;
4193 }
4194 default:
4195 SkDEBUGFAILF("unsupported prefix expression: %s",
4196 p.description(OperatorPrecedence::kExpression).c_str());
4197 return NA;
4198 }
4199}
4200
4201SpvId SPIRVCodeGenerator::writePostfixExpression(const PostfixExpression& p, OutputStream& out) {
4202 const Type& type = p.type();
4203 std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out);
4204 SpvId result = lv->load(out);
4205 SpvId one = this->writeLiteral(1.0, type);
4206 switch (p.getOperator().kind()) {
4207 case Operator::Kind::PLUSPLUS: {
4208 SpvId temp = this->writeBinaryOperation(type, type, result, one, SpvOpFAdd,
4210 lv->store(temp, out);
4211 return result;
4212 }
4213 case Operator::Kind::MINUSMINUS: {
4214 SpvId temp = this->writeBinaryOperation(type, type, result, one, SpvOpFSub,
4216 lv->store(temp, out);
4217 return result;
4218 }
4219 default:
4220 SkDEBUGFAILF("unsupported postfix expression %s",
4221 p.description(OperatorPrecedence::kExpression).c_str());
4222 return NA;
4223 }
4224}
4225
4226SpvId SPIRVCodeGenerator::writeLiteral(const Literal& l) {
4227 return this->writeLiteral(l.value(), l.type());
4228}
4229
4230SpvId SPIRVCodeGenerator::writeLiteral(double value, const Type& type) {
4231 switch (type.numberKind()) {
4233 float floatVal = value;
4234 int32_t valueBits;
4235 memcpy(&valueBits, &floatVal, sizeof(valueBits));
4236 return this->writeOpConstant(type, valueBits);
4237 }
4239 return value ? this->writeOpConstantTrue(type)
4240 : this->writeOpConstantFalse(type);
4241 }
4242 default: {
4243 return this->writeOpConstant(type, (SKSL_INT)value);
4244 }
4245 }
4246}
4247
4248SpvId SPIRVCodeGenerator::writeFunctionStart(const FunctionDeclaration& f, OutputStream& out) {
4249 SpvId result = fFunctionMap[&f];
4250 SpvId returnTypeId = this->getType(f.returnType());
4251 SpvId functionTypeId = this->getFunctionType(f);
4252 this->writeInstruction(SpvOpFunction, returnTypeId, result,
4253 SpvFunctionControlMaskNone, functionTypeId, out);
4254 std::string mangledName = f.mangledName();
4255 this->writeInstruction(SpvOpName,
4256 result,
4257 std::string_view(mangledName.c_str(), mangledName.size()),
4258 fNameBuffer);
4259 for (const Variable* parameter : f.parameters()) {
4260 if (fUseTextureSamplerPairs && parameter->type().isSampler()) {
4261 auto [texture, sampler] = this->synthesizeTextureAndSampler(*parameter);
4262
4263 SpvId textureId = this->nextId(nullptr);
4264 SpvId samplerId = this->nextId(nullptr);
4265 fVariableMap.set(texture, textureId);
4266 fVariableMap.set(sampler, samplerId);
4267
4268 SpvId textureType = this->getFunctionParameterType(texture->type(), texture->layout());
4269 SpvId samplerType = this->getFunctionParameterType(sampler->type(), kDefaultTypeLayout);
4270
4271 this->writeInstruction(SpvOpFunctionParameter, textureType, textureId, out);
4272 this->writeInstruction(SpvOpFunctionParameter, samplerType, samplerId, out);
4273 } else {
4274 SpvId id = this->nextId(nullptr);
4275 fVariableMap.set(parameter, id);
4276
4277 SpvId type = this->getFunctionParameterType(parameter->type(), parameter->layout());
4278 this->writeInstruction(SpvOpFunctionParameter, type, id, out);
4279 }
4280 }
4281 return result;
4282}
4283
4284SpvId SPIRVCodeGenerator::writeFunction(const FunctionDefinition& f, OutputStream& out) {
4285 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4286
4287 fVariableBuffer.reset();
4288 SpvId result = this->writeFunctionStart(f.declaration(), out);
4289 fCurrentBlock = 0;
4290 this->writeLabel(this->nextId(nullptr), kBranchlessBlock, out);
4291 StringStream bodyBuffer;
4292 this->writeBlock(f.body()->as<Block>(), bodyBuffer);
4293 write_stringstream(fVariableBuffer, out);
4294 if (f.declaration().isMain()) {
4295 write_stringstream(fGlobalInitializersBuffer, out);
4296 }
4297 write_stringstream(bodyBuffer, out);
4298 if (fCurrentBlock) {
4299 if (f.declaration().returnType().isVoid()) {
4300 this->writeInstruction(SpvOpReturn, out);
4301 } else {
4302 this->writeInstruction(SpvOpUnreachable, out);
4303 }
4304 }
4305 this->writeInstruction(SpvOpFunctionEnd, out);
4306 this->pruneConditionalOps(conditionalOps);
4307 return result;
4308}
4309
4310void SPIRVCodeGenerator::writeLayout(const Layout& layout, SpvId target, Position pos) {
4311 bool isPushConstant = SkToBool(layout.fFlags & LayoutFlag::kPushConstant);
4312 if (layout.fLocation >= 0) {
4313 this->writeInstruction(SpvOpDecorate, target, SpvDecorationLocation, layout.fLocation,
4314 fDecorationBuffer);
4315 }
4316 if (layout.fBinding >= 0) {
4317 if (isPushConstant) {
4318 fContext.fErrors->error(pos, "Can't apply 'binding' to push constants");
4319 } else {
4320 this->writeInstruction(SpvOpDecorate, target, SpvDecorationBinding, layout.fBinding,
4321 fDecorationBuffer);
4322 }
4323 }
4324 if (layout.fIndex >= 0) {
4325 this->writeInstruction(SpvOpDecorate, target, SpvDecorationIndex, layout.fIndex,
4326 fDecorationBuffer);
4327 }
4328 if (layout.fSet >= 0) {
4329 if (isPushConstant) {
4330 fContext.fErrors->error(pos, "Can't apply 'set' to push constants");
4331 } else {
4332 this->writeInstruction(SpvOpDecorate, target, SpvDecorationDescriptorSet, layout.fSet,
4333 fDecorationBuffer);
4334 }
4335 }
4336 if (layout.fInputAttachmentIndex >= 0) {
4338 layout.fInputAttachmentIndex, fDecorationBuffer);
4339 fCapabilities |= (((uint64_t) 1) << SpvCapabilityInputAttachment);
4340 }
4341 if (layout.fBuiltin >= 0 && layout.fBuiltin != SK_FRAGCOLOR_BUILTIN) {
4342 this->writeInstruction(SpvOpDecorate, target, SpvDecorationBuiltIn, layout.fBuiltin,
4343 fDecorationBuffer);
4344 }
4345}
4346
4347void SPIRVCodeGenerator::writeFieldLayout(const Layout& layout, SpvId target, int member) {
4348 // 'binding' and 'set' can not be applied to struct members
4349 SkASSERT(layout.fBinding == -1);
4350 SkASSERT(layout.fSet == -1);
4351 if (layout.fLocation >= 0) {
4352 this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationLocation,
4353 layout.fLocation, fDecorationBuffer);
4354 }
4355 if (layout.fIndex >= 0) {
4356 this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationIndex,
4357 layout.fIndex, fDecorationBuffer);
4358 }
4359 if (layout.fInputAttachmentIndex >= 0) {
4360 this->writeInstruction(SpvOpDecorate, target, member, SpvDecorationInputAttachmentIndex,
4361 layout.fInputAttachmentIndex, fDecorationBuffer);
4362 }
4363 if (layout.fBuiltin >= 0) {
4364 this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationBuiltIn,
4365 layout.fBuiltin, fDecorationBuffer);
4366 }
4367}
4368
4369MemoryLayout SPIRVCodeGenerator::memoryLayoutForStorageClass(SpvStorageClass_ storageClass) {
4370 return storageClass == SpvStorageClassPushConstant ? MemoryLayout(MemoryLayout::Standard::k430)
4371 : fDefaultMemoryLayout;
4372}
4373
4374MemoryLayout SPIRVCodeGenerator::memoryLayoutForVariable(const Variable& v) const {
4375 bool pushConstant = SkToBool(v.layout().fFlags & LayoutFlag::kPushConstant);
4376 return pushConstant ? MemoryLayout(MemoryLayout::Standard::k430)
4377 : fDefaultMemoryLayout;
4378}
4379
4380SpvId SPIRVCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip) {
4381 MemoryLayout memoryLayout = this->memoryLayoutForVariable(*intf.var());
4382 SpvId result = this->nextId(nullptr);
4383 const Variable& intfVar = *intf.var();
4384 const Type& type = intfVar.type();
4385 if (!memoryLayout.isSupported(type)) {
4386 fContext.fErrors->error(type.fPosition, "type '" + type.displayName() +
4387 "' is not permitted here");
4388 return this->nextId(nullptr);
4389 }
4390 SpvStorageClass_ storageClass =
4393 !fWroteRTFlip && type.isStruct()) {
4394 // We can only have one interface block (because we use push_constant and that is limited
4395 // to one per program), so we need to append rtflip to this one rather than synthesize an
4396 // entirely new block when the variable is referenced. And we can't modify the existing
4397 // block, so we instead create a modified copy of it and write that.
4398 SkSpan<const Field> fieldSpan = type.fields();
4399 TArray<Field> fields(fieldSpan.data(), fieldSpan.size());
4400 fields.emplace_back(Position(),
4402 /*location=*/-1,
4403 fProgram.fConfig->fSettings.fRTFlipOffset,
4404 /*binding=*/-1,
4405 /*index=*/-1,
4406 /*set=*/-1,
4407 /*builtin=*/-1,
4408 /*inputAttachmentIndex=*/-1),
4411 fContext.fTypes.fFloat2.get());
4412 {
4413 AutoAttachPoolToThread attach(fProgram.fPool.get());
4414 const Type* rtFlipStructType = fProgram.fSymbols->takeOwnershipOfSymbol(
4416 type.fPosition,
4417 type.name(),
4418 std::move(fields),
4419 /*interfaceBlock=*/true));
4420 Variable* modifiedVar = fProgram.fSymbols->takeOwnershipOfSymbol(
4421 Variable::Make(intfVar.fPosition,
4422 intfVar.modifiersPosition(),
4423 intfVar.layout(),
4424 intfVar.modifierFlags(),
4425 rtFlipStructType,
4426 intfVar.name(),
4427 /*mangledName=*/"",
4428 intfVar.isBuiltin(),
4429 intfVar.storage()));
4430 InterfaceBlock modifiedCopy(intf.fPosition, modifiedVar);
4431 result = this->writeInterfaceBlock(modifiedCopy, /*appendRTFlip=*/false);
4432 fProgram.fSymbols->add(fContext, std::make_unique<FieldSymbol>(
4433 Position(), modifiedVar, rtFlipStructType->fields().size() - 1));
4434 }
4435 fVariableMap.set(&intfVar, result);
4436 fWroteRTFlip = true;
4437 return result;
4438 }
4439 SpvId typeId = this->getType(type, kDefaultTypeLayout, memoryLayout);
4440 if (intfVar.layout().fBuiltin == -1) {
4441 // Note: In SPIR-V 1.3, a storage buffer can be declared with the "StorageBuffer"
4442 // storage class and the "Block" decoration and the <1.3 approach we use here ("Uniform"
4443 // storage class and the "BufferBlock" decoration) is deprecated. Since we target SPIR-V
4444 // 1.0, we have to use the deprecated approach which is well supported in Vulkan and
4445 // addresses SkSL use cases (notably SkSL currently doesn't support pointer features that
4446 // would benefit from SPV_KHR_variable_pointers capabilities).
4447 bool isStorageBuffer = intfVar.modifierFlags().isBuffer();
4448 this->writeInstruction(SpvOpDecorate,
4449 typeId,
4450 isStorageBuffer ? SpvDecorationBufferBlock : SpvDecorationBlock,
4451 fDecorationBuffer);
4452 }
4453 SpvId ptrType = this->nextId(nullptr);
4454 this->writeInstruction(SpvOpTypePointer, ptrType, storageClass, typeId, fConstantBuffer);
4455 this->writeInstruction(SpvOpVariable, ptrType, result, storageClass, fConstantBuffer);
4456 Layout layout = intfVar.layout();
4457 if (storageClass == SpvStorageClassUniform && layout.fSet < 0) {
4458 layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet;
4459 }
4460 this->writeLayout(layout, result, intfVar.fPosition);
4461 fVariableMap.set(&intfVar, result);
4462 return result;
4463}
4464
4465// This function determines whether to skip an OpVariable (of pointer type) declaration for
4466// compile-time constant scalars and vectors which we turn into OpConstant/OpConstantComposite and
4467// always reference by value.
4468//
4469// Accessing a matrix or array member with a dynamic index requires the use of OpAccessChain which
4470// requires a base operand of pointer type. However, a vector can always be accessed by value using
4471// OpVectorExtractDynamic (see writeIndexExpression).
4472//
4473// This is why we always emit an OpVariable for all non-scalar and non-vector types in case they get
4474// accessed via a dynamic index.
4476 return varDecl.var()->modifierFlags().isConst() &&
4477 (varDecl.var()->type().isScalar() || varDecl.var()->type().isVector()) &&
4480}
4481
4482bool SPIRVCodeGenerator::writeGlobalVarDeclaration(ProgramKind kind,
4483 const VarDeclaration& varDecl) {
4484 const Variable* var = varDecl.var();
4485 const LayoutFlags backendFlags = var->layout().fFlags & LayoutFlag::kAllBackends;
4486 const LayoutFlags kPermittedBackendFlags =
4488 if (backendFlags & ~kPermittedBackendFlags) {
4489 fContext.fErrors->error(var->fPosition, "incompatible backend flag in SPIR-V codegen");
4490 return false;
4491 }
4492
4493 // If this global variable is a compile-time constant then we'll emit OpConstant or
4494 // OpConstantComposite later when the variable is referenced. Avoid declaring an OpVariable now.
4495 if (is_vardecl_compile_time_constant(varDecl)) {
4496 return true;
4497 }
4498
4499 SpvStorageClass_ storageClass =
4501 if (storageClass == SpvStorageClassUniform) {
4502 // Top-level uniforms are emitted in writeUniformBuffer.
4503 fTopLevelUniforms.push_back(&varDecl);
4504 return true;
4505 }
4506
4507 if (fUseTextureSamplerPairs && var->type().isSampler()) {
4508 if (var->layout().fTexture == -1 || var->layout().fSampler == -1) {
4509 fContext.fErrors->error(var->fPosition, "selected backend requires separate texture "
4510 "and sampler indices");
4511 return false;
4512 }
4514
4515 auto [texture, sampler] = this->synthesizeTextureAndSampler(*var);
4516 this->writeGlobalVar(kind, storageClass, *texture);
4517 this->writeGlobalVar(kind, storageClass, *sampler);
4518
4519 return true;
4520 }
4521
4522 SpvId id = this->writeGlobalVar(kind, storageClass, *var);
4523 if (id != NA && varDecl.value()) {
4524 SkASSERT(!fCurrentBlock);
4525 fCurrentBlock = NA;
4526 SpvId value = this->writeExpression(*varDecl.value(), fGlobalInitializersBuffer);
4527 this->writeOpStore(storageClass, id, value, fGlobalInitializersBuffer);
4528 fCurrentBlock = 0;
4529 }
4530 return true;
4531}
4532
4533SpvId SPIRVCodeGenerator::writeGlobalVar(ProgramKind kind,
4534 SpvStorageClass_ storageClass,
4535 const Variable& var) {
4536 Layout layout = var.layout();
4537 const ModifierFlags flags = var.modifierFlags();
4538 const Type* type = &var.type();
4539 switch (layout.fBuiltin) {
4541 if (!ProgramConfig::IsFragment(kind)) {
4542 SkASSERT(!fProgram.fConfig->fSettings.fFragColorIsInOut);
4543 return NA;
4544 }
4545 break;
4546
4549 // SkSL exposes this as a `uint` but SPIR-V, like GLSL, uses an array of signed `uint`
4550 // decorated with SpvBuiltinSampleMask.
4551 type = fSynthetics.addArrayDimension(fContext, type, /*arraySize=*/1);
4552 layout.fBuiltin = SpvBuiltInSampleMask;
4553 break;
4554 }
4555
4556 // Add this global to the variable map.
4557 SpvId id = this->nextId(type);
4558 fVariableMap.set(&var, id);
4559
4560 if (layout.fSet < 0 && storageClass == SpvStorageClassUniformConstant) {
4561 layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet;
4562 }
4563
4564 SpvId typeId = this->getPointerType(*type,
4565 layout,
4566 this->memoryLayoutForStorageClass(storageClass),
4567 storageClass);
4568 this->writeInstruction(SpvOpVariable, typeId, id, storageClass, fConstantBuffer);
4569 this->writeInstruction(SpvOpName, id, var.name(), fNameBuffer);
4570 this->writeLayout(layout, id, var.fPosition);
4571 if (flags & ModifierFlag::kFlat) {
4572 this->writeInstruction(SpvOpDecorate, id, SpvDecorationFlat, fDecorationBuffer);
4573 }
4575 this->writeInstruction(SpvOpDecorate, id, SpvDecorationNoPerspective,
4576 fDecorationBuffer);
4577 }
4578 if (flags.isWriteOnly()) {
4579 this->writeInstruction(SpvOpDecorate, id, SpvDecorationNonReadable, fDecorationBuffer);
4580 } else if (flags.isReadOnly()) {
4581 this->writeInstruction(SpvOpDecorate, id, SpvDecorationNonWritable, fDecorationBuffer);
4582 }
4583
4584 return id;
4585}
4586
4587void SPIRVCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl, OutputStream& out) {
4588 // If this variable is a compile-time constant then we'll emit OpConstant or
4589 // OpConstantComposite later when the variable is referenced. Avoid declaring an OpVariable now.
4590 if (is_vardecl_compile_time_constant(varDecl)) {
4591 return;
4592 }
4593
4594 const Variable* var = varDecl.var();
4595 SpvId id = this->nextId(&var->type());
4596 fVariableMap.set(var, id);
4597 SpvId type = this->getPointerType(var->type(), SpvStorageClassFunction);
4598 this->writeInstruction(SpvOpVariable, type, id, SpvStorageClassFunction, fVariableBuffer);
4599 this->writeInstruction(SpvOpName, id, var->name(), fNameBuffer);
4600 if (varDecl.value()) {
4601 SpvId value = this->writeExpression(*varDecl.value(), out);
4602 this->writeOpStore(SpvStorageClassFunction, id, value, out);
4603 }
4604}
4605
4606void SPIRVCodeGenerator::writeStatement(const Statement& s, OutputStream& out) {
4607 switch (s.kind()) {
4608 case Statement::Kind::kNop:
4609 break;
4610 case Statement::Kind::kBlock:
4611 this->writeBlock(s.as<Block>(), out);
4612 break;
4613 case Statement::Kind::kExpression:
4614 this->writeExpression(*s.as<ExpressionStatement>().expression(), out);
4615 break;
4616 case Statement::Kind::kReturn:
4617 this->writeReturnStatement(s.as<ReturnStatement>(), out);
4618 break;
4619 case Statement::Kind::kVarDeclaration:
4620 this->writeVarDeclaration(s.as<VarDeclaration>(), out);
4621 break;
4622 case Statement::Kind::kIf:
4623 this->writeIfStatement(s.as<IfStatement>(), out);
4624 break;
4625 case Statement::Kind::kFor:
4626 this->writeForStatement(s.as<ForStatement>(), out);
4627 break;
4628 case Statement::Kind::kDo:
4629 this->writeDoStatement(s.as<DoStatement>(), out);
4630 break;
4631 case Statement::Kind::kSwitch:
4632 this->writeSwitchStatement(s.as<SwitchStatement>(), out);
4633 break;
4634 case Statement::Kind::kBreak:
4635 this->writeInstruction(SpvOpBranch, fBreakTarget.back(), out);
4636 break;
4637 case Statement::Kind::kContinue:
4638 this->writeInstruction(SpvOpBranch, fContinueTarget.back(), out);
4639 break;
4640 case Statement::Kind::kDiscard:
4641 this->writeInstruction(SpvOpKill, out);
4642 break;
4643 default:
4644 SkDEBUGFAILF("unsupported statement: %s", s.description().c_str());
4645 break;
4646 }
4647}
4648
4649void SPIRVCodeGenerator::writeBlock(const Block& b, OutputStream& out) {
4650 for (const std::unique_ptr<Statement>& stmt : b.children()) {
4651 this->writeStatement(*stmt, out);
4652 }
4653}
4654
4655SPIRVCodeGenerator::ConditionalOpCounts SPIRVCodeGenerator::getConditionalOpCounts() {
4656 return {fReachableOps.size(), fStoreOps.size()};
4657}
4658
4659void SPIRVCodeGenerator::pruneConditionalOps(ConditionalOpCounts ops) {
4660 // Remove ops which are no longer reachable.
4661 while (fReachableOps.size() > ops.numReachableOps) {
4662 SpvId prunableSpvId = fReachableOps.back();
4663 const Instruction* prunableOp = fSpvIdCache.find(prunableSpvId);
4664
4665 if (prunableOp) {
4666 fOpCache.remove(*prunableOp);
4667 fSpvIdCache.remove(prunableSpvId);
4668 } else {
4669 SkDEBUGFAIL("reachable-op list contains unrecognized SpvId");
4670 }
4671
4672 fReachableOps.pop_back();
4673 }
4674
4675 // Remove any cached stores that occurred during the conditional block.
4676 while (fStoreOps.size() > ops.numStoreOps) {
4677 if (fStoreCache.find(fStoreOps.back())) {
4678 fStoreCache.remove(fStoreOps.back());
4679 }
4680 fStoreOps.pop_back();
4681 }
4682}
4683
4684void SPIRVCodeGenerator::writeIfStatement(const IfStatement& stmt, OutputStream& out) {
4685 SpvId test = this->writeExpression(*stmt.test(), out);
4686 SpvId ifTrue = this->nextId(nullptr);
4687 SpvId ifFalse = this->nextId(nullptr);
4688
4689 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4690
4691 if (stmt.ifFalse()) {
4692 SpvId end = this->nextId(nullptr);
4693 this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
4694 this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out);
4695 this->writeLabel(ifTrue, kBranchIsOnPreviousLine, out);
4696 this->writeStatement(*stmt.ifTrue(), out);
4697 if (fCurrentBlock) {
4698 this->writeInstruction(SpvOpBranch, end, out);
4699 }
4700 this->writeLabel(ifFalse, kBranchIsAbove, conditionalOps, out);
4701 this->writeStatement(*stmt.ifFalse(), out);
4702 if (fCurrentBlock) {
4703 this->writeInstruction(SpvOpBranch, end, out);
4704 }
4705 this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
4706 } else {
4707 this->writeInstruction(SpvOpSelectionMerge, ifFalse, SpvSelectionControlMaskNone, out);
4708 this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out);
4709 this->writeLabel(ifTrue, kBranchIsOnPreviousLine, out);
4710 this->writeStatement(*stmt.ifTrue(), out);
4711 if (fCurrentBlock) {
4712 this->writeInstruction(SpvOpBranch, ifFalse, out);
4713 }
4714 this->writeLabel(ifFalse, kBranchIsAbove, conditionalOps, out);
4715 }
4716}
4717
4718void SPIRVCodeGenerator::writeForStatement(const ForStatement& f, OutputStream& out) {
4719 if (f.initializer()) {
4720 this->writeStatement(*f.initializer(), out);
4721 }
4722
4723 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4724
4725 // The store cache isn't trustworthy in the presence of branches; store caching only makes sense
4726 // in the context of linear straight-line execution. If we wanted to be more clever, we could
4727 // only invalidate store cache entries for variables affected by the loop body, but for now we
4728 // simply clear the entire cache whenever branching occurs.
4729 SpvId header = this->nextId(nullptr);
4730 SpvId start = this->nextId(nullptr);
4731 SpvId body = this->nextId(nullptr);
4732 SpvId next = this->nextId(nullptr);
4733 fContinueTarget.push_back(next);
4734 SpvId end = this->nextId(nullptr);
4735 fBreakTarget.push_back(end);
4736 this->writeInstruction(SpvOpBranch, header, out);
4737 this->writeLabel(header, kBranchIsBelow, conditionalOps, out);
4738 this->writeInstruction(SpvOpLoopMerge, end, next, SpvLoopControlMaskNone, out);
4739 this->writeInstruction(SpvOpBranch, start, out);
4740 this->writeLabel(start, kBranchIsOnPreviousLine, out);
4741 if (f.test()) {
4742 SpvId test = this->writeExpression(*f.test(), out);
4743 this->writeInstruction(SpvOpBranchConditional, test, body, end, out);
4744 } else {
4745 this->writeInstruction(SpvOpBranch, body, out);
4746 }
4747 this->writeLabel(body, kBranchIsOnPreviousLine, out);
4748 this->writeStatement(*f.statement(), out);
4749 if (fCurrentBlock) {
4750 this->writeInstruction(SpvOpBranch, next, out);
4751 }
4752 this->writeLabel(next, kBranchIsAbove, conditionalOps, out);
4753 if (f.next()) {
4754 this->writeExpression(*f.next(), out);
4755 }
4756 this->writeInstruction(SpvOpBranch, header, out);
4757 this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
4758 fBreakTarget.pop_back();
4759 fContinueTarget.pop_back();
4760}
4761
4762void SPIRVCodeGenerator::writeDoStatement(const DoStatement& d, OutputStream& out) {
4763 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4764
4765 // The store cache isn't trustworthy in the presence of branches; store caching only makes sense
4766 // in the context of linear straight-line execution. If we wanted to be more clever, we could
4767 // only invalidate store cache entries for variables affected by the loop body, but for now we
4768 // simply clear the entire cache whenever branching occurs.
4769 SpvId header = this->nextId(nullptr);
4770 SpvId start = this->nextId(nullptr);
4771 SpvId next = this->nextId(nullptr);
4772 SpvId continueTarget = this->nextId(nullptr);
4773 fContinueTarget.push_back(continueTarget);
4774 SpvId end = this->nextId(nullptr);
4775 fBreakTarget.push_back(end);
4776 this->writeInstruction(SpvOpBranch, header, out);
4777 this->writeLabel(header, kBranchIsBelow, conditionalOps, out);
4778 this->writeInstruction(SpvOpLoopMerge, end, continueTarget, SpvLoopControlMaskNone, out);
4779 this->writeInstruction(SpvOpBranch, start, out);
4780 this->writeLabel(start, kBranchIsOnPreviousLine, out);
4781 this->writeStatement(*d.statement(), out);
4782 if (fCurrentBlock) {
4783 this->writeInstruction(SpvOpBranch, next, out);
4784 this->writeLabel(next, kBranchIsOnPreviousLine, out);
4785 this->writeInstruction(SpvOpBranch, continueTarget, out);
4786 }
4787 this->writeLabel(continueTarget, kBranchIsAbove, conditionalOps, out);
4788 SpvId test = this->writeExpression(*d.test(), out);
4789 this->writeInstruction(SpvOpBranchConditional, test, header, end, out);
4790 this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
4791 fBreakTarget.pop_back();
4792 fContinueTarget.pop_back();
4793}
4794
4795void SPIRVCodeGenerator::writeSwitchStatement(const SwitchStatement& s, OutputStream& out) {
4796 SpvId value = this->writeExpression(*s.value(), out);
4797
4798 ConditionalOpCounts conditionalOps = this->getConditionalOpCounts();
4799
4800 // The store cache isn't trustworthy in the presence of branches; store caching only makes sense
4801 // in the context of linear straight-line execution. If we wanted to be more clever, we could
4802 // only invalidate store cache entries for variables affected by the switch body, but for now we
4803 // simply clear the entire cache whenever branching occurs.
4804 TArray<SpvId> labels;
4805 SpvId end = this->nextId(nullptr);
4806 SpvId defaultLabel = end;
4807 fBreakTarget.push_back(end);
4808 int size = 3;
4809 const StatementArray& cases = s.cases();
4810 for (const std::unique_ptr<Statement>& stmt : cases) {
4811 const SwitchCase& c = stmt->as<SwitchCase>();
4812 SpvId label = this->nextId(nullptr);
4813 labels.push_back(label);
4814 if (!c.isDefault()) {
4815 size += 2;
4816 } else {
4817 defaultLabel = label;
4818 }
4819 }
4820
4821 // We should have exactly one label for each case.
4822 SkASSERT(labels.size() == cases.size());
4823
4824 // Collapse adjacent switch-cases into one; that is, reduce `case 1: case 2: case 3:` into a
4825 // single OpLabel. The Tint SPIR-V reader does not support switch-case fallthrough, but it
4826 // does support multiple switch-cases branching to the same label.
4827 SkBitSet caseIsCollapsed(cases.size());
4828 for (int index = cases.size() - 2; index >= 0; index--) {
4829 if (cases[index]->as<SwitchCase>().statement()->isEmpty()) {
4830 caseIsCollapsed.set(index);
4831 labels[index] = labels[index + 1];
4832 }
4833 }
4834
4835 labels.push_back(end);
4836
4837 this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
4838 this->writeOpCode(SpvOpSwitch, size, out);
4839 this->writeWord(value, out);
4840 this->writeWord(defaultLabel, out);
4841 for (int i = 0; i < cases.size(); ++i) {
4842 const SwitchCase& c = cases[i]->as<SwitchCase>();
4843 if (c.isDefault()) {
4844 continue;
4845 }
4846 this->writeWord(c.value(), out);
4847 this->writeWord(labels[i], out);
4848 }
4849 for (int i = 0; i < cases.size(); ++i) {
4850 if (caseIsCollapsed.test(i)) {
4851 continue;
4852 }
4853 const SwitchCase& c = cases[i]->as<SwitchCase>();
4854 if (i == 0) {
4855 this->writeLabel(labels[i], kBranchIsOnPreviousLine, out);
4856 } else {
4857 this->writeLabel(labels[i], kBranchIsAbove, conditionalOps, out);
4858 }
4859 this->writeStatement(*c.statement(), out);
4860 if (fCurrentBlock) {
4861 this->writeInstruction(SpvOpBranch, labels[i + 1], out);
4862 }
4863 }
4864 this->writeLabel(end, kBranchIsAbove, conditionalOps, out);
4865 fBreakTarget.pop_back();
4866}
4867
4868void SPIRVCodeGenerator::writeReturnStatement(const ReturnStatement& r, OutputStream& out) {
4869 if (r.expression()) {
4870 this->writeInstruction(SpvOpReturnValue, this->writeExpression(*r.expression(), out),
4871 out);
4872 } else {
4873 this->writeInstruction(SpvOpReturn, out);
4874 }
4875}
4876
4877// Given any function, returns the top-level symbol table (OUTSIDE of the function's scope).
4879 return anyFunc.definition()->body()->as<Block>().symbolTable()->fParent;
4880}
4881
4882SPIRVCodeGenerator::EntrypointAdapter SPIRVCodeGenerator::writeEntrypointAdapter(
4883 const FunctionDeclaration& main) {
4884 // Our goal is to synthesize a tiny helper function which looks like this:
4885 // void _entrypoint() { sk_FragColor = main(); }
4886
4887 // Fish a symbol table out of main().
4888 SymbolTable* symbolTable = get_top_level_symbol_table(main);
4889
4890 // Get `sk_FragColor` as a writable reference.
4891 const Symbol* skFragColorSymbol = symbolTable->find("sk_FragColor");
4892 SkASSERT(skFragColorSymbol);
4893 const Variable& skFragColorVar = skFragColorSymbol->as<Variable>();
4894 auto skFragColorRef = std::make_unique<VariableReference>(Position(), &skFragColorVar,
4895 VariableReference::RefKind::kWrite);
4896 // Synthesize a call to the `main()` function.
4897 if (!main.returnType().matches(skFragColorRef->type())) {
4898 fContext.fErrors->error(main.fPosition, "SPIR-V does not support returning '" +
4899 main.returnType().description() + "' from main()");
4900 return {};
4901 }
4902 ExpressionArray args;
4903 if (main.parameters().size() == 1) {
4904 if (!main.parameters()[0]->type().matches(*fContext.fTypes.fFloat2)) {
4905 fContext.fErrors->error(main.fPosition,
4906 "SPIR-V does not support parameter of type '" +
4907 main.parameters()[0]->type().description() + "' to main()");
4908 return {};
4909 }
4910 double kZero[2] = {0.0, 0.0};
4913 }
4914 auto callMainFn = std::make_unique<FunctionCall>(Position(), &main.returnType(), &main,
4915 std::move(args));
4916
4917 // Synthesize `skFragColor = main()` as a BinaryExpression.
4918 auto assignmentStmt = std::make_unique<ExpressionStatement>(std::make_unique<BinaryExpression>(
4919 Position(),
4920 std::move(skFragColorRef),
4921 Operator::Kind::EQ,
4922 std::move(callMainFn),
4923 &main.returnType()));
4924
4925 // Function bodies are always wrapped in a Block.
4926 StatementArray entrypointStmts;
4927 entrypointStmts.push_back(std::move(assignmentStmt));
4928 auto entrypointBlock = Block::Make(Position(), std::move(entrypointStmts),
4929 Block::Kind::kBracedScope, /*symbols=*/nullptr);
4930 // Declare an entrypoint function.
4931 EntrypointAdapter adapter;
4932 adapter.entrypointDecl =
4933 std::make_unique<FunctionDeclaration>(fContext,
4934 Position(),
4936 "_entrypoint",
4937 /*parameters=*/TArray<Variable*>{},
4938 /*returnType=*/fContext.fTypes.fVoid.get(),
4940 // Define it.
4941 adapter.entrypointDef = FunctionDefinition::Convert(fContext,
4942 Position(),
4943 *adapter.entrypointDecl,
4944 std::move(entrypointBlock),
4945 /*builtin=*/false);
4946
4947 adapter.entrypointDecl->setDefinition(adapter.entrypointDef.get());
4948 return adapter;
4949}
4950
4951void SPIRVCodeGenerator::writeUniformBuffer(SymbolTable* topLevelSymbolTable) {
4952 SkASSERT(!fTopLevelUniforms.empty());
4953 static constexpr char kUniformBufferName[] = "_UniformBuffer";
4954
4955 // Convert the list of top-level uniforms into a matching struct named _UniformBuffer, and build
4956 // a lookup table of variables to UniformBuffer field indices.
4957 TArray<Field> fields;
4958 fields.reserve_exact(fTopLevelUniforms.size());
4959 for (const VarDeclaration* topLevelUniform : fTopLevelUniforms) {
4960 const Variable* var = topLevelUniform->var();
4961 fTopLevelUniformMap.set(var, (int)fields.size());
4962 ModifierFlags flags = var->modifierFlags() & ~ModifierFlag::kUniform;
4963 fields.emplace_back(var->fPosition, var->layout(), flags, var->name(), &var->type());
4964 }
4965 fUniformBuffer.fStruct = Type::MakeStructType(fContext,
4966 Position(),
4967 kUniformBufferName,
4968 std::move(fields),
4969 /*interfaceBlock=*/true);
4970
4971 // Create a global variable to contain this struct.
4972 Layout layout;
4973 layout.fBinding = fProgram.fConfig->fSettings.fDefaultUniformBinding;
4974 layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet;
4975
4976 fUniformBuffer.fInnerVariable = Variable::Make(/*pos=*/Position(),
4977 /*modifiersPosition=*/Position(),
4978 layout,
4980 fUniformBuffer.fStruct.get(),
4981 kUniformBufferName,
4982 /*mangledName=*/"",
4983 /*builtin=*/false,
4984 Variable::Storage::kGlobal);
4985
4986 // Create an interface block object for this global variable.
4987 fUniformBuffer.fInterfaceBlock =
4988 std::make_unique<InterfaceBlock>(Position(), fUniformBuffer.fInnerVariable.get());
4989
4990 // Generate an interface block and hold onto its ID.
4991 fUniformBufferId = this->writeInterfaceBlock(*fUniformBuffer.fInterfaceBlock);
4992}
4993
4994void SPIRVCodeGenerator::addRTFlipUniform(Position pos) {
4995 SkASSERT(!fProgram.fConfig->fSettings.fForceNoRTFlip);
4996
4997 if (fWroteRTFlip) {
4998 return;
4999 }
5000 // Flip variable hasn't been written yet. This means we don't have an existing
5001 // interface block, so we're free to just synthesize one.
5002 fWroteRTFlip = true;
5003 TArray<Field> fields;
5004 if (fProgram.fConfig->fSettings.fRTFlipOffset < 0) {
5005 fContext.fErrors->error(pos, "RTFlipOffset is negative");
5006 }
5007 fields.emplace_back(pos,
5009 /*location=*/-1,
5010 fProgram.fConfig->fSettings.fRTFlipOffset,
5011 /*binding=*/-1,
5012 /*index=*/-1,
5013 /*set=*/-1,
5014 /*builtin=*/-1,
5015 /*inputAttachmentIndex=*/-1),
5018 fContext.fTypes.fFloat2.get());
5019 std::string_view name = "sksl_synthetic_uniforms";
5020 const Type* intfStruct = fSynthetics.takeOwnershipOfSymbol(Type::MakeStructType(
5021 fContext, Position(), name, std::move(fields), /*interfaceBlock=*/true));
5022 bool usePushConstants = fProgram.fConfig->fSettings.fUsePushConstants;
5023 int binding = -1, set = -1;
5024 if (!usePushConstants) {
5025 binding = fProgram.fConfig->fSettings.fRTFlipBinding;
5026 if (binding == -1) {
5027 fContext.fErrors->error(pos, "layout(binding=...) is required in SPIR-V");
5028 }
5029 set = fProgram.fConfig->fSettings.fRTFlipSet;
5030 if (set == -1) {
5031 fContext.fErrors->error(pos, "layout(set=...) is required in SPIR-V");
5032 }
5033 }
5034 Layout layout(/*flags=*/usePushConstants ? LayoutFlag::kPushConstant : LayoutFlag::kNone,
5035 /*location=*/-1,
5036 /*offset=*/-1,
5037 binding,
5038 /*index=*/-1,
5039 set,
5040 /*builtin=*/-1,
5041 /*inputAttachmentIndex=*/-1);
5042 Variable* intfVar =
5043 fSynthetics.takeOwnershipOfSymbol(Variable::Make(/*pos=*/Position(),
5044 /*modifiersPosition=*/Position(),
5045 layout,
5047 intfStruct,
5048 name,
5049 /*mangledName=*/"",
5050 /*builtin=*/false,
5051 Variable::Storage::kGlobal));
5052 {
5053 AutoAttachPoolToThread attach(fProgram.fPool.get());
5055 std::make_unique<FieldSymbol>(Position(), intfVar, /*field=*/0));
5056 }
5057 InterfaceBlock intf(Position(), intfVar);
5058 this->writeInterfaceBlock(intf, false);
5059}
5060
5061std::tuple<const Variable*, const Variable*> SPIRVCodeGenerator::synthesizeTextureAndSampler(
5062 const Variable& combinedSampler) {
5063 SkASSERT(fUseTextureSamplerPairs);
5064 SkASSERT(combinedSampler.type().typeKind() == Type::TypeKind::kSampler);
5065
5066 auto data = std::make_unique<SynthesizedTextureSamplerPair>();
5067
5068 Layout texLayout = combinedSampler.layout();
5069 texLayout.fBinding = texLayout.fTexture;
5070 data->fTextureName = std::string(combinedSampler.name()) + "_texture";
5071
5072 auto texture = Variable::Make(/*pos=*/Position(),
5073 /*modifiersPosition=*/Position(),
5074 texLayout,
5075 combinedSampler.modifierFlags(),
5076 &combinedSampler.type().textureType(),
5077 data->fTextureName,
5078 /*mangledName=*/"",
5079 /*builtin=*/false,
5080 Variable::Storage::kGlobal);
5081
5082 Layout samplerLayout = combinedSampler.layout();
5083 samplerLayout.fBinding = samplerLayout.fSampler;
5084 samplerLayout.fFlags &= ~LayoutFlag::kAllPixelFormats;
5085 data->fSamplerName = std::string(combinedSampler.name()) + "_sampler";
5086
5087 auto sampler = Variable::Make(/*pos=*/Position(),
5088 /*modifiersPosition=*/Position(),
5089 samplerLayout,
5090 combinedSampler.modifierFlags(),
5091 fContext.fTypes.fSampler.get(),
5092 data->fSamplerName,
5093 /*mangledName=*/"",
5094 /*builtin=*/false,
5095 Variable::Storage::kGlobal);
5096
5097 const Variable* t = texture.get();
5098 const Variable* s = sampler.get();
5099 data->fTexture = std::move(texture);
5100 data->fSampler = std::move(sampler);
5101 fSynthesizedSamplerMap.set(&combinedSampler, std::move(data));
5102
5103 return {t, s};
5104}
5105
5106void SPIRVCodeGenerator::writeInstructions(const Program& program, OutputStream& out) {
5107 fGLSLExtendedInstructions = this->nextId(nullptr);
5108 StringStream body;
5109
5110 // Do an initial pass over the program elements to establish some baseline info.
5111 const FunctionDeclaration* main = nullptr;
5112 int localSizeX = 1, localSizeY = 1, localSizeZ = 1;
5113 Position combinedSamplerPos;
5114 Position separateSamplerPos;
5115 for (const ProgramElement* e : program.elements()) {
5116 switch (e->kind()) {
5117 case ProgramElement::Kind::kFunction: {
5118 // Assign SpvIds to functions.
5119 const FunctionDefinition& funcDef = e->as<FunctionDefinition>();
5120 const FunctionDeclaration& funcDecl = funcDef.declaration();
5121 fFunctionMap.set(&funcDecl, this->nextId(nullptr));
5122 if (funcDecl.isMain()) {
5123 main = &funcDecl;
5124 }
5125 break;
5126 }
5127 case ProgramElement::Kind::kGlobalVar: {
5128 // Look for sampler variables and determine whether or not this program uses
5129 // combined samplers or separate samplers. The layout backend will be marked as
5130 // WebGPU for separate samplers, or Vulkan for combined samplers.
5131 const GlobalVarDeclaration& decl = e->as<GlobalVarDeclaration>();
5132 const Variable& var = *decl.varDeclaration().var();
5133 if (var.type().isSampler()) {
5134 if (var.layout().fFlags & LayoutFlag::kVulkan) {
5135 combinedSamplerPos = decl.position();
5136 }
5137 if (var.layout().fFlags & (LayoutFlag::kWebGPU | LayoutFlag::kDirect3D)) {
5138 separateSamplerPos = decl.position();
5139 }
5140 }
5141 break;
5142 }
5143 case ProgramElement::Kind::kModifiers: {
5144 // If this is a compute program, collect the local-size values. Dimensions that are
5145 // not present will be assigned a value of 1.
5146 if (ProgramConfig::IsCompute(program.fConfig->fKind)) {
5147 const ModifiersDeclaration& modifiers = e->as<ModifiersDeclaration>();
5148 if (modifiers.layout().fLocalSizeX >= 0) {
5149 localSizeX = modifiers.layout().fLocalSizeX;
5150 }
5151 if (modifiers.layout().fLocalSizeY >= 0) {
5152 localSizeY = modifiers.layout().fLocalSizeY;
5153 }
5154 if (modifiers.layout().fLocalSizeZ >= 0) {
5155 localSizeZ = modifiers.layout().fLocalSizeZ;
5156 }
5157 }
5158 break;
5159 }
5160 default:
5161 break;
5162 }
5163 }
5164
5165 // Make sure we have a main() function.
5166 if (!main) {
5167 fContext.fErrors->error(Position(), "program does not contain a main() function");
5168 return;
5169 }
5170 // Make sure our program's sampler usage is consistent.
5171 if (combinedSamplerPos.valid() && separateSamplerPos.valid()) {
5172 fContext.fErrors->error(Position(), "programs cannot contain a mixture of sampler types");
5173 fContext.fErrors->error(combinedSamplerPos, "combined sampler found here:");
5174 fContext.fErrors->error(separateSamplerPos, "separate sampler found here:");
5175 return;
5176 }
5177 fUseTextureSamplerPairs = separateSamplerPos.valid();
5178
5179 // Emit interface blocks.
5180 std::set<SpvId> interfaceVars;
5181 for (const ProgramElement* e : program.elements()) {
5182 if (e->is<InterfaceBlock>()) {
5183 const InterfaceBlock& intf = e->as<InterfaceBlock>();
5184 SpvId id = this->writeInterfaceBlock(intf);
5185
5186 if ((intf.var()->modifierFlags() & (ModifierFlag::kIn | ModifierFlag::kOut)) &&
5187 intf.var()->layout().fBuiltin == -1) {
5188 interfaceVars.insert(id);
5189 }
5190 }
5191 }
5192 // If MustDeclareFragmentFrontFacing is set, the front-facing flag (sk_Clockwise) needs to be
5193 // explicitly declared in the output, whether or not the program explicitly references it.
5194 // However, if the program naturally declares it, we don't want to include it a second time;
5195 // we keep track of the real global variable declarations to see if sk_Clockwise is emitted.
5196 const VarDeclaration* missingClockwiseDecl = nullptr;
5198 if (const Symbol* clockwise = program.fSymbols->findBuiltinSymbol("sk_Clockwise")) {
5199 missingClockwiseDecl = clockwise->as<Variable>().varDeclaration();
5200 }
5201 }
5202 // Emit global variable declarations.
5203 for (const ProgramElement* e : program.elements()) {
5204 if (e->is<GlobalVarDeclaration>()) {
5205 const VarDeclaration& decl = e->as<GlobalVarDeclaration>().varDeclaration();
5206 if (!this->writeGlobalVarDeclaration(program.fConfig->fKind, decl)) {
5207 return;
5208 }
5209 if (missingClockwiseDecl == &decl) {
5210 // We emitted an sk_Clockwise declaration naturally, so we don't need a workaround.
5211 missingClockwiseDecl = nullptr;
5212 }
5213 }
5214 }
5215 // All the global variables have been declared. If sk_Clockwise was not naturally included in
5216 // the output, but MustDeclareFragmentFrontFacing was set, we need to bodge it in ourselves.
5217 if (missingClockwiseDecl) {
5218 if (!this->writeGlobalVarDeclaration(program.fConfig->fKind, *missingClockwiseDecl)) {
5219 return;
5220 }
5221 missingClockwiseDecl = nullptr;
5222 }
5223 // Emit top-level uniforms into a dedicated uniform buffer.
5224 if (!fTopLevelUniforms.empty()) {
5225 this->writeUniformBuffer(get_top_level_symbol_table(*main));
5226 }
5227 // If main() returns a half4, synthesize a tiny entrypoint function which invokes the real
5228 // main() and stores the result into sk_FragColor.
5229 EntrypointAdapter adapter;
5230 if (main->returnType().matches(*fContext.fTypes.fHalf4)) {
5231 adapter = this->writeEntrypointAdapter(*main);
5232 if (adapter.entrypointDecl) {
5233 fFunctionMap.set(adapter.entrypointDecl.get(), this->nextId(nullptr));
5234 this->writeFunction(*adapter.entrypointDef, body);
5235 main = adapter.entrypointDecl.get();
5236 }
5237 }
5238 // Emit all the functions.
5239 for (const ProgramElement* e : program.elements()) {
5240 if (e->is<FunctionDefinition>()) {
5241 this->writeFunction(e->as<FunctionDefinition>(), body);
5242 }
5243 }
5244 // Add global in/out variables to the list of interface variables.
5245 for (const auto& [var, spvId] : fVariableMap) {
5246 if (var->storage() == Variable::Storage::kGlobal &&
5247 (var->modifierFlags() & (ModifierFlag::kIn | ModifierFlag::kOut))) {
5248 interfaceVars.insert(spvId);
5249 }
5250 }
5251 this->writeCapabilities(out);
5252 this->writeInstruction(SpvOpExtInstImport, fGLSLExtendedInstructions, "GLSL.std.450", out);
5254 this->writeOpCode(SpvOpEntryPoint,
5255 (SpvId)(3 + (main->name().length() + 4) / 4) + (int32_t)interfaceVars.size(),
5256 out);
5257 if (ProgramConfig::IsVertex(program.fConfig->fKind)) {
5258 this->writeWord(SpvExecutionModelVertex, out);
5259 } else if (ProgramConfig::IsFragment(program.fConfig->fKind)) {
5260 this->writeWord(SpvExecutionModelFragment, out);
5261 } else if (ProgramConfig::IsCompute(program.fConfig->fKind)) {
5262 this->writeWord(SpvExecutionModelGLCompute, out);
5263 } else {
5264 SK_ABORT("cannot write this kind of program to SPIR-V\n");
5265 }
5266 SpvId entryPoint = fFunctionMap[main];
5267 this->writeWord(entryPoint, out);
5268 this->writeString(main->name(), out);
5269 for (int var : interfaceVars) {
5270 this->writeWord(var, out);
5271 }
5272 if (ProgramConfig::IsFragment(program.fConfig->fKind)) {
5273 this->writeInstruction(SpvOpExecutionMode,
5274 fFunctionMap[main],
5276 out);
5277 } else if (ProgramConfig::IsCompute(program.fConfig->fKind)) {
5278 this->writeInstruction(SpvOpExecutionMode,
5279 fFunctionMap[main],
5281 localSizeX, localSizeY, localSizeZ,
5282 out);
5283 }
5284 for (const ProgramElement* e : program.elements()) {
5285 if (e->is<Extension>()) {
5286 this->writeInstruction(SpvOpSourceExtension, e->as<Extension>().name(), out);
5287 }
5288 }
5289
5290 write_stringstream(fNameBuffer, out);
5291 write_stringstream(fDecorationBuffer, out);
5292 write_stringstream(fConstantBuffer, out);
5293 write_stringstream(body, out);
5294}
5295
5298 this->writeWord(SpvMagicNumber, *fOut);
5299 this->writeWord(SpvVersion, *fOut);
5300 this->writeWord(SKSL_MAGIC, *fOut);
5302 this->writeInstructions(fProgram, buffer);
5303 this->writeWord(fIdCount, *fOut);
5304 this->writeWord(0, *fOut); // reserved, always zero
5306 return fContext.fErrors->errorCount() == 0;
5307}
5308
5309#if defined(SK_ENABLE_SPIRV_VALIDATION)
5310static bool validate_spirv(ErrorReporter& reporter, std::string_view program) {
5311 SkASSERT(0 == program.size() % 4);
5312 const uint32_t* programData = reinterpret_cast<const uint32_t*>(program.data());
5313 size_t programSize = program.size() / 4;
5314
5315 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0);
5316 std::string errors;
5317 auto msgFn = [&errors](spv_message_level_t, const char*, const spv_position_t&, const char* m) {
5318 errors += "SPIR-V validation error: ";
5319 errors += m;
5320 errors += '\n';
5321 };
5322 tools.SetMessageConsumer(msgFn);
5323
5324 // Verify that the SPIR-V we produced is valid. At runtime, we will abort() with a message
5325 // explaining the error. In standalone mode (skslc), we will send the message, plus the
5326 // entire disassembled SPIR-V (for easier context & debugging) as *our* error message.
5327 bool result = tools.Validate(programData, programSize);
5328 if (!result) {
5329#if defined(SKSL_STANDALONE)
5330 // Convert the string-stream to a SPIR-V disassembly.
5331 std::string disassembly;
5332 uint32_t options = spvtools::SpirvTools::kDefaultDisassembleOption;
5333 options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;
5334 if (tools.Disassemble(programData, programSize, &disassembly, options)) {
5335 errors.append(disassembly);
5336 }
5337 reporter.error(Position(), errors);
5338#else
5339 SkDEBUGFAILF("%s", errors.c_str());
5340#endif
5341 }
5342 return result;
5343}
5344#endif
5345
5346bool ToSPIRV(Program& program, const ShaderCaps* caps, OutputStream& out) {
5347 TRACE_EVENT0("skia.shaders", "SkSL::ToSPIRV");
5348 SkASSERT(caps != nullptr);
5349
5350 program.fContext->fErrors->setSource(*program.fSource);
5351#ifdef SK_ENABLE_SPIRV_VALIDATION
5353 SPIRVCodeGenerator cg(program.fContext.get(), caps, &program, &buffer);
5354 bool result = cg.generateCode();
5355
5356 if (result && program.fConfig->fSettings.fValidateSPIRV) {
5357 std::string_view binary = buffer.str();
5358 result = validate_spirv(*program.fContext->fErrors, binary);
5359 out.write(binary.data(), binary.size());
5360 }
5361#else
5362 SPIRVCodeGenerator cg(program.fContext.get(), caps, &program, &out);
5363 bool result = cg.generateCode();
5364#endif
5365 program.fContext->fErrors->setSource(std::string_view());
5366
5367 return result;
5368}
5369
5370bool ToSPIRV(Program& program, const ShaderCaps* caps, std::string* out) {
5372 if (!ToSPIRV(program, caps, buffer)) {
5373 return false;
5374 }
5375 *out = buffer.str();
5376 return true;
5377}
5378
5379} // namespace SkSL
static bool compare(const SkBitmap &ref, const SkIRect &iref, const SkBitmap &test, const SkIRect &itest)
Definition BlurTest.cpp:100
const char * options
reporter
int count
@ GLSLstd450FMix
@ GLSLstd450UClamp
@ GLSLstd450FMax
@ GLSLstd450SmoothStep
@ GLSLstd450UMax
@ GLSLstd450Step
@ GLSLstd450Atan2
@ GLSLstd450SMax
@ GLSLstd450FClamp
@ GLSLstd450Atan
@ GLSLstd450UMin
@ GLSLstd450FMin
@ GLSLstd450SClamp
@ GLSLstd450SMin
SkPoint pos
static float next(float f)
SkPathOp ops[]
static void merge(const uint8_t *SK_RESTRICT row, int rowN, const SkAlpha *SK_RESTRICT srcAA, const int16_t *SK_RESTRICT srcRuns, SkAlpha *SK_RESTRICT dstAA, int16_t *SK_RESTRICT dstRuns, int width)
#define SkUNREACHABLE
Definition SkAssert.h:135
#define SkDEBUGFAIL(message)
Definition SkAssert.h:118
#define SK_ABORT(message,...)
Definition SkAssert.h:70
#define SkDEBUGFAILF(fmt,...)
Definition SkAssert.h:119
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SkASSERTF(cond, fmt,...)
Definition SkAssert.h:117
static std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap *src, const SkYUVAPixmaps *srcYUVA, const SkColorSpace *srcYUVAColorSpace, const SkJpegEncoder::Options &options)
static uint32_t hash(const SkShaderBase::GradientInfo &v)
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
constexpr int SK_SAMPLEMASK_BUILTIN
constexpr int SK_CLOCKWISE_BUILTIN
constexpr int SK_FRAGCOLOR_BUILTIN
constexpr int SK_SECONDARYFRAGCOLOR_BUILTIN
constexpr int SK_FRAGCOORD_BUILTIN
constexpr int SK_SAMPLEMASKIN_BUILTIN
int64_t SKSL_INT
Definition SkSLDefines.h:16
#define SKSL_RTFLIP_NAME
Definition SkSLProgram.h:19
#define FLOAT_SPIRV(x)
constexpr int DEVICE_CLOCKWISE_BUILTIN
#define kLast_Capability
constexpr int DEVICE_FRAGCOORDS_BUILTIN
static constexpr SkSL::Layout kDefaultTypeLayout
#define BOOL_SPIRV(x)
#define ALL_SPIRV(x)
#define ALL_GLSL(x)
#define BY_TYPE_GLSL(ifFloat, ifInt, ifUInt)
#define SPECIAL(x)
#define PACK(type)
static constexpr bool SkToBool(const T &x)
Definition SkTo.h:35
SI T load(const P *ptr)
Type::kYUV Type::kRGBA() int(0.7 *637)
const std::unique_ptr< Type > fFloat2
const std::unique_ptr< Type > fHalf4
const std::unique_ptr< Type > fUInt2
const std::unique_ptr< Type > fInt2
const std::unique_ptr< Type > fInt
const std::unique_ptr< Type > fAtomicUInt
const std::unique_ptr< Type > fSampler
const std::unique_ptr< Type > fFloat4
const std::unique_ptr< Type > fUInt
const std::unique_ptr< Type > fBool
const std::unique_ptr< Type > fVoid
const std::unique_ptr< Type > fFloat
const std::unique_ptr< Type > fFloat3
static constexpr float kSharpenTexturesBias
const ShaderCaps & fCaps
const Program & fProgram
static const Expression * GetConstantValueOrNull(const Expression &value)
static std::unique_ptr< Expression > MakeFromConstants(const Context &context, Position pos, const Type &type, const double values[])
const BuiltinTypes & fTypes
Definition SkSLContext.h:30
ErrorReporter * fErrors
Definition SkSLContext.h:36
void error(Position position, std::string_view msg)
Kind kind() const
std::unique_ptr< Expression > & base()
const FunctionDefinition * definition() const
std::unique_ptr< Statement > & body()
static std::unique_ptr< FunctionDefinition > Convert(const Context &context, Position pos, const FunctionDeclaration &function, std::unique_ptr< Statement > body, bool builtin)
const T & as() const
Definition SkSLIRNode.h:133
std::unique_ptr< Expression > & base()
static const Type & IndexType(const Context &context, const Type &type)
static std::unique_ptr< Literal > MakeInt(const Context &context, Position pos, SKSL_INT value)
Definition SkSLLiteral.h:54
static std::unique_ptr< Literal > MakeFloat(const Context &context, Position pos, float value)
Definition SkSLLiteral.h:43
SpvStorageClass storageClass() const override
SpvId load(OutputStream &out) override
void store(SpvId value, OutputStream &out) override
bool isMemoryObjectPointer() const override
PointerLValue(SPIRVCodeGenerator &gen, SpvId pointer, bool isMemoryObject, SpvId type, SPIRVCodeGenerator::Precision precision, SpvStorageClass_ storageClass)
virtual SpvStorageClass storageClass() const =0
virtual SpvId load(OutputStream &out)=0
virtual bool applySwizzle(const ComponentArray &components, const Type &newType)
virtual void store(SpvId value, OutputStream &out)=0
SPIRVCodeGenerator(const Context *context, const ShaderCaps *caps, const Program *program, OutputStream *out)
SpvId load(OutputStream &out) override
SwizzleLValue(SPIRVCodeGenerator &gen, SpvId vecPointer, const ComponentArray &components, const Type &baseType, const Type &swizzleType, SpvStorageClass_ storageClass)
SpvStorageClass storageClass() const override
void store(SpvId value, OutputStream &out) override
bool applySwizzle(const ComponentArray &components, const Type &newType) override
const Type * addArrayDimension(const Context &context, const Type *type, int arraySize)
T * takeOwnershipOfSymbol(std::unique_ptr< T > symbol)
const Type & type() const
Definition SkSLSymbol.h:42
virtual bool isVector() const
Definition SkSLType.h:524
virtual bool isScalar() const
Definition SkSLType.h:512
static std::unique_ptr< Type > MakeStructType(const Context &context, Position pos, std::string_view name, skia_private::TArray< Field > fields, bool interfaceBlock=false)
Definition SkSLType.cpp:811
TypeKind typeKind() const
Definition SkSLType.h:283
std::unique_ptr< Expression > & value()
Variable * var() const
static std::unique_ptr< Variable > Make(Position pos, Position modifiersPosition, const Layout &layout, ModifierFlags flags, const Type *type, std::string_view name, std::string mangledName, bool builtin, Storage storage)
Storage storage() const
ModifierFlags modifierFlags() const
virtual const Layout & layout() const
constexpr T * data() const
Definition SkSpan_impl.h:94
constexpr bool empty() const
Definition SkSpan_impl.h:96
constexpr size_t size() const
Definition SkSpan_impl.h:95
T * push_back_n(int n)
Definition SkTArray.h:262
bool empty() const
Definition SkTArray.h:194
void resize(size_t count)
Definition SkTArray.h:418
int size() const
Definition SkTArray.h:416
void reserve_exact(int n)
Definition SkTArray.h:176
T & emplace_back(Args &&... args)
Definition SkTArray.h:243
V * find(const K &key) const
Definition SkTHash.h:479
V * set(K key, V val)
Definition SkTHash.h:472
void remove(const K &key)
Definition SkTHash.h:494
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition main.cc:19
sk_sp< SkImage > image
Definition examples.cpp:29
static bool b
struct MyStruct s
struct MyStruct a[10]
FlutterSemanticsFlag flags
glong glong end
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
static const uint8_t buffer[]
uint8_t value
GAsyncResult * result
uint32_t uint32_t * format
uint32_t * target
Dart_NativeFunction function
Definition fuchsia.cc:51
const char * name
Definition fuchsia.cc:50
size_t length
FlTexture * texture
uint32_t Hash32(const void *data, size_t bytes, uint32_t seed)
bool IsCompileTimeConstant(const Expression &expr)
std::unique_ptr< Expression > RewriteIndexedSwizzle(const Context &context, const IndexExpression &swizzle)
static SpvStorageClass_ get_storage_class(const Expression &expr)
static const int32_t SKSL_MAGIC
static bool types_match(const Type &a, const Type &b)
static SymbolTable * get_top_level_symbol_table(const FunctionDeclaration &anyFunc)
skia_private::STArray< 4, int8_t > ComponentArray
Definition SkSLDefines.h:24
static bool is_out(ModifierFlags f)
bool ToSPIRV(Program &program, const ShaderCaps *caps, OutputStream &out)
static SpvStorageClass_ get_storage_class_for_global_variable(const Variable &var, SpvStorageClass_ fallbackStorageClass)
void write_stringstream(const StringStream &s, OutputStream &out)
Definition SkSLUtil.cpp:41
static bool is_bool(const Type &type)
static bool is_float(const Type &type)
static bool is_unsigned(const Type &type)
skia_private::STArray< 2, std::unique_ptr< Statement > > StatementArray
Definition SkSLDefines.h:34
static bool is_signed(const Type &type)
static SpvImageFormat layout_flags_to_image_format(LayoutFlags flags)
LayoutFlag
Definition SkSLLayout.h:20
static bool is_vardecl_compile_time_constant(const VarDeclaration &varDecl)
static bool is_control_flow_op(SpvOp_ op)
static bool is_globally_reachable_op(SpvOp_ op)
static T pick_by_type(const Type &type, T ifFloat, T ifInt, T ifUInt, T ifBool)
static bool is_in(ModifierFlags f)
SkEnumBitMask< SkSL::LayoutFlag > LayoutFlags
Definition SkSLLayout.h:68
intptr_t word
Definition globals.h:500
call(args)
Definition dom.py:159
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switches.h:41
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition switches.h:259
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition switches.h:76
bool operator==(C p1, const scoped_nsprotocol< C > &p2)
Definition gen.py:1
Definition main.py:1
#define T
static const char header[]
Definition skpbench.cpp:88
SpvStorageClass_
Definition spirv.h:126
@ SpvStorageClassUniformConstant
Definition spirv.h:127
@ SpvStorageClassPushConstant
Definition spirv.h:136
@ SpvStorageClassUniform
Definition spirv.h:129
@ SpvStorageClassInput
Definition spirv.h:128
@ SpvStorageClassFunction
Definition spirv.h:134
@ SpvStorageClassPrivate
Definition spirv.h:133
@ SpvStorageClassWorkgroup
Definition spirv.h:131
@ SpvStorageClassOutput
Definition spirv.h:130
enum SpvImageFormat_ SpvImageFormat
enum SpvOp_ SpvOp
@ SpvBuiltInSampleMask
Definition spirv.h:384
@ SpvSelectionControlMaskNone
Definition spirv.h:414
@ SpvMemoryModelGLSL450
Definition spirv.h:88
@ SpvDecorationMatrixStride
Definition spirv.h:326
@ SpvDecorationInputAttachmentIndex
Definition spirv.h:360
@ SpvDecorationFlat
Definition spirv.h:332
@ SpvDecorationColMajor
Definition spirv.h:324
@ SpvDecorationArrayStride
Definition spirv.h:325
@ SpvDecorationBufferBlock
Definition spirv.h:322
@ SpvDecorationBuiltIn
Definition spirv.h:330
@ SpvDecorationBinding
Definition spirv.h:350
@ SpvDecorationNonWritable
Definition spirv.h:342
@ SpvDecorationRelaxedPrecision
Definition spirv.h:319
@ SpvDecorationOffset
Definition spirv.h:352
@ SpvDecorationNonReadable
Definition spirv.h:343
@ SpvDecorationIndex
Definition spirv.h:349
@ SpvDecorationNoPerspective
Definition spirv.h:331
@ SpvDecorationBlock
Definition spirv.h:321
@ SpvDecorationLocation
Definition spirv.h:347
@ SpvDecorationDescriptorSet
Definition spirv.h:351
static const unsigned int SpvMagicNumber
Definition spirv.h:56
@ SpvExecutionModeLocalSize
Definition spirv.h:109
@ SpvExecutionModeOriginUpperLeft
Definition spirv.h:100
@ SpvLoopControlMaskNone
Definition spirv.h:425
@ SpvDimCube
Definition spirv.h:145
@ SpvDim3D
Definition spirv.h:144
@ SpvDim2D
Definition spirv.h:143
@ SpvDim1D
Definition spirv.h:142
@ SpvDimBuffer
Definition spirv.h:147
@ SpvDimSubpassData
Definition spirv.h:148
@ SpvDimRect
Definition spirv.h:146
@ SpvScopeDevice
Definition spirv.h:487
@ SpvScopeWorkgroup
Definition spirv.h:488
@ SpvImageOperandsBiasMask
Definition spirv.h:262
@ SpvImageOperandsSampleMask
Definition spirv.h:268
@ SpvImageOperandsLodMask
Definition spirv.h:263
@ SpvImageOperandsGradMask
Definition spirv.h:264
@ SpvCapabilitySampledBuffer
Definition spirv.h:559
@ SpvCapabilityShader
Definition spirv.h:516
@ SpvCapabilityInputAttachment
Definition spirv.h:553
@ SpvCapabilityImageQuery
Definition spirv.h:563
@ SpvImageFormatUnknown
Definition spirv.h:165
@ SpvImageFormatRgba32f
Definition spirv.h:166
@ SpvImageFormatRgba8
Definition spirv.h:169
@ SpvImageFormatR32f
Definition spirv.h:168
enum SpvScope_ SpvScope
@ SpvFunctionControlMaskNone
Definition spirv.h:438
@ SpvAddressingModelLogical
Definition spirv.h:81
enum SpvStorageClass_ SpvStorageClass
@ SpvExecutionModelGLCompute
Definition spirv.h:76
@ SpvExecutionModelFragment
Definition spirv.h:75
@ SpvExecutionModelVertex
Definition spirv.h:71
@ SpvMemorySemanticsWorkgroupMemoryMask
Definition spirv.h:466
@ SpvMemorySemanticsUniformMemoryMask
Definition spirv.h:464
@ SpvMemorySemanticsMaskNone
Definition spirv.h:459
@ SpvMemorySemanticsAcquireReleaseMask
Definition spirv.h:462
unsigned int SpvId
Definition spirv.h:51
SpvOp_
Definition spirv.h:573
@ SpvOpBitcast
Definition spirv.h:688
@ SpvOpFOrdLessThan
Definition spirv.h:746
@ SpvOpCapability
Definition spirv.h:589
@ SpvOpFUnordNotEqual
Definition spirv.h:745
@ SpvOpSampledImage
Definition spirv.h:651
@ SpvOpFOrdGreaterThanEqual
Definition spirv.h:752
@ SpvOpSourceExtension
Definition spirv.h:578
@ SpvOpConvertFToS
Definition spirv.h:674
@ SpvOpTypeMatrix
Definition spirv.h:595
@ SpvOpSource
Definition spirv.h:577
@ SpvOpShiftRightLogical
Definition spirv.h:754
@ SpvOpSNegate
Definition spirv.h:689
@ SpvOpStore
Definition spirv.h:629
@ SpvOpFDiv
Definition spirv.h:699
@ SpvOpLogicalNotEqual
Definition spirv.h:727
@ SpvOpShiftLeftLogical
Definition spirv.h:756
@ SpvOpTypeBool
Definition spirv.h:591
@ SpvOpTypeArray
Definition spirv.h:599
@ SpvOpMatrixTimesMatrix
Definition spirv.h:709
@ SpvOpVariable
Definition spirv.h:626
@ SpvOpImageQuerySize
Definition spirv.h:669
@ SpvOpLoopMerge
Definition spirv.h:798
@ SpvOpDecorate
Definition spirv.h:638
@ SpvOpImageFetch
Definition spirv.h:660
@ SpvOpName
Definition spirv.h:579
@ SpvOpFunctionCall
Definition spirv.h:625
@ SpvOpTypeSampler
Definition spirv.h:597
@ SpvOpMatrixTimesScalar
Definition spirv.h:706
@ SpvOpFSub
Definition spirv.h:694
@ SpvOpFOrdGreaterThan
Definition spirv.h:748
@ SpvOpVectorTimesScalar
Definition spirv.h:705
@ SpvOpLabel
Definition spirv.h:800
@ SpvOpConvertUToF
Definition spirv.h:676
@ SpvOpCompositeExtract
Definition spirv.h:647
@ SpvOpExtInstImport
Definition spirv.h:584
@ SpvOpConvertSToF
Definition spirv.h:675
@ SpvOpUGreaterThan
Definition spirv.h:734
@ SpvOpAccessChain
Definition spirv.h:632
@ SpvOpLogicalNot
Definition spirv.h:730
@ SpvOpBranchConditional
Definition spirv.h:802
@ SpvOpBranch
Definition spirv.h:801
@ SpvOpFunctionParameter
Definition spirv.h:623
@ SpvOpSDiv
Definition spirv.h:698
@ SpvOpConstantComposite
Definition spirv.h:614
@ SpvOpVectorTimesMatrix
Definition spirv.h:707
@ SpvOpMemoryModel
Definition spirv.h:586
@ SpvOpLoad
Definition spirv.h:628
@ SpvOpUMod
Definition spirv.h:700
@ SpvOpControlBarrier
Definition spirv.h:779
@ SpvOpAny
Definition spirv.h:716
@ SpvOpMatrixTimesVector
Definition spirv.h:708
@ SpvOpBitwiseOr
Definition spirv.h:757
@ SpvOpImageSampleExplicitLod
Definition spirv.h:653
@ SpvOpBitwiseXor
Definition spirv.h:758
@ SpvOpFOrdEqual
Definition spirv.h:742
@ SpvOpULessThan
Definition spirv.h:738
@ SpvOpAll
Definition spirv.h:717
@ SpvOpAtomicIAdd
Definition spirv.h:788
@ SpvOpTypeImage
Definition spirv.h:596
@ SpvOpUnreachable
Definition spirv.h:807
@ SpvOpImageSampleProjExplicitLod
Definition spirv.h:657
@ SpvOpTypeSampledImage
Definition spirv.h:598
@ SpvOpTypeVector
Definition spirv.h:594
@ SpvOpCompositeConstruct
Definition spirv.h:646
@ SpvOpFunctionEnd
Definition spirv.h:624
@ SpvOpSGreaterThanEqual
Definition spirv.h:737
@ SpvOpFAdd
Definition spirv.h:692
@ SpvOpImageSampleProjImplicitLod
Definition spirv.h:656
@ SpvOpReturn
Definition spirv.h:805
@ SpvOpUndef
Definition spirv.h:575
@ SpvOpDot
Definition spirv.h:711
@ SpvOpAtomicLoad
Definition spirv.h:781
@ SpvOpFMod
Definition spirv.h:704
@ SpvOpConvertFToU
Definition spirv.h:673
@ SpvOpIEqual
Definition spirv.h:732
@ SpvOpSelect
Definition spirv.h:731
@ SpvOpShiftRightArithmetic
Definition spirv.h:755
@ SpvOpFMul
Definition spirv.h:696
@ SpvOpExtInst
Definition spirv.h:585
@ SpvOpULessThanEqual
Definition spirv.h:740
@ SpvOpIAdd
Definition spirv.h:691
@ SpvOpEntryPoint
Definition spirv.h:587
@ SpvOpFOrdLessThanEqual
Definition spirv.h:750
@ SpvOpAtomicStore
Definition spirv.h:782
@ SpvOpTypeInt
Definition spirv.h:592
@ SpvOpLogicalOr
Definition spirv.h:728
@ SpvOpMemberName
Definition spirv.h:580
@ SpvOpVectorShuffle
Definition spirv.h:645
@ SpvOpTypeFloat
Definition spirv.h:593
@ SpvOpImageWrite
Definition spirv.h:664
@ SpvOpBitwiseAnd
Definition spirv.h:759
@ SpvOpFunction
Definition spirv.h:622
@ SpvOpMemberDecorate
Definition spirv.h:639
@ SpvOpPhi
Definition spirv.h:797
@ SpvOpSwitch
Definition spirv.h:803
@ SpvOpSLessThanEqual
Definition spirv.h:741
@ SpvOpSLessThan
Definition spirv.h:739
@ SpvOpConstantFalse
Definition spirv.h:612
@ SpvOpDPdy
Definition spirv.h:767
@ SpvOpIMul
Definition spirv.h:695
@ SpvOpConstantTrue
Definition spirv.h:611
@ SpvOpVectorExtractDynamic
Definition spirv.h:643
@ SpvOpKill
Definition spirv.h:804
@ SpvOpImageSampleImplicitLod
Definition spirv.h:652
@ SpvOpSelectionMerge
Definition spirv.h:799
@ SpvOpTypeFunction
Definition spirv.h:604
@ SpvOpNot
Definition spirv.h:760
@ SpvOpTypeStruct
Definition spirv.h:601
@ SpvOpUDiv
Definition spirv.h:697
@ SpvOpExecutionMode
Definition spirv.h:588
@ SpvOpReturnValue
Definition spirv.h:806
@ SpvOpLogicalEqual
Definition spirv.h:726
@ SpvOpImageRead
Definition spirv.h:663
@ SpvOpISub
Definition spirv.h:693
@ SpvOpFNegate
Definition spirv.h:690
@ SpvOpConstant
Definition spirv.h:613
@ SpvOpSGreaterThan
Definition spirv.h:735
@ SpvOpTypeVoid
Definition spirv.h:590
@ SpvOpTypeRuntimeArray
Definition spirv.h:600
@ SpvOpUGreaterThanEqual
Definition spirv.h:736
@ SpvOpTypePointer
Definition spirv.h:603
@ SpvOpLogicalAnd
Definition spirv.h:729
@ SpvOpSMod
Definition spirv.h:702
@ SpvOpINotEqual
Definition spirv.h:733
static const unsigned int SpvVersion
Definition spirv.h:57
Point offset
LayoutFlags fFlags
Definition SkSLLayout.h:112
static bool IsVertex(ProgramKind kind)
static bool IsFragment(ProgramKind kind)
static bool IsCompute(ProgramKind kind)
std::shared_ptr< Context > fContext
std::unique_ptr< Pool > fPool
ProgramInterface fInterface
std::unique_ptr< SymbolTable > fSymbols
std::unique_ptr< std::string > fSource
std::unique_ptr< ProgramConfig > fConfig
uint32_t operator()(const SPIRVCodeGenerator::Instruction &key) const
static Word Result(const Type &type)
bool fMustDeclareFragmentFrontFacing
Definition SkSLUtil.h:151
bool fRewriteMatrixVectorMultiply
Definition SkSLUtil.h:140
const uintptr_t id
#define TRACE_EVENT0(category_group, name)