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