blob: cb118489baa894c8a1d4387fb45f9c6b04f86d29 [file] [log] [blame]
* Copyright 2022 Google Inc.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
#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>
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
namespace SkSL {
* Convert a Program into WGSL code.
class WGSLCodeGenerator : public CodeGenerator {
// See
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 {
struct ProgramRequirements {
using DepsMap = skia_private::THashMap<const FunctionDeclaration*,
// Mappings used to synthesize function parameters according to dependencies on pipeline
// input/output variables.
DepsMap fDependencies;
// These flags track extensions that will need to be enabled.
bool fPixelLocalExtension = false;
WGSLCodeGenerator(const Context* context, const Program* program, OutputStream* out)
: INHERITED(context, program, out) {}
bool generateCode() override;
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
// ( 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 writeEnables();
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 functions which use stage inputs/outputs as well as required WGSL extensions.
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