2 * Copyright 2022 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "src/sksl/codegen/SkSLWGSLCodeGenerator.h"
15 #include "include/core/SkSpan.h"
16 #include "include/core/SkTypes.h"
17 #include "include/private/SkBitmaskEnum.h"
18 #include "include/private/SkSLLayout.h"
19 #include "include/private/SkSLModifiers.h"
20 #include "include/private/SkSLProgramElement.h"
21 #include "include/private/SkSLProgramKind.h"
22 #include "include/private/SkSLStatement.h"
23 #include "include/private/SkSLString.h"
24 #include "include/private/SkSLSymbol.h"
25 #include "include/private/SkTArray.h"
26 #include "include/sksl/SkSLErrorReporter.h"
27 #include "include/sksl/SkSLPosition.h"
28 #include "src/sksl/SkSLBuiltinTypes.h"
29 #include "src/sksl/SkSLCompiler.h"
30 #include "src/sksl/SkSLContext.h"
31 #include "src/sksl/SkSLOutputStream.h"
32 #include "src/sksl/SkSLProgramSettings.h"
33 #include "src/sksl/SkSLStringStream.h"
34 #include "src/sksl/SkSLUtil.h"
35 #include "src/sksl/analysis/SkSLProgramVisitor.h"
36 #include "src/sksl/ir/SkSLBinaryExpression.h"
37 #include "src/sksl/ir/SkSLBlock.h"
38 #include "src/sksl/ir/SkSLConstructor.h"
39 #include "src/sksl/ir/SkSLConstructorCompound.h"
40 #include "src/sksl/ir/SkSLExpression.h"
41 #include "src/sksl/ir/SkSLExpressionStatement.h"
42 #include "src/sksl/ir/SkSLFieldAccess.h"
43 #include "src/sksl/ir/SkSLFunctionCall.h"
44 #include "src/sksl/ir/SkSLFunctionDeclaration.h"
45 #include "src/sksl/ir/SkSLFunctionDefinition.h"
46 #include "src/sksl/ir/SkSLInterfaceBlock.h"
47 #include "src/sksl/ir/SkSLLiteral.h"
48 #include "src/sksl/ir/SkSLProgram.h"
49 #include "src/sksl/ir/SkSLReturnStatement.h"
50 #include "src/sksl/ir/SkSLSwizzle.h"
51 #include "src/sksl/ir/SkSLSymbolTable.h"
52 #include "src/sksl/ir/SkSLType.h"
53 #include "src/sksl/ir/SkSLVarDeclarations.h"
54 #include "src/sksl/ir/SkSLVariable.h"
55 #include "src/sksl/ir/SkSLVariableReference.h"
57 // TODO(skia:13092): This is a temporary debug feature. Remove when the implementation is
58 // complete and this is no longer needed.
64 // See https://www.w3.org/TR/WGSL/#memory-view-types
65 enum class PtrAddressSpace {
71 std::string_view pipeline_struct_prefix(ProgramKind kind) {
72 if (ProgramConfig::IsVertex(kind)) {
75 if (ProgramConfig::IsFragment(kind)) {
81 std::string_view address_space_to_str(PtrAddressSpace addressSpace) {
82 switch (addressSpace) {
83 case PtrAddressSpace::kFunction:
85 case PtrAddressSpace::kPrivate:
87 case PtrAddressSpace::kStorage:
90 SkDEBUGFAIL("unsupported ptr address space");
94 std::string_view to_scalar_type(const Type& type) {
95 SkASSERT(type.typeKind() == Type::TypeKind::kScalar);
96 switch (type.numberKind()) {
97 // Floating-point numbers in WebGPU currently always have 32-bit footprint and
98 // relaxed-precision is not supported without extensions. f32 is the only floating-point
99 // number type in WGSL (see the discussion on https://github.com/gpuweb/gpuweb/issues/658).
100 case Type::NumberKind::kFloat:
102 case Type::NumberKind::kSigned:
104 case Type::NumberKind::kUnsigned:
106 case Type::NumberKind::kBoolean:
108 case Type::NumberKind::kNonnumeric:
116 // Convert a SkSL type to a WGSL type. Handles all plain types except structure types
117 // (see https://www.w3.org/TR/WGSL/#plain-types-section).
118 std::string to_wgsl_type(const Type& type) {
119 // TODO(skia:13092): Handle array, matrix, sampler types.
120 switch (type.typeKind()) {
121 case Type::TypeKind::kScalar:
122 return std::string(to_scalar_type(type));
123 case Type::TypeKind::kVector:
124 return "vec" + std::to_string(type.columns()) + "<" +
125 std::string(to_scalar_type(type.componentType())) + ">";
129 return std::string(type.name());
132 std::string to_ptr_type(const Type& type,
133 PtrAddressSpace addressSpace = PtrAddressSpace::kFunction) {
134 return "ptr<" + std::string(address_space_to_str(addressSpace)) + ", " + to_wgsl_type(type) +
138 std::string_view wgsl_builtin_name(WGSLCodeGenerator::Builtin builtin) {
139 using Builtin = WGSLCodeGenerator::Builtin;
141 case Builtin::kVertexIndex:
142 return "vertex_index";
143 case Builtin::kInstanceIndex:
144 return "instance_index";
145 case Builtin::kPosition:
147 case Builtin::kFrontFacing:
148 return "front_facing";
149 case Builtin::kSampleIndex:
150 return "sample_index";
151 case Builtin::kFragDepth:
153 case Builtin::kSampleMask:
154 return "sample_mask";
155 case Builtin::kLocalInvocationId:
156 return "local_invocation_id";
157 case Builtin::kLocalInvocationIndex:
158 return "local_invocation_index";
159 case Builtin::kGlobalInvocationId:
160 return "global_invocation_id";
161 case Builtin::kWorkgroupId:
162 return "workgroup_id";
163 case Builtin::kNumWorkgroups:
164 return "num_workgroups";
169 SkDEBUGFAIL("unsupported builtin");
170 return "unsupported";
173 std::string_view wgsl_builtin_type(WGSLCodeGenerator::Builtin builtin) {
174 using Builtin = WGSLCodeGenerator::Builtin;
176 case Builtin::kVertexIndex:
178 case Builtin::kInstanceIndex:
180 case Builtin::kPosition:
182 case Builtin::kFrontFacing:
184 case Builtin::kSampleIndex:
186 case Builtin::kFragDepth:
188 case Builtin::kSampleMask:
190 case Builtin::kLocalInvocationId:
192 case Builtin::kLocalInvocationIndex:
194 case Builtin::kGlobalInvocationId:
196 case Builtin::kWorkgroupId:
198 case Builtin::kNumWorkgroups:
204 SkDEBUGFAIL("unsupported builtin");
205 return "unsupported";
208 // Some built-in variables have a type that differs from their SkSL counterpart (e.g. signed vs
209 // unsigned integer). We handle these cases with an explicit type conversion during a variable
210 // reference. Returns the WGSL type of the conversion target if conversion is needed, otherwise
211 // returns std::nullopt.
212 std::optional<std::string_view> needs_builtin_type_conversion(const Variable& v) {
213 switch (v.modifiers().fLayout.fBuiltin) {
214 case SK_VERTEXID_BUILTIN:
215 case SK_INSTANCEID_BUILTIN:
223 // Map a SkSL builtin flag to a WGSL builtin kind. Returns std::nullopt if `builtin` is not
224 // not supported for WGSL.
226 // Also see //src/sksl/sksl_vert.sksl and //src/sksl/sksl_frag.sksl for supported built-ins.
227 std::optional<WGSLCodeGenerator::Builtin> builtin_from_sksl_name(int builtin) {
228 using Builtin = WGSLCodeGenerator::Builtin;
230 case SK_POSITION_BUILTIN:
232 case SK_FRAGCOORD_BUILTIN:
233 return {Builtin::kPosition};
234 case SK_VERTEXID_BUILTIN:
235 return {Builtin::kVertexIndex};
236 case SK_INSTANCEID_BUILTIN:
237 return {Builtin::kInstanceIndex};
238 case SK_CLOCKWISE_BUILTIN:
239 // TODO(skia:13092): While `front_facing` is the corresponding built-in, it does not
240 // imply a particular winding order. We correctly compute the face orientation based
241 // on how Skia configured the render pipeline for all references to this built-in
242 // variable (see `SkSL::Program::Inputs::fUseFlipRTUniform`).
243 return {Builtin::kFrontFacing};
250 std::shared_ptr<SymbolTable> top_level_symbol_table(const FunctionDefinition& f) {
251 return f.body()->as<Block>().symbolTable()->fParent;
254 const char* delimiter_to_str(WGSLCodeGenerator::Delimiter delimiter) {
255 using Delim = WGSLCodeGenerator::Delimiter;
259 case Delim::kSemicolon:
268 // FunctionDependencyResolver visits the IR tree rooted at a particular function definition and
269 // computes that function's dependencies on pipeline stage IO parameters. These are later used to
270 // synthesize arguments when writing out function definitions.
271 class FunctionDependencyResolver : public ProgramVisitor {
273 using Deps = WGSLCodeGenerator::FunctionDependencies;
274 using DepsMap = WGSLCodeGenerator::ProgramRequirements::DepsMap;
276 FunctionDependencyResolver(const Program* p,
277 const FunctionDeclaration* f,
278 DepsMap* programDependencyMap)
279 : fProgram(p), fFunction(f), fDependencyMap(programDependencyMap) {}
283 this->visit(*fProgram);
288 bool visitProgramElement(const ProgramElement& p) override {
289 // Only visit the program that matches the requested function.
290 if (p.is<FunctionDefinition>() && &p.as<FunctionDefinition>().declaration() == fFunction) {
291 return INHERITED::visitProgramElement(p);
293 // Continue visiting other program elements.
297 bool visitExpression(const Expression& e) override {
298 if (e.is<VariableReference>()) {
299 const VariableReference& v = e.as<VariableReference>();
300 const Modifiers& modifiers = v.variable()->modifiers();
301 if (v.variable()->storage() == Variable::Storage::kGlobal) {
302 if (modifiers.fFlags & Modifiers::kIn_Flag) {
303 fDeps |= Deps::kPipelineInputs;
305 if (modifiers.fFlags & Modifiers::kOut_Flag) {
306 fDeps |= Deps::kPipelineOutputs;
309 } else if (e.is<FunctionCall>()) {
310 // The current function that we're processing (`fFunction`) inherits the dependencies of
311 // functions that it makes calls to, because the pipeline stage IO parameters need to be
312 // passed down as an argument.
313 const FunctionCall& callee = e.as<FunctionCall>();
315 // Don't process a function again if we have already resolved it.
316 Deps* found = fDependencyMap->find(&callee.function());
320 // Store the dependencies that have been discovered for the current function so far.
321 // If `callee` directly or indirectly calls the current function, then this value
322 // will prevent an infinite recursion.
323 fDependencyMap->set(fFunction, fDeps);
325 // Separately traverse the called function's definition and determine its
327 FunctionDependencyResolver resolver(fProgram, &callee.function(), fDependencyMap);
328 Deps calleeDeps = resolver.resolve();
330 // Store the callee's dependencies in the global map to avoid processing
331 // the function again for future calls.
332 fDependencyMap->set(&callee.function(), calleeDeps);
334 // Add to the current function's dependencies.
338 return INHERITED::visitExpression(e);
341 const Program* const fProgram;
342 const FunctionDeclaration* const fFunction;
343 DepsMap* const fDependencyMap;
344 Deps fDeps = Deps::kNone;
346 using INHERITED = ProgramVisitor;
349 WGSLCodeGenerator::ProgramRequirements resolve_program_requirements(const Program* program) {
350 bool mainNeedsCoordsArgument = false;
351 WGSLCodeGenerator::ProgramRequirements::DepsMap dependencies;
353 for (const ProgramElement* e : program->elements()) {
354 if (!e->is<FunctionDefinition>()) {
358 const FunctionDeclaration& decl = e->as<FunctionDefinition>().declaration();
360 for (const Variable* v : decl.parameters()) {
361 if (v->modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) {
362 mainNeedsCoordsArgument = true;
368 FunctionDependencyResolver resolver(program, &decl, &dependencies);
369 dependencies.set(&decl, resolver.resolve());
372 return WGSLCodeGenerator::ProgramRequirements(std::move(dependencies), mainNeedsCoordsArgument);
375 int count_pipeline_inputs(const Program* program) {
377 for (const ProgramElement* e : program->elements()) {
378 if (e->is<GlobalVarDeclaration>()) {
380 e->as<GlobalVarDeclaration>().declaration()->as<VarDeclaration>().var();
381 if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
384 } else if (e->is<InterfaceBlock>()) {
385 const Variable& v = e->as<InterfaceBlock>().variable();
386 if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
396 bool WGSLCodeGenerator::generateCode() {
397 // The resources of a WGSL program are structured in the following way:
398 // - Vertex and fragment stage attribute inputs and outputs are bundled
399 // inside synthetic structs called VSIn/VSOut/FSIn/FSOut.
400 // - All uniform and storage type resources are declared in global scope.
401 this->preprocessProgram();
405 AutoOutputStream outputToHeader(this, &header, &fIndentation);
406 // TODO(skia:13092): Implement the following:
407 // - struct definitions
408 // - global uniform/storage resource declarations, including interface blocks.
409 this->writeStageInputStruct();
410 this->writeStageOutputStruct();
414 AutoOutputStream outputToBody(this, &body, &fIndentation);
415 for (const ProgramElement* e : fProgram.elements()) {
416 this->writeProgramElement(*e);
419 // TODO(skia:13092): This is a temporary debug feature. Remove when the implementation is
420 // complete and this is no longer needed.
422 this->writeLine("\n----------");
423 this->writeLine("Source IR:\n");
424 for (const ProgramElement* e : fProgram.elements()) {
425 this->writeLine(e->description().c_str());
430 write_stringstream(header, *fOut);
431 write_stringstream(body, *fOut);
432 fContext.fErrors->reportPendingErrors(Position());
433 return fContext.fErrors->errorCount() == 0;
436 void WGSLCodeGenerator::preprocessProgram() {
437 fRequirements = resolve_program_requirements(&fProgram);
438 fPipelineInputCount = count_pipeline_inputs(&fProgram);
441 void WGSLCodeGenerator::write(std::string_view s) {
446 for (int i = 0; i < fIndentation; i++) {
447 fOut->writeText(" ");
450 fOut->writeText(std::string(s).c_str());
451 fAtLineStart = false;
454 void WGSLCodeGenerator::writeLine(std::string_view s) {
456 fOut->writeText("\n");
460 void WGSLCodeGenerator::finishLine() {
466 void WGSLCodeGenerator::writeName(std::string_view name) {
467 // Add underscore before name to avoid conflict with reserved words.
468 if (fReservedWords.contains(name)) {
474 void WGSLCodeGenerator::writePipelineIODeclaration(Modifiers modifiers,
476 std::string_view name,
477 Delimiter delimiter) {
478 // In WGSL, an entry-point IO parameter is "one of either a built-in value or
479 // assigned a location". However, some SkSL declarations, specifically sk_FragColor, can
480 // contain both a location and a builtin modifier. In addition, WGSL doesn't have a built-in
481 // equivalent for sk_FragColor as it relies on the user-defined location for a render
484 // Instead of special-casing sk_FragColor, we just give higher precedence to a location
485 // modifier if a declaration happens to both have a location and it's a built-in.
488 // https://www.w3.org/TR/WGSL/#input-output-locations
489 // https://www.w3.org/TR/WGSL/#attribute-location
490 // https://www.w3.org/TR/WGSL/#builtin-inputs-outputs
491 int location = modifiers.fLayout.fLocation;
493 this->writeUserDefinedIODecl(type, name, location, delimiter);
494 } else if (modifiers.fLayout.fBuiltin >= 0) {
495 auto builtin = builtin_from_sksl_name(modifiers.fLayout.fBuiltin);
496 if (builtin.has_value()) {
497 this->writeBuiltinIODecl(type, name, *builtin, delimiter);
502 void WGSLCodeGenerator::writeUserDefinedIODecl(const Type& type,
503 std::string_view name,
505 Delimiter delimiter) {
506 this->write("@location(" + std::to_string(location) + ") ");
508 // "User-defined IO of scalar or vector integer type must always be specified as
509 // @interpolate(flat)" (see https://www.w3.org/TR/WGSL/#interpolation)
510 if (type.isInteger() || (type.isVector() && type.componentType().isInteger())) {
511 this->write("@interpolate(flat) ");
514 this->writeName(name);
515 this->write(": " + to_wgsl_type(type));
516 this->writeLine(delimiter_to_str(delimiter));
519 void WGSLCodeGenerator::writeBuiltinIODecl(const Type& type,
520 std::string_view name,
522 Delimiter delimiter) {
523 this->write("@builtin(");
524 this->write(wgsl_builtin_name(builtin));
527 this->writeName(name);
529 this->write(wgsl_builtin_type(builtin));
530 this->writeLine(delimiter_to_str(delimiter));
533 void WGSLCodeGenerator::writeFunction(const FunctionDefinition& f) {
534 this->writeFunctionDeclaration(f.declaration());
536 this->writeBlock(f.body()->as<Block>());
538 if (f.declaration().isMain()) {
539 // We just emitted the user-defined main function. Next, we generate a program entry point
540 // that calls the user-defined main.
541 this->writeEntryPoint(f);
545 void WGSLCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) {
547 this->write(f.mangledName());
549 const char* separator = "";
550 FunctionDependencies* deps = fRequirements.dependencies.find(&f);
552 std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
553 if (structNamePrefix.length() != 0) {
554 if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) {
556 this->write("_stageIn: ");
557 this->write(structNamePrefix);
560 if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) {
561 this->write(separator);
563 this->write("_stageOut: ptr<function, ");
564 this->write(structNamePrefix);
569 for (const Variable* param : f.parameters()) {
570 this->write(separator);
572 this->writeName(param->name());
575 // Declare an "out" function parameter as a pointer.
576 if (param->modifiers().fFlags & Modifiers::kOut_Flag) {
577 this->write(to_ptr_type(param->type()));
579 this->write(to_wgsl_type(param->type()));
583 if (!f.returnType().isVoid()) {
585 this->write(to_wgsl_type(f.returnType()));
589 void WGSLCodeGenerator::writeEntryPoint(const FunctionDefinition& main) {
590 SkASSERT(main.declaration().isMain());
592 // The input and output parameters for a vertex/fragment stage entry point function have the
593 // FSIn/FSOut/VSIn/VSOut struct types that have been synthesized in generateCode(). An entry
594 // point always has the same signature and acts as a trampoline to the user-defined main
596 std::string outputType;
597 if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) {
598 this->write("@stage(vertex) fn vertexMain(");
599 if (fPipelineInputCount > 0) {
600 this->write("_stageIn: VSIn");
602 this->writeLine(") -> VSOut {");
603 outputType = "VSOut";
604 } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
605 this->write("@stage(fragment) fn fragmentMain(");
606 if (fPipelineInputCount > 0) {
607 this->write("_stageIn: FSIn");
609 this->writeLine(") -> FSOut {");
610 outputType = "FSOut";
612 fContext.fErrors->error(Position(), "program kind not supported");
616 // Declare the stage output struct.
618 this->write("var _stageOut: ");
619 this->write(outputType);
620 this->writeLine(";");
622 // Generate assignment to sk_FragColor built-in if the user-defined main returns a color.
623 if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) {
624 auto symbolTable = top_level_symbol_table(main);
625 const Symbol* symbol = (*symbolTable)["sk_FragColor"];
627 if (main.declaration().returnType().matches(symbol->type())) {
628 this->write("_stageOut.sk_FragColor = ");
632 // Generate the function call to the user-defined main:
633 this->write(main.declaration().mangledName());
635 const char* separator = "";
636 FunctionDependencies* deps = fRequirements.dependencies.find(&main.declaration());
638 if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) {
640 this->write("_stageIn");
642 if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) {
643 this->write(separator);
645 this->write("&_stageOut");
648 // TODO(armansito): Handle arbitrary parameters.
649 if (main.declaration().parameters().size() != 0) {
650 const Variable* v = main.declaration().parameters()[0];
651 const Type& type = v->type();
652 if (v->modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) {
653 if (!type.matches(*fContext.fTypes.fFloat2)) {
654 fContext.fErrors->error(
656 "main function has unsupported parameter: " + type.description());
660 this->write(separator);
662 this->write("_stageIn.sk_FragCoord.xy");
665 this->writeLine(");");
666 this->writeLine("return _stageOut;");
669 this->writeLine("}");
672 void WGSLCodeGenerator::writeStatement(const Statement& s) {
674 case Statement::Kind::kBlock:
675 this->writeBlock(s.as<Block>());
677 case Statement::Kind::kExpression:
678 this->writeExpressionStatement(s.as<ExpressionStatement>());
680 case Statement::Kind::kReturn:
681 this->writeReturnStatement(s.as<ReturnStatement>());
683 case Statement::Kind::kVarDeclaration:
684 this->writeVarDeclaration(s.as<VarDeclaration>());
687 SkDEBUGFAILF("unsupported statement (kind: %d) %s", s.kind(), s.description().c_str());
692 void WGSLCodeGenerator::writeStatements(const StatementArray& statements) {
693 for (const auto& s : statements) {
695 this->writeStatement(*s);
701 void WGSLCodeGenerator::writeBlock(const Block& b) {
702 // Write scope markers if this block is a scope, or if the block is empty (since we need to emit
703 // something here to make the code valid).
704 bool isScope = b.isScope() || b.isEmpty();
706 this->writeLine("{");
709 this->writeStatements(b.children());
712 this->writeLine("}");
716 void WGSLCodeGenerator::writeExpressionStatement(const ExpressionStatement& s) {
717 if (s.expression()->hasSideEffects()) {
718 this->writeExpression(*s.expression(), Precedence::kTopLevel);
723 void WGSLCodeGenerator::writeReturnStatement(const ReturnStatement& s) {
724 this->write("return");
725 if (s.expression()) {
727 this->writeExpression(*s.expression(), Precedence::kTopLevel);
732 void WGSLCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl) {
733 bool isConst = varDecl.var().modifiers().fFlags & Modifiers::kConst_Flag;
739 this->writeName(varDecl.var().name());
741 this->write(to_wgsl_type(varDecl.var().type()));
743 if (varDecl.value()) {
745 this->writeExpression(*varDecl.value(), Precedence::kTopLevel);
746 } else if (isConst) {
747 SkDEBUGFAILF("A let-declared constant must specify a value");
753 void WGSLCodeGenerator::writeExpression(const Expression& e, Precedence parentPrecedence) {
755 case Expression::Kind::kBinary:
756 this->writeBinaryExpression(e.as<BinaryExpression>(), parentPrecedence);
758 case Expression::Kind::kConstructorCompound:
759 this->writeConstructorCompound(e.as<ConstructorCompound>(), parentPrecedence);
761 case Expression::Kind::kConstructorCompoundCast:
762 case Expression::Kind::kConstructorScalarCast:
763 this->writeAnyConstructor(e.asAnyConstructor(), parentPrecedence);
765 case Expression::Kind::kFieldAccess:
766 this->writeFieldAccess(e.as<FieldAccess>());
768 case Expression::Kind::kLiteral:
769 this->writeLiteral(e.as<Literal>());
771 case Expression::Kind::kSwizzle:
772 this->writeSwizzle(e.as<Swizzle>());
774 case Expression::Kind::kVariableReference:
775 this->writeVariableReference(e.as<VariableReference>());
778 SkDEBUGFAILF("unsupported expression (kind: %d) %s",
779 static_cast<int>(e.kind()),
780 e.description().c_str());
785 void WGSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
786 Precedence parentPrecedence) {
787 const Expression& left = *b.left();
788 const Expression& right = *b.right();
789 Operator op = b.getOperator();
790 Precedence precedence = op.getBinaryPrecedence();
791 bool needParens = precedence >= parentPrecedence;
797 // TODO(skia:13092): Correctly handle the case when lhs is a pointer.
799 this->writeExpression(left, precedence);
800 this->write(op.operatorName());
801 this->writeExpression(right, precedence);
808 void WGSLCodeGenerator::writeFieldAccess(const FieldAccess& f) {
809 const Type::Field* field = &f.base()->type().fields()[f.fieldIndex()];
810 if (FieldAccess::OwnerKind::kDefault == f.ownerKind()) {
811 this->writeExpression(*f.base(), Precedence::kPostfix);
814 // We are accessing a field in an anonymous interface block. If the field refers to a
815 // pipeline IO parameter, then we access it via the synthesized IO structs. We make an
816 // explicit exception for `sk_PointSize` which we declare as a placeholder variable in
817 // global scope as it is not supported by WebGPU as a pipeline IO parameter (see comments
818 // in `writeStageOutputStruct`).
819 const Variable& v = *f.base()->as<VariableReference>().variable();
820 if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
821 this->write("_stageIn.");
822 } else if (v.modifiers().fFlags & Modifiers::kOut_Flag &&
823 field->fModifiers.fLayout.fBuiltin != SK_POINTSIZE_BUILTIN) {
824 this->write("(*_stageOut).");
826 // TODO(skia:13902): Reference the variable using the base name used for its
827 // uniform/storage block global declaration.
830 this->writeName(field->fName);
833 void WGSLCodeGenerator::writeLiteral(const Literal& l) {
834 const Type& type = l.type();
835 if (type.isFloat()) {
836 this->write(skstd::to_string(l.floatValue()));
839 if (type.isBoolean()) {
840 this->write(l.boolValue() ? "true" : "false");
843 SkASSERT(type.isInteger());
844 if (type.matches(*fContext.fTypes.fUInt)) {
845 this->write(std::to_string(l.intValue() & 0xffffffff));
847 } else if (type.matches(*fContext.fTypes.fUShort)) {
848 this->write(std::to_string(l.intValue() & 0xffff));
851 this->write(std::to_string(l.intValue()));
855 void WGSLCodeGenerator::writeSwizzle(const Swizzle& swizzle) {
856 this->writeExpression(*swizzle.base(), Precedence::kPostfix);
858 for (int c : swizzle.components()) {
859 SkASSERT(c >= 0 && c <= 3);
860 this->write(&("x\0y\0z\0w\0"[c * 2]));
864 void WGSLCodeGenerator::writeVariableReference(const VariableReference& r) {
865 // TODO(skia:13902): Correctly handle function parameters.
866 // TODO(skia:13902): Correctly handle RTflip for built-ins.
867 const Variable& v = *r.variable();
869 // Insert a conversion expression if this is a built-in variable whose type differs from the
871 std::optional<std::string_view> conversion = needs_builtin_type_conversion(v);
872 if (conversion.has_value()) {
873 this->write(*conversion);
876 if (v.storage() == Variable::Storage::kGlobal) {
877 if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
878 this->write("_stageIn.");
879 } else if (v.modifiers().fFlags & Modifiers::kOut_Flag) {
880 this->write("(*_stageOut).");
884 this->writeName(v.name());
886 if (conversion.has_value()) {
891 void WGSLCodeGenerator::writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence) {
892 this->write(to_wgsl_type(c.type()));
894 const char* separator = "";
895 for (const auto& e : c.argumentSpan()) {
896 this->write(separator);
898 this->writeExpression(*e, Precedence::kSequence);
903 void WGSLCodeGenerator::writeConstructorCompound(const ConstructorCompound& c,
904 Precedence parentPrecedence) {
905 // TODO(skia:13092): Support matrix constructors
906 if (c.type().isVector()) {
907 this->writeConstructorCompoundVector(c, parentPrecedence);
909 fContext.fErrors->error(c.fPosition, "unsupported compound constructor");
913 void WGSLCodeGenerator::writeConstructorCompoundVector(const ConstructorCompound& c,
914 Precedence parentPrecedence) {
915 // TODO(skia:13092): WGSL supports constructing vectors from a mix of scalars and vectors but
916 // not matrices. SkSL supports vec4(mat2x2) which we need to handle here
917 // (see https://www.w3.org/TR/WGSL/#type-constructor-expr).
918 this->writeAnyConstructor(c, parentPrecedence);
921 void WGSLCodeGenerator::writeProgramElement(const ProgramElement& e) {
923 case ProgramElement::Kind::kExtension:
924 // TODO(skia:13092): WGSL supports extensions via the "enable" directive
925 // (https://www.w3.org/TR/WGSL/#language-extensions). While we could easily emit this
926 // directive, we should first ensure that all possible SkSL extension names are
927 // converted to their appropriate WGSL extension. Currently there are no known supported
928 // WGSL extensions aside from the hypotheticals listed in the spec.
930 case ProgramElement::Kind::kGlobalVar:
931 // All global declarations are handled explicitly as the "program header" in
934 case ProgramElement::Kind::kInterfaceBlock:
935 // All interface block declarations are handled explicitly as the "program header" in
938 case ProgramElement::Kind::kStructDefinition:
939 // All struct type declarations are handled explicitly as the "program header" in
942 case ProgramElement::Kind::kFunctionPrototype:
943 // A WGSL function declaration must contain its body and the function name is in scope
944 // for the entire program (see https://www.w3.org/TR/WGSL/#function-declaration and
945 // https://www.w3.org/TR/WGSL/#declaration-and-scope).
947 // As such, we don't emit function prototypes.
949 case ProgramElement::Kind::kFunction:
950 this->writeFunction(e.as<FunctionDefinition>());
953 SkDEBUGFAILF("unsupported program element: %s\n", e.description().c_str());
958 void WGSLCodeGenerator::writeStageInputStruct() {
959 std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
960 if (structNamePrefix.empty()) {
961 // There's no need to declare pipeline stage outputs.
965 // It is illegal to declare a struct with no members.
966 if (fPipelineInputCount < 1) {
970 this->write("struct ");
971 this->write(structNamePrefix);
972 this->writeLine("In {");
975 bool declaredFragCoordsBuiltin = false;
976 for (const ProgramElement* e : fProgram.elements()) {
977 if (e->is<GlobalVarDeclaration>()) {
979 e->as<GlobalVarDeclaration>().declaration()->as<VarDeclaration>().var();
980 if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
981 this->writePipelineIODeclaration(
982 v.modifiers(), v.type(), v.name(), Delimiter::kComma);
983 if (v.modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
984 declaredFragCoordsBuiltin = true;
987 } else if (e->is<InterfaceBlock>()) {
988 const Variable& v = e->as<InterfaceBlock>().variable();
989 // Merge all the members of `in` interface blocks to the input struct, which are
990 // specified as either "builtin" or with a "layout(location=".
992 // TODO(armansito): Is it legal to have an interface block without a storage qualifier
993 // but with members that have individual storage qualifiers?
994 if (v.modifiers().fFlags & Modifiers::kIn_Flag) {
995 for (const auto& f : v.type().fields()) {
996 this->writePipelineIODeclaration(
997 f.fModifiers, *f.fType, f.fName, Delimiter::kComma);
998 if (f.fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
999 declaredFragCoordsBuiltin = true;
1006 if (ProgramConfig::IsFragment(fProgram.fConfig->fKind) &&
1007 fRequirements.mainNeedsCoordsArgument && !declaredFragCoordsBuiltin) {
1008 this->writeLine("@builtin(position) sk_FragCoord: vec4<f32>,");
1012 this->writeLine("};");
1015 void WGSLCodeGenerator::writeStageOutputStruct() {
1016 std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind);
1017 if (structNamePrefix.empty()) {
1018 // There's no need to declare pipeline stage outputs.
1022 this->write("struct ");
1023 this->write(structNamePrefix);
1024 this->writeLine("Out {");
1027 // TODO(skia:13092): Remember all variables that are added to the output struct here so they
1028 // can be referenced correctly when handling variable references.
1029 bool declaredPositionBuiltin = false;
1030 bool requiresPointSizeBuiltin = false;
1031 for (const ProgramElement* e : fProgram.elements()) {
1032 if (e->is<GlobalVarDeclaration>()) {
1034 e->as<GlobalVarDeclaration>().declaration()->as<VarDeclaration>().var();
1035 if (v.modifiers().fFlags & Modifiers::kOut_Flag) {
1036 this->writePipelineIODeclaration(
1037 v.modifiers(), v.type(), v.name(), Delimiter::kComma);
1039 } else if (e->is<InterfaceBlock>()) {
1040 const Variable& v = e->as<InterfaceBlock>().variable();
1041 // Merge all the members of `out` interface blocks to the output struct, which are
1042 // specified as either "builtin" or with a "layout(location=".
1044 // TODO(armansito): Is it legal to have an interface block without a storage qualifier
1045 // but with members that have individual storage qualifiers?
1046 if (v.modifiers().fFlags & Modifiers::kOut_Flag) {
1047 for (const auto& f : v.type().fields()) {
1048 this->writePipelineIODeclaration(
1049 f.fModifiers, *f.fType, f.fName, Delimiter::kComma);
1050 if (f.fModifiers.fLayout.fBuiltin == SK_POSITION_BUILTIN) {
1051 declaredPositionBuiltin = true;
1052 } else if (f.fModifiers.fLayout.fBuiltin == SK_POINTSIZE_BUILTIN) {
1053 // sk_PointSize is explicitly not supported by `builtin_from_sksl_name` so
1054 // writePipelineIODeclaration will never write it. We mark it here if the
1055 // declaration is needed so we can synthesize it below.
1056 requiresPointSizeBuiltin = true;
1063 // A vertex program must include the `position` builtin in its entry point return type.
1064 if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) && !declaredPositionBuiltin) {
1065 this->writeLine("@builtin(position) sk_Position: vec4<f32>,");
1069 this->writeLine("};");
1071 // In WebGPU/WGSL, the vertex stage does not support a point-size output and the size
1072 // of a point primitive is always 1 pixel (see https://github.com/gpuweb/gpuweb/issues/332).
1074 // There isn't anything we can do to emulate this correctly at this stage so we
1075 // synthesize a placeholder variable that has no effect. Programs should not rely on
1076 // sk_PointSize when using the Dawn backend.
1077 if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) && requiresPointSizeBuiltin) {
1078 this->writeLine("/* unsupported */ var<private> sk_PointSize: f32;");