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