| /* |
| * Copyright 2022 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #ifndef SKSL_WGSLCODEGENERATOR |
| #define SKSL_WGSLCODEGENERATOR |
| |
| #include "include/core/SkSpan.h" |
| #include "include/private/base/SkTArray.h" |
| #include "src/base/SkEnumBitMask.h" |
| #include "src/core/SkTHash.h" |
| #include "src/sksl/SkSLDefines.h" |
| #include "src/sksl/SkSLMemoryLayout.h" |
| #include "src/sksl/SkSLOperator.h" |
| #include "src/sksl/SkSLStringStream.h" |
| #include "src/sksl/codegen/SkSLCodeGenerator.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| namespace SkSL { |
| |
| class AnyConstructor; |
| class BinaryExpression; |
| class Block; |
| class Context; |
| class ConstructorCompound; |
| class ConstructorDiagonalMatrix; |
| class ConstructorMatrixResize; |
| class DoStatement; |
| class Expression; |
| struct Field; |
| class FieldAccess; |
| class ForStatement; |
| class FunctionCall; |
| class FunctionDeclaration; |
| class FunctionDefinition; |
| class GlobalVarDeclaration; |
| class IfStatement; |
| class IndexExpression; |
| class InterfaceBlock; |
| enum IntrinsicKind : int8_t; |
| struct Layout; |
| class Literal; |
| class ModifiersDeclaration; |
| class OutputStream; |
| class PostfixExpression; |
| class PrefixExpression; |
| struct Program; |
| class ProgramElement; |
| class ReturnStatement; |
| class Statement; |
| class StructDefinition; |
| class SwitchCase; |
| class SwitchStatement; |
| class Swizzle; |
| class TernaryExpression; |
| class Type; |
| class VarDeclaration; |
| class Variable; |
| class VariableReference; |
| |
| // Represents a function's dependencies that are not accessible in global scope. For instance, |
| // pipeline stage input and output parameters must be passed in as an argument. |
| // |
| // This is a bitmask enum. (It would be inside `class WGSLCodeGenerator`, but this leads to build |
| // errors in MSVC.) |
| enum class WGSLFunctionDependency : uint8_t { |
| kNone = 0, |
| kPipelineInputs = 1 << 0, |
| kPipelineOutputs = 1 << 1, |
| }; |
| using WGSLFunctionDependencies = SkEnumBitMask<WGSLFunctionDependency>; |
| |
| } // namespace SkSL |
| |
| SK_MAKE_BITMASK_OPS(SkSL::WGSLFunctionDependency) |
| |
| namespace SkSL { |
| |
| /** |
| * Convert a Program into WGSL code. |
| */ |
| class WGSLCodeGenerator : public CodeGenerator { |
| public: |
| // See https://www.w3.org/TR/WGSL/#builtin-values |
| enum class Builtin { |
| // Vertex stage: |
| kVertexIndex, // input |
| kInstanceIndex, // input |
| kPosition, // output, fragment stage input |
| |
| // Fragment stage: |
| kFrontFacing, // input |
| kSampleIndex, // input |
| kFragDepth, // output |
| kSampleMaskIn, // input |
| kSampleMask, // output |
| |
| // Compute stage: |
| kLocalInvocationId, // input |
| kLocalInvocationIndex, // input |
| kGlobalInvocationId, // input |
| kWorkgroupId, // input |
| kNumWorkgroups, // input |
| }; |
| |
| |
| // Variable declarations can be terminated by: |
| // - comma (","), e.g. in struct member declarations or function parameters |
| // - semicolon (";"), e.g. in function scope variables |
| // A "none" option is provided to skip the delimiter when not needed, e.g. at the end of a list |
| // of declarations. |
| enum class Delimiter { |
| kComma, |
| kSemicolon, |
| kNone, |
| }; |
| |
| struct ProgramRequirements { |
| using DepsMap = skia_private::THashMap<const FunctionDeclaration*, |
| WGSLFunctionDependencies>; |
| |
| ProgramRequirements() = default; |
| ProgramRequirements(DepsMap dependencies) : dependencies(std::move(dependencies)) {} |
| |
| // Mappings used to synthesize function parameters according to dependencies on pipeline |
| // input/output variables. |
| DepsMap dependencies; |
| }; |
| |
| WGSLCodeGenerator(const Context* context, const Program* program, OutputStream* out) |
| : INHERITED(context, program, out) {} |
| |
| bool generateCode() override; |
| |
| private: |
| using INHERITED = CodeGenerator; |
| using Precedence = OperatorPrecedence; |
| |
| // Called by generateCode() as the first step. |
| void preprocessProgram(); |
| |
| // Write output content while correctly handling indentation. |
| void write(std::string_view s); |
| void writeLine(std::string_view s = std::string_view()); |
| void finishLine(); |
| |
| // Helpers to declare a pipeline stage IO parameter declaration. |
| void writePipelineIODeclaration(const Layout& layout, |
| const Type& type, |
| std::string_view name, |
| Delimiter delimiter); |
| void writeUserDefinedIODecl(const Layout& layout, |
| const Type& type, |
| std::string_view name, |
| Delimiter delimiter); |
| void writeBuiltinIODecl(const Type& type, |
| std::string_view name, |
| Builtin builtin, |
| Delimiter delimiter); |
| void writeVariableDecl(const Layout& layout, |
| const Type& type, |
| std::string_view name, |
| Delimiter delimiter); |
| |
| // Write a function definition. |
| void writeFunction(const FunctionDefinition& f); |
| void writeFunctionDeclaration(const FunctionDeclaration& f, |
| SkSpan<const bool> paramNeedsDedicatedStorage); |
| |
| // Write the program entry point. |
| void writeEntryPoint(const FunctionDefinition& f); |
| |
| // Writers for supported statement types. |
| void writeStatement(const Statement& s); |
| void writeStatements(const StatementArray& statements); |
| void writeBlock(const Block& b); |
| void writeDoStatement(const DoStatement& expr); |
| void writeExpressionStatement(const Expression& expr); |
| void writeForStatement(const ForStatement& s); |
| void writeIfStatement(const IfStatement& s); |
| void writeReturnStatement(const ReturnStatement& s); |
| void writeSwitchStatement(const SwitchStatement& s); |
| void writeSwitchCases(SkSpan<const SwitchCase* const> cases); |
| void writeEmulatedSwitchFallthroughCases(SkSpan<const SwitchCase* const> cases, |
| std::string_view switchValue); |
| void writeSwitchCaseList(SkSpan<const SwitchCase* const> cases); |
| void writeVarDeclaration(const VarDeclaration& varDecl); |
| |
| // Synthesizes an LValue for an expression. |
| class LValue; |
| class PointerLValue; |
| class SwizzleLValue; |
| class VectorComponentLValue; |
| std::unique_ptr<LValue> makeLValue(const Expression& e); |
| |
| std::string variableReferenceNameForLValue(const VariableReference& r); |
| std::string variablePrefix(const Variable& v); |
| |
| bool binaryOpNeedsComponentwiseMatrixPolyfill(const Type& left, const Type& right, Operator op); |
| |
| // Writers for expressions. These return the final expression text as a string, and emit any |
| // necessary setup code directly into the program as necessary. The returned expression may be |
| // a `let`-alias that cannot be assigned-into; use `makeLValue` for an assignable expression. |
| std::string assembleExpression(const Expression& e, Precedence parentPrecedence); |
| std::string assembleBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence); |
| std::string assembleBinaryExpression(const Expression& left, |
| Operator op, |
| const Expression& right, |
| const Type& resultType, |
| Precedence parentPrecedence); |
| std::string assembleFieldAccess(const FieldAccess& f); |
| std::string assembleFunctionCall(const FunctionCall& call, Precedence parentPrecedence); |
| std::string assembleIndexExpression(const IndexExpression& i); |
| std::string assembleLiteral(const Literal& l); |
| std::string assemblePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence); |
| std::string assemblePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence); |
| std::string assembleSwizzle(const Swizzle& swizzle); |
| std::string assembleTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence); |
| std::string assembleVariableReference(const VariableReference& r); |
| std::string assembleName(std::string_view name); |
| |
| std::string assembleIncrementExpr(const Type& type); |
| |
| // Intrinsic helper functions. |
| std::string assembleIntrinsicCall(const FunctionCall& call, |
| IntrinsicKind kind, |
| Precedence parentPrecedence); |
| std::string assembleSimpleIntrinsic(std::string_view intrinsicName, const FunctionCall& call); |
| std::string assembleUnaryOpIntrinsic(Operator op, |
| const FunctionCall& call, |
| Precedence parentPrecedence); |
| std::string assembleBinaryOpIntrinsic(Operator op, |
| const FunctionCall& call, |
| Precedence parentPrecedence); |
| std::string assembleVectorizedIntrinsic(std::string_view intrinsicName, |
| const FunctionCall& call); |
| std::string assemblePartialSampleCall(std::string_view functionName, |
| const Expression& sampler, |
| const Expression& coords); |
| std::string assembleInversePolyfill(const FunctionCall& call); |
| std::string assembleComponentwiseMatrixBinary(const Type& leftType, |
| const Type& rightType, |
| const std::string& left, |
| const std::string& right, |
| Operator op); |
| |
| // Constructor expressions |
| std::string assembleAnyConstructor(const AnyConstructor& c); |
| std::string assembleConstructorCompound(const ConstructorCompound& c); |
| std::string assembleConstructorCompoundVector(const ConstructorCompound& c); |
| std::string assembleConstructorCompoundMatrix(const ConstructorCompound& c); |
| std::string assembleConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c); |
| std::string assembleConstructorMatrixResize(const ConstructorMatrixResize& ctor); |
| |
| // Synthesized helper functions for comparison operators that are not supported by WGSL. |
| std::string assembleEqualityExpression(const Type& left, |
| const std::string& leftName, |
| const Type& right, |
| const std::string& rightName, |
| Operator op, |
| Precedence parentPrecedence); |
| std::string assembleEqualityExpression(const Expression& left, |
| const Expression& right, |
| Operator op, |
| Precedence parentPrecedence); |
| |
| // Writes a scratch variable into the program and returns its name (e.g. `_skTemp123`). |
| std::string writeScratchVar(const Type& type, const std::string& value = ""); |
| |
| // Writes a scratch let-variable into the program, gives it the value of `expr`, and returns its |
| // name (e.g. `_skTemp123`). |
| std::string writeScratchLet(const std::string& expr); |
| std::string writeScratchLet(const Expression& expr, Precedence parentPrecedence); |
| |
| // Converts `expr` into a string and returns a scratch let-variable associated with the |
| // expression. Compile-time constants and plain variable references will return the expression |
| // directly and omit the let-variable. |
| std::string writeNontrivialScratchLet(const Expression& expr, Precedence parentPrecedence); |
| |
| // Generic recursive ProgramElement visitor. |
| void writeProgramElement(const ProgramElement& e); |
| void writeGlobalVarDeclaration(const GlobalVarDeclaration& d); |
| void writeStructDefinition(const StructDefinition& s); |
| void writeModifiersDeclaration(const ModifiersDeclaration&); |
| |
| // Writes the WGSL struct fields for SkSL structs and interface blocks. Enforces WGSL address |
| // space layout constraints |
| // (https://www.w3.org/TR/WGSL/#address-space-layout-constraints) if a `layout` is |
| // provided. A struct that does not need to be host-shareable does not require a `layout`. |
| void writeFields(SkSpan<const Field> fields, const MemoryLayout* memoryLayout = nullptr); |
| |
| // We bundle uniforms, and all varying pipeline stage inputs and outputs, into separate structs. |
| bool needsStageInputStruct() const; |
| void writeStageInputStruct(); |
| bool needsStageOutputStruct() const; |
| void writeStageOutputStruct(); |
| void writeUniformsAndBuffers(); |
| void prepareUniformPolyfillsForInterfaceBlock(const InterfaceBlock* interfaceBlock, |
| std::string_view instanceName, |
| MemoryLayout::Standard nativeLayout); |
| void writeUniformPolyfills(); |
| |
| void writeTextureOrSampler(const Variable& var, |
| int bindingLocation, |
| std::string_view suffix, |
| std::string_view wgslType); |
| |
| // Writes all top-level non-opaque global uniform declarations (i.e. not part of an interface |
| // block) into a single uniform block binding. |
| // |
| // In complete fragment/vertex/compute programs, uniforms will be declared only as interface |
| // blocks and global opaque types (like textures and samplers) which we expect to be declared |
| // with a unique binding and descriptor set index. However, test files that are declared as RTE |
| // programs may contain OpenGL-style global uniform declarations with no clear binding index to |
| // use for the containing synthesized block. |
| // |
| // Since we are handling these variables only to generate gold files from RTEs and never run |
| // them, we always declare them at the default bind group and binding index. |
| void writeNonBlockUniformsForTests(); |
| |
| // For a given function declaration, writes out any implicitly required pipeline stage arguments |
| // based on the function's pre-determined dependencies. These are expected to be written out as |
| // the first parameters for a function that requires them. Returns true if any arguments were |
| // written. |
| std::string functionDependencyArgs(const FunctionDeclaration&); |
| bool writeFunctionDependencyParams(const FunctionDeclaration&); |
| |
| // Code in the header appears before the main body of code. |
| StringStream fHeader; |
| |
| // We assign unique names to anonymous interface blocks based on the type. |
| skia_private::THashMap<const Type*, std::string> fInterfaceBlockNameMap; |
| |
| // Stores the disallowed identifier names. |
| ProgramRequirements fRequirements; |
| skia_private::TArray<const Variable*> fPipelineInputs; |
| skia_private::TArray<const Variable*> fPipelineOutputs; |
| |
| // These fields track whether we have written the polyfill for `inverse()` for a given matrix |
| // type. |
| bool fWrittenInverse2 = false; |
| bool fWrittenInverse3 = false; |
| bool fWrittenInverse4 = false; |
| |
| // These fields control uniform polyfill support in cases where WGSL and std140 disagree. |
| // In std140 layout, matrices need to be represented as arrays of @size(16)-aligned vectors, and |
| // array elements are wrapped in a struct containing a single @size(16)-aligned element. Arrays |
| // of matrices combine both wrappers. These wrapper structs are unpacked into natively-typed |
| // globals at the shader entrypoint. |
| struct FieldPolyfillInfo { |
| const InterfaceBlock* fInterfaceBlock; |
| std::string fReplacementName; |
| bool fIsArray = false; |
| bool fIsMatrix = false; |
| bool fWasAccessed = false; |
| }; |
| using FieldPolyfillMap = skia_private::THashMap<const Field*, FieldPolyfillInfo>; |
| FieldPolyfillMap fFieldPolyfillMap; |
| |
| // Output processing state. |
| int fIndentation = 0; |
| bool fAtLineStart = false; |
| bool fHasUnconditionalReturn = false; |
| bool fAtFunctionScope = false; |
| int fConditionalScopeDepth = 0; |
| int fLocalSizeX = 1; |
| int fLocalSizeY = 1; |
| int fLocalSizeZ = 1; |
| |
| int fScratchCount = 0; |
| }; |
| |
| } // namespace SkSL |
| |
| #endif // SKSL_WGSLCODEGENERATOR |