Flutter Engine
The Flutter Engine
SkSLWGSLCodeGenerator.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 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"
16#include "src/core/SkTHash.h"
31#include "src/sksl/SkSLString.h"
33#include "src/sksl/SkSLUtil.h"
78#include "src/sksl/spirv.h"
80
81#include <algorithm>
82#include <cstddef>
83#include <cstdint>
84#include <initializer_list>
85#include <iterator>
86#include <memory>
87#include <optional>
88#include <string>
89#include <string_view>
90#include <utility>
91
92#ifdef SK_ENABLE_WGSL_VALIDATION
93#include "tint/tint.h"
94#include "src/tint/lang/wgsl/reader/options.h"
95#include "src/tint/lang/wgsl/extension.h"
96#endif
97
98using namespace skia_private;
99
100namespace {
101
102// Represents a function's dependencies that are not accessible in global scope. For instance,
103// pipeline stage input and output parameters must be passed in as an argument.
104//
105// This is a bitmask enum. (It would be inside `class WGSLCodeGenerator`, but this leads to build
106// errors in MSVC.)
107enum class WGSLFunctionDependency : uint8_t {
108 kNone = 0,
109 kPipelineInputs = 1 << 0,
110 kPipelineOutputs = 1 << 1,
111};
112using WGSLFunctionDependencies = SkEnumBitMask<WGSLFunctionDependency>;
113
114SK_MAKE_BITMASK_OPS(WGSLFunctionDependency)
115
116} // namespace
117
118namespace SkSL {
119
121public:
122 // See https://www.w3.org/TR/WGSL/#builtin-values
123 enum class Builtin {
124 // Vertex stage:
125 kVertexIndex, // input
126 kInstanceIndex, // input
127 kPosition, // output, fragment stage input
128
129 // Fragment stage:
130 kLastFragColor, // input
131 kFrontFacing, // input
132 kSampleIndex, // input
133 kFragDepth, // output
134 kSampleMaskIn, // input
135 kSampleMask, // output
136
137 // Compute stage:
138 kLocalInvocationId, // input
139 kLocalInvocationIndex, // input
140 kGlobalInvocationId, // input
141 kWorkgroupId, // input
142 kNumWorkgroups, // input
143 };
144
145 // Variable declarations can be terminated by:
146 // - comma (","), e.g. in struct member declarations or function parameters
147 // - semicolon (";"), e.g. in function scope variables
148 // A "none" option is provided to skip the delimiter when not needed, e.g. at the end of a list
149 // of declarations.
150 enum class Delimiter {
151 kComma,
152 kSemicolon,
153 kNone,
154 };
155
158 WGSLFunctionDependencies>;
159
160 // Mappings used to synthesize function parameters according to dependencies on pipeline
161 // input/output variables.
163
164 // These flags track extensions that will need to be enabled.
166 };
167
169 const ShaderCaps* caps,
170 const Program* program,
172 : INHERITED(context, caps, program, out) {}
173
174 bool generateCode() override;
175
176private:
177 using INHERITED = CodeGenerator;
178 using Precedence = OperatorPrecedence;
179
180 // Called by generateCode() as the first step.
181 void preprocessProgram();
182
183 // Write output content while correctly handling indentation.
184 void write(std::string_view s);
185 void writeLine(std::string_view s = std::string_view());
186 void finishLine();
187
188 // Helpers to declare a pipeline stage IO parameter declaration.
189 void writePipelineIODeclaration(const Layout& layout,
190 const Type& type,
191 std::string_view name,
192 Delimiter delimiter);
193 void writeUserDefinedIODecl(const Layout& layout,
194 const Type& type,
195 std::string_view name,
196 Delimiter delimiter);
197 void writeBuiltinIODecl(const Type& type,
198 std::string_view name,
199 Builtin builtin,
200 Delimiter delimiter);
201 void writeVariableDecl(const Layout& layout,
202 const Type& type,
203 std::string_view name,
204 Delimiter delimiter);
205
206 // Write a function definition.
207 void writeFunction(const FunctionDefinition& f);
208 void writeFunctionDeclaration(const FunctionDeclaration& f,
209 SkSpan<const bool> paramNeedsDedicatedStorage);
210
211 // Write the program entry point.
212 void writeEntryPoint(const FunctionDefinition& f);
213
214 // Writers for supported statement types.
215 void writeStatement(const Statement& s);
216 void writeStatements(const StatementArray& statements);
217 void writeBlock(const Block& b);
218 void writeDoStatement(const DoStatement& expr);
219 void writeExpressionStatement(const Expression& expr);
220 void writeForStatement(const ForStatement& s);
221 void writeIfStatement(const IfStatement& s);
222 void writeReturnStatement(const ReturnStatement& s);
223 void writeSwitchStatement(const SwitchStatement& s);
224 void writeSwitchCases(SkSpan<const SwitchCase* const> cases);
225 void writeEmulatedSwitchFallthroughCases(SkSpan<const SwitchCase* const> cases,
226 std::string_view switchValue);
227 void writeSwitchCaseList(SkSpan<const SwitchCase* const> cases);
228 void writeVarDeclaration(const VarDeclaration& varDecl);
229
230 // Synthesizes an LValue for an expression.
231 class LValue;
232 class PointerLValue;
233 class SwizzleLValue;
234 class VectorComponentLValue;
235 std::unique_ptr<LValue> makeLValue(const Expression& e);
236
237 std::string variableReferenceNameForLValue(const VariableReference& r);
238 std::string variablePrefix(const Variable& v);
239
240 bool binaryOpNeedsComponentwiseMatrixPolyfill(const Type& left, const Type& right, Operator op);
241
242 // Writers for expressions. These return the final expression text as a string, and emit any
243 // necessary setup code directly into the program as necessary. The returned expression may be
244 // a `let`-alias that cannot be assigned-into; use `makeLValue` for an assignable expression.
245 std::string assembleExpression(const Expression& e, Precedence parentPrecedence);
246 std::string assembleBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence);
247 std::string assembleBinaryExpression(const Expression& left,
248 Operator op,
249 const Expression& right,
250 const Type& resultType,
251 Precedence parentPrecedence);
252 std::string assembleFieldAccess(const FieldAccess& f);
253 std::string assembleFunctionCall(const FunctionCall& call, Precedence parentPrecedence);
254 std::string assembleIndexExpression(const IndexExpression& i);
255 std::string assembleLiteral(const Literal& l);
256 std::string assemblePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence);
257 std::string assemblePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence);
258 std::string assembleSwizzle(const Swizzle& swizzle);
259 std::string assembleTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence);
260 std::string assembleVariableReference(const VariableReference& r);
261 std::string assembleName(std::string_view name);
262
263 std::string assembleIncrementExpr(const Type& type);
264
265 // Intrinsic helper functions.
266 std::string assembleIntrinsicCall(const FunctionCall& call,
267 IntrinsicKind kind,
268 Precedence parentPrecedence);
269 std::string assembleSimpleIntrinsic(std::string_view intrinsicName, const FunctionCall& call);
270 std::string assembleUnaryOpIntrinsic(Operator op,
271 const FunctionCall& call,
272 Precedence parentPrecedence);
273 std::string assembleBinaryOpIntrinsic(Operator op,
274 const FunctionCall& call,
275 Precedence parentPrecedence);
276 std::string assembleVectorizedIntrinsic(std::string_view intrinsicName,
277 const FunctionCall& call);
278 std::string assembleOutAssignedIntrinsic(std::string_view intrinsicName,
279 std::string_view returnFieldName,
280 std::string_view outFieldName,
281 const FunctionCall& call);
282 std::string assemblePartialSampleCall(std::string_view intrinsicName,
283 const Expression& sampler,
284 const Expression& coords);
285 std::string assembleInversePolyfill(const FunctionCall& call);
286 std::string assembleComponentwiseMatrixBinary(const Type& leftType,
287 const Type& rightType,
288 const std::string& left,
289 const std::string& right,
290 Operator op);
291
292 // Constructor expressions
293 std::string assembleAnyConstructor(const AnyConstructor& c);
294 std::string assembleConstructorCompound(const ConstructorCompound& c);
295 std::string assembleConstructorCompoundVector(const ConstructorCompound& c);
296 std::string assembleConstructorCompoundMatrix(const ConstructorCompound& c);
297 std::string assembleConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c);
298 std::string assembleConstructorMatrixResize(const ConstructorMatrixResize& ctor);
299
300 // Synthesized helper functions for comparison operators that are not supported by WGSL.
301 std::string assembleEqualityExpression(const Type& left,
302 const std::string& leftName,
303 const Type& right,
304 const std::string& rightName,
305 Operator op,
306 Precedence parentPrecedence);
307 std::string assembleEqualityExpression(const Expression& left,
308 const Expression& right,
309 Operator op,
310 Precedence parentPrecedence);
311
312 // Writes a scratch variable into the program and returns its name (e.g. `_skTemp123`).
313 std::string writeScratchVar(const Type& type, const std::string& value = "");
314
315 // Writes a scratch let-variable into the program, gives it the value of `expr`, and returns its
316 // name (e.g. `_skTemp123`).
317 std::string writeScratchLet(const std::string& expr, bool isCompileTimeConstant = false);
318 std::string writeScratchLet(const Expression& expr, Precedence parentPrecedence);
319
320 // Converts `expr` into a string and returns a scratch let-variable associated with the
321 // expression. Compile-time constants and plain variable references will return the expression
322 // directly and omit the let-variable.
323 std::string writeNontrivialScratchLet(const Expression& expr, Precedence parentPrecedence);
324
325 // Generic recursive ProgramElement visitor.
326 void writeProgramElement(const ProgramElement& e);
327 void writeGlobalVarDeclaration(const GlobalVarDeclaration& d);
328 void writeStructDefinition(const StructDefinition& s);
329 void writeModifiersDeclaration(const ModifiersDeclaration&);
330
331 // Writes the WGSL struct fields for SkSL structs and interface blocks. Enforces WGSL address
332 // space layout constraints
333 // (https://www.w3.org/TR/WGSL/#address-space-layout-constraints) if a `layout` is
334 // provided. A struct that does not need to be host-shareable does not require a `layout`.
335 void writeFields(SkSpan<const Field> fields, const MemoryLayout* memoryLayout = nullptr);
336
337 // We bundle uniforms, and all varying pipeline stage inputs and outputs, into separate structs.
338 bool needsStageInputStruct() const;
339 void writeStageInputStruct();
340 bool needsStageOutputStruct() const;
341 void writeStageOutputStruct();
342 void writeUniformsAndBuffers();
343 void prepareUniformPolyfillsForInterfaceBlock(const InterfaceBlock* interfaceBlock,
344 std::string_view instanceName,
345 MemoryLayout::Standard nativeLayout);
346 void writeEnables();
347 void writeUniformPolyfills();
348
349 void writeTextureOrSampler(const Variable& var,
350 int bindingLocation,
351 std::string_view suffix,
352 std::string_view wgslType);
353
354 // Writes all top-level non-opaque global uniform declarations (i.e. not part of an interface
355 // block) into a single uniform block binding.
356 //
357 // In complete fragment/vertex/compute programs, uniforms will be declared only as interface
358 // blocks and global opaque types (like textures and samplers) which we expect to be declared
359 // with a unique binding and descriptor set index. However, test files that are declared as RTE
360 // programs may contain OpenGL-style global uniform declarations with no clear binding index to
361 // use for the containing synthesized block.
362 //
363 // Since we are handling these variables only to generate gold files from RTEs and never run
364 // them, we always declare them at the default bind group and binding index.
365 void writeNonBlockUniformsForTests();
366
367 // For a given function declaration, writes out any implicitly required pipeline stage arguments
368 // based on the function's pre-determined dependencies. These are expected to be written out as
369 // the first parameters for a function that requires them. Returns true if any arguments were
370 // written.
371 std::string functionDependencyArgs(const FunctionDeclaration&);
372 bool writeFunctionDependencyParams(const FunctionDeclaration&);
373
374 // Code in the header appears before the main body of code.
375 StringStream fHeader;
376
377 // We assign unique names to anonymous interface blocks based on the type.
379
380 // Stores the functions which use stage inputs/outputs as well as required WGSL extensions.
381 ProgramRequirements fRequirements;
384
385 // These fields track whether we have written the polyfill for `inverse()` for a given matrix
386 // type.
387 bool fWrittenInverse2 = false;
388 bool fWrittenInverse3 = false;
389 bool fWrittenInverse4 = false;
390
391 // These fields control uniform polyfill support in cases where WGSL and std140 disagree.
392 // In std140 layout, matrices need to be represented as arrays of @size(16)-aligned vectors, and
393 // array elements are wrapped in a struct containing a single @size(16)-aligned element. Arrays
394 // of matrices combine both wrappers. These wrapper structs are unpacked into natively-typed
395 // globals at the shader entrypoint.
396 struct FieldPolyfillInfo {
397 const InterfaceBlock* fInterfaceBlock;
398 std::string fReplacementName;
399 bool fIsArray = false;
400 bool fIsMatrix = false;
401 bool fWasAccessed = false;
402 };
404 FieldPolyfillMap fFieldPolyfillMap;
405
406 // Output processing state.
407 int fIndentation = 0;
408 bool fAtLineStart = false;
409 bool fHasUnconditionalReturn = false;
410 bool fAtFunctionScope = false;
411 int fConditionalScopeDepth = 0;
412 int fLocalSizeX = 1;
413 int fLocalSizeY = 1;
414 int fLocalSizeZ = 1;
415
416 int fScratchCount = 0;
417};
418
419enum class ProgramKind : int8_t;
420
421namespace {
422
423static constexpr char kSamplerSuffix[] = "_Sampler";
424static constexpr char kTextureSuffix[] = "_Texture";
425
426// See https://www.w3.org/TR/WGSL/#memory-view-types
427enum class PtrAddressSpace {
428 kFunction,
429 kPrivate,
430 kStorage,
431};
432
433const char* operator_name(Operator op) {
434 switch (op.kind()) {
435 case Operator::Kind::LOGICALXOR: return " != ";
436 default: return op.operatorName();
437 }
438}
439
440bool is_reserved_word(std::string_view word) {
441 static const THashSet<std::string_view> kReservedWords{
442 // Used by SkSL:
443 "FSIn",
444 "FSOut",
445 "VSIn",
446 "VSOut",
447 "CSIn",
448 "_globalUniforms",
449 "_GlobalUniforms",
450 "_return",
451 "_stageIn",
452 "_stageOut",
453 // Keywords: https://www.w3.org/TR/WGSL/#keyword-summary
454 "alias",
455 "break",
456 "case",
457 "const",
458 "const_assert",
459 "continue",
460 "continuing",
461 "default",
462 "diagnostic",
463 "discard",
464 "else",
465 "enable",
466 "false",
467 "fn",
468 "for",
469 "if",
470 "let",
471 "loop",
472 "override",
473 "requires",
474 "return",
475 "struct",
476 "switch",
477 "true",
478 "var",
479 "while",
480 // Pre-declared types: https://www.w3.org/TR/WGSL/#predeclared-types
481 "bool",
482 "f16",
483 "f32",
484 "i32",
485 "u32",
486 // ... and pre-declared type generators:
487 "array",
488 "atomic",
489 "mat2x2",
490 "mat2x3",
491 "mat2x4",
492 "mat3x2",
493 "mat3x3",
494 "mat3x4",
495 "mat4x2",
496 "mat4x3",
497 "mat4x4",
498 "ptr",
499 "texture_1d",
500 "texture_2d",
501 "texture_2d_array",
502 "texture_3d",
503 "texture_cube",
504 "texture_cube_array",
505 "texture_multisampled_2d",
506 "texture_storage_1d",
507 "texture_storage_2d",
508 "texture_storage_2d_array",
509 "texture_storage_3d",
510 "vec2",
511 "vec3",
512 "vec4",
513 // Pre-declared enumerants: https://www.w3.org/TR/WGSL/#predeclared-enumerants
514 "read",
515 "write",
516 "read_write",
517 "function",
518 "private",
519 "workgroup",
520 "uniform",
521 "storage",
522 "perspective",
523 "linear",
524 "flat",
525 "center",
526 "centroid",
527 "sample",
528 "vertex_index",
529 "instance_index",
530 "position",
531 "front_facing",
532 "frag_depth",
533 "local_invocation_id",
534 "local_invocation_index",
535 "global_invocation_id",
536 "workgroup_id",
537 "num_workgroups",
538 "sample_index",
539 "sample_mask",
540 "rgba8unorm",
541 "rgba8snorm",
542 "rgba8uint",
543 "rgba8sint",
544 "rgba16uint",
545 "rgba16sint",
546 "rgba16float",
547 "r32uint",
548 "r32sint",
549 "r32float",
550 "rg32uint",
551 "rg32sint",
552 "rg32float",
553 "rgba32uint",
554 "rgba32sint",
555 "rgba32float",
556 "bgra8unorm",
557 // Reserved words: https://www.w3.org/TR/WGSL/#reserved-words
558 "_",
559 "NULL",
560 "Self",
561 "abstract",
562 "active",
563 "alignas",
564 "alignof",
565 "as",
566 "asm",
567 "asm_fragment",
568 "async",
569 "attribute",
570 "auto",
571 "await",
572 "become",
573 "binding_array",
574 "cast",
575 "catch",
576 "class",
577 "co_await",
578 "co_return",
579 "co_yield",
580 "coherent",
581 "column_major",
582 "common",
583 "compile",
584 "compile_fragment",
585 "concept",
586 "const_cast",
587 "consteval",
588 "constexpr",
589 "constinit",
590 "crate",
591 "debugger",
592 "decltype",
593 "delete",
594 "demote",
595 "demote_to_helper",
596 "do",
597 "dynamic_cast",
598 "enum",
599 "explicit",
600 "export",
601 "extends",
602 "extern",
603 "external",
604 "fallthrough",
605 "filter",
606 "final",
607 "finally",
608 "friend",
609 "from",
610 "fxgroup",
611 "get",
612 "goto",
613 "groupshared",
614 "highp",
615 "impl",
616 "implements",
617 "import",
618 "inline",
619 "instanceof",
620 "interface",
621 "layout",
622 "lowp",
623 "macro",
624 "macro_rules",
625 "match",
626 "mediump",
627 "meta",
628 "mod",
629 "module",
630 "move",
631 "mut",
632 "mutable",
633 "namespace",
634 "new",
635 "nil",
636 "noexcept",
637 "noinline",
638 "nointerpolation",
639 "noperspective",
640 "null",
641 "nullptr",
642 "of",
643 "operator",
644 "package",
645 "packoffset",
646 "partition",
647 "pass",
648 "patch",
649 "pixelfragment",
650 "precise",
651 "precision",
652 "premerge",
653 "priv",
654 "protected",
655 "pub",
656 "public",
657 "readonly",
658 "ref",
659 "regardless",
660 "register",
661 "reinterpret_cast",
662 "require",
663 "resource",
664 "restrict",
665 "self",
666 "set",
667 "shared",
668 "sizeof",
669 "smooth",
670 "snorm",
671 "static",
672 "static_assert",
673 "static_cast",
674 "std",
675 "subroutine",
676 "super",
677 "target",
678 "template",
679 "this",
680 "thread_local",
681 "throw",
682 "trait",
683 "try",
684 "type",
685 "typedef",
686 "typeid",
687 "typename",
688 "typeof",
689 "union",
690 "unless",
691 "unorm",
692 "unsafe",
693 "unsized",
694 "use",
695 "using",
696 "varying",
697 "virtual",
698 "volatile",
699 "wgsl",
700 "where",
701 "with",
702 "writeonly",
703 "yield",
704 };
705
706 return kReservedWords.contains(word);
707}
708
709std::string_view pipeline_struct_prefix(ProgramKind kind) {
710 if (ProgramConfig::IsVertex(kind)) {
711 return "VS";
712 }
713 if (ProgramConfig::IsFragment(kind)) {
714 return "FS";
715 }
716 if (ProgramConfig::IsCompute(kind)) {
717 return "CS";
718 }
719 // Compute programs don't have stage-in/stage-out pipeline structs.
720 return "";
721}
722
723std::string_view address_space_to_str(PtrAddressSpace addressSpace) {
724 switch (addressSpace) {
725 case PtrAddressSpace::kFunction:
726 return "function";
727 case PtrAddressSpace::kPrivate:
728 return "private";
729 case PtrAddressSpace::kStorage:
730 return "storage";
731 }
732 SkDEBUGFAIL("unsupported ptr address space");
733 return "unsupported";
734}
735
736std::string_view to_scalar_type(const Type& type) {
738 switch (type.numberKind()) {
739 // Floating-point numbers in WebGPU currently always have 32-bit footprint and
740 // relaxed-precision is not supported without extensions. f32 is the only floating-point
741 // number type in WGSL (see the discussion on https://github.com/gpuweb/gpuweb/issues/658).
743 return "f32";
745 return "i32";
747 return "u32";
749 return "bool";
751 [[fallthrough]];
752 default:
753 break;
754 }
755 return type.name();
756}
757
758// Convert a SkSL type to a WGSL type. Handles all plain types except structure types
759// (see https://www.w3.org/TR/WGSL/#plain-types-section).
760std::string to_wgsl_type(const Context& context, const Type& raw, const Layout* layout = nullptr) {
761 const Type& type = raw.resolve().scalarTypeForLiteral();
762 switch (type.typeKind()) {
764 return std::string(to_scalar_type(type));
765
767 SkASSERT(type.matches(*context.fTypes.fAtomicUInt));
768 return "atomic<u32>";
769
771 std::string_view ct = to_scalar_type(type.componentType());
772 return String::printf("vec%d<%.*s>", type.columns(), (int)ct.length(), ct.data());
773 }
775 std::string_view ct = to_scalar_type(type.componentType());
776 return String::printf("mat%dx%d<%.*s>",
777 type.columns(), type.rows(), (int)ct.length(), ct.data());
778 }
780 std::string result = "array<" + to_wgsl_type(context, type.componentType(), layout);
781 if (!type.isUnsizedArray()) {
782 result += ", ";
783 result += std::to_string(type.columns());
784 }
785 return result + '>';
786 }
788 if (type.matches(*context.fTypes.fWriteOnlyTexture2D)) {
789 std::string result = "texture_storage_2d<";
790 // Write-only storage texture types require a pixel format, which is in the layout.
791 SkASSERT(layout);
792 LayoutFlags pixelFormat = layout->fFlags & LayoutFlag::kAllPixelFormats;
793 switch (pixelFormat.value()) {
794 case (int)LayoutFlag::kRGBA8:
795 return result + "rgba8unorm, write>";
796
797 case (int)LayoutFlag::kRGBA32F:
798 return result + "rgba32float, write>";
799
800 case (int)LayoutFlag::kR32F:
801 return result + "r32float, write>";
802
803 default:
804 // The front-end should have rejected this.
805 return result + "write>";
806 }
807 }
808 if (type.matches(*context.fTypes.fReadOnlyTexture2D)) {
809 return "texture_2d<f32>";
810 }
811 break;
812 }
813 default:
814 break;
815 }
816 return std::string(type.name());
817}
818
819std::string to_ptr_type(const Context& context,
820 const Type& type,
821 const Layout* layout,
822 PtrAddressSpace addressSpace = PtrAddressSpace::kFunction) {
823 return "ptr<" + std::string(address_space_to_str(addressSpace)) + ", " +
824 to_wgsl_type(context, type, layout) + '>';
825}
826
827std::string_view wgsl_builtin_name(WGSLCodeGenerator::Builtin builtin) {
828 using Builtin = WGSLCodeGenerator::Builtin;
829 switch (builtin) {
830 case Builtin::kVertexIndex:
831 return "@builtin(vertex_index)";
832 case Builtin::kInstanceIndex:
833 return "@builtin(instance_index)";
834 case Builtin::kPosition:
835 return "@builtin(position)";
836 case Builtin::kLastFragColor:
837 return "@color(0)";
838 case Builtin::kFrontFacing:
839 return "@builtin(front_facing)";
840 case Builtin::kSampleIndex:
841 return "@builtin(sample_index)";
842 case Builtin::kFragDepth:
843 return "@builtin(frag_depth)";
844 case Builtin::kSampleMask:
845 case Builtin::kSampleMaskIn:
846 return "@builtin(sample_mask)";
847 case Builtin::kLocalInvocationId:
848 return "@builtin(local_invocation_id)";
849 case Builtin::kLocalInvocationIndex:
850 return "@builtin(local_invocation_index)";
851 case Builtin::kGlobalInvocationId:
852 return "@builtin(global_invocation_id)";
853 case Builtin::kWorkgroupId:
854 return "@builtin(workgroup_id)";
855 case Builtin::kNumWorkgroups:
856 return "@builtin(num_workgroups)";
857 default:
858 break;
859 }
860
861 SkDEBUGFAIL("unsupported builtin");
862 return "unsupported";
863}
864
865std::string_view wgsl_builtin_type(WGSLCodeGenerator::Builtin builtin) {
866 using Builtin = WGSLCodeGenerator::Builtin;
867 switch (builtin) {
868 case Builtin::kVertexIndex:
869 return "u32";
870 case Builtin::kInstanceIndex:
871 return "u32";
872 case Builtin::kPosition:
873 return "vec4<f32>";
874 case Builtin::kLastFragColor:
875 return "vec4<f32>";
876 case Builtin::kFrontFacing:
877 return "bool";
878 case Builtin::kSampleIndex:
879 return "u32";
880 case Builtin::kFragDepth:
881 return "f32";
882 case Builtin::kSampleMask:
883 return "u32";
884 case Builtin::kSampleMaskIn:
885 return "u32";
886 case Builtin::kLocalInvocationId:
887 return "vec3<u32>";
888 case Builtin::kLocalInvocationIndex:
889 return "u32";
890 case Builtin::kGlobalInvocationId:
891 return "vec3<u32>";
892 case Builtin::kWorkgroupId:
893 return "vec3<u32>";
894 case Builtin::kNumWorkgroups:
895 return "vec3<u32>";
896 default:
897 break;
898 }
899
900 SkDEBUGFAIL("unsupported builtin");
901 return "unsupported";
902}
903
904// Some built-in variables have a type that differs from their SkSL counterpart (e.g. signed vs
905// unsigned integer). We handle these cases with an explicit type conversion during a variable
906// reference. Returns the WGSL type of the conversion target if conversion is needed, otherwise
907// returns std::nullopt.
908std::optional<std::string_view> needs_builtin_type_conversion(const Variable& v) {
909 switch (v.layout().fBuiltin) {
912 return {"i32"};
913 default:
914 break;
915 }
916 return std::nullopt;
917}
918
919// Map a SkSL builtin flag to a WGSL builtin kind. Returns std::nullopt if `builtin` is not
920// not supported for WGSL.
921//
922// Also see //src/sksl/sksl_vert.sksl and //src/sksl/sksl_frag.sksl for supported built-ins.
923std::optional<WGSLCodeGenerator::Builtin> builtin_from_sksl_name(int builtin) {
924 using Builtin = WGSLCodeGenerator::Builtin;
925 switch (builtin) {
927 [[fallthrough]];
929 return Builtin::kPosition;
931 return Builtin::kVertexIndex;
933 return Builtin::kInstanceIndex;
935 return Builtin::kLastFragColor;
937 // TODO(skia:13092): While `front_facing` is the corresponding built-in, it does not
938 // imply a particular winding order. We correctly compute the face orientation based
939 // on how Skia configured the render pipeline for all references to this built-in
940 // variable (see `SkSL::Program::Interface::fRTFlipUniform`).
941 return Builtin::kFrontFacing;
943 return Builtin::kSampleMaskIn;
945 return Builtin::kSampleMask;
947 return Builtin::kNumWorkgroups;
949 return Builtin::kWorkgroupId;
951 return Builtin::kLocalInvocationId;
953 return Builtin::kGlobalInvocationId;
955 return Builtin::kLocalInvocationIndex;
956 default:
957 break;
958 }
959 return std::nullopt;
960}
961
962const char* delimiter_to_str(WGSLCodeGenerator::Delimiter delimiter) {
963 using Delim = WGSLCodeGenerator::Delimiter;
964 switch (delimiter) {
965 case Delim::kComma:
966 return ",";
967 case Delim::kSemicolon:
968 return ";";
969 case Delim::kNone:
970 default:
971 break;
972 }
973 return "";
974}
975
976// FunctionDependencyResolver visits the IR tree rooted at a particular function definition and
977// computes that function's dependencies on pipeline stage IO parameters. These are later used to
978// synthesize arguments when writing out function definitions.
979class FunctionDependencyResolver : public ProgramVisitor {
980public:
981 using Deps = WGSLFunctionDependencies;
983
984 FunctionDependencyResolver(const Program* p,
985 const FunctionDeclaration* f,
986 DepsMap* programDependencyMap)
987 : fProgram(p), fFunction(f), fDependencyMap(programDependencyMap) {}
988
989 Deps resolve() {
991 this->visit(*fProgram);
992 return fDeps;
993 }
994
995private:
996 bool visitProgramElement(const ProgramElement& p) override {
997 // Only visit the program that matches the requested function.
998 if (p.is<FunctionDefinition>() && &p.as<FunctionDefinition>().declaration() == fFunction) {
1000 }
1001 // Continue visiting other program elements.
1002 return false;
1003 }
1004
1005 bool visitExpression(const Expression& e) override {
1006 if (e.is<VariableReference>()) {
1007 const VariableReference& v = e.as<VariableReference>();
1008 if (v.variable()->storage() == Variable::Storage::kGlobal) {
1009 ModifierFlags flags = v.variable()->modifierFlags();
1010 if (flags & ModifierFlag::kIn) {
1011 fDeps |= WGSLFunctionDependency::kPipelineInputs;
1012 }
1013 if (flags & ModifierFlag::kOut) {
1014 fDeps |= WGSLFunctionDependency::kPipelineOutputs;
1015 }
1016 }
1017 } else if (e.is<FunctionCall>()) {
1018 // The current function that we're processing (`fFunction`) inherits the dependencies of
1019 // functions that it makes calls to, because the pipeline stage IO parameters need to be
1020 // passed down as an argument.
1021 const FunctionCall& callee = e.as<FunctionCall>();
1022
1023 // Don't process a function again if we have already resolved it.
1024 Deps* found = fDependencyMap->find(&callee.function());
1025 if (found) {
1026 fDeps |= *found;
1027 } else {
1028 // Store the dependencies that have been discovered for the current function so far.
1029 // If `callee` directly or indirectly calls the current function, then this value
1030 // will prevent an infinite recursion.
1031 fDependencyMap->set(fFunction, fDeps);
1032
1033 // Separately traverse the called function's definition and determine its
1034 // dependencies.
1035 FunctionDependencyResolver resolver(fProgram, &callee.function(), fDependencyMap);
1036 Deps calleeDeps = resolver.resolve();
1037
1038 // Store the callee's dependencies in the global map to avoid processing
1039 // the function again for future calls.
1040 fDependencyMap->set(&callee.function(), calleeDeps);
1041
1042 // Add to the current function's dependencies.
1043 fDeps |= calleeDeps;
1044 }
1045 }
1047 }
1048
1049 const Program* const fProgram;
1050 const FunctionDeclaration* const fFunction;
1051 DepsMap* const fDependencyMap;
1052 Deps fDeps = WGSLFunctionDependency::kNone;
1053
1054 using INHERITED = ProgramVisitor;
1055};
1056
1057WGSLCodeGenerator::ProgramRequirements resolve_program_requirements(const Program* program) {
1058 WGSLCodeGenerator::ProgramRequirements requirements;
1059
1060 for (const ProgramElement* e : program->elements()) {
1061 switch (e->kind()) {
1063 const FunctionDeclaration& decl = e->as<FunctionDefinition>().declaration();
1064
1065 FunctionDependencyResolver resolver(program, &decl, &requirements.fDependencies);
1066 requirements.fDependencies.set(&decl, resolver.resolve());
1067 break;
1068 }
1069 case ProgramElement::Kind::kGlobalVar: {
1070 const GlobalVarDeclaration& decl = e->as<GlobalVarDeclaration>();
1071 if (decl.varDeclaration().var()->modifierFlags().isPixelLocal()) {
1072 requirements.fPixelLocalExtension = true;
1073 }
1074 break;
1075 }
1076 default:
1077 break;
1078 }
1079 }
1080
1081 return requirements;
1082}
1083
1084void collect_pipeline_io_vars(const Program* program,
1086 ModifierFlag ioType) {
1087 for (const ProgramElement* e : program->elements()) {
1088 if (e->is<GlobalVarDeclaration>()) {
1089 const Variable* v = e->as<GlobalVarDeclaration>().varDeclaration().var();
1090 if (v->modifierFlags() & ioType) {
1091 ioVars->push_back(v);
1092 }
1093 } else if (e->is<InterfaceBlock>()) {
1094 const Variable* v = e->as<InterfaceBlock>().var();
1095 if (v->modifierFlags() & ioType) {
1096 ioVars->push_back(v);
1097 }
1098 }
1099 }
1100}
1101
1102bool is_in_global_uniforms(const Variable& var) {
1103 SkASSERT(var.storage() == VariableStorage::kGlobal);
1104 return var.modifierFlags().isUniform() &&
1105 !var.type().isOpaque() &&
1106 !var.interfaceBlock();
1107}
1108
1109} // namespace
1110
1112public:
1113 virtual ~LValue() = default;
1114
1115 // Returns a WGSL expression that loads from the lvalue with no side effects.
1116 // (e.g. `array[index].field`)
1117 virtual std::string load() = 0;
1118
1119 // Returns a WGSL statement that stores into the lvalue with no side effects.
1120 // (e.g. `array[index].field = the_passed_in_value_string;`)
1121 virtual std::string store(const std::string& value) = 0;
1122};
1123
1125public:
1126 // `name` must be a WGSL expression with no side-effects, which we can safely take the address
1127 // of. (e.g. `array[index].field` would be valid, but `array[Func()]` or `vector.x` are not.)
1128 PointerLValue(std::string name) : fName(std::move(name)) {}
1129
1130 std::string load() override {
1131 return fName;
1132 }
1133
1134 std::string store(const std::string& value) override {
1135 return fName + " = " + value + ";";
1136 }
1137
1138private:
1139 std::string fName;
1140};
1141
1143public:
1144 // `name` must be a WGSL expression with no side-effects that points to a single component of a
1145 // WGSL vector.
1146 VectorComponentLValue(std::string name) : fName(std::move(name)) {}
1147
1148 std::string load() override {
1149 return fName;
1150 }
1151
1152 std::string store(const std::string& value) override {
1153 return fName + " = " + value + ";";
1154 }
1155
1156private:
1157 std::string fName;
1158};
1159
1161public:
1162 // `name` must be a WGSL expression with no side-effects that points to a WGSL vector.
1163 SwizzleLValue(const Context& ctx, std::string name, const Type& t, const ComponentArray& c)
1164 : fContext(ctx)
1165 , fName(std::move(name))
1166 , fType(t)
1167 , fComponents(c) {
1168 // If the component array doesn't cover the entire value, we need to create masks for
1169 // writing back into the lvalue. For example, if the type is vec4 and the component array
1170 // holds `zx`, a GLSL assignment would look like:
1171 // name.zx = new_value;
1172 //
1173 // The equivalent WGSL assignment statement would look like:
1174 // name = vec4<f32>(new_value, name.xw).yzxw;
1175 //
1176 // This replaces name.zy with new_value.xy, and leaves name.xw at their original values.
1177 // By convention, we always put the new value first and the original values second; it might
1178 // be possible to find better arrangements which simplify the assignment overall, but we
1179 // don't attempt this.
1180 int fullSlotCount = fType.slotCount();
1181 SkASSERT(fullSlotCount <= 4);
1182
1183 // First, see which components are used.
1184 // The assignment swizzle must not reuse components.
1185 bool used[4] = {};
1186 for (int8_t component : fComponents) {
1187 SkASSERT(!used[component]);
1188 used[component] = true;
1189 }
1190
1191 // Any untouched components will need to be fetched from the original value.
1192 for (int index = 0; index < fullSlotCount; ++index) {
1193 if (!used[index]) {
1194 fUntouchedComponents.push_back(index);
1195 }
1196 }
1197
1198 // The reintegration swizzle needs to move the components back into their proper slots.
1199 fReintegrationSwizzle.resize(fullSlotCount);
1200 int reintegrateIndex = 0;
1201
1202 // This refills the untouched slots with the original values.
1203 auto refillUntouchedSlots = [&] {
1204 for (int index = 0; index < fullSlotCount; ++index) {
1205 if (!used[index]) {
1206 fReintegrationSwizzle[index] = reintegrateIndex++;
1207 }
1208 }
1209 };
1210
1211 // This places the new-value components into the proper slots.
1212 auto insertNewValuesIntoSlots = [&] {
1213 for (int index = 0; index < fComponents.size(); ++index) {
1214 fReintegrationSwizzle[fComponents[index]] = reintegrateIndex++;
1215 }
1216 };
1217
1218 // When reintegrating the untouched and new values, if the `x` slot is overwritten, we
1219 // reintegrate the new value first. Otherwise, we reintegrate the original value first.
1220 // This increases our odds of getting an identity swizzle for the reintegration.
1221 if (used[0]) {
1222 fReintegrateNewValueFirst = true;
1223 insertNewValuesIntoSlots();
1224 refillUntouchedSlots();
1225 } else {
1226 fReintegrateNewValueFirst = false;
1227 refillUntouchedSlots();
1228 insertNewValuesIntoSlots();
1229 }
1230 }
1231
1232 std::string load() override {
1233 return fName + "." + Swizzle::MaskString(fComponents);
1234 }
1235
1236 std::string store(const std::string& value) override {
1237 // `variable = `
1238 std::string result = fName;
1239 result += " = ";
1240
1241 if (fUntouchedComponents.empty()) {
1242 // `(new_value);`
1243 result += '(';
1244 result += value;
1245 result += ")";
1246 } else if (fReintegrateNewValueFirst) {
1247 // `vec4<f32>((new_value), `
1248 result += to_wgsl_type(fContext, fType);
1249 result += "((";
1250 result += value;
1251 result += "), ";
1252
1253 // `variable.yz)`
1254 result += fName;
1255 result += '.';
1256 result += Swizzle::MaskString(fUntouchedComponents);
1257 result += ')';
1258 } else {
1259 // `vec4<f32>(variable.yz`
1260 result += to_wgsl_type(fContext, fType);
1261 result += '(';
1262 result += fName;
1263 result += '.';
1264 result += Swizzle::MaskString(fUntouchedComponents);
1265
1266 // `, (new_value))`
1267 result += ", (";
1268 result += value;
1269 result += "))";
1270 }
1271
1272 if (!Swizzle::IsIdentity(fReintegrationSwizzle)) {
1273 // `.wzyx`
1274 result += '.';
1275 result += Swizzle::MaskString(fReintegrationSwizzle);
1276 }
1277
1278 return result + ';';
1279 }
1280
1281private:
1282 const Context& fContext;
1283 std::string fName;
1284 const Type& fType;
1285 ComponentArray fComponents;
1286 ComponentArray fUntouchedComponents;
1287 ComponentArray fReintegrationSwizzle;
1288 bool fReintegrateNewValueFirst = false;
1289};
1290
1292 // The resources of a WGSL program are structured in the following way:
1293 // - Stage attribute inputs and outputs are bundled inside synthetic structs called
1294 // VSIn/VSOut/FSIn/FSOut/CSIn.
1295 // - All uniform and storage type resources are declared in global scope.
1296 this->preprocessProgram();
1297
1298 {
1299 AutoOutputStream outputToHeader(this, &fHeader, &fIndentation);
1300 this->writeEnables();
1301 this->writeStageInputStruct();
1302 this->writeStageOutputStruct();
1303 this->writeUniformsAndBuffers();
1304 this->writeNonBlockUniformsForTests();
1305 }
1306 StringStream body;
1307 {
1308 // Emit the program body.
1309 AutoOutputStream outputToBody(this, &body, &fIndentation);
1310 const FunctionDefinition* mainFunc = nullptr;
1311 for (const ProgramElement* e : fProgram.elements()) {
1312 this->writeProgramElement(*e);
1313
1314 if (e->is<FunctionDefinition>()) {
1315 const FunctionDefinition& func = e->as<FunctionDefinition>();
1316 if (func.declaration().isMain()) {
1317 mainFunc = &func;
1318 }
1319 }
1320 }
1321
1322 // At the bottom of the program body, emit the entrypoint function.
1323 // The entrypoint relies on state that has been collected while we emitted the rest of the
1324 // program, so it's important to do it last to make sure we don't miss anything.
1325 if (mainFunc) {
1326 this->writeEntryPoint(*mainFunc);
1327 }
1328 }
1329
1330 write_stringstream(fHeader, *fOut);
1331 write_stringstream(body, *fOut);
1332
1333 this->writeUniformPolyfills();
1334
1335 return fContext.fErrors->errorCount() == 0;
1336}
1337
1338void WGSLCodeGenerator::writeUniformPolyfills() {
1339 // If we didn't encounter any uniforms that need polyfilling, there is nothing to do.
1340 if (fFieldPolyfillMap.empty()) {
1341 return;
1342 }
1343
1344 // We store the list of polyfilled fields as pointers in a hash-map, so the order can be
1345 // inconsistent across runs. For determinism, we sort the polyfilled objects by name here.
1347 orderedFields.reserve_exact(fFieldPolyfillMap.count());
1348
1349 fFieldPolyfillMap.foreach([&](const FieldPolyfillMap::Pair& pair) {
1350 orderedFields.push_back(&pair);
1351 });
1352
1353 std::sort(orderedFields.begin(),
1354 orderedFields.end(),
1355 [](const FieldPolyfillMap::Pair* a, const FieldPolyfillMap::Pair* b) {
1356 return a->second.fReplacementName < b->second.fReplacementName;
1357 });
1358
1359 THashSet<const Type*> writtenArrayElementPolyfill;
1360 bool writtenUniformMatrixPolyfill[5][5] = {}; // m[column][row] for each matrix type
1361 bool writtenUniformRowPolyfill[5] = {}; // for each matrix row-size
1362 bool anyFieldAccessed = false;
1363 for (const FieldPolyfillMap::Pair* pair : orderedFields) {
1364 const auto& [field, info] = *pair;
1365 const Type* fieldType = field->fType;
1366 const Layout* fieldLayout = &field->fLayout;
1367
1368 if (info.fIsArray) {
1369 fieldType = &fieldType->componentType();
1370 if (!writtenArrayElementPolyfill.contains(fieldType)) {
1371 writtenArrayElementPolyfill.add(fieldType);
1372 this->write("struct _skArrayElement_");
1373 this->write(fieldType->abbreviatedName());
1374 this->writeLine(" {");
1375
1376 if (info.fIsMatrix) {
1377 // Create a struct representing the array containing std140-padded matrices.
1378 this->write(" e : _skMatrix");
1379 this->write(std::to_string(fieldType->columns()));
1380 this->writeLine(std::to_string(fieldType->rows()));
1381 } else {
1382 // Create a struct representing the array with extra padding between elements.
1383 this->write(" @size(16) e : ");
1384 this->writeLine(to_wgsl_type(fContext, *fieldType, fieldLayout));
1385 }
1386 this->writeLine("};");
1387 }
1388 }
1389
1390 if (info.fIsMatrix) {
1391 // Create structs representing the matrix as an array of vectors, whether or not the
1392 // matrix is ever accessed by the SkSL. (The struct itself is mentioned in the list of
1393 // uniforms.)
1394 int c = fieldType->columns();
1395 int r = fieldType->rows();
1396 if (!writtenUniformRowPolyfill[r]) {
1397 writtenUniformRowPolyfill[r] = true;
1398
1399 this->write("struct _skRow");
1400 this->write(std::to_string(r));
1401 this->writeLine(" {");
1402 this->write(" @size(16) r : vec");
1403 this->write(std::to_string(r));
1404 this->write("<");
1405 this->write(to_wgsl_type(fContext, fieldType->componentType(), fieldLayout));
1406 this->writeLine(">");
1407 this->writeLine("};");
1408 }
1409
1410 if (!writtenUniformMatrixPolyfill[c][r]) {
1411 writtenUniformMatrixPolyfill[c][r] = true;
1412
1413 this->write("struct _skMatrix");
1414 this->write(std::to_string(c));
1415 this->write(std::to_string(r));
1416 this->writeLine(" {");
1417 this->write(" c : array<_skRow");
1418 this->write(std::to_string(r));
1419 this->write(", ");
1420 this->write(std::to_string(c));
1421 this->writeLine(">");
1422 this->writeLine("};");
1423 }
1424 }
1425
1426 // We create a polyfill variable only if the uniform was actually accessed.
1427 if (!info.fWasAccessed) {
1428 continue;
1429 }
1430 anyFieldAccessed = true;
1431 this->write("var<private> ");
1432 this->write(info.fReplacementName);
1433 this->write(": ");
1434
1435 const Type& interfaceBlockType = info.fInterfaceBlock->var()->type();
1436 if (interfaceBlockType.isArray()) {
1437 this->write("array<");
1438 this->write(to_wgsl_type(fContext, *field->fType, fieldLayout));
1439 this->write(", ");
1440 this->write(std::to_string(interfaceBlockType.columns()));
1441 this->write(">");
1442 } else {
1443 this->write(to_wgsl_type(fContext, *field->fType, fieldLayout));
1444 }
1445 this->writeLine(";");
1446 }
1447
1448 // If no fields were actually accessed, _skInitializePolyfilledUniforms will not be called and
1449 // we can avoid emitting an empty, dead function.
1450 if (!anyFieldAccessed) {
1451 return;
1452 }
1453
1454 this->writeLine("fn _skInitializePolyfilledUniforms() {");
1455 ++fIndentation;
1456
1457 for (const FieldPolyfillMap::Pair* pair : orderedFields) {
1458 // Only initialize a polyfill global if the uniform was actually accessed.
1459 const auto& [field, info] = *pair;
1460 if (!info.fWasAccessed) {
1461 continue;
1462 }
1463
1464 // Synthesize the name of this uniform variable
1465 std::string_view instanceName = info.fInterfaceBlock->instanceName();
1466 const Type& interfaceBlockType = info.fInterfaceBlock->var()->type();
1467 if (instanceName.empty()) {
1468 instanceName = fInterfaceBlockNameMap[&interfaceBlockType.componentType()];
1469 }
1470
1471 // Initialize the global variable associated with this uniform.
1472 // If the interface block is arrayed, the associated global will be arrayed as well.
1473 int numIBElements = interfaceBlockType.isArray() ? interfaceBlockType.columns() : 1;
1474 for (int ibIdx = 0; ibIdx < numIBElements; ++ibIdx) {
1475 this->write(info.fReplacementName);
1476 if (interfaceBlockType.isArray()) {
1477 this->write("[");
1478 this->write(std::to_string(ibIdx));
1479 this->write("]");
1480 }
1481 this->write(" = ");
1482
1483 const Type* fieldType = field->fType;
1484 const Layout* fieldLayout = &field->fLayout;
1485
1486 int numArrayElements;
1487 if (info.fIsArray) {
1488 this->write(to_wgsl_type(fContext, *fieldType, fieldLayout));
1489 this->write("(");
1490 numArrayElements = fieldType->columns();
1491 fieldType = &fieldType->componentType();
1492 } else {
1493 numArrayElements = 1;
1494 }
1495
1496 auto arraySeparator = String::Separator();
1497 for (int arrayIdx = 0; arrayIdx < numArrayElements; arrayIdx++) {
1498 this->write(arraySeparator());
1499
1500 std::string fieldName{instanceName};
1501 if (interfaceBlockType.isArray()) {
1502 fieldName += '[';
1503 fieldName += std::to_string(ibIdx);
1504 fieldName += ']';
1505 }
1506 fieldName += '.';
1507 fieldName += this->assembleName(field->fName);
1508
1509 if (info.fIsArray) {
1510 fieldName += '[';
1511 fieldName += std::to_string(arrayIdx);
1512 fieldName += "].e";
1513 }
1514
1515 if (info.fIsMatrix) {
1516 this->write(to_wgsl_type(fContext, *fieldType, fieldLayout));
1517 this->write("(");
1518 int numColumns = fieldType->columns();
1519 auto matrixSeparator = String::Separator();
1520 for (int column = 0; column < numColumns; column++) {
1521 this->write(matrixSeparator());
1522 this->write(fieldName);
1523 this->write(".c[");
1524 this->write(std::to_string(column));
1525 this->write("].r");
1526 }
1527 this->write(")");
1528 } else {
1529 this->write(fieldName);
1530 }
1531 }
1532
1533 if (info.fIsArray) {
1534 this->write(")");
1535 }
1536
1537 this->writeLine(";");
1538 }
1539 }
1540
1541 --fIndentation;
1542 this->writeLine("}");
1543}
1544
1545
1546void WGSLCodeGenerator::preprocessProgram() {
1547 fRequirements = resolve_program_requirements(&fProgram);
1548 collect_pipeline_io_vars(&fProgram, &fPipelineInputs, ModifierFlag::kIn);
1549 collect_pipeline_io_vars(&fProgram, &fPipelineOutputs, ModifierFlag::kOut);
1550}
1551
1552void WGSLCodeGenerator::write(std::string_view s) {
1553 if (s.empty()) {
1554 return;
1555 }
1556#if defined(SK_DEBUG) || defined(SKSL_STANDALONE)
1557 if (fAtLineStart) {
1558 for (int i = 0; i < fIndentation; i++) {
1559 fOut->writeText(" ");
1560 }
1561 }
1562#endif
1563 fOut->writeText(std::string(s).c_str());
1564 fAtLineStart = false;
1565}
1566
1567void WGSLCodeGenerator::writeLine(std::string_view s) {
1568 this->write(s);
1569 fOut->writeText("\n");
1570 fAtLineStart = true;
1571}
1572
1573void WGSLCodeGenerator::finishLine() {
1574 if (!fAtLineStart) {
1575 this->writeLine();
1576 }
1577}
1578
1579std::string WGSLCodeGenerator::assembleName(std::string_view name) {
1580 if (name.empty()) {
1581 // WGSL doesn't allow anonymous function parameters.
1582 return "_skAnonymous" + std::to_string(fScratchCount++);
1583 }
1584 // Add `R_` before reserved names to avoid any potential reserved-word conflict.
1585 return (skstd::starts_with(name, "_sk") ||
1586 skstd::starts_with(name, "R_") ||
1587 is_reserved_word(name))
1588 ? std::string("R_") + std::string(name)
1589 : std::string(name);
1590}
1591
1592void WGSLCodeGenerator::writeVariableDecl(const Layout& layout,
1593 const Type& type,
1594 std::string_view name,
1595 Delimiter delimiter) {
1596 this->write(this->assembleName(name));
1597 this->write(": " + to_wgsl_type(fContext, type, &layout));
1598 this->writeLine(delimiter_to_str(delimiter));
1599}
1600
1601void WGSLCodeGenerator::writePipelineIODeclaration(const Layout& layout,
1602 const Type& type,
1603 std::string_view name,
1604 Delimiter delimiter) {
1605 // In WGSL, an entry-point IO parameter is "one of either a built-in value or assigned a
1606 // location". However, some SkSL declarations, specifically sk_FragColor, can contain both a
1607 // location and a builtin modifier. In addition, WGSL doesn't have a built-in equivalent for
1608 // sk_FragColor as it relies on the user-defined location for a render target.
1609 //
1610 // Instead of special-casing sk_FragColor, we just give higher precedence to a location modifier
1611 // if a declaration happens to both have a location and it's a built-in.
1612 //
1613 // Also see:
1614 // https://www.w3.org/TR/WGSL/#input-output-locations
1615 // https://www.w3.org/TR/WGSL/#attribute-location
1616 // https://www.w3.org/TR/WGSL/#builtin-inputs-outputs
1617 if (layout.fLocation >= 0) {
1618 this->writeUserDefinedIODecl(layout, type, name, delimiter);
1619 return;
1620 }
1621 if (layout.fBuiltin >= 0) {
1622 if (layout.fBuiltin == SK_POINTSIZE_BUILTIN) {
1623 // WebGPU does not support the point-size builtin, but we silently replace it with a
1624 // global variable when it is used, instead of reporting an error.
1625 return;
1626 }
1627 auto builtin = builtin_from_sksl_name(layout.fBuiltin);
1628 if (builtin.has_value()) {
1629 this->writeBuiltinIODecl(type, name, *builtin, delimiter);
1630 return;
1631 }
1632 }
1633 fContext.fErrors->error(Position(), "declaration '" + std::string(name) + "' is not supported");
1634}
1635
1636void WGSLCodeGenerator::writeUserDefinedIODecl(const Layout& layout,
1637 const Type& type,
1638 std::string_view name,
1639 Delimiter delimiter) {
1640 this->write("@location(" + std::to_string(layout.fLocation) + ") ");
1641
1642 // @blend_src is only allowed when doing dual-source blending, and only on color attachment 0.
1643 if (layout.fLocation == 0 && layout.fIndex >= 0 && fProgram.fInterface.fOutputSecondaryColor) {
1644 this->write("@blend_src(" + std::to_string(layout.fIndex) + ") ");
1645 }
1646
1647 // "User-defined IO of scalar or vector integer type must always be specified as
1648 // @interpolate(flat)" (see https://www.w3.org/TR/WGSL/#interpolation)
1649 if (type.isInteger() || (type.isVector() && type.componentType().isInteger())) {
1650 this->write("@interpolate(flat) ");
1651 }
1652
1653 this->writeVariableDecl(layout, type, name, delimiter);
1654}
1655
1656void WGSLCodeGenerator::writeBuiltinIODecl(const Type& type,
1657 std::string_view name,
1658 Builtin builtin,
1659 Delimiter delimiter) {
1660 this->write(wgsl_builtin_name(builtin));
1661 this->write(" ");
1662 this->write(this->assembleName(name));
1663 this->write(": ");
1664 this->write(wgsl_builtin_type(builtin));
1665 this->writeLine(delimiter_to_str(delimiter));
1666}
1667
1668void WGSLCodeGenerator::writeFunction(const FunctionDefinition& f) {
1669 const FunctionDeclaration& decl = f.declaration();
1670 fHasUnconditionalReturn = false;
1671 fConditionalScopeDepth = 0;
1672
1673 SkASSERT(!fAtFunctionScope);
1674 fAtFunctionScope = true;
1675
1676 // WGSL parameters are immutable and are considered as taking no storage, but SkSL parameters
1677 // are real variables. To work around this, we make var-based copies of parameters. It's
1678 // wasteful to make a copy of every single parameter--even if the compiler can eventually
1679 // optimize them all away, that takes time and generates bloated code. So, we only make
1680 // parameter copies if the variable is actually written-to.
1681 STArray<32, bool> paramNeedsDedicatedStorage;
1682 paramNeedsDedicatedStorage.push_back_n(decl.parameters().size(), true);
1683
1684 for (size_t index = 0; index < decl.parameters().size(); ++index) {
1685 const Variable& param = *decl.parameters()[index];
1686 if (param.type().isOpaque() || param.name().empty()) {
1687 // Opaque-typed or anonymous parameters don't need dedicated storage.
1688 paramNeedsDedicatedStorage[index] = false;
1689 continue;
1690 }
1691
1692 const ProgramUsage::VariableCounts counts = fProgram.fUsage->get(param);
1693 if ((param.modifierFlags() & ModifierFlag::kOut) || counts.fWrite == 0) {
1694 // Variables which are never written-to don't need dedicated storage.
1695 // Out-parameters are passed as pointers; the pointer itself is never modified, so
1696 // it doesn't need dedicated storage.
1697 paramNeedsDedicatedStorage[index] = false;
1698 }
1699 }
1700
1701 this->writeFunctionDeclaration(decl, paramNeedsDedicatedStorage);
1702 this->writeLine(" {");
1703 ++fIndentation;
1704
1705 // The parameters were given generic names like `_skParam1`, because WGSL parameters don't have
1706 // storage and are immutable. If mutability is required, we create variables here; otherwise, we
1707 // create properly-named `let` aliases.
1708 for (size_t index = 0; index < decl.parameters().size(); ++index) {
1709 if (paramNeedsDedicatedStorage[index]) {
1710 const Variable& param = *decl.parameters()[index];
1711 this->write("var ");
1712 this->write(this->assembleName(param.mangledName()));
1713 this->write(" = _skParam");
1714 this->write(std::to_string(index));
1715 this->writeLine(";");
1716 }
1717 }
1718
1719 this->writeBlock(f.body()->as<Block>());
1720
1721 // If fConditionalScopeDepth isn't zero, we have an unbalanced +1 or -1 when updating the depth.
1722 SkASSERT(fConditionalScopeDepth == 0);
1723 if (!fHasUnconditionalReturn && !f.declaration().returnType().isVoid()) {
1724 this->write("return ");
1725 this->write(to_wgsl_type(fContext, f.declaration().returnType()));
1726 this->writeLine("();");
1727 }
1728
1729 --fIndentation;
1730 this->writeLine("}");
1731
1732 SkASSERT(fAtFunctionScope);
1733 fAtFunctionScope = false;
1734}
1735
1736void WGSLCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& decl,
1737 SkSpan<const bool> paramNeedsDedicatedStorage) {
1738 this->write("fn ");
1739 if (decl.isMain()) {
1740 this->write("_skslMain(");
1741 } else {
1742 this->write(this->assembleName(decl.mangledName()));
1743 this->write("(");
1744 }
1745 auto separator = SkSL::String::Separator();
1746 if (this->writeFunctionDependencyParams(decl)) {
1747 separator(); // update the separator as parameters have been written
1748 }
1749 for (size_t index = 0; index < decl.parameters().size(); ++index) {
1750 this->write(separator());
1751
1752 const Variable& param = *decl.parameters()[index];
1753 if (param.type().isOpaque()) {
1754 SkASSERT(!paramNeedsDedicatedStorage[index]);
1755 if (param.type().isSampler()) {
1756 // Create parameters for both the texture and associated sampler.
1757 this->write(param.name());
1758 this->write(kTextureSuffix);
1759 this->write(": texture_2d<f32>, ");
1760 this->write(param.name());
1761 this->write(kSamplerSuffix);
1762 this->write(": sampler");
1763 } else {
1764 // Create a parameter for the opaque object.
1765 this->write(param.name());
1766 this->write(": ");
1767 this->write(to_wgsl_type(fContext, param.type(), &param.layout()));
1768 }
1769 } else {
1770 if (paramNeedsDedicatedStorage[index] || param.name().empty()) {
1771 // Create an unnamed parameter. If the parameter needs dedicated storage, it will
1772 // later be assigned a `var` in the function body. (If it's anonymous, a var isn't
1773 // needed.)
1774 this->write("_skParam");
1775 this->write(std::to_string(index));
1776 } else {
1777 // Use the name directly from the SkSL program.
1778 this->write(this->assembleName(param.name()));
1779 }
1780 this->write(": ");
1781 if (param.modifierFlags() & ModifierFlag::kOut) {
1782 // Declare an "out" function parameter as a pointer.
1783 this->write(to_ptr_type(fContext, param.type(), &param.layout()));
1784 } else {
1785 this->write(to_wgsl_type(fContext, param.type(), &param.layout()));
1786 }
1787 }
1788 }
1789 this->write(")");
1790 if (!decl.returnType().isVoid()) {
1791 this->write(" -> ");
1792 this->write(to_wgsl_type(fContext, decl.returnType()));
1793 }
1794}
1795
1796void WGSLCodeGenerator::writeEntryPoint(const FunctionDefinition& main) {
1797 SkASSERT(main.declaration().isMain());
1798 const ProgramKind programKind = fProgram.fConfig->fKind;
1799
1800#if defined(SKSL_STANDALONE)
1802 // Synthesize a basic entrypoint which just calls straight through to main.
1803 // This is only used by skslc and just needs to pass the WGSL validator; Skia won't ever
1804 // emit functions like this.
1805 this->writeLine("@fragment fn main(@location(0) _coords: vec2<f32>) -> "
1806 "@location(0) vec4<f32> {");
1807 ++fIndentation;
1808 this->writeLine("return _skslMain(_coords);");
1809 --fIndentation;
1810 this->writeLine("}");
1811 return;
1812 }
1813#endif
1814
1815 // The input and output parameters for a vertex/fragment stage entry point function have the
1816 // FSIn/FSOut/VSIn/VSOut/CSIn struct types that have been synthesized in generateCode(). An
1817 // entrypoint always has a predictable signature and acts as a trampoline to the user-defined
1818 // main function.
1820 this->write("@vertex");
1822 this->write("@fragment");
1824 this->write("@compute @workgroup_size(");
1825 this->write(std::to_string(fLocalSizeX));
1826 this->write(", ");
1827 this->write(std::to_string(fLocalSizeY));
1828 this->write(", ");
1829 this->write(std::to_string(fLocalSizeZ));
1830 this->write(")");
1831 } else {
1832 fContext.fErrors->error(Position(), "program kind not supported");
1833 return;
1834 }
1835
1836 this->write(" fn main(");
1837 // The stage input struct is a parameter passed to main().
1838 if (this->needsStageInputStruct()) {
1839 this->write("_stageIn: ");
1840 this->write(pipeline_struct_prefix(programKind));
1841 this->write("In");
1842 }
1843 // The stage output struct is returned from main().
1844 if (this->needsStageOutputStruct()) {
1845 this->write(") -> ");
1846 this->write(pipeline_struct_prefix(programKind));
1847 this->writeLine("Out {");
1848 } else {
1849 this->writeLine(") {");
1850 }
1851 // Initialize polyfilled matrix uniforms if any were used.
1852 fIndentation++;
1853 for (const auto& [field, info] : fFieldPolyfillMap) {
1854 if (info.fWasAccessed) {
1855 this->writeLine("_skInitializePolyfilledUniforms();");
1856 break;
1857 }
1858 }
1859 // Declare the stage output struct.
1860 if (this->needsStageOutputStruct()) {
1861 this->write("var _stageOut: ");
1862 this->write(pipeline_struct_prefix(programKind));
1863 this->writeLine("Out;");
1864 }
1865
1866#if defined(SKSL_STANDALONE)
1867 // We are compiling a Runtime Effect as a fragment shader, for testing purposes. We assign the
1868 // result from _skslMain into sk_FragColor if the user-defined main returns a color. This
1869 // doesn't actually matter, but it is more indicative of what a real program would do.
1870 // `addImplicitFragColorWrite` from Transform::FindAndDeclareBuiltinVariables has already
1871 // injected sk_FragColor into our stage outputs even if it wasn't explicitly referenced.
1873 if (main.declaration().returnType().matches(*fContext.fTypes.fHalf4)) {
1874 this->write("_stageOut.sk_FragColor = ");
1875 }
1876 }
1877#endif
1878
1879 // Generate a function call to the user-defined main.
1880 this->write("_skslMain(");
1881 auto separator = SkSL::String::Separator();
1882 WGSLFunctionDependencies* deps = fRequirements.fDependencies.find(&main.declaration());
1883 if (deps) {
1884 if (*deps & WGSLFunctionDependency::kPipelineInputs) {
1885 this->write(separator());
1886 this->write("_stageIn");
1887 }
1888 if (*deps & WGSLFunctionDependency::kPipelineOutputs) {
1889 this->write(separator());
1890 this->write("&_stageOut");
1891 }
1892 }
1893
1894#if defined(SKSL_STANDALONE)
1895 if (const Variable* v = main.declaration().getMainCoordsParameter()) {
1896 // We are compiling a Runtime Effect as a fragment shader, for testing purposes.
1897 // We need to synthesize a coordinates parameter, but the coordinates don't matter.
1899 const Type& type = v->type();
1900 if (!type.matches(*fContext.fTypes.fFloat2)) {
1901 fContext.fErrors->error(main.fPosition, "main function has unsupported parameter: " +
1902 type.description());
1903 return;
1904 }
1905 this->write(separator());
1906 this->write("/*fragcoord*/ vec2<f32>()");
1907 }
1908#endif
1909
1910 this->writeLine(");");
1911
1912 if (this->needsStageOutputStruct()) {
1913 // Return the stage output struct.
1914 this->writeLine("return _stageOut;");
1915 }
1916
1917 fIndentation--;
1918 this->writeLine("}");
1919}
1920
1921void WGSLCodeGenerator::writeStatement(const Statement& s) {
1922 switch (s.kind()) {
1923 case Statement::Kind::kBlock:
1924 this->writeBlock(s.as<Block>());
1925 break;
1926 case Statement::Kind::kBreak:
1927 this->writeLine("break;");
1928 break;
1930 this->writeLine("continue;");
1931 break;
1933 this->writeLine("discard;");
1934 break;
1935 case Statement::Kind::kDo:
1936 this->writeDoStatement(s.as<DoStatement>());
1937 break;
1938 case Statement::Kind::kExpression:
1939 this->writeExpressionStatement(*s.as<ExpressionStatement>().expression());
1940 break;
1941 case Statement::Kind::kFor:
1942 this->writeForStatement(s.as<ForStatement>());
1943 break;
1944 case Statement::Kind::kIf:
1945 this->writeIfStatement(s.as<IfStatement>());
1946 break;
1948 this->writeLine(";");
1949 break;
1950 case Statement::Kind::kReturn:
1951 this->writeReturnStatement(s.as<ReturnStatement>());
1952 break;
1953 case Statement::Kind::kSwitch:
1954 this->writeSwitchStatement(s.as<SwitchStatement>());
1955 break;
1956 case Statement::Kind::kSwitchCase:
1957 SkDEBUGFAIL("switch-case statements should only be present inside a switch");
1958 break;
1959 case Statement::Kind::kVarDeclaration:
1960 this->writeVarDeclaration(s.as<VarDeclaration>());
1961 break;
1962 }
1963}
1964
1965void WGSLCodeGenerator::writeStatements(const StatementArray& statements) {
1966 for (const auto& s : statements) {
1967 if (!s->isEmpty()) {
1968 this->writeStatement(*s);
1969 this->finishLine();
1970 }
1971 }
1972}
1973
1974void WGSLCodeGenerator::writeBlock(const Block& b) {
1975 // Write scope markers if this block is a scope, or if the block is empty (since we need to emit
1976 // something here to make the code valid).
1977 bool isScope = b.isScope() || b.isEmpty();
1978 if (isScope) {
1979 this->writeLine("{");
1980 fIndentation++;
1981 }
1982 this->writeStatements(b.children());
1983 if (isScope) {
1984 fIndentation--;
1985 this->writeLine("}");
1986 }
1987}
1988
1989void WGSLCodeGenerator::writeExpressionStatement(const Expression& expr) {
1990 // Any expression-related side effects must be emitted as separate statements when
1991 // `assembleExpression` is called.
1992 // The final result of the expression will be a variable, let-reference, or an expression with
1993 // no side effects (`foo + bar`). Discarding this result is safe, as the program never uses it.
1994 (void)this->assembleExpression(expr, Precedence::kStatement);
1995}
1996
1997void WGSLCodeGenerator::writeDoStatement(const DoStatement& s) {
1998 // Generate a loop structure like this:
1999 // loop {
2000 // body-statement;
2001 // continuing {
2002 // break if inverted-test-expression;
2003 // }
2004 // }
2005
2006 ++fConditionalScopeDepth;
2007
2008 std::unique_ptr<Expression> invertedTestExpr = PrefixExpression::Make(
2009 fContext, s.test()->fPosition, OperatorKind::LOGICALNOT, s.test()->clone());
2010
2011 this->writeLine("loop {");
2012 fIndentation++;
2013 this->writeStatement(*s.statement());
2014 this->finishLine();
2015
2016 this->writeLine("continuing {");
2017 fIndentation++;
2018 std::string breakIfExpr = this->assembleExpression(*invertedTestExpr, Precedence::kExpression);
2019 this->write("break if ");
2020 this->write(breakIfExpr);
2021 this->writeLine(";");
2022 fIndentation--;
2023 this->writeLine("}");
2024 fIndentation--;
2025 this->writeLine("}");
2026
2027 --fConditionalScopeDepth;
2028}
2029
2030void WGSLCodeGenerator::writeForStatement(const ForStatement& s) {
2031 // Generate a loop structure wrapped in an extra scope:
2032 // {
2033 // initializer-statement;
2034 // loop;
2035 // }
2036 // The outer scope is necessary to prevent the initializer-variable from leaking out into the
2037 // rest of the code. In practice, the generated code actually tends to be scoped even more
2038 // deeply, as the body-statement almost always contributes an extra block.
2039
2040 ++fConditionalScopeDepth;
2041
2042 if (s.initializer()) {
2043 this->writeLine("{");
2044 fIndentation++;
2045 this->writeStatement(*s.initializer());
2046 this->writeLine();
2047 }
2048
2049 this->writeLine("loop {");
2050 fIndentation++;
2051
2052 if (s.unrollInfo()) {
2053 if (s.unrollInfo()->fCount <= 0) {
2054 // Loops which are known to never execute don't need to be emitted at all.
2055 // (However, the front end should have already replaced this loop with a Nop.)
2056 } else {
2057 // Loops which are known to execute at least once can use this form:
2058 //
2059 // loop {
2060 // body-statement;
2061 // continuing {
2062 // next-expression;
2063 // break if inverted-test-expression;
2064 // }
2065 // }
2066
2067 this->writeStatement(*s.statement());
2068 this->finishLine();
2069 this->writeLine("continuing {");
2070 ++fIndentation;
2071
2072 if (s.next()) {
2073 this->writeExpressionStatement(*s.next());
2074 this->finishLine();
2075 }
2076
2077 if (s.test()) {
2078 std::unique_ptr<Expression> invertedTestExpr = PrefixExpression::Make(
2079 fContext, s.test()->fPosition, OperatorKind::LOGICALNOT, s.test()->clone());
2080
2081 std::string breakIfExpr =
2082 this->assembleExpression(*invertedTestExpr, Precedence::kExpression);
2083 this->write("break if ");
2084 this->write(breakIfExpr);
2085 this->writeLine(";");
2086 }
2087
2088 --fIndentation;
2089 this->writeLine("}");
2090 }
2091 } else {
2092 // Loops without a known execution count are emitted in this form:
2093 //
2094 // loop {
2095 // if test-expression {
2096 // body-statement;
2097 // } else {
2098 // break;
2099 // }
2100 // continuing {
2101 // next-expression;
2102 // }
2103 // }
2104
2105 if (s.test()) {
2106 std::string testExpr = this->assembleExpression(*s.test(), Precedence::kExpression);
2107 this->write("if ");
2108 this->write(testExpr);
2109 this->writeLine(" {");
2110
2111 fIndentation++;
2112 this->writeStatement(*s.statement());
2113 this->finishLine();
2114 fIndentation--;
2115
2116 this->writeLine("} else {");
2117
2118 fIndentation++;
2119 this->writeLine("break;");
2120 fIndentation--;
2121
2122 this->writeLine("}");
2123 }
2124 else {
2125 this->writeStatement(*s.statement());
2126 this->finishLine();
2127 }
2128
2129 if (s.next()) {
2130 this->writeLine("continuing {");
2131 fIndentation++;
2132 this->writeExpressionStatement(*s.next());
2133 this->finishLine();
2134 fIndentation--;
2135 this->writeLine("}");
2136 }
2137 }
2138
2139 // This matches an open-brace at the top of the loop.
2140 fIndentation--;
2141 this->writeLine("}");
2142
2143 if (s.initializer()) {
2144 // This matches an open-brace before the initializer-statement.
2145 fIndentation--;
2146 this->writeLine("}");
2147 }
2148
2149 --fConditionalScopeDepth;
2150}
2151
2152void WGSLCodeGenerator::writeIfStatement(const IfStatement& s) {
2153 ++fConditionalScopeDepth;
2154
2155 std::string testExpr = this->assembleExpression(*s.test(), Precedence::kExpression);
2156 this->write("if ");
2157 this->write(testExpr);
2158 this->writeLine(" {");
2159 fIndentation++;
2160 this->writeStatement(*s.ifTrue());
2161 this->finishLine();
2162 fIndentation--;
2163 if (s.ifFalse()) {
2164 this->writeLine("} else {");
2165 fIndentation++;
2166 this->writeStatement(*s.ifFalse());
2167 this->finishLine();
2168 fIndentation--;
2169 }
2170 this->writeLine("}");
2171
2172 --fConditionalScopeDepth;
2173}
2174
2175void WGSLCodeGenerator::writeReturnStatement(const ReturnStatement& s) {
2176 fHasUnconditionalReturn |= (fConditionalScopeDepth == 0);
2177
2178 std::string expr = s.expression()
2179 ? this->assembleExpression(*s.expression(), Precedence::kExpression)
2180 : std::string();
2181 this->write("return ");
2182 this->write(expr);
2183 this->write(";");
2184}
2185
2186void WGSLCodeGenerator::writeSwitchCaseList(SkSpan<const SwitchCase* const> cases) {
2187 auto separator = SkSL::String::Separator();
2188 for (const SwitchCase* const sc : cases) {
2189 this->write(separator());
2190 if (sc->isDefault()) {
2191 this->write("default");
2192 } else {
2193 this->write(std::to_string(sc->value()));
2194 }
2195 }
2196}
2197
2198void WGSLCodeGenerator::writeSwitchCases(SkSpan<const SwitchCase* const> cases) {
2199 if (!cases.empty()) {
2200 // Only the last switch-case should have a non-empty statement.
2201 SkASSERT(std::all_of(cases.begin(), std::prev(cases.end()), [](const SwitchCase* sc) {
2202 return sc->statement()->isEmpty();
2203 }));
2204
2205 // Emit the cases in a comma-separated list.
2206 this->write("case ");
2207 this->writeSwitchCaseList(cases);
2208 this->writeLine(" {");
2209 ++fIndentation;
2210
2211 // Emit the switch-case body.
2212 this->writeStatement(*cases.back()->statement());
2213 this->finishLine();
2214
2215 --fIndentation;
2216 this->writeLine("}");
2217 }
2218}
2219
2220void WGSLCodeGenerator::writeEmulatedSwitchFallthroughCases(SkSpan<const SwitchCase* const> cases,
2221 std::string_view switchValue) {
2222 // There's no need for fallthrough handling unless we actually have multiple case blocks.
2223 if (cases.size() < 2) {
2224 this->writeSwitchCases(cases);
2225 return;
2226 }
2227
2228 // Match against the entire case group.
2229 this->write("case ");
2230 this->writeSwitchCaseList(cases);
2231 this->writeLine(" {");
2232 ++fIndentation;
2233
2234 std::string fallthroughVar = this->writeScratchVar(*fContext.fTypes.fBool, "false");
2235 const size_t secondToLastCaseIndex = cases.size() - 2;
2236 const size_t lastCaseIndex = cases.size() - 1;
2237
2238 for (size_t index = 0; index < cases.size(); ++index) {
2239 const SwitchCase& sc = *cases[index];
2240 if (index < lastCaseIndex) {
2241 // The default case must come last in SkSL, and this case isn't the last one, so it
2242 // can't possibly be the default.
2243 SkASSERT(!sc.isDefault());
2244
2245 this->write("if ");
2246 if (index > 0) {
2247 this->write(fallthroughVar);
2248 this->write(" || ");
2249 }
2250 this->write(switchValue);
2251 this->write(" == ");
2252 this->write(std::to_string(sc.value()));
2253 this->writeLine(" {");
2254 fIndentation++;
2255
2256 // We write the entire case-block statement here, and then set `switchFallthrough`
2257 // to 1. If the case-block had a break statement in it, we break out of the outer
2258 // for-loop entirely, meaning the `switchFallthrough` assignment never occurs, nor
2259 // does any code after it inside the switch. We've forbidden `continue` statements
2260 // inside switch case-blocks entirely, so we don't need to consider their effect on
2261 // control flow; see the Finalizer in FunctionDefinition::Convert.
2262 this->writeStatement(*sc.statement());
2263 this->finishLine();
2264
2265 if (index < secondToLastCaseIndex) {
2266 // Set a variable to indicate falling through to the next block. The very last
2267 // case-block is reached by process of elimination and doesn't need this
2268 // variable, so we don't actually need to set it if we are on the second-to-last
2269 // case block.
2270 this->write(fallthroughVar);
2271 this->write(" = true; ");
2272 }
2273 this->writeLine("// fallthrough");
2274
2275 fIndentation--;
2276 this->writeLine("}");
2277 } else {
2278 // This is the final case. Since it's always last, we can just dump in the code.
2279 // (If we didn't match any of the other values, we must have matched this one by
2280 // process of elimination. If we did match one of the other values, we either hit a
2281 // `break` statement earlier--and won't get this far--or we're falling through.)
2282 this->writeStatement(*sc.statement());
2283 this->finishLine();
2284 }
2285 }
2286
2287 --fIndentation;
2288 this->writeLine("}");
2289}
2290
2291void WGSLCodeGenerator::writeSwitchStatement(const SwitchStatement& s) {
2292 // WGSL supports the `switch` statement in a limited capacity. A default case must always be
2293 // specified. Each switch-case must be scoped inside braces. Fallthrough is not supported; a
2294 // trailing break is implied at the end of each switch-case block. (Explicit breaks are also
2295 // allowed.) One minor improvement over a traditional switch is that switch-cases take a list
2296 // of values to match, instead of a single value:
2297 // case 1, 2 { foo(); }
2298 // case 3, default { bar(); }
2299 //
2300 // We will use the native WGSL switch statement for any switch-cases in the SkSL which can be
2301 // made to conform to these limitations. The remaining cases which cannot conform will be
2302 // emulated with if-else blocks (similar to our GLSL ES2 switch-statement emulation path). This
2303 // should give us good performance in the common case, since most switches naturally conform.
2304
2305 // First, let's emit the switch itself.
2306 std::string valueExpr = this->writeNontrivialScratchLet(*s.value(), Precedence::kExpression);
2307 this->write("switch ");
2308 this->write(valueExpr);
2309 this->writeLine(" {");
2310 ++fIndentation;
2311
2312 // Now let's go through the switch-cases, and emit the ones that don't fall through.
2313 TArray<const SwitchCase*> nativeCases;
2314 TArray<const SwitchCase*> fallthroughCases;
2315 bool previousCaseFellThrough = false;
2316 bool foundNativeDefault = false;
2317 [[maybe_unused]] bool foundFallthroughDefault = false;
2318
2319 const int lastSwitchCaseIdx = s.cases().size() - 1;
2320 for (int index = 0; index <= lastSwitchCaseIdx; ++index) {
2321 const SwitchCase& sc = s.cases()[index]->as<SwitchCase>();
2322
2323 if (sc.statement()->isEmpty()) {
2324 // This is a `case X:` that immediately falls through to the next case.
2325 // If we aren't already falling through, we can handle this via a comma-separated list.
2326 if (previousCaseFellThrough) {
2327 fallthroughCases.push_back(&sc);
2328 foundFallthroughDefault |= sc.isDefault();
2329 } else {
2330 nativeCases.push_back(&sc);
2331 foundNativeDefault |= sc.isDefault();
2332 }
2333 continue;
2334 }
2335
2336 if (index == lastSwitchCaseIdx || Analysis::SwitchCaseContainsUnconditionalExit(sc)) {
2337 // This is a `case X:` that never falls through.
2338 if (previousCaseFellThrough) {
2339 // Because the previous cases fell through, we can't use a native switch-case here.
2340 fallthroughCases.push_back(&sc);
2341 foundFallthroughDefault |= sc.isDefault();
2342
2343 this->writeEmulatedSwitchFallthroughCases(fallthroughCases, valueExpr);
2344 fallthroughCases.clear();
2345
2346 // Fortunately, we're no longer falling through blocks, so we might be able to use a
2347 // native switch-case list again.
2348 previousCaseFellThrough = false;
2349 } else {
2350 // Emit a native switch-case block with a comma-separated case list.
2351 nativeCases.push_back(&sc);
2352 foundNativeDefault |= sc.isDefault();
2353
2354 this->writeSwitchCases(nativeCases);
2355 nativeCases.clear();
2356 }
2357 continue;
2358 }
2359
2360 // This case falls through, so it will need to be handled via emulation.
2361 // If we have put together a collection of "native" cases (cases that fall through with no
2362 // actual case-body), we will need to slide them over into the fallthrough-case list.
2363 fallthroughCases.push_back_n(nativeCases.size(), nativeCases.data());
2364 nativeCases.clear();
2365
2366 fallthroughCases.push_back(&sc);
2367 foundFallthroughDefault |= sc.isDefault();
2368 previousCaseFellThrough = true;
2369 }
2370
2371 // Finish out the remaining switch-cases.
2372 this->writeSwitchCases(nativeCases);
2373 nativeCases.clear();
2374
2375 this->writeEmulatedSwitchFallthroughCases(fallthroughCases, valueExpr);
2376 fallthroughCases.clear();
2377
2378 // WGSL requires a default case.
2379 if (!foundNativeDefault && !foundFallthroughDefault) {
2380 this->writeLine("case default {}");
2381 }
2382
2383 --fIndentation;
2384 this->writeLine("}");
2385}
2386
2387void WGSLCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl) {
2388 std::string initialValue =
2389 varDecl.value() ? this->assembleExpression(*varDecl.value(), Precedence::kAssignment)
2390 : std::string();
2391
2392 if (varDecl.var()->modifierFlags().isConst() ||
2393 (fProgram.fUsage->get(*varDecl.var()).fWrite == 1 && varDecl.value())) {
2394 // Use `const` at global scope, or if the value is a compile-time constant.
2395 SkASSERTF(varDecl.value(), "an immutable variable must specify a value");
2396 const bool useConst =
2397 !fAtFunctionScope || Analysis::IsCompileTimeConstant(*varDecl.value());
2398 this->write(useConst ? "const " : "let ");
2399 } else {
2400 this->write("var ");
2401 }
2402 this->write(this->assembleName(varDecl.var()->mangledName()));
2403 this->write(": ");
2404 this->write(to_wgsl_type(fContext, varDecl.var()->type(), &varDecl.var()->layout()));
2405
2406 if (varDecl.value()) {
2407 this->write(" = ");
2408 this->write(initialValue);
2409 }
2410
2411 this->write(";");
2412}
2413
2414std::unique_ptr<WGSLCodeGenerator::LValue> WGSLCodeGenerator::makeLValue(const Expression& e) {
2415 if (e.is<VariableReference>()) {
2416 return std::make_unique<PointerLValue>(
2417 this->variableReferenceNameForLValue(e.as<VariableReference>()));
2418 }
2419 if (e.is<FieldAccess>()) {
2420 return std::make_unique<PointerLValue>(this->assembleFieldAccess(e.as<FieldAccess>()));
2421 }
2422 if (e.is<IndexExpression>()) {
2423 const IndexExpression& idx = e.as<IndexExpression>();
2424 if (idx.base()->type().isVector()) {
2425 // Rewrite indexed-swizzle accesses like `myVec.zyx[i]` into an index onto `myVec`.
2426 if (std::unique_ptr<Expression> rewrite =
2428 return std::make_unique<VectorComponentLValue>(
2429 this->assembleExpression(*rewrite, Precedence::kAssignment));
2430 } else {
2431 return std::make_unique<VectorComponentLValue>(this->assembleIndexExpression(idx));
2432 }
2433 } else {
2434 return std::make_unique<PointerLValue>(this->assembleIndexExpression(idx));
2435 }
2436 }
2437 if (e.is<Swizzle>()) {
2438 const Swizzle& swizzle = e.as<Swizzle>();
2439 if (swizzle.components().size() == 1) {
2440 return std::make_unique<VectorComponentLValue>(this->assembleSwizzle(swizzle));
2441 } else {
2442 return std::make_unique<SwizzleLValue>(
2443 fContext,
2444 this->assembleExpression(*swizzle.base(), Precedence::kAssignment),
2445 swizzle.base()->type(),
2446 swizzle.components());
2447 }
2448 }
2449
2450 fContext.fErrors->error(e.fPosition, "unsupported lvalue type");
2451 return nullptr;
2452}
2453
2454std::string WGSLCodeGenerator::assembleExpression(const Expression& e,
2455 Precedence parentPrecedence) {
2456 switch (e.kind()) {
2457 case Expression::Kind::kBinary:
2458 return this->assembleBinaryExpression(e.as<BinaryExpression>(), parentPrecedence);
2459
2460 case Expression::Kind::kConstructorCompound:
2461 return this->assembleConstructorCompound(e.as<ConstructorCompound>());
2462
2463 case Expression::Kind::kConstructorArrayCast:
2464 // This is a no-op, since WGSL 1.0 doesn't have any concept of precision qualifiers.
2465 // When we add support for f16, this will need to copy the array contents.
2466 return this->assembleExpression(*e.as<ConstructorArrayCast>().argument(),
2467 parentPrecedence);
2468
2469 case Expression::Kind::kConstructorArray:
2470 case Expression::Kind::kConstructorCompoundCast:
2471 case Expression::Kind::kConstructorScalarCast:
2472 case Expression::Kind::kConstructorSplat:
2473 case Expression::Kind::kConstructorStruct:
2474 return this->assembleAnyConstructor(e.asAnyConstructor());
2475
2476 case Expression::Kind::kConstructorDiagonalMatrix:
2477 return this->assembleConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>());
2478
2479 case Expression::Kind::kConstructorMatrixResize:
2480 return this->assembleConstructorMatrixResize(e.as<ConstructorMatrixResize>());
2481
2483 return "false";
2484
2485 case Expression::Kind::kFieldAccess:
2486 return this->assembleFieldAccess(e.as<FieldAccess>());
2487
2488 case Expression::Kind::kFunctionCall:
2489 return this->assembleFunctionCall(e.as<FunctionCall>(), parentPrecedence);
2490
2492 return this->assembleIndexExpression(e.as<IndexExpression>());
2493
2495 return this->assembleLiteral(e.as<Literal>());
2496
2497 case Expression::Kind::kPrefix:
2498 return this->assemblePrefixExpression(e.as<PrefixExpression>(), parentPrecedence);
2499
2500 case Expression::Kind::kPostfix:
2501 return this->assemblePostfixExpression(e.as<PostfixExpression>(), parentPrecedence);
2502
2503 case Expression::Kind::kSetting:
2504 return this->assembleExpression(*e.as<Setting>().toLiteral(fCaps), parentPrecedence);
2505
2506 case Expression::Kind::kSwizzle:
2507 return this->assembleSwizzle(e.as<Swizzle>());
2508
2509 case Expression::Kind::kTernary:
2510 return this->assembleTernaryExpression(e.as<TernaryExpression>(), parentPrecedence);
2511
2512 case Expression::Kind::kVariableReference:
2513 return this->assembleVariableReference(e.as<VariableReference>());
2514
2515 default:
2516 SkDEBUGFAILF("unsupported expression:\n%s", e.description().c_str());
2517 return {};
2518 }
2519}
2520
2521static bool is_nontrivial_expression(const Expression& expr) {
2522 // We consider a "trivial expression" one which we can repeat multiple times in the output
2523 // without being dangerous or spammy. We avoid emitting temporary variables for very trivial
2524 // expressions: literals, unadorned variable references, or constant vectors.
2525 if (expr.is<VariableReference>() || expr.is<Literal>()) {
2526 // Variables and literals are trivial; adding a let-declaration won't simplify anything.
2527 return false;
2528 }
2529 if (expr.type().isVector() && Analysis::IsConstantExpression(expr)) {
2530 // Compile-time constant vectors are also considered trivial; they're short and sweet.
2531 return false;
2532 }
2533 return true;
2534}
2535
2537 // WGSL always requires parentheses for some operators which are deemed to be ambiguous.
2538 // (8.19. Operator Precedence and Associativity)
2539 switch (op.kind()) {
2545 case OperatorKind::SHL:
2546 case OperatorKind::SHR:
2547 case OperatorKind::LT:
2548 case OperatorKind::GT:
2549 case OperatorKind::LTEQ:
2550 case OperatorKind::GTEQ:
2551 return true;
2552
2553 default:
2554 return false;
2555 }
2556}
2557
2558bool WGSLCodeGenerator::binaryOpNeedsComponentwiseMatrixPolyfill(const Type& left,
2559 const Type& right,
2560 Operator op) {
2561 switch (op.kind()) {
2563 // WGSL does not natively support componentwise matrix-op-matrix for division.
2564 if (left.isMatrix() && right.isMatrix()) {
2565 return true;
2566 }
2567 [[fallthrough]];
2568
2569 case OperatorKind::PLUS:
2571 // WGSL does not natively support componentwise matrix-op-scalar or scalar-op-matrix for
2572 // addition, subtraction or division.
2573 return (left.isMatrix() && right.isScalar()) ||
2574 (left.isScalar() && right.isMatrix());
2575
2576 default:
2577 return false;
2578 }
2579}
2580
2581std::string WGSLCodeGenerator::assembleBinaryExpression(const BinaryExpression& b,
2582 Precedence parentPrecedence) {
2583 return this->assembleBinaryExpression(*b.left(), b.getOperator(), *b.right(), b.type(),
2584 parentPrecedence);
2585}
2586
2587std::string WGSLCodeGenerator::assembleBinaryExpression(const Expression& left,
2588 Operator op,
2589 const Expression& right,
2590 const Type& resultType,
2591 Precedence parentPrecedence) {
2592 // If the operator is && or ||, we need to handle short-circuiting properly. Specifically, we
2593 // sometimes need to emit extra statements to paper over functionality that WGSL lacks, like
2594 // assignment in the middle of an expression. We need to guard those extra statements, to ensure
2595 // that they don't occur if the expression evaluation is short-circuited. Converting the
2596 // expression into an if-else block keeps the short-circuit property intact even when extra
2597 // statements are involved.
2598 // If the RHS doesn't have any side effects, then it's safe to just leave the expression as-is,
2599 // since we know that any possible extra statements are non-side-effecting.
2600 std::string expr;
2601 if (op.kind() == OperatorKind::LOGICALAND && Analysis::HasSideEffects(right)) {
2602 // Converts `left_expression && right_expression` into the following block:
2603
2604 // var _skTemp1: bool;
2605 // [[ prepare left_expression ]]
2606 // if left_expression {
2607 // [[ prepare right_expression ]]
2608 // _skTemp1 = right_expression;
2609 // } else {
2610 // _skTemp1 = false;
2611 // }
2612
2613 expr = this->writeScratchVar(resultType);
2614
2615 std::string leftExpr = this->assembleExpression(left, Precedence::kExpression);
2616 this->write("if ");
2617 this->write(leftExpr);
2618 this->writeLine(" {");
2619
2620 ++fIndentation;
2621 std::string rightExpr = this->assembleExpression(right, Precedence::kAssignment);
2622 this->write(expr);
2623 this->write(" = ");
2624 this->write(rightExpr);
2625 this->writeLine(";");
2626 --fIndentation;
2627
2628 this->writeLine("} else {");
2629
2630 ++fIndentation;
2631 this->write(expr);
2632 this->writeLine(" = false;");
2633 --fIndentation;
2634
2635 this->writeLine("}");
2636 return expr;
2637 }
2638
2639 if (op.kind() == OperatorKind::LOGICALOR && Analysis::HasSideEffects(right)) {
2640 // Converts `left_expression || right_expression` into the following block:
2641
2642 // var _skTemp1: bool;
2643 // [[ prepare left_expression ]]
2644 // if left_expression {
2645 // _skTemp1 = true;
2646 // } else {
2647 // [[ prepare right_expression ]]
2648 // _skTemp1 = right_expression;
2649 // }
2650
2651 expr = this->writeScratchVar(resultType);
2652
2653 std::string leftExpr = this->assembleExpression(left, Precedence::kExpression);
2654 this->write("if ");
2655 this->write(leftExpr);
2656 this->writeLine(" {");
2657
2658 ++fIndentation;
2659 this->write(expr);
2660 this->writeLine(" = true;");
2661 --fIndentation;
2662
2663 this->writeLine("} else {");
2664
2665 ++fIndentation;
2666 std::string rightExpr = this->assembleExpression(right, Precedence::kAssignment);
2667 this->write(expr);
2668 this->write(" = ");
2669 this->write(rightExpr);
2670 this->writeLine(";");
2671 --fIndentation;
2672
2673 this->writeLine("}");
2674 return expr;
2675 }
2676
2677 // Handle comma-expressions.
2678 if (op.kind() == OperatorKind::COMMA) {
2679 // The result from the left-expression is ignored, but its side effects must occur.
2680 this->assembleExpression(left, Precedence::kStatement);
2681
2682 // Evaluate the right side normally.
2683 return this->assembleExpression(right, parentPrecedence);
2684 }
2685
2686 // Handle assignment-expressions.
2687 if (op.isAssignment()) {
2688 std::unique_ptr<LValue> lvalue = this->makeLValue(left);
2689 if (!lvalue) {
2690 return "";
2691 }
2692
2693 if (op.kind() == OperatorKind::EQ) {
2694 // Evaluate the right-hand side of simple assignment (`a = b` --> `b`).
2695 expr = this->assembleExpression(right, Precedence::kAssignment);
2696 } else {
2697 // Evaluate the right-hand side of compound-assignment (`a += b` --> `a + b`).
2698 op = op.removeAssignment();
2699
2700 std::string lhs = lvalue->load();
2701 std::string rhs = this->assembleExpression(right, op.getBinaryPrecedence());
2702
2703 if (this->binaryOpNeedsComponentwiseMatrixPolyfill(left.type(), right.type(), op)) {
2704 if (is_nontrivial_expression(right)) {
2705 rhs = this->writeScratchLet(rhs);
2706 }
2707
2708 expr = this->assembleComponentwiseMatrixBinary(left.type(), right.type(),
2709 lhs, rhs, op);
2710 } else {
2711 expr = lhs + operator_name(op) + rhs;
2712 }
2713 }
2714
2715 // Emit the assignment statement (`a = a + b`).
2716 this->writeLine(lvalue->store(expr));
2717
2718 // Return the lvalue (`a`) as the result, since the value might be used by the caller.
2719 return lvalue->load();
2720 }
2721
2722 if (op.isEquality()) {
2723 return this->assembleEqualityExpression(left, right, op, parentPrecedence);
2724 }
2725
2726 Precedence precedence = op.getBinaryPrecedence();
2727 bool needParens = precedence >= parentPrecedence;
2729 precedence = Precedence::kParentheses;
2730 }
2731 if (needParens) {
2732 expr = "(";
2733 }
2734
2735 // If we are emitting `constant + constant`, this generally indicates that the values could not
2736 // be constant-folded. This happens when the values overflow or become nan. WGSL will refuse to
2737 // compile such expressions, as WGSL 1.0 has no infinity/nan support. However, the WGSL
2738 // compile-time check can be dodged by putting one side into a let-variable. This technically
2739 // gives us an indeterminate result, but the vast majority of backends will just calculate an
2740 // infinity or nan here, as we would expect. (skia:14385)
2741 bool bothSidesConstant = ConstantFolder::GetConstantValueOrNull(left) &&
2743
2744 std::string lhs = this->assembleExpression(left, precedence);
2745 std::string rhs = this->assembleExpression(right, precedence);
2746
2747 if (this->binaryOpNeedsComponentwiseMatrixPolyfill(left.type(), right.type(), op)) {
2748 if (bothSidesConstant || is_nontrivial_expression(left)) {
2749 lhs = this->writeScratchLet(lhs);
2750 }
2751 if (is_nontrivial_expression(right)) {
2752 rhs = this->writeScratchLet(rhs);
2753 }
2754
2755 expr += this->assembleComponentwiseMatrixBinary(left.type(), right.type(), lhs, rhs, op);
2756 } else {
2757 if (bothSidesConstant) {
2758 lhs = this->writeScratchLet(lhs);
2759 }
2760
2761 expr += lhs + operator_name(op) + rhs;
2762 }
2763
2764 if (needParens) {
2765 expr += ')';
2766 }
2767
2768 return expr;
2769}
2770
2771std::string WGSLCodeGenerator::assembleFieldAccess(const FieldAccess& f) {
2772 const Field* field = &f.base()->type().fields()[f.fieldIndex()];
2773 std::string expr;
2774
2775 if (FieldPolyfillInfo* polyfillInfo = fFieldPolyfillMap.find(field)) {
2776 // We found a matrix uniform. We are required to pass some matrix uniforms as array vectors,
2777 // since the std140 layout for a matrix assumes 4-column vectors for each row, and WGSL
2778 // tightly packs 2-column matrices. When emitting code, we replace the field-access
2779 // expression with a global variable which holds an unpacked version of the uniform.
2780 polyfillInfo->fWasAccessed = true;
2781
2782 // The polyfill can either be based directly onto a uniform in an interface block, or it
2783 // might be based on an index-expression onto a uniform if the interface block is arrayed.
2784 const Expression* base = f.base().get();
2785 const IndexExpression* indexExpr = nullptr;
2786 if (base->is<IndexExpression>()) {
2787 indexExpr = &base->as<IndexExpression>();
2788 base = indexExpr->base().get();
2789 }
2790
2791 SkASSERT(base->is<VariableReference>());
2792 expr = polyfillInfo->fReplacementName;
2793
2794 // If we had an index expression, we must append the index.
2795 if (indexExpr) {
2796 expr += '[';
2797 expr += this->assembleExpression(*indexExpr->index(), Precedence::kSequence);
2798 expr += ']';
2799 }
2800 return expr;
2801 }
2802
2803 switch (f.ownerKind()) {
2805 expr = this->assembleExpression(*f.base(), Precedence::kPostfix) + '.';
2806 break;
2807
2808 case FieldAccess::OwnerKind::kAnonymousInterfaceBlock:
2809 if (f.base()->is<VariableReference>() &&
2810 field->fLayout.fBuiltin != SK_POINTSIZE_BUILTIN) {
2811 expr = this->variablePrefix(*f.base()->as<VariableReference>().variable());
2812 }
2813 break;
2814 }
2815
2816 expr += this->assembleName(field->fName);
2817 return expr;
2818}
2819
2820static bool all_arguments_constant(const ExpressionArray& arguments) {
2821 // Returns true if all arguments in the ExpressionArray are compile-time constants. If we are
2822 // calling an intrinsic and all of its inputs are constant, but we didn't constant-fold it, this
2823 // generally indicates that constant-folding resulted in an infinity or nan. The WGSL compiler
2824 // will reject such an expression with a compile-time error. We can dodge the error, taking on
2825 // the risk of indeterminate behavior instead, by replacing one of the constant values with a
2826 // scratch let-variable. (skia:14385)
2827 for (const std::unique_ptr<Expression>& arg : arguments) {
2829 return false;
2830 }
2831 }
2832 return true;
2833}
2834
2835std::string WGSLCodeGenerator::assembleSimpleIntrinsic(std::string_view intrinsicName,
2836 const FunctionCall& call) {
2837 // Invoke the function, passing each function argument.
2838 std::string expr = std::string(intrinsicName);
2839 expr.push_back('(');
2840 const ExpressionArray& args = call.arguments();
2841 auto separator = SkSL::String::Separator();
2842 bool allConstant = all_arguments_constant(call.arguments());
2843 for (int index = 0; index < args.size(); ++index) {
2844 expr += separator();
2845
2846 std::string argument = this->assembleExpression(*args[index], Precedence::kSequence);
2847 if (args[index]->type().isAtomic()) {
2848 // WGSL passes atomic values to intrinsics as pointers.
2849 expr += '&';
2850 expr += argument;
2851 } else if (allConstant && index == 0) {
2852 // We can use a scratch-let for argument 0 to dodge WGSL overflow errors. (skia:14385)
2853 expr += this->writeScratchLet(argument);
2854 } else {
2855 expr += argument;
2856 }
2857 }
2858 expr.push_back(')');
2859
2860 if (call.type().isVoid()) {
2861 this->write(expr);
2862 this->writeLine(";");
2863 return std::string();
2864 } else {
2865 return this->writeScratchLet(expr);
2866 }
2867}
2868
2869std::string WGSLCodeGenerator::assembleVectorizedIntrinsic(std::string_view intrinsicName,
2870 const FunctionCall& call) {
2871 SkASSERT(!call.type().isVoid());
2872
2873 // Invoke the function, passing each function argument.
2874 std::string expr = std::string(intrinsicName);
2875 expr.push_back('(');
2876
2877 auto separator = SkSL::String::Separator();
2878 const ExpressionArray& args = call.arguments();
2879 bool returnsVector = call.type().isVector();
2880 bool allConstant = all_arguments_constant(call.arguments());
2881 for (int index = 0; index < args.size(); ++index) {
2882 expr += separator();
2883
2884 bool vectorize = returnsVector && args[index]->type().isScalar();
2885 if (vectorize) {
2886 expr += to_wgsl_type(fContext, call.type());
2887 expr.push_back('(');
2888 }
2889
2890 // We can use a scratch-let for argument 0 to dodge WGSL overflow errors. (skia:14385)
2891 std::string argument = this->assembleExpression(*args[index], Precedence::kSequence);
2892 expr += (allConstant && index == 0) ? this->writeScratchLet(argument)
2893 : argument;
2894 if (vectorize) {
2895 expr.push_back(')');
2896 }
2897 }
2898 expr.push_back(')');
2899
2900 return this->writeScratchLet(expr);
2901}
2902
2903std::string WGSLCodeGenerator::assembleUnaryOpIntrinsic(Operator op,
2904 const FunctionCall& call,
2905 Precedence parentPrecedence) {
2906 SkASSERT(!call.type().isVoid());
2907
2908 bool needParens = Precedence::kPrefix >= parentPrecedence;
2909
2910 std::string expr;
2911 if (needParens) {
2912 expr.push_back('(');
2913 }
2914
2915 expr += operator_name(op);
2916 expr += this->assembleExpression(*call.arguments()[0], Precedence::kPrefix);
2917
2918 if (needParens) {
2919 expr.push_back(')');
2920 }
2921
2922 return expr;
2923}
2924
2925std::string WGSLCodeGenerator::assembleBinaryOpIntrinsic(Operator op,
2926 const FunctionCall& call,
2927 Precedence parentPrecedence) {
2928 SkASSERT(!call.type().isVoid());
2929
2930 Precedence precedence = op.getBinaryPrecedence();
2931 bool needParens = precedence >= parentPrecedence ||
2933 std::string expr;
2934 if (needParens) {
2935 expr.push_back('(');
2936 }
2937
2938 // We can use a scratch-let for argument 0 to dodge WGSL overflow errors. (skia:14385)
2939 std::string argument = this->assembleExpression(*call.arguments()[0], precedence);
2940 expr += all_arguments_constant(call.arguments()) ? this->writeScratchLet(argument)
2941 : argument;
2942 expr += operator_name(op);
2943 expr += this->assembleExpression(*call.arguments()[1], precedence);
2944
2945 if (needParens) {
2946 expr.push_back(')');
2947 }
2948
2949 return expr;
2950}
2951
2952// Rewrite a WGSL intrinsic of the form "intrinsicName(in) -> struct" to the SkSL's
2953// "intrinsicName(in, outField) -> returnField", where outField and returnField are the names of the
2954// fields in the struct returned by the WGSL intrinsic.
2955std::string WGSLCodeGenerator::assembleOutAssignedIntrinsic(std::string_view intrinsicName,
2956 std::string_view returnField,
2957 std::string_view outField,
2958 const FunctionCall& call) {
2959 SkASSERT(call.type().componentType().isNumber());
2960 SkASSERT(call.arguments().size() == 2);
2961 SkASSERT(call.function().parameters()[1]->modifierFlags() & ModifierFlag::kOut);
2962
2963 std::string expr = std::string(intrinsicName);
2964 expr += "(";
2965
2966 // Invoke the intrinsic with the first parameter. Use a scratch-let if argument is a constant
2967 // to dodge WGSL overflow errors. (skia:14385)
2968 std::string argument = this->assembleExpression(*call.arguments()[0], Precedence::kSequence);
2969 expr += ConstantFolder::GetConstantValueOrNull(*call.arguments()[0])
2970 ? this->writeScratchLet(argument) : argument;
2971 expr += ")";
2972 // In WGSL the intrinsic returns a struct; assign it to a local so that its fields can be
2973 // accessed multiple times.
2974 expr = this->writeScratchLet(expr);
2975 expr += ".";
2976
2977 // Store the outField of `expr` to the intended "out" argument
2978 std::unique_ptr<LValue> lvalue = this->makeLValue(*call.arguments()[1]);
2979 if (!lvalue) {
2980 return "";
2981 }
2982 std::string outValue = expr;
2983 outValue += outField;
2984 this->writeLine(lvalue->store(outValue));
2985
2986 // And return the expression accessing the returnField.
2987 expr += returnField;
2988 return expr;
2989}
2990
2991std::string WGSLCodeGenerator::assemblePartialSampleCall(std::string_view functionName,
2992 const Expression& sampler,
2993 const Expression& coords) {
2994 // This function returns `functionName(inSampler_texture, inSampler_sampler, coords` without a
2995 // terminating comma or close-parenthesis. This allows the caller to add more arguments as
2996 // needed.
2997 SkASSERT(sampler.type().typeKind() == Type::TypeKind::kSampler);
2998 std::string expr = std::string(functionName) + '(';
2999 expr += this->assembleExpression(sampler, Precedence::kSequence);
3000 expr += kTextureSuffix;
3001 expr += ", ";
3002 expr += this->assembleExpression(sampler, Precedence::kSequence);
3003 expr += kSamplerSuffix;
3004 expr += ", ";
3005
3006 // Compute the sample coordinates, dividing out the Z if a vec3 was provided.
3007 SkASSERT(coords.type().isVector());
3008 if (coords.type().columns() == 3) {
3009 // The coordinates were passed as a vec3, so we need to emit `coords.xy / coords.z`.
3010 std::string vec3Coords = this->writeScratchLet(coords, Precedence::kMultiplicative);
3011 expr += vec3Coords + ".xy / " + vec3Coords + ".z";
3012 } else {
3013 // The coordinates should be a plain vec2; emit the expression as-is.
3014 SkASSERT(coords.type().columns() == 2);
3015 expr += this->assembleExpression(coords, Precedence::kSequence);
3016 }
3017
3018 return expr;
3019}
3020
3021std::string WGSLCodeGenerator::assembleComponentwiseMatrixBinary(const Type& leftType,
3022 const Type& rightType,
3023 const std::string& left,
3024 const std::string& right,
3025 Operator op) {
3026 bool leftIsMatrix = leftType.isMatrix();
3027 bool rightIsMatrix = rightType.isMatrix();
3028 const Type& matrixType = leftIsMatrix ? leftType : rightType;
3029
3030 std::string expr = to_wgsl_type(fContext, matrixType) + '(';
3031 auto separator = String::Separator();
3032 int columns = matrixType.columns();
3033 for (int c = 0; c < columns; ++c) {
3034 expr += separator();
3035 expr += left;
3036 if (leftIsMatrix) {
3037 expr += '[';
3038 expr += std::to_string(c);
3039 expr += ']';
3040 }
3041 expr += op.operatorName();
3042 expr += right;
3043 if (rightIsMatrix) {
3044 expr += '[';
3045 expr += std::to_string(c);
3046 expr += ']';
3047 }
3048 }
3049 return expr + ')';
3050}
3051
3052std::string WGSLCodeGenerator::assembleIntrinsicCall(const FunctionCall& call,
3053 IntrinsicKind kind,
3054 Precedence parentPrecedence) {
3055 // Be careful: WGSL 1.0 will reject any intrinsic calls which can be constant-evaluated to
3056 // infinity or nan with a compile error. If all arguments to an intrinsic are compile-time
3057 // constants (`all_arguments_constant`), it is safest to copy one argument into a scratch-let so
3058 // that the call will be seen as runtime-evaluated, which defuses the overflow checks.
3059 // Don't worry; a competent driver should still optimize it away.
3060
3061 const ExpressionArray& arguments = call.arguments();
3062 switch (kind) {
3063 case k_atan_IntrinsicKind: {
3064 const char* name = (arguments.size() == 1) ? "atan" : "atan2";
3065 return this->assembleSimpleIntrinsic(name, call);
3066 }
3067 case k_dFdx_IntrinsicKind:
3068 return this->assembleSimpleIntrinsic("dpdx", call);
3069
3070 case k_dFdy_IntrinsicKind:
3071 // TODO(b/294274678): apply RTFlip here
3072 return this->assembleSimpleIntrinsic("dpdy", call);
3073
3074 case k_dot_IntrinsicKind: {
3075 if (arguments[0]->type().isScalar()) {
3076 return this->assembleBinaryOpIntrinsic(OperatorKind::STAR, call, parentPrecedence);
3077 }
3078 return this->assembleSimpleIntrinsic("dot", call);
3079 }
3080 case k_equal_IntrinsicKind:
3081 return this->assembleBinaryOpIntrinsic(OperatorKind::EQEQ, call, parentPrecedence);
3082
3083 case k_faceforward_IntrinsicKind: {
3084 if (arguments[0]->type().isScalar()) {
3085 // select(-N, N, (I * Nref) < 0)
3086 std::string N = this->writeNontrivialScratchLet(*arguments[0],
3087 Precedence::kAssignment);
3088 return this->writeScratchLet(
3089 "select(-" + N + ", " + N + ", " +
3090 this->assembleBinaryExpression(*arguments[1],
3092 *arguments[2],
3093 arguments[1]->type(),
3094 Precedence::kRelational) +
3095 " < 0)");
3096 }
3097 return this->assembleSimpleIntrinsic("faceForward", call);
3098 }
3099 case k_frexp_IntrinsicKind:
3100 // SkSL frexp is "$genType fract = frexp($genType, out $genIType exp)" whereas WGSL
3101 // returns a struct with no out param: "let [fract, exp] = frexp($genType)".
3102 return this->assembleOutAssignedIntrinsic("frexp", "fract", "exp", call);
3103
3104 case k_greaterThan_IntrinsicKind:
3105 return this->assembleBinaryOpIntrinsic(OperatorKind::GT, call, parentPrecedence);
3106
3107 case k_greaterThanEqual_IntrinsicKind:
3108 return this->assembleBinaryOpIntrinsic(OperatorKind::GTEQ, call, parentPrecedence);
3109
3110 case k_inverse_IntrinsicKind:
3111 return this->assembleInversePolyfill(call);
3112
3113 case k_inversesqrt_IntrinsicKind:
3114 return this->assembleSimpleIntrinsic("inverseSqrt", call);
3115
3116 case k_lessThan_IntrinsicKind:
3117 return this->assembleBinaryOpIntrinsic(OperatorKind::LT, call, parentPrecedence);
3118
3119 case k_lessThanEqual_IntrinsicKind:
3120 return this->assembleBinaryOpIntrinsic(OperatorKind::LTEQ, call, parentPrecedence);
3121
3122 case k_matrixCompMult_IntrinsicKind: {
3123 // We use a scratch-let for arg0 to avoid the potential for WGSL overflow. (skia:14385)
3124 std::string arg0 = all_arguments_constant(arguments)
3125 ? this->writeScratchLet(*arguments[0], Precedence::kPostfix)
3126 : this->writeNontrivialScratchLet(*arguments[0], Precedence::kPostfix);
3127 std::string arg1 = this->writeNontrivialScratchLet(*arguments[1], Precedence::kPostfix);
3128 return this->writeScratchLet(
3129 this->assembleComponentwiseMatrixBinary(arguments[0]->type(),
3130 arguments[1]->type(),
3131 arg0,
3132 arg1,
3134 }
3135 case k_mix_IntrinsicKind: {
3136 const char* name = arguments[2]->type().componentType().isBoolean() ? "select" : "mix";
3137 return this->assembleVectorizedIntrinsic(name, call);
3138 }
3139 case k_mod_IntrinsicKind: {
3140 // WGSL has no intrinsic equivalent to `mod`. Synthesize `x - y * floor(x / y)`.
3141 // We can use a scratch-let on one side to dodge WGSL overflow errors. In practice, I
3142 // can't find any values of x or y which would overflow, but it can't hurt. (skia:14385)
3143 std::string arg0 = all_arguments_constant(arguments)
3144 ? this->writeScratchLet(*arguments[0], Precedence::kAdditive)
3145 : this->writeNontrivialScratchLet(*arguments[0], Precedence::kAdditive);
3146 std::string arg1 = this->writeNontrivialScratchLet(*arguments[1],
3147 Precedence::kAdditive);
3148 return this->writeScratchLet(arg0 + " - " + arg1 + " * floor(" +
3149 arg0 + " / " + arg1 + ")");
3150 }
3151
3152 case k_modf_IntrinsicKind:
3153 // SkSL modf is "$genType fract = modf($genType, out $genType whole)" whereas WGSL
3154 // returns a struct with no out param: "let [fract, whole] = modf($genType)".
3155 return this->assembleOutAssignedIntrinsic("modf", "fract", "whole", call);
3156
3157 case k_normalize_IntrinsicKind: {
3158 const char* name = arguments[0]->type().isScalar() ? "sign" : "normalize";
3159 return this->assembleSimpleIntrinsic(name, call);
3160 }
3161 case k_not_IntrinsicKind:
3162 return this->assembleUnaryOpIntrinsic(OperatorKind::LOGICALNOT, call, parentPrecedence);
3163
3164 case k_notEqual_IntrinsicKind:
3165 return this->assembleBinaryOpIntrinsic(OperatorKind::NEQ, call, parentPrecedence);
3166
3167 case k_packHalf2x16_IntrinsicKind:
3168 return this->assembleSimpleIntrinsic("pack2x16float", call);
3169
3170 case k_packSnorm2x16_IntrinsicKind:
3171 return this->assembleSimpleIntrinsic("pack2x16snorm", call);
3172
3173 case k_packSnorm4x8_IntrinsicKind:
3174 return this->assembleSimpleIntrinsic("pack4x8snorm", call);
3175
3176 case k_packUnorm2x16_IntrinsicKind:
3177 return this->assembleSimpleIntrinsic("pack2x16unorm", call);
3178
3179 case k_packUnorm4x8_IntrinsicKind:
3180 return this->assembleSimpleIntrinsic("pack4x8unorm", call);
3181
3182 case k_reflect_IntrinsicKind:
3183 if (arguments[0]->type().isScalar()) {
3184 // I - 2 * N * I * N
3185 // We can use a scratch-let for N to dodge WGSL overflow errors. (skia:14385)
3186 std::string I = this->writeNontrivialScratchLet(*arguments[0],
3187 Precedence::kAdditive);
3188 std::string N = all_arguments_constant(arguments)
3189 ? this->writeScratchLet(*arguments[1], Precedence::kMultiplicative)
3190 : this->writeNontrivialScratchLet(*arguments[1], Precedence::kMultiplicative);
3191 return this->writeScratchLet(String::printf("%s - 2 * %s * %s * %s",
3192 I.c_str(), N.c_str(),
3193 I.c_str(), N.c_str()));
3194 }
3195 return this->assembleSimpleIntrinsic("reflect", call);
3196
3197 case k_refract_IntrinsicKind:
3198 if (arguments[0]->type().isScalar()) {
3199 // WGSL only implements refract for vectors; rather than reimplementing refract from
3200 // scratch, we can replace the call with `refract(float2(I,0), float2(N,0), eta).x`.
3201 std::string I = this->writeNontrivialScratchLet(*arguments[0],
3202 Precedence::kSequence);
3203 std::string N = this->writeNontrivialScratchLet(*arguments[1],
3204 Precedence::kSequence);
3205 // We can use a scratch-let for Eta to avoid WGSL overflow errors. (skia:14385)
3206 std::string Eta = all_arguments_constant(arguments)
3207 ? this->writeScratchLet(*arguments[2], Precedence::kSequence)
3208 : this->writeNontrivialScratchLet(*arguments[2], Precedence::kSequence);
3209 return this->writeScratchLet(
3210 String::printf("refract(vec2<%s>(%s, 0), vec2<%s>(%s, 0), %s).x",
3211 to_wgsl_type(fContext, arguments[0]->type()).c_str(),
3212 I.c_str(),
3213 to_wgsl_type(fContext, arguments[1]->type()).c_str(),
3214 N.c_str(),
3215 Eta.c_str()));
3216 }
3217 return this->assembleSimpleIntrinsic("refract", call);
3218
3219 case k_sample_IntrinsicKind: {
3220 // Determine if a bias argument was passed in.
3221 SkASSERT(arguments.size() == 2 || arguments.size() == 3);
3222 bool callIncludesBias = (arguments.size() == 3);
3223
3224 if (fProgram.fConfig->fSettings.fSharpenTextures || callIncludesBias) {
3225 // We need to supply a bias argument; this is a separate intrinsic in WGSL.
3226 std::string expr = this->assemblePartialSampleCall("textureSampleBias",
3227 *arguments[0],
3228 *arguments[1]);
3229 expr += ", ";
3230 if (callIncludesBias) {
3231 expr += this->assembleExpression(*arguments[2], Precedence::kAdditive) +
3232 " + ";
3233 }
3234 expr += skstd::to_string(fProgram.fConfig->fSettings.fSharpenTextures
3236 : 0.0f);
3237 return expr + ')';
3238 }
3239
3240 // No bias is necessary, so we can call `textureSample` directly.
3241 return this->assemblePartialSampleCall("textureSample",
3242 *arguments[0],
3243 *arguments[1]) + ')';
3244 }
3245 case k_sampleLod_IntrinsicKind: {
3246 std::string expr = this->assemblePartialSampleCall("textureSampleLevel",
3247 *arguments[0],
3248 *arguments[1]);
3249 expr += ", " + this->assembleExpression(*arguments[2], Precedence::kSequence);
3250 return expr + ')';
3251 }
3252 case k_sampleGrad_IntrinsicKind: {
3253 std::string expr = this->assemblePartialSampleCall("textureSampleGrad",
3254 *arguments[0],
3255 *arguments[1]);
3256 expr += ", " + this->assembleExpression(*arguments[2], Precedence::kSequence);
3257 expr += ", " + this->assembleExpression(*arguments[3], Precedence::kSequence);
3258 return expr + ')';
3259 }
3260 case k_textureHeight_IntrinsicKind:
3261 return this->assembleSimpleIntrinsic("textureDimensions", call) + ".y";
3262
3263 case k_textureRead_IntrinsicKind: {
3264 // We need to inject an extra argument for the mip-level. We don't plan on using mipmaps
3265 // in our storage textures, so we can just pass zero.
3266 std::string tex = this->assembleExpression(*arguments[0], Precedence::kSequence);
3267 std::string pos = this->writeScratchLet(*arguments[1], Precedence::kSequence);
3268 return std::string("textureLoad(") + tex + ", " + pos + ", 0)";
3269 }
3270 case k_textureWidth_IntrinsicKind:
3271 return this->assembleSimpleIntrinsic("textureDimensions", call) + ".x";
3272
3273 case k_textureWrite_IntrinsicKind:
3274 return this->assembleSimpleIntrinsic("textureStore", call);
3275
3276 case k_unpackHalf2x16_IntrinsicKind:
3277 return this->assembleSimpleIntrinsic("unpack2x16float", call);
3278
3279 case k_unpackSnorm2x16_IntrinsicKind:
3280 return this->assembleSimpleIntrinsic("unpack2x16snorm", call);
3281
3282 case k_unpackSnorm4x8_IntrinsicKind:
3283 return this->assembleSimpleIntrinsic("unpack4x8snorm", call);
3284
3285 case k_unpackUnorm2x16_IntrinsicKind:
3286 return this->assembleSimpleIntrinsic("unpack2x16unorm", call);
3287
3288 case k_unpackUnorm4x8_IntrinsicKind:
3289 return this->assembleSimpleIntrinsic("unpack4x8unorm", call);
3290
3291 case k_loadFloatBuffer_IntrinsicKind: {
3292 auto indexExpression = IRHelpers::LoadFloatBuffer(
3293 fContext,
3294 fCaps,
3295 call.position(),
3296 call.arguments()[0]->clone());
3297
3298 return this->assembleExpression(*indexExpression, Precedence::kExpression);
3299 }
3300
3301 case k_clamp_IntrinsicKind:
3302 case k_max_IntrinsicKind:
3303 case k_min_IntrinsicKind:
3304 case k_smoothstep_IntrinsicKind:
3305 case k_step_IntrinsicKind:
3306 return this->assembleVectorizedIntrinsic(call.function().name(), call);
3307
3308 case k_abs_IntrinsicKind:
3309 case k_acos_IntrinsicKind:
3310 case k_all_IntrinsicKind:
3311 case k_any_IntrinsicKind:
3312 case k_asin_IntrinsicKind:
3313 case k_atomicAdd_IntrinsicKind:
3314 case k_atomicLoad_IntrinsicKind:
3315 case k_atomicStore_IntrinsicKind:
3316 case k_ceil_IntrinsicKind:
3317 case k_cos_IntrinsicKind:
3318 case k_cross_IntrinsicKind:
3319 case k_degrees_IntrinsicKind:
3320 case k_distance_IntrinsicKind:
3321 case k_exp_IntrinsicKind:
3322 case k_exp2_IntrinsicKind:
3323 case k_floor_IntrinsicKind:
3324 case k_fract_IntrinsicKind:
3325 case k_length_IntrinsicKind:
3326 case k_log_IntrinsicKind:
3327 case k_log2_IntrinsicKind:
3328 case k_radians_IntrinsicKind:
3329 case k_pow_IntrinsicKind:
3330 case k_saturate_IntrinsicKind:
3331 case k_sign_IntrinsicKind:
3332 case k_sin_IntrinsicKind:
3333 case k_sqrt_IntrinsicKind:
3334 case k_storageBarrier_IntrinsicKind:
3335 case k_tan_IntrinsicKind:
3336 case k_workgroupBarrier_IntrinsicKind:
3337 default:
3338 return this->assembleSimpleIntrinsic(call.function().name(), call);
3339 }
3340}
3341
3342static constexpr char kInverse2x2[] =
3343 "fn mat2_inverse(m: mat2x2<f32>) -> mat2x2<f32> {"
3344"\n" "return mat2x2<f32>(m[1].y, -m[0].y, -m[1].x, m[0].x) * (1/determinant(m));"
3345"\n" "}"
3346"\n";
3347
3348static constexpr char kInverse3x3[] =
3349 "fn mat3_inverse(m: mat3x3<f32>) -> mat3x3<f32> {"
3350"\n" "let a00 = m[0].x; let a01 = m[0].y; let a02 = m[0].z;"
3351"\n" "let a10 = m[1].x; let a11 = m[1].y; let a12 = m[1].z;"
3352"\n" "let a20 = m[2].x; let a21 = m[2].y; let a22 = m[2].z;"
3353"\n" "let b01 = a22*a11 - a12*a21;"
3354"\n" "let b11 = -a22*a10 + a12*a20;"
3355"\n" "let b21 = a21*a10 - a11*a20;"
3356"\n" "let det = a00*b01 + a01*b11 + a02*b21;"
3357"\n" "return mat3x3<f32>(b01, (-a22*a01 + a02*a21), ( a12*a01 - a02*a11),"
3358"\n" "b11, ( a22*a00 - a02*a20), (-a12*a00 + a02*a10),"
3359"\n" "b21, (-a21*a00 + a01*a20), ( a11*a00 - a01*a10)) * (1/det);"
3360"\n" "}"
3361"\n";
3362
3363static constexpr char kInverse4x4[] =
3364 "fn mat4_inverse(m: mat4x4<f32>) -> mat4x4<f32>{"
3365"\n" "let a00 = m[0].x; let a01 = m[0].y; let a02 = m[0].z; let a03 = m[0].w;"
3366"\n" "let a10 = m[1].x; let a11 = m[1].y; let a12 = m[1].z; let a13 = m[1].w;"
3367"\n" "let a20 = m[2].x; let a21 = m[2].y; let a22 = m[2].z; let a23 = m[2].w;"
3368"\n" "let a30 = m[3].x; let a31 = m[3].y; let a32 = m[3].z; let a33 = m[3].w;"
3369"\n" "let b00 = a00*a11 - a01*a10;"
3370"\n" "let b01 = a00*a12 - a02*a10;"
3371"\n" "let b02 = a00*a13 - a03*a10;"
3372"\n" "let b03 = a01*a12 - a02*a11;"
3373"\n" "let b04 = a01*a13 - a03*a11;"
3374"\n" "let b05 = a02*a13 - a03*a12;"
3375"\n" "let b06 = a20*a31 - a21*a30;"
3376"\n" "let b07 = a20*a32 - a22*a30;"
3377"\n" "let b08 = a20*a33 - a23*a30;"
3378"\n" "let b09 = a21*a32 - a22*a31;"
3379"\n" "let b10 = a21*a33 - a23*a31;"
3380"\n" "let b11 = a22*a33 - a23*a32;"
3381"\n" "let det = b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06;"
3382"\n" "return mat4x4<f32>(a11*b11 - a12*b10 + a13*b09,"
3383"\n" "a02*b10 - a01*b11 - a03*b09,"
3384"\n" "a31*b05 - a32*b04 + a33*b03,"
3385"\n" "a22*b04 - a21*b05 - a23*b03,"
3386"\n" "a12*b08 - a10*b11 - a13*b07,"
3387"\n" "a00*b11 - a02*b08 + a03*b07,"
3388"\n" "a32*b02 - a30*b05 - a33*b01,"
3389"\n" "a20*b05 - a22*b02 + a23*b01,"
3390"\n" "a10*b10 - a11*b08 + a13*b06,"
3391"\n" "a01*b08 - a00*b10 - a03*b06,"
3392"\n" "a30*b04 - a31*b02 + a33*b00,"
3393"\n" "a21*b02 - a20*b04 - a23*b00,"
3394"\n" "a11*b07 - a10*b09 - a12*b06,"
3395"\n" "a00*b09 - a01*b07 + a02*b06,"
3396"\n" "a31*b01 - a30*b03 - a32*b00,"
3397"\n" "a20*b03 - a21*b01 + a22*b00) * (1/det);"
3398"\n" "}"
3399"\n";
3400
3401std::string WGSLCodeGenerator::assembleInversePolyfill(const FunctionCall& call) {
3402 const ExpressionArray& arguments = call.arguments();
3403 const Type& type = arguments.front()->type();
3404
3405 // The `inverse` intrinsic should only accept a single-argument square matrix.
3406 // Once we implement f16 support, these polyfills will need to be updated to support `hmat`;
3407 // for the time being, all floats in WGSL are f32, so we don't need to worry about precision.
3408 SkASSERT(arguments.size() == 1);
3409 SkASSERT(type.isMatrix());
3410 SkASSERT(type.rows() == type.columns());
3411
3412 switch (type.slotCount()) {
3413 case 4:
3414 if (!fWrittenInverse2) {
3415 fWrittenInverse2 = true;
3416 fHeader.writeText(kInverse2x2);
3417 }
3418 return this->assembleSimpleIntrinsic("mat2_inverse", call);
3419
3420 case 9:
3421 if (!fWrittenInverse3) {
3422 fWrittenInverse3 = true;
3423 fHeader.writeText(kInverse3x3);
3424 }
3425 return this->assembleSimpleIntrinsic("mat3_inverse", call);
3426
3427 case 16:
3428 if (!fWrittenInverse4) {
3429 fWrittenInverse4 = true;
3430 fHeader.writeText(kInverse4x4);
3431 }
3432 return this->assembleSimpleIntrinsic("mat4_inverse", call);
3433
3434 default:
3435 // We only support square matrices.
3437 }
3438}
3439
3440std::string WGSLCodeGenerator::assembleFunctionCall(const FunctionCall& call,
3441 Precedence parentPrecedence) {
3442 const FunctionDeclaration& func = call.function();
3443 std::string result;
3444
3445 // Many intrinsics need to be rewritten in WGSL.
3446 if (func.isIntrinsic()) {
3447 return this->assembleIntrinsicCall(call, func.intrinsicKind(), parentPrecedence);
3448 }
3449
3450 // We implement function out-parameters by declaring them as pointers. SkSL follows GLSL's
3451 // out-parameter semantics, in which out-parameters are only written back to the original
3452 // variable after the function's execution is complete (see
3453 // https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Parameters).
3454 //
3455 // In addition, SkSL supports swizzles and array index expressions to be passed into
3456 // out-parameters; however, WGSL does not allow taking their address into a pointer.
3457 //
3458 // We support these by using LValues to create temporary copies and then pass pointers to the
3459 // copies. Once the function returns, we copy the values back to the LValue.
3460
3461 // First detect which arguments are passed to out-parameters.
3462 // TODO: rewrite this method in terms of LValues.
3463 const ExpressionArray& args = call.arguments();
3464 SkSpan<Variable* const> params = func.parameters();
3465 SkASSERT(SkToSizeT(args.size()) == params.size());
3466
3468 STArray<16, std::string> substituteArgument;
3469 writeback.reserve_exact(args.size());
3470 substituteArgument.reserve_exact(args.size());
3471
3472 for (int index = 0; index < args.size(); ++index) {
3473 if (params[index]->modifierFlags() & ModifierFlag::kOut) {
3474 std::unique_ptr<LValue> lvalue = this->makeLValue(*args[index]);
3475 if (params[index]->modifierFlags() & ModifierFlag::kIn) {
3476 // Load the lvalue's contents into the substitute argument.
3477 substituteArgument.push_back(this->writeScratchVar(args[index]->type(),
3478 lvalue->load()));
3479 } else {
3480 // Create a substitute argument, but leave it uninitialized.
3481 substituteArgument.push_back(this->writeScratchVar(args[index]->type()));
3482 }
3483 writeback.push_back(std::move(lvalue));
3484 } else {
3485 substituteArgument.push_back(std::string());
3486 writeback.push_back(nullptr);
3487 }
3488 }
3489
3490 std::string expr = this->assembleName(func.mangledName());
3491 expr.push_back('(');
3492 auto separator = SkSL::String::Separator();
3493
3494 if (std::string funcDepArgs = this->functionDependencyArgs(func); !funcDepArgs.empty()) {
3495 expr += funcDepArgs;
3496 separator();
3497 }
3498
3499 // Pass the function arguments, or any substitutes as needed.
3500 for (int index = 0; index < args.size(); ++index) {
3501 expr += separator();
3502 if (!substituteArgument[index].empty()) {
3503 // We need to take the address of the variable and pass it down as a pointer.
3504 expr += '&' + substituteArgument[index];
3505 } else if (args[index]->type().isSampler()) {
3506 // If the argument is a sampler, we need to pass the texture _and_ its associated
3507 // sampler. (Function parameter lists also convert sampler parameters into a matching
3508 // texture/sampler parameter pair.)
3509 expr += this->assembleExpression(*args[index], Precedence::kSequence);
3510 expr += kTextureSuffix;
3511 expr += ", ";
3512 expr += this->assembleExpression(*args[index], Precedence::kSequence);
3513 expr += kSamplerSuffix;
3514 } else {
3515 expr += this->assembleExpression(*args[index], Precedence::kSequence);
3516 }
3517 }
3518 expr += ')';
3519
3520 if (call.type().isVoid()) {
3521 // Making function calls that result in `void` is only valid in on the left side of a
3522 // comma-sequence, or in a top-level statement. Emit the function call as a top-level
3523 // statement and return an empty string, as the result will not be used.
3524 SkASSERT(parentPrecedence >= Precedence::kSequence);
3525 this->write(expr);
3526 this->writeLine(";");
3527 } else {
3528 result = this->writeScratchLet(expr);
3529 }
3530
3531 // Write the substitute arguments back into their lvalues.
3532 for (int index = 0; index < args.size(); ++index) {
3533 if (!substituteArgument[index].empty()) {
3534 this->writeLine(writeback[index]->store(substituteArgument[index]));
3535 }
3536 }
3537
3538 // Return the result of invoking the function.
3539 return result;
3540}
3541
3542std::string WGSLCodeGenerator::assembleIndexExpression(const IndexExpression& i) {
3543 // Put the index value into a let-expression.
3544 std::string idx = this->writeNontrivialScratchLet(*i.index(), Precedence::kExpression);
3545 return this->assembleExpression(*i.base(), Precedence::kPostfix) + "[" + idx + "]";
3546}
3547
3548std::string WGSLCodeGenerator::assembleLiteral(const Literal& l) {
3549 const Type& type = l.type();
3550 if (type.isFloat() || type.isBoolean()) {
3551 return l.description(OperatorPrecedence::kExpression);
3552 }
3553 SkASSERT(type.isInteger());
3554 if (type.matches(*fContext.fTypes.fUInt)) {
3555 return std::to_string(l.intValue() & 0xffffffff) + "u";
3556 } else if (type.matches(*fContext.fTypes.fUShort)) {
3557 return std::to_string(l.intValue() & 0xffff) + "u";
3558 } else {
3559 return std::to_string(l.intValue());
3560 }
3561}
3562
3563std::string WGSLCodeGenerator::assembleIncrementExpr(const Type& type) {
3564 // `type(`
3565 std::string expr = to_wgsl_type(fContext, type);
3566 expr.push_back('(');
3567
3568 // `1, 1, 1...)`
3569 auto separator = SkSL::String::Separator();
3570 for (int slots = type.slotCount(); slots > 0; --slots) {
3571 expr += separator();
3572 expr += "1";
3573 }
3574 expr.push_back(')');
3575 return expr;
3576}
3577
3578std::string WGSLCodeGenerator::assemblePrefixExpression(const PrefixExpression& p,
3579 Precedence parentPrecedence) {
3580 // Unary + does nothing, so we omit it from the output.
3581 Operator op = p.getOperator();
3582 if (op.kind() == Operator::Kind::PLUS) {
3583 return this->assembleExpression(*p.operand(), Precedence::kPrefix);
3584 }
3585
3586 // Pre-increment/decrement expressions have no direct equivalent in WGSL.
3587 if (op.kind() == Operator::Kind::PLUSPLUS || op.kind() == Operator::Kind::MINUSMINUS) {
3588 std::unique_ptr<LValue> lvalue = this->makeLValue(*p.operand());
3589 if (!lvalue) {
3590 return "";
3591 }
3592
3593 // Generate the new value: `lvalue + type(1, 1, 1...)`.
3594 std::string newValue =
3595 lvalue->load() +
3596 (p.getOperator().kind() == Operator::Kind::PLUSPLUS ? " + " : " - ") +
3597 this->assembleIncrementExpr(p.operand()->type());
3598 this->writeLine(lvalue->store(newValue));
3599 return lvalue->load();
3600 }
3601
3602 // WGSL natively supports unary negation/not expressions (!,~,-).
3603 SkASSERT(op.kind() == OperatorKind::LOGICALNOT ||
3604 op.kind() == OperatorKind::BITWISENOT ||
3605 op.kind() == OperatorKind::MINUS);
3606
3607 // The unary negation operator only applies to scalars and vectors. For other mathematical
3608 // objects (such as matrices) we can express it as a multiplication by -1.
3609 std::string expr;
3610 const bool needsNegation = op.kind() == Operator::Kind::MINUS &&
3611 !p.operand()->type().isScalar() && !p.operand()->type().isVector();
3612 const bool needParens = needsNegation || Precedence::kPrefix >= parentPrecedence;
3613
3614 if (needParens) {
3615 expr.push_back('(');
3616 }
3617
3618 if (needsNegation) {
3619 expr += "-1.0 * ";
3620 expr += this->assembleExpression(*p.operand(), Precedence::kMultiplicative);
3621 } else {
3622 expr += p.getOperator().tightOperatorName();
3623 expr += this->assembleExpression(*p.operand(), Precedence::kPrefix);
3624 }
3625
3626 if (needParens) {
3627 expr.push_back(')');
3628 }
3629
3630 return expr;
3631}
3632
3633std::string WGSLCodeGenerator::assemblePostfixExpression(const PostfixExpression& p,
3634 Precedence parentPrecedence) {
3635 SkASSERT(p.getOperator().kind() == Operator::Kind::PLUSPLUS ||
3636 p.getOperator().kind() == Operator::Kind::MINUSMINUS);
3637
3638 // Post-increment/decrement expressions have no direct equivalent in WGSL; they do exist as a
3639 // standalone statement for convenience, but these aren't the same as SkSL's post-increments.
3640 std::unique_ptr<LValue> lvalue = this->makeLValue(*p.operand());
3641 if (!lvalue) {
3642 return "";
3643 }
3644
3645 // If the expression is used, create a let-copy of the original value.
3646 // (At statement-level precedence, we know the value is unused and can skip this let-copy.)
3647 std::string originalValue;
3648 if (parentPrecedence != Precedence::kStatement) {
3649 originalValue = this->writeScratchLet(lvalue->load());
3650 }
3651 // Generate the new value: `lvalue + type(1, 1, 1...)`.
3652 std::string newValue = lvalue->load() +
3653 (p.getOperator().kind() == Operator::Kind::PLUSPLUS ? " + " : " - ") +
3654 this->assembleIncrementExpr(p.operand()->type());
3655 this->writeLine(lvalue->store(newValue));
3656
3657 return originalValue;
3658}
3659
3660std::string WGSLCodeGenerator::assembleSwizzle(const Swizzle& swizzle) {
3661 return this->assembleExpression(*swizzle.base(), Precedence::kPostfix) + "." +
3662 Swizzle::MaskString(swizzle.components());
3663}
3664
3665std::string WGSLCodeGenerator::writeScratchVar(const Type& type, const std::string& value) {
3666 std::string scratchVarName = "_skTemp" + std::to_string(fScratchCount++);
3667 this->write("var ");
3668 this->write(scratchVarName);
3669 this->write(": ");
3670 this->write(to_wgsl_type(fContext, type));
3671 if (!value.empty()) {
3672 this->write(" = ");
3673 this->write(value);
3674 }
3675 this->writeLine(";");
3676 return scratchVarName;
3677}
3678
3679std::string WGSLCodeGenerator::writeScratchLet(const std::string& expr,
3680 bool isCompileTimeConstant) {
3681 std::string scratchVarName = "_skTemp" + std::to_string(fScratchCount++);
3682 this->write(fAtFunctionScope && !isCompileTimeConstant ? "let " : "const ");
3683 this->write(scratchVarName);
3684 this->write(" = ");
3685 this->write(expr);
3686 this->writeLine(";");
3687 return scratchVarName;
3688}
3689
3690std::string WGSLCodeGenerator::writeScratchLet(const Expression& expr,
3691 Precedence parentPrecedence) {
3692 return this->writeScratchLet(this->assembleExpression(expr, parentPrecedence));
3693}
3694
3695std::string WGSLCodeGenerator::writeNontrivialScratchLet(const Expression& expr,
3696 Precedence parentPrecedence) {
3697 std::string result = this->assembleExpression(expr, parentPrecedence);
3698 return is_nontrivial_expression(expr)
3699 ? this->writeScratchLet(result, Analysis::IsCompileTimeConstant(expr))
3700 : result;
3701}
3702
3703std::string WGSLCodeGenerator::assembleTernaryExpression(const TernaryExpression& t,
3704 Precedence parentPrecedence) {
3705 std::string expr;
3706
3707 // The trivial case is when neither branch has side effects and evaluate to a scalar or vector
3708 // type. This can be represented with a call to the WGSL `select` intrinsic. Select doesn't
3709 // support short-circuiting, so we should only use it when both the true- and false-expressions
3710 // are trivial to evaluate.
3711 if ((t.type().isScalar() || t.type().isVector()) &&
3712 !Analysis::HasSideEffects(*t.test()) &&
3713 Analysis::IsTrivialExpression(*t.ifTrue()) &&
3714 Analysis::IsTrivialExpression(*t.ifFalse())) {
3715
3716 bool needParens = Precedence::kTernary >= parentPrecedence;
3717 if (needParens) {
3718 expr.push_back('(');
3719 }
3720 expr += "select(";
3721 expr += this->assembleExpression(*t.ifFalse(), Precedence::kSequence);
3722 expr += ", ";
3723 expr += this->assembleExpression(*t.ifTrue(), Precedence::kSequence);
3724 expr += ", ";
3725
3726 bool isVector = t.type().isVector();
3727 if (isVector) {
3728 // Splat the condition expression into a vector.
3729 expr += String::printf("vec%d<bool>(", t.type().columns());
3730 }
3731 expr += this->assembleExpression(*t.test(), Precedence::kSequence);
3732 if (isVector) {
3733 expr.push_back(')');
3734 }
3735 expr.push_back(')');
3736 if (needParens) {
3737 expr.push_back(')');
3738 }
3739 } else {
3740 // WGSL does not support ternary expressions. Instead, we hoist the expression out into the
3741 // surrounding block, convert it into an if statement, and write the result to a synthesized
3742 // variable. Instead of the original expression, we return that variable.
3743 expr = this->writeScratchVar(t.ifTrue()->type());
3744
3745 std::string testExpr = this->assembleExpression(*t.test(), Precedence::kExpression);
3746 this->write("if ");
3747 this->write(testExpr);
3748 this->writeLine(" {");
3749
3750 ++fIndentation;
3751 std::string trueExpr = this->assembleExpression(*t.ifTrue(), Precedence::kAssignment);
3752 this->write(expr);
3753 this->write(" = ");
3754 this->write(trueExpr);
3755 this->writeLine(";");
3756 --fIndentation;
3757
3758 this->writeLine("} else {");
3759
3760 ++fIndentation;
3761 std::string falseExpr = this->assembleExpression(*t.ifFalse(), Precedence::kAssignment);
3762 this->write(expr);
3763 this->write(" = ");
3764 this->write(falseExpr);
3765 this->writeLine(";");
3766 --fIndentation;
3767
3768 this->writeLine("}");
3769 }
3770 return expr;
3771}
3772
3773std::string WGSLCodeGenerator::variablePrefix(const Variable& v) {
3774 if (v.storage() == Variable::Storage::kGlobal) {
3775 // If the field refers to a pipeline IO parameter, then we access it via the synthesized IO
3776 // structs. We make an explicit exception for `sk_PointSize` which we declare as a
3777 // placeholder variable in global scope as it is not supported by WebGPU as a pipeline IO
3778 // parameter (see comments in `writeStageOutputStruct`).
3779 if (v.modifierFlags() & ModifierFlag::kIn) {
3780 return "_stageIn.";
3781 }
3782 if (v.modifierFlags() & ModifierFlag::kOut) {
3783 return "(*_stageOut).";
3784 }
3785
3786 // If the field refers to an anonymous-interface-block structure, access it via the
3787 // synthesized `_uniform0` or `_storage1` global.
3788 if (const InterfaceBlock* ib = v.interfaceBlock()) {
3789 const Type& ibType = ib->var()->type().componentType();
3790 if (const std::string* ibName = fInterfaceBlockNameMap.find(&ibType)) {
3791 return *ibName + '.';
3792 }
3793 }
3794
3795 // If the field refers to an top-level uniform, access it via the synthesized
3796 // `_globalUniforms` global. (Note that this should only occur in test code; Skia will
3797 // always put uniforms in an interface block.)
3798 if (is_in_global_uniforms(v)) {
3799 return "_globalUniforms.";
3800 }
3801 }
3802
3803 return "";
3804}
3805
3806std::string WGSLCodeGenerator::variableReferenceNameForLValue(const VariableReference& r) {
3807 const Variable& v = *r.variable();
3808
3809 if ((v.storage() == Variable::Storage::kParameter &&
3810 v.modifierFlags() & ModifierFlag::kOut)) {
3811 // This is an out-parameter; it's pointer-typed, so we need to dereference it. We wrap the
3812 // dereference in parentheses, in case the value is used in an access expression later.
3813 return "(*" + this->assembleName(v.mangledName()) + ')';
3814 }
3815
3816 return this->variablePrefix(v) + this->assembleName(v.mangledName());
3817}
3818
3819std::string WGSLCodeGenerator::assembleVariableReference(const VariableReference& r) {
3820 // TODO(b/294274678): Correctly handle RTFlip for built-ins.
3821 const Variable& v = *r.variable();
3822
3823 // Insert a conversion expression if this is a built-in variable whose type differs from the
3824 // SkSL.
3825 std::string expr;
3826 std::optional<std::string_view> conversion = needs_builtin_type_conversion(v);
3827 if (conversion.has_value()) {
3828 expr += *conversion;
3829 expr.push_back('(');
3830 }
3831
3832 expr += this->variableReferenceNameForLValue(r);
3833
3834 if (conversion.has_value()) {
3835 expr.push_back(')');
3836 }
3837
3838 return expr;
3839}
3840
3841std::string WGSLCodeGenerator::assembleAnyConstructor(const AnyConstructor& c) {
3842 std::string expr = to_wgsl_type(fContext, c.type());
3843 expr.push_back('(');
3844 auto separator = SkSL::String::Separator();
3845 for (const auto& e : c.argumentSpan()) {
3846 expr += separator();
3847 expr += this->assembleExpression(*e, Precedence::kSequence);
3848 }
3849 expr.push_back(')');
3850 return expr;
3851}
3852
3853std::string WGSLCodeGenerator::assembleConstructorCompound(const ConstructorCompound& c) {
3854 if (c.type().isVector()) {
3855 return this->assembleConstructorCompoundVector(c);
3856 } else if (c.type().isMatrix()) {
3857 return this->assembleConstructorCompoundMatrix(c);
3858 } else {
3859 fContext.fErrors->error(c.fPosition, "unsupported compound constructor");
3860 return {};
3861 }
3862}
3863
3864std::string WGSLCodeGenerator::assembleConstructorCompoundVector(const ConstructorCompound& c) {
3865 // WGSL supports constructing vectors from a mix of scalars and vectors but
3866 // not matrices (see https://www.w3.org/TR/WGSL/#type-constructor-expr).
3867 //
3868 // SkSL supports vec4(mat2x2) which we handle specially.
3869 if (c.type().columns() == 4 && c.argumentSpan().size() == 1) {
3870 const Expression& arg = *c.argumentSpan().front();
3871 if (arg.type().isMatrix()) {
3872 SkASSERT(arg.type().columns() == 2);
3873 SkASSERT(arg.type().rows() == 2);
3874
3875 std::string matrix = this->writeNontrivialScratchLet(arg, Precedence::kPostfix);
3876 return String::printf("%s(%s[0], %s[1])", to_wgsl_type(fContext, c.type()).c_str(),
3877 matrix.c_str(),
3878 matrix.c_str());
3879 }
3880 }
3881 return this->assembleAnyConstructor(c);
3882}
3883
3884std::string WGSLCodeGenerator::assembleConstructorCompoundMatrix(const ConstructorCompound& ctor) {
3885 SkASSERT(ctor.type().isMatrix());
3886
3887 std::string expr = to_wgsl_type(fContext, ctor.type()) + '(';
3888 auto separator = String::Separator();
3889 for (const std::unique_ptr<Expression>& arg : ctor.arguments()) {
3890 SkASSERT(arg->type().isScalar() || arg->type().isVector());
3891
3892 if (arg->type().isScalar()) {
3893 expr += separator();
3894 expr += this->assembleExpression(*arg, Precedence::kSequence);
3895 } else {
3896 std::string inner = this->writeNontrivialScratchLet(*arg, Precedence::kSequence);
3897 int numSlots = arg->type().slotCount();
3898 for (int slot = 0; slot < numSlots; ++slot) {
3899 String::appendf(&expr, "%s%s[%d]", separator().c_str(), inner.c_str(), slot);
3900 }
3901 }
3902 }
3903 return expr + ')';
3904}
3905
3906std::string WGSLCodeGenerator::assembleConstructorDiagonalMatrix(
3907 const ConstructorDiagonalMatrix& c) {
3908 const Type& type = c.type();
3909 SkASSERT(type.isMatrix());
3910 SkASSERT(c.argument()->type().isScalar());
3911
3912 // Evaluate the inner-expression, creating a scratch variable if necessary.
3913 std::string inner = this->writeNontrivialScratchLet(*c.argument(), Precedence::kAssignment);
3914
3915 // Assemble a diagonal-matrix expression.
3916 std::string expr = to_wgsl_type(fContext, type) + '(';
3917 auto separator = String::Separator();
3918 for (int col = 0; col < type.columns(); ++col) {
3919 for (int row = 0; row < type.rows(); ++row) {
3920 expr += separator();
3921 if (col == row) {
3922 expr += inner;
3923 } else {
3924 expr += "0.0";
3925 }
3926 }
3927 }
3928 return expr + ')';
3929}
3930
3931std::string WGSLCodeGenerator::assembleConstructorMatrixResize(
3932 const ConstructorMatrixResize& ctor) {
3933 std::string source = this->writeNontrivialScratchLet(*ctor.argument(), Precedence::kSequence);
3934 int columns = ctor.type().columns();
3935 int rows = ctor.type().rows();
3936 int sourceColumns = ctor.argument()->type().columns();
3937 int sourceRows = ctor.argument()->type().rows();
3938 auto separator = String::Separator();
3939 std::string expr = to_wgsl_type(fContext, ctor.type()) + '(';
3940
3941 for (int c = 0; c < columns; ++c) {
3942 for (int r = 0; r < rows; ++r) {
3943 expr += separator();
3944 if (c < sourceColumns && r < sourceRows) {
3945 String::appendf(&expr, "%s[%d][%d]", source.c_str(), c, r);
3946 } else if (r == c) {
3947 expr += "1.0";
3948 } else {
3949 expr += "0.0";
3950 }
3951 }
3952 }
3953
3954 return expr + ')';
3955}
3956
3957std::string WGSLCodeGenerator::assembleEqualityExpression(const Type& left,
3958 const std::string& leftName,
3959 const Type& right,
3960 const std::string& rightName,
3961 Operator op,
3962 Precedence parentPrecedence) {
3963 SkASSERT(op.kind() == OperatorKind::EQEQ || op.kind() == OperatorKind::NEQ);
3964
3965 std::string expr;
3966 bool isEqual = (op.kind() == Operator::Kind::EQEQ);
3967 const char* const combiner = isEqual ? " && " : " || ";
3968
3969 if (left.isMatrix()) {
3970 // Each matrix column must be compared as if it were an individual vector.
3971 SkASSERT(right.isMatrix());
3972 SkASSERT(left.rows() == right.rows());
3973 SkASSERT(left.columns() == right.columns());
3974 int columns = left.columns();
3975 const Type& vecType = left.columnType(fContext);
3976 const char* separator = "(";
3977 for (int index = 0; index < columns; ++index) {
3978 expr += separator;
3979 std::string suffix = '[' + std::to_string(index) + ']';
3980 expr += this->assembleEqualityExpression(vecType, leftName + suffix,
3981 vecType, rightName + suffix,
3982 op, Precedence::kParentheses);
3983 separator = combiner;
3984 }
3985 return expr + ')';
3986 }
3987
3988 if (left.isArray()) {
3989 SkASSERT(right.matches(left));
3990 const Type& indexedType = left.componentType();
3991 const char* separator = "(";
3992 for (int index = 0; index < left.columns(); ++index) {
3993 expr += separator;
3994 std::string suffix = '[' + std::to_string(index) + ']';
3995 expr += this->assembleEqualityExpression(indexedType, leftName + suffix,
3996 indexedType, rightName + suffix,
3997 op, Precedence::kParentheses);
3998 separator = combiner;
3999 }
4000 return expr + ')';
4001 }
4002
4003 if (left.isStruct()) {
4004 // Recursively compare every field in the struct.
4005 SkASSERT(right.matches(left));
4006 SkSpan<const Field> fields = left.fields();
4007
4008 const char* separator = "(";
4009 for (const Field& field : fields) {
4010 expr += separator;
4011 expr += this->assembleEqualityExpression(
4012 *field.fType, leftName + '.' + this->assembleName(field.fName),
4013 *field.fType, rightName + '.' + this->assembleName(field.fName),
4014 op, Precedence::kParentheses);
4015 separator = combiner;
4016 }
4017 return expr + ')';
4018 }
4019
4020 if (left.isVector()) {
4021 // Compare vectors via `all(x == y)` or `any(x != y)`.
4022 SkASSERT(right.isVector());
4023 SkASSERT(left.slotCount() == right.slotCount());
4024
4025 expr += isEqual ? "all(" : "any(";
4026 expr += leftName;
4027 expr += operator_name(op);
4028 expr += rightName;
4029 return expr + ')';
4030 }
4031
4032 // Compare scalars via `x == y`.
4033 SkASSERT(right.isScalar());
4034 if (parentPrecedence < Precedence::kSequence) {
4035 expr = '(';
4036 }
4037 expr += leftName;
4038 expr += operator_name(op);
4039 expr += rightName;
4040 if (parentPrecedence < Precedence::kSequence) {
4041 expr += ')';
4042 }
4043 return expr;
4044}
4045
4046std::string WGSLCodeGenerator::assembleEqualityExpression(const Expression& left,
4047 const Expression& right,
4048 Operator op,
4049 Precedence parentPrecedence) {
4050 std::string leftName, rightName;
4051 if (left.type().isScalar() || left.type().isVector()) {
4052 // WGSL supports scalar and vector comparisons natively. We know the expressions will only
4053 // be emitted once, so there isn't a benefit to creating a let-declaration.
4054 leftName = this->assembleExpression(left, Precedence::kParentheses);
4055 rightName = this->assembleExpression(right, Precedence::kParentheses);
4056 } else {
4057 leftName = this->writeNontrivialScratchLet(left, Precedence::kAssignment);
4058 rightName = this->writeNontrivialScratchLet(right, Precedence::kAssignment);
4059 }
4060 return this->assembleEqualityExpression(left.type(), leftName, right.type(), rightName,
4061 op, parentPrecedence);
4062}
4063
4064void WGSLCodeGenerator::writeProgramElement(const ProgramElement& e) {
4065 switch (e.kind()) {
4067 // TODO(skia:13092): WGSL supports extensions via the "enable" directive
4068 // (https://www.w3.org/TR/WGSL/#enable-extensions-sec ). While we could easily emit this
4069 // directive, we should first ensure that all possible SkSL extension names are
4070 // converted to their appropriate WGSL extension.
4071 break;
4072 case ProgramElement::Kind::kGlobalVar:
4073 this->writeGlobalVarDeclaration(e.as<GlobalVarDeclaration>());
4074 break;
4075 case ProgramElement::Kind::kInterfaceBlock:
4076 // All interface block declarations are handled explicitly as the "program header" in
4077 // generateCode().
4078 break;
4079 case ProgramElement::Kind::kStructDefinition:
4080 this->writeStructDefinition(e.as<StructDefinition>());
4081 break;
4082 case ProgramElement::Kind::kFunctionPrototype:
4083 // A WGSL function declaration must contain its body and the function name is in scope
4084 // for the entire program (see https://www.w3.org/TR/WGSL/#function-declaration and
4085 // https://www.w3.org/TR/WGSL/#declaration-and-scope).
4086 //
4087 // As such, we don't emit function prototypes.
4088 break;
4090 this->writeFunction(e.as<FunctionDefinition>());
4091 break;
4092 case ProgramElement::Kind::kModifiers:
4093 this->writeModifiersDeclaration(e.as<ModifiersDeclaration>());
4094 break;
4095 default:
4096 SkDEBUGFAILF("unsupported program element: %s\n", e.description().c_str());
4097 break;
4098 }
4099}
4100
4101void WGSLCodeGenerator::writeTextureOrSampler(const Variable& var,
4102 int bindingLocation,
4103 std::string_view suffix,
4104 std::string_view wgslType) {
4105 if (var.type().dimensions() != SpvDim2D) {
4106 // Skia currently only uses 2D textures.
4107 fContext.fErrors->error(var.varDeclaration()->position(), "unsupported texture dimensions");
4108 return;
4109 }
4110
4111 this->write("@group(");
4112 this->write(std::to_string(std::max(0, var.layout().fSet)));
4113 this->write(") @binding(");
4114 this->write(std::to_string(bindingLocation));
4115 this->write(") var ");
4116 this->write(this->assembleName(var.mangledName()));
4117 this->write(suffix);
4118 this->write(": ");
4119 this->write(wgslType);
4120 this->writeLine(";");
4121}
4122
4123void WGSLCodeGenerator::writeGlobalVarDeclaration(const GlobalVarDeclaration& d) {
4124 const VarDeclaration& decl = d.varDeclaration();
4125 const Variable& var = *decl.var();
4126 if ((var.modifierFlags() & (ModifierFlag::kIn | ModifierFlag::kOut)) ||
4127 is_in_global_uniforms(var)) {
4128 // Pipeline stage I/O parameters and top-level (non-block) uniforms are handled specially
4129 // in generateCode().
4130 return;
4131 }
4132
4133 const Type::TypeKind varKind = var.type().typeKind();
4134 if (varKind == Type::TypeKind::kSampler) {
4135 // If the sampler binding was unassigned, provide a scratch value; this will make
4136 // golden-output tests pass, but will not actually be usable for drawing.
4137 int samplerLocation = var.layout().fSampler >= 0 ? var.layout().fSampler
4138 : 10000 + fScratchCount++;
4139 this->writeTextureOrSampler(var, samplerLocation, kSamplerSuffix, "sampler");
4140
4141 // If the texture binding was unassigned, provide a scratch value (for golden-output tests).
4142 int textureLocation = var.layout().fTexture >= 0 ? var.layout().fTexture
4143 : 10000 + fScratchCount++;
4144 this->writeTextureOrSampler(var, textureLocation, kTextureSuffix, "texture_2d<f32>");
4145 return;
4146 }
4147
4148 if (varKind == Type::TypeKind::kTexture) {
4149 // If a binding location was unassigned, provide a scratch value (for golden-output tests).
4150 int textureLocation = var.layout().fBinding >= 0 ? var.layout().fBinding
4151 : 10000 + fScratchCount++;
4152 // For a texture without an associated sampler, we don't apply a suffix.
4153 this->writeTextureOrSampler(var, textureLocation, /*suffix=*/"",
4154 to_wgsl_type(fContext, var.type(), &var.layout()));
4155 return;
4156 }
4157
4158 std::string initializer;
4159 if (decl.value()) {
4160 // We assume here that the initial-value expression will not emit any helper statements.
4161 // Initial-value expressions are required to pass IsConstantExpression, which limits the
4162 // blast radius to constructors, literals, and other constant values/variables.
4163 initializer += " = ";
4164 initializer += this->assembleExpression(*decl.value(), Precedence::kAssignment);
4165 }
4166
4167 if (var.modifierFlags().isConst()) {
4168 this->write("const ");
4169 } else if (var.modifierFlags().isWorkgroup()) {
4170 this->write("var<workgroup> ");
4171 } else if (var.modifierFlags().isPixelLocal()) {
4172 this->write("var<pixel_local> ");
4173 } else {
4174 this->write("var<private> ");
4175 }
4176 this->write(this->assembleName(var.mangledName()));
4177 this->write(": " + to_wgsl_type(fContext, var.type(), &var.layout()));
4178 this->write(initializer);
4179 this->writeLine(";");
4180}
4181
4182void WGSLCodeGenerator::writeStructDefinition(const StructDefinition& s) {
4183 const Type& type = s.type();
4184 this->writeLine("struct " + type.displayName() + " {");
4185 this->writeFields(type.fields(), /*memoryLayout=*/nullptr);
4186 this->writeLine("};");
4187}
4188
4189void WGSLCodeGenerator::writeModifiersDeclaration(const ModifiersDeclaration& modifiers) {
4190 LayoutFlags flags = modifiers.layout().fFlags;
4192 if (flags != LayoutFlag::kNone) {
4193 fContext.fErrors->error(modifiers.position(), "unsupported declaration");
4194 return;
4195 }
4196
4197 if (modifiers.layout().fLocalSizeX >= 0) {
4198 fLocalSizeX = modifiers.layout().fLocalSizeX;
4199 }
4200 if (modifiers.layout().fLocalSizeY >= 0) {
4201 fLocalSizeY = modifiers.layout().fLocalSizeY;
4202 }
4203 if (modifiers.layout().fLocalSizeZ >= 0) {
4204 fLocalSizeZ = modifiers.layout().fLocalSizeZ;
4205 }
4206}
4207
4208void WGSLCodeGenerator::writeFields(SkSpan<const Field> fields, const MemoryLayout* memoryLayout) {
4209 fIndentation++;
4210
4211 // TODO(skia:14370): array uniforms may need manual fixup for std140 padding. (Those uniforms
4212 // will also need special handling when they are accessed, or passed to functions.)
4213 for (size_t index = 0; index < fields.size(); ++index) {
4214 const Field& field = fields[index];
4215 if (memoryLayout && !memoryLayout->isSupported(*field.fType)) {
4216 // Reject types that aren't supported by the memory layout.
4217 fContext.fErrors->error(field.fPosition, "type '" + std::string(field.fType->name()) +
4218 "' is not permitted here");
4219 return;
4220 }
4221
4222 // Prepend @size(n) to enforce the offsets from the SkSL layout. (This is effectively
4223 // a gadget that we can use to insert padding between elements.)
4224 if (index < fields.size() - 1) {
4225 int thisFieldOffset = field.fLayout.fOffset;
4226 int nextFieldOffset = fields[index + 1].fLayout.fOffset;
4227 if (index == 0 && thisFieldOffset > 0) {
4228 fContext.fErrors->error(field.fPosition, "field must have an offset of zero");
4229 return;
4230 }
4231 if (thisFieldOffset >= 0 && nextFieldOffset > thisFieldOffset) {
4232 this->write("@size(");
4233 this->write(std::to_string(nextFieldOffset - thisFieldOffset));
4234 this->write(") ");
4235 }
4236 }
4237
4238 this->write(this->assembleName(field.fName));
4239 this->write(": ");
4240 if (const FieldPolyfillInfo* info = fFieldPolyfillMap.find(&field)) {
4241 if (info->fIsArray) {
4242 // This properly handles arrays of matrices, as well as arrays of other primitives.
4243 SkASSERT(field.fType->isArray());
4244 this->write("array<_skArrayElement_");
4245 this->write(field.fType->abbreviatedName());
4246 this->write(", ");
4247 this->write(std::to_string(field.fType->columns()));
4248 this->write(">");
4249 } else if (info->fIsMatrix) {
4250 this->write("_skMatrix");
4251 this->write(std::to_string(field.fType->columns()));
4252 this->write(std::to_string(field.fType->rows()));
4253 } else {
4254 SkDEBUGFAILF("need polyfill for %s", info->fReplacementName.c_str());
4255 }
4256 } else {
4257 this->write(to_wgsl_type(fContext, *field.fType, &field.fLayout));
4258 }
4259 this->writeLine(",");
4260 }
4261
4262 fIndentation--;
4263}
4264
4265void WGSLCodeGenerator::writeEnables() {
4266 this->writeLine("diagnostic(off, derivative_uniformity);");
4267 this->writeLine("diagnostic(off, chromium.unreachable_code);");
4268
4269 if (fRequirements.fPixelLocalExtension) {
4270 this->writeLine("enable chromium_experimental_pixel_local;");
4271 }
4273 this->writeLine("enable chromium_experimental_framebuffer_fetch;");
4274 }
4276 this->writeLine("enable dual_source_blending;");
4277 }
4278}
4279
4280bool WGSLCodeGenerator::needsStageInputStruct() const {
4281 // It is illegal to declare a struct with no members; we can't emit a placeholder empty stage
4282 // input struct.
4283 return !fPipelineInputs.empty();
4284}
4285
4286void WGSLCodeGenerator::writeStageInputStruct() {
4287 if (!this->needsStageInputStruct()) {
4288 return;
4289 }
4290
4291 std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
4292 SkASSERT(!structNamePrefix.empty());
4293
4294 this->write("struct ");
4295 this->write(structNamePrefix);
4296 this->writeLine("In {");
4297 fIndentation++;
4298
4299 for (const Variable* v : fPipelineInputs) {
4300 if (v->type().isInterfaceBlock()) {
4301 for (const Field& f : v->type().fields()) {
4302 this->writePipelineIODeclaration(f.fLayout, *f.fType, f.fName, Delimiter::kComma);
4303 }
4304 } else {
4305 this->writePipelineIODeclaration(v->layout(), v->type(), v->mangledName(),
4307 }
4308 }
4309
4310 fIndentation--;
4311 this->writeLine("};");
4312}
4313
4314bool WGSLCodeGenerator::needsStageOutputStruct() const {
4315 // It is illegal to declare a struct with no members. However, vertex programs will _always_
4316 // have an output stage in WGSL, because the spec requires them to emit `@builtin(position)`.
4317 // So we always synthesize a reference to `sk_Position` even if the program doesn't need it.
4318 return !fPipelineOutputs.empty() || ProgramConfig::IsVertex(fProgram.fConfig->fKind);
4319}
4320
4321void WGSLCodeGenerator::writeStageOutputStruct() {
4322 if (!this->needsStageOutputStruct()) {
4323 return;
4324 }
4325
4326 std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
4327 SkASSERT(!structNamePrefix.empty());
4328
4329 this->write("struct ");
4330 this->write(structNamePrefix);
4331 this->writeLine("Out {");
4332 fIndentation++;
4333
4334 bool declaredPositionBuiltin = false;
4335 bool requiresPointSizeBuiltin = false;
4336 for (const Variable* v : fPipelineOutputs) {
4337 if (v->type().isInterfaceBlock()) {
4338 for (const auto& f : v->type().fields()) {
4339 this->writePipelineIODeclaration(f.fLayout, *f.fType, f.fName, Delimiter::kComma);
4340 if (f.fLayout.fBuiltin == SK_POSITION_BUILTIN) {
4341 declaredPositionBuiltin = true;
4342 } else if (f.fLayout.fBuiltin == SK_POINTSIZE_BUILTIN) {
4343 // sk_PointSize is explicitly not supported by `builtin_from_sksl_name` so
4344 // writePipelineIODeclaration will never write it. We mark it here if the
4345 // declaration is needed so we can synthesize it below.
4346 requiresPointSizeBuiltin = true;
4347 }
4348 }
4349 } else {
4350 this->writePipelineIODeclaration(v->layout(), v->type(), v->mangledName(),
4352 }
4353 }
4354
4355 // A vertex program must include the `position` builtin in its entrypoint's return type.
4356 const bool positionBuiltinRequired = ProgramConfig::IsVertex(fProgram.fConfig->fKind);
4357 if (positionBuiltinRequired && !declaredPositionBuiltin) {
4358 this->writeLine("@builtin(position) sk_Position: vec4<f32>,");
4359 }
4360
4361 fIndentation--;
4362 this->writeLine("};");
4363
4364 // In WebGPU/WGSL, the vertex stage does not support a point-size output and the size
4365 // of a point primitive is always 1 pixel (see https://github.com/gpuweb/gpuweb/issues/332).
4366 //
4367 // There isn't anything we can do to emulate this correctly at this stage so we synthesize a
4368 // placeholder global variable that has no effect. Programs should not rely on sk_PointSize when
4369 // using the Dawn backend.
4370 if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) && requiresPointSizeBuiltin) {
4371 this->writeLine("/* unsupported */ var<private> sk_PointSize: f32;");
4372 }
4373}
4374
4375void WGSLCodeGenerator::prepareUniformPolyfillsForInterfaceBlock(
4376 const InterfaceBlock* interfaceBlock,
4377 std::string_view instanceName,
4378 MemoryLayout::Standard nativeLayout) {
4380 SkSL::MemoryLayout native(nativeLayout);
4381
4382 const Type& structType = interfaceBlock->var()->type().componentType();
4383 for (const Field& field : structType.fields()) {
4384 const Type* type = field.fType;
4385 bool needsArrayPolyfill = false;
4386 bool needsMatrixPolyfill = false;
4387
4388 auto isPolyfillableMatrixType = [&](const Type* type) {
4389 return type->isMatrix() && std140.stride(*type) != native.stride(*type);
4390 };
4391
4392 if (isPolyfillableMatrixType(type)) {
4393 // Matrices will be represented as 16-byte aligned arrays in std140, and reconstituted
4394 // into proper matrices as they are later accessed. We need to synthesize polyfill.
4395 needsMatrixPolyfill = true;
4396 } else if (type->isArray() && !type->isUnsizedArray() &&
4397 !type->componentType().isOpaque()) {
4398 const Type* innerType = &type->componentType();
4399 if (isPolyfillableMatrixType(innerType)) {
4400 // Use a polyfill when the array contains a matrix that requires polyfill.
4401 needsArrayPolyfill = true;
4402 needsMatrixPolyfill = true;
4403 } else if (native.size(*innerType) < 16) {
4404 // Use a polyfill when the array elements are smaller than 16 bytes, since std140
4405 // will pad elements to a 16-byte stride.
4406 needsArrayPolyfill = true;
4407 }
4408 }
4409
4410 if (needsArrayPolyfill || needsMatrixPolyfill) {
4411 // Add a polyfill for this matrix type.
4412 FieldPolyfillInfo info;
4413 info.fInterfaceBlock = interfaceBlock;
4414 info.fReplacementName = "_skUnpacked_" + std::string(instanceName) + '_' +
4415 this->assembleName(field.fName);
4416 info.fIsArray = needsArrayPolyfill;
4417 info.fIsMatrix = needsMatrixPolyfill;
4418 fFieldPolyfillMap.set(&field, info);
4419 }
4420 }
4421}
4422
4423void WGSLCodeGenerator::writeUniformsAndBuffers() {
4424 for (const ProgramElement* e : fProgram.elements()) {
4425 // Iterate through the interface blocks.
4426 if (!e->is<InterfaceBlock>()) {
4427 continue;
4428 }
4429 const InterfaceBlock& ib = e->as<InterfaceBlock>();
4430
4431 // Determine if this interface block holds uniforms, buffers, or something else (skip it).
4432 std::string_view addressSpace;
4433 std::string_view accessMode;
4434 MemoryLayout::Standard nativeLayout;
4435 if (ib.var()->modifierFlags().isUniform()) {
4436 addressSpace = "uniform";
4438 } else if (ib.var()->modifierFlags().isBuffer()) {
4439 addressSpace = "storage";
4441 accessMode = ib.var()->modifierFlags().isReadOnly() ? ", read" : ", read_write";
4442 } else {
4443 continue;
4444 }
4445
4446 // If we have an anonymous interface block, assign a name like `_uniform0` or `_storage1`.
4447 std::string instanceName;
4448 if (ib.instanceName().empty()) {
4449 instanceName = "_" + std::string(addressSpace) + std::to_string(fScratchCount++);
4450 fInterfaceBlockNameMap[&ib.var()->type().componentType()] = instanceName;
4451 } else {
4452 instanceName = std::string(ib.instanceName());
4453 }
4454
4455 this->prepareUniformPolyfillsForInterfaceBlock(&ib, instanceName, nativeLayout);
4456
4457 // Create a struct to hold all of the fields from this InterfaceBlock.
4458 SkASSERT(!ib.typeName().empty());
4459 this->write("struct ");
4460 this->write(ib.typeName());
4461 this->writeLine(" {");
4462
4463 // Find the struct type and fields used by this interface block.
4464 const Type& ibType = ib.var()->type().componentType();
4465 SkASSERT(ibType.isStruct());
4466
4467 SkSpan<const Field> ibFields = ibType.fields();
4468 SkASSERT(!ibFields.empty());
4469
4470 MemoryLayout layout(MemoryLayout::Standard::k140);
4471 this->writeFields(ibFields, &layout);
4472 this->writeLine("};");
4473 this->write("@group(");
4474 this->write(std::to_string(std::max(0, ib.var()->layout().fSet)));
4475 this->write(") @binding(");
4476 this->write(std::to_string(std::max(0, ib.var()->layout().fBinding)));
4477 this->write(") var<");
4478 this->write(addressSpace);
4479 this->write(accessMode);
4480 this->write("> ");
4481 this->write(instanceName);
4482 this->write(" : ");
4483 this->write(to_wgsl_type(fContext, ib.var()->type(), &ib.var()->layout()));
4484 this->writeLine(";");
4485 }
4486}
4487
4488void WGSLCodeGenerator::writeNonBlockUniformsForTests() {
4489 bool declaredUniformsStruct = false;
4490
4491 for (const ProgramElement* e : fProgram.elements()) {
4492 if (e->is<GlobalVarDeclaration>()) {
4493 const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>();
4494 const Variable& var = *decls.varDeclaration().var();
4495 if (is_in_global_uniforms(var)) {
4496 if (!declaredUniformsStruct) {
4497 this->write("struct _GlobalUniforms {\n");
4498 declaredUniformsStruct = true;
4499 }
4500 this->write(" ");
4501 this->writeVariableDecl(var.layout(), var.type(), var.mangledName(),
4503 }
4504 }
4505 }
4506 if (declaredUniformsStruct) {
4507 int binding = fProgram.fConfig->fSettings.fDefaultUniformBinding;
4508 int set = fProgram.fConfig->fSettings.fDefaultUniformSet;
4509 this->write("};\n");
4510 this->write("@binding(" + std::to_string(binding) + ") ");
4511 this->write("@group(" + std::to_string(set) + ") ");
4512 this->writeLine("var<uniform> _globalUniforms: _GlobalUniforms;");
4513 }
4514}
4515
4516std::string WGSLCodeGenerator::functionDependencyArgs(const FunctionDeclaration& f) {
4517 WGSLFunctionDependencies* deps = fRequirements.fDependencies.find(&f);
4518 std::string args;
4519 if (deps && *deps) {
4520 const char* separator = "";
4521 if (*deps & WGSLFunctionDependency::kPipelineInputs) {
4522 args += "_stageIn";
4523 separator = ", ";
4524 }
4525 if (*deps & WGSLFunctionDependency::kPipelineOutputs) {
4526 args += separator;
4527 args += "_stageOut";
4528 }
4529 }
4530 return args;
4531}
4532
4533bool WGSLCodeGenerator::writeFunctionDependencyParams(const FunctionDeclaration& f) {
4534 WGSLFunctionDependencies* deps = fRequirements.fDependencies.find(&f);
4535 if (!deps || !*deps) {
4536 return false;
4537 }
4538
4539 std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
4540 if (structNamePrefix.empty()) {
4541 return false;
4542 }
4543 const char* separator = "";
4544 if (*deps & WGSLFunctionDependency::kPipelineInputs) {
4545 this->write("_stageIn: ");
4546 separator = ", ";
4547 this->write(structNamePrefix);
4548 this->write("In");
4549 }
4550 if (*deps & WGSLFunctionDependency::kPipelineOutputs) {
4551 this->write(separator);
4552 this->write("_stageOut: ptr<function, ");
4553 this->write(structNamePrefix);
4554 this->write("Out>");
4555 }
4556 return true;
4557}
4558
4559#if defined(SK_ENABLE_WGSL_VALIDATION)
4560static bool validate_wgsl(ErrorReporter& reporter, const std::string& wgsl, std::string* warnings) {
4561 // Enable the WGSL optional features that Skia might rely on.
4562 tint::wgsl::reader::Options options;
4563 for (auto extension : {tint::wgsl::Extension::kChromiumExperimentalPixelLocal,
4564 tint::wgsl::Extension::kDualSourceBlending}) {
4565 options.allowed_features.extensions.insert(extension);
4566 }
4567
4568 // Verify that the WGSL we produced is valid.
4569 tint::Source::File srcFile("", wgsl);
4570 tint::Program program(tint::wgsl::reader::Parse(&srcFile, options));
4571
4572 if (program.Diagnostics().ContainsErrors()) {
4573 // The program isn't valid WGSL.
4574#if defined(SKSL_STANDALONE)
4575 reporter.error(Position(), std::string("Tint compilation failed.\n\n") + wgsl);
4576#else
4577 // In debug, report the error via SkDEBUGFAIL. We also append the generated program for
4578 // ease of debugging.
4579 tint::diag::Formatter diagFormatter;
4580 std::string diagOutput = diagFormatter.Format(program.Diagnostics()).Plain();
4581 diagOutput += "\n";
4582 diagOutput += wgsl;
4583 SkDEBUGFAILF("%s", diagOutput.c_str());
4584#endif
4585 return false;
4586 }
4587
4588 if (!program.Diagnostics().empty()) {
4589 // The program contains warnings. Report them as-is.
4590 tint::diag::Formatter diagFormatter;
4591 *warnings = diagFormatter.Format(program.Diagnostics()).Plain();
4592 }
4593 return true;
4594}
4595#endif // defined(SK_ENABLE_WGSL_VALIDATION)
4596
4597bool ToWGSL(Program& program, const ShaderCaps* caps, OutputStream& out) {
4598 TRACE_EVENT0("skia.shaders", "SkSL::ToWGSL");
4599 SkASSERT(caps != nullptr);
4600
4601 program.fContext->fErrors->setSource(*program.fSource);
4602#ifdef SK_ENABLE_WGSL_VALIDATION
4603 StringStream wgsl;
4604 WGSLCodeGenerator cg(program.fContext.get(), caps, &program, &wgsl);
4605 bool result = cg.generateCode();
4606 if (result) {
4607 std::string wgslString = wgsl.str();
4608 std::string warnings;
4609 result = validate_wgsl(*program.fContext->fErrors, wgslString, &warnings);
4610 if (!warnings.empty()) {
4611 out.writeText("/* Tint reported warnings. */\n\n");
4612 }
4613 out.writeString(wgslString);
4614 }
4615#else
4616 WGSLCodeGenerator cg(program.fContext.get(), caps, &program, &out);
4617 bool result = cg.generateCode();
4618#endif
4619 program.fContext->fErrors->setSource(std::string_view());
4620
4621 return result;
4622}
4623
4624bool ToWGSL(Program& program, const ShaderCaps* caps, std::string* out) {
4626 if (!ToWGSL(program, caps, buffer)) {
4627 return false;
4628 }
4629 *out = buffer.str();
4630 return true;
4631}
4632
4633} // namespace SkSL
const char * options
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
static struct Initializer initializer
reporter
Definition: FontMgrTest.cpp:39
SkPoint pos
static float prev(float f)
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkDEBUGFAIL(message)
Definition: SkAssert.h:118
#define SkDEBUGFAILF(fmt,...)
Definition: SkAssert.h:119
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SkASSERTF(cond, fmt,...)
Definition: SkAssert.h:117
#define SK_MAKE_BITMASK_OPS(E)
Definition: SkEnumBitMask.h:68
static std::vector< SkPDFIndirectReference > sort(const THashSet< SkPDFIndirectReference > &src)
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
constexpr int SK_SAMPLEMASK_BUILTIN
Definition: SkSLCompiler.h:33
constexpr int SK_WORKGROUPID_BUILTIN
Definition: SkSLCompiler.h:41
constexpr int SK_CLOCKWISE_BUILTIN
Definition: SkSLCompiler.h:31
constexpr int SK_VERTEXID_BUILTIN
Definition: SkSLCompiler.h:35
constexpr int SK_LASTFRAGCOLOR_BUILTIN
Definition: SkSLCompiler.h:28
constexpr int SK_GLOBALINVOCATIONID_BUILTIN
Definition: SkSLCompiler.h:43
constexpr int SK_POSITION_BUILTIN
Definition: SkSLCompiler.h:37
constexpr int SK_LOCALINVOCATIONID_BUILTIN
Definition: SkSLCompiler.h:42
constexpr int SK_INSTANCEID_BUILTIN
Definition: SkSLCompiler.h:36
constexpr int SK_NUMWORKGROUPS_BUILTIN
Definition: SkSLCompiler.h:40
constexpr int SK_POINTSIZE_BUILTIN
Definition: SkSLCompiler.h:38
constexpr int SK_FRAGCOORD_BUILTIN
Definition: SkSLCompiler.h:30
constexpr int SK_LOCALINVOCATIONINDEX_BUILTIN
Definition: SkSLCompiler.h:44
constexpr int SK_SAMPLEMASKIN_BUILTIN
Definition: SkSLCompiler.h:32
constexpr size_t SkToSizeT(S x)
Definition: SkTo.h:31
SI void store(P *ptr, const T &val)
#define N
Definition: beziers.cpp:19
GLenum type
const std::unique_ptr< Type > fFloat2
const std::unique_ptr< Type > fHalf4
const std::unique_ptr< Type > fUShort
const std::unique_ptr< Type > fUInt
const std::unique_ptr< Type > fBool
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)
const BuiltinTypes & fTypes
Definition: SkSLContext.h:30
ErrorReporter * fErrors
Definition: SkSLContext.h:36
void error(Position position, std::string_view msg)
const Type & type() const
const FunctionDeclaration & declaration() const
bool is() const
Definition: SkSLIRNode.h:124
Kind kind() const
Definition: SkSLOperator.h:85
virtual void writeText(const char *s)=0
static std::unique_ptr< Expression > Make(const Context &context, Position pos, Operator op, std::unique_ptr< Expression > base)
void writeText(const char *s) override
const std::string & str() const
static bool IsIdentity(const ComponentArray &components)
static std::string MaskString(const ComponentArray &inComponents)
virtual bool visitExpression(typename T::Expression &expression)
virtual bool visitProgramElement(typename T::ProgramElement &programElement)
virtual bool isVector() const
Definition: SkSLType.h:524
virtual size_t slotCount() const
Definition: SkSLType.h:457
virtual std::string store(const std::string &value)=0
virtual std::string load()=0
std::string store(const std::string &value) override
std::string store(const std::string &value) override
SwizzleLValue(const Context &ctx, std::string name, const Type &t, const ComponentArray &c)
std::string store(const std::string &value) override
WGSLCodeGenerator(const Context *context, const ShaderCaps *caps, const Program *program, OutputStream *out)
constexpr T * begin() const
Definition: SkSpan_impl.h:90
constexpr T & back() const
Definition: SkSpan_impl.h:89
constexpr T * end() const
Definition: SkSpan_impl.h:91
constexpr bool empty() const
Definition: SkSpan_impl.h:96
constexpr size_t size() const
Definition: SkSpan_impl.h:95
void resize(int newSize)
Definition: SkFixedArray.h:101
T * push_back_n(int n)
Definition: SkTArray.h:267
int size() const
Definition: SkTArray.h:421
void reserve_exact(int n)
Definition: SkTArray.h:181
void foreach(Fn &&fn)
Definition: SkTHash.h:521
int count() const
Definition: SkTHash.h:471
V * find(const K &key) const
Definition: SkTHash.h:494
bool empty() const
Definition: SkTHash.h:474
void add(T item)
Definition: SkTHash.h:592
bool contains(const T &item) const
Definition: SkTHash.h:595
const EmbeddedViewParams * params
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
SkBitmap source
Definition: examples.cpp:28
static bool b
struct MyStruct s
struct MyStruct a[10]
EMSCRIPTEN_KEEPALIVE void empty()
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint8_t value
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
ModifierFlag
void SK_API Parse(Metadata &metadata, const SkData *data)
Definition: SkExif.cpp:182
unsigned useCenter Optional< SkMatrix > matrix
Definition: SkRecords.h:258
bool IsCompileTimeConstant(const Expression &expr)
bool IsConstantExpression(const Expression &expr)
bool SwitchCaseContainsUnconditionalExit(const Statement &stmt)
bool IsTrivialExpression(const Expression &expr)
bool HasSideEffects(const Expression &expr)
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: SkSLString.cpp:83
std::string void void auto Separator()
Definition: SkSLString.h:30
std::string void appendf(std::string *str, const char *fmt,...) SK_PRINTF_LIKE(2
Definition: SkSLString.cpp:92
std::unique_ptr< Expression > RewriteIndexedSwizzle(const Context &context, const IndexExpression &swizzle)
static constexpr char kInverse3x3[]
static bool all_arguments_constant(const ExpressionArray &arguments)
static bool binary_op_is_ambiguous_in_wgsl(Operator op)
static constexpr char kInverse2x2[]
void write_stringstream(const StringStream &s, OutputStream &out)
Definition: SkSLUtil.cpp:42
skia_private::STArray< 2, std::unique_ptr< Statement > > StatementArray
Definition: SkSLDefines.h:32
static constexpr char kInverse4x4[]
bool ToWGSL(Program &program, const ShaderCaps *caps, OutputStream &out)
static bool is_nontrivial_expression(const Expression &expr)
OperatorPrecedence
Definition: SkSLOperator.h:57
static const char * operator_name(Operator op)
SkEnumBitMask< SkSL::LayoutFlag > LayoutFlags
Definition: SkSLLayout.h:68
intptr_t word
Definition: globals.h:500
def call(args)
Definition: dom.py:159
@ kNone
Definition: layer.h:53
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
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 counts
Definition: switches.h:239
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
string programKind
Definition: minify_sksl.py:40
Definition: main.py:1
constexpr bool starts_with(std::string_view str, std::string_view prefix)
Definition: SkStringView.h:17
std::string to_string(float value)
Definition: SkSLString.cpp:50
Definition: ref_ptr.h:256
static SkString to_string(int n)
Definition: nanobench.cpp:119
@ SpvDim2D
Definition: spirv.h:143
Definition: SkMD5.cpp:134
static std::unique_ptr< Expression > LoadFloatBuffer(const Context &context, const SkSL::ShaderCaps &shaderCaps, Position position, std::unique_ptr< Expression > idx)
static bool IsRuntimeShader(ProgramKind kind)
static bool IsVertex(ProgramKind kind)
static bool IsFragment(ProgramKind kind)
static bool IsCompute(ProgramKind kind)
ElementsCollection elements() const
Definition: SkSLProgram.h:140
std::shared_ptr< Context > fContext
Definition: SkSLProgram.h:154
std::unique_ptr< ProgramUsage > fUsage
Definition: SkSLProgram.h:155
ProgramInterface fInterface
Definition: SkSLProgram.h:165
std::unique_ptr< std::string > fSource
Definition: SkSLProgram.h:152
std::unique_ptr< ProgramConfig > fConfig
Definition: SkSLProgram.h:153
skia_private::THashMap< const FunctionDeclaration *, WGSLFunctionDependencies > DepsMap
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:131