diff options
Diffstat (limited to 'gfx/skia/skia/src/sksl')
251 files changed, 63527 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/sksl/GLSL.std.450.h b/gfx/skia/skia/src/sksl/GLSL.std.450.h new file mode 100644 index 0000000000..943fd8650f --- /dev/null +++ b/gfx/skia/skia/src/sksl/GLSL.std.450.h @@ -0,0 +1,131 @@ +/* +** Copyright (c) 2014-2016 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and/or associated documentation files (the "Materials"), +** to deal in the Materials without restriction, including without limitation +** the rights to use, copy, modify, merge, publish, distribute, sublicense, +** and/or sell copies of the Materials, and to permit persons to whom the +** Materials are furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Materials. +** +** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +** IN THE MATERIALS. +*/ + +#ifndef GLSLstd450_H +#define GLSLstd450_H + +static const int GLSLstd450Version = 100; +static const int GLSLstd450Revision = 3; + +enum GLSLstd450 { + GLSLstd450Bad = 0, // Don't use + + GLSLstd450Round = 1, + GLSLstd450RoundEven = 2, + GLSLstd450Trunc = 3, + GLSLstd450FAbs = 4, + GLSLstd450SAbs = 5, + GLSLstd450FSign = 6, + GLSLstd450SSign = 7, + GLSLstd450Floor = 8, + GLSLstd450Ceil = 9, + GLSLstd450Fract = 10, + + GLSLstd450Radians = 11, + GLSLstd450Degrees = 12, + GLSLstd450Sin = 13, + GLSLstd450Cos = 14, + GLSLstd450Tan = 15, + GLSLstd450Asin = 16, + GLSLstd450Acos = 17, + GLSLstd450Atan = 18, + GLSLstd450Sinh = 19, + GLSLstd450Cosh = 20, + GLSLstd450Tanh = 21, + GLSLstd450Asinh = 22, + GLSLstd450Acosh = 23, + GLSLstd450Atanh = 24, + GLSLstd450Atan2 = 25, + + GLSLstd450Pow = 26, + GLSLstd450Exp = 27, + GLSLstd450Log = 28, + GLSLstd450Exp2 = 29, + GLSLstd450Log2 = 30, + GLSLstd450Sqrt = 31, + GLSLstd450InverseSqrt = 32, + + GLSLstd450Determinant = 33, + GLSLstd450MatrixInverse = 34, + + GLSLstd450Modf = 35, // second operand needs an OpVariable to write to + GLSLstd450ModfStruct = 36, // no OpVariable operand + GLSLstd450FMin = 37, + GLSLstd450UMin = 38, + GLSLstd450SMin = 39, + GLSLstd450FMax = 40, + GLSLstd450UMax = 41, + GLSLstd450SMax = 42, + GLSLstd450FClamp = 43, + GLSLstd450UClamp = 44, + GLSLstd450SClamp = 45, + GLSLstd450FMix = 46, + GLSLstd450IMix = 47, // Reserved + GLSLstd450Step = 48, + GLSLstd450SmoothStep = 49, + + GLSLstd450Fma = 50, + GLSLstd450Frexp = 51, // second operand needs an OpVariable to write to + GLSLstd450FrexpStruct = 52, // no OpVariable operand + GLSLstd450Ldexp = 53, + + GLSLstd450PackSnorm4x8 = 54, + GLSLstd450PackUnorm4x8 = 55, + GLSLstd450PackSnorm2x16 = 56, + GLSLstd450PackUnorm2x16 = 57, + GLSLstd450PackHalf2x16 = 58, + GLSLstd450PackDouble2x32 = 59, + GLSLstd450UnpackSnorm2x16 = 60, + GLSLstd450UnpackUnorm2x16 = 61, + GLSLstd450UnpackHalf2x16 = 62, + GLSLstd450UnpackSnorm4x8 = 63, + GLSLstd450UnpackUnorm4x8 = 64, + GLSLstd450UnpackDouble2x32 = 65, + + GLSLstd450Length = 66, + GLSLstd450Distance = 67, + GLSLstd450Cross = 68, + GLSLstd450Normalize = 69, + GLSLstd450FaceForward = 70, + GLSLstd450Reflect = 71, + GLSLstd450Refract = 72, + + GLSLstd450FindILsb = 73, + GLSLstd450FindSMsb = 74, + GLSLstd450FindUMsb = 75, + + GLSLstd450InterpolateAtCentroid = 76, + GLSLstd450InterpolateAtSample = 77, + GLSLstd450InterpolateAtOffset = 78, + + GLSLstd450NMin = 79, + GLSLstd450NMax = 80, + GLSLstd450NClamp = 81, + + GLSLstd450Count +}; + +#endif // #ifndef GLSLstd450_H diff --git a/gfx/skia/skia/src/sksl/README.md b/gfx/skia/skia/src/sksl/README.md new file mode 100644 index 0000000000..862f5c6965 --- /dev/null +++ b/gfx/skia/skia/src/sksl/README.md @@ -0,0 +1,158 @@ +# Overview + +SkSL ("Skia Shading Language") is a variant of GLSL which is used as Skia's +internal shading language. SkSL is, at its heart, a single standardized version +of GLSL which avoids all of the various version and dialect differences found +in GLSL "in the wild", but it does bring a few of its own changes to the table. + +Skia uses the SkSL compiler to convert SkSL code to GLSL, GLSL ES, SPIR-V, or +MSL before handing it over to the graphics driver. + + +# Differences from GLSL + +* Precision modifiers are not used. 'float', 'int', and 'uint' are always high + precision. New types 'half', 'short', and 'ushort' are medium precision (we + do not use low precision). +* Vector types are named <base type><columns>, so float2 instead of vec2 and + bool4 instead of bvec4 +* Matrix types are named <base type><columns>x<rows>, so float2x3 instead of + mat2x3 and double4x4 instead of dmat4 +* GLSL caps can be referenced via the syntax 'sk_Caps.<name>', e.g. + sk_Caps.integerSupport. The value will be a constant boolean or int, + as appropriate. As SkSL supports constant folding and branch elimination, this + means that an 'if' statement which statically queries a cap will collapse down + to the chosen branch, meaning that: + + if (sk_Caps.integerSupport) + do_something(); + else + do_something_else(); + + will compile as if you had written either 'do_something();' or + 'do_something_else();', depending on whether that cap is enabled or not. +* no #version statement is required, and it will be ignored if present +* the output color is sk_FragColor (do not declare it) +* use sk_Position instead of gl_Position. sk_Position is in device coordinates + rather than normalized coordinates. +* use sk_PointSize instead of gl_PointSize +* use sk_VertexID instead of gl_VertexID +* use sk_InstanceID instead of gl_InstanceID +* the fragment coordinate is sk_FragCoord, and is always relative to the upper + left. +* use sk_Clockwise instead of gl_FrontFacing. This is always relative to an + upper left origin. +* you do not need to include ".0" to make a number a float (meaning that + "float2(x, y) * 4" is perfectly legal in SkSL, unlike GLSL where it would + often have to be expressed "float2(x, y) * 4.0". There is no performance + penalty for this, as the number is converted to a float at compile time) +* type suffixes on numbers (1.0f, 0xFFu) are both unnecessary and unsupported +* creating a smaller vector from a larger vector (e.g. float2(float3(1))) is + intentionally disallowed, as it is just a wordier way of performing a swizzle. + Use swizzles instead. +* Swizzle components, in addition to the normal rgba / xyzw components, can also + be LTRB (meaning "left/top/right/bottom", for when we store rectangles in + vectors), and may also be the constants '0' or '1' to produce a constant 0 or + 1 in that channel instead of selecting anything from the source vector. + foo.rgb1 is equivalent to float4(foo.rgb, 1). +* All texture functions are named "sample", e.g. sample(sampler2D, float3) is + equivalent to GLSL's textureProj(sampler2D, float3). +* Functions support the 'inline' modifier, which causes the compiler to ignore + its normal inlining heuristics and inline the function if at all possible +* some built-in functions and one or two rarely-used language features are not + yet supported (sorry!) + + +# Synchronization Primitives + +SkSL offers atomic operations and synchronization primitives geared towards GPU compute +programs. These primitives are designed to abstract over the capabilities provided by +MSL, SPIR-V, and WGSL, and differ from the corresponding primitives in GLSL. + +## Atomics + +SkSL provides the `atomicUint` type. This is an opaque type that requires the use of an +atomic intrinsic (such as `atomicLoad`, `atomicStore`, and `atomicAdd`) to act on its value (which +is of type `uint`). + +A variable with the `atomicUint` type must be declared inside a writable storage buffer block or as +a workgroup-shared variable. When declared inside a buffer block, it is guaranteed to conform to the +same size and stride as a `uint`. + +``` +workgroup atomicUint myLocalAtomicUint; + +layout(set = 0, binding = 0) buffer mySSBO { + atomicUint myGlobalAtomicUint; +}; + +``` + +An `atomicUint` can be declared as a struct member or the element type of an array, provided that +the struct/array type is only instantiated in a workgroup-shared or storage buffer block variable. + +### Backend considerations and differences from GLSL + +`atomicUint` should not be confused with the GLSL [`atomic_uint` (aka Atomic +Counter)](https://www.khronos.org/opengl/wiki/Atomic_Counter) type. The semantics provided by +`atomicUint` are more similar to GLSL ["Atomic Memory +Functions"](https://www.khronos.org/opengl/wiki/Atomic_Variable_Operations) +(see GLSL Spec v4.3, 8.11 "Atomic Memory Functions"). The key difference is that SkSL atomic +operations only operate on a variable of type `atomicUint` while GLSL Atomic Memory Functions can +operate over arbitrary memory locations (such as a component of a vector). + +* The semantics of `atomicUint` are similar to Metal's `atomic<uint>` and WGSL's `atomic<u32>`. + These are the types that an `atomicUint` is translated to when targeting Metal and WGSL. +* When translated to Metal, the atomic intrinsics use relaxed memory order semantics. +* When translated to SPIR-V, the atomic intrinsics use relaxed [memory + semantics](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Memory_Semantics_-id-) + (i.e. `0x0 None`). The [memory + scope](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Scope_-id-) is either `1 + Device` or `2 Workgroup` depending on whether the `atomicUint` is declared in a buffer block or + workgroup variable. + +## Barriers + +SkSL provides two barrier intrinsics: `workgroupBarrier()` and `storageBarrier()`. These functions +are only available in compute programs and synchronize access to workgroup-shared and storage buffer +memory between invocations in the same workgroup. They provide the same semantics as the equivalent +[WGSL Synchronization Built-in Functions](https://www.w3.org/TR/WGSL/#sync-builtin-functions). More +specifically: + +* Both functions execute a control barrier with Acquire/Release memory ordering. +* Both functions use a `Workgroup` execution and memory scope. This means that a coherent memory + view is only guaranteed between invocations in the same workgroup and NOT across workgroups in a + given compute pipeline dispatch. If multiple workgroups require a _synchronized_ coherent view + over the same shared mutable state, their access must be synchronized via other means (such as a + pipeline barrier between multiple dispatches). + +### Backend considerations + +* The closest GLSL equivalent for `workgroupBarrier()` is the +[`barrier()`](https://registry.khronos.org/OpenGL-Refpages/gl4/html/barrier.xhtml) intrinsic. Both +`workgroupBarrier()` and `storageBarrier()` can be defined as the following invocations of the +`controlBarrier` intrinsic defined in +[GL_KHR_memory_scope_semantics](https://github.com/KhronosGroup/GLSL/blob/master/extensions/khr/GL_KHR_memory_scope_semantics.txt): + +``` +// workgroupBarrier(): +controlBarrier(gl_ScopeWorkgroup, + gl_ScopeWorkgroup, + gl_StorageSemanticsShared, + gl_SemanticsAcquireRelease); + +// storageBarrier(): +controlBarrier(gl_ScopeWorkgroup, + gl_ScopeWorkgroup, + gl_StorageSemanticsBuffer, + gl_SemanticsAcquireRelease); +``` + +* In Metal, `workgroupBarrier()` is equivalent to `threadgroup_barrier(mem_flags::mem_threadgroup)`. + `storageBarrier()` is equivalent to `threadgroup_barrier(mem_flags::mem_device)`. + +* In Vulkan SPIR-V, `workgroupBarrier()` is equivalent to `OpControlBarrier` with `Workgroup` + execution and memory scope, and `AcquireRelease | WorkgroupMemory` memory semantics. + + `storageBarrier()` is equivalent to `OpControlBarrier` with `Workgroup` execution and memory + scope, and `AcquireRelease | UniformMemory` memory semantics. diff --git a/gfx/skia/skia/src/sksl/SkSLAnalysis.cpp b/gfx/skia/skia/src/sksl/SkSLAnalysis.cpp new file mode 100644 index 0000000000..a7aba3d02c --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLAnalysis.cpp @@ -0,0 +1,705 @@ +/* + * Copyright 2020 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLAnalysis.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLSampleUsage.h" +#include "include/private/SkSLStatement.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/analysis/SkSLNoOpErrorReporter.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLChildCall.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/transform/SkSLProgramWriter.h" + +#include <optional> +#include <string> +#include <string_view> + +namespace SkSL { + +namespace { + +// Visitor that determines the merged SampleUsage for a given child in the program. +class MergeSampleUsageVisitor : public ProgramVisitor { +public: + MergeSampleUsageVisitor(const Context& context, + const Variable& child, + bool writesToSampleCoords) + : fContext(context), fChild(child), fWritesToSampleCoords(writesToSampleCoords) {} + + SampleUsage visit(const Program& program) { + fUsage = SampleUsage(); // reset to none + INHERITED::visit(program); + return fUsage; + } + + int elidedSampleCoordCount() const { return fElidedSampleCoordCount; } + +protected: + const Context& fContext; + const Variable& fChild; + const bool fWritesToSampleCoords; + SampleUsage fUsage; + int fElidedSampleCoordCount = 0; + + bool visitExpression(const Expression& e) override { + // Looking for child(...) + if (e.is<ChildCall>() && &e.as<ChildCall>().child() == &fChild) { + // Determine the type of call at this site, and merge it with the accumulated state + const ExpressionArray& arguments = e.as<ChildCall>().arguments(); + SkASSERT(arguments.size() >= 1); + + const Expression* maybeCoords = arguments[0].get(); + if (maybeCoords->type().matches(*fContext.fTypes.fFloat2)) { + // If the coords are a direct reference to the program's sample-coords, and those + // coords are never modified, we can conservatively turn this into PassThrough + // sampling. In all other cases, we consider it Explicit. + if (!fWritesToSampleCoords && maybeCoords->is<VariableReference>() && + maybeCoords->as<VariableReference>().variable()->modifiers().fLayout.fBuiltin == + SK_MAIN_COORDS_BUILTIN) { + fUsage.merge(SampleUsage::PassThrough()); + ++fElidedSampleCoordCount; + } else { + fUsage.merge(SampleUsage::Explicit()); + } + } else { + // child(inputColor) or child(srcColor, dstColor) -> PassThrough + fUsage.merge(SampleUsage::PassThrough()); + } + } + + return INHERITED::visitExpression(e); + } + + using INHERITED = ProgramVisitor; +}; + +// Visitor that searches for child calls from a function other than main() +class SampleOutsideMainVisitor : public ProgramVisitor { +public: + SampleOutsideMainVisitor() {} + + bool visitExpression(const Expression& e) override { + if (e.is<ChildCall>()) { + return true; + } + return INHERITED::visitExpression(e); + } + + bool visitProgramElement(const ProgramElement& p) override { + return p.is<FunctionDefinition>() && + !p.as<FunctionDefinition>().declaration().isMain() && + INHERITED::visitProgramElement(p); + } + + using INHERITED = ProgramVisitor; +}; + +class ReturnsNonOpaqueColorVisitor : public ProgramVisitor { +public: + ReturnsNonOpaqueColorVisitor() {} + + bool visitStatement(const Statement& s) override { + if (s.is<ReturnStatement>()) { + const Expression* e = s.as<ReturnStatement>().expression().get(); + bool knownOpaque = e && e->type().slotCount() == 4 && + ConstantFolder::GetConstantValueForVariable(*e) + ->getConstantValue(/*n=*/3) + .value_or(0) == 1; + return !knownOpaque; + } + return INHERITED::visitStatement(s); + } + + bool visitExpression(const Expression& e) override { + // No need to recurse into expressions, these can never contain return statements + return false; + } + + using INHERITED = ProgramVisitor; + using INHERITED::visitProgramElement; +}; + +// Visitor that counts the number of nodes visited +class NodeCountVisitor : public ProgramVisitor { +public: + NodeCountVisitor(int limit) : fLimit(limit) {} + + int visit(const Statement& s) { + this->visitStatement(s); + return fCount; + } + + bool visitExpression(const Expression& e) override { + ++fCount; + return (fCount >= fLimit) || INHERITED::visitExpression(e); + } + + bool visitProgramElement(const ProgramElement& p) override { + ++fCount; + return (fCount >= fLimit) || INHERITED::visitProgramElement(p); + } + + bool visitStatement(const Statement& s) override { + ++fCount; + return (fCount >= fLimit) || INHERITED::visitStatement(s); + } + +private: + int fCount = 0; + int fLimit; + + using INHERITED = ProgramVisitor; +}; + +class VariableWriteVisitor : public ProgramVisitor { +public: + VariableWriteVisitor(const Variable* var) + : fVar(var) {} + + bool visit(const Statement& s) { + return this->visitStatement(s); + } + + bool visitExpression(const Expression& e) override { + if (e.is<VariableReference>()) { + const VariableReference& ref = e.as<VariableReference>(); + if (ref.variable() == fVar && + (ref.refKind() == VariableReference::RefKind::kWrite || + ref.refKind() == VariableReference::RefKind::kReadWrite || + ref.refKind() == VariableReference::RefKind::kPointer)) { + return true; + } + } + return INHERITED::visitExpression(e); + } + +private: + const Variable* fVar; + + using INHERITED = ProgramVisitor; +}; + +// This isn't actually using ProgramVisitor, because it only considers a subset of the fields for +// any given expression kind. For instance, when indexing an array (e.g. `x[1]`), we only want to +// know if the base (`x`) is assignable; the index expression (`1`) doesn't need to be. +class IsAssignableVisitor { +public: + IsAssignableVisitor(ErrorReporter* errors) : fErrors(errors) {} + + bool visit(Expression& expr, Analysis::AssignmentInfo* info) { + int oldErrorCount = fErrors->errorCount(); + this->visitExpression(expr); + if (info) { + info->fAssignedVar = fAssignedVar; + } + return fErrors->errorCount() == oldErrorCount; + } + + void visitExpression(Expression& expr, const FieldAccess* fieldAccess = nullptr) { + switch (expr.kind()) { + case Expression::Kind::kVariableReference: { + VariableReference& varRef = expr.as<VariableReference>(); + const Variable* var = varRef.variable(); + auto fieldName = [&] { + return fieldAccess ? fieldAccess->description(OperatorPrecedence::kTopLevel) + : std::string(var->name()); + }; + if (var->modifiers().fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag)) { + fErrors->error(expr.fPosition, + "cannot modify immutable variable '" + fieldName() + "'"); + } else if (var->storage() == Variable::Storage::kGlobal && + (var->modifiers().fFlags & Modifiers::kIn_Flag)) { + fErrors->error(expr.fPosition, + "cannot modify pipeline input variable '" + fieldName() + "'"); + } else { + SkASSERT(fAssignedVar == nullptr); + fAssignedVar = &varRef; + } + break; + } + case Expression::Kind::kFieldAccess: { + const FieldAccess& f = expr.as<FieldAccess>(); + this->visitExpression(*f.base(), &f); + break; + } + case Expression::Kind::kSwizzle: { + const Swizzle& swizzle = expr.as<Swizzle>(); + this->checkSwizzleWrite(swizzle); + this->visitExpression(*swizzle.base(), fieldAccess); + break; + } + case Expression::Kind::kIndex: + this->visitExpression(*expr.as<IndexExpression>().base(), fieldAccess); + break; + + case Expression::Kind::kPoison: + break; + + default: + fErrors->error(expr.fPosition, "cannot assign to this expression"); + break; + } + } + +private: + void checkSwizzleWrite(const Swizzle& swizzle) { + int bits = 0; + for (int8_t idx : swizzle.components()) { + SkASSERT(idx >= SwizzleComponent::X && idx <= SwizzleComponent::W); + int bit = 1 << idx; + if (bits & bit) { + fErrors->error(swizzle.fPosition, + "cannot write to the same swizzle field more than once"); + break; + } + bits |= bit; + } + } + + ErrorReporter* fErrors; + VariableReference* fAssignedVar = nullptr; + + using INHERITED = ProgramVisitor; +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// Analysis + +SampleUsage Analysis::GetSampleUsage(const Program& program, + const Variable& child, + bool writesToSampleCoords, + int* elidedSampleCoordCount) { + MergeSampleUsageVisitor visitor(*program.fContext, child, writesToSampleCoords); + SampleUsage result = visitor.visit(program); + if (elidedSampleCoordCount) { + *elidedSampleCoordCount += visitor.elidedSampleCoordCount(); + } + return result; +} + +bool Analysis::ReferencesBuiltin(const Program& program, int builtin) { + SkASSERT(program.fUsage); + for (const auto& [variable, counts] : program.fUsage->fVariableCounts) { + if (counts.fRead > 0 && variable->modifiers().fLayout.fBuiltin == builtin) { + return true; + } + } + return false; +} + +bool Analysis::ReferencesSampleCoords(const Program& program) { + return Analysis::ReferencesBuiltin(program, SK_MAIN_COORDS_BUILTIN); +} + +bool Analysis::ReferencesFragCoords(const Program& program) { + return Analysis::ReferencesBuiltin(program, SK_FRAGCOORD_BUILTIN); +} + +bool Analysis::CallsSampleOutsideMain(const Program& program) { + SampleOutsideMainVisitor visitor; + return visitor.visit(program); +} + +bool Analysis::CallsColorTransformIntrinsics(const Program& program) { + for (auto [fn, count] : program.usage()->fCallCounts) { + if (count != 0 && (fn->intrinsicKind() == k_toLinearSrgb_IntrinsicKind || + fn->intrinsicKind() == k_fromLinearSrgb_IntrinsicKind)) { + return true; + } + } + return false; +} + +bool Analysis::ReturnsOpaqueColor(const FunctionDefinition& function) { + ReturnsNonOpaqueColorVisitor visitor; + return !visitor.visitProgramElement(function); +} + +bool Analysis::ContainsRTAdjust(const Expression& expr) { + class ContainsRTAdjustVisitor : public ProgramVisitor { + public: + bool visitExpression(const Expression& expr) override { + if (expr.is<VariableReference>() && + expr.as<VariableReference>().variable()->name() == Compiler::RTADJUST_NAME) { + return true; + } + return INHERITED::visitExpression(expr); + } + + using INHERITED = ProgramVisitor; + }; + + ContainsRTAdjustVisitor visitor; + return visitor.visitExpression(expr); +} + +bool Analysis::IsCompileTimeConstant(const Expression& expr) { + class IsCompileTimeConstantVisitor : public ProgramVisitor { + public: + bool visitExpression(const Expression& expr) override { + switch (expr.kind()) { + case Expression::Kind::kLiteral: + // Literals are compile-time constants. + return false; + + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorStruct: + // Constructors might be compile-time constants, if they are composed entirely + // of literals and constructors. (Casting constructors are intentionally omitted + // here. If the value inside was a compile-time constant, we would have not have + // generated a cast at all.) + return INHERITED::visitExpression(expr); + + default: + // This expression isn't a compile-time constant. + fIsConstant = false; + return true; + } + } + + bool fIsConstant = true; + using INHERITED = ProgramVisitor; + }; + + IsCompileTimeConstantVisitor visitor; + visitor.visitExpression(expr); + return visitor.fIsConstant; +} + +bool Analysis::DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors) { + // A variable declaration can create either a lone VarDeclaration or an unscoped Block + // containing multiple VarDeclaration statements. We need to detect either case. + const Variable* var; + if (stmt.is<VarDeclaration>()) { + // The single-variable case. No blocks at all. + var = stmt.as<VarDeclaration>().var(); + } else if (stmt.is<Block>()) { + // The multiple-variable case: an unscoped, non-empty block... + const Block& block = stmt.as<Block>(); + if (block.isScope() || block.children().empty()) { + return false; + } + // ... holding a variable declaration. + const Statement& innerStmt = *block.children().front(); + if (!innerStmt.is<VarDeclaration>()) { + return false; + } + var = innerStmt.as<VarDeclaration>().var(); + } else { + // This statement wasn't a variable declaration. No problem. + return false; + } + + // Report an error. + SkASSERT(var); + if (errors) { + errors->error(var->fPosition, + "variable '" + std::string(var->name()) + "' must be created in a scope"); + } + return true; +} + +int Analysis::NodeCountUpToLimit(const FunctionDefinition& function, int limit) { + return NodeCountVisitor{limit}.visit(*function.body()); +} + +bool Analysis::StatementWritesToVariable(const Statement& stmt, const Variable& var) { + return VariableWriteVisitor(&var).visit(stmt); +} + +bool Analysis::IsAssignable(Expression& expr, AssignmentInfo* info, ErrorReporter* errors) { + NoOpErrorReporter unusedErrors; + return IsAssignableVisitor{errors ? errors : &unusedErrors}.visit(expr, info); +} + +bool Analysis::UpdateVariableRefKind(Expression* expr, + VariableReference::RefKind kind, + ErrorReporter* errors) { + Analysis::AssignmentInfo info; + if (!Analysis::IsAssignable(*expr, &info, errors)) { + return false; + } + if (!info.fAssignedVar) { + if (errors) { + errors->error(expr->fPosition, "can't assign to expression '" + expr->description() + + "'"); + } + return false; + } + info.fAssignedVar->setRefKind(kind); + return true; +} + +class ES2IndexingVisitor : public ProgramVisitor { +public: + ES2IndexingVisitor(ErrorReporter& errors) : fErrors(errors) {} + + bool visitStatement(const Statement& s) override { + if (s.is<ForStatement>()) { + const ForStatement& f = s.as<ForStatement>(); + SkASSERT(f.initializer() && f.initializer()->is<VarDeclaration>()); + const Variable* var = f.initializer()->as<VarDeclaration>().var(); + auto [iter, inserted] = fLoopIndices.insert(var); + SkASSERT(inserted); + bool result = this->visitStatement(*f.statement()); + fLoopIndices.erase(iter); + return result; + } + return INHERITED::visitStatement(s); + } + + bool visitExpression(const Expression& e) override { + if (e.is<IndexExpression>()) { + const IndexExpression& i = e.as<IndexExpression>(); + if (!Analysis::IsConstantIndexExpression(*i.index(), &fLoopIndices)) { + fErrors.error(i.fPosition, "index expression must be constant"); + return true; + } + } + return INHERITED::visitExpression(e); + } + + using ProgramVisitor::visitProgramElement; + +private: + ErrorReporter& fErrors; + std::set<const Variable*> fLoopIndices; + using INHERITED = ProgramVisitor; +}; + +void Analysis::ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& errors) { + ES2IndexingVisitor visitor(errors); + visitor.visitProgramElement(pe); +} + +//////////////////////////////////////////////////////////////////////////////// +// ProgramVisitor + +bool ProgramVisitor::visit(const Program& program) { + for (const ProgramElement* pe : program.elements()) { + if (this->visitProgramElement(*pe)) { + return true; + } + } + return false; +} + +template <typename T> bool TProgramVisitor<T>::visitExpression(typename T::Expression& e) { + switch (e.kind()) { + case Expression::Kind::kFunctionReference: + case Expression::Kind::kLiteral: + case Expression::Kind::kMethodReference: + case Expression::Kind::kPoison: + case Expression::Kind::kSetting: + case Expression::Kind::kTypeReference: + case Expression::Kind::kVariableReference: + // Leaf expressions return false + return false; + + case Expression::Kind::kBinary: { + auto& b = e.template as<BinaryExpression>(); + return (b.left() && this->visitExpressionPtr(b.left())) || + (b.right() && this->visitExpressionPtr(b.right())); + } + case Expression::Kind::kChildCall: { + // We don't visit the child variable itself, just the arguments + auto& c = e.template as<ChildCall>(); + for (auto& arg : c.arguments()) { + if (arg && this->visitExpressionPtr(arg)) { return true; } + } + return false; + } + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorStruct: { + auto& c = e.asAnyConstructor(); + for (auto& arg : c.argumentSpan()) { + if (this->visitExpressionPtr(arg)) { return true; } + } + return false; + } + case Expression::Kind::kFieldAccess: + return this->visitExpressionPtr(e.template as<FieldAccess>().base()); + + case Expression::Kind::kFunctionCall: { + auto& c = e.template as<FunctionCall>(); + for (auto& arg : c.arguments()) { + if (arg && this->visitExpressionPtr(arg)) { return true; } + } + return false; + } + case Expression::Kind::kIndex: { + auto& i = e.template as<IndexExpression>(); + return this->visitExpressionPtr(i.base()) || this->visitExpressionPtr(i.index()); + } + case Expression::Kind::kPostfix: + return this->visitExpressionPtr(e.template as<PostfixExpression>().operand()); + + case Expression::Kind::kPrefix: + return this->visitExpressionPtr(e.template as<PrefixExpression>().operand()); + + case Expression::Kind::kSwizzle: { + auto& s = e.template as<Swizzle>(); + return s.base() && this->visitExpressionPtr(s.base()); + } + + case Expression::Kind::kTernary: { + auto& t = e.template as<TernaryExpression>(); + return this->visitExpressionPtr(t.test()) || + (t.ifTrue() && this->visitExpressionPtr(t.ifTrue())) || + (t.ifFalse() && this->visitExpressionPtr(t.ifFalse())); + } + default: + SkUNREACHABLE; + } +} + +template <typename T> bool TProgramVisitor<T>::visitStatement(typename T::Statement& s) { + switch (s.kind()) { + case Statement::Kind::kBreak: + case Statement::Kind::kContinue: + case Statement::Kind::kDiscard: + case Statement::Kind::kNop: + // Leaf statements just return false + return false; + + case Statement::Kind::kBlock: + for (auto& stmt : s.template as<Block>().children()) { + if (stmt && this->visitStatementPtr(stmt)) { + return true; + } + } + return false; + + case Statement::Kind::kSwitchCase: { + auto& sc = s.template as<SwitchCase>(); + return this->visitStatementPtr(sc.statement()); + } + case Statement::Kind::kDo: { + auto& d = s.template as<DoStatement>(); + return this->visitExpressionPtr(d.test()) || this->visitStatementPtr(d.statement()); + } + case Statement::Kind::kExpression: + return this->visitExpressionPtr(s.template as<ExpressionStatement>().expression()); + + case Statement::Kind::kFor: { + auto& f = s.template as<ForStatement>(); + return (f.initializer() && this->visitStatementPtr(f.initializer())) || + (f.test() && this->visitExpressionPtr(f.test())) || + (f.next() && this->visitExpressionPtr(f.next())) || + this->visitStatementPtr(f.statement()); + } + case Statement::Kind::kIf: { + auto& i = s.template as<IfStatement>(); + return (i.test() && this->visitExpressionPtr(i.test())) || + (i.ifTrue() && this->visitStatementPtr(i.ifTrue())) || + (i.ifFalse() && this->visitStatementPtr(i.ifFalse())); + } + case Statement::Kind::kReturn: { + auto& r = s.template as<ReturnStatement>(); + return r.expression() && this->visitExpressionPtr(r.expression()); + } + case Statement::Kind::kSwitch: { + auto& sw = s.template as<SwitchStatement>(); + if (this->visitExpressionPtr(sw.value())) { + return true; + } + for (auto& c : sw.cases()) { + if (this->visitStatementPtr(c)) { + return true; + } + } + return false; + } + case Statement::Kind::kVarDeclaration: { + auto& v = s.template as<VarDeclaration>(); + return v.value() && this->visitExpressionPtr(v.value()); + } + default: + SkUNREACHABLE; + } +} + +template <typename T> bool TProgramVisitor<T>::visitProgramElement(typename T::ProgramElement& pe) { + switch (pe.kind()) { + case ProgramElement::Kind::kExtension: + case ProgramElement::Kind::kFunctionPrototype: + case ProgramElement::Kind::kInterfaceBlock: + case ProgramElement::Kind::kModifiers: + case ProgramElement::Kind::kStructDefinition: + // Leaf program elements just return false by default + return false; + + case ProgramElement::Kind::kFunction: + return this->visitStatementPtr(pe.template as<FunctionDefinition>().body()); + + case ProgramElement::Kind::kGlobalVar: + return this->visitStatementPtr(pe.template as<GlobalVarDeclaration>().declaration()); + + default: + SkUNREACHABLE; + } +} + +template class TProgramVisitor<ProgramVisitorTypes>; +template class TProgramVisitor<ProgramWriterTypes>; + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLAnalysis.h b/gfx/skia/skia/src/sksl/SkSLAnalysis.h new file mode 100644 index 0000000000..b875098fda --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLAnalysis.h @@ -0,0 +1,261 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSLAnalysis_DEFINED +#define SkSLAnalysis_DEFINED + +#include "include/private/SkSLSampleUsage.h" +#include "include/private/base/SkTArray.h" + +#include <cstdint> +#include <memory> +#include <set> +#include <vector> + +namespace SkSL { + +class Context; +class ErrorReporter; +class Expression; +class FunctionDeclaration; +class FunctionDefinition; +class Position; +class ProgramElement; +class ProgramUsage; +class Statement; +class SymbolTable; +class Variable; +class VariableReference; +enum class VariableRefKind : int8_t; +struct ForLoopPositions; +struct LoopUnrollInfo; +struct Module; +struct Program; + +/** + * Provides utilities for analyzing SkSL statically before it's composed into a full program. + */ +namespace Analysis { + +/** + * Determines how `program` samples `child`. By default, assumes that the sample coords + * (SK_MAIN_COORDS_BUILTIN) might be modified, so `child.eval(sampleCoords)` is treated as + * Explicit. If writesToSampleCoords is false, treats that as PassThrough, instead. + * If elidedSampleCoordCount is provided, the pointed to value will be incremented by the + * number of sample calls where the above rewrite was performed. + */ +SampleUsage GetSampleUsage(const Program& program, + const Variable& child, + bool writesToSampleCoords = true, + int* elidedSampleCoordCount = nullptr); + +bool ReferencesBuiltin(const Program& program, int builtin); + +bool ReferencesSampleCoords(const Program& program); +bool ReferencesFragCoords(const Program& program); + +bool CallsSampleOutsideMain(const Program& program); + +bool CallsColorTransformIntrinsics(const Program& program); + +/** + * Determines if `function` always returns an opaque color (a vec4 where the last component is known + * to be 1). This is conservative, and based on constant expression analysis. + */ +bool ReturnsOpaqueColor(const FunctionDefinition& function); + +/** + * Checks for recursion or overly-deep function-call chains, and rejects programs which have them. + * Also, computes the size of the program in a completely flattened state--loops fully unrolled, + * function calls inlined--and rejects programs that exceed an arbitrary upper bound. This is + * intended to prevent absurdly large programs from overwhemling SkVM. Only strict-ES2 mode is + * supported; complex control flow is not SkVM-compatible (and this becomes the halting problem) + */ +bool CheckProgramStructure(const Program& program, bool enforceSizeLimit); + +/** Determines if `expr` contains a reference to the variable sk_RTAdjust. */ +bool ContainsRTAdjust(const Expression& expr); + +/** Determines if `expr` has any side effects. (Is the expression state-altering or pure?) */ +bool HasSideEffects(const Expression& expr); + +/** Determines if `expr` is a compile-time constant (composed of just constructors and literals). */ +bool IsCompileTimeConstant(const Expression& expr); + +/** + * Determines if `expr` is a dynamically-uniform expression; this returns true if the expression + * could be evaluated at compile time if uniform values were known. + */ +bool IsDynamicallyUniformExpression(const Expression& expr); + +/** + * Detect an orphaned variable declaration outside of a scope, e.g. if (true) int a;. Returns + * true if an error was reported. + */ +bool DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors = nullptr); + +int NodeCountUpToLimit(const FunctionDefinition& function, int limit); + +/** + * Finds unconditional exits from a switch-case. Returns true if this statement unconditionally + * causes an exit from this switch (via continue, break or return). + */ +bool SwitchCaseContainsUnconditionalExit(Statement& stmt); + +/** + * Finds conditional exits from a switch-case. Returns true if this statement contains a + * conditional that wraps a potential exit from the switch (via continue, break or return). + */ +bool SwitchCaseContainsConditionalExit(Statement& stmt); + +std::unique_ptr<ProgramUsage> GetUsage(const Program& program); +std::unique_ptr<ProgramUsage> GetUsage(const Module& module); + +/** Returns true if the passed-in statement might alter `var`. */ +bool StatementWritesToVariable(const Statement& stmt, const Variable& var); + +/** + * Detects if the passed-in block contains a `continue`, `break` or `return` that could directly + * affect its control flow. (A `continue` or `break` nested inside an inner loop/switch will not + * affect the loop, but a `return` will.) + */ +struct LoopControlFlowInfo { + bool fHasContinue = false; + bool fHasBreak = false; + bool fHasReturn = false; +}; +LoopControlFlowInfo GetLoopControlFlowInfo(const Statement& stmt); + +/** + * Returns true if the expression can be assigned-into. Pass `info` if you want to know the + * VariableReference that will be written to. Pass `errors` to report an error for expressions that + * are not actually writable. + */ +struct AssignmentInfo { + VariableReference* fAssignedVar = nullptr; +}; +bool IsAssignable(Expression& expr, AssignmentInfo* info = nullptr, + ErrorReporter* errors = nullptr); + +/** + * Updates the `refKind` field of the VariableReference at the top level of `expr`. + * If `expr` can be assigned to (`IsAssignable`), true is returned and no errors are reported. + * If not, false is returned. and an error is reported if `errors` is non-null. + */ +bool UpdateVariableRefKind(Expression* expr, VariableRefKind kind, ErrorReporter* errors = nullptr); + +/** + * A "trivial" expression is one where we'd feel comfortable cloning it multiple times in + * the code, without worrying about incurring a performance penalty. Examples: + * - true + * - 3.14159265 + * - myIntVariable + * - myColor.rgb + * - myArray[123] + * - myStruct.myField + * - half4(0) + * + * Trivial-ness is stackable. Somewhat large expressions can occasionally make the cut: + * - half4(myColor.a) + * - myStruct.myArrayField[7].xzy + */ +bool IsTrivialExpression(const Expression& expr); + +/** + * Returns true if both expression trees are the same. Used by the optimizer to look for self- + * assignment or self-comparison; won't necessarily catch complex cases. Rejects expressions + * that may cause side effects. + */ +bool IsSameExpressionTree(const Expression& left, const Expression& right); + +/** + * Returns true if expr is a constant-expression, as defined by GLSL 1.0, section 5.10. + * A constant expression is one of: + * - A literal value + * - A global or local variable qualified as 'const', excluding function parameters + * - An expression formed by an operator on operands that are constant expressions, including + * getting an element of a constant vector or a constant matrix, or a field of a constant + * structure + * - A constructor whose arguments are all constant expressions + * - A built-in function call whose arguments are all constant expressions, with the exception + * of the texture lookup functions + */ +bool IsConstantExpression(const Expression& expr); + +/** + * Returns true if expr is a valid constant-index-expression, as defined by GLSL 1.0, Appendix A, + * Section 5. A constant-index-expression is: + * - A constant-expression + * - Loop indices (as defined in Appendix A, Section 4) + * - Expressions composed of both of the above + */ +bool IsConstantIndexExpression(const Expression& expr, + const std::set<const Variable*>* loopIndices); + +/** + * Ensures that a for-loop meets the strict requirements of The OpenGL ES Shading Language 1.00, + * Appendix A, Section 4. + * If the requirements are met, information about the loop's structure is returned. + * If the requirements are not met, the problem is reported via `errors` (if not nullptr), and + * null is returned. + */ +std::unique_ptr<LoopUnrollInfo> GetLoopUnrollInfo(Position pos, + const ForLoopPositions& positions, + const Statement* loopInitializer, + const Expression* loopTest, + const Expression* loopNext, + const Statement* loopStatement, + ErrorReporter* errors); + +void ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& errors); + +/** Detects functions that fail to return a value on at least one path. */ +bool CanExitWithoutReturningValue(const FunctionDeclaration& funcDecl, const Statement& body); + +/** Determines if a given function has multiple and/or early returns. */ +enum class ReturnComplexity { + kSingleSafeReturn, + kScopedReturns, + kEarlyReturns, +}; +ReturnComplexity GetReturnComplexity(const FunctionDefinition& funcDef); + +/** + * Runs at finalization time to perform any last-minute correctness checks: + * - Reports dangling FunctionReference or TypeReference expressions + * - Reports function `out` params which are never written to (structs are currently exempt) + */ +void DoFinalizationChecks(const Program& program); + +/** + * Error checks compute shader in/outs and returns a vector containing them ordered by location. + */ +SkTArray<const SkSL::Variable*> GetComputeShaderMainParams(const Context& context, + const Program& program); + +/** + * Tracks the symbol table stack, in conjunction with a ProgramVisitor. Inside `visitStatement`, + * pass the current statement and a symbol-table vector to a SymbolTableStackBuilder and the symbol + * table stack will be maintained automatically. + */ +class SymbolTableStackBuilder { +public: + // If the passed-in statement holds a symbol table, adds it to the stack. + SymbolTableStackBuilder(const Statement* stmt, + std::vector<std::shared_ptr<SymbolTable>>* stack); + + // If a symbol table was added to the stack earlier, removes it from the stack. + ~SymbolTableStackBuilder(); + +private: + std::vector<std::shared_ptr<SymbolTable>>* fStackToPop = nullptr; +}; + +} // namespace Analysis +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.cpp b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.cpp new file mode 100644 index 0000000000..460358e54a --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLBuiltinTypes.h" + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/spirv.h" + +namespace SkSL { + +/** + * Initializes the core SkSL types. + */ +BuiltinTypes::BuiltinTypes() + : fFloat(Type::MakeScalarType( + "float", "f", Type::NumberKind::kFloat, /*priority=*/10, /*bitWidth=*/32)) + , fFloat2(Type::MakeVectorType("float2", "f2", *fFloat, /*columns=*/2)) + , fFloat3(Type::MakeVectorType("float3", "f3", *fFloat, /*columns=*/3)) + , fFloat4(Type::MakeVectorType("float4", "f4", *fFloat, /*columns=*/4)) + , fHalf(Type::MakeScalarType( + "half", "h", Type::NumberKind::kFloat, /*priority=*/9, /*bitWidth=*/16)) + , fHalf2(Type::MakeVectorType("half2", "h2", *fHalf, /*columns=*/2)) + , fHalf3(Type::MakeVectorType("half3", "h3", *fHalf, /*columns=*/3)) + , fHalf4(Type::MakeVectorType("half4", "h4", *fHalf, /*columns=*/4)) + , fInt(Type::MakeScalarType( + "int", "i", Type::NumberKind::kSigned, /*priority=*/7, /*bitWidth=*/32)) + , fInt2(Type::MakeVectorType("int2", "i2", *fInt, /*columns=*/2)) + , fInt3(Type::MakeVectorType("int3", "i3", *fInt, /*columns=*/3)) + , fInt4(Type::MakeVectorType("int4", "i4", *fInt, /*columns=*/4)) + , fUInt(Type::MakeScalarType( + "uint", "I", Type::NumberKind::kUnsigned, /*priority=*/6, /*bitWidth=*/32)) + , fUInt2(Type::MakeVectorType("uint2", "I2", *fUInt, /*columns=*/2)) + , fUInt3(Type::MakeVectorType("uint3", "I3", *fUInt, /*columns=*/3)) + , fUInt4(Type::MakeVectorType("uint4", "I4", *fUInt, /*columns=*/4)) + , fShort(Type::MakeScalarType( + "short", "s", Type::NumberKind::kSigned, /*priority=*/4, /*bitWidth=*/16)) + , fShort2(Type::MakeVectorType("short2", "s2", *fShort, /*columns=*/2)) + , fShort3(Type::MakeVectorType("short3", "s3", *fShort, /*columns=*/3)) + , fShort4(Type::MakeVectorType("short4", "s4", *fShort, /*columns=*/4)) + , fUShort(Type::MakeScalarType( + "ushort", "S", Type::NumberKind::kUnsigned, /*priority=*/3, /*bitWidth=*/16)) + , fUShort2(Type::MakeVectorType("ushort2", "S2", *fUShort, /*columns=*/2)) + , fUShort3(Type::MakeVectorType("ushort3", "S3", *fUShort, /*columns=*/3)) + , fUShort4(Type::MakeVectorType("ushort4", "S4", *fUShort, /*columns=*/4)) + , fBool(Type::MakeScalarType( + "bool", "b", Type::NumberKind::kBoolean, /*priority=*/0, /*bitWidth=*/1)) + , fBool2(Type::MakeVectorType("bool2", "b2", *fBool, /*columns=*/2)) + , fBool3(Type::MakeVectorType("bool3", "b3", *fBool, /*columns=*/3)) + , fBool4(Type::MakeVectorType("bool4", "b4", *fBool, /*columns=*/4)) + , fInvalid(Type::MakeSpecialType("<INVALID>", "O", Type::TypeKind::kOther)) + , fPoison(Type::MakeSpecialType(Compiler::POISON_TAG, "P", Type::TypeKind::kOther)) + , fVoid(Type::MakeSpecialType("void", "v", Type::TypeKind::kVoid)) + , fFloatLiteral(Type::MakeLiteralType("$floatLiteral", *fFloat, /*priority=*/8)) + , fIntLiteral(Type::MakeLiteralType("$intLiteral", *fInt, /*priority=*/5)) + , fFloat2x2(Type::MakeMatrixType("float2x2", "f22", *fFloat, /*columns=*/2, /*rows=*/2)) + , fFloat2x3(Type::MakeMatrixType("float2x3", "f23", *fFloat, /*columns=*/2, /*rows=*/3)) + , fFloat2x4(Type::MakeMatrixType("float2x4", "f24", *fFloat, /*columns=*/2, /*rows=*/4)) + , fFloat3x2(Type::MakeMatrixType("float3x2", "f32", *fFloat, /*columns=*/3, /*rows=*/2)) + , fFloat3x3(Type::MakeMatrixType("float3x3", "f33", *fFloat, /*columns=*/3, /*rows=*/3)) + , fFloat3x4(Type::MakeMatrixType("float3x4", "f34", *fFloat, /*columns=*/3, /*rows=*/4)) + , fFloat4x2(Type::MakeMatrixType("float4x2", "f42", *fFloat, /*columns=*/4, /*rows=*/2)) + , fFloat4x3(Type::MakeMatrixType("float4x3", "f43", *fFloat, /*columns=*/4, /*rows=*/3)) + , fFloat4x4(Type::MakeMatrixType("float4x4", "f44", *fFloat, /*columns=*/4, /*rows=*/4)) + , fHalf2x2(Type::MakeMatrixType("half2x2", "h22", *fHalf, /*columns=*/2, /*rows=*/2)) + , fHalf2x3(Type::MakeMatrixType("half2x3", "h23", *fHalf, /*columns=*/2, /*rows=*/3)) + , fHalf2x4(Type::MakeMatrixType("half2x4", "h24", *fHalf, /*columns=*/2, /*rows=*/4)) + , fHalf3x2(Type::MakeMatrixType("half3x2", "h32", *fHalf, /*columns=*/3, /*rows=*/2)) + , fHalf3x3(Type::MakeMatrixType("half3x3", "h33", *fHalf, /*columns=*/3, /*rows=*/3)) + , fHalf3x4(Type::MakeMatrixType("half3x4", "h34", *fHalf, /*columns=*/3, /*rows=*/4)) + , fHalf4x2(Type::MakeMatrixType("half4x2", "h42", *fHalf, /*columns=*/4, /*rows=*/2)) + , fHalf4x3(Type::MakeMatrixType("half4x3", "h43", *fHalf, /*columns=*/4, /*rows=*/3)) + , fHalf4x4(Type::MakeMatrixType("half4x4", "h44", *fHalf, /*columns=*/4, /*rows=*/4)) + , fVec2(Type::MakeAliasType("vec2", *fFloat2)) + , fVec3(Type::MakeAliasType("vec3", *fFloat3)) + , fVec4(Type::MakeAliasType("vec4", *fFloat4)) + , fIVec2(Type::MakeAliasType("ivec2", *fInt2)) + , fIVec3(Type::MakeAliasType("ivec3", *fInt3)) + , fIVec4(Type::MakeAliasType("ivec4", *fInt4)) + , fBVec2(Type::MakeAliasType("bvec2", *fBool2)) + , fBVec3(Type::MakeAliasType("bvec3", *fBool3)) + , fBVec4(Type::MakeAliasType("bvec4", *fBool4)) + , fMat2(Type::MakeAliasType("mat2", *fFloat2x2)) + , fMat3(Type::MakeAliasType("mat3", *fFloat3x3)) + , fMat4(Type::MakeAliasType("mat4", *fFloat4x4)) + , fMat2x2(Type::MakeAliasType("mat2x2", *fFloat2x2)) + , fMat2x3(Type::MakeAliasType("mat2x3", *fFloat2x3)) + , fMat2x4(Type::MakeAliasType("mat2x4", *fFloat2x4)) + , fMat3x2(Type::MakeAliasType("mat3x2", *fFloat3x2)) + , fMat3x3(Type::MakeAliasType("mat3x3", *fFloat3x3)) + , fMat3x4(Type::MakeAliasType("mat3x4", *fFloat3x4)) + , fMat4x2(Type::MakeAliasType("mat4x2", *fFloat4x2)) + , fMat4x3(Type::MakeAliasType("mat4x3", *fFloat4x3)) + , fMat4x4(Type::MakeAliasType("mat4x4", *fFloat4x4)) + , fTexture2D(Type::MakeTextureType("texture2D", + SpvDim2D, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/false, + Type::TextureAccess::kSample)) + , fTextureExternalOES(Type::MakeTextureType("textureExternalOES", + SpvDim2D, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/false, + Type::TextureAccess::kSample)) + , fTexture2DRect(Type::MakeTextureType("texture2DRect", + SpvDimRect, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/false, + Type::TextureAccess::kSample)) + , fReadWriteTexture2D(Type::MakeTextureType("readWriteTexture2D", + SpvDim2D, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/false, + Type::TextureAccess::kReadWrite)) + , fReadOnlyTexture2D(Type::MakeTextureType("readonlyTexture2D", + SpvDim2D, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/false, + Type::TextureAccess::kRead)) + , fWriteOnlyTexture2D(Type::MakeTextureType("writeonlyTexture2D", + SpvDim2D, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/false, + Type::TextureAccess::kWrite)) + , fGenTexture2D(Type::MakeGenericType("$genTexture2D", + {fReadOnlyTexture2D.get(), + fWriteOnlyTexture2D.get(), + fReadWriteTexture2D.get()})) + , fReadableTexture2D(Type::MakeGenericType("$readableTexture2D", + {fReadOnlyTexture2D.get(), + fInvalid.get(), + fReadWriteTexture2D.get()})) + , fWritableTexture2D(Type::MakeGenericType("$writableTexture2D", + {fInvalid.get(), + fWriteOnlyTexture2D.get(), + fReadWriteTexture2D.get()})) + , fSampler2D(Type::MakeSamplerType("sampler2D", *fTexture2D)) + , fSamplerExternalOES(Type::MakeSamplerType("samplerExternalOES", *fTextureExternalOES)) + , fSampler2DRect(Type::MakeSamplerType("sampler2DRect", *fTexture2DRect)) + + , fSampler(Type::MakeSpecialType("sampler", "ss", Type::TypeKind::kSeparateSampler)) + + , fSubpassInput(Type::MakeTextureType("subpassInput", + SpvDimSubpassData, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/false, + Type::TextureAccess::kRead)) + , fSubpassInputMS(Type::MakeTextureType("subpassInputMS", + SpvDimSubpassData, + /*isDepth=*/false, + /*isArrayedTexture=*/false, + /*isMultisampled=*/true, + Type::TextureAccess::kRead)) + , fGenType(Type::MakeGenericType("$genType", {fFloat.get(), fFloat2.get(), fFloat3.get(), + fFloat4.get()})) + , fGenHType(Type::MakeGenericType("$genHType", {fHalf.get(), fHalf2.get(), fHalf3.get(), + fHalf4.get()})) + , fGenIType(Type::MakeGenericType("$genIType", {fInt.get(), fInt2.get(), fInt3.get(), + fInt4.get()})) + , fGenUType(Type::MakeGenericType("$genUType", {fUInt.get(), fUInt2.get(), fUInt3.get(), + fUInt4.get()})) + , fGenBType(Type::MakeGenericType("$genBType", {fBool.get(), fBool2.get(), fBool3.get(), + fBool4.get()})) + , fMat(Type::MakeGenericType("$mat", {fFloat2x2.get(), fFloat2x3.get(), fFloat2x4.get(), + fFloat3x2.get(), fFloat3x3.get(), fFloat3x4.get(), + fFloat4x2.get(), fFloat4x3.get(), fFloat4x4.get()})) + , fHMat(Type::MakeGenericType( + "$hmat", + {fHalf2x2.get(), fHalf2x3.get(), fHalf2x4.get(), fHalf3x2.get(), fHalf3x3.get(), + fHalf3x4.get(), fHalf4x2.get(), fHalf4x3.get(), fHalf4x4.get()})) + , fSquareMat(Type::MakeGenericType("$squareMat", {fInvalid.get(), fFloat2x2.get(), + fFloat3x3.get(), fFloat4x4.get()})) + , fSquareHMat(Type::MakeGenericType("$squareHMat", {fInvalid.get(), fHalf2x2.get(), + fHalf3x3.get(), fHalf4x4.get()})) + , fVec(Type::MakeGenericType("$vec", {fInvalid.get(), fFloat2.get(), fFloat3.get(), + fFloat4.get()})) + , fHVec(Type::MakeGenericType("$hvec", {fInvalid.get(), fHalf2.get(), fHalf3.get(), + fHalf4.get()})) + , fIVec(Type::MakeGenericType("$ivec", {fInvalid.get(), fInt2.get(), fInt3.get(), + fInt4.get()})) + , fUVec(Type::MakeGenericType("$uvec", {fInvalid.get(), fUInt2.get(), fUInt3.get(), + fUInt4.get()})) + , fSVec(Type::MakeGenericType("$svec", {fInvalid.get(), fShort2.get(), fShort3.get(), + fShort4.get()})) + , fUSVec(Type::MakeGenericType("$usvec", {fInvalid.get(), fUShort2.get(), fUShort3.get(), + fUShort4.get()})) + , fBVec(Type::MakeGenericType("$bvec", {fInvalid.get(), fBool2.get(), fBool3.get(), + fBool4.get()})) + , fSkCaps(Type::MakeSpecialType("$sk_Caps", "O", Type::TypeKind::kOther)) + , fColorFilter(Type::MakeSpecialType("colorFilter", "CF", Type::TypeKind::kColorFilter)) + , fShader(Type::MakeSpecialType("shader", "SH", Type::TypeKind::kShader)) + , fBlender(Type::MakeSpecialType("blender", "B", Type::TypeKind::kBlender)) + , fAtomicUInt(Type::MakeAtomicType("atomicUint", "au")) {} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.h b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.h new file mode 100644 index 0000000000..75da759659 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLBuiltinTypes.h @@ -0,0 +1,167 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BUILTIN_TYPES +#define SKSL_BUILTIN_TYPES + +#include <memory> + +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +/** + * Contains the built-in, core types for SkSL. + */ +class BuiltinTypes { +public: + BuiltinTypes(); + + const std::unique_ptr<Type> fFloat; + const std::unique_ptr<Type> fFloat2; + const std::unique_ptr<Type> fFloat3; + const std::unique_ptr<Type> fFloat4; + + const std::unique_ptr<Type> fHalf; + const std::unique_ptr<Type> fHalf2; + const std::unique_ptr<Type> fHalf3; + const std::unique_ptr<Type> fHalf4; + + const std::unique_ptr<Type> fInt; + const std::unique_ptr<Type> fInt2; + const std::unique_ptr<Type> fInt3; + const std::unique_ptr<Type> fInt4; + + const std::unique_ptr<Type> fUInt; + const std::unique_ptr<Type> fUInt2; + const std::unique_ptr<Type> fUInt3; + const std::unique_ptr<Type> fUInt4; + + const std::unique_ptr<Type> fShort; + const std::unique_ptr<Type> fShort2; + const std::unique_ptr<Type> fShort3; + const std::unique_ptr<Type> fShort4; + + const std::unique_ptr<Type> fUShort; + const std::unique_ptr<Type> fUShort2; + const std::unique_ptr<Type> fUShort3; + const std::unique_ptr<Type> fUShort4; + + const std::unique_ptr<Type> fBool; + const std::unique_ptr<Type> fBool2; + const std::unique_ptr<Type> fBool3; + const std::unique_ptr<Type> fBool4; + + const std::unique_ptr<Type> fInvalid; + const std::unique_ptr<Type> fPoison; + const std::unique_ptr<Type> fVoid; + const std::unique_ptr<Type> fFloatLiteral; + const std::unique_ptr<Type> fIntLiteral; + + const std::unique_ptr<Type> fFloat2x2; + const std::unique_ptr<Type> fFloat2x3; + const std::unique_ptr<Type> fFloat2x4; + const std::unique_ptr<Type> fFloat3x2; + const std::unique_ptr<Type> fFloat3x3; + const std::unique_ptr<Type> fFloat3x4; + const std::unique_ptr<Type> fFloat4x2; + const std::unique_ptr<Type> fFloat4x3; + const std::unique_ptr<Type> fFloat4x4; + + const std::unique_ptr<Type> fHalf2x2; + const std::unique_ptr<Type> fHalf2x3; + const std::unique_ptr<Type> fHalf2x4; + const std::unique_ptr<Type> fHalf3x2; + const std::unique_ptr<Type> fHalf3x3; + const std::unique_ptr<Type> fHalf3x4; + const std::unique_ptr<Type> fHalf4x2; + const std::unique_ptr<Type> fHalf4x3; + const std::unique_ptr<Type> fHalf4x4; + + const std::unique_ptr<Type> fVec2; + const std::unique_ptr<Type> fVec3; + const std::unique_ptr<Type> fVec4; + + const std::unique_ptr<Type> fIVec2; + const std::unique_ptr<Type> fIVec3; + const std::unique_ptr<Type> fIVec4; + + const std::unique_ptr<Type> fBVec2; + const std::unique_ptr<Type> fBVec3; + const std::unique_ptr<Type> fBVec4; + + const std::unique_ptr<Type> fMat2; + const std::unique_ptr<Type> fMat3; + const std::unique_ptr<Type> fMat4; + + const std::unique_ptr<Type> fMat2x2; + const std::unique_ptr<Type> fMat2x3; + const std::unique_ptr<Type> fMat2x4; + const std::unique_ptr<Type> fMat3x2; + const std::unique_ptr<Type> fMat3x3; + const std::unique_ptr<Type> fMat3x4; + const std::unique_ptr<Type> fMat4x2; + const std::unique_ptr<Type> fMat4x3; + const std::unique_ptr<Type> fMat4x4; + + const std::unique_ptr<Type> fTexture2D; + const std::unique_ptr<Type> fTextureExternalOES; + const std::unique_ptr<Type> fTexture2DRect; + + const std::unique_ptr<Type> fReadWriteTexture2D; + const std::unique_ptr<Type> fReadOnlyTexture2D; + const std::unique_ptr<Type> fWriteOnlyTexture2D; + + const std::unique_ptr<Type> fGenTexture2D; + const std::unique_ptr<Type> fReadableTexture2D; + const std::unique_ptr<Type> fWritableTexture2D; + + const std::unique_ptr<Type> fSampler2D; + const std::unique_ptr<Type> fSamplerExternalOES; + const std::unique_ptr<Type> fSampler2DRect; + + const std::unique_ptr<Type> fSampler; + + const std::unique_ptr<Type> fSubpassInput; + const std::unique_ptr<Type> fSubpassInputMS; + + const std::unique_ptr<Type> fGenType; + const std::unique_ptr<Type> fGenHType; + const std::unique_ptr<Type> fGenIType; + const std::unique_ptr<Type> fGenUType; + const std::unique_ptr<Type> fGenBType; + + const std::unique_ptr<Type> fMat; + const std::unique_ptr<Type> fHMat; + const std::unique_ptr<Type> fSquareMat; + const std::unique_ptr<Type> fSquareHMat; + + const std::unique_ptr<Type> fVec; + + const std::unique_ptr<Type> fHVec; + const std::unique_ptr<Type> fDVec; + const std::unique_ptr<Type> fIVec; + const std::unique_ptr<Type> fUVec; + const std::unique_ptr<Type> fSVec; + const std::unique_ptr<Type> fUSVec; + const std::unique_ptr<Type> fByteVec; + const std::unique_ptr<Type> fUByteVec; + + const std::unique_ptr<Type> fBVec; + + const std::unique_ptr<Type> fSkCaps; + + const std::unique_ptr<Type> fColorFilter; + const std::unique_ptr<Type> fShader; + const std::unique_ptr<Type> fBlender; + + const std::unique_ptr<Type> fAtomicUInt; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLCompiler.cpp b/gfx/skia/skia/src/sksl/SkSLCompiler.cpp new file mode 100644 index 0000000000..78498b58af --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCompiler.cpp @@ -0,0 +1,726 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLCompiler.h" + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLProgramKind.h" +#include "include/private/SkSLSymbol.h" +#include "include/private/base/SkDebug.h" +#include "include/sksl/DSLCore.h" +#include "include/sksl/DSLModifiers.h" +#include "include/sksl/DSLType.h" +#include "src/core/SkTraceEvent.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLInliner.h" +#include "src/sksl/SkSLModuleLoader.h" +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLParser.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLField.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionReference.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSymbolTable.h" // IWYU pragma: keep +#include "src/sksl/ir/SkSLTypeReference.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <atomic> +#include <cstdint> +#include <memory> +#include <utility> + +#if defined(SKSL_STANDALONE) +#include <fstream> +#endif + +#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE) +#include "src/sksl/codegen/SkSLGLSLCodeGenerator.h" +#include "src/sksl/codegen/SkSLMetalCodeGenerator.h" +#include "src/sksl/codegen/SkSLSPIRVCodeGenerator.h" +#include "src/sksl/codegen/SkSLSPIRVtoHLSL.h" +#include "src/sksl/codegen/SkSLWGSLCodeGenerator.h" +#endif + +#ifdef SK_ENABLE_SPIRV_VALIDATION +#include "spirv-tools/libspirv.hpp" +#endif + +#ifdef SK_ENABLE_WGSL_VALIDATION +#include "tint/tint.h" +#endif + +namespace SkSL { + +class ModifiersPool; + +// These flags allow tools like Viewer or Nanobench to override the compiler's ProgramSettings. +Compiler::OverrideFlag Compiler::sOptimizer = OverrideFlag::kDefault; +Compiler::OverrideFlag Compiler::sInliner = OverrideFlag::kDefault; + +using RefKind = VariableReference::RefKind; + +class AutoSource { +public: + AutoSource(Compiler* compiler, std::string_view source) + : fCompiler(compiler) { + SkASSERT(!fCompiler->errorReporter().source().data()); + fCompiler->errorReporter().setSource(source); + } + + ~AutoSource() { + fCompiler->errorReporter().setSource(std::string_view()); + } + + Compiler* fCompiler; +}; + +class AutoProgramConfig { +public: + AutoProgramConfig(Context& context, ProgramConfig* config) + : fContext(context) + , fOldConfig(context.fConfig) { + fContext.fConfig = config; + } + + ~AutoProgramConfig() { + fContext.fConfig = fOldConfig; + } + + Context& fContext; + ProgramConfig* fOldConfig; +}; + +class AutoShaderCaps { +public: + AutoShaderCaps(std::shared_ptr<Context>& context, const ShaderCaps* caps) + : fContext(context.get()) + , fOldCaps(fContext->fCaps) { + fContext->fCaps = caps; + } + + ~AutoShaderCaps() { + fContext->fCaps = fOldCaps; + } + + Context* fContext; + const ShaderCaps* fOldCaps; +}; + +class AutoModifiersPool { +public: + AutoModifiersPool(std::shared_ptr<Context>& context, ModifiersPool* modifiersPool) + : fContext(context.get()) { + SkASSERT(!fContext->fModifiersPool); + fContext->fModifiersPool = modifiersPool; + } + + ~AutoModifiersPool() { + fContext->fModifiersPool = nullptr; + } + + Context* fContext; +}; + +Compiler::Compiler(const ShaderCaps* caps) : fErrorReporter(this), fCaps(caps) { + SkASSERT(caps); + + auto moduleLoader = ModuleLoader::Get(); + fContext = std::make_shared<Context>(moduleLoader.builtinTypes(), /*caps=*/nullptr, + fErrorReporter); +} + +Compiler::~Compiler() {} + +const Module* Compiler::moduleForProgramKind(ProgramKind kind) { + auto m = ModuleLoader::Get(); + switch (kind) { + case ProgramKind::kVertex: return m.loadVertexModule(this); + case ProgramKind::kFragment: return m.loadFragmentModule(this); + case ProgramKind::kCompute: return m.loadComputeModule(this); + case ProgramKind::kGraphiteVertex: return m.loadGraphiteVertexModule(this); + case ProgramKind::kGraphiteFragment: return m.loadGraphiteFragmentModule(this); + case ProgramKind::kPrivateRuntimeShader: return m.loadPrivateRTShaderModule(this); + case ProgramKind::kRuntimeColorFilter: + case ProgramKind::kRuntimeShader: + case ProgramKind::kRuntimeBlender: + case ProgramKind::kPrivateRuntimeColorFilter: + case ProgramKind::kPrivateRuntimeBlender: + case ProgramKind::kMeshVertex: + case ProgramKind::kMeshFragment: return m.loadPublicModule(this); + } + SkUNREACHABLE; +} + +void Compiler::FinalizeSettings(ProgramSettings* settings, ProgramKind kind) { + // Honor our optimization-override flags. + switch (sOptimizer) { + case OverrideFlag::kDefault: + break; + case OverrideFlag::kOff: + settings->fOptimize = false; + break; + case OverrideFlag::kOn: + settings->fOptimize = true; + break; + } + + switch (sInliner) { + case OverrideFlag::kDefault: + break; + case OverrideFlag::kOff: + settings->fInlineThreshold = 0; + break; + case OverrideFlag::kOn: + if (settings->fInlineThreshold == 0) { + settings->fInlineThreshold = kDefaultInlineThreshold; + } + break; + } + + // Disable optimization settings that depend on a parent setting which has been disabled. + settings->fInlineThreshold *= (int)settings->fOptimize; + settings->fRemoveDeadFunctions &= settings->fOptimize; + settings->fRemoveDeadVariables &= settings->fOptimize; + + // Runtime effects always allow narrowing conversions. + if (ProgramConfig::IsRuntimeEffect(kind)) { + settings->fAllowNarrowingConversions = true; + } +} + +std::unique_ptr<Module> Compiler::compileModule(ProgramKind kind, + const char* moduleName, + std::string moduleSource, + const Module* parent, + ModifiersPool& modifiersPool, + bool shouldInline) { + SkASSERT(parent); + SkASSERT(!moduleSource.empty()); + SkASSERT(this->errorCount() == 0); + + // Modules are shared and cannot rely on shader caps. + AutoShaderCaps autoCaps(fContext, nullptr); + AutoModifiersPool autoPool(fContext, &modifiersPool); + + // Compile the module from source, using default program settings. + ProgramSettings settings; + FinalizeSettings(&settings, kind); + SkSL::Parser parser{this, settings, kind, std::move(moduleSource)}; + std::unique_ptr<Module> module = parser.moduleInheritingFrom(parent); + if (this->errorCount() != 0) { + SkDebugf("Unexpected errors compiling %s:\n\n%s\n", moduleName, this->errorText().c_str()); + return nullptr; + } + if (shouldInline) { + this->optimizeModuleAfterLoading(kind, *module); + } + return module; +} + +std::unique_ptr<Program> Compiler::convertProgram(ProgramKind kind, + std::string text, + ProgramSettings settings) { + TRACE_EVENT0("skia.shaders", "SkSL::Compiler::convertProgram"); + + // Make sure the passed-in settings are valid. + FinalizeSettings(&settings, kind); + + // Put the ShaderCaps into the context while compiling a program. + AutoShaderCaps autoCaps(fContext, fCaps); + + this->resetErrors(); + + return Parser(this, settings, kind, std::move(text)).program(); +} + +std::unique_ptr<Expression> Compiler::convertIdentifier(Position pos, std::string_view name) { + const Symbol* result = fSymbolTable->find(name); + if (!result) { + this->errorReporter().error(pos, "unknown identifier '" + std::string(name) + "'"); + return nullptr; + } + switch (result->kind()) { + case Symbol::Kind::kFunctionDeclaration: { + return std::make_unique<FunctionReference>(*fContext, pos, + &result->as<FunctionDeclaration>()); + } + case Symbol::Kind::kVariable: { + const Variable* var = &result->as<Variable>(); + // default to kRead_RefKind; this will be corrected later if the variable is written to + return VariableReference::Make(pos, var, VariableReference::RefKind::kRead); + } + case Symbol::Kind::kField: { + const Field* field = &result->as<Field>(); + auto base = VariableReference::Make(pos, &field->owner(), + VariableReference::RefKind::kRead); + return FieldAccess::Make(*fContext, pos, std::move(base), field->fieldIndex(), + FieldAccess::OwnerKind::kAnonymousInterfaceBlock); + } + case Symbol::Kind::kType: { + // go through DSLType so we report errors on private types + dsl::DSLModifiers modifiers; + dsl::DSLType dslType(result->name(), &modifiers, pos); + return TypeReference::Convert(*fContext, pos, &dslType.skslType()); + } + default: + SK_ABORT("unsupported symbol type %d\n", (int) result->kind()); + } +} + +bool Compiler::optimizeModuleBeforeMinifying(ProgramKind kind, Module& module) { + SkASSERT(this->errorCount() == 0); + + auto m = SkSL::ModuleLoader::Get(); + + // Create a temporary program configuration with default settings. + ProgramConfig config; + config.fIsBuiltinCode = true; + config.fKind = kind; + AutoProgramConfig autoConfig(this->context(), &config); + AutoModifiersPool autoPool(fContext, &m.coreModifiers()); + + std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module); + + // Assign shorter names to symbols as long as it won't change the external meaning of the code. + Transform::RenamePrivateSymbols(this->context(), module, usage.get(), kind); + + // Replace constant variables with their literal values to save space. + Transform::ReplaceConstVarsWithLiterals(module, usage.get()); + + // Remove any unreachable code. + Transform::EliminateUnreachableCode(module, usage.get()); + + // We can only remove dead functions from runtime shaders, since runtime-effect helper functions + // are isolated from other parts of the program. In a module, an unreferenced function is + // intended to be called by the code that includes the module. + if (kind == ProgramKind::kRuntimeShader) { + while (Transform::EliminateDeadFunctions(this->context(), module, usage.get())) { + // Removing dead functions may cause more functions to become unreferenced. Try again. + } + } + + while (Transform::EliminateDeadLocalVariables(this->context(), module, usage.get())) { + // Removing dead variables may cause more variables to become unreferenced. Try again. + } + + // Runtime shaders are isolated from other parts of the program via name mangling, so we can + // eliminate public globals if they aren't referenced. Otherwise, we only eliminate private + // globals (prefixed with `$`) to avoid changing the meaning of the module code. + bool onlyPrivateGlobals = !ProgramConfig::IsRuntimeEffect(kind); + while (Transform::EliminateDeadGlobalVariables(this->context(), module, usage.get(), + onlyPrivateGlobals)) { + // Repeat until no changes occur. + } + + // We eliminate empty statements to avoid runs of `;;;;;;` caused by the previous passes. + SkSL::Transform::EliminateEmptyStatements(module); + + // Make sure that program usage is still correct after the optimization pass is complete. + SkASSERT(*usage == *Analysis::GetUsage(module)); + + return this->errorCount() == 0; +} + +bool Compiler::optimizeModuleAfterLoading(ProgramKind kind, Module& module) { + SkASSERT(this->errorCount() == 0); + +#ifndef SK_ENABLE_OPTIMIZE_SIZE + // Create a temporary program configuration with default settings. + ProgramConfig config; + config.fIsBuiltinCode = true; + config.fKind = kind; + AutoProgramConfig autoConfig(this->context(), &config); + + std::unique_ptr<ProgramUsage> usage = Analysis::GetUsage(module); + + // Perform inline-candidate analysis and inline any functions deemed suitable. + Inliner inliner(fContext.get()); + while (this->errorCount() == 0) { + if (!this->runInliner(&inliner, module.fElements, module.fSymbols, usage.get())) { + break; + } + } + // Make sure that program usage is still correct after the optimization pass is complete. + SkASSERT(*usage == *Analysis::GetUsage(module)); +#endif + + return this->errorCount() == 0; +} + +bool Compiler::optimize(Program& program) { + // The optimizer only needs to run when it is enabled. + if (!program.fConfig->fSettings.fOptimize) { + return true; + } + + AutoShaderCaps autoCaps(fContext, fCaps); + + SkASSERT(!this->errorCount()); + if (this->errorCount() == 0) { +#ifndef SK_ENABLE_OPTIMIZE_SIZE + // Run the inliner only once; it is expensive! Multiple passes can occasionally shake out + // more wins, but it's diminishing returns. + Inliner inliner(fContext.get()); + this->runInliner(&inliner, program.fOwnedElements, program.fSymbols, program.fUsage.get()); +#endif + + // Unreachable code can confuse some drivers, so it's worth removing. (skia:12012) + Transform::EliminateUnreachableCode(program); + + while (Transform::EliminateDeadFunctions(program)) { + // Removing dead functions may cause more functions to become unreferenced. Try again. + } + while (Transform::EliminateDeadLocalVariables(program)) { + // Removing dead variables may cause more variables to become unreferenced. Try again. + } + while (Transform::EliminateDeadGlobalVariables(program)) { + // Repeat until no changes occur. + } + // Make sure that program usage is still correct after the optimization pass is complete. + SkASSERT(*program.usage() == *Analysis::GetUsage(program)); + } + + return this->errorCount() == 0; +} + +bool Compiler::runInliner(Inliner* inliner, + const std::vector<std::unique_ptr<ProgramElement>>& elements, + std::shared_ptr<SymbolTable> symbols, + ProgramUsage* usage) { +#ifdef SK_ENABLE_OPTIMIZE_SIZE + return true; +#else + // The program's SymbolTable was taken out of fSymbolTable when the program was bundled, but + // the inliner relies (indirectly) on having a valid SymbolTable. + // In particular, inlining can turn a non-optimizable expression like `normalize(myVec)` into + // `normalize(vec2(7))`, which is now optimizable. The optimizer can use DSL to simplify this + // expression--e.g., in the case of normalize, using DSL's Length(). The DSL relies on + // convertIdentifier() to look up `length`. convertIdentifier() needs a valid symbol table to + // find the declaration of `length`. To allow this chain of events to succeed, we re-insert the + // program's symbol table temporarily. + SkASSERT(!fSymbolTable); + fSymbolTable = symbols; + + bool result = inliner->analyze(elements, symbols, usage); + + fSymbolTable = nullptr; + return result; +#endif +} + +bool Compiler::finalize(Program& program) { + AutoShaderCaps autoCaps(fContext, fCaps); + + // Copy all referenced built-in functions into the Program. + Transform::FindAndDeclareBuiltinFunctions(program); + + // Variables defined in the pre-includes need their declaring elements added to the program. + Transform::FindAndDeclareBuiltinVariables(program); + + // Do one last correctness-check pass. This looks for dangling FunctionReference/TypeReference + // expressions, and reports them as errors. + Analysis::DoFinalizationChecks(program); + + if (fContext->fConfig->strictES2Mode() && this->errorCount() == 0) { + // Enforce Appendix A, Section 5 of the GLSL ES 1.00 spec -- Indexing. This logic assumes + // that all loops meet the criteria of Section 4, and if they don't, could crash. + for (const auto& pe : program.fOwnedElements) { + Analysis::ValidateIndexingForES2(*pe, this->errorReporter()); + } + } + if (this->errorCount() == 0) { + bool enforceSizeLimit = ProgramConfig::IsRuntimeEffect(program.fConfig->fKind); + Analysis::CheckProgramStructure(program, enforceSizeLimit); + } + + // Make sure that program usage is still correct after finalization is complete. + SkASSERT(*program.usage() == *Analysis::GetUsage(program)); + + return this->errorCount() == 0; +} + +#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE) + +#if defined(SK_ENABLE_SPIRV_VALIDATION) +static bool validate_spirv(ErrorReporter& reporter, std::string_view program) { + SkASSERT(0 == program.size() % 4); + const uint32_t* programData = reinterpret_cast<const uint32_t*>(program.data()); + size_t programSize = program.size() / 4; + + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_0); + std::string errors; + auto msgFn = [&errors](spv_message_level_t, const char*, const spv_position_t&, const char* m) { + errors += "SPIR-V validation error: "; + errors += m; + errors += '\n'; + }; + tools.SetMessageConsumer(msgFn); + + // Verify that the SPIR-V we produced is valid. At runtime, we will abort() with a message + // explaining the error. In standalone mode (skslc), we will send the message, plus the + // entire disassembled SPIR-V (for easier context & debugging) as *our* error message. + bool result = tools.Validate(programData, programSize); + if (!result) { +#if defined(SKSL_STANDALONE) + // Convert the string-stream to a SPIR-V disassembly. + std::string disassembly; + if (tools.Disassemble(programData, programSize, &disassembly)) { + errors.append(disassembly); + } + reporter.error(Position(), errors); +#else + SkDEBUGFAILF("%s", errors.c_str()); +#endif + } + return result; +} +#endif + +bool Compiler::toSPIRV(Program& program, OutputStream& out) { + TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toSPIRV"); + AutoSource as(this, *program.fSource); + AutoShaderCaps autoCaps(fContext, fCaps); + ProgramSettings settings; + settings.fUseMemoryPool = false; + dsl::Start(this, program.fConfig->fKind, settings); + dsl::SetErrorReporter(&fErrorReporter); + fSymbolTable = program.fSymbols; +#ifdef SK_ENABLE_SPIRV_VALIDATION + StringStream buffer; + SPIRVCodeGenerator cg(fContext.get(), &program, &buffer); + bool result = cg.generateCode(); + + if (result && program.fConfig->fSettings.fValidateSPIRV) { + std::string_view binary = buffer.str(); + result = validate_spirv(this->errorReporter(), binary); + out.write(binary.data(), binary.size()); + } +#else + SPIRVCodeGenerator cg(fContext.get(), &program, &out); + bool result = cg.generateCode(); +#endif + dsl::End(); + return result; +} + +bool Compiler::toSPIRV(Program& program, std::string* out) { + StringStream buffer; + bool result = this->toSPIRV(program, buffer); + if (result) { + *out = buffer.str(); + } + return result; +} + +bool Compiler::toGLSL(Program& program, OutputStream& out) { + TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toGLSL"); + AutoSource as(this, *program.fSource); + AutoShaderCaps autoCaps(fContext, fCaps); + GLSLCodeGenerator cg(fContext.get(), &program, &out); + bool result = cg.generateCode(); + return result; +} + +bool Compiler::toGLSL(Program& program, std::string* out) { + StringStream buffer; + bool result = this->toGLSL(program, buffer); + if (result) { + *out = buffer.str(); + } + return result; +} + +bool Compiler::toHLSL(Program& program, OutputStream& out) { + TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toHLSL"); + std::string hlsl; + if (!this->toHLSL(program, &hlsl)) { + return false; + } + out.writeString(hlsl); + return true; +} + +bool Compiler::toHLSL(Program& program, std::string* out) { + std::string spirv; + if (!this->toSPIRV(program, &spirv)) { + return false; + } + + if (!SPIRVtoHLSL(spirv, out)) { + fErrorText += "HLSL cross-compilation not enabled"; + return false; + } + + return true; +} + +bool Compiler::toMetal(Program& program, OutputStream& out) { + TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toMetal"); + AutoSource as(this, *program.fSource); + AutoShaderCaps autoCaps(fContext, fCaps); + MetalCodeGenerator cg(fContext.get(), &program, &out); + bool result = cg.generateCode(); + return result; +} + +bool Compiler::toMetal(Program& program, std::string* out) { + StringStream buffer; + bool result = this->toMetal(program, buffer); + if (result) { + *out = buffer.str(); + } + return result; +} + +#if defined(SK_ENABLE_WGSL_VALIDATION) +static bool validate_wgsl(ErrorReporter& reporter, const std::string& wgsl) { + tint::Source::File srcFile("", wgsl); + tint::Program program(tint::reader::wgsl::Parse(&srcFile)); + if (program.Diagnostics().count() > 0) { + tint::diag::Formatter diagFormatter; + std::string diagOutput = diagFormatter.format(program.Diagnostics()); +#if defined(SKSL_STANDALONE) + reporter.error(Position(), diagOutput); +#else + SkDEBUGFAILF("%s", diagOutput.c_str()); +#endif + return false; + } + return true; +} +#endif // defined(SK_ENABLE_WGSL_VALIDATION) + +bool Compiler::toWGSL(Program& program, OutputStream& out) { + TRACE_EVENT0("skia.shaders", "SkSL::Compiler::toWGSL"); + AutoSource as(this, *program.fSource); +#ifdef SK_ENABLE_WGSL_VALIDATION + StringStream wgsl; + WGSLCodeGenerator cg(fContext.get(), &program, &wgsl); + bool result = cg.generateCode(); + if (result) { + std::string wgslString = wgsl.str(); + result = validate_wgsl(this->errorReporter(), wgslString); + out.writeString(wgslString); + } +#else + WGSLCodeGenerator cg(fContext.get(), &program, &out); + bool result = cg.generateCode(); +#endif + return result; +} + +#endif // defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE) + +void Compiler::handleError(std::string_view msg, Position pos) { + fErrorText += "error: "; + bool printLocation = false; + std::string_view src = this->errorReporter().source(); + int line = -1; + if (pos.valid()) { + line = pos.line(src); + printLocation = pos.startOffset() < (int)src.length(); + fErrorText += std::to_string(line) + ": "; + } + fErrorText += std::string(msg) + "\n"; + if (printLocation) { + const int kMaxSurroundingChars = 100; + + // Find the beginning of the line. + int lineStart = pos.startOffset(); + while (lineStart > 0) { + if (src[lineStart - 1] == '\n') { + break; + } + --lineStart; + } + + // We don't want to show more than 100 characters surrounding the error, so push the line + // start forward and add a leading ellipsis if there would be more than this. + std::string lineText; + std::string caretText; + if ((pos.startOffset() - lineStart) > kMaxSurroundingChars) { + lineStart = pos.startOffset() - kMaxSurroundingChars; + lineText = "..."; + caretText = " "; + } + + // Echo the line. Again, we don't want to show more than 100 characters after the end of the + // error, so truncate with a trailing ellipsis if needed. + const char* lineSuffix = "...\n"; + int lineStop = pos.endOffset() + kMaxSurroundingChars; + if (lineStop >= (int)src.length()) { + lineStop = src.length() - 1; + lineSuffix = "\n"; // no ellipsis if we reach end-of-file + } + for (int i = lineStart; i < lineStop; ++i) { + char c = src[i]; + if (c == '\n') { + lineSuffix = "\n"; // no ellipsis if we reach end-of-line + break; + } + switch (c) { + case '\t': lineText += " "; break; + case '\0': lineText += " "; break; + default: lineText += src[i]; break; + } + } + fErrorText += lineText + lineSuffix; + + // print the carets underneath it, pointing to the range in question + for (int i = lineStart; i < (int)src.length(); i++) { + if (i >= pos.endOffset()) { + break; + } + switch (src[i]) { + case '\t': + caretText += (i >= pos.startOffset()) ? "^^^^" : " "; + break; + case '\n': + SkASSERT(i >= pos.startOffset()); + // use an ellipsis if the error continues past the end of the line + caretText += (pos.endOffset() > i + 1) ? "..." : "^"; + i = src.length(); + break; + default: + caretText += (i >= pos.startOffset()) ? '^' : ' '; + break; + } + } + fErrorText += caretText + '\n'; + } +} + +std::string Compiler::errorText(bool showCount) { + if (showCount) { + this->writeErrorCount(); + } + std::string result = fErrorText; + this->resetErrors(); + return result; +} + +void Compiler::writeErrorCount() { + int count = this->errorCount(); + if (count) { + fErrorText += std::to_string(count) + " error"; + if (count > 1) { + fErrorText += "s"; + } + fErrorText += "\n"; + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLCompiler.h b/gfx/skia/skia/src/sksl/SkSLCompiler.h new file mode 100644 index 0000000000..382c69609b --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLCompiler.h @@ -0,0 +1,242 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_COMPILER +#define SKSL_COMPILER + +#include "include/core/SkSize.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLProgramElement.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLContext.h" // IWYU pragma: keep + +#include <array> +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <type_traits> +#include <vector> + +#define SK_FRAGCOLOR_BUILTIN 10001 +#define SK_LASTFRAGCOLOR_BUILTIN 10008 +#define SK_MAIN_COORDS_BUILTIN 10009 +#define SK_INPUT_COLOR_BUILTIN 10010 +#define SK_DEST_COLOR_BUILTIN 10011 +#define SK_SECONDARYFRAGCOLOR_BUILTIN 10012 +#define SK_FRAGCOORD_BUILTIN 15 +#define SK_CLOCKWISE_BUILTIN 17 + +#define SK_VERTEXID_BUILTIN 42 +#define SK_INSTANCEID_BUILTIN 43 +#define SK_POSITION_BUILTIN 0 +#define SK_POINTSIZE_BUILTIN 1 + +#define SK_NUMWORKGROUPS_BUILTIN 24 +#define SK_WORKGROUPID_BUILTIN 26 +#define SK_LOCALINVOCATIONID_BUILTIN 27 +#define SK_GLOBALINVOCATIONID_BUILTIN 28 +#define SK_LOCALINVOCATIONINDEX_BUILTIN 29 + +namespace SkSL { + +namespace dsl { + class DSLCore; +} + +class Expression; +class Inliner; +class ModifiersPool; +class OutputStream; +class ProgramUsage; +class SymbolTable; +enum class ProgramKind : int8_t; +struct Program; +struct ProgramSettings; +struct ShaderCaps; + +struct Module { + const Module* fParent = nullptr; + std::shared_ptr<SymbolTable> fSymbols; + std::vector<std::unique_ptr<ProgramElement>> fElements; +}; + +/** + * Main compiler entry point. The compiler parses the SkSL text directly into a tree of IRNodes, + * while performing basic optimizations such as constant-folding and dead-code elimination. Then the + * Program is passed into a CodeGenerator to produce compiled output. + * + * See the README for information about SkSL. + */ +class SK_API Compiler { +public: + inline static constexpr const char FRAGCOLOR_NAME[] = "sk_FragColor"; + inline static constexpr const char RTADJUST_NAME[] = "sk_RTAdjust"; + inline static constexpr const char POSITION_NAME[] = "sk_Position"; + inline static constexpr const char POISON_TAG[] = "<POISON>"; + + /** + * Gets a float4 that adjusts the position from Skia device coords to normalized device coords, + * used to populate sk_RTAdjust. Assuming the transformed position, pos, is a homogeneous + * float4, the vec, v, is applied as such: + * float4((pos.xy * v.xz) + sk_Position.ww * v.yw, 0, pos.w); + */ + static std::array<float, 4> GetRTAdjustVector(SkISize rtDims, bool flipY) { + std::array<float, 4> result; + result[0] = 2.f/rtDims.width(); + result[2] = 2.f/rtDims.height(); + result[1] = -1.f; + result[3] = -1.f; + if (flipY) { + result[2] = -result[2]; + result[3] = -result[3]; + } + return result; + } + + /** + * Uniform values used by the compiler to implement origin-neutral dFdy, sk_Clockwise, and + * sk_FragCoord. + */ + static std::array<float, 2> GetRTFlipVector(int rtHeight, bool flipY) { + std::array<float, 2> result; + result[0] = flipY ? rtHeight : 0.f; + result[1] = flipY ? -1.f : 1.f; + return result; + } + + Compiler(const ShaderCaps* caps); + + ~Compiler(); + + Compiler(const Compiler&) = delete; + Compiler& operator=(const Compiler&) = delete; + + /** + * Allows optimization settings to be unilaterally overridden. This is meant to allow tools like + * Viewer or Nanobench to override the compiler's ProgramSettings and ShaderCaps for debugging. + */ + enum class OverrideFlag { + kDefault, + kOff, + kOn, + }; + static void EnableOptimizer(OverrideFlag flag) { sOptimizer = flag; } + static void EnableInliner(OverrideFlag flag) { sInliner = flag; } + + std::unique_ptr<Program> convertProgram(ProgramKind kind, + std::string text, + ProgramSettings settings); + + std::unique_ptr<Expression> convertIdentifier(Position pos, std::string_view name); + + bool toSPIRV(Program& program, OutputStream& out); + + bool toSPIRV(Program& program, std::string* out); + + bool toGLSL(Program& program, OutputStream& out); + + bool toGLSL(Program& program, std::string* out); + + bool toHLSL(Program& program, OutputStream& out); + + bool toHLSL(Program& program, std::string* out); + + bool toMetal(Program& program, OutputStream& out); + + bool toMetal(Program& program, std::string* out); + + bool toWGSL(Program& program, OutputStream& out); + + void handleError(std::string_view msg, Position pos); + + std::string errorText(bool showCount = true); + + ErrorReporter& errorReporter() { return *fContext->fErrors; } + + int errorCount() const { return fContext->fErrors->errorCount(); } + + void writeErrorCount(); + + void resetErrors() { + fErrorText.clear(); + this->errorReporter().resetErrorCount(); + } + + Context& context() const { + return *fContext; + } + + std::shared_ptr<SymbolTable>& symbolTable() { + return fSymbolTable; + } + + std::unique_ptr<Module> compileModule(ProgramKind kind, + const char* moduleName, + std::string moduleSource, + const Module* parent, + ModifiersPool& modifiersPool, + bool shouldInline); + + /** Optimize a module at minification time, before writing it out. */ + bool optimizeModuleBeforeMinifying(ProgramKind kind, Module& module); + + const Module* moduleForProgramKind(ProgramKind kind); + +private: + class CompilerErrorReporter : public ErrorReporter { + public: + CompilerErrorReporter(Compiler* compiler) + : fCompiler(*compiler) {} + + void handleError(std::string_view msg, Position pos) override { + fCompiler.handleError(msg, pos); + } + + private: + Compiler& fCompiler; + }; + + /** Updates ProgramSettings to eliminate contradictions and to honor the ProgramKind. */ + static void FinalizeSettings(ProgramSettings* settings, ProgramKind kind); + + /** Optimize every function in the program. */ + bool optimize(Program& program); + + /** Performs final checks to confirm that a fully-assembled/optimized is valid. */ + bool finalize(Program& program); + + /** Optimize a module at Skia runtime, after loading it. */ + bool optimizeModuleAfterLoading(ProgramKind kind, Module& module); + + /** Flattens out function calls when it is safe to do so. */ + bool runInliner(Inliner* inliner, + const std::vector<std::unique_ptr<ProgramElement>>& elements, + std::shared_ptr<SymbolTable> symbols, + ProgramUsage* usage); + + CompilerErrorReporter fErrorReporter; + std::shared_ptr<Context> fContext; + const ShaderCaps* fCaps; + + // This is the current symbol table of the code we are processing, and therefore changes during + // compilation + std::shared_ptr<SymbolTable> fSymbolTable; + + std::string fErrorText; + + static OverrideFlag sOptimizer; + static OverrideFlag sInliner; + + friend class ThreadContext; + friend class dsl::DSLCore; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLConstantFolder.cpp b/gfx/skia/skia/src/sksl/SkSLConstantFolder.cpp new file mode 100644 index 0000000000..76cf7f820a --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLConstantFolder.cpp @@ -0,0 +1,884 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLConstantFolder.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/base/SkFloatingPoint.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <cstdint> +#include <float.h> +#include <limits> +#include <optional> +#include <string> +#include <utility> + +namespace SkSL { + +static bool is_vec_or_mat(const Type& type) { + switch (type.typeKind()) { + case Type::TypeKind::kMatrix: + case Type::TypeKind::kVector: + return true; + + default: + return false; + } +} + +static std::unique_ptr<Expression> eliminate_no_op_boolean(Position pos, + const Expression& left, + Operator op, + const Expression& right) { + bool rightVal = right.as<Literal>().boolValue(); + + // Detect no-op Boolean expressions and optimize them away. + if ((op.kind() == Operator::Kind::LOGICALAND && rightVal) || // (expr && true) -> (expr) + (op.kind() == Operator::Kind::LOGICALOR && !rightVal) || // (expr || false) -> (expr) + (op.kind() == Operator::Kind::LOGICALXOR && !rightVal) || // (expr ^^ false) -> (expr) + (op.kind() == Operator::Kind::EQEQ && rightVal) || // (expr == true) -> (expr) + (op.kind() == Operator::Kind::NEQ && !rightVal)) { // (expr != false) -> (expr) + + return left.clone(pos); + } + + return nullptr; +} + +static std::unique_ptr<Expression> short_circuit_boolean(Position pos, + const Expression& left, + Operator op, + const Expression& right) { + bool leftVal = left.as<Literal>().boolValue(); + + // When the literal is on the left, we can sometimes eliminate the other expression entirely. + if ((op.kind() == Operator::Kind::LOGICALAND && !leftVal) || // (false && expr) -> (false) + (op.kind() == Operator::Kind::LOGICALOR && leftVal)) { // (true || expr) -> (true) + + return left.clone(pos); + } + + // We can't eliminate the right-side expression via short-circuit, but we might still be able to + // simplify away a no-op expression. + return eliminate_no_op_boolean(pos, right, op, left); +} + +static std::unique_ptr<Expression> simplify_constant_equality(const Context& context, + Position pos, + const Expression& left, + Operator op, + const Expression& right) { + if (op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ) { + bool equality = (op.kind() == Operator::Kind::EQEQ); + + switch (left.compareConstant(right)) { + case Expression::ComparisonResult::kNotEqual: + equality = !equality; + [[fallthrough]]; + + case Expression::ComparisonResult::kEqual: + return Literal::MakeBool(context, pos, equality); + + case Expression::ComparisonResult::kUnknown: + break; + } + } + return nullptr; +} + +static std::unique_ptr<Expression> simplify_matrix_multiplication(const Context& context, + Position pos, + const Expression& left, + const Expression& right, + int leftColumns, + int leftRows, + int rightColumns, + int rightRows) { + const Type& componentType = left.type().componentType(); + SkASSERT(componentType.matches(right.type().componentType())); + + // Fetch the left matrix. + double leftVals[4][4]; + for (int c = 0; c < leftColumns; ++c) { + for (int r = 0; r < leftRows; ++r) { + leftVals[c][r] = *left.getConstantValue((c * leftRows) + r); + } + } + // Fetch the right matrix. + double rightVals[4][4]; + for (int c = 0; c < rightColumns; ++c) { + for (int r = 0; r < rightRows; ++r) { + rightVals[c][r] = *right.getConstantValue((c * rightRows) + r); + } + } + + SkASSERT(leftColumns == rightRows); + int outColumns = rightColumns, + outRows = leftRows; + + ExpressionArray args; + args.reserve_back(outColumns * outRows); + for (int c = 0; c < outColumns; ++c) { + for (int r = 0; r < outRows; ++r) { + // Compute a dot product for this position. + double val = 0; + for (int dotIdx = 0; dotIdx < leftColumns; ++dotIdx) { + val += leftVals[dotIdx][r] * rightVals[c][dotIdx]; + } + args.push_back(Literal::Make(pos, val, &componentType)); + } + } + + if (outColumns == 1) { + // Matrix-times-vector conceptually makes a 1-column N-row matrix, but we return vecN. + std::swap(outColumns, outRows); + } + + const Type& resultType = componentType.toCompound(context, outColumns, outRows); + return ConstructorCompound::Make(context, pos, resultType, std::move(args)); +} + +static std::unique_ptr<Expression> simplify_matrix_times_matrix(const Context& context, + Position pos, + const Expression& left, + const Expression& right) { + const Type& leftType = left.type(); + const Type& rightType = right.type(); + + SkASSERT(leftType.isMatrix()); + SkASSERT(rightType.isMatrix()); + + return simplify_matrix_multiplication(context, pos, left, right, + leftType.columns(), leftType.rows(), + rightType.columns(), rightType.rows()); +} + +static std::unique_ptr<Expression> simplify_vector_times_matrix(const Context& context, + Position pos, + const Expression& left, + const Expression& right) { + const Type& leftType = left.type(); + const Type& rightType = right.type(); + + SkASSERT(leftType.isVector()); + SkASSERT(rightType.isMatrix()); + + return simplify_matrix_multiplication(context, pos, left, right, + /*leftColumns=*/leftType.columns(), /*leftRows=*/1, + rightType.columns(), rightType.rows()); +} + +static std::unique_ptr<Expression> simplify_matrix_times_vector(const Context& context, + Position pos, + const Expression& left, + const Expression& right) { + const Type& leftType = left.type(); + const Type& rightType = right.type(); + + SkASSERT(leftType.isMatrix()); + SkASSERT(rightType.isVector()); + + return simplify_matrix_multiplication(context, pos, left, right, + leftType.columns(), leftType.rows(), + /*rightColumns=*/1, /*rightRows=*/rightType.columns()); +} + +static std::unique_ptr<Expression> simplify_componentwise(const Context& context, + Position pos, + const Expression& left, + Operator op, + const Expression& right) { + SkASSERT(is_vec_or_mat(left.type())); + SkASSERT(left.type().matches(right.type())); + const Type& type = left.type(); + + // Handle equality operations: == != + if (std::unique_ptr<Expression> result = simplify_constant_equality(context, pos, left, op, + right)) { + return result; + } + + // Handle floating-point arithmetic: + - * / + using FoldFn = double (*)(double, double); + FoldFn foldFn; + switch (op.kind()) { + case Operator::Kind::PLUS: foldFn = +[](double a, double b) { return a + b; }; break; + case Operator::Kind::MINUS: foldFn = +[](double a, double b) { return a - b; }; break; + case Operator::Kind::STAR: foldFn = +[](double a, double b) { return a * b; }; break; + case Operator::Kind::SLASH: foldFn = +[](double a, double b) { return a / b; }; break; + default: + return nullptr; + } + + const Type& componentType = type.componentType(); + SkASSERT(componentType.isNumber()); + + double minimumValue = componentType.minimumValue(); + double maximumValue = componentType.maximumValue(); + + ExpressionArray args; + int numSlots = type.slotCount(); + args.reserve_back(numSlots); + for (int i = 0; i < numSlots; i++) { + double value = foldFn(*left.getConstantValue(i), *right.getConstantValue(i)); + if (value < minimumValue || value > maximumValue) { + return nullptr; + } + + args.push_back(Literal::Make(pos, value, &componentType)); + } + return ConstructorCompound::Make(context, pos, type, std::move(args)); +} + +static std::unique_ptr<Expression> splat_scalar(const Context& context, + const Expression& scalar, + const Type& type) { + if (type.isVector()) { + return ConstructorSplat::Make(context, scalar.fPosition, type, scalar.clone()); + } + if (type.isMatrix()) { + int numSlots = type.slotCount(); + ExpressionArray splatMatrix; + splatMatrix.reserve_back(numSlots); + for (int index = 0; index < numSlots; ++index) { + splatMatrix.push_back(scalar.clone()); + } + return ConstructorCompound::Make(context, scalar.fPosition, type, std::move(splatMatrix)); + } + SkDEBUGFAILF("unsupported type %s", type.description().c_str()); + return nullptr; +} + +static std::unique_ptr<Expression> cast_expression(const Context& context, + Position pos, + const Expression& expr, + const Type& type) { + SkASSERT(type.componentType().matches(expr.type().componentType())); + if (expr.type().isScalar()) { + if (type.isMatrix()) { + return ConstructorDiagonalMatrix::Make(context, pos, type, expr.clone()); + } + if (type.isVector()) { + return ConstructorSplat::Make(context, pos, type, expr.clone()); + } + } + if (type.matches(expr.type())) { + return expr.clone(pos); + } + // We can't cast matrices into vectors or vice-versa. + return nullptr; +} + +static std::unique_ptr<Expression> zero_expression(const Context& context, + Position pos, + const Type& type) { + std::unique_ptr<Expression> zero = Literal::Make(pos, 0.0, &type.componentType()); + if (type.isScalar()) { + return zero; + } + if (type.isVector()) { + return ConstructorSplat::Make(context, pos, type, std::move(zero)); + } + if (type.isMatrix()) { + return ConstructorDiagonalMatrix::Make(context, pos, type, std::move(zero)); + } + SkDEBUGFAILF("unsupported type %s", type.description().c_str()); + return nullptr; +} + +static std::unique_ptr<Expression> negate_expression(const Context& context, + Position pos, + const Expression& expr, + const Type& type) { + std::unique_ptr<Expression> ctor = cast_expression(context, pos, expr, type); + return ctor ? PrefixExpression::Make(context, pos, Operator::Kind::MINUS, std::move(ctor)) + : nullptr; +} + +bool ConstantFolder::GetConstantInt(const Expression& value, SKSL_INT* out) { + const Expression* expr = GetConstantValueForVariable(value); + if (!expr->isIntLiteral()) { + return false; + } + *out = expr->as<Literal>().intValue(); + return true; +} + +bool ConstantFolder::GetConstantValue(const Expression& value, double* out) { + const Expression* expr = GetConstantValueForVariable(value); + if (!expr->is<Literal>()) { + return false; + } + *out = expr->as<Literal>().value(); + return true; +} + +static bool contains_constant_zero(const Expression& expr) { + int numSlots = expr.type().slotCount(); + for (int index = 0; index < numSlots; ++index) { + std::optional<double> slotVal = expr.getConstantValue(index); + if (slotVal.has_value() && *slotVal == 0.0) { + return true; + } + } + return false; +} + +// Returns true if the expression contains `value` in every slot. +static bool is_constant_splat(const Expression& expr, double value) { + int numSlots = expr.type().slotCount(); + for (int index = 0; index < numSlots; ++index) { + std::optional<double> slotVal = expr.getConstantValue(index); + if (!slotVal.has_value() || *slotVal != value) { + return false; + } + } + return true; +} + +// Returns true if the expression is a square diagonal matrix containing `value`. +static bool is_constant_diagonal(const Expression& expr, double value) { + SkASSERT(expr.type().isMatrix()); + int columns = expr.type().columns(); + int rows = expr.type().rows(); + if (columns != rows) { + return false; + } + int slotIdx = 0; + for (int c = 0; c < columns; ++c) { + for (int r = 0; r < rows; ++r) { + double expectation = (c == r) ? value : 0; + std::optional<double> slotVal = expr.getConstantValue(slotIdx++); + if (!slotVal.has_value() || *slotVal != expectation) { + return false; + } + } + } + return true; +} + +// Returns true if the expression is a scalar, vector, or diagonal matrix containing `value`. +static bool is_constant_value(const Expression& expr, double value) { + return expr.type().isMatrix() ? is_constant_diagonal(expr, value) + : is_constant_splat(expr, value); +} + +// The expression represents the right-hand side of a division op. If the division can be +// strength-reduced into multiplication by a reciprocal, returns that reciprocal as an expression. +// Note that this only supports literal values with safe-to-use reciprocals, and returns null if +// Expression contains anything else. +static std::unique_ptr<Expression> make_reciprocal_expression(const Context& context, + const Expression& right) { + if (right.type().isMatrix() || !right.type().componentType().isFloat()) { + return nullptr; + } + // Verify that each slot contains a finite, non-zero literal, take its reciprocal. + int nslots = right.type().slotCount(); + SkSTArray<4, double> values; + for (int index = 0; index < nslots; ++index) { + std::optional<double> value = right.getConstantValue(index); + if (!value) { + return nullptr; + } + *value = sk_ieee_double_divide(1.0, *value); + if (*value >= -FLT_MAX && *value <= FLT_MAX && *value != 0.0) { + // The reciprocal can be represented safely as a finite 32-bit float. + values.push_back(*value); + } else { + // The value is outside the 32-bit float range, or is NaN; do not optimize. + return nullptr; + } + } + // Convert our reciprocal values to Literals. + ExpressionArray exprs; + exprs.reserve_back(nslots); + for (double value : values) { + exprs.push_back(Literal::Make(right.fPosition, value, &right.type().componentType())); + } + // Turn the expression array into a compound constructor. (If this is a single-slot expression, + // this will return the literal as-is.) + return ConstructorCompound::Make(context, right.fPosition, right.type(), std::move(exprs)); +} + +static bool error_on_divide_by_zero(const Context& context, Position pos, Operator op, + const Expression& right) { + switch (op.kind()) { + case Operator::Kind::SLASH: + case Operator::Kind::SLASHEQ: + case Operator::Kind::PERCENT: + case Operator::Kind::PERCENTEQ: + if (contains_constant_zero(right)) { + context.fErrors->error(pos, "division by zero"); + return true; + } + return false; + default: + return false; + } +} + +const Expression* ConstantFolder::GetConstantValueOrNullForVariable(const Expression& inExpr) { + for (const Expression* expr = &inExpr;;) { + if (!expr->is<VariableReference>()) { + break; + } + const VariableReference& varRef = expr->as<VariableReference>(); + if (varRef.refKind() != VariableRefKind::kRead) { + break; + } + const Variable& var = *varRef.variable(); + if (!(var.modifiers().fFlags & Modifiers::kConst_Flag)) { + break; + } + expr = var.initialValue(); + if (!expr) { + // Function parameters can be const but won't have an initial value. + break; + } + if (Analysis::IsCompileTimeConstant(*expr)) { + return expr; + } + } + // We didn't find a compile-time constant at the end. + return nullptr; +} + +const Expression* ConstantFolder::GetConstantValueForVariable(const Expression& inExpr) { + const Expression* expr = GetConstantValueOrNullForVariable(inExpr); + return expr ? expr : &inExpr; +} + +std::unique_ptr<Expression> ConstantFolder::MakeConstantValueForVariable( + Position pos, std::unique_ptr<Expression> inExpr) { + const Expression* expr = GetConstantValueOrNullForVariable(*inExpr); + return expr ? expr->clone(pos) : std::move(inExpr); +} + +static bool is_scalar_op_matrix(const Expression& left, const Expression& right) { + return left.type().isScalar() && right.type().isMatrix(); +} + +static bool is_matrix_op_scalar(const Expression& left, const Expression& right) { + return is_scalar_op_matrix(right, left); +} + +static std::unique_ptr<Expression> simplify_arithmetic(const Context& context, + Position pos, + const Expression& left, + Operator op, + const Expression& right, + const Type& resultType) { + switch (op.kind()) { + case Operator::Kind::PLUS: + if (!is_scalar_op_matrix(left, right) && is_constant_splat(right, 0.0)) { // x + 0 + if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left, + resultType)) { + return expr; + } + } + if (!is_matrix_op_scalar(left, right) && is_constant_splat(left, 0.0)) { // 0 + x + if (std::unique_ptr<Expression> expr = cast_expression(context, pos, right, + resultType)) { + return expr; + } + } + break; + + case Operator::Kind::STAR: + if (is_constant_value(right, 1.0)) { // x * 1 + if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left, + resultType)) { + return expr; + } + } + if (is_constant_value(left, 1.0)) { // 1 * x + if (std::unique_ptr<Expression> expr = cast_expression(context, pos, right, + resultType)) { + return expr; + } + } + if (is_constant_value(right, 0.0) && !Analysis::HasSideEffects(left)) { // x * 0 + return zero_expression(context, pos, resultType); + } + if (is_constant_value(left, 0.0) && !Analysis::HasSideEffects(right)) { // 0 * x + return zero_expression(context, pos, resultType); + } + if (is_constant_value(right, -1.0)) { // x * -1 (to `-x`) + if (std::unique_ptr<Expression> expr = negate_expression(context, pos, left, + resultType)) { + return expr; + } + } + if (is_constant_value(left, -1.0)) { // -1 * x (to `-x`) + if (std::unique_ptr<Expression> expr = negate_expression(context, pos, right, + resultType)) { + return expr; + } + } + break; + + case Operator::Kind::MINUS: + if (!is_scalar_op_matrix(left, right) && is_constant_splat(right, 0.0)) { // x - 0 + if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left, + resultType)) { + return expr; + } + } + if (!is_matrix_op_scalar(left, right) && is_constant_splat(left, 0.0)) { // 0 - x + if (std::unique_ptr<Expression> expr = negate_expression(context, pos, right, + resultType)) { + return expr; + } + } + break; + + case Operator::Kind::SLASH: + if (!is_scalar_op_matrix(left, right) && is_constant_splat(right, 1.0)) { // x / 1 + if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left, + resultType)) { + return expr; + } + } + if (!left.type().isMatrix()) { // convert `x / 2` into `x * 0.5` + if (std::unique_ptr<Expression> expr = make_reciprocal_expression(context, right)) { + return BinaryExpression::Make(context, pos, left.clone(), Operator::Kind::STAR, + std::move(expr)); + } + } + break; + + case Operator::Kind::PLUSEQ: + case Operator::Kind::MINUSEQ: + if (is_constant_splat(right, 0.0)) { // x += 0, x -= 0 + if (std::unique_ptr<Expression> var = cast_expression(context, pos, left, + resultType)) { + Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead); + return var; + } + } + break; + + case Operator::Kind::STAREQ: + if (is_constant_value(right, 1.0)) { // x *= 1 + if (std::unique_ptr<Expression> var = cast_expression(context, pos, left, + resultType)) { + Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead); + return var; + } + } + break; + + case Operator::Kind::SLASHEQ: + if (is_constant_splat(right, 1.0)) { // x /= 1 + if (std::unique_ptr<Expression> var = cast_expression(context, pos, left, + resultType)) { + Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead); + return var; + } + } + if (std::unique_ptr<Expression> expr = make_reciprocal_expression(context, right)) { + return BinaryExpression::Make(context, pos, left.clone(), Operator::Kind::STAREQ, + std::move(expr)); + } + break; + + default: + break; + } + + return nullptr; +} + +// The expression must be scalar, and represents the right-hand side of a division op. It can +// contain anything, not just literal values. This returns the binary expression `1.0 / expr`. The +// expression might be further simplified by the constant folding, if possible. +static std::unique_ptr<Expression> one_over_scalar(const Context& context, + const Expression& right) { + SkASSERT(right.type().isScalar()); + Position pos = right.fPosition; + return BinaryExpression::Make(context, pos, + Literal::Make(pos, 1.0, &right.type()), + Operator::Kind::SLASH, + right.clone()); +} + +static std::unique_ptr<Expression> simplify_matrix_division(const Context& context, + Position pos, + const Expression& left, + Operator op, + const Expression& right, + const Type& resultType) { + // Convert matrix-over-scalar `x /= y` into `x *= (1.0 / y)`. This generates better + // code in SPIR-V and Metal, and should be roughly equivalent elsewhere. + switch (op.kind()) { + case OperatorKind::SLASH: + case OperatorKind::SLASHEQ: + if (left.type().isMatrix() && right.type().isScalar()) { + Operator multiplyOp = op.isAssignment() ? OperatorKind::STAREQ + : OperatorKind::STAR; + return BinaryExpression::Make(context, pos, + left.clone(), + multiplyOp, + one_over_scalar(context, right)); + } + break; + + default: + break; + } + + return nullptr; +} + +static std::unique_ptr<Expression> fold_expression(Position pos, + double result, + const Type* resultType) { + if (resultType->isNumber()) { + if (result >= resultType->minimumValue() && result <= resultType->maximumValue()) { + // This result will fit inside its type. + } else { + // The value is outside the range or is NaN (all if-checks fail); do not optimize. + return nullptr; + } + } + + return Literal::Make(pos, result, resultType); +} + +std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context, + Position pos, + const Expression& leftExpr, + Operator op, + const Expression& rightExpr, + const Type& resultType) { + // Replace constant variables with their literal values. + const Expression* left = GetConstantValueForVariable(leftExpr); + const Expression* right = GetConstantValueForVariable(rightExpr); + + // If this is the assignment operator, and both sides are the same trivial expression, this is + // self-assignment (i.e., `var = var`) and can be reduced to just a variable reference (`var`). + // This can happen when other parts of the assignment are optimized away. + if (op.kind() == Operator::Kind::EQ && Analysis::IsSameExpressionTree(*left, *right)) { + return right->clone(pos); + } + + // Simplify the expression when both sides are constant Boolean literals. + if (left->isBoolLiteral() && right->isBoolLiteral()) { + bool leftVal = left->as<Literal>().boolValue(); + bool rightVal = right->as<Literal>().boolValue(); + bool result; + switch (op.kind()) { + case Operator::Kind::LOGICALAND: result = leftVal && rightVal; break; + case Operator::Kind::LOGICALOR: result = leftVal || rightVal; break; + case Operator::Kind::LOGICALXOR: result = leftVal ^ rightVal; break; + case Operator::Kind::EQEQ: result = leftVal == rightVal; break; + case Operator::Kind::NEQ: result = leftVal != rightVal; break; + default: return nullptr; + } + return Literal::MakeBool(context, pos, result); + } + + // If the left side is a Boolean literal, apply short-circuit optimizations. + if (left->isBoolLiteral()) { + return short_circuit_boolean(pos, *left, op, *right); + } + + // If the right side is a Boolean literal... + if (right->isBoolLiteral()) { + // ... and the left side has no side effects... + if (!Analysis::HasSideEffects(*left)) { + // We can reverse the expressions and short-circuit optimizations are still valid. + return short_circuit_boolean(pos, *right, op, *left); + } + + // We can't use short-circuiting, but we can still optimize away no-op Boolean expressions. + return eliminate_no_op_boolean(pos, *left, op, *right); + } + + if (op.kind() == Operator::Kind::EQEQ && Analysis::IsSameExpressionTree(*left, *right)) { + // With == comparison, if both sides are the same trivial expression, this is self- + // comparison and is always true. (We are not concerned with NaN.) + return Literal::MakeBool(context, pos, /*value=*/true); + } + + if (op.kind() == Operator::Kind::NEQ && Analysis::IsSameExpressionTree(*left, *right)) { + // With != comparison, if both sides are the same trivial expression, this is self- + // comparison and is always false. (We are not concerned with NaN.) + return Literal::MakeBool(context, pos, /*value=*/false); + } + + if (error_on_divide_by_zero(context, pos, op, *right)) { + return nullptr; + } + + // Perform full constant folding when both sides are compile-time constants. + const Type& leftType = left->type(); + const Type& rightType = right->type(); + bool leftSideIsConstant = Analysis::IsCompileTimeConstant(*left); + bool rightSideIsConstant = Analysis::IsCompileTimeConstant(*right); + + if (leftSideIsConstant && rightSideIsConstant) { + // Handle pairs of integer literals. + if (left->isIntLiteral() && right->isIntLiteral()) { + using SKSL_UINT = uint64_t; + SKSL_INT leftVal = left->as<Literal>().intValue(); + SKSL_INT rightVal = right->as<Literal>().intValue(); + + // Note that fold_expression returns null if the result would overflow its type. + #define RESULT(Op) fold_expression(pos, (SKSL_INT)(leftVal) Op \ + (SKSL_INT)(rightVal), &resultType) + #define URESULT(Op) fold_expression(pos, (SKSL_INT)((SKSL_UINT)(leftVal) Op \ + (SKSL_UINT)(rightVal)), &resultType) + switch (op.kind()) { + case Operator::Kind::PLUS: return URESULT(+); + case Operator::Kind::MINUS: return URESULT(-); + case Operator::Kind::STAR: return URESULT(*); + case Operator::Kind::SLASH: + if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) { + context.fErrors->error(pos, "arithmetic overflow"); + return nullptr; + } + return RESULT(/); + case Operator::Kind::PERCENT: + if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) { + context.fErrors->error(pos, "arithmetic overflow"); + return nullptr; + } + return RESULT(%); + case Operator::Kind::BITWISEAND: return RESULT(&); + case Operator::Kind::BITWISEOR: return RESULT(|); + case Operator::Kind::BITWISEXOR: return RESULT(^); + case Operator::Kind::EQEQ: return RESULT(==); + case Operator::Kind::NEQ: return RESULT(!=); + case Operator::Kind::GT: return RESULT(>); + case Operator::Kind::GTEQ: return RESULT(>=); + case Operator::Kind::LT: return RESULT(<); + case Operator::Kind::LTEQ: return RESULT(<=); + case Operator::Kind::SHL: + if (rightVal >= 0 && rightVal <= 31) { + // Left-shifting a negative (or really, any signed) value is undefined + // behavior in C++, but not in GLSL. Do the shift on unsigned values to avoid + // triggering an UBSAN error. + return URESULT(<<); + } + context.fErrors->error(pos, "shift value out of range"); + return nullptr; + case Operator::Kind::SHR: + if (rightVal >= 0 && rightVal <= 31) { + return RESULT(>>); + } + context.fErrors->error(pos, "shift value out of range"); + return nullptr; + + default: + return nullptr; + } + #undef RESULT + #undef URESULT + } + + // Handle pairs of floating-point literals. + if (left->isFloatLiteral() && right->isFloatLiteral()) { + SKSL_FLOAT leftVal = left->as<Literal>().floatValue(); + SKSL_FLOAT rightVal = right->as<Literal>().floatValue(); + + #define RESULT(Op) fold_expression(pos, leftVal Op rightVal, &resultType) + switch (op.kind()) { + case Operator::Kind::PLUS: return RESULT(+); + case Operator::Kind::MINUS: return RESULT(-); + case Operator::Kind::STAR: return RESULT(*); + case Operator::Kind::SLASH: return RESULT(/); + case Operator::Kind::EQEQ: return RESULT(==); + case Operator::Kind::NEQ: return RESULT(!=); + case Operator::Kind::GT: return RESULT(>); + case Operator::Kind::GTEQ: return RESULT(>=); + case Operator::Kind::LT: return RESULT(<); + case Operator::Kind::LTEQ: return RESULT(<=); + default: return nullptr; + } + #undef RESULT + } + + // Perform matrix multiplication. + if (op.kind() == Operator::Kind::STAR) { + if (leftType.isMatrix() && rightType.isMatrix()) { + return simplify_matrix_times_matrix(context, pos, *left, *right); + } + if (leftType.isVector() && rightType.isMatrix()) { + return simplify_vector_times_matrix(context, pos, *left, *right); + } + if (leftType.isMatrix() && rightType.isVector()) { + return simplify_matrix_times_vector(context, pos, *left, *right); + } + } + + // Perform constant folding on pairs of vectors/matrices. + if (is_vec_or_mat(leftType) && leftType.matches(rightType)) { + return simplify_componentwise(context, pos, *left, op, *right); + } + + // Perform constant folding on vectors/matrices against scalars, e.g.: half4(2) + 2 + if (rightType.isScalar() && is_vec_or_mat(leftType) && + leftType.componentType().matches(rightType)) { + return simplify_componentwise(context, pos, + *left, op, *splat_scalar(context, *right, left->type())); + } + + // Perform constant folding on scalars against vectors/matrices, e.g.: 2 + half4(2) + if (leftType.isScalar() && is_vec_or_mat(rightType) && + rightType.componentType().matches(leftType)) { + return simplify_componentwise(context, pos, + *splat_scalar(context, *left, right->type()), op, *right); + } + + // Perform constant folding on pairs of matrices, arrays or structs. + if ((leftType.isMatrix() && rightType.isMatrix()) || + (leftType.isArray() && rightType.isArray()) || + (leftType.isStruct() && rightType.isStruct())) { + return simplify_constant_equality(context, pos, *left, op, *right); + } + } + + if (context.fConfig->fSettings.fOptimize) { + // If just one side is constant, we might still be able to simplify arithmetic expressions + // like `x * 1`, `x *= 1`, `x + 0`, `x * 0`, `0 / x`, etc. + if (leftSideIsConstant || rightSideIsConstant) { + if (std::unique_ptr<Expression> expr = simplify_arithmetic(context, pos, *left, op, + *right, resultType)) { + return expr; + } + } + + // We can simplify some forms of matrix division even when neither side is constant. + if (std::unique_ptr<Expression> expr = simplify_matrix_division(context, pos, *left, op, + *right, resultType)) { + return expr; + } + } + + // We aren't able to constant-fold. + return nullptr; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLConstantFolder.h b/gfx/skia/skia/src/sksl/SkSLConstantFolder.h new file mode 100644 index 0000000000..25dd2e7b86 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLConstantFolder.h @@ -0,0 +1,71 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTANT_FOLDER +#define SKSL_CONSTANT_FOLDER + +#include <memory> + +#include "include/private/SkSLDefines.h" +#include "include/sksl/SkSLOperator.h" + +namespace SkSL { + +class Context; +class Expression; +class Position; +class Type; + +/** + * Performs constant folding on IR expressions. This simplifies expressions containing + * compile-time constants, such as replacing `Literal(2) + Literal(2)` with `Literal(4)`. + */ +class ConstantFolder { +public: + /** + * If value is an int literal or const int variable with a known value, returns true and stores + * the value in out. Otherwise returns false. + */ + static bool GetConstantInt(const Expression& value, SKSL_INT* out); + + /** + * If value is a literal or const scalar variable with a known value, returns true and stores + * the value in out. Otherwise returns false. + */ + static bool GetConstantValue(const Expression& value, double* out); + + /** + * If the expression is a const variable with a known compile-time-constant value, returns that + * value. If not, returns the original expression as-is. + */ + static const Expression* GetConstantValueForVariable(const Expression& value); + + /** + * If the expression is a const variable with a known compile-time-constant value, returns that + * value. If not, returns null. + */ + static const Expression* GetConstantValueOrNullForVariable(const Expression& value); + + /** + * If the expression is a const variable with a known compile-time-constant value, returns a + * clone of that value. If not, returns the original expression as-is. + */ + static std::unique_ptr<Expression> MakeConstantValueForVariable(Position pos, + std::unique_ptr<Expression> expr); + + /** Simplifies the binary expression `left OP right`. Returns null if it can't be simplified. */ + static std::unique_ptr<Expression> Simplify(const Context& context, + Position pos, + const Expression& left, + Operator op, + const Expression& right, + const Type& resultType); +}; + +} // namespace SkSL + +#endif // SKSL_CONSTANT_FOLDER diff --git a/gfx/skia/skia/src/sksl/SkSLContext.cpp b/gfx/skia/skia/src/sksl/SkSLContext.cpp new file mode 100644 index 0000000000..d28fc9d727 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLContext.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLContext.h" + +#include "include/core/SkTypes.h" +#ifdef SK_DEBUG +#include "src/sksl/SkSLPool.h" +#endif + +namespace SkSL { + +Context::Context(const BuiltinTypes& types, const ShaderCaps* caps, ErrorReporter& errors) + : fTypes(types) + , fCaps(caps) + , fErrors(&errors) { + SkASSERT(!Pool::IsAttached()); +} + +Context::~Context() { + SkASSERT(!Pool::IsAttached()); +} + +} // namespace SkSL + diff --git a/gfx/skia/skia/src/sksl/SkSLContext.h b/gfx/skia/skia/src/sksl/SkSLContext.h new file mode 100644 index 0000000000..e83f2f36ec --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLContext.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONTEXT +#define SKSL_CONTEXT + +namespace SkSL { + +class BuiltinTypes; +class ErrorReporter; +class ModifiersPool; +struct Module; +struct ProgramConfig; +struct ShaderCaps; + +/** + * Contains compiler-wide objects, which currently means the core types. + */ +class Context { +public: + Context(const BuiltinTypes& types, const ShaderCaps* caps, ErrorReporter& errors); + ~Context(); + + // The Context holds a reference to all of the built-in types. + const BuiltinTypes& fTypes; + + // The Context holds a reference to our shader caps bits. + const ShaderCaps* fCaps; + + // The Context holds a pointer to our pool of modifiers. + ModifiersPool* fModifiersPool = nullptr; + + // The Context holds a pointer to the configuration of the program being compiled. + ProgramConfig* fConfig = nullptr; + + // The Context holds a pointer to our error reporter. + ErrorReporter* fErrors; + + // The Context holds a pointer to our module with built-in declarations. + const Module* fModule = nullptr; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLErrorReporter.cpp b/gfx/skia/skia/src/sksl/SkSLErrorReporter.cpp new file mode 100644 index 0000000000..a11234ff5e --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLErrorReporter.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/SkSLErrorReporter.h" + +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLCompiler.h" + +namespace SkSL { + +void ErrorReporter::error(Position position, std::string_view msg) { + if (skstd::contains(msg, Compiler::POISON_TAG)) { + // Don't report errors on poison values. + return; + } + ++fErrorCount; + this->handleError(msg, position); +} + +void TestingOnly_AbortErrorReporter::handleError(std::string_view msg, Position pos) { + SK_ABORT("%.*s", (int)msg.length(), msg.data()); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h b/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h new file mode 100644 index 0000000000..26f59edefc --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLFileOutputStream.h @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FILEOUTPUTSTREAM +#define SKSL_FILEOUTPUTSTREAM + +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLUtil.h" +#include <stdio.h> + +namespace SkSL { + +class FileOutputStream : public OutputStream { +public: + FileOutputStream(const char* name) { + fFile = fopen(name, "wb"); + } + + ~FileOutputStream() override { + if (fOpen) { + close(); + } + } + + bool isValid() const override { + return nullptr != fFile; + } + + void write8(uint8_t b) override { + SkASSERT(fOpen); + if (isValid()) { + if (EOF == fputc(b, fFile)) { + fFile = nullptr; + } + } + } + + void writeText(const char* s) override { + SkASSERT(fOpen); + if (isValid()) { + if (EOF == fputs(s, fFile)) { + fFile = nullptr; + } + } + } + + void write(const void* s, size_t size) override { + if (isValid()) { + size_t written = fwrite(s, 1, size, fFile); + if (written != size) { + fFile = nullptr; + } + } + } + + bool close() { + fOpen = false; + if (isValid() && fclose(fFile)) { + fFile = nullptr; + return false; + } + return true; + } + +private: + bool fOpen = true; + FILE *fFile; + + using INHERITED = OutputStream; +}; + +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLGLSL.h b/gfx/skia/skia/src/sksl/SkSLGLSL.h new file mode 100644 index 0000000000..55c8bc87e5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLGLSL.h @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSLGLSL_DEFINED +#define SkSLGLSL_DEFINED + +namespace SkSL { + +// Limited set of GLSL versions we build shaders for. Caller should round +// down the GLSL version to one of these enums. +enum class GLSLGeneration { + /** + * Desktop GLSL 1.10 and ES2 shading language (based on desktop GLSL 1.20) + */ + k110, + k100es = k110, + /** + * Desktop GLSL 1.30 + */ + k130, + /** + * Desktop GLSL 1.40 + */ + k140, + /** + * Desktop GLSL 1.50 + */ + k150, + /** + * Desktop GLSL 3.30, and ES GLSL 3.00 + */ + k330, + k300es = k330, + /** + * Desktop GLSL 4.00 + */ + k400, + /** + * Desktop GLSL 4.20 + */ + k420, + /** + * ES GLSL 3.10 only TODO Make GLSLCap objects to make this more granular + */ + k310es, + /** + * ES GLSL 3.20 + */ + k320es, +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLInliner.cpp b/gfx/skia/skia/src/sksl/SkSLInliner.cpp new file mode 100644 index 0000000000..d90227e3bb --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLInliner.cpp @@ -0,0 +1,1062 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLInliner.h" + +#ifndef SK_ENABLE_OPTIMIZE_SIZE + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLChildCall.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorArray.h" +#include "src/sksl/ir/SkSLConstructorArrayCast.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorCompoundCast.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" +#include "src/sksl/ir/SkSLConstructorScalarCast.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLConstructorStruct.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <algorithm> +#include <climits> +#include <cstddef> +#include <memory> +#include <string> +#include <string_view> +#include <utility> + +namespace SkSL { +namespace { + +static constexpr int kInlinedStatementLimit = 2500; + +static std::unique_ptr<Statement>* find_parent_statement( + const std::vector<std::unique_ptr<Statement>*>& stmtStack) { + SkASSERT(!stmtStack.empty()); + + // Walk the statement stack from back to front, ignoring the last element (which is the + // enclosing statement). + auto iter = stmtStack.rbegin(); + ++iter; + + // Anything counts as a parent statement other than a scopeless Block. + for (; iter != stmtStack.rend(); ++iter) { + std::unique_ptr<Statement>* stmt = *iter; + if (!(*stmt)->is<Block>() || (*stmt)->as<Block>().isScope()) { + return stmt; + } + } + + // There wasn't any parent statement to be found. + return nullptr; +} + +std::unique_ptr<Expression> clone_with_ref_kind(const Expression& expr, + VariableReference::RefKind refKind) { + std::unique_ptr<Expression> clone = expr.clone(); + Analysis::UpdateVariableRefKind(clone.get(), refKind); + return clone; +} + +} // namespace + +const Variable* Inliner::RemapVariable(const Variable* variable, + const VariableRewriteMap* varMap) { + std::unique_ptr<Expression>* remap = varMap->find(variable); + if (!remap) { + SkDEBUGFAILF("rewrite map does not contain variable '%.*s'", + (int)variable->name().size(), variable->name().data()); + return variable; + } + Expression* expr = remap->get(); + SkASSERT(expr); + if (!expr->is<VariableReference>()) { + SkDEBUGFAILF("rewrite map contains non-variable replacement for '%.*s'", + (int)variable->name().size(), variable->name().data()); + return variable; + } + return expr->as<VariableReference>().variable(); +} + +void Inliner::ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt) { + // No changes necessary if this statement isn't actually a block. + if (!inlinedBody || !inlinedBody->is<Block>()) { + return; + } + + // No changes necessary if the parent statement doesn't require a scope. + if (!parentStmt || !(parentStmt->is<IfStatement>() || parentStmt->is<ForStatement>() || + parentStmt->is<DoStatement>())) { + return; + } + + Block& block = inlinedBody->as<Block>(); + + // The inliner will create inlined function bodies as a Block containing multiple statements, + // but no scope. Normally, this is fine, but if this block is used as the statement for a + // do/for/if/while, the block needs to be scoped for the generated code to match the intent. + // In the case of Blocks nested inside other Blocks, we add the scope to the outermost block if + // needed. + for (Block* nestedBlock = █; ) { + if (nestedBlock->isScope()) { + // We found an explicit scope; all is well. + return; + } + if (nestedBlock->children().size() == 1 && nestedBlock->children()[0]->is<Block>()) { + // This block wraps another unscoped block; we need to go deeper. + nestedBlock = &nestedBlock->children()[0]->as<Block>(); + continue; + } + // We found a block containing real statements (not just more blocks), but no scope. + // Let's add a scope to the outermost block. + block.setBlockKind(Block::Kind::kBracedScope); + return; + } +} + +std::unique_ptr<Expression> Inliner::inlineExpression(Position pos, + VariableRewriteMap* varMap, + SymbolTable* symbolTableForExpression, + const Expression& expression) { + auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> { + if (e) { + return this->inlineExpression(pos, varMap, symbolTableForExpression, *e); + } + return nullptr; + }; + auto argList = [&](const ExpressionArray& originalArgs) -> ExpressionArray { + ExpressionArray args; + args.reserve_back(originalArgs.size()); + for (const std::unique_ptr<Expression>& arg : originalArgs) { + args.push_back(expr(arg)); + } + return args; + }; + + switch (expression.kind()) { + case Expression::Kind::kBinary: { + const BinaryExpression& binaryExpr = expression.as<BinaryExpression>(); + return BinaryExpression::Make(*fContext, + pos, + expr(binaryExpr.left()), + binaryExpr.getOperator(), + expr(binaryExpr.right())); + } + case Expression::Kind::kLiteral: + return expression.clone(); + case Expression::Kind::kChildCall: { + const ChildCall& childCall = expression.as<ChildCall>(); + return ChildCall::Make(*fContext, + pos, + childCall.type().clone(symbolTableForExpression), + childCall.child(), + argList(childCall.arguments())); + } + case Expression::Kind::kConstructorArray: { + const ConstructorArray& ctor = expression.as<ConstructorArray>(); + return ConstructorArray::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + argList(ctor.arguments())); + } + case Expression::Kind::kConstructorArrayCast: { + const ConstructorArrayCast& ctor = expression.as<ConstructorArrayCast>(); + return ConstructorArrayCast::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + expr(ctor.argument())); + } + case Expression::Kind::kConstructorCompound: { + const ConstructorCompound& ctor = expression.as<ConstructorCompound>(); + return ConstructorCompound::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + argList(ctor.arguments())); + } + case Expression::Kind::kConstructorCompoundCast: { + const ConstructorCompoundCast& ctor = expression.as<ConstructorCompoundCast>(); + return ConstructorCompoundCast::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + expr(ctor.argument())); + } + case Expression::Kind::kConstructorDiagonalMatrix: { + const ConstructorDiagonalMatrix& ctor = expression.as<ConstructorDiagonalMatrix>(); + return ConstructorDiagonalMatrix::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + expr(ctor.argument())); + } + case Expression::Kind::kConstructorMatrixResize: { + const ConstructorMatrixResize& ctor = expression.as<ConstructorMatrixResize>(); + return ConstructorMatrixResize::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + expr(ctor.argument())); + } + case Expression::Kind::kConstructorScalarCast: { + const ConstructorScalarCast& ctor = expression.as<ConstructorScalarCast>(); + return ConstructorScalarCast::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + expr(ctor.argument())); + } + case Expression::Kind::kConstructorSplat: { + const ConstructorSplat& ctor = expression.as<ConstructorSplat>(); + return ConstructorSplat::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + expr(ctor.argument())); + } + case Expression::Kind::kConstructorStruct: { + const ConstructorStruct& ctor = expression.as<ConstructorStruct>(); + return ConstructorStruct::Make(*fContext, pos, + *ctor.type().clone(symbolTableForExpression), + argList(ctor.arguments())); + } + case Expression::Kind::kFieldAccess: { + const FieldAccess& f = expression.as<FieldAccess>(); + return FieldAccess::Make(*fContext, pos, expr(f.base()), f.fieldIndex(), f.ownerKind()); + } + case Expression::Kind::kFunctionCall: { + const FunctionCall& funcCall = expression.as<FunctionCall>(); + return FunctionCall::Make(*fContext, + pos, + funcCall.type().clone(symbolTableForExpression), + funcCall.function(), + argList(funcCall.arguments())); + } + case Expression::Kind::kFunctionReference: + return expression.clone(); + case Expression::Kind::kIndex: { + const IndexExpression& idx = expression.as<IndexExpression>(); + return IndexExpression::Make(*fContext, pos, expr(idx.base()), expr(idx.index())); + } + case Expression::Kind::kMethodReference: + return expression.clone(); + case Expression::Kind::kPrefix: { + const PrefixExpression& p = expression.as<PrefixExpression>(); + return PrefixExpression::Make(*fContext, pos, p.getOperator(), expr(p.operand())); + } + case Expression::Kind::kPostfix: { + const PostfixExpression& p = expression.as<PostfixExpression>(); + return PostfixExpression::Make(*fContext, pos, expr(p.operand()), p.getOperator()); + } + case Expression::Kind::kSetting: { + const Setting& s = expression.as<Setting>(); + return Setting::Convert(*fContext, pos, s.name()); + } + case Expression::Kind::kSwizzle: { + const Swizzle& s = expression.as<Swizzle>(); + return Swizzle::Make(*fContext, pos, expr(s.base()), s.components()); + } + case Expression::Kind::kTernary: { + const TernaryExpression& t = expression.as<TernaryExpression>(); + return TernaryExpression::Make(*fContext, pos, expr(t.test()), + expr(t.ifTrue()), expr(t.ifFalse())); + } + case Expression::Kind::kTypeReference: + return expression.clone(); + case Expression::Kind::kVariableReference: { + const VariableReference& v = expression.as<VariableReference>(); + std::unique_ptr<Expression>* remap = varMap->find(v.variable()); + if (remap) { + return clone_with_ref_kind(**remap, v.refKind()); + } + return expression.clone(); + } + default: + SkASSERT(false); + return nullptr; + } +} + +std::unique_ptr<Statement> Inliner::inlineStatement(Position pos, + VariableRewriteMap* varMap, + SymbolTable* symbolTableForStatement, + std::unique_ptr<Expression>* resultExpr, + Analysis::ReturnComplexity returnComplexity, + const Statement& statement, + const ProgramUsage& usage, + bool isBuiltinCode) { + auto stmt = [&](const std::unique_ptr<Statement>& s) -> std::unique_ptr<Statement> { + if (s) { + return this->inlineStatement(pos, varMap, symbolTableForStatement, resultExpr, + returnComplexity, *s, usage, isBuiltinCode); + } + return nullptr; + }; + auto blockStmts = [&](const Block& block) { + StatementArray result; + result.reserve_back(block.children().size()); + for (const std::unique_ptr<Statement>& child : block.children()) { + result.push_back(stmt(child)); + } + return result; + }; + auto expr = [&](const std::unique_ptr<Expression>& e) -> std::unique_ptr<Expression> { + if (e) { + return this->inlineExpression(pos, varMap, symbolTableForStatement, *e); + } + return nullptr; + }; + auto variableModifiers = [&](const Variable& variable, + const Expression* initialValue) -> const Modifiers* { + return Transform::AddConstToVarModifiers(*fContext, variable, initialValue, &usage); + }; + + ++fInlinedStatementCounter; + + switch (statement.kind()) { + case Statement::Kind::kBlock: { + const Block& b = statement.as<Block>(); + return Block::Make(pos, blockStmts(b), b.blockKind(), + SymbolTable::WrapIfBuiltin(b.symbolTable())); + } + + case Statement::Kind::kBreak: + case Statement::Kind::kContinue: + case Statement::Kind::kDiscard: + return statement.clone(); + + case Statement::Kind::kDo: { + const DoStatement& d = statement.as<DoStatement>(); + return DoStatement::Make(*fContext, pos, stmt(d.statement()), expr(d.test())); + } + case Statement::Kind::kExpression: { + const ExpressionStatement& e = statement.as<ExpressionStatement>(); + return ExpressionStatement::Make(*fContext, expr(e.expression())); + } + case Statement::Kind::kFor: { + const ForStatement& f = statement.as<ForStatement>(); + // need to ensure initializer is evaluated first so that we've already remapped its + // declarations by the time we evaluate test & next + std::unique_ptr<Statement> initializer = stmt(f.initializer()); + + std::unique_ptr<LoopUnrollInfo> unrollInfo; + if (f.unrollInfo()) { + // The for loop's unroll-info points to the Variable in the initializer as the + // index. This variable has been rewritten into a clone by the inliner, so we need + // to update the loop-unroll info to point to the clone. + unrollInfo = std::make_unique<LoopUnrollInfo>(*f.unrollInfo()); + unrollInfo->fIndex = RemapVariable(unrollInfo->fIndex, varMap); + } + return ForStatement::Make(*fContext, pos, ForLoopPositions{}, std::move(initializer), + expr(f.test()), expr(f.next()), stmt(f.statement()), + std::move(unrollInfo), + SymbolTable::WrapIfBuiltin(f.symbols())); + } + case Statement::Kind::kIf: { + const IfStatement& i = statement.as<IfStatement>(); + return IfStatement::Make(*fContext, pos, expr(i.test()), + stmt(i.ifTrue()), stmt(i.ifFalse())); + } + case Statement::Kind::kNop: + return statement.clone(); + + case Statement::Kind::kReturn: { + const ReturnStatement& r = statement.as<ReturnStatement>(); + if (!r.expression()) { + // This function doesn't return a value. We won't inline functions with early + // returns, so a return statement is a no-op and can be treated as such. + return Nop::Make(); + } + + // If a function only contains a single return, and it doesn't reference variables from + // inside an Block's scope, we don't need to store the result in a variable at all. Just + // replace the function-call expression with the function's return expression. + SkASSERT(resultExpr); + if (returnComplexity <= Analysis::ReturnComplexity::kSingleSafeReturn) { + *resultExpr = expr(r.expression()); + return Nop::Make(); + } + + // For more complex functions, we assign their result into a variable. We refuse to + // inline anything with early returns, so this should be safe to do; that is, on this + // control path, this is the last statement that will occur. + SkASSERT(*resultExpr); + return ExpressionStatement::Make( + *fContext, + BinaryExpression::Make( + *fContext, + pos, + clone_with_ref_kind(**resultExpr, VariableRefKind::kWrite), + Operator::Kind::EQ, + expr(r.expression()))); + } + case Statement::Kind::kSwitch: { + const SwitchStatement& ss = statement.as<SwitchStatement>(); + StatementArray cases; + cases.reserve_back(ss.cases().size()); + for (const std::unique_ptr<Statement>& switchCaseStmt : ss.cases()) { + const SwitchCase& sc = switchCaseStmt->as<SwitchCase>(); + if (sc.isDefault()) { + cases.push_back(SwitchCase::MakeDefault(pos, stmt(sc.statement()))); + } else { + cases.push_back(SwitchCase::Make(pos, sc.value(), stmt(sc.statement()))); + } + } + return SwitchStatement::Make(*fContext, pos, expr(ss.value()), + std::move(cases), SymbolTable::WrapIfBuiltin(ss.symbols())); + } + case Statement::Kind::kVarDeclaration: { + const VarDeclaration& decl = statement.as<VarDeclaration>(); + std::unique_ptr<Expression> initialValue = expr(decl.value()); + const Variable* variable = decl.var(); + + // We assign unique names to inlined variables--scopes hide most of the problems in this + // regard, but see `InlinerAvoidsVariableNameOverlap` for a counterexample where unique + // names are important. + const std::string* name = symbolTableForStatement->takeOwnershipOfString( + fMangler.uniqueName(variable->name(), symbolTableForStatement)); + auto clonedVar = std::make_unique<Variable>( + pos, + variable->modifiersPosition(), + variableModifiers(*variable, initialValue.get()), + name->c_str(), + variable->type().clone(symbolTableForStatement), + isBuiltinCode, + variable->storage()); + varMap->set(variable, VariableReference::Make(pos, clonedVar.get())); + auto result = VarDeclaration::Make(*fContext, + clonedVar.get(), + decl.baseType().clone(symbolTableForStatement), + decl.arraySize(), + std::move(initialValue)); + symbolTableForStatement->takeOwnershipOfSymbol(std::move(clonedVar)); + return result; + } + default: + SkASSERT(false); + return nullptr; + } +} + +Inliner::InlinedCall Inliner::inlineCall(const FunctionCall& call, + std::shared_ptr<SymbolTable> symbolTable, + const ProgramUsage& usage, + const FunctionDeclaration* caller) { + using ScratchVariable = Variable::ScratchVariable; + + // Inlining is more complicated here than in a typical compiler, because we have to have a + // high-level IR and can't just drop statements into the middle of an expression or even use + // gotos. + // + // Since we can't insert statements into an expression, we run the inline function as extra + // statements before the statement we're currently processing, relying on a lack of execution + // order guarantees. Since we can't use gotos (which are normally used to replace return + // statements), we wrap the whole function in a loop and use break statements to jump to the + // end. + SkASSERT(fContext); + SkASSERT(this->isSafeToInline(call.function().definition(), usage)); + + const ExpressionArray& arguments = call.arguments(); + const Position pos = call.fPosition; + const FunctionDefinition& function = *call.function().definition(); + const Block& body = function.body()->as<Block>(); + const Analysis::ReturnComplexity returnComplexity = Analysis::GetReturnComplexity(function); + + StatementArray inlineStatements; + int expectedStmtCount = 1 + // Result variable + arguments.size() + // Function argument temp-vars + body.children().size(); // Inlined code + + inlineStatements.reserve_back(expectedStmtCount); + + std::unique_ptr<Expression> resultExpr; + if (returnComplexity > Analysis::ReturnComplexity::kSingleSafeReturn && + !function.declaration().returnType().isVoid()) { + // Create a variable to hold the result in the extra statements. We don't need to do this + // for void-return functions, or in cases that are simple enough that we can just replace + // the function-call node with the result expression. + ScratchVariable var = Variable::MakeScratchVariable(*fContext, + fMangler, + function.declaration().name(), + &function.declaration().returnType(), + Modifiers{}, + symbolTable.get(), + /*initialValue=*/nullptr); + inlineStatements.push_back(std::move(var.fVarDecl)); + resultExpr = VariableReference::Make(Position(), var.fVarSymbol); + } + + // Create variables in the extra statements to hold the arguments, and assign the arguments to + // them. + VariableRewriteMap varMap; + for (int i = 0; i < arguments.size(); ++i) { + // If the parameter isn't written to within the inline function ... + const Expression* arg = arguments[i].get(); + const Variable* param = function.declaration().parameters()[i]; + const ProgramUsage::VariableCounts& paramUsage = usage.get(*param); + if (!paramUsage.fWrite) { + // ... and can be inlined trivially (e.g. a swizzle, or a constant array index), + // or any expression without side effects that is only accessed at most once... + if ((paramUsage.fRead > 1) ? Analysis::IsTrivialExpression(*arg) + : !Analysis::HasSideEffects(*arg)) { + // ... we don't need to copy it at all! We can just use the existing expression. + varMap.set(param, arg->clone()); + continue; + } + } + ScratchVariable var = Variable::MakeScratchVariable(*fContext, + fMangler, + param->name(), + &arg->type(), + param->modifiers(), + symbolTable.get(), + arg->clone()); + inlineStatements.push_back(std::move(var.fVarDecl)); + varMap.set(param, VariableReference::Make(Position(), var.fVarSymbol)); + } + + for (const std::unique_ptr<Statement>& stmt : body.children()) { + inlineStatements.push_back(this->inlineStatement(pos, &varMap, symbolTable.get(), + &resultExpr, returnComplexity, *stmt, + usage, caller->isBuiltin())); + } + + SkASSERT(inlineStatements.size() <= expectedStmtCount); + + // Wrap all of the generated statements in a block. We need a real Block here, because we need + // to add another child statement to the Block later. + InlinedCall inlinedCall; + inlinedCall.fInlinedBody = Block::MakeBlock(pos, std::move(inlineStatements), + Block::Kind::kUnbracedBlock); + if (resultExpr) { + // Return our result expression as-is. + inlinedCall.fReplacementExpr = std::move(resultExpr); + } else if (function.declaration().returnType().isVoid()) { + // It's a void function, so it doesn't actually result in anything, but we have to return + // something non-null as a standin. + inlinedCall.fReplacementExpr = Literal::MakeBool(*fContext, pos, /*value=*/false); + } else { + // It's a non-void function, but it never created a result expression--that is, it never + // returned anything on any path! This should have been detected in the function finalizer. + // Still, discard our output and generate an error. + SkDEBUGFAIL("inliner found non-void function that fails to return a value on any path"); + fContext->fErrors->error(function.fPosition, "inliner found non-void function '" + + std::string(function.declaration().name()) + + "' that fails to return a value on any path"); + inlinedCall = {}; + } + + return inlinedCall; +} + +bool Inliner::isSafeToInline(const FunctionDefinition* functionDef, const ProgramUsage& usage) { + // A threshold of zero indicates that the inliner is completely disabled, so we can just return. + if (this->settings().fInlineThreshold <= 0) { + return false; + } + + // Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl) + if (fInlinedStatementCounter >= kInlinedStatementLimit) { + return false; + } + + if (functionDef == nullptr) { + // Can't inline something if we don't actually have its definition. + return false; + } + + if (functionDef->declaration().modifiers().fFlags & Modifiers::kNoInline_Flag) { + // Refuse to inline functions decorated with `noinline`. + return false; + } + + // We don't allow inlining a function with out parameters that are written to. + // (See skia:11326 for rationale.) + for (const Variable* param : functionDef->declaration().parameters()) { + if (param->modifiers().fFlags & Modifiers::Flag::kOut_Flag) { + ProgramUsage::VariableCounts counts = usage.get(*param); + if (counts.fWrite > 0) { + return false; + } + } + } + + // We don't have a mechanism to simulate early returns, so we can't inline if there is one. + return Analysis::GetReturnComplexity(*functionDef) < Analysis::ReturnComplexity::kEarlyReturns; +} + +// A candidate function for inlining, containing everything that `inlineCall` needs. +struct InlineCandidate { + std::shared_ptr<SymbolTable> fSymbols; // the SymbolTable of the candidate + std::unique_ptr<Statement>* fParentStmt; // the parent Statement of the enclosing stmt + std::unique_ptr<Statement>* fEnclosingStmt; // the Statement containing the candidate + std::unique_ptr<Expression>* fCandidateExpr; // the candidate FunctionCall to be inlined + FunctionDefinition* fEnclosingFunction; // the Function containing the candidate +}; + +struct InlineCandidateList { + std::vector<InlineCandidate> fCandidates; +}; + +class InlineCandidateAnalyzer { +public: + // A list of all the inlining candidates we found during analysis. + InlineCandidateList* fCandidateList; + + // A stack of the symbol tables; since most nodes don't have one, expected to be shallower than + // the enclosing-statement stack. + std::vector<std::shared_ptr<SymbolTable>> fSymbolTableStack; + // A stack of "enclosing" statements--these would be suitable for the inliner to use for adding + // new instructions. Not all statements are suitable (e.g. a for-loop's initializer). The + // inliner might replace a statement with a block containing the statement. + std::vector<std::unique_ptr<Statement>*> fEnclosingStmtStack; + // The function that we're currently processing (i.e. inlining into). + FunctionDefinition* fEnclosingFunction = nullptr; + + void visit(const std::vector<std::unique_ptr<ProgramElement>>& elements, + std::shared_ptr<SymbolTable> symbols, + InlineCandidateList* candidateList) { + fCandidateList = candidateList; + fSymbolTableStack.push_back(symbols); + + for (const std::unique_ptr<ProgramElement>& pe : elements) { + this->visitProgramElement(pe.get()); + } + + fSymbolTableStack.pop_back(); + fCandidateList = nullptr; + } + + void visitProgramElement(ProgramElement* pe) { + switch (pe->kind()) { + case ProgramElement::Kind::kFunction: { + FunctionDefinition& funcDef = pe->as<FunctionDefinition>(); + fEnclosingFunction = &funcDef; + this->visitStatement(&funcDef.body()); + break; + } + default: + // The inliner can't operate outside of a function's scope. + break; + } + } + + void visitStatement(std::unique_ptr<Statement>* stmt, + bool isViableAsEnclosingStatement = true) { + if (!*stmt) { + return; + } + + Analysis::SymbolTableStackBuilder scopedStackBuilder(stmt->get(), &fSymbolTableStack); + size_t oldEnclosingStmtStackSize = fEnclosingStmtStack.size(); + + if (isViableAsEnclosingStatement) { + fEnclosingStmtStack.push_back(stmt); + } + + switch ((*stmt)->kind()) { + case Statement::Kind::kBreak: + case Statement::Kind::kContinue: + case Statement::Kind::kDiscard: + case Statement::Kind::kNop: + break; + + case Statement::Kind::kBlock: { + Block& block = (*stmt)->as<Block>(); + for (std::unique_ptr<Statement>& blockStmt : block.children()) { + this->visitStatement(&blockStmt); + } + break; + } + case Statement::Kind::kDo: { + DoStatement& doStmt = (*stmt)->as<DoStatement>(); + // The loop body is a candidate for inlining. + this->visitStatement(&doStmt.statement()); + // The inliner isn't smart enough to inline the test-expression for a do-while + // loop at this time. There are two limitations: + // - We would need to insert the inlined-body block at the very end of the do- + // statement's inner fStatement. We don't support that today, but it's doable. + // - We cannot inline the test expression if the loop uses `continue` anywhere; that + // would skip over the inlined block that evaluates the test expression. There + // isn't a good fix for this--any workaround would be more complex than the cost + // of a function call. However, loops that don't use `continue` would still be + // viable candidates for inlining. + break; + } + case Statement::Kind::kExpression: { + ExpressionStatement& expr = (*stmt)->as<ExpressionStatement>(); + this->visitExpression(&expr.expression()); + break; + } + case Statement::Kind::kFor: { + ForStatement& forStmt = (*stmt)->as<ForStatement>(); + // The initializer and loop body are candidates for inlining. + this->visitStatement(&forStmt.initializer(), + /*isViableAsEnclosingStatement=*/false); + this->visitStatement(&forStmt.statement()); + + // The inliner isn't smart enough to inline the test- or increment-expressions + // of a for loop loop at this time. There are a handful of limitations: + // - We would need to insert the test-expression block at the very beginning of the + // for-loop's inner fStatement, and the increment-expression block at the very + // end. We don't support that today, but it's doable. + // - The for-loop's built-in test-expression would need to be dropped entirely, + // and the loop would be halted via a break statement at the end of the inlined + // test-expression. This is again something we don't support today, but it could + // be implemented. + // - We cannot inline the increment-expression if the loop uses `continue` anywhere; + // that would skip over the inlined block that evaluates the increment expression. + // There isn't a good fix for this--any workaround would be more complex than the + // cost of a function call. However, loops that don't use `continue` would still + // be viable candidates for increment-expression inlining. + break; + } + case Statement::Kind::kIf: { + IfStatement& ifStmt = (*stmt)->as<IfStatement>(); + this->visitExpression(&ifStmt.test()); + this->visitStatement(&ifStmt.ifTrue()); + this->visitStatement(&ifStmt.ifFalse()); + break; + } + case Statement::Kind::kReturn: { + ReturnStatement& returnStmt = (*stmt)->as<ReturnStatement>(); + this->visitExpression(&returnStmt.expression()); + break; + } + case Statement::Kind::kSwitch: { + SwitchStatement& switchStmt = (*stmt)->as<SwitchStatement>(); + this->visitExpression(&switchStmt.value()); + for (const std::unique_ptr<Statement>& switchCase : switchStmt.cases()) { + // The switch-case's fValue cannot be a FunctionCall; skip it. + this->visitStatement(&switchCase->as<SwitchCase>().statement()); + } + break; + } + case Statement::Kind::kVarDeclaration: { + VarDeclaration& varDeclStmt = (*stmt)->as<VarDeclaration>(); + // Don't need to scan the declaration's sizes; those are always IntLiterals. + this->visitExpression(&varDeclStmt.value()); + break; + } + default: + SkUNREACHABLE; + } + + // Pop our symbol and enclosing-statement stacks. + fEnclosingStmtStack.resize(oldEnclosingStmtStackSize); + } + + void visitExpression(std::unique_ptr<Expression>* expr) { + if (!*expr) { + return; + } + + switch ((*expr)->kind()) { + case Expression::Kind::kFieldAccess: + case Expression::Kind::kFunctionReference: + case Expression::Kind::kLiteral: + case Expression::Kind::kMethodReference: + case Expression::Kind::kSetting: + case Expression::Kind::kTypeReference: + case Expression::Kind::kVariableReference: + // Nothing to scan here. + break; + + case Expression::Kind::kBinary: { + BinaryExpression& binaryExpr = (*expr)->as<BinaryExpression>(); + this->visitExpression(&binaryExpr.left()); + + // Logical-and and logical-or binary expressions do not inline the right side, + // because that would invalidate short-circuiting. That is, when evaluating + // expressions like these: + // (false && x()) // always false + // (true || y()) // always true + // It is illegal for side-effects from x() or y() to occur. The simplest way to + // enforce that rule is to avoid inlining the right side entirely. However, it is + // safe for other types of binary expression to inline both sides. + Operator op = binaryExpr.getOperator(); + bool shortCircuitable = (op.kind() == Operator::Kind::LOGICALAND || + op.kind() == Operator::Kind::LOGICALOR); + if (!shortCircuitable) { + this->visitExpression(&binaryExpr.right()); + } + break; + } + case Expression::Kind::kChildCall: { + ChildCall& childCallExpr = (*expr)->as<ChildCall>(); + for (std::unique_ptr<Expression>& arg : childCallExpr.arguments()) { + this->visitExpression(&arg); + } + break; + } + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorStruct: { + AnyConstructor& constructorExpr = (*expr)->asAnyConstructor(); + for (std::unique_ptr<Expression>& arg : constructorExpr.argumentSpan()) { + this->visitExpression(&arg); + } + break; + } + case Expression::Kind::kFunctionCall: { + FunctionCall& funcCallExpr = (*expr)->as<FunctionCall>(); + for (std::unique_ptr<Expression>& arg : funcCallExpr.arguments()) { + this->visitExpression(&arg); + } + this->addInlineCandidate(expr); + break; + } + case Expression::Kind::kIndex: { + IndexExpression& indexExpr = (*expr)->as<IndexExpression>(); + this->visitExpression(&indexExpr.base()); + this->visitExpression(&indexExpr.index()); + break; + } + case Expression::Kind::kPostfix: { + PostfixExpression& postfixExpr = (*expr)->as<PostfixExpression>(); + this->visitExpression(&postfixExpr.operand()); + break; + } + case Expression::Kind::kPrefix: { + PrefixExpression& prefixExpr = (*expr)->as<PrefixExpression>(); + this->visitExpression(&prefixExpr.operand()); + break; + } + case Expression::Kind::kSwizzle: { + Swizzle& swizzleExpr = (*expr)->as<Swizzle>(); + this->visitExpression(&swizzleExpr.base()); + break; + } + case Expression::Kind::kTernary: { + TernaryExpression& ternaryExpr = (*expr)->as<TernaryExpression>(); + // The test expression is a candidate for inlining. + this->visitExpression(&ternaryExpr.test()); + // The true- and false-expressions cannot be inlined, because we are only allowed to + // evaluate one side. + break; + } + default: + SkUNREACHABLE; + } + } + + void addInlineCandidate(std::unique_ptr<Expression>* candidate) { + fCandidateList->fCandidates.push_back( + InlineCandidate{fSymbolTableStack.back(), + find_parent_statement(fEnclosingStmtStack), + fEnclosingStmtStack.back(), + candidate, + fEnclosingFunction}); + } +}; + +static const FunctionDeclaration& candidate_func(const InlineCandidate& candidate) { + return (*candidate.fCandidateExpr)->as<FunctionCall>().function(); +} + +bool Inliner::candidateCanBeInlined(const InlineCandidate& candidate, + const ProgramUsage& usage, + InlinabilityCache* cache) { + const FunctionDeclaration& funcDecl = candidate_func(candidate); + if (const bool* cachedInlinability = cache->find(&funcDecl)) { + return *cachedInlinability; + } + bool inlinability = this->isSafeToInline(funcDecl.definition(), usage); + cache->set(&funcDecl, inlinability); + return inlinability; +} + +int Inliner::getFunctionSize(const FunctionDeclaration& funcDecl, FunctionSizeCache* cache) { + if (const int* cachedSize = cache->find(&funcDecl)) { + return *cachedSize; + } + int size = Analysis::NodeCountUpToLimit(*funcDecl.definition(), + this->settings().fInlineThreshold); + cache->set(&funcDecl, size); + return size; +} + +void Inliner::buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>>& elements, + std::shared_ptr<SymbolTable> symbols, ProgramUsage* usage, + InlineCandidateList* candidateList) { + // This is structured much like a ProgramVisitor, but does not actually use ProgramVisitor. + // The analyzer needs to keep track of the `unique_ptr<T>*` of statements and expressions so + // that they can later be replaced, and ProgramVisitor does not provide this; it only provides a + // `const T&`. + InlineCandidateAnalyzer analyzer; + analyzer.visit(elements, symbols, candidateList); + + // Early out if there are no inlining candidates. + std::vector<InlineCandidate>& candidates = candidateList->fCandidates; + if (candidates.empty()) { + return; + } + + // Remove candidates that are not safe to inline. + InlinabilityCache cache; + candidates.erase(std::remove_if(candidates.begin(), + candidates.end(), + [&](const InlineCandidate& candidate) { + return !this->candidateCanBeInlined( + candidate, *usage, &cache); + }), + candidates.end()); + + // If the inline threshold is unlimited, or if we have no candidates left, our candidate list is + // complete. + if (this->settings().fInlineThreshold == INT_MAX || candidates.empty()) { + return; + } + + // Remove candidates on a per-function basis if the effect of inlining would be to make more + // than `inlineThreshold` nodes. (i.e. if Func() would be inlined six times and its size is + // 10 nodes, it should be inlined if the inlineThreshold is 60 or higher.) + FunctionSizeCache functionSizeCache; + FunctionSizeCache candidateTotalCost; + for (InlineCandidate& candidate : candidates) { + const FunctionDeclaration& fnDecl = candidate_func(candidate); + candidateTotalCost[&fnDecl] += this->getFunctionSize(fnDecl, &functionSizeCache); + } + + candidates.erase(std::remove_if(candidates.begin(), candidates.end(), + [&](const InlineCandidate& candidate) { + const FunctionDeclaration& fnDecl = candidate_func(candidate); + if (fnDecl.modifiers().fFlags & Modifiers::kInline_Flag) { + // Functions marked `inline` ignore size limitations. + return false; + } + if (usage->get(fnDecl) == 1) { + // If a function is only used once, it's cost-free to inline. + return false; + } + if (candidateTotalCost[&fnDecl] <= this->settings().fInlineThreshold) { + // We won't exceed the inline threshold by inlining this. + return false; + } + // Inlining this function will add too many IRNodes. + return true; + }), + candidates.end()); +} + +bool Inliner::analyze(const std::vector<std::unique_ptr<ProgramElement>>& elements, + std::shared_ptr<SymbolTable> symbols, + ProgramUsage* usage) { + // A threshold of zero indicates that the inliner is completely disabled, so we can just return. + if (this->settings().fInlineThreshold <= 0) { + return false; + } + + // Enforce a limit on inlining to avoid pathological cases. (inliner/ExponentialGrowth.sksl) + if (fInlinedStatementCounter >= kInlinedStatementLimit) { + return false; + } + + InlineCandidateList candidateList; + this->buildCandidateList(elements, symbols, usage, &candidateList); + + // Inline the candidates where we've determined that it's safe to do so. + using StatementRemappingTable = SkTHashMap<std::unique_ptr<Statement>*, + std::unique_ptr<Statement>*>; + StatementRemappingTable statementRemappingTable; + + bool madeChanges = false; + for (const InlineCandidate& candidate : candidateList.fCandidates) { + const FunctionCall& funcCall = (*candidate.fCandidateExpr)->as<FunctionCall>(); + + // Convert the function call to its inlined equivalent. + InlinedCall inlinedCall = this->inlineCall(funcCall, candidate.fSymbols, *usage, + &candidate.fEnclosingFunction->declaration()); + + // Stop if an error was detected during the inlining process. + if (!inlinedCall.fInlinedBody && !inlinedCall.fReplacementExpr) { + break; + } + + // Ensure that the inlined body has a scope if it needs one. + this->ensureScopedBlocks(inlinedCall.fInlinedBody.get(), candidate.fParentStmt->get()); + + // Add references within the inlined body + usage->add(inlinedCall.fInlinedBody.get()); + + // Look up the enclosing statement; remap it if necessary. + std::unique_ptr<Statement>* enclosingStmt = candidate.fEnclosingStmt; + for (;;) { + std::unique_ptr<Statement>** remappedStmt = statementRemappingTable.find(enclosingStmt); + if (!remappedStmt) { + break; + } + enclosingStmt = *remappedStmt; + } + + // Move the enclosing statement to the end of the unscoped Block containing the inlined + // function, then replace the enclosing statement with that Block. + // Before: + // fInlinedBody = Block{ stmt1, stmt2, stmt3 } + // fEnclosingStmt = stmt4 + // After: + // fInlinedBody = null + // fEnclosingStmt = Block{ stmt1, stmt2, stmt3, stmt4 } + inlinedCall.fInlinedBody->children().push_back(std::move(*enclosingStmt)); + *enclosingStmt = std::move(inlinedCall.fInlinedBody); + + // Replace the candidate function call with our replacement expression. + usage->remove(candidate.fCandidateExpr->get()); + usage->add(inlinedCall.fReplacementExpr.get()); + *candidate.fCandidateExpr = std::move(inlinedCall.fReplacementExpr); + madeChanges = true; + + // If anything else pointed at our enclosing statement, it's now pointing at a Block + // containing many other statements as well. Maintain a fix-up table to account for this. + statementRemappingTable.set(enclosingStmt,&(*enclosingStmt)->as<Block>().children().back()); + + // Stop inlining if we've reached our hard cap on new statements. + if (fInlinedStatementCounter >= kInlinedStatementLimit) { + break; + } + + // Note that nothing was destroyed except for the FunctionCall. All other nodes should + // remain valid. + } + + return madeChanges; +} + +} // namespace SkSL + +#endif // SK_ENABLE_OPTIMIZE_SIZE diff --git a/gfx/skia/skia/src/sksl/SkSLInliner.h b/gfx/skia/skia/src/sksl/SkSLInliner.h new file mode 100644 index 0000000000..618365baf0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLInliner.h @@ -0,0 +1,119 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_INLINER +#define SKSL_INLINER + +#ifndef SK_ENABLE_OPTIMIZE_SIZE + +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLMangler.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <vector> + +namespace SkSL { + +class FunctionCall; +class FunctionDeclaration; +class FunctionDefinition; +class Position; +class ProgramElement; +class ProgramUsage; +class Statement; +class SymbolTable; +class Variable; +struct InlineCandidate; +struct InlineCandidateList; +namespace Analysis { enum class ReturnComplexity; } + +/** + * Converts a FunctionCall in the IR to a set of statements to be injected ahead of the function + * call, and a replacement expression. Can also detect cases where inlining isn't cleanly possible + * (e.g. return statements nested inside of a loop construct). The inliner isn't able to guarantee + * identical-to-GLSL execution order if the inlined function has visible side effects. + */ +class Inliner { +public: + Inliner(const Context* context) : fContext(context) {} + + /** Inlines any eligible functions that are found. Returns true if any changes are made. */ + bool analyze(const std::vector<std::unique_ptr<ProgramElement>>& elements, + std::shared_ptr<SymbolTable> symbols, + ProgramUsage* usage); + +private: + using VariableRewriteMap = SkTHashMap<const Variable*, std::unique_ptr<Expression>>; + + const ProgramSettings& settings() const { return fContext->fConfig->fSettings; } + + void buildCandidateList(const std::vector<std::unique_ptr<ProgramElement>>& elements, + std::shared_ptr<SymbolTable> symbols, ProgramUsage* usage, + InlineCandidateList* candidateList); + + std::unique_ptr<Expression> inlineExpression(Position pos, + VariableRewriteMap* varMap, + SymbolTable* symbolTableForExpression, + const Expression& expression); + std::unique_ptr<Statement> inlineStatement(Position pos, + VariableRewriteMap* varMap, + SymbolTable* symbolTableForStatement, + std::unique_ptr<Expression>* resultExpr, + Analysis::ReturnComplexity returnComplexity, + const Statement& statement, + const ProgramUsage& usage, + bool isBuiltinCode); + + /** + * Searches the rewrite map for an rewritten Variable* for the passed-in one. Asserts if the + * rewrite map doesn't contain the variable, or contains a different type of expression. + */ + static const Variable* RemapVariable(const Variable* variable, + const VariableRewriteMap* varMap); + + using InlinabilityCache = SkTHashMap<const FunctionDeclaration*, bool>; + bool candidateCanBeInlined(const InlineCandidate& candidate, + const ProgramUsage& usage, + InlinabilityCache* cache); + + using FunctionSizeCache = SkTHashMap<const FunctionDeclaration*, int>; + int getFunctionSize(const FunctionDeclaration& fnDecl, FunctionSizeCache* cache); + + /** + * Processes the passed-in FunctionCall expression. The FunctionCall expression should be + * replaced with `fReplacementExpr`. If non-null, `fInlinedBody` should be inserted immediately + * above the statement containing the inlined expression. + */ + struct InlinedCall { + std::unique_ptr<Block> fInlinedBody; + std::unique_ptr<Expression> fReplacementExpr; + }; + InlinedCall inlineCall(const FunctionCall&, + std::shared_ptr<SymbolTable>, + const ProgramUsage&, + const FunctionDeclaration* caller); + + /** Adds a scope to inlined bodies returned by `inlineCall`, if one is required. */ + void ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt); + + /** Checks whether inlining is viable for a FunctionCall, modulo recursion and function size. */ + bool isSafeToInline(const FunctionDefinition* functionDef, const ProgramUsage& usage); + + const Context* fContext = nullptr; + Mangler fMangler; + int fInlinedStatementCounter = 0; +}; + +} // namespace SkSL + +#endif // SK_ENABLE_OPTIMIZE_SIZE + +#endif // SKSL_INLINER diff --git a/gfx/skia/skia/src/sksl/SkSLIntrinsicList.cpp b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.cpp new file mode 100644 index 0000000000..a582bdff60 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLIntrinsicList.h" + +namespace SkSL { + +const IntrinsicMap& GetIntrinsicMap() { + #define SKSL_INTRINSIC(name) {#name, k_##name##_IntrinsicKind}, + static const auto* kAllIntrinsics = new SkTHashMap<std::string_view, IntrinsicKind>{ + SKSL_INTRINSIC_LIST + }; + #undef SKSL_INTRINSIC + + return *kAllIntrinsics; +} + +IntrinsicKind FindIntrinsicKind(std::string_view functionName) { + if (skstd::starts_with(functionName, '$')) { + functionName.remove_prefix(1); + } + + const IntrinsicMap& intrinsicMap = GetIntrinsicMap(); + IntrinsicKind* kind = intrinsicMap.find(functionName); + return kind ? *kind : kNotIntrinsic; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLIntrinsicList.h b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.h new file mode 100644 index 0000000000..9e41f3b1aa --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLIntrinsicList.h @@ -0,0 +1,145 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_INTRINSIC_LIST_DEFINED +#define SKSL_INTRINSIC_LIST_DEFINED + +#include "src/core/SkTHash.h" + +#include <cstdint> +#include <initializer_list> +#include <string_view> + +// A list of every intrinsic supported by SkSL. +// Using an X-Macro (https://en.wikipedia.org/wiki/X_Macro) to manage the list. +#define SKSL_INTRINSIC_LIST \ + SKSL_INTRINSIC(abs) \ + SKSL_INTRINSIC(acosh) \ + SKSL_INTRINSIC(acos) \ + SKSL_INTRINSIC(all) \ + SKSL_INTRINSIC(any) \ + SKSL_INTRINSIC(asinh) \ + SKSL_INTRINSIC(asin) \ + SKSL_INTRINSIC(atanh) \ + SKSL_INTRINSIC(atan) \ + SKSL_INTRINSIC(atomicAdd) \ + SKSL_INTRINSIC(atomicLoad) \ + SKSL_INTRINSIC(atomicStore) \ + SKSL_INTRINSIC(bitCount) \ + SKSL_INTRINSIC(ceil) \ + SKSL_INTRINSIC(clamp) \ + SKSL_INTRINSIC(cosh) \ + SKSL_INTRINSIC(cos) \ + SKSL_INTRINSIC(cross) \ + SKSL_INTRINSIC(degrees) \ + SKSL_INTRINSIC(determinant) \ + SKSL_INTRINSIC(dFdx) \ + SKSL_INTRINSIC(dFdy) \ + SKSL_INTRINSIC(distance) \ + SKSL_INTRINSIC(dot) \ + SKSL_INTRINSIC(equal) \ + SKSL_INTRINSIC(eval) \ + SKSL_INTRINSIC(exp2) \ + SKSL_INTRINSIC(exp) \ + SKSL_INTRINSIC(faceforward) \ + SKSL_INTRINSIC(findLSB) \ + SKSL_INTRINSIC(findMSB) \ + SKSL_INTRINSIC(floatBitsToInt) \ + SKSL_INTRINSIC(floatBitsToUint) \ + SKSL_INTRINSIC(floor) \ + SKSL_INTRINSIC(fma) \ + SKSL_INTRINSIC(fract) \ + SKSL_INTRINSIC(frexp) \ + SKSL_INTRINSIC(fromLinearSrgb) \ + SKSL_INTRINSIC(fwidth) \ + SKSL_INTRINSIC(greaterThanEqual) \ + SKSL_INTRINSIC(greaterThan) \ + SKSL_INTRINSIC(height) \ + SKSL_INTRINSIC(intBitsToFloat) \ + SKSL_INTRINSIC(inversesqrt) \ + SKSL_INTRINSIC(inverse) \ + SKSL_INTRINSIC(isinf) \ + SKSL_INTRINSIC(isnan) \ + SKSL_INTRINSIC(ldexp) \ + SKSL_INTRINSIC(length) \ + SKSL_INTRINSIC(lessThanEqual) \ + SKSL_INTRINSIC(lessThan) \ + SKSL_INTRINSIC(log2) \ + SKSL_INTRINSIC(log) \ + SKSL_INTRINSIC(makeSampler2D) \ + SKSL_INTRINSIC(matrixCompMult) \ + SKSL_INTRINSIC(matrixInverse) \ + SKSL_INTRINSIC(max) \ + SKSL_INTRINSIC(min) \ + SKSL_INTRINSIC(mix) \ + SKSL_INTRINSIC(modf) \ + SKSL_INTRINSIC(mod) \ + SKSL_INTRINSIC(normalize) \ + SKSL_INTRINSIC(notEqual) \ + SKSL_INTRINSIC(not ) \ + SKSL_INTRINSIC(outerProduct) \ + SKSL_INTRINSIC(packDouble2x32) \ + SKSL_INTRINSIC(packHalf2x16) \ + SKSL_INTRINSIC(packSnorm2x16) \ + SKSL_INTRINSIC(packSnorm4x8) \ + SKSL_INTRINSIC(packUnorm2x16) \ + SKSL_INTRINSIC(packUnorm4x8) \ + SKSL_INTRINSIC(pow) \ + SKSL_INTRINSIC(radians) \ + SKSL_INTRINSIC(read) \ + SKSL_INTRINSIC(reflect) \ + SKSL_INTRINSIC(refract) \ + SKSL_INTRINSIC(roundEven) \ + SKSL_INTRINSIC(round) \ + SKSL_INTRINSIC(sample) \ + SKSL_INTRINSIC(sampleGrad) \ + SKSL_INTRINSIC(sampleLod) \ + SKSL_INTRINSIC(saturate) \ + SKSL_INTRINSIC(sign) \ + SKSL_INTRINSIC(sinh) \ + SKSL_INTRINSIC(sin) \ + SKSL_INTRINSIC(smoothstep) \ + SKSL_INTRINSIC(sqrt) \ + SKSL_INTRINSIC(step) \ + SKSL_INTRINSIC(storageBarrier) \ + SKSL_INTRINSIC(subpassLoad) \ + SKSL_INTRINSIC(tanh) \ + SKSL_INTRINSIC(tan) \ + SKSL_INTRINSIC(toLinearSrgb) \ + SKSL_INTRINSIC(transpose) \ + SKSL_INTRINSIC(trunc) \ + SKSL_INTRINSIC(uintBitsToFloat) \ + SKSL_INTRINSIC(unpackDouble2x32) \ + SKSL_INTRINSIC(unpackHalf2x16) \ + SKSL_INTRINSIC(unpackSnorm2x16) \ + SKSL_INTRINSIC(unpackSnorm4x8) \ + SKSL_INTRINSIC(unpackUnorm2x16) \ + SKSL_INTRINSIC(unpackUnorm4x8) \ + SKSL_INTRINSIC(width) \ + SKSL_INTRINSIC(workgroupBarrier) \ + SKSL_INTRINSIC(write) + +namespace SkSL { + +// The `IntrinsicKind` enum holds every intrinsic supported by SkSL. +#define SKSL_INTRINSIC(name) k_##name##_IntrinsicKind, +enum IntrinsicKind : int8_t { + kNotIntrinsic = -1, + SKSL_INTRINSIC_LIST +}; +#undef SKSL_INTRINSIC + +// Returns a map which allows IntrinsicKind values to be looked up by name. +using IntrinsicMap = SkTHashMap<std::string_view, IntrinsicKind>; +const IntrinsicMap& GetIntrinsicMap(); + +// Looks up intrinsic functions by name. +IntrinsicKind FindIntrinsicKind(std::string_view functionName); + +} + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLLexer.cpp b/gfx/skia/skia/src/sksl/SkSLLexer.cpp new file mode 100644 index 0000000000..10c1108c09 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLLexer.cpp @@ -0,0 +1,808 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +/***************************************************************************************** + ******************** This file was generated by sksllex. Do not edit. ******************* + *****************************************************************************************/ +#include "src/sksl/SkSLLexer.h" + +namespace SkSL { + +using State = uint16_t; +static constexpr uint8_t kInvalidChar = 18; +static constexpr int8_t kMappings[118] = { + 1, 2, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 1, 4, 3, 5, 6, 7, 8, 3, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 22, 22, 23, 23, 24, 25, 26, 27, 28, 29, 3, 30, 30, 31, 32, + 33, 30, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 36, 34, 37, 34, 34, 38, + 34, 34, 39, 3, 40, 41, 42, 3, 43, 44, 45, 46, 47, 48, 49, 50, 51, 34, 52, 53, + 54, 55, 56, 57, 34, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70}; +using IndexEntry = int16_t; +struct FullEntry { + State data[71]; +}; +struct CompactEntry { + uint32_t values; + uint8_t data[18]; +}; +static constexpr FullEntry kFull[] = { + { + 0, 2, 3, 4, 5, 7, 9, 23, 25, 28, 29, 30, 32, 35, 36, + 39, 44, 50, 69, 69, 69, 69, 69, 69, 71, 72, 73, 77, 79, 83, + 84, 84, 84, 84, 84, 84, 84, 84, 84, 86, 87, 88, 84, 91, 104, + 114, 130, 150, 162, 178, 183, 191, 84, 215, 225, 232, 258, 263, 279, 291, + 345, 362, 378, 390, 84, 84, 84, 411, 412, 415, 416, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 59, 59, 59, 59, 59, 59, 60, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 66, 67, 0, 0, 0, 0, 0, 0, 0, 0, 61, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 67, 0, 0, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 59, 59, 59, 59, 59, 59, 60, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 60, 60, 60, 60, 60, 60, 60, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 70, 70, 70, 70, 70, 70, 70, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 115, 85, 85, 85, 85, 85, 85, 85, 85, 85, 118, + 85, 85, 121, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 131, 85, 85, 85, 137, 85, 85, + 85, 85, 143, 85, 85, 85, 85, 85, 147, 85, 85, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 151, + 85, 154, 85, 85, 85, 85, 85, 85, 85, 85, 156, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 163, 85, 85, 85, 85, 85, 85, 85, 167, 85, 170, + 85, 85, 173, 85, 85, 85, 85, 85, 175, 85, 85, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 194, + 85, 85, 198, 201, 85, 85, 203, 85, 209, 85, 85, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 264, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 85, 268, 85, 85, 275, 85, 85, 85, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 292, 85, 85, 85, 85, 85, 85, 85, 324, 85, 85, + 85, 85, 85, 85, 85, 85, 328, 336, 85, 340, 85, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 298, 305, 316, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 321, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 346, 85, 85, 352, 85, 85, 85, + 85, 85, 85, 85, 354, 85, 85, 85, 85, 85, 85, 357, 85, 0, 0, 0, 0, + }, + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, + 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 0, 0, 0, 85, 85, 85, 85, 85, 85, 85, 85, 391, 85, 85, 85, + 85, 85, 395, 85, 403, 85, 85, 85, 85, 85, 85, 85, 85, 0, 0, 0, 0, + }, +}; +static constexpr CompactEntry kCompact[] = { + {0, + {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {3, + {195, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {6, + {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {8, {255, 255, 255, 255, 255, 255, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}}, + {8, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}}, + {19 | (11 << 9) | (10 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 106, 170, 170, 162, 170, 234, 63}}, + {10, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}}, + {14 | (12 << 9) | (10 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 106, 170, 232, 63}}, + {13 | (10 << 9), + {255, 255, 255, 255, 87, 84, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 213, 63}}, + {15 | (10 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {16 | (10 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {17 | (10 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {18 | (10 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {20 | (10 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {21 | (10 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {22 | (10 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {24, + {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {27 | (26 << 9), + {255, 255, 253, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {31, + {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {34 | (33 << 9), + {255, 255, 255, 253, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {38 | (37 << 9), + {255, 255, 255, 223, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {40, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {41 | (40 << 9), + {255, 255, 255, 255, 87, 85, 255, 255, 243, 255, 255, 63, 255, 255, 255, 255, 255, 63}}, + {43 | (42 << 9), + {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {43, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {49 | (48 << 9) | (45 << 18), + {255, 255, 191, 255, 253, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {46 | (45 << 9), {87, 85, 21, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 21}}, + {47 | (45 << 9), {87, 85, 85, 85, 84, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 21}}, + {48, {51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + {56 | (52 << 9), + {255, 255, 255, 255, 87, 85, 255, 255, 243, 255, 255, 63, 255, 255, 255, 255, 255, 63}}, + {53 | (52 << 9), + {255, 255, 255, 255, 87, 85, 255, 255, 243, 255, 255, 63, 255, 255, 255, 255, 255, 63}}, + {55 | (54 << 9), + {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {55, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {58 | (57 << 9), + {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {58, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {63 | (62 << 9), + {255, 255, 255, 221, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {63, {255, 255, 255, 255, 3, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {68, {255, 255, 255, 255, 3, 0, 255, 15, 240, 255, 63, 0, 252, 255, 255, 255, 255, 63}}, + {68 | (66 << 9), + {255, 255, 255, 255, 3, 0, 255, 15, 240, 247, 63, 0, 252, 255, 255, 247, 255, 63}}, + {76 | (74 << 9), + {255, 255, 255, 255, 255, 255, 31, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {75, + {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {78, + {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {81 | (80 << 9), + {255, 255, 255, 255, 255, 255, 127, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {82, + {255, 255, 255, 255, 255, 255, 63, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63}}, + {85, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}}, + {90 | (89 << 9), + {255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 243, 255, 255, 255, 255, 255, 255, 63}}, + {94 | (92 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 106, 168, 234, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}}, + {98 | (95 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 169, 168, 234, 63}}, + {96 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}}, + {97 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {99 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {100 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {101 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}}, + {102 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {103 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {109 | (105 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 154, 162, 234, 63}}, + {106 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {107 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {108 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 84, 85, 85, 213, 63}}, + {110 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}}, + {111 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}}, + {112 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {113 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {116 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {117 | (93 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 42, 170, 170, 170, 169, 234, 63}}, + {119 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {120 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {122 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {125 | (123 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 170, 106, 168, 234, 63}}, + {124 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {126 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {127 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {128 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {129 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {132 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}}, + {133 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {134 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {135 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {136 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {138 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {139 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {140 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {141 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {142 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}}, + {144 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {145 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}}, + {146 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {148 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {149 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 23, 80, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 213, 63}}, + {152 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {153 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {155 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {157 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {158 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {159 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {160 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {161 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {164 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {165 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {166 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {168 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 212, 63}}, + {169 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}}, + {171 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {172 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {174 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {176 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {177 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {181 | (179 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 166, 168, 170, 234, 63}}, + {180 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 79, 85, 85, 85, 85, 85, 213, 63}}, + {180, {255, 255, 255, 255, 3, 0, 255, 15, 0, 192, 15, 0, 0, 0, 0, 0, 192, 63}}, + {182 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {188 | (184 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 106, 170, 170, 138, 234, 63}}, + {185 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}}, + {186 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}}, + {187 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {189 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {190 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {193 | (192 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 169, 42, 170, 170, 234, 63}}, + {195 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {196 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {197 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {199 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {200 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {202 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {204 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {205 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {206 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}}, + {207 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {208 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {210 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {211 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {212 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {213 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {214 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {221 | (216 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 168, 170, 234, 63}}, + {217 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}}, + {218 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {219 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {220 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {223 | (222 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 106, 170, 42, 234, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}}, + {224 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {226 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {227 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}}, + {228 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {229 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {230 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}}, + {231 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {240 | (233 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 168, 170, 234, 63}}, + {234 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}}, + {235 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {236 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {237 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {238 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {239 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {247 | (241 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 106, 170, 162, 170, 234, 63}}, + {242 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {243 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {244 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {245 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {246 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {248 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {249 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {250 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {251 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {252 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {253 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {254 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {255 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {256 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 69, 213, 63}}, + {257 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {259 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {260 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {261 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {262 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {265 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {266 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 84, 85, 85, 213, 63}}, + {267 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {269 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {270 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {271 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {272 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 21, 85, 213, 63}}, + {273 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {274 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {276 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}}, + {277 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {278 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {280 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {287 | (281 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 170, 168, 234, 63}}, + {282 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}}, + {283 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {284 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {285 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {286 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}}, + {288 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {289 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {290 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {293 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}}, + {294 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {295 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {296 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {297 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {299 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 84, 213, 95, 85, 85, 85, 85, 85, 213, 63}}, + {300 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 212, 95, 85, 85, 85, 85, 85, 213, 63}}, + {301 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}}, + {302 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {303 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}}, + {304 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 21, 213, 63}}, + {306 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 84, 213, 95, 85, 85, 85, 85, 85, 213, 63}}, + {307 | (300 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 42, 233, 175, 170, 170, 170, 170, 170, 234, 63}}, + {308 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {309 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {310 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {311 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 212, 95, 85, 85, 85, 85, 85, 213, 63}}, + {312 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}}, + {313 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {314 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}}, + {315 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {317 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 84, 213, 95, 85, 85, 85, 85, 85, 213, 63}}, + {318 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 21, 213, 95, 85, 85, 85, 85, 85, 213, 63}}, + {319 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {320 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {322 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {323 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 84, 85, 85, 85, 85, 213, 63}}, + {325 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 197, 63}}, + {326 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {327 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 84, 85, 85, 85, 213, 63}}, + {332 | (329 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 138, 170, 234, 63}}, + {330 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {331 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {333 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {334 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {335 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {337 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {338 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {339 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {93 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {341 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {342 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {343 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 81, 85, 85, 85, 85, 213, 63}}, + {344 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 69, 85, 85, 85, 213, 63}}, + {347 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}}, + {348 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {349 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {350 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {351 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {353 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {355 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {356 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {358 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {359 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {360 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 69, 85, 85, 85, 85, 213, 63}}, + {361 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {375 | (363 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 170, 106, 42, 170, 234, 63}}, + {370 | (364 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 106, 170, 42, 170, 234, 63}}, + {369 | (365 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 175, 170, 169, 170, 168, 170, 234, 63}}, + {366 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {367 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {368 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 69, 85, 85, 213, 63}}, + {371 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {372 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}}, + {373 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {374 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {376 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {377 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {384 | (379 << 9) | (85 << 18), + {255, 255, 255, 255, 171, 170, 255, 175, 170, 234, 111, 170, 170, 170, 168, 170, 234, 63}}, + {380 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {381 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}}, + {382 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {383 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {385 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {386 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 31, 85, 85, 85, 85, 85, 213, 63}}, + {387 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {388 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {389 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {392 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {393 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {394 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {396 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {397 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 84, 85, 85, 213, 63}}, + {398 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 81, 85, 85, 85, 213, 63}}, + {399 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 69, 85, 213, 63}}, + {400 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {401 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 81, 213, 63}}, + {402 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 81, 85, 213, 63}}, + {404 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 21, 85, 85, 85, 213, 63}}, + {405 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 84, 213, 63}}, + {406 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 21, 85, 85, 85, 85, 213, 63}}, + {407 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 84, 85, 213, 63}}, + {408 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 21, 85, 85, 213, 63}}, + {409 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 81, 85, 85, 213, 63}}, + {410 | (85 << 9), + {255, 255, 255, 255, 87, 85, 255, 95, 85, 213, 95, 85, 85, 85, 85, 85, 209, 63}}, + {414 | (413 << 9), + {255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 60}}, +}; +static constexpr IndexEntry kIndices[] = { + 0, -1, 1, 1, 0, 2, 0, 3, 4, 5, 6, 7, 8, 6, 9, 10, 11, 12, + 6, 13, 14, 15, 6, 16, 0, 17, 0, 0, 0, 0, 18, 0, 19, 0, 0, 0, + 20, 0, 0, 21, 22, 23, 24, 24, 25, 26, 27, 0, 28, 0, -2, 29, 30, 31, + 32, 32, 33, 34, 34, -3, -4, 35, 36, 36, 0, 0, 0, 37, 38, -5, -5, 0, + 0, 39, 40, 0, 0, 41, 0, 42, 0, 43, 0, 0, 44, 44, 0, 0, 45, 0, + 0, 46, 47, 44, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, + 44, 62, 63, 64, 65, 44, -6, 66, 67, 44, 68, 69, 70, 71, 72, 73, 44, 74, + 75, 76, 77, 44, -7, 78, 79, 80, 81, 82, 44, 83, 84, 85, 86, 87, 44, 88, + 89, 90, 57, 91, 92, 93, -8, 94, 95, 44, 96, 47, 97, 98, 99, 100, 101, 102, + -9, 103, 104, 105, 44, 106, 107, 108, 109, 110, 44, 111, 44, 112, 113, 93, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 44, 123, 124, 93, 125, 44, -10, 126, 127, 128, 44, + 129, 130, 44, 131, 132, 133, 134, 135, 136, 137, 57, 138, 139, 140, 141, 142, 132, 143, + 144, 145, 146, 147, 44, 148, 149, 150, 44, 151, 152, 153, 154, 155, 156, 44, 157, 158, + 159, 160, 161, 162, 163, 57, 164, 165, 166, 167, 168, 169, 44, 170, 171, 172, 173, 174, + 175, 176, 177, 178, 179, 44, 180, 181, 182, 183, 132, -11, 184, 185, 186, 108, 187, 188, + 189, 190, 191, 192, 193, 194, 195, 196, 51, 197, 198, 199, 200, 201, 202, 203, 44, 204, + 205, 206, 44, -12, 207, 208, 209, 210, 211, -13, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 218, 229, 230, 231, 232, 132, 233, 234, 57, + 235, 236, 237, 238, 239, 240, 241, 51, 242, 243, 244, 44, 245, 246, 247, 248, 249, 250, + 251, 252, 44, -14, 253, 254, 255, 256, 257, 57, 258, 70, 259, 260, 44, 261, 262, 263, + 264, 238, 265, 266, 267, 268, 269, 270, 44, 193, 271, 272, 273, 274, 108, 275, 276, 149, + 277, 278, 279, 280, 281, 149, 282, 283, 284, 285, 286, 57, -15, 287, 288, 289, 44, 290, + 291, 292, 293, 294, 295, 296, 44, 297, 298, 299, 300, 301, 302, 303, 44, 0, 304, 0, + 0, 0, 0, +}; +State get_transition(int transition, int state) { + IndexEntry index = kIndices[state]; + if (index < 0) { + return kFull[~index].data[transition]; + } + const CompactEntry& entry = kCompact[index]; + int v = entry.data[transition >> 2]; + v >>= 2 * (transition & 3); + v &= 3; + v *= 9; + return (entry.values >> v) & 511; +} +static const int8_t kAccepts[417] = { + -1, -1, 88, 88, 91, 67, 72, 91, 42, 40, 40, 40, 40, 36, 40, 40, 40, 40, 37, 40, 40, 40, + 27, 57, 81, 62, 66, 86, 43, 44, 55, 79, 53, 51, 77, 50, 54, 52, 78, 49, 1, -1, -1, 1, + 56, -1, -1, 90, 89, 80, 2, 1, 1, -1, -1, 1, -1, -1, 1, 2, 3, -1, -1, 1, 3, 2, + 2, -1, 2, 2, 2, 69, 87, 74, 58, 82, 76, 70, 71, 73, 75, 59, 83, 68, 41, 41, 47, 48, + 61, 85, 65, 41, 41, 39, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 14, 41, + 41, 41, 41, 30, 41, 41, 41, 12, 41, 41, 41, 41, 41, 41, 22, 41, 41, 41, 41, 15, 41, 41, + 41, 41, 41, 41, 13, 41, 41, 41, 41, 41, 16, 10, 41, 41, 41, 41, 41, 41, 41, 41, 41, 7, + 41, 41, 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 5, 41, 41, 41, 41, 41, 23, 41, 8, 41, + 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 41, 33, 41, 41, 41, 41, 6, 18, 41, 41, 41, 25, + 41, 41, 20, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 32, 41, 41, 41, 35, 41, 41, 41, 41, 41, 41, 34, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 26, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 24, 41, 41, 19, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 28, 41, 41, 41, 17, 41, 41, 41, 41, 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 39, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 31, 41, 41, 41, 41, 41, 41, 41, 41, 11, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 4, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 21, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 9, 41, + 41, 41, 41, 41, 41, 41, 38, 41, 41, 41, 41, 41, 41, 41, 29, 45, 60, 84, 64, 46, 63, +}; + +Token Lexer::next() { + // note that we cheat here: normally a lexer needs to worry about the case + // where a token has a prefix which is not itself a valid token - for instance, + // maybe we have a valid token 'while', but 'w', 'wh', etc. are not valid + // tokens. Our grammar doesn't have this property, so we can simplify the logic + // a bit. + int32_t startOffset = fOffset; + State state = 1; + for (;;) { + if (fOffset >= (int32_t)fText.length()) { + if (startOffset == (int32_t)fText.length() || kAccepts[state] == -1) { + return Token(Token::Kind::TK_END_OF_FILE, startOffset, 0); + } + break; + } + uint8_t c = (uint8_t)(fText[fOffset] - 9); + if (c >= 118) { + c = kInvalidChar; + } + State newState = get_transition(kMappings[c], state); + if (!newState) { + break; + } + state = newState; + ++fOffset; + } + Token::Kind kind = (Token::Kind)kAccepts[state]; + return Token(kind, startOffset, fOffset - startOffset); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLLexer.h b/gfx/skia/skia/src/sksl/SkSLLexer.h new file mode 100644 index 0000000000..1cd81a66b7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLLexer.h @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +/***************************************************************************************** + ******************** This file was generated by sksllex. Do not edit. ******************* + *****************************************************************************************/ +#ifndef SKSL_Lexer +#define SKSL_Lexer +#include <cstdint> +#include <string_view> +namespace SkSL { + +struct Token { + enum class Kind { + TK_END_OF_FILE, + TK_FLOAT_LITERAL, + TK_INT_LITERAL, + TK_BAD_OCTAL, + TK_TRUE_LITERAL, + TK_FALSE_LITERAL, + TK_IF, + TK_ELSE, + TK_FOR, + TK_WHILE, + TK_DO, + TK_SWITCH, + TK_CASE, + TK_DEFAULT, + TK_BREAK, + TK_CONTINUE, + TK_DISCARD, + TK_RETURN, + TK_IN, + TK_OUT, + TK_INOUT, + TK_UNIFORM, + TK_CONST, + TK_FLAT, + TK_NOPERSPECTIVE, + TK_INLINE, + TK_NOINLINE, + TK_PURE, + TK_READONLY, + TK_WRITEONLY, + TK_BUFFER, + TK_STRUCT, + TK_LAYOUT, + TK_HIGHP, + TK_MEDIUMP, + TK_LOWP, + TK_ES3, + TK_EXPORT, + TK_WORKGROUP, + TK_RESERVED, + TK_PRIVATE_IDENTIFIER, + TK_IDENTIFIER, + TK_DIRECTIVE, + TK_LPAREN, + TK_RPAREN, + TK_LBRACE, + TK_RBRACE, + TK_LBRACKET, + TK_RBRACKET, + TK_DOT, + TK_COMMA, + TK_PLUSPLUS, + TK_MINUSMINUS, + TK_PLUS, + TK_MINUS, + TK_STAR, + TK_SLASH, + TK_PERCENT, + TK_SHL, + TK_SHR, + TK_BITWISEOR, + TK_BITWISEXOR, + TK_BITWISEAND, + TK_BITWISENOT, + TK_LOGICALOR, + TK_LOGICALXOR, + TK_LOGICALAND, + TK_LOGICALNOT, + TK_QUESTION, + TK_COLON, + TK_EQ, + TK_EQEQ, + TK_NEQ, + TK_GT, + TK_LT, + TK_GTEQ, + TK_LTEQ, + TK_PLUSEQ, + TK_MINUSEQ, + TK_STAREQ, + TK_SLASHEQ, + TK_PERCENTEQ, + TK_SHLEQ, + TK_SHREQ, + TK_BITWISEOREQ, + TK_BITWISEXOREQ, + TK_BITWISEANDEQ, + TK_SEMICOLON, + TK_WHITESPACE, + TK_LINE_COMMENT, + TK_BLOCK_COMMENT, + TK_INVALID, + TK_NONE, + }; + + Token() {} + Token(Kind kind, int32_t offset, int32_t length) + : fKind(kind), fOffset(offset), fLength(length) {} + + Kind fKind = Kind::TK_NONE; + int32_t fOffset = -1; + int32_t fLength = -1; +}; + +class Lexer { +public: + void start(std::string_view text) { + fText = text; + fOffset = 0; + } + + Token next(); + + struct Checkpoint { + int32_t fOffset; + }; + + Checkpoint getCheckpoint() const { return {fOffset}; } + + void rewindToCheckpoint(Checkpoint checkpoint) { fOffset = checkpoint.fOffset; } + +private: + std::string_view fText; + int32_t fOffset; +}; + +} // namespace SkSL +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLMangler.cpp b/gfx/skia/skia/src/sksl/SkSLMangler.cpp new file mode 100644 index 0000000000..650837d7aa --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMangler.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLMangler.h" + +#include "include/core/SkString.h" +#include "include/core/SkTypes.h" +#include "src/base/SkStringView.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#include <algorithm> +#include <cstring> +#include <ctype.h> + +namespace SkSL { + +std::string Mangler::uniqueName(std::string_view baseName, SymbolTable* symbolTable) { + SkASSERT(symbolTable); + + // Private names might begin with a $. Strip that off. + if (skstd::starts_with(baseName, '$')) { + baseName.remove_prefix(1); + } + + // The inliner runs more than once, so the base name might already have been mangled and have a + // prefix like "_123_x". Let's strip that prefix off to make the generated code easier to read. + if (skstd::starts_with(baseName, '_')) { + // Determine if we have a string of digits. + int offset = 1; + while (isdigit(baseName[offset])) { + ++offset; + } + // If we found digits, another underscore, and anything else, that's the mangler prefix. + // Strip it off. + if (offset > 1 && baseName[offset] == '_' && baseName[offset + 1] != '\0') { + baseName.remove_prefix(offset + 1); + } else { + // This name doesn't contain a mangler prefix, but it does start with an underscore. + // OpenGL disallows two consecutive underscores anywhere in the string, and we'll be + // adding one as part of the mangler prefix, so strip the leading underscore. + baseName.remove_prefix(1); + } + } + + // Append a unique numeric prefix to avoid name overlap. Check the symbol table to make sure + // we're not reusing an existing name. (Note that within a single compilation pass, this check + // isn't fully comprehensive, as code isn't always generated in top-to-bottom order.) + + // This code is a performance hotspot. Assemble the string manually to save a few cycles. + char uniqueName[256]; + uniqueName[0] = '_'; + char* uniqueNameEnd = uniqueName + std::size(uniqueName); + for (;;) { + // _123 + char* endPtr = SkStrAppendS32(uniqueName + 1, fCounter++); + + // _123_ + *endPtr++ = '_'; + + // _123_baseNameTruncatedToFit (no null terminator, because string_view doesn't require one) + int baseNameCopyLength = std::min<int>(baseName.size(), uniqueNameEnd - endPtr); + memcpy(endPtr, baseName.data(), baseNameCopyLength); + endPtr += baseNameCopyLength; + + std::string_view uniqueNameView(uniqueName, endPtr - uniqueName); + if (symbolTable->find(uniqueNameView) == nullptr) { + return std::string(uniqueNameView); + } + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLMangler.h b/gfx/skia/skia/src/sksl/SkSLMangler.h new file mode 100644 index 0000000000..8c0dd5e6e0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMangler.h @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_MANGLER +#define SKSL_MANGLER + +#include <string> +#include <string_view> + +namespace SkSL { + +class SymbolTable; + +class Mangler { +public: + /** + * Mangles baseName to create a name that is unique within symbolTable. + */ + std::string uniqueName(std::string_view baseName, SymbolTable* symbolTable); + + void reset() { + fCounter = 0; + } + +private: + int fCounter = 0; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h b/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h new file mode 100644 index 0000000000..8389c00346 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMemoryLayout.h @@ -0,0 +1,211 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKIASL_MEMORYLAYOUT +#define SKIASL_MEMORYLAYOUT + +#include <algorithm> + +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +class MemoryLayout { +public: + enum class Standard { + // GLSL std140 layout as described in OpenGL Spec v4.5, 7.6.2.2. + k140, + + // GLSL std430 layout. This layout is like std140 but with optimizations. This layout can + // ONLY be used with shader storage blocks. + k430, + + // MSL memory layout. + kMetal, + + // WebGPU Shading Language buffer layout constraints for the uniform address space. + kWGSLUniform, + + // WebGPU Shading Language buffer layout constraints for the storage address space. + kWGSLStorage, + }; + + MemoryLayout(Standard std) + : fStd(std) {} + + bool isWGSL() const { return fStd == Standard::kWGSLUniform || fStd == Standard::kWGSLStorage; } + + bool isMetal() const { return fStd == Standard::kMetal; } + + /** + * WGSL and std140 require various types of variables (structs, arrays, and matrices) in the + * uniform address space to be rounded up to the nearest multiple of 16. This function performs + * the rounding depending on the given `type` and the current memory layout standard. + * + * (For WGSL, see https://www.w3.org/TR/WGSL/#address-space-layout-constraints). + */ + size_t roundUpIfNeeded(size_t raw, Type::TypeKind type) const { + if (fStd == Standard::k140) { + return roundUp16(raw); + } + // WGSL uniform matrix layout is simply the alignment of the matrix columns and + // doesn't have a 16-byte multiple alignment constraint. + if (fStd == Standard::kWGSLUniform && type != Type::TypeKind::kMatrix) { + return roundUp16(raw); + } + return raw; + } + + /** + * Rounds up the integer `n` to the smallest multiple of 16 greater than `n`. + */ + size_t roundUp16(size_t n) const { return (n + 15) & ~15; } + + /** + * Returns a type's required alignment when used as a standalone variable. + */ + size_t alignment(const Type& type) const { + // See OpenGL Spec 7.6.2.2 Standard Uniform Block Layout + switch (type.typeKind()) { + case Type::TypeKind::kScalar: + case Type::TypeKind::kAtomic: + return this->size(type); + case Type::TypeKind::kVector: + return GetVectorAlignment(this->size(type.componentType()), type.columns()); + case Type::TypeKind::kMatrix: + return this->roundUpIfNeeded( + GetVectorAlignment(this->size(type.componentType()), type.rows()), + type.typeKind()); + case Type::TypeKind::kArray: + return this->roundUpIfNeeded(this->alignment(type.componentType()), + type.typeKind()); + case Type::TypeKind::kStruct: { + size_t result = 0; + for (const auto& f : type.fields()) { + size_t alignment = this->alignment(*f.fType); + if (alignment > result) { + result = alignment; + } + } + return this->roundUpIfNeeded(result, type.typeKind()); + } + default: + SK_ABORT("cannot determine alignment of type %s", type.displayName().c_str()); + } + } + + /** + * For matrices and arrays, returns the number of bytes from the start of one entry (row, in + * the case of matrices) to the start of the next. + */ + size_t stride(const Type& type) const { + switch (type.typeKind()) { + case Type::TypeKind::kMatrix: + return this->alignment(type); + case Type::TypeKind::kArray: { + int stride = this->size(type.componentType()); + if (stride > 0) { + int align = this->alignment(type.componentType()); + stride += align - 1; + stride -= stride % align; + stride = this->roundUpIfNeeded(stride, type.typeKind()); + } + return stride; + } + default: + SK_ABORT("type does not have a stride"); + } + } + + /** + * Returns the size of a type in bytes. Returns 0 if the given type is not supported. + */ + size_t size(const Type& type) const { + switch (type.typeKind()) { + case Type::TypeKind::kScalar: + if (type.isBoolean()) { + if (this->isWGSL()) { + return 0; + } + return 1; + } + if ((this->isMetal() || this->isWGSL()) && !type.highPrecision() && + type.isNumber()) { + return 2; + } + return 4; + case Type::TypeKind::kAtomic: + // Our atomic types (currently atomicUint) always occupy 4 bytes. + return 4; + case Type::TypeKind::kVector: + if (this->isMetal() && type.columns() == 3) { + return 4 * this->size(type.componentType()); + } + return type.columns() * this->size(type.componentType()); + case Type::TypeKind::kMatrix: // fall through + case Type::TypeKind::kArray: + return type.isUnsizedArray() ? 0 : (type.columns() * this->stride(type)); + case Type::TypeKind::kStruct: { + size_t total = 0; + for (const auto& f : type.fields()) { + size_t alignment = this->alignment(*f.fType); + if (total % alignment != 0) { + total += alignment - total % alignment; + } + SkASSERT(total % alignment == 0); + total += this->size(*f.fType); + } + size_t alignment = this->alignment(type); + SkASSERT(!type.fields().size() || + (0 == alignment % this->alignment(*type.fields()[0].fType))); + return (total + alignment - 1) & ~(alignment - 1); + } + default: + SK_ABORT("cannot determine size of type %s", type.displayName().c_str()); + } + } + + /** + * Not all types are compatible with memory layout. + */ + size_t isSupported(const Type& type) const { + switch (type.typeKind()) { + case Type::TypeKind::kAtomic: + return true; + + case Type::TypeKind::kScalar: + // bool and short are not host-shareable in WGSL. + return !this->isWGSL() || + (!type.isBoolean() && (type.isFloat() || type.highPrecision())); + + case Type::TypeKind::kVector: + case Type::TypeKind::kMatrix: + case Type::TypeKind::kArray: + return this->isSupported(type.componentType()); + + case Type::TypeKind::kStruct: + return std::all_of( + type.fields().begin(), type.fields().end(), [this](const Type::Field& f) { + return this->isSupported(*f.fType); + }); + + default: + return false; + } + } + +private: + static size_t GetVectorAlignment(size_t componentSize, int columns) { + return componentSize * (columns + columns % 2); + } + + const Standard fStd; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLMemoryPool.h b/gfx/skia/skia/src/sksl/SkSLMemoryPool.h new file mode 100644 index 0000000000..0d16d84ac0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLMemoryPool.h @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_MEMORYPOOL +#define SKSL_MEMORYPOOL + +#include <memory> + +#include "include/core/SkTypes.h" + +#if defined(SK_GANESH) + +#include "src/gpu/ganesh/GrMemoryPool.h" + +namespace SkSL { +using MemoryPool = ::GrMemoryPool; +} + +#else + +// When Ganesh is disabled, GrMemoryPool is not linked in. We include a minimal class which mimics +// the GrMemoryPool interface but simply redirects to the system allocator. +namespace SkSL { + +class MemoryPool { +public: + static std::unique_ptr<MemoryPool> Make(size_t, size_t) { + return std::make_unique<MemoryPool>(); + } + void resetScratchSpace() {} + void reportLeaks() const {} + bool isEmpty() const { return true; } + void* allocate(size_t size) { return ::operator new(size); } + void release(void* p) { ::operator delete(p); } +}; + +} // namespace SkSL + +#endif // defined(SK_GANESH) +#endif // SKSL_MEMORYPOOL diff --git a/gfx/skia/skia/src/sksl/SkSLModifiersPool.h b/gfx/skia/skia/src/sksl/SkSLModifiersPool.h new file mode 100644 index 0000000000..e9b863c871 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLModifiersPool.h @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_MODIFIERSPOOL +#define SKSL_MODIFIERSPOOL + +#include "include/private/SkSLModifiers.h" + +#include <unordered_set> + +namespace SkSL { + +/** + * Deduplicates Modifiers objects and stores them in a shared pool. Modifiers are fairly heavy, and + * tend to be reused a lot, so deduplication can be a significant win. + */ +class ModifiersPool { +public: + const Modifiers* add(const Modifiers& modifiers) { + auto [iter, wasInserted] = fModifiersSet.insert(modifiers); + return &*iter; + } + + void clear() { + fModifiersSet.clear(); + } + +private: + std::unordered_set<Modifiers> fModifiersSet; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLModuleLoader.cpp b/gfx/skia/skia/src/sksl/SkSLModuleLoader.cpp new file mode 100644 index 0000000000..c164fc1fe6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLModuleLoader.cpp @@ -0,0 +1,444 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "src/sksl/SkSLModuleLoader.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLProgramKind.h" +#include "include/private/base/SkMutex.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <algorithm> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#if SKSL_STANDALONE + +#include "include/core/SkString.h" +#include "src/utils/SkOSPath.h" +#include "tools/SkGetExecutablePath.h" + + // In standalone mode, we load the original SkSL source files. GN is responsible for copying + // these files from src/sksl/ to the directory where the executable is located. + #include <fstream> + + static std::string load_module_file(const char* moduleFilename) { + std::string exePath = SkGetExecutablePath(); + SkString exeDir = SkOSPath::Dirname(exePath.c_str()); + SkString modulePath = SkOSPath::Join(exeDir.c_str(), moduleFilename); + std::ifstream in(std::string{modulePath.c_str()}); + std::string moduleSource{std::istreambuf_iterator<char>(in), + std::istreambuf_iterator<char>()}; + if (in.rdstate()) { + SK_ABORT("Error reading %s\n", modulePath.c_str()); + } + return moduleSource; + } + + #define MODULE_DATA(name) #name, load_module_file(#name ".sksl") + +#else + + // We include minified SkSL module code and pass it directly to the compiler. + #if defined(SK_ENABLE_OPTIMIZE_SIZE) || !defined(SK_DEBUG) + #include "src/sksl/generated/sksl_shared.minified.sksl" + #include "src/sksl/generated/sksl_compute.minified.sksl" + #include "src/sksl/generated/sksl_frag.minified.sksl" + #include "src/sksl/generated/sksl_gpu.minified.sksl" + #include "src/sksl/generated/sksl_public.minified.sksl" + #include "src/sksl/generated/sksl_rt_shader.minified.sksl" + #include "src/sksl/generated/sksl_vert.minified.sksl" + #if defined(SK_GRAPHITE) + #include "src/sksl/generated/sksl_graphite_frag.minified.sksl" + #include "src/sksl/generated/sksl_graphite_vert.minified.sksl" + #endif + #else + #include "src/sksl/generated/sksl_shared.unoptimized.sksl" + #include "src/sksl/generated/sksl_compute.unoptimized.sksl" + #include "src/sksl/generated/sksl_frag.unoptimized.sksl" + #include "src/sksl/generated/sksl_gpu.unoptimized.sksl" + #include "src/sksl/generated/sksl_public.unoptimized.sksl" + #include "src/sksl/generated/sksl_rt_shader.unoptimized.sksl" + #include "src/sksl/generated/sksl_vert.unoptimized.sksl" + #if defined(SK_GRAPHITE) + #include "src/sksl/generated/sksl_graphite_frag.unoptimized.sksl" + #include "src/sksl/generated/sksl_graphite_vert.unoptimized.sksl" + #endif + #endif + + #define MODULE_DATA(name) #name, std::string(SKSL_MINIFIED_##name) + +#endif + +namespace SkSL { + +#define TYPE(t) &BuiltinTypes::f ## t + +static constexpr BuiltinTypePtr kRootTypes[] = { + TYPE(Void), + + TYPE( Float), TYPE( Float2), TYPE( Float3), TYPE( Float4), + TYPE( Half), TYPE( Half2), TYPE( Half3), TYPE( Half4), + TYPE( Int), TYPE( Int2), TYPE( Int3), TYPE( Int4), + TYPE( UInt), TYPE( UInt2), TYPE( UInt3), TYPE( UInt4), + TYPE( Short), TYPE( Short2), TYPE( Short3), TYPE( Short4), + TYPE(UShort), TYPE(UShort2), TYPE(UShort3), TYPE(UShort4), + TYPE( Bool), TYPE( Bool2), TYPE( Bool3), TYPE( Bool4), + + TYPE(Float2x2), TYPE(Float2x3), TYPE(Float2x4), + TYPE(Float3x2), TYPE(Float3x3), TYPE(Float3x4), + TYPE(Float4x2), TYPE(Float4x3), TYPE(Float4x4), + + TYPE(Half2x2), TYPE(Half2x3), TYPE(Half2x4), + TYPE(Half3x2), TYPE(Half3x3), TYPE(Half3x4), + TYPE(Half4x2), TYPE(Half4x3), TYPE(Half4x4), + + TYPE(SquareMat), TYPE(SquareHMat), + TYPE(Mat), TYPE(HMat), + + // TODO(skia:12349): generic short/ushort + TYPE(GenType), TYPE(GenIType), TYPE(GenUType), + TYPE(GenHType), /* (GenSType) (GenUSType) */ + TYPE(GenBType), + TYPE(IntLiteral), + TYPE(FloatLiteral), + + TYPE(Vec), TYPE(IVec), TYPE(UVec), + TYPE(HVec), TYPE(SVec), TYPE(USVec), + TYPE(BVec), + + TYPE(ColorFilter), + TYPE(Shader), + TYPE(Blender), +}; + +static constexpr BuiltinTypePtr kPrivateTypes[] = { + TYPE(Sampler2D), TYPE(SamplerExternalOES), TYPE(Sampler2DRect), + + TYPE(SubpassInput), TYPE(SubpassInputMS), + + TYPE(Sampler), + TYPE(Texture2D), + TYPE(ReadWriteTexture2D), TYPE(ReadOnlyTexture2D), TYPE(WriteOnlyTexture2D), + TYPE(GenTexture2D), TYPE(ReadableTexture2D), TYPE(WritableTexture2D), + + TYPE(AtomicUInt), +}; + +#undef TYPE + +struct ModuleLoader::Impl { + Impl(); + + void makeRootSymbolTable(); + + // This mutex is taken when ModuleLoader::Get is called, and released when the returned + // ModuleLoader object falls out of scope. + SkMutex fMutex; + const BuiltinTypes fBuiltinTypes; + ModifiersPool fCoreModifiers; + + std::unique_ptr<const Module> fRootModule; + + std::unique_ptr<const Module> fSharedModule; // [Root] + Public intrinsics + std::unique_ptr<const Module> fGPUModule; // [Shared] + Non-public intrinsics/ + // helper functions + std::unique_ptr<const Module> fVertexModule; // [GPU] + Vertex stage decls + std::unique_ptr<const Module> fFragmentModule; // [GPU] + Fragment stage decls + std::unique_ptr<const Module> fComputeModule; // [GPU] + Compute stage decls + std::unique_ptr<const Module> fGraphiteVertexModule; // [Vert] + Graphite vertex helpers + std::unique_ptr<const Module> fGraphiteFragmentModule; // [Frag] + Graphite fragment helpers + + std::unique_ptr<const Module> fPublicModule; // [Shared] minus Private types + + // Runtime effect intrinsics + std::unique_ptr<const Module> fRuntimeShaderModule; // [Public] + Runtime shader decls +}; + +ModuleLoader ModuleLoader::Get() { + static ModuleLoader::Impl* sModuleLoaderImpl = new ModuleLoader::Impl; + return ModuleLoader(*sModuleLoaderImpl); +} + +ModuleLoader::ModuleLoader(ModuleLoader::Impl& m) : fModuleLoader(m) { + fModuleLoader.fMutex.acquire(); +} + +ModuleLoader::~ModuleLoader() { + fModuleLoader.fMutex.release(); +} + +void ModuleLoader::unloadModules() { + fModuleLoader.fSharedModule = nullptr; + fModuleLoader.fGPUModule = nullptr; + fModuleLoader.fVertexModule = nullptr; + fModuleLoader.fFragmentModule = nullptr; + fModuleLoader.fComputeModule = nullptr; + fModuleLoader.fGraphiteVertexModule = nullptr; + fModuleLoader.fGraphiteFragmentModule = nullptr; + fModuleLoader.fPublicModule = nullptr; + fModuleLoader.fRuntimeShaderModule = nullptr; +} + +ModuleLoader::Impl::Impl() { + this->makeRootSymbolTable(); +} + +static void add_compute_type_aliases(SkSL::SymbolTable* symbols, const SkSL::BuiltinTypes& types) { + // A `texture2D` in a compute shader should generally mean "read-write" texture access, not + // "sample" texture access. Remap the name `texture2D` to point to `readWriteTexture2D`. + symbols->inject(Type::MakeAliasType("texture2D", *types.fReadWriteTexture2D)); +} + +static std::unique_ptr<Module> compile_and_shrink(SkSL::Compiler* compiler, + ProgramKind kind, + const char* moduleName, + std::string moduleSource, + const Module* parent, + ModifiersPool& modifiersPool) { + std::unique_ptr<Module> m = compiler->compileModule(kind, + moduleName, + std::move(moduleSource), + parent, + modifiersPool, + /*shouldInline=*/true); + if (!m) { + SK_ABORT("Unable to load module %s", moduleName); + } + + // We can eliminate FunctionPrototypes without changing the meaning of the module; the function + // declaration is still safely in the symbol table. This only impacts our ability to recreate + // the input verbatim, which we don't care about at runtime. + m->fElements.erase(std::remove_if(m->fElements.begin(), m->fElements.end(), + [](const std::unique_ptr<ProgramElement>& element) { + switch (element->kind()) { + case ProgramElement::Kind::kFunction: + case ProgramElement::Kind::kGlobalVar: + case ProgramElement::Kind::kInterfaceBlock: + // We need to preserve these. + return false; + + case ProgramElement::Kind::kFunctionPrototype: + // These are already in the symbol table; the + // ProgramElement isn't needed anymore. + return true; + + default: + SkDEBUGFAILF("Unsupported element: %s\n", + element->description().c_str()); + return false; + } + }), + m->fElements.end()); + + m->fElements.shrink_to_fit(); + return m; +} + +const BuiltinTypes& ModuleLoader::builtinTypes() { + return fModuleLoader.fBuiltinTypes; +} + +ModifiersPool& ModuleLoader::coreModifiers() { + return fModuleLoader.fCoreModifiers; +} + +const Module* ModuleLoader::rootModule() { + return fModuleLoader.fRootModule.get(); +} + +void ModuleLoader::addPublicTypeAliases(const SkSL::Module* module) { + const SkSL::BuiltinTypes& types = this->builtinTypes(); + SymbolTable* symbols = module->fSymbols.get(); + + // Add some aliases to the runtime effect modules so that it's friendlier, and more like GLSL. + symbols->addWithoutOwnership(types.fVec2.get()); + symbols->addWithoutOwnership(types.fVec3.get()); + symbols->addWithoutOwnership(types.fVec4.get()); + + symbols->addWithoutOwnership(types.fIVec2.get()); + symbols->addWithoutOwnership(types.fIVec3.get()); + symbols->addWithoutOwnership(types.fIVec4.get()); + + symbols->addWithoutOwnership(types.fBVec2.get()); + symbols->addWithoutOwnership(types.fBVec3.get()); + symbols->addWithoutOwnership(types.fBVec4.get()); + + symbols->addWithoutOwnership(types.fMat2.get()); + symbols->addWithoutOwnership(types.fMat3.get()); + symbols->addWithoutOwnership(types.fMat4.get()); + + symbols->addWithoutOwnership(types.fMat2x2.get()); + symbols->addWithoutOwnership(types.fMat2x3.get()); + symbols->addWithoutOwnership(types.fMat2x4.get()); + symbols->addWithoutOwnership(types.fMat3x2.get()); + symbols->addWithoutOwnership(types.fMat3x3.get()); + symbols->addWithoutOwnership(types.fMat3x4.get()); + symbols->addWithoutOwnership(types.fMat4x2.get()); + symbols->addWithoutOwnership(types.fMat4x3.get()); + symbols->addWithoutOwnership(types.fMat4x4.get()); + + // Hide all the private symbols by aliasing them all to "invalid". This will prevent code from + // using built-in names like `sampler2D` as variable names. + for (BuiltinTypePtr privateType : kPrivateTypes) { + symbols->inject(Type::MakeAliasType((types.*privateType)->name(), *types.fInvalid)); + } +} + +const Module* ModuleLoader::loadPublicModule(SkSL::Compiler* compiler) { + if (!fModuleLoader.fPublicModule) { + const Module* sharedModule = this->loadSharedModule(compiler); + fModuleLoader.fPublicModule = compile_and_shrink(compiler, + ProgramKind::kFragment, + MODULE_DATA(sksl_public), + sharedModule, + this->coreModifiers()); + this->addPublicTypeAliases(fModuleLoader.fPublicModule.get()); + } + return fModuleLoader.fPublicModule.get(); +} + +const Module* ModuleLoader::loadPrivateRTShaderModule(SkSL::Compiler* compiler) { + if (!fModuleLoader.fRuntimeShaderModule) { + const Module* publicModule = this->loadPublicModule(compiler); + fModuleLoader.fRuntimeShaderModule = compile_and_shrink(compiler, + ProgramKind::kFragment, + MODULE_DATA(sksl_rt_shader), + publicModule, + this->coreModifiers()); + } + return fModuleLoader.fRuntimeShaderModule.get(); +} + +const Module* ModuleLoader::loadSharedModule(SkSL::Compiler* compiler) { + if (!fModuleLoader.fSharedModule) { + const Module* rootModule = this->rootModule(); + fModuleLoader.fSharedModule = compile_and_shrink(compiler, + ProgramKind::kFragment, + MODULE_DATA(sksl_shared), + rootModule, + this->coreModifiers()); + } + return fModuleLoader.fSharedModule.get(); +} + +const Module* ModuleLoader::loadGPUModule(SkSL::Compiler* compiler) { + if (!fModuleLoader.fGPUModule) { + const Module* sharedModule = this->loadSharedModule(compiler); + fModuleLoader.fGPUModule = compile_and_shrink(compiler, + ProgramKind::kFragment, + MODULE_DATA(sksl_gpu), + sharedModule, + this->coreModifiers()); + } + return fModuleLoader.fGPUModule.get(); +} + +const Module* ModuleLoader::loadFragmentModule(SkSL::Compiler* compiler) { + if (!fModuleLoader.fFragmentModule) { + const Module* gpuModule = this->loadGPUModule(compiler); + fModuleLoader.fFragmentModule = compile_and_shrink(compiler, + ProgramKind::kFragment, + MODULE_DATA(sksl_frag), + gpuModule, + this->coreModifiers()); + } + return fModuleLoader.fFragmentModule.get(); +} + +const Module* ModuleLoader::loadVertexModule(SkSL::Compiler* compiler) { + if (!fModuleLoader.fVertexModule) { + const Module* gpuModule = this->loadGPUModule(compiler); + fModuleLoader.fVertexModule = compile_and_shrink(compiler, + ProgramKind::kVertex, + MODULE_DATA(sksl_vert), + gpuModule, + this->coreModifiers()); + } + return fModuleLoader.fVertexModule.get(); +} + +const Module* ModuleLoader::loadComputeModule(SkSL::Compiler* compiler) { + if (!fModuleLoader.fComputeModule) { + const Module* gpuModule = this->loadGPUModule(compiler); + fModuleLoader.fComputeModule = compile_and_shrink(compiler, + ProgramKind::kCompute, + MODULE_DATA(sksl_compute), + gpuModule, + this->coreModifiers()); + add_compute_type_aliases(fModuleLoader.fComputeModule->fSymbols.get(), + this->builtinTypes()); + } + return fModuleLoader.fComputeModule.get(); +} + +const Module* ModuleLoader::loadGraphiteFragmentModule(SkSL::Compiler* compiler) { +#if defined(SK_GRAPHITE) + if (!fModuleLoader.fGraphiteFragmentModule) { + const Module* fragmentModule = this->loadFragmentModule(compiler); + fModuleLoader.fGraphiteFragmentModule = compile_and_shrink(compiler, + ProgramKind::kGraphiteFragment, + MODULE_DATA(sksl_graphite_frag), + fragmentModule, + this->coreModifiers()); + } + return fModuleLoader.fGraphiteFragmentModule.get(); +#else + return this->loadFragmentModule(compiler); +#endif +} + +const Module* ModuleLoader::loadGraphiteVertexModule(SkSL::Compiler* compiler) { +#if defined(SK_GRAPHITE) + if (!fModuleLoader.fGraphiteVertexModule) { + const Module* vertexModule = this->loadVertexModule(compiler); + fModuleLoader.fGraphiteVertexModule = compile_and_shrink(compiler, + ProgramKind::kGraphiteVertex, + MODULE_DATA(sksl_graphite_vert), + vertexModule, + this->coreModifiers()); + } + return fModuleLoader.fGraphiteVertexModule.get(); +#else + return this->loadVertexModule(compiler); +#endif +} + +void ModuleLoader::Impl::makeRootSymbolTable() { + auto rootModule = std::make_unique<Module>(); + rootModule->fSymbols = std::make_shared<SymbolTable>(/*builtin=*/true); + + for (BuiltinTypePtr rootType : kRootTypes) { + rootModule->fSymbols->addWithoutOwnership((fBuiltinTypes.*rootType).get()); + } + + for (BuiltinTypePtr privateType : kPrivateTypes) { + rootModule->fSymbols->addWithoutOwnership((fBuiltinTypes.*privateType).get()); + } + + // sk_Caps is "builtin", but all references to it are resolved to Settings, so we don't need to + // treat it as builtin (ie, no need to clone it into the Program). + rootModule->fSymbols->add(std::make_unique<Variable>(/*pos=*/Position(), + /*modifiersPosition=*/Position(), + fCoreModifiers.add(Modifiers{}), + "sk_Caps", + fBuiltinTypes.fSkCaps.get(), + /*builtin=*/false, + Variable::Storage::kGlobal)); + fRootModule = std::move(rootModule); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLModuleLoader.h b/gfx/skia/skia/src/sksl/SkSLModuleLoader.h new file mode 100644 index 0000000000..bb300e2f7a --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLModuleLoader.h @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_MODULELOADER +#define SKSL_MODULELOADER + +#include "src/sksl/SkSLBuiltinTypes.h" +#include <memory> + +namespace SkSL { + +class Compiler; +class ModifiersPool; +struct Module; +class Type; + +using BuiltinTypePtr = const std::unique_ptr<Type> BuiltinTypes::*; + +class ModuleLoader { +private: + struct Impl; + Impl& fModuleLoader; + +public: + ModuleLoader(ModuleLoader::Impl&); + ~ModuleLoader(); + + // Acquires a mutex-locked reference to the singleton ModuleLoader. When the ModuleLoader is + // allowed to fall out of scope, the mutex will be released. + static ModuleLoader Get(); + + // The built-in types and root module are universal, immutable, and shared by every Compiler. + // They are created when the ModuleLoader is instantiated and never change. + const BuiltinTypes& builtinTypes(); + const Module* rootModule(); + + // This ModifiersPool is shared by every built-in module. + ModifiersPool& coreModifiers(); + + // These modules are loaded on demand; once loaded, they are kept for the lifetime of the + // process. + const Module* loadSharedModule(SkSL::Compiler* compiler); + const Module* loadGPUModule(SkSL::Compiler* compiler); + const Module* loadVertexModule(SkSL::Compiler* compiler); + const Module* loadFragmentModule(SkSL::Compiler* compiler); + const Module* loadComputeModule(SkSL::Compiler* compiler); + const Module* loadGraphiteVertexModule(SkSL::Compiler* compiler); + const Module* loadGraphiteFragmentModule(SkSL::Compiler* compiler); + + const Module* loadPublicModule(SkSL::Compiler* compiler); + const Module* loadPrivateRTShaderModule(SkSL::Compiler* compiler); + + // This updates an existing Module's symbol table to match Runtime Effect rules. GLSL types like + // `vec4` are added; SkSL private types like `sampler2D` are replaced with an invalid type. + void addPublicTypeAliases(const SkSL::Module* module); + + // This unloads every module. It's useful primarily for benchmarking purposes. + void unloadModules(); +}; + +} // namespace SkSL + +#endif // SKSL_MODULELOADER diff --git a/gfx/skia/skia/src/sksl/SkSLOperator.cpp b/gfx/skia/skia/src/sksl/SkSLOperator.cpp new file mode 100644 index 0000000000..6c9ddc92b4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLOperator.cpp @@ -0,0 +1,384 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/SkSLOperator.h" + +#include "include/core/SkTypes.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLType.h" + +#include <memory> + +namespace SkSL { + +OperatorPrecedence Operator::getBinaryPrecedence() const { + switch (this->kind()) { + case Kind::STAR: // fall through + case Kind::SLASH: // fall through + case Kind::PERCENT: return OperatorPrecedence::kMultiplicative; + case Kind::PLUS: // fall through + case Kind::MINUS: return OperatorPrecedence::kAdditive; + case Kind::SHL: // fall through + case Kind::SHR: return OperatorPrecedence::kShift; + case Kind::LT: // fall through + case Kind::GT: // fall through + case Kind::LTEQ: // fall through + case Kind::GTEQ: return OperatorPrecedence::kRelational; + case Kind::EQEQ: // fall through + case Kind::NEQ: return OperatorPrecedence::kEquality; + case Kind::BITWISEAND: return OperatorPrecedence::kBitwiseAnd; + case Kind::BITWISEXOR: return OperatorPrecedence::kBitwiseXor; + case Kind::BITWISEOR: return OperatorPrecedence::kBitwiseOr; + case Kind::LOGICALAND: return OperatorPrecedence::kLogicalAnd; + case Kind::LOGICALXOR: return OperatorPrecedence::kLogicalXor; + case Kind::LOGICALOR: return OperatorPrecedence::kLogicalOr; + case Kind::EQ: // fall through + case Kind::PLUSEQ: // fall through + case Kind::MINUSEQ: // fall through + case Kind::STAREQ: // fall through + case Kind::SLASHEQ: // fall through + case Kind::PERCENTEQ: // fall through + case Kind::SHLEQ: // fall through + case Kind::SHREQ: // fall through + case Kind::BITWISEANDEQ: // fall through + case Kind::BITWISEXOREQ: // fall through + case Kind::BITWISEOREQ: return OperatorPrecedence::kAssignment; + case Kind::COMMA: return OperatorPrecedence::kSequence; + default: SK_ABORT("unsupported binary operator"); + } +} + +const char* Operator::operatorName() const { + switch (this->kind()) { + case Kind::PLUS: return " + "; + case Kind::MINUS: return " - "; + case Kind::STAR: return " * "; + case Kind::SLASH: return " / "; + case Kind::PERCENT: return " % "; + case Kind::SHL: return " << "; + case Kind::SHR: return " >> "; + case Kind::LOGICALNOT: return "!"; + case Kind::LOGICALAND: return " && "; + case Kind::LOGICALOR: return " || "; + case Kind::LOGICALXOR: return " ^^ "; + case Kind::BITWISENOT: return "~"; + case Kind::BITWISEAND: return " & "; + case Kind::BITWISEOR: return " | "; + case Kind::BITWISEXOR: return " ^ "; + case Kind::EQ: return " = "; + case Kind::EQEQ: return " == "; + case Kind::NEQ: return " != "; + case Kind::LT: return " < "; + case Kind::GT: return " > "; + case Kind::LTEQ: return " <= "; + case Kind::GTEQ: return " >= "; + case Kind::PLUSEQ: return " += "; + case Kind::MINUSEQ: return " -= "; + case Kind::STAREQ: return " *= "; + case Kind::SLASHEQ: return " /= "; + case Kind::PERCENTEQ: return " %= "; + case Kind::SHLEQ: return " <<= "; + case Kind::SHREQ: return " >>= "; + case Kind::BITWISEANDEQ: return " &= "; + case Kind::BITWISEOREQ: return " |= "; + case Kind::BITWISEXOREQ: return " ^= "; + case Kind::PLUSPLUS: return "++"; + case Kind::MINUSMINUS: return "--"; + case Kind::COMMA: return ", "; + default: SkUNREACHABLE; + } +} + +std::string_view Operator::tightOperatorName() const { + std::string_view name = this->operatorName(); + if (skstd::starts_with(name, ' ')) { + name.remove_prefix(1); + } + if (skstd::ends_with(name, ' ')) { + name.remove_suffix(1); + } + return name; +} + +bool Operator::isAssignment() const { + switch (this->kind()) { + case Kind::EQ: // fall through + case Kind::PLUSEQ: // fall through + case Kind::MINUSEQ: // fall through + case Kind::STAREQ: // fall through + case Kind::SLASHEQ: // fall through + case Kind::PERCENTEQ: // fall through + case Kind::SHLEQ: // fall through + case Kind::SHREQ: // fall through + case Kind::BITWISEOREQ: // fall through + case Kind::BITWISEXOREQ: // fall through + case Kind::BITWISEANDEQ: + return true; + default: + return false; + } +} + +Operator Operator::removeAssignment() const { + switch (this->kind()) { + case Kind::PLUSEQ: return Kind::PLUS; + case Kind::MINUSEQ: return Kind::MINUS; + case Kind::STAREQ: return Kind::STAR; + case Kind::SLASHEQ: return Kind::SLASH; + case Kind::PERCENTEQ: return Kind::PERCENT; + case Kind::SHLEQ: return Kind::SHL; + case Kind::SHREQ: return Kind::SHR; + case Kind::BITWISEOREQ: return Kind::BITWISEOR; + case Kind::BITWISEXOREQ: return Kind::BITWISEXOR; + case Kind::BITWISEANDEQ: return Kind::BITWISEAND; + default: return *this; + } +} + +bool Operator::isRelational() const { + switch (this->kind()) { + case Kind::LT: + case Kind::GT: + case Kind::LTEQ: + case Kind::GTEQ: + return true; + default: + return false; + } +} + +bool Operator::isOnlyValidForIntegralTypes() const { + switch (this->kind()) { + case Kind::SHL: + case Kind::SHR: + case Kind::BITWISEAND: + case Kind::BITWISEOR: + case Kind::BITWISEXOR: + case Kind::PERCENT: + case Kind::SHLEQ: + case Kind::SHREQ: + case Kind::BITWISEANDEQ: + case Kind::BITWISEOREQ: + case Kind::BITWISEXOREQ: + case Kind::PERCENTEQ: + return true; + default: + return false; + } +} + +bool Operator::isValidForMatrixOrVector() const { + switch (this->kind()) { + case Kind::PLUS: + case Kind::MINUS: + case Kind::STAR: + case Kind::SLASH: + case Kind::PERCENT: + case Kind::SHL: + case Kind::SHR: + case Kind::BITWISEAND: + case Kind::BITWISEOR: + case Kind::BITWISEXOR: + case Kind::PLUSEQ: + case Kind::MINUSEQ: + case Kind::STAREQ: + case Kind::SLASHEQ: + case Kind::PERCENTEQ: + case Kind::SHLEQ: + case Kind::SHREQ: + case Kind::BITWISEANDEQ: + case Kind::BITWISEOREQ: + case Kind::BITWISEXOREQ: + return true; + default: + return false; + } +} + +bool Operator::isMatrixMultiply(const Type& left, const Type& right) const { + if (this->kind() != Kind::STAR && this->kind() != Kind::STAREQ) { + return false; + } + if (left.isMatrix()) { + return right.isMatrix() || right.isVector(); + } + return left.isVector() && right.isMatrix(); +} + +/** + * Determines the operand and result types of a binary expression. Returns true if the expression is + * legal, false otherwise. If false, the values of the out parameters are undefined. + */ +bool Operator::determineBinaryType(const Context& context, + const Type& left, + const Type& right, + const Type** outLeftType, + const Type** outRightType, + const Type** outResultType) const { + const bool allowNarrowing = context.fConfig->fSettings.fAllowNarrowingConversions; + switch (this->kind()) { + case Kind::EQ: // left = right + if (left.isVoid()) { + return false; + } + *outLeftType = &left; + *outRightType = &left; + *outResultType = &left; + return right.canCoerceTo(left, allowNarrowing); + + case Kind::EQEQ: // left == right + case Kind::NEQ: { // left != right + if (left.isVoid() || left.isOpaque()) { + return false; + } + CoercionCost rightToLeft = right.coercionCost(left), + leftToRight = left.coercionCost(right); + if (rightToLeft < leftToRight) { + if (rightToLeft.isPossible(allowNarrowing)) { + *outLeftType = &left; + *outRightType = &left; + *outResultType = context.fTypes.fBool.get(); + return true; + } + } else { + if (leftToRight.isPossible(allowNarrowing)) { + *outLeftType = &right; + *outRightType = &right; + *outResultType = context.fTypes.fBool.get(); + return true; + } + } + return false; + } + case Kind::LOGICALOR: // left || right + case Kind::LOGICALAND: // left && right + case Kind::LOGICALXOR: // left ^^ right + *outLeftType = context.fTypes.fBool.get(); + *outRightType = context.fTypes.fBool.get(); + *outResultType = context.fTypes.fBool.get(); + return left.canCoerceTo(*context.fTypes.fBool, allowNarrowing) && + right.canCoerceTo(*context.fTypes.fBool, allowNarrowing); + + case Operator::Kind::COMMA: // left, right + if (left.isOpaque() || right.isOpaque()) { + return false; + } + *outLeftType = &left; + *outRightType = &right; + *outResultType = &right; + return true; + + default: + break; + } + + // Boolean types only support the operators listed above (, = == != || && ^^). + // If we've gotten this far with a boolean, we have an unsupported operator. + const Type& leftComponentType = left.componentType(); + const Type& rightComponentType = right.componentType(); + if (leftComponentType.isBoolean() || rightComponentType.isBoolean()) { + return false; + } + + bool isAssignment = this->isAssignment(); + if (this->isMatrixMultiply(left, right)) { // left * right + // Determine final component type. + if (!this->determineBinaryType(context, left.componentType(), right.componentType(), + outLeftType, outRightType, outResultType)) { + return false; + } + // Convert component type to compound. + *outLeftType = &(*outResultType)->toCompound(context, left.columns(), left.rows()); + *outRightType = &(*outResultType)->toCompound(context, right.columns(), right.rows()); + int leftColumns = left.columns(), leftRows = left.rows(); + int rightColumns = right.columns(), rightRows = right.rows(); + if (right.isVector()) { + // `matrix * vector` treats the vector as a column vector; we need to transpose it. + std::swap(rightColumns, rightRows); + SkASSERT(rightColumns == 1); + } + if (rightColumns > 1) { + *outResultType = &(*outResultType)->toCompound(context, rightColumns, leftRows); + } else { + // The result was a column vector. Transpose it back to a row. + *outResultType = &(*outResultType)->toCompound(context, leftRows, rightColumns); + } + if (isAssignment && ((*outResultType)->columns() != leftColumns || + (*outResultType)->rows() != leftRows)) { + return false; + } + return leftColumns == rightRows; + } + + bool leftIsVectorOrMatrix = left.isVector() || left.isMatrix(); + bool validMatrixOrVectorOp = this->isValidForMatrixOrVector(); + + if (leftIsVectorOrMatrix && validMatrixOrVectorOp && right.isScalar()) { + // Determine final component type. + if (!this->determineBinaryType(context, left.componentType(), right, + outLeftType, outRightType, outResultType)) { + return false; + } + // Convert component type to compound. + *outLeftType = &(*outLeftType)->toCompound(context, left.columns(), left.rows()); + if (!this->isRelational()) { + *outResultType = &(*outResultType)->toCompound(context, left.columns(), left.rows()); + } + return true; + } + + bool rightIsVectorOrMatrix = right.isVector() || right.isMatrix(); + + if (!isAssignment && rightIsVectorOrMatrix && validMatrixOrVectorOp && left.isScalar()) { + // Determine final component type. + if (!this->determineBinaryType(context, left, right.componentType(), + outLeftType, outRightType, outResultType)) { + return false; + } + // Convert component type to compound. + *outRightType = &(*outRightType)->toCompound(context, right.columns(), right.rows()); + if (!this->isRelational()) { + *outResultType = &(*outResultType)->toCompound(context, right.columns(), right.rows()); + } + return true; + } + + CoercionCost rightToLeftCost = right.coercionCost(left); + CoercionCost leftToRightCost = isAssignment ? CoercionCost::Impossible() + : left.coercionCost(right); + + if ((left.isScalar() && right.isScalar()) || (leftIsVectorOrMatrix && validMatrixOrVectorOp)) { + if (this->isOnlyValidForIntegralTypes()) { + if (!leftComponentType.isInteger() || !rightComponentType.isInteger()) { + return false; + } + } + if (rightToLeftCost.isPossible(allowNarrowing) && rightToLeftCost < leftToRightCost) { + // Right-to-Left conversion is possible and cheaper + *outLeftType = &left; + *outRightType = &left; + *outResultType = &left; + } else if (leftToRightCost.isPossible(allowNarrowing)) { + // Left-to-Right conversion is possible (and at least as cheap as Right-to-Left) + *outLeftType = &right; + *outRightType = &right; + *outResultType = &right; + } else { + return false; + } + if (this->isRelational()) { + *outResultType = context.fTypes.fBool.get(); + } + return true; + } + return false; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp b/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp new file mode 100644 index 0000000000..7972c9fd19 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLOutputStream.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLOutputStream.h" + +#include <stdio.h> +#include <memory> + +namespace SkSL { + +void OutputStream::writeString(const std::string& s) { + this->write(s.c_str(), s.size()); +} + +void OutputStream::printf(const char format[], ...) { + va_list args; + va_start(args, format); + this->appendVAList(format, args); + va_end(args); +} + +void OutputStream::appendVAList(const char format[], va_list args) { + char buffer[kBufferSize]; + va_list copy; + va_copy(copy, args); + int length = vsnprintf(buffer, kBufferSize, format, args); + if (length > (int) kBufferSize) { + std::unique_ptr<char[]> bigBuffer(new char[length + 1]); + vsnprintf(bigBuffer.get(), length + 1, format, copy); + this->write(bigBuffer.get(), length); + } else { + this->write(buffer, length); + } + va_end(copy); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLOutputStream.h b/gfx/skia/skia/src/sksl/SkSLOutputStream.h new file mode 100644 index 0000000000..542ffd6f90 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLOutputStream.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_OUTPUTSTREAM +#define SKSL_OUTPUTSTREAM + +#include "include/core/SkTypes.h" + +#include <cstdarg> +#include <cstddef> +#include <cstdint> +#include <string> + +namespace SkSL { + +class OutputStream { +public: + virtual bool isValid() const { + return true; + } + + virtual void write8(uint8_t b) = 0; + + void write16(uint16_t i) { + this->write8((uint8_t) i); + this->write8((uint8_t) (i >> 8)); + } + + void write32(uint32_t i) { + this->write8((uint8_t) i); + this->write8((uint8_t) (i >> 8)); + this->write8((uint8_t) (i >> 16)); + this->write8((uint8_t) (i >> 24)); + } + + virtual void writeText(const char* s) = 0; + + virtual void write(const void* s, size_t size) = 0; + + void writeString(const std::string& s); + + void printf(const char format[], ...) SK_PRINTF_LIKE(2, 3); + + void appendVAList(const char format[], va_list args) SK_PRINTF_LIKE(2, 0); + + virtual ~OutputStream() {} + +private: + static const int kBufferSize = 1024; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLParser.cpp b/gfx/skia/skia/src/sksl/SkSLParser.cpp new file mode 100644 index 0000000000..d63f930a63 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLParser.cpp @@ -0,0 +1,2248 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLParser.h" + +#include "include/core/SkSpan.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLString.h" +#include "include/sksl/DSLBlock.h" +#include "include/sksl/DSLCase.h" +#include "include/sksl/DSLFunction.h" +#include "include/sksl/DSLVar.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLVersion.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/dsl/priv/DSLWriter.h" +#include "src/sksl/dsl/priv/DSL_priv.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <algorithm> +#include <climits> +#include <initializer_list> +#include <memory> +#include <utility> +#include <vector> + +using namespace SkSL::dsl; + +namespace SkSL { + +static constexpr int kMaxParseDepth = 50; + +static int parse_modifier_token(Token::Kind token) { + switch (token) { + case Token::Kind::TK_UNIFORM: return Modifiers::kUniform_Flag; + case Token::Kind::TK_CONST: return Modifiers::kConst_Flag; + case Token::Kind::TK_IN: return Modifiers::kIn_Flag; + case Token::Kind::TK_OUT: return Modifiers::kOut_Flag; + case Token::Kind::TK_INOUT: return Modifiers::kIn_Flag | Modifiers::kOut_Flag; + case Token::Kind::TK_FLAT: return Modifiers::kFlat_Flag; + case Token::Kind::TK_NOPERSPECTIVE: return Modifiers::kNoPerspective_Flag; + case Token::Kind::TK_PURE: return Modifiers::kPure_Flag; + case Token::Kind::TK_INLINE: return Modifiers::kInline_Flag; + case Token::Kind::TK_NOINLINE: return Modifiers::kNoInline_Flag; + case Token::Kind::TK_HIGHP: return Modifiers::kHighp_Flag; + case Token::Kind::TK_MEDIUMP: return Modifiers::kMediump_Flag; + case Token::Kind::TK_LOWP: return Modifiers::kLowp_Flag; + case Token::Kind::TK_EXPORT: return Modifiers::kExport_Flag; + case Token::Kind::TK_ES3: return Modifiers::kES3_Flag; + case Token::Kind::TK_WORKGROUP: return Modifiers::kWorkgroup_Flag; + case Token::Kind::TK_READONLY: return Modifiers::kReadOnly_Flag; + case Token::Kind::TK_WRITEONLY: return Modifiers::kWriteOnly_Flag; + case Token::Kind::TK_BUFFER: return Modifiers::kBuffer_Flag; + default: return 0; + } +} + +class Parser::AutoDepth { +public: + AutoDepth(Parser* p) + : fParser(p) + , fDepth(0) {} + + ~AutoDepth() { + fParser->fDepth -= fDepth; + } + + bool increase() { + ++fDepth; + ++fParser->fDepth; + if (fParser->fDepth > kMaxParseDepth) { + fParser->error(fParser->peek(), "exceeded max parse depth"); + fParser->fEncounteredFatalError = true; + return false; + } + return true; + } + +private: + Parser* fParser; + int fDepth; +}; + +class Parser::AutoSymbolTable { +public: + AutoSymbolTable(Parser* p) : fParser(p) { + SymbolTable::Push(&fParser->symbolTable()); + } + + ~AutoSymbolTable() { + SymbolTable::Pop(&fParser->symbolTable()); + } + +private: + Parser* fParser; +}; + +Parser::Parser(Compiler* compiler, + const ProgramSettings& settings, + ProgramKind kind, + std::string text) + : fCompiler(*compiler) + , fSettings(settings) + , fKind(kind) + , fText(std::make_unique<std::string>(std::move(text))) + , fPushback(Token::Kind::TK_NONE, /*offset=*/-1, /*length=*/-1) { + fLexer.start(*fText); +} + +std::shared_ptr<SymbolTable>& Parser::symbolTable() { + return fCompiler.symbolTable(); +} + +void Parser::addToSymbolTable(DSLVarBase& var, Position pos) { + if (SkSL::Variable* skslVar = DSLWriter::Var(var)) { + this->symbolTable()->addWithoutOwnership(skslVar); + } +} + +Token Parser::nextRawToken() { + Token token; + if (fPushback.fKind != Token::Kind::TK_NONE) { + // Retrieve the token from the pushback buffer. + token = fPushback; + fPushback.fKind = Token::Kind::TK_NONE; + } else { + // Fetch a token from the lexer. + token = fLexer.next(); + + // Some tokens are always invalid, so we detect and report them here. + switch (token.fKind) { + case Token::Kind::TK_PRIVATE_IDENTIFIER: + if (ProgramConfig::AllowsPrivateIdentifiers(fKind)) { + token.fKind = Token::Kind::TK_IDENTIFIER; + break; + } + [[fallthrough]]; + + case Token::Kind::TK_RESERVED: + this->error(token, "name '" + std::string(this->text(token)) + "' is reserved"); + token.fKind = Token::Kind::TK_IDENTIFIER; // reduces additional follow-up errors + break; + + case Token::Kind::TK_BAD_OCTAL: + this->error(token, "'" + std::string(this->text(token)) + + "' is not a valid octal number"); + break; + + default: + break; + } + } + + return token; +} + +static bool is_whitespace(Token::Kind kind) { + switch (kind) { + case Token::Kind::TK_WHITESPACE: + case Token::Kind::TK_LINE_COMMENT: + case Token::Kind::TK_BLOCK_COMMENT: + return true; + + default: + return false; + } +} + +bool Parser::expectNewline() { + Token token = this->nextRawToken(); + if (token.fKind == Token::Kind::TK_WHITESPACE) { + // The lexer doesn't distinguish newlines from other forms of whitespace, so we check + // for newlines by searching through the token text. + std::string_view tokenText = this->text(token); + if (tokenText.find_first_of('\r') != std::string_view::npos || + tokenText.find_first_of('\n') != std::string_view::npos) { + return true; + } + } + // We didn't find a newline. + this->pushback(token); + return false; +} + +Token Parser::nextToken() { + for (;;) { + Token token = this->nextRawToken(); + if (!is_whitespace(token.fKind)) { + return token; + } + } +} + +void Parser::pushback(Token t) { + SkASSERT(fPushback.fKind == Token::Kind::TK_NONE); + fPushback = std::move(t); +} + +Token Parser::peek() { + if (fPushback.fKind == Token::Kind::TK_NONE) { + fPushback = this->nextToken(); + } + return fPushback; +} + +bool Parser::checkNext(Token::Kind kind, Token* result) { + if (fPushback.fKind != Token::Kind::TK_NONE && fPushback.fKind != kind) { + return false; + } + Token next = this->nextToken(); + if (next.fKind == kind) { + if (result) { + *result = next; + } + return true; + } + this->pushback(std::move(next)); + return false; +} + +bool Parser::expect(Token::Kind kind, const char* expected, Token* result) { + Token next = this->nextToken(); + if (next.fKind == kind) { + if (result) { + *result = std::move(next); + } + return true; + } else { + this->error(next, "expected " + std::string(expected) + ", but found '" + + std::string(this->text(next)) + "'"); + this->fEncounteredFatalError = true; + return false; + } +} + +bool Parser::expectIdentifier(Token* result) { + if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", result)) { + return false; + } + if (this->symbolTable()->isBuiltinType(this->text(*result))) { + this->error(*result, "expected an identifier, but found type '" + + std::string(this->text(*result)) + "'"); + this->fEncounteredFatalError = true; + return false; + } + return true; +} + +bool Parser::checkIdentifier(Token* result) { + if (!this->checkNext(Token::Kind::TK_IDENTIFIER, result)) { + return false; + } + if (this->symbolTable()->isBuiltinType(this->text(*result))) { + this->pushback(std::move(*result)); + return false; + } + return true; +} + +std::string_view Parser::text(Token token) { + return std::string_view(fText->data() + token.fOffset, token.fLength); +} + +Position Parser::position(Token t) { + if (t.fOffset >= 0) { + return Position::Range(t.fOffset, t.fOffset + t.fLength); + } else { + return Position(); + } +} + +void Parser::error(Token token, std::string_view msg) { + this->error(this->position(token), msg); +} + +void Parser::error(Position position, std::string_view msg) { + GetErrorReporter().error(position, msg); +} + +Position Parser::rangeFrom(Position start) { + int offset = fPushback.fKind != Token::Kind::TK_NONE ? fPushback.fOffset + : fLexer.getCheckpoint().fOffset; + return Position::Range(start.startOffset(), offset); +} + +Position Parser::rangeFrom(Token start) { + return this->rangeFrom(this->position(start)); +} + +/* declaration* END_OF_FILE */ +std::unique_ptr<Program> Parser::program() { + ErrorReporter* errorReporter = &fCompiler.errorReporter(); + Start(&fCompiler, fKind, fSettings); + SetErrorReporter(errorReporter); + errorReporter->setSource(*fText); + this->declarations(); + std::unique_ptr<Program> result; + if (!GetErrorReporter().errorCount()) { + result = dsl::ReleaseProgram(std::move(fText)); + } + errorReporter->setSource(std::string_view()); + End(); + return result; +} + +std::unique_ptr<SkSL::Module> Parser::moduleInheritingFrom(const SkSL::Module* parent) { + ErrorReporter* errorReporter = &fCompiler.errorReporter(); + StartModule(&fCompiler, fKind, fSettings, parent); + SetErrorReporter(errorReporter); + errorReporter->setSource(*fText); + this->declarations(); + this->symbolTable()->takeOwnershipOfString(std::move(*fText)); + auto result = std::make_unique<SkSL::Module>(); + result->fParent = parent; + result->fSymbols = this->symbolTable(); + result->fElements = std::move(ThreadContext::ProgramElements()); + errorReporter->setSource(std::string_view()); + End(); + return result; +} + +void Parser::declarations() { + fEncounteredFatalError = false; + // Any #version directive must appear as the first thing in a file + if (this->peek().fKind == Token::Kind::TK_DIRECTIVE) { + this->directive(/*allowVersion=*/true); + } + bool done = false; + while (!done) { + switch (this->peek().fKind) { + case Token::Kind::TK_END_OF_FILE: + done = true; + break; + case Token::Kind::TK_DIRECTIVE: + this->directive(/*allowVersion=*/false); + break; + case Token::Kind::TK_INVALID: + this->error(this->peek(), "invalid token"); + this->nextToken(); + done = true; + break; + default: + this->declaration(); + done = fEncounteredFatalError; + break; + } + } +} + +/* DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER NEWLINE | + DIRECTIVE(#version) INTLITERAL NEWLINE */ +void Parser::directive(bool allowVersion) { + Token start; + if (!this->expect(Token::Kind::TK_DIRECTIVE, "a directive", &start)) { + return; + } + std::string_view text = this->text(start); + const bool allowExtensions = !ProgramConfig::IsRuntimeEffect(fKind); + if (text == "#extension" && allowExtensions) { + Token name; + if (!this->expectIdentifier(&name)) { + return; + } + if (!this->expect(Token::Kind::TK_COLON, "':'")) { + return; + } + Token behavior; + if (!this->expect(Token::Kind::TK_IDENTIFIER, "an identifier", &behavior)) { + return; + } + std::string_view behaviorText = this->text(behavior); + if (behaviorText != "disable") { + if (behaviorText == "require" || behaviorText == "enable" || behaviorText == "warn") { + // We don't currently do anything different between require, enable, and warn + dsl::AddExtension(this->text(name)); + } else { + this->error(behavior, "expected 'require', 'enable', 'warn', or 'disable'"); + } + } + + // We expect a newline after an #extension directive. + if (!this->expectNewline()) { + this->error(start, "invalid #extension directive"); + } + } else if (text == "#version") { + if (!allowVersion) { + this->error(start, "#version directive must appear before anything else"); + return; + } + SKSL_INT version; + if (!this->intLiteral(&version)) { + return; + } + switch (version) { + case 100: + ThreadContext::GetProgramConfig()->fRequiredSkSLVersion = Version::k100; + break; + case 300: + ThreadContext::GetProgramConfig()->fRequiredSkSLVersion = Version::k300; + break; + default: + this->error(start, "unsupported version number"); + return; + } + // We expect a newline after a #version directive. + if (!this->expectNewline()) { + this->error(start, "invalid #version directive"); + } + } else { + this->error(start, "unsupported directive '" + std::string(this->text(start)) + "'"); + } +} + +/* modifiers (structVarDeclaration | type IDENTIFIER ((LPAREN parameter (COMMA parameter)* RPAREN + (block | SEMICOLON)) | SEMICOLON) | interfaceBlock) */ +bool Parser::declaration() { + Token start = this->peek(); + if (start.fKind == Token::Kind::TK_SEMICOLON) { + this->nextToken(); + this->error(start, "expected a declaration, but found ';'"); + return false; + } + DSLModifiers modifiers = this->modifiers(); + Token lookahead = this->peek(); + if (lookahead.fKind == Token::Kind::TK_IDENTIFIER && + !this->symbolTable()->isType(this->text(lookahead))) { + // we have an identifier that's not a type, could be the start of an interface block + return this->interfaceBlock(modifiers); + } + if (lookahead.fKind == Token::Kind::TK_SEMICOLON) { + this->nextToken(); + Declare(modifiers, this->position(start)); + return true; + } + if (lookahead.fKind == Token::Kind::TK_STRUCT) { + this->structVarDeclaration(this->position(start), modifiers); + return true; + } + DSLType type = this->type(&modifiers); + if (!type.hasValue()) { + return false; + } + Token name; + if (!this->expectIdentifier(&name)) { + return false; + } + if (this->checkNext(Token::Kind::TK_LPAREN)) { + return this->functionDeclarationEnd(this->position(start), modifiers, type, name); + } else { + this->globalVarDeclarationEnd(this->position(start), modifiers, type, name); + return true; + } +} + +/* (RPAREN | VOID RPAREN | parameter (COMMA parameter)* RPAREN) (block | SEMICOLON) */ +bool Parser::functionDeclarationEnd(Position start, + DSLModifiers& modifiers, + DSLType type, + const Token& name) { + SkSTArray<8, DSLParameter> parameters; + Token lookahead = this->peek(); + if (lookahead.fKind == Token::Kind::TK_RPAREN) { + // `()` means no parameters at all. + } else if (lookahead.fKind == Token::Kind::TK_IDENTIFIER && this->text(lookahead) == "void") { + // `(void)` also means no parameters at all. + this->nextToken(); + } else { + for (;;) { + size_t paramIndex = parameters.size(); + std::optional<DSLParameter> parameter = this->parameter(paramIndex); + if (!parameter) { + return false; + } + parameters.push_back(std::move(*parameter)); + if (!this->checkNext(Token::Kind::TK_COMMA)) { + break; + } + } + } + if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { + return false; + } + SkSTArray<8, DSLParameter*> parameterPointers; + parameterPointers.reserve_back(parameters.size()); + for (DSLParameter& param : parameters) { + parameterPointers.push_back(¶m); + } + + DSLFunction result(this->text(name), modifiers, type, parameterPointers, + this->rangeFrom(start)); + + const bool hasFunctionBody = !this->checkNext(Token::Kind::TK_SEMICOLON); + if (hasFunctionBody) { + AutoSymbolTable symbols(this); + for (DSLParameter* var : parameterPointers) { + if (!var->name().empty()) { + this->addToSymbolTable(*var); + } + } + Token bodyStart = this->peek(); + std::optional<DSLBlock> body = this->block(); + if (!body) { + return false; + } + result.define(std::move(*body), this->rangeFrom(bodyStart)); + } else { + result.prototype(); + } + return true; +} + +bool Parser::arraySize(SKSL_INT* outResult) { + // Start out with a safe value that won't generate any errors downstream + *outResult = 1; + Token next = this->peek(); + if (next.fKind == Token::Kind::TK_RBRACKET) { + this->error(this->position(next), "unsized arrays are not permitted here"); + return true; + } + DSLExpression sizeExpr = this->expression(); + if (!sizeExpr.hasValue()) { + return false; + } + if (sizeExpr.isValid()) { + std::unique_ptr<SkSL::Expression> sizeLiteral = sizeExpr.release(); + SKSL_INT size; + if (!ConstantFolder::GetConstantInt(*sizeLiteral, &size)) { + this->error(sizeLiteral->fPosition, "array size must be an integer"); + return true; + } + if (size > INT32_MAX) { + this->error(sizeLiteral->fPosition, "array size out of bounds"); + return true; + } + if (size <= 0) { + this->error(sizeLiteral->fPosition, "array size must be positive"); + return true; + } + // Now that we've validated it, output the real value + *outResult = size; + } + return true; +} + +bool Parser::parseArrayDimensions(Position pos, DSLType* type) { + Token next; + while (this->checkNext(Token::Kind::TK_LBRACKET, &next)) { + if (this->checkNext(Token::Kind::TK_RBRACKET)) { + if (this->allowUnsizedArrays()) { + *type = UnsizedArray(*type, this->rangeFrom(pos)); + } else { + this->error(this->rangeFrom(pos), "unsized arrays are not permitted here"); + } + } else { + SKSL_INT size; + if (!this->arraySize(&size)) { + return false; + } + if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) { + return false; + } + *type = Array(*type, size, this->rangeFrom(pos)); + } + } + return true; +} + +bool Parser::parseInitializer(Position pos, DSLExpression* initializer) { + if (this->checkNext(Token::Kind::TK_EQ)) { + DSLExpression value = this->assignmentExpression(); + if (!value.hasValue()) { + return false; + } + initializer->swap(value); + } + return true; +} + +/* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER + (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */ +void Parser::globalVarDeclarationEnd(Position pos, + const dsl::DSLModifiers& mods, + dsl::DSLType baseType, + Token name) { + using namespace dsl; + DSLType type = baseType; + DSLExpression initializer; + if (!this->parseArrayDimensions(pos, &type)) { + return; + } + if (!this->parseInitializer(pos, &initializer)) { + return; + } + DSLGlobalVar first(mods, type, this->text(name), std::move(initializer), this->rangeFrom(pos), + this->position(name)); + Declare(first); + this->addToSymbolTable(first); + + while (this->checkNext(Token::Kind::TK_COMMA)) { + type = baseType; + Token identifierName; + if (!this->expectIdentifier(&identifierName)) { + return; + } + if (!this->parseArrayDimensions(pos, &type)) { + return; + } + DSLExpression anotherInitializer; + if (!this->parseInitializer(pos, &anotherInitializer)) { + return; + } + DSLGlobalVar next(mods, type, this->text(identifierName), std::move(anotherInitializer), + this->rangeFrom(identifierName)); + Declare(next); + this->addToSymbolTable(next, this->position(identifierName)); + } + this->expect(Token::Kind::TK_SEMICOLON, "';'"); +} + +/* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER + (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */ +DSLStatement Parser::localVarDeclarationEnd(Position pos, + const dsl::DSLModifiers& mods, + dsl::DSLType baseType, + Token name) { + using namespace dsl; + DSLType type = baseType; + DSLExpression initializer; + if (!this->parseArrayDimensions(pos, &type)) { + return {}; + } + if (!this->parseInitializer(pos, &initializer)) { + return {}; + } + DSLVar first(mods, type, this->text(name), std::move(initializer), this->rangeFrom(pos), + this->position(name)); + DSLStatement result = Declare(first); + this->addToSymbolTable(first); + + while (this->checkNext(Token::Kind::TK_COMMA)) { + type = baseType; + Token identifierName; + if (!this->expectIdentifier(&identifierName)) { + return result; + } + if (!this->parseArrayDimensions(pos, &type)) { + return result; + } + DSLExpression anotherInitializer; + if (!this->parseInitializer(pos, &anotherInitializer)) { + return result; + } + DSLVar next(mods, type, this->text(identifierName), std::move(anotherInitializer), + this->rangeFrom(identifierName), this->position(identifierName)); + DSLWriter::AddVarDeclaration(result, next); + this->addToSymbolTable(next, this->position(identifierName)); + } + this->expect(Token::Kind::TK_SEMICOLON, "';'"); + result.setPosition(this->rangeFrom(pos)); + return result; +} + +/* (varDeclarations | expressionStatement) */ +DSLStatement Parser::varDeclarationsOrExpressionStatement() { + Token nextToken = this->peek(); + if (nextToken.fKind == Token::Kind::TK_CONST) { + // Statements that begin with `const` might be variable declarations, but can't be legal + // SkSL expression-statements. (SkSL constructors don't take a `const` modifier.) + return this->varDeclarations(); + } + + if (nextToken.fKind == Token::Kind::TK_HIGHP || + nextToken.fKind == Token::Kind::TK_MEDIUMP || + nextToken.fKind == Token::Kind::TK_LOWP || + this->symbolTable()->isType(this->text(nextToken))) { + // Statements that begin with a typename are most often variable declarations, but + // occasionally the type is part of a constructor, and these are actually expression- + // statements in disguise. First, attempt the common case: parse it as a vardecl. + Checkpoint checkpoint(this); + VarDeclarationsPrefix prefix; + if (this->varDeclarationsPrefix(&prefix)) { + checkpoint.accept(); + return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType, + prefix.fName); + } + + // If this statement wasn't actually a vardecl after all, rewind and try parsing it as an + // expression-statement instead. + checkpoint.rewind(); + } + return this->expressionStatement(); +} + +// Helper function for varDeclarations(). If this function succeeds, we assume that the rest of the +// statement is a variable-declaration statement, not an expression-statement. +bool Parser::varDeclarationsPrefix(VarDeclarationsPrefix* prefixData) { + prefixData->fPosition = this->position(this->peek()); + prefixData->fModifiers = this->modifiers(); + prefixData->fType = this->type(&prefixData->fModifiers); + if (!prefixData->fType.hasValue()) { + return false; + } + return this->expectIdentifier(&prefixData->fName); +} + +/* modifiers type IDENTIFIER varDeclarationEnd */ +DSLStatement Parser::varDeclarations() { + VarDeclarationsPrefix prefix; + if (!this->varDeclarationsPrefix(&prefix)) { + return {}; + } + return this->localVarDeclarationEnd(prefix.fPosition, prefix.fModifiers, prefix.fType, + prefix.fName); +} + +/* STRUCT IDENTIFIER LBRACE varDeclaration* RBRACE */ +DSLType Parser::structDeclaration() { + Position start = this->position(this->peek()); + if (!this->expect(Token::Kind::TK_STRUCT, "'struct'")) { + return DSLType(nullptr); + } + Token name; + if (!this->expectIdentifier(&name)) { + return DSLType(nullptr); + } + if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) { + return DSLType(nullptr); + } + AutoDepth depth(this); + if (!depth.increase()) { + return DSLType(nullptr); + } + SkTArray<DSLField> fields; + SkTHashSet<std::string_view> fieldNames; + while (!this->checkNext(Token::Kind::TK_RBRACE)) { + Token fieldStart = this->peek(); + DSLModifiers modifiers = this->modifiers(); + DSLType type = this->type(&modifiers); + if (!type.hasValue()) { + return DSLType(nullptr); + } + + do { + DSLType actualType = type; + Token memberName; + if (!this->expectIdentifier(&memberName)) { + return DSLType(nullptr); + } + + while (this->checkNext(Token::Kind::TK_LBRACKET)) { + SKSL_INT size; + if (!this->arraySize(&size)) { + return DSLType(nullptr); + } + if (!this->expect(Token::Kind::TK_RBRACKET, "']'")) { + return DSLType(nullptr); + } + actualType = dsl::Array(actualType, size, + this->rangeFrom(this->position(fieldStart))); + } + + std::string_view nameText = this->text(memberName); + if (!fieldNames.contains(nameText)) { + fields.push_back(DSLField(modifiers, + std::move(actualType), + nameText, + this->rangeFrom(fieldStart))); + fieldNames.add(nameText); + } else { + this->error(memberName, "field '" + std::string(nameText) + + "' was already defined in the same struct ('" + + std::string(this->text(name)) + "')"); + } + } while (this->checkNext(Token::Kind::TK_COMMA)); + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return DSLType(nullptr); + } + } + if (fields.empty()) { + this->error(this->rangeFrom(start), "struct '" + std::string(this->text(name)) + + "' must contain at least one field"); + } + return dsl::Struct(this->text(name), SkSpan(fields), this->rangeFrom(start)); +} + +/* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */ +SkTArray<dsl::DSLGlobalVar> Parser::structVarDeclaration(Position start, + const DSLModifiers& modifiers) { + DSLType type = this->structDeclaration(); + if (!type.hasValue()) { + return {}; + } + Token name; + if (this->checkIdentifier(&name)) { + this->globalVarDeclarationEnd(this->rangeFrom(name), modifiers, type, name); + } else { + this->expect(Token::Kind::TK_SEMICOLON, "';'"); + } + return {}; +} + +/* modifiers type IDENTIFIER (LBRACKET INT_LITERAL RBRACKET)? */ +std::optional<DSLParameter> Parser::parameter(size_t paramIndex) { + Position pos = this->position(this->peek()); + DSLModifiers modifiers = this->modifiers(); + DSLType type = this->type(&modifiers); + if (!type.hasValue()) { + return std::nullopt; + } + Token name; + std::string_view paramText; + Position paramPos; + if (this->checkIdentifier(&name)) { + paramText = this->text(name); + paramPos = this->position(name); + } else { + paramPos = this->rangeFrom(pos); + } + if (!this->parseArrayDimensions(pos, &type)) { + return std::nullopt; + } + return DSLParameter(modifiers, type, paramText, this->rangeFrom(pos), paramPos); +} + +/** EQ INT_LITERAL */ +int Parser::layoutInt() { + if (!this->expect(Token::Kind::TK_EQ, "'='")) { + return -1; + } + Token resultToken; + if (!this->expect(Token::Kind::TK_INT_LITERAL, "a non-negative integer", &resultToken)) { + return -1; + } + std::string_view resultFrag = this->text(resultToken); + SKSL_INT resultValue; + if (!SkSL::stoi(resultFrag, &resultValue)) { + this->error(resultToken, "value in layout is too large: " + std::string(resultFrag)); + return -1; + } + return resultValue; +} + +/** EQ IDENTIFIER */ +std::string_view Parser::layoutIdentifier() { + if (!this->expect(Token::Kind::TK_EQ, "'='")) { + return {}; + } + Token resultToken; + if (!this->expectIdentifier(&resultToken)) { + return {}; + } + return this->text(resultToken); +} + +/* LAYOUT LPAREN IDENTIFIER (EQ INT_LITERAL)? (COMMA IDENTIFIER (EQ INT_LITERAL)?)* RPAREN */ +DSLLayout Parser::layout() { + enum class LayoutToken { + LOCATION, + OFFSET, + BINDING, + TEXTURE, + SAMPLER, + INDEX, + SET, + BUILTIN, + INPUT_ATTACHMENT_INDEX, + ORIGIN_UPPER_LEFT, + BLEND_SUPPORT_ALL_EQUATIONS, + PUSH_CONSTANT, + COLOR, + SPIRV, + METAL, + GL, + WGSL + }; + + using LayoutMap = SkTHashMap<std::string_view, LayoutToken>; + static LayoutMap* sLayoutTokens = new LayoutMap{ + {"location", LayoutToken::LOCATION}, + {"offset", LayoutToken::OFFSET}, + {"binding", LayoutToken::BINDING}, + {"texture", LayoutToken::TEXTURE}, + {"sampler", LayoutToken::SAMPLER}, + {"index", LayoutToken::INDEX}, + {"set", LayoutToken::SET}, + {"builtin", LayoutToken::BUILTIN}, + {"input_attachment_index", LayoutToken::INPUT_ATTACHMENT_INDEX}, + {"origin_upper_left", LayoutToken::ORIGIN_UPPER_LEFT}, + {"blend_support_all_equations", LayoutToken::BLEND_SUPPORT_ALL_EQUATIONS}, + {"push_constant", LayoutToken::PUSH_CONSTANT}, + {"color", LayoutToken::COLOR}, + {"spirv", LayoutToken::SPIRV}, + {"metal", LayoutToken::METAL}, + {"gl", LayoutToken::GL}, + {"wgsl", LayoutToken::WGSL}, + }; + + DSLLayout result; + if (this->checkNext(Token::Kind::TK_LAYOUT)) { + if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { + return result; + } + for (;;) { + Token t = this->nextToken(); + std::string text(this->text(t)); + LayoutToken* found = sLayoutTokens->find(text); + if (found != nullptr) { + switch (*found) { + case LayoutToken::SPIRV: + result.spirv(this->position(t)); + break; + case LayoutToken::METAL: + result.metal(this->position(t)); + break; + case LayoutToken::GL: + result.gl(this->position(t)); + break; + case LayoutToken::WGSL: + result.wgsl(this->position(t)); + break; + case LayoutToken::ORIGIN_UPPER_LEFT: + result.originUpperLeft(this->position(t)); + break; + case LayoutToken::PUSH_CONSTANT: + result.pushConstant(this->position(t)); + break; + case LayoutToken::BLEND_SUPPORT_ALL_EQUATIONS: + result.blendSupportAllEquations(this->position(t)); + break; + case LayoutToken::COLOR: + result.color(this->position(t)); + break; + case LayoutToken::LOCATION: + result.location(this->layoutInt(), this->position(t)); + break; + case LayoutToken::OFFSET: + result.offset(this->layoutInt(), this->position(t)); + break; + case LayoutToken::BINDING: + result.binding(this->layoutInt(), this->position(t)); + break; + case LayoutToken::INDEX: + result.index(this->layoutInt(), this->position(t)); + break; + case LayoutToken::SET: + result.set(this->layoutInt(), this->position(t)); + break; + case LayoutToken::TEXTURE: + result.texture(this->layoutInt(), this->position(t)); + break; + case LayoutToken::SAMPLER: + result.sampler(this->layoutInt(), this->position(t)); + break; + case LayoutToken::BUILTIN: + result.builtin(this->layoutInt(), this->position(t)); + break; + case LayoutToken::INPUT_ATTACHMENT_INDEX: + result.inputAttachmentIndex(this->layoutInt(), this->position(t)); + break; + } + } else { + this->error(t, "'" + text + "' is not a valid layout qualifier"); + } + if (this->checkNext(Token::Kind::TK_RPAREN)) { + break; + } + if (!this->expect(Token::Kind::TK_COMMA, "','")) { + break; + } + } + } + return result; +} + +/* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE | + VARYING | INLINE | WORKGROUP | READONLY | WRITEONLY | BUFFER)* */ +DSLModifiers Parser::modifiers() { + int start = this->peek().fOffset; + DSLLayout layout = this->layout(); + Token raw = this->nextRawToken(); + int end = raw.fOffset; + if (!is_whitespace(raw.fKind)) { + this->pushback(raw); + } + int flags = 0; + for (;;) { + int tokenFlag = parse_modifier_token(peek().fKind); + if (!tokenFlag) { + break; + } + Token modifier = this->nextToken(); + if (int duplicateFlags = (tokenFlag & flags)) { + this->error(modifier, "'" + Modifiers::DescribeFlags(duplicateFlags) + + "' appears more than once"); + } + flags |= tokenFlag; + end = this->position(modifier).endOffset(); + } + return DSLModifiers(std::move(layout), flags, Position::Range(start, end)); +} + +/* ifStatement | forStatement | doStatement | whileStatement | block | expression */ +DSLStatement Parser::statement() { + Token start = this->nextToken(); + AutoDepth depth(this); + if (!depth.increase()) { + return {}; + } + this->pushback(start); + switch (start.fKind) { + case Token::Kind::TK_IF: + return this->ifStatement(); + case Token::Kind::TK_FOR: + return this->forStatement(); + case Token::Kind::TK_DO: + return this->doStatement(); + case Token::Kind::TK_WHILE: + return this->whileStatement(); + case Token::Kind::TK_SWITCH: + return this->switchStatement(); + case Token::Kind::TK_RETURN: + return this->returnStatement(); + case Token::Kind::TK_BREAK: + return this->breakStatement(); + case Token::Kind::TK_CONTINUE: + return this->continueStatement(); + case Token::Kind::TK_DISCARD: + return this->discardStatement(); + case Token::Kind::TK_LBRACE: { + std::optional<DSLBlock> result = this->block(); + return result ? DSLStatement(std::move(*result)) : DSLStatement(); + } + case Token::Kind::TK_SEMICOLON: + this->nextToken(); + return DSLBlock(); + case Token::Kind::TK_HIGHP: + case Token::Kind::TK_MEDIUMP: + case Token::Kind::TK_LOWP: + case Token::Kind::TK_CONST: + case Token::Kind::TK_IDENTIFIER: + return this->varDeclarationsOrExpressionStatement(); + default: + return this->expressionStatement(); + } +} + +/* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* QUESTION? */ +DSLType Parser::type(DSLModifiers* modifiers) { + Token type; + if (!this->expect(Token::Kind::TK_IDENTIFIER, "a type", &type)) { + return DSLType(nullptr); + } + if (!this->symbolTable()->isType(this->text(type))) { + this->error(type, "no type named '" + std::string(this->text(type)) + "'"); + return DSLType::Invalid(); + } + DSLType result(this->text(type), modifiers, this->position(type)); + if (result.isInterfaceBlock()) { + // SkSL puts interface blocks into the symbol table, but they aren't general-purpose types; + // you can't use them to declare a variable type or a function return type. + this->error(type, "expected a type, found '" + std::string(this->text(type)) + "'"); + return DSLType::Invalid(); + } + Token bracket; + while (this->checkNext(Token::Kind::TK_LBRACKET, &bracket)) { + if (this->checkNext(Token::Kind::TK_RBRACKET)) { + if (this->allowUnsizedArrays()) { + result = UnsizedArray(result, this->rangeFrom(type)); + } else { + this->error(this->rangeFrom(bracket), "unsized arrays are not permitted here"); + } + } else { + SKSL_INT size; + if (!this->arraySize(&size)) { + return DSLType(nullptr); + } + this->expect(Token::Kind::TK_RBRACKET, "']'"); + result = Array(result, size, this->rangeFrom(type)); + } + } + return result; +} + +/* IDENTIFIER LBRACE + varDeclaration+ + RBRACE (IDENTIFIER (LBRACKET expression RBRACKET)*)? SEMICOLON */ +bool Parser::interfaceBlock(const dsl::DSLModifiers& modifiers) { + Token typeName; + if (!this->expectIdentifier(&typeName)) { + return false; + } + if (this->peek().fKind != Token::Kind::TK_LBRACE) { + // we only get into interfaceBlock if we found a top-level identifier which was not a type. + // 99% of the time, the user was not actually intending to create an interface block, so + // it's better to report it as an unknown type + this->error(typeName, "no type named '" + std::string(this->text(typeName)) + "'"); + return false; + } + this->nextToken(); + SkTArray<DSLField> fields; + SkTHashSet<std::string_view> fieldNames; + while (!this->checkNext(Token::Kind::TK_RBRACE)) { + Position fieldPos = this->position(this->peek()); + DSLModifiers fieldModifiers = this->modifiers(); + DSLType type = this->type(&fieldModifiers); + if (!type.hasValue()) { + return false; + } + do { + Token fieldName; + if (!this->expectIdentifier(&fieldName)) { + return false; + } + DSLType actualType = type; + if (this->checkNext(Token::Kind::TK_LBRACKET)) { + Token sizeToken = this->peek(); + if (sizeToken.fKind != Token::Kind::TK_RBRACKET) { + SKSL_INT size; + if (!this->arraySize(&size)) { + return false; + } + actualType = Array(std::move(actualType), size, this->position(typeName)); + } else if (this->allowUnsizedArrays()) { + actualType = UnsizedArray(std::move(actualType), this->position(typeName)); + } else { + this->error(sizeToken, "unsized arrays are not permitted here"); + } + this->expect(Token::Kind::TK_RBRACKET, "']'"); + } + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return false; + } + + std::string_view nameText = this->text(fieldName); + if (!fieldNames.contains(nameText)) { + fields.push_back(DSLField(fieldModifiers, + std::move(actualType), + nameText, + this->rangeFrom(fieldPos))); + fieldNames.add(nameText); + } else { + this->error(fieldName, "field '" + std::string(nameText) + + "' was already defined in the same interface block ('" + + std::string(this->text(typeName)) + "')"); + } + } while (this->checkNext(Token::Kind::TK_COMMA)); + } + if (fields.empty()) { + this->error(this->rangeFrom(typeName), "interface block '" + + std::string(this->text(typeName)) + "' must contain at least one member"); + } + std::string_view instanceName; + Token instanceNameToken; + SKSL_INT size = 0; + if (this->checkIdentifier(&instanceNameToken)) { + instanceName = this->text(instanceNameToken); + if (this->checkNext(Token::Kind::TK_LBRACKET)) { + if (!this->arraySize(&size)) { + return false; + } + this->expect(Token::Kind::TK_RBRACKET, "']'"); + } + } + if (!fields.empty()) { + dsl::InterfaceBlock(modifiers, this->text(typeName), std::move(fields), instanceName, + size, this->position(typeName)); + } + this->expect(Token::Kind::TK_SEMICOLON, "';'"); + return true; +} + +/* IF LPAREN expression RPAREN statement (ELSE statement)? */ +DSLStatement Parser::ifStatement() { + Token start; + if (!this->expect(Token::Kind::TK_IF, "'if'", &start)) { + return {}; + } + if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { + return {}; + } + DSLExpression test = this->expression(); + if (!test.hasValue()) { + return {}; + } + if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { + return {}; + } + DSLStatement ifTrue = this->statement(); + if (!ifTrue.hasValue()) { + return {}; + } + DSLStatement ifFalse; + if (this->checkNext(Token::Kind::TK_ELSE)) { + ifFalse = this->statement(); + if (!ifFalse.hasValue()) { + return {}; + } + } + Position pos = this->rangeFrom(start); + return If(std::move(test), std::move(ifTrue), + ifFalse.hasValue() ? std::move(ifFalse) : DSLStatement(), pos); +} + +/* DO statement WHILE LPAREN expression RPAREN SEMICOLON */ +DSLStatement Parser::doStatement() { + Token start; + if (!this->expect(Token::Kind::TK_DO, "'do'", &start)) { + return {}; + } + DSLStatement statement = this->statement(); + if (!statement.hasValue()) { + return {}; + } + if (!this->expect(Token::Kind::TK_WHILE, "'while'")) { + return {}; + } + if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { + return {}; + } + DSLExpression test = this->expression(); + if (!test.hasValue()) { + return {}; + } + if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { + return {}; + } + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return {}; + } + return Do(std::move(statement), std::move(test), this->rangeFrom(start)); +} + +/* WHILE LPAREN expression RPAREN STATEMENT */ +DSLStatement Parser::whileStatement() { + Token start; + if (!this->expect(Token::Kind::TK_WHILE, "'while'", &start)) { + return {}; + } + if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { + return {}; + } + DSLExpression test = this->expression(); + if (!test.hasValue()) { + return {}; + } + if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { + return {}; + } + DSLStatement statement = this->statement(); + if (!statement.hasValue()) { + return {}; + } + return While(std::move(test), std::move(statement), this->rangeFrom(start)); +} + +/* CASE expression COLON statement* */ +std::optional<DSLCase> Parser::switchCase() { + Token start; + if (!this->expect(Token::Kind::TK_CASE, "'case'", &start)) { + return {}; + } + DSLExpression value = this->expression(); + if (!value.hasValue()) { + return {}; + } + if (!this->expect(Token::Kind::TK_COLON, "':'")) { + return {}; + } + SkTArray<DSLStatement> statements; + while (this->peek().fKind != Token::Kind::TK_RBRACE && + this->peek().fKind != Token::Kind::TK_CASE && + this->peek().fKind != Token::Kind::TK_DEFAULT) { + DSLStatement s = this->statement(); + if (!s.hasValue()) { + return {}; + } + statements.push_back(std::move(s)); + } + return DSLCase(std::move(value), std::move(statements)); +} + +/* SWITCH LPAREN expression RPAREN LBRACE switchCase* (DEFAULT COLON statement*)? RBRACE */ +DSLStatement Parser::switchStatement() { + Token start; + if (!this->expect(Token::Kind::TK_SWITCH, "'switch'", &start)) { + return {}; + } + if (!this->expect(Token::Kind::TK_LPAREN, "'('")) { + return {}; + } + DSLExpression value = this->expression(); + if (!value.hasValue()) { + return {}; + } + if (!this->expect(Token::Kind::TK_RPAREN, "')'")) { + return {}; + } + if (!this->expect(Token::Kind::TK_LBRACE, "'{'")) { + return {}; + } + SkTArray<DSLCase> cases; + while (this->peek().fKind == Token::Kind::TK_CASE) { + std::optional<DSLCase> c = this->switchCase(); + if (!c) { + return {}; + } + cases.push_back(std::move(*c)); + } + // Requiring default: to be last (in defiance of C and GLSL) was a deliberate decision. Other + // parts of the compiler may rely upon this assumption. + if (this->peek().fKind == Token::Kind::TK_DEFAULT) { + SkTArray<DSLStatement> statements; + Token defaultStart; + SkAssertResult(this->expect(Token::Kind::TK_DEFAULT, "'default'", &defaultStart)); + if (!this->expect(Token::Kind::TK_COLON, "':'")) { + return {}; + } + while (this->peek().fKind != Token::Kind::TK_RBRACE) { + DSLStatement s = this->statement(); + if (!s.hasValue()) { + return {}; + } + statements.push_back(std::move(s)); + } + cases.push_back(DSLCase(DSLExpression(), std::move(statements), this->position(start))); + } + if (!this->expect(Token::Kind::TK_RBRACE, "'}'")) { + return {}; + } + Position pos = this->rangeFrom(start); + return Switch(std::move(value), std::move(cases), pos); +} + +static Position range_of_at_least_one_char(int start, int end) { + return Position::Range(start, std::max(end, start + 1)); +} + +/* FOR LPAREN (declaration | expression)? SEMICOLON expression? SEMICOLON expression? RPAREN + STATEMENT */ +dsl::DSLStatement Parser::forStatement() { + Token start; + if (!this->expect(Token::Kind::TK_FOR, "'for'", &start)) { + return {}; + } + Token lparen; + if (!this->expect(Token::Kind::TK_LPAREN, "'('", &lparen)) { + return {}; + } + AutoSymbolTable symbols(this); + dsl::DSLStatement initializer; + Token nextToken = this->peek(); + int firstSemicolonOffset; + if (nextToken.fKind == Token::Kind::TK_SEMICOLON) { + // An empty init-statement. + firstSemicolonOffset = this->nextToken().fOffset; + } else { + // The init-statement must be an expression or variable declaration. + initializer = this->varDeclarationsOrExpressionStatement(); + if (!initializer.hasValue()) { + return {}; + } + firstSemicolonOffset = fLexer.getCheckpoint().fOffset - 1; + } + dsl::DSLExpression test; + if (this->peek().fKind != Token::Kind::TK_SEMICOLON) { + dsl::DSLExpression testValue = this->expression(); + if (!testValue.hasValue()) { + return {}; + } + test.swap(testValue); + } + Token secondSemicolon; + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'", &secondSemicolon)) { + return {}; + } + dsl::DSLExpression next; + if (this->peek().fKind != Token::Kind::TK_RPAREN) { + dsl::DSLExpression nextValue = this->expression(); + if (!nextValue.hasValue()) { + return {}; + } + next.swap(nextValue); + } + Token rparen; + if (!this->expect(Token::Kind::TK_RPAREN, "')'", &rparen)) { + return {}; + } + dsl::DSLStatement statement = this->statement(); + if (!statement.hasValue()) { + return {}; + } + return For(initializer.hasValue() ? std::move(initializer) : DSLStatement(), + test.hasValue() ? std::move(test) : DSLExpression(), + next.hasValue() ? std::move(next) : DSLExpression(), + std::move(statement), + this->rangeFrom(start), + ForLoopPositions{ + range_of_at_least_one_char(lparen.fOffset + 1, firstSemicolonOffset), + range_of_at_least_one_char(firstSemicolonOffset + 1, secondSemicolon.fOffset), + range_of_at_least_one_char(secondSemicolon.fOffset + 1, rparen.fOffset) + }); +} + +/* RETURN expression? SEMICOLON */ +DSLStatement Parser::returnStatement() { + Token start; + if (!this->expect(Token::Kind::TK_RETURN, "'return'", &start)) { + return {}; + } + DSLExpression expression; + if (this->peek().fKind != Token::Kind::TK_SEMICOLON) { + DSLExpression next = this->expression(); + if (!next.hasValue()) { + return {}; + } + expression.swap(next); + } + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return {}; + } + return Return(expression.hasValue() ? std::move(expression) : DSLExpression(), + this->rangeFrom(start)); +} + +/* BREAK SEMICOLON */ +DSLStatement Parser::breakStatement() { + Token start; + if (!this->expect(Token::Kind::TK_BREAK, "'break'", &start)) { + return {}; + } + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return {}; + } + return Break(this->position(start)); +} + +/* CONTINUE SEMICOLON */ +DSLStatement Parser::continueStatement() { + Token start; + if (!this->expect(Token::Kind::TK_CONTINUE, "'continue'", &start)) { + return {}; + } + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return {}; + } + return Continue(this->position(start)); +} + +/* DISCARD SEMICOLON */ +DSLStatement Parser::discardStatement() { + Token start; + if (!this->expect(Token::Kind::TK_DISCARD, "'continue'", &start)) { + return {}; + } + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return {}; + } + return Discard(this->position(start)); +} + +/* LBRACE statement* RBRACE */ +std::optional<DSLBlock> Parser::block() { + Token start; + if (!this->expect(Token::Kind::TK_LBRACE, "'{'", &start)) { + return std::nullopt; + } + AutoDepth depth(this); + if (!depth.increase()) { + return std::nullopt; + } + AutoSymbolTable symbols(this); + StatementArray statements; + for (;;) { + switch (this->peek().fKind) { + case Token::Kind::TK_RBRACE: + this->nextToken(); + return DSLBlock(std::move(statements), this->symbolTable(), this->rangeFrom(start)); + case Token::Kind::TK_END_OF_FILE: + this->error(this->peek(), "expected '}', but found end of file"); + return std::nullopt; + default: { + DSLStatement statement = this->statement(); + if (fEncounteredFatalError) { + return std::nullopt; + } + if (statement.hasValue()) { + statements.push_back(statement.release()); + } + break; + } + } + } +} + +/* expression SEMICOLON */ +DSLStatement Parser::expressionStatement() { + DSLExpression expr = this->expression(); + if (expr.hasValue()) { + if (!this->expect(Token::Kind::TK_SEMICOLON, "';'")) { + return {}; + } + return DSLStatement(std::move(expr)); + } + return {}; +} + +bool Parser::operatorRight(Parser::AutoDepth& depth, + Operator::Kind op, + BinaryParseFn rightFn, + DSLExpression& result) { + this->nextToken(); + if (!depth.increase()) { + return false; + } + DSLExpression right = (this->*rightFn)(); + if (!right.hasValue()) { + return false; + } + Position pos = result.position().rangeThrough(right.position()); + DSLExpression next = result.binary(op, std::move(right), pos); + result.swap(next); + return true; +} + +/* assignmentExpression (COMMA assignmentExpression)* */ +DSLExpression Parser::expression() { + [[maybe_unused]] Token start = this->peek(); + DSLExpression result = this->assignmentExpression(); + if (!result.hasValue()) { + return {}; + } + Token t; + AutoDepth depth(this); + while (this->peek().fKind == Token::Kind::TK_COMMA) { + if (!operatorRight(depth, Operator::Kind::COMMA, &Parser::assignmentExpression, + result)) { + return {}; + } + } + SkASSERTF(result.position().valid(), "Expression %s has invalid position", + result.description().c_str()); + SkASSERTF(result.position().startOffset() == this->position(start).startOffset(), + "Expected %s to start at %d (first token: '%.*s'), but it has range %d-%d\n", + result.description().c_str(), this->position(start).startOffset(), + (int)this->text(start).length(), this->text(start).data(), + result.position().startOffset(), result.position().endOffset()); + return result; +} + +/* ternaryExpression ((EQEQ | STAREQ | SLASHEQ | PERCENTEQ | PLUSEQ | MINUSEQ | SHLEQ | SHREQ | + BITWISEANDEQ | BITWISEXOREQ | BITWISEOREQ | LOGICALANDEQ | LOGICALXOREQ | LOGICALOREQ) + assignmentExpression)* + */ +DSLExpression Parser::assignmentExpression() { + AutoDepth depth(this); + DSLExpression result = this->ternaryExpression(); + if (!result.hasValue()) { + return {}; + } + for (;;) { + switch (this->peek().fKind) { + case Token::Kind::TK_EQ: + if (!operatorRight(depth, Operator::Kind::EQ, &Parser::assignmentExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_STAREQ: + if (!operatorRight(depth, Operator::Kind::STAREQ, &Parser::assignmentExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_SLASHEQ: + if (!operatorRight(depth, Operator::Kind::SLASHEQ, &Parser::assignmentExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_PERCENTEQ: + if (!operatorRight(depth, Operator::Kind::PERCENTEQ, + &Parser::assignmentExpression, result)) { + return {}; + } + break; + case Token::Kind::TK_PLUSEQ: + if (!operatorRight(depth, Operator::Kind::PLUSEQ, &Parser::assignmentExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_MINUSEQ: + if (!operatorRight(depth, Operator::Kind::MINUSEQ, &Parser::assignmentExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_SHLEQ: + if (!operatorRight(depth, Operator::Kind::SHLEQ, &Parser::assignmentExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_SHREQ: + if (!operatorRight(depth, Operator::Kind::SHREQ, &Parser::assignmentExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_BITWISEANDEQ: + if (!operatorRight(depth, Operator::Kind::BITWISEANDEQ, + &Parser::assignmentExpression, result)) { + return {}; + } + break; + case Token::Kind::TK_BITWISEXOREQ: + if (!operatorRight(depth, Operator::Kind::BITWISEXOREQ, + &Parser::assignmentExpression, result)) { + return {}; + } + break; + case Token::Kind::TK_BITWISEOREQ: + if (!operatorRight(depth, Operator::Kind::BITWISEOREQ, + &Parser::assignmentExpression, result)) { + return {}; + } + break; + default: + return result; + } + } +} + +/* logicalOrExpression ('?' expression ':' assignmentExpression)? */ +DSLExpression Parser::ternaryExpression() { + DSLExpression base = this->logicalOrExpression(); + if (!base.hasValue()) { + return {}; + } + if (!this->checkNext(Token::Kind::TK_QUESTION)) { + return base; + } + AutoDepth depth(this); + if (!depth.increase()) { + return {}; + } + DSLExpression trueExpr = this->expression(); + if (!trueExpr.hasValue()) { + return {}; + } + if (!this->expect(Token::Kind::TK_COLON, "':'")) { + return {}; + } + DSLExpression falseExpr = this->assignmentExpression(); + if (!falseExpr.hasValue()) { + return {}; + } + Position pos = base.position().rangeThrough(falseExpr.position()); + return Select(std::move(base), std::move(trueExpr), std::move(falseExpr), pos); +} + +/* logicalXorExpression (LOGICALOR logicalXorExpression)* */ +DSLExpression Parser::logicalOrExpression() { + AutoDepth depth(this); + DSLExpression result = this->logicalXorExpression(); + if (!result.hasValue()) { + return {}; + } + while (this->peek().fKind == Token::Kind::TK_LOGICALOR) { + if (!operatorRight(depth, Operator::Kind::LOGICALOR, &Parser::logicalXorExpression, + result)) { + return {}; + } + } + return result; +} + +/* logicalAndExpression (LOGICALXOR logicalAndExpression)* */ +DSLExpression Parser::logicalXorExpression() { + AutoDepth depth(this); + DSLExpression result = this->logicalAndExpression(); + if (!result.hasValue()) { + return {}; + } + while (this->peek().fKind == Token::Kind::TK_LOGICALXOR) { + if (!operatorRight(depth, Operator::Kind::LOGICALXOR, &Parser::logicalAndExpression, + result)) { + return {}; + } + } + return result; +} + +/* bitwiseOrExpression (LOGICALAND bitwiseOrExpression)* */ +DSLExpression Parser::logicalAndExpression() { + AutoDepth depth(this); + DSLExpression result = this->bitwiseOrExpression(); + if (!result.hasValue()) { + return {}; + } + while (this->peek().fKind == Token::Kind::TK_LOGICALAND) { + if (!operatorRight(depth, Operator::Kind::LOGICALAND, &Parser::bitwiseOrExpression, + result)) { + return {}; + } + } + return result; +} + +/* bitwiseXorExpression (BITWISEOR bitwiseXorExpression)* */ +DSLExpression Parser::bitwiseOrExpression() { + AutoDepth depth(this); + DSLExpression result = this->bitwiseXorExpression(); + if (!result.hasValue()) { + return {}; + } + while (this->peek().fKind == Token::Kind::TK_BITWISEOR) { + if (!operatorRight(depth, Operator::Kind::BITWISEOR, &Parser::bitwiseXorExpression, + result)) { + return {}; + } + } + return result; +} + +/* bitwiseAndExpression (BITWISEXOR bitwiseAndExpression)* */ +DSLExpression Parser::bitwiseXorExpression() { + AutoDepth depth(this); + DSLExpression result = this->bitwiseAndExpression(); + if (!result.hasValue()) { + return {}; + } + while (this->peek().fKind == Token::Kind::TK_BITWISEXOR) { + if (!operatorRight(depth, Operator::Kind::BITWISEXOR, &Parser::bitwiseAndExpression, + result)) { + return {}; + } + } + return result; +} + +/* equalityExpression (BITWISEAND equalityExpression)* */ +DSLExpression Parser::bitwiseAndExpression() { + AutoDepth depth(this); + DSLExpression result = this->equalityExpression(); + if (!result.hasValue()) { + return {}; + } + while (this->peek().fKind == Token::Kind::TK_BITWISEAND) { + if (!operatorRight(depth, Operator::Kind::BITWISEAND, &Parser::equalityExpression, + result)) { + return {}; + } + } + return result; +} + +/* relationalExpression ((EQEQ | NEQ) relationalExpression)* */ +DSLExpression Parser::equalityExpression() { + AutoDepth depth(this); + DSLExpression result = this->relationalExpression(); + if (!result.hasValue()) { + return {}; + } + for (;;) { + switch (this->peek().fKind) { + case Token::Kind::TK_EQEQ: + if (!operatorRight(depth, Operator::Kind::EQEQ, &Parser::relationalExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_NEQ: + if (!operatorRight(depth, Operator::Kind::NEQ, &Parser::relationalExpression, + result)) { + return {}; + } + break; + default: return result; + } + } +} + +/* shiftExpression ((LT | GT | LTEQ | GTEQ) shiftExpression)* */ +DSLExpression Parser::relationalExpression() { + AutoDepth depth(this); + DSLExpression result = this->shiftExpression(); + if (!result.hasValue()) { + return {}; + } + for (;;) { + switch (this->peek().fKind) { + case Token::Kind::TK_LT: + if (!operatorRight(depth, Operator::Kind::LT, &Parser::shiftExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_GT: + if (!operatorRight(depth, Operator::Kind::GT, &Parser::shiftExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_LTEQ: + if (!operatorRight(depth, Operator::Kind::LTEQ, &Parser::shiftExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_GTEQ: + if (!operatorRight(depth, Operator::Kind::GTEQ, &Parser::shiftExpression, + result)) { + return {}; + } + break; + default: + return result; + } + } +} + +/* additiveExpression ((SHL | SHR) additiveExpression)* */ +DSLExpression Parser::shiftExpression() { + AutoDepth depth(this); + DSLExpression result = this->additiveExpression(); + if (!result.hasValue()) { + return {}; + } + for (;;) { + switch (this->peek().fKind) { + case Token::Kind::TK_SHL: + if (!operatorRight(depth, Operator::Kind::SHL, &Parser::additiveExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_SHR: + if (!operatorRight(depth, Operator::Kind::SHR, &Parser::additiveExpression, + result)) { + return {}; + } + break; + default: + return result; + } + } +} + +/* multiplicativeExpression ((PLUS | MINUS) multiplicativeExpression)* */ +DSLExpression Parser::additiveExpression() { + AutoDepth depth(this); + DSLExpression result = this->multiplicativeExpression(); + if (!result.hasValue()) { + return {}; + } + for (;;) { + switch (this->peek().fKind) { + case Token::Kind::TK_PLUS: + if (!operatorRight(depth, Operator::Kind::PLUS, + &Parser::multiplicativeExpression, result)) { + return {}; + } + break; + case Token::Kind::TK_MINUS: + if (!operatorRight(depth, Operator::Kind::MINUS, + &Parser::multiplicativeExpression, result)) { + return {}; + } + break; + default: + return result; + } + } +} + +/* unaryExpression ((STAR | SLASH | PERCENT) unaryExpression)* */ +DSLExpression Parser::multiplicativeExpression() { + AutoDepth depth(this); + DSLExpression result = this->unaryExpression(); + if (!result.hasValue()) { + return {}; + } + for (;;) { + switch (this->peek().fKind) { + case Token::Kind::TK_STAR: + if (!operatorRight(depth, Operator::Kind::STAR, &Parser::unaryExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_SLASH: + if (!operatorRight(depth, Operator::Kind::SLASH, &Parser::unaryExpression, + result)) { + return {}; + } + break; + case Token::Kind::TK_PERCENT: + if (!operatorRight(depth, Operator::Kind::PERCENT, &Parser::unaryExpression, + result)) { + return {}; + } + break; + default: return result; + } + } +} + +/* postfixExpression | (PLUS | MINUS | NOT | PLUSPLUS | MINUSMINUS) unaryExpression */ +DSLExpression Parser::unaryExpression() { + AutoDepth depth(this); + Token start = this->peek(); + switch (start.fKind) { + case Token::Kind::TK_PLUS: + case Token::Kind::TK_MINUS: + case Token::Kind::TK_LOGICALNOT: + case Token::Kind::TK_BITWISENOT: + case Token::Kind::TK_PLUSPLUS: + case Token::Kind::TK_MINUSMINUS: { + this->nextToken(); + if (!depth.increase()) { + return {}; + } + DSLExpression expr = this->unaryExpression(); + if (!expr.hasValue()) { + return {}; + } + Position p = Position::Range(start.fOffset, expr.position().endOffset()); + switch (start.fKind) { + case Token::Kind::TK_PLUS: return expr.prefix(Operator::Kind::PLUS, p); + case Token::Kind::TK_MINUS: return expr.prefix(Operator::Kind::MINUS, p); + case Token::Kind::TK_LOGICALNOT: return expr.prefix(Operator::Kind::LOGICALNOT, p); + case Token::Kind::TK_BITWISENOT: return expr.prefix(Operator::Kind::BITWISENOT, p); + case Token::Kind::TK_PLUSPLUS: return expr.prefix(Operator::Kind::PLUSPLUS, p); + case Token::Kind::TK_MINUSMINUS: return expr.prefix(Operator::Kind::MINUSMINUS, p); + default: SkUNREACHABLE; + } + } + default: + return this->postfixExpression(); + } +} + +/* term suffix* */ +DSLExpression Parser::postfixExpression() { + AutoDepth depth(this); + DSLExpression result = this->term(); + if (!result.hasValue()) { + return {}; + } + for (;;) { + Token t = this->peek(); + switch (t.fKind) { + case Token::Kind::TK_FLOAT_LITERAL: + if (this->text(t)[0] != '.') { + return result; + } + [[fallthrough]]; + case Token::Kind::TK_LBRACKET: + case Token::Kind::TK_DOT: + case Token::Kind::TK_LPAREN: + case Token::Kind::TK_PLUSPLUS: + case Token::Kind::TK_MINUSMINUS: { + if (!depth.increase()) { + return {}; + } + DSLExpression next = this->suffix(std::move(result)); + if (!next.hasValue()) { + return {}; + } + result.swap(next); + break; + } + default: + return result; + } + } +} + +DSLExpression Parser::swizzle(Position pos, + DSLExpression base, + std::string_view swizzleMask, + Position maskPos) { + SkASSERT(swizzleMask.length() > 0); + if (!base.type().isVector() && !base.type().isScalar()) { + return base.field(swizzleMask, pos); + } + int length = swizzleMask.length(); + SkSL::SwizzleComponent::Type components[4]; + for (int i = 0; i < length; ++i) { + if (i >= 4) { + Position errorPos = maskPos.valid() ? Position::Range(maskPos.startOffset() + 4, + maskPos.endOffset()) + : pos; + this->error(errorPos, "too many components in swizzle mask"); + return DSLExpression::Poison(pos); + } + switch (swizzleMask[i]) { + case '0': components[i] = SwizzleComponent::ZERO; break; + case '1': components[i] = SwizzleComponent::ONE; break; + case 'r': components[i] = SwizzleComponent::R; break; + case 'x': components[i] = SwizzleComponent::X; break; + case 's': components[i] = SwizzleComponent::S; break; + case 'L': components[i] = SwizzleComponent::UL; break; + case 'g': components[i] = SwizzleComponent::G; break; + case 'y': components[i] = SwizzleComponent::Y; break; + case 't': components[i] = SwizzleComponent::T; break; + case 'T': components[i] = SwizzleComponent::UT; break; + case 'b': components[i] = SwizzleComponent::B; break; + case 'z': components[i] = SwizzleComponent::Z; break; + case 'p': components[i] = SwizzleComponent::P; break; + case 'R': components[i] = SwizzleComponent::UR; break; + case 'a': components[i] = SwizzleComponent::A; break; + case 'w': components[i] = SwizzleComponent::W; break; + case 'q': components[i] = SwizzleComponent::Q; break; + case 'B': components[i] = SwizzleComponent::UB; break; + default: { + Position componentPos = Position::Range(maskPos.startOffset() + i, + maskPos.startOffset() + i + 1); + this->error(componentPos, String::printf("invalid swizzle component '%c'", + swizzleMask[i]).c_str()); + return DSLExpression::Poison(pos); + } + } + } + switch (length) { + case 1: return dsl::Swizzle(std::move(base), components[0], pos, maskPos); + case 2: return dsl::Swizzle(std::move(base), components[0], components[1], pos, maskPos); + case 3: return dsl::Swizzle(std::move(base), components[0], components[1], components[2], + pos, maskPos); + case 4: return dsl::Swizzle(std::move(base), components[0], components[1], components[2], + components[3], pos, maskPos); + default: SkUNREACHABLE; + } +} + +dsl::DSLExpression Parser::call(Position pos, dsl::DSLExpression base, ExpressionArray args) { + return base(std::move(args), pos); +} + +/* LBRACKET expression? RBRACKET | DOT IDENTIFIER | LPAREN arguments RPAREN | + PLUSPLUS | MINUSMINUS | COLONCOLON IDENTIFIER | FLOAT_LITERAL [IDENTIFIER] */ +DSLExpression Parser::suffix(DSLExpression base) { + Token next = this->nextToken(); + AutoDepth depth(this); + if (!depth.increase()) { + return {}; + } + switch (next.fKind) { + case Token::Kind::TK_LBRACKET: { + if (this->checkNext(Token::Kind::TK_RBRACKET)) { + this->error(this->rangeFrom(next), "missing index in '[]'"); + return DSLExpression::Poison(this->rangeFrom(base.position())); + } + DSLExpression index = this->expression(); + if (!index.hasValue()) { + return {}; + } + this->expect(Token::Kind::TK_RBRACKET, "']' to complete array access expression"); + return base.index(std::move(index), this->rangeFrom(base.position())); + } + case Token::Kind::TK_DOT: { + std::string_view text; + if (this->identifier(&text)) { + Position pos = this->rangeFrom(base.position()); + return this->swizzle(pos, std::move(base), text, + this->rangeFrom(this->position(next).after())); + } + [[fallthrough]]; + } + case Token::Kind::TK_FLOAT_LITERAL: { + // Swizzles that start with a constant number, e.g. '.000r', will be tokenized as + // floating point literals, possibly followed by an identifier. Handle that here. + std::string_view field = this->text(next); + SkASSERT(field[0] == '.'); + field.remove_prefix(1); + // use the next *raw* token so we don't ignore whitespace - we only care about + // identifiers that directly follow the float + Position pos = this->rangeFrom(base.position()); + Position start = this->position(next); + // skip past the "." + start = Position::Range(start.startOffset() + 1, start.endOffset()); + Position maskPos = this->rangeFrom(start); + Token id = this->nextRawToken(); + if (id.fKind == Token::Kind::TK_IDENTIFIER) { + pos = this->rangeFrom(base.position()); + maskPos = this->rangeFrom(start); + return this->swizzle(pos, std::move(base), std::string(field) + + std::string(this->text(id)), maskPos); + } else if (field.empty()) { + this->error(pos, "expected field name or swizzle mask after '.'"); + return {{DSLExpression::Poison(pos)}}; + } + this->pushback(id); + return this->swizzle(pos, std::move(base), field, maskPos); + } + case Token::Kind::TK_LPAREN: { + ExpressionArray args; + if (this->peek().fKind != Token::Kind::TK_RPAREN) { + for (;;) { + DSLExpression expr = this->assignmentExpression(); + if (!expr.hasValue()) { + return {}; + } + args.push_back(expr.release()); + if (!this->checkNext(Token::Kind::TK_COMMA)) { + break; + } + } + } + this->expect(Token::Kind::TK_RPAREN, "')' to complete function arguments"); + Position pos = this->rangeFrom(base.position()); + return this->call(pos, std::move(base), std::move(args)); + } + case Token::Kind::TK_PLUSPLUS: + return base.postfix(Operator::Kind::PLUSPLUS, this->rangeFrom(base.position())); + case Token::Kind::TK_MINUSMINUS: + return base.postfix(Operator::Kind::MINUSMINUS, this->rangeFrom(base.position())); + default: { + this->error(next, "expected expression suffix, but found '" + + std::string(this->text(next)) + "'"); + return {}; + } + } +} + +/* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | '(' expression ')' */ +DSLExpression Parser::term() { + Token t = this->peek(); + switch (t.fKind) { + case Token::Kind::TK_IDENTIFIER: { + std::string_view text; + if (this->identifier(&text)) { + Position pos = this->position(t); + return DSLExpression(fCompiler.convertIdentifier(pos, text), pos); + } + break; + } + case Token::Kind::TK_INT_LITERAL: { + SKSL_INT i; + if (!this->intLiteral(&i)) { + i = 0; + } + return DSLExpression(i, this->position(t)); + } + case Token::Kind::TK_FLOAT_LITERAL: { + SKSL_FLOAT f; + if (!this->floatLiteral(&f)) { + f = 0.0f; + } + return DSLExpression(f, this->position(t)); + } + case Token::Kind::TK_TRUE_LITERAL: // fall through + case Token::Kind::TK_FALSE_LITERAL: { + bool b; + SkAssertResult(this->boolLiteral(&b)); + return DSLExpression(b, this->position(t)); + } + case Token::Kind::TK_LPAREN: { + this->nextToken(); + AutoDepth depth(this); + if (!depth.increase()) { + return {}; + } + DSLExpression result = this->expression(); + if (result.hasValue()) { + this->expect(Token::Kind::TK_RPAREN, "')' to complete expression"); + result.setPosition(this->rangeFrom(this->position(t))); + return result; + } + break; + } + default: + this->nextToken(); + this->error(t, "expected expression, but found '" + std::string(this->text(t)) + "'"); + fEncounteredFatalError = true; + break; + } + return {}; +} + +/* INT_LITERAL */ +bool Parser::intLiteral(SKSL_INT* dest) { + Token t; + if (!this->expect(Token::Kind::TK_INT_LITERAL, "integer literal", &t)) { + return false; + } + std::string_view s = this->text(t); + if (!SkSL::stoi(s, dest)) { + this->error(t, "integer is too large: " + std::string(s)); + return false; + } + return true; +} + +/* FLOAT_LITERAL */ +bool Parser::floatLiteral(SKSL_FLOAT* dest) { + Token t; + if (!this->expect(Token::Kind::TK_FLOAT_LITERAL, "float literal", &t)) { + return false; + } + std::string_view s = this->text(t); + if (!SkSL::stod(s, dest)) { + this->error(t, "floating-point value is too large: " + std::string(s)); + return false; + } + return true; +} + +/* TRUE_LITERAL | FALSE_LITERAL */ +bool Parser::boolLiteral(bool* dest) { + Token t = this->nextToken(); + switch (t.fKind) { + case Token::Kind::TK_TRUE_LITERAL: + *dest = true; + return true; + case Token::Kind::TK_FALSE_LITERAL: + *dest = false; + return true; + default: + this->error(t, "expected 'true' or 'false', but found '" + + std::string(this->text(t)) + "'"); + return false; + } +} + +/* IDENTIFIER */ +bool Parser::identifier(std::string_view* dest) { + Token t; + if (this->expect(Token::Kind::TK_IDENTIFIER, "identifier", &t)) { + *dest = this->text(t); + return true; + } + return false; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLParser.h b/gfx/skia/skia/src/sksl/SkSLParser.h new file mode 100644 index 0000000000..74909f5e94 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLParser.h @@ -0,0 +1,369 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PARSER +#define SKSL_PARSER + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/DSLCore.h" +#include "include/sksl/DSLExpression.h" +#include "include/sksl/DSLLayout.h" +#include "include/sksl/DSLModifiers.h" +#include "include/sksl/DSLStatement.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLLexer.h" +#include "src/sksl/SkSLProgramSettings.h" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <string_view> + +namespace SkSL { + +class Compiler; +class SymbolTable; +enum class ProgramKind : int8_t; +struct Module; +struct Program; + +namespace dsl { +class DSLBlock; +class DSLCase; +class DSLGlobalVar; +class DSLParameter; +class DSLVarBase; +} + +/** + * Consumes .sksl text and invokes DSL functions to instantiate the program. + */ +class Parser { +public: + Parser(Compiler* compiler, const ProgramSettings& settings, ProgramKind kind, std::string text); + + std::unique_ptr<Program> program(); + + std::unique_ptr<Module> moduleInheritingFrom(const Module* parent); + + std::string_view text(Token token); + + Position position(Token token); + +private: + class AutoDepth; + class AutoSymbolTable; + + /** + * Return the next token, including whitespace tokens, from the parse stream. + */ + Token nextRawToken(); + + /** + * Return the next non-whitespace token from the parse stream. + */ + Token nextToken(); + + /** + * Push a token back onto the parse stream, so that it is the next one read. Only a single level + * of pushback is supported (that is, it is an error to call pushback() twice in a row without + * an intervening nextToken()). + */ + void pushback(Token t); + + /** + * Returns the next non-whitespace token without consuming it from the stream. + */ + Token peek(); + + /** + * Checks to see if the next token is of the specified type. If so, stores it in result (if + * result is non-null) and returns true. Otherwise, pushes it back and returns false. + */ + bool checkNext(Token::Kind kind, Token* result = nullptr); + + /** + * Behaves like checkNext(TK_IDENTIFIER), but also verifies that identifier is not a builtin + * type. If the token was actually a builtin type, false is returned (the next token is not + * considered to be an identifier). + */ + bool checkIdentifier(Token* result = nullptr); + + /** + * Reads the next non-whitespace token and generates an error if it is not the expected type. + * The 'expected' string is part of the error message, which reads: + * + * "expected <expected>, but found '<actual text>'" + * + * If 'result' is non-null, it is set to point to the token that was read. + * Returns true if the read token was as expected, false otherwise. + */ + bool expect(Token::Kind kind, const char* expected, Token* result = nullptr); + bool expect(Token::Kind kind, std::string expected, Token* result = nullptr); + + /** + * Behaves like expect(TK_IDENTIFIER), but also verifies that identifier is not a type. + * If the token was actually a type, generates an error message of the form: + * + * "expected an identifier, but found type 'float2'" + */ + bool expectIdentifier(Token* result); + + /** If the next token is a newline, consumes it and returns true. If not, returns false. */ + bool expectNewline(); + + void error(Token token, std::string_view msg); + void error(Position position, std::string_view msg); + + // Returns the range from `start` to the current parse position. + Position rangeFrom(Position start); + Position rangeFrom(Token start); + + // these functions parse individual grammar rules from the current parse position; you probably + // don't need to call any of these outside of the parser. The function declarations in the .cpp + // file have comments describing the grammar rules. + + void declarations(); + + /** + * Parses an expression representing an array size. Reports errors if the array size is not + * valid (out of bounds, not a literal integer). Returns true if an expression was + * successfully parsed, even if that array size is not actually valid. In the event of a true + * return, outResult always contains a valid array size (even if the parsed array size was not + * actually valid; invalid array sizes result in a 1 to avoid additional errors downstream). + */ + bool arraySize(SKSL_INT* outResult); + + void directive(bool allowVersion); + + bool declaration(); + + bool functionDeclarationEnd(Position start, + dsl::DSLModifiers& modifiers, + dsl::DSLType type, + const Token& name); + + struct VarDeclarationsPrefix { + Position fPosition; + dsl::DSLModifiers fModifiers; + dsl::DSLType fType = dsl::DSLType(dsl::kVoid_Type); + Token fName; + }; + + bool varDeclarationsPrefix(VarDeclarationsPrefix* prefixData); + + dsl::DSLStatement varDeclarationsOrExpressionStatement(); + + dsl::DSLStatement varDeclarations(); + + dsl::DSLType structDeclaration(); + + SkTArray<dsl::DSLGlobalVar> structVarDeclaration(Position start, + const dsl::DSLModifiers& modifiers); + + bool allowUnsizedArrays() { + return ProgramConfig::IsCompute(fKind) || ProgramConfig::IsFragment(fKind) || + ProgramConfig::IsVertex(fKind); + } + + bool parseArrayDimensions(Position pos, dsl::DSLType* type); + + bool parseInitializer(Position pos, dsl::DSLExpression* initializer); + + void globalVarDeclarationEnd(Position position, const dsl::DSLModifiers& mods, + dsl::DSLType baseType, Token name); + + dsl::DSLStatement localVarDeclarationEnd(Position position, const dsl::DSLModifiers& mods, + dsl::DSLType baseType, Token name); + + std::optional<dsl::DSLParameter> parameter(size_t paramIndex); + + int layoutInt(); + + std::string_view layoutIdentifier(); + + dsl::DSLLayout layout(); + + dsl::DSLModifiers modifiers(); + + dsl::DSLStatement statement(); + + dsl::DSLType type(dsl::DSLModifiers* modifiers); + + bool interfaceBlock(const dsl::DSLModifiers& mods); + + dsl::DSLStatement ifStatement(); + + dsl::DSLStatement doStatement(); + + dsl::DSLStatement whileStatement(); + + dsl::DSLStatement forStatement(); + + std::optional<dsl::DSLCase> switchCase(); + + dsl::DSLStatement switchStatement(); + + dsl::DSLStatement returnStatement(); + + dsl::DSLStatement breakStatement(); + + dsl::DSLStatement continueStatement(); + + dsl::DSLStatement discardStatement(); + + std::optional<dsl::DSLBlock> block(); + + dsl::DSLStatement expressionStatement(); + + using BinaryParseFn = dsl::DSLExpression (Parser::*)(); + bool SK_WARN_UNUSED_RESULT operatorRight(AutoDepth& depth, Operator::Kind op, + BinaryParseFn rightFn, dsl::DSLExpression& result); + + dsl::DSLExpression expression(); + + dsl::DSLExpression assignmentExpression(); + + dsl::DSLExpression ternaryExpression(); + + dsl::DSLExpression logicalOrExpression(); + + dsl::DSLExpression logicalXorExpression(); + + dsl::DSLExpression logicalAndExpression(); + + dsl::DSLExpression bitwiseOrExpression(); + + dsl::DSLExpression bitwiseXorExpression(); + + dsl::DSLExpression bitwiseAndExpression(); + + dsl::DSLExpression equalityExpression(); + + dsl::DSLExpression relationalExpression(); + + dsl::DSLExpression shiftExpression(); + + dsl::DSLExpression additiveExpression(); + + dsl::DSLExpression multiplicativeExpression(); + + dsl::DSLExpression unaryExpression(); + + dsl::DSLExpression postfixExpression(); + + dsl::DSLExpression swizzle(Position pos, dsl::DSLExpression base, + std::string_view swizzleMask, Position maskPos); + + dsl::DSLExpression call(Position pos, dsl::DSLExpression base, ExpressionArray args); + + dsl::DSLExpression suffix(dsl::DSLExpression base); + + dsl::DSLExpression term(); + + bool intLiteral(SKSL_INT* dest); + + bool floatLiteral(SKSL_FLOAT* dest); + + bool boolLiteral(bool* dest); + + bool identifier(std::string_view* dest); + + std::shared_ptr<SymbolTable>& symbolTable(); + + void addToSymbolTable(dsl::DSLVarBase& var, Position pos = {}); + + class Checkpoint { + public: + Checkpoint(Parser* p) : fParser(p) { + fPushbackCheckpoint = fParser->fPushback; + fLexerCheckpoint = fParser->fLexer.getCheckpoint(); + fOldErrorReporter = &dsl::GetErrorReporter(); + fOldEncounteredFatalError = fParser->fEncounteredFatalError; + SkASSERT(fOldErrorReporter); + dsl::SetErrorReporter(&fErrorReporter); + } + + ~Checkpoint() { + SkASSERTF(!fOldErrorReporter, + "Checkpoint was not accepted or rewound before destruction"); + } + + void accept() { + this->restoreErrorReporter(); + // Parser errors should have been fatal, but we can encounter other errors like type + // mismatches despite accepting the parse. Forward those messages to the actual error + // handler now. + fErrorReporter.forwardErrors(); + } + + void rewind() { + this->restoreErrorReporter(); + fParser->fPushback = fPushbackCheckpoint; + fParser->fLexer.rewindToCheckpoint(fLexerCheckpoint); + fParser->fEncounteredFatalError = fOldEncounteredFatalError; + } + + private: + class ForwardingErrorReporter : public ErrorReporter { + public: + void handleError(std::string_view msg, Position pos) override { + fErrors.push_back({std::string(msg), pos}); + } + + void forwardErrors() { + for (Error& error : fErrors) { + dsl::GetErrorReporter().error(error.fPos, error.fMsg); + } + } + + private: + struct Error { + std::string fMsg; + Position fPos; + }; + + SkTArray<Error> fErrors; + }; + + void restoreErrorReporter() { + SkASSERT(fOldErrorReporter); + dsl::SetErrorReporter(fOldErrorReporter); + fOldErrorReporter = nullptr; + } + + Parser* fParser; + Token fPushbackCheckpoint; + SkSL::Lexer::Checkpoint fLexerCheckpoint; + ForwardingErrorReporter fErrorReporter; + ErrorReporter* fOldErrorReporter; + bool fOldEncounteredFatalError; + }; + + Compiler& fCompiler; + ProgramSettings fSettings; + ErrorReporter* fErrorReporter; + bool fEncounteredFatalError; + ProgramKind fKind; + std::unique_ptr<std::string> fText; + Lexer fLexer; + // current parse depth, used to enforce a recursion limit to try to keep us from overflowing the + // stack on pathological inputs + int fDepth = 0; + Token fPushback; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLPool.cpp b/gfx/skia/skia/src/sksl/SkSLPool.cpp new file mode 100644 index 0000000000..5ef5d0065e --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLPool.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLPool.h" + +#include "include/core/SkTypes.h" + +#if defined(SK_GANESH) +// With GPU support, SkSL::MemoryPool is really GrMemoryPool +#include "src/gpu/ganesh/GrMemoryPool.h" +#endif + +#define VLOG(...) // printf(__VA_ARGS__) + +namespace SkSL { + +static thread_local MemoryPool* sMemPool = nullptr; + +static MemoryPool* get_thread_local_memory_pool() { + return sMemPool; +} + +static void set_thread_local_memory_pool(MemoryPool* memPool) { + sMemPool = memPool; +} + +Pool::~Pool() { + if (get_thread_local_memory_pool() == fMemPool.get()) { + SkDEBUGFAIL("SkSL pool is being destroyed while it is still attached to the thread"); + set_thread_local_memory_pool(nullptr); + } + + fMemPool->reportLeaks(); + SkASSERT(fMemPool->isEmpty()); + + VLOG("DELETE Pool:0x%016llX\n", (uint64_t)fMemPool.get()); +} + +std::unique_ptr<Pool> Pool::Create() { + auto pool = std::unique_ptr<Pool>(new Pool); + pool->fMemPool = MemoryPool::Make(/*preallocSize=*/65536, /*minAllocSize=*/32768); + VLOG("CREATE Pool:0x%016llX\n", (uint64_t)pool->fMemPool.get()); + return pool; +} + +bool Pool::IsAttached() { + return get_thread_local_memory_pool(); +} + +void Pool::attachToThread() { + VLOG("ATTACH Pool:0x%016llX\n", (uint64_t)fMemPool.get()); + SkASSERT(get_thread_local_memory_pool() == nullptr); + set_thread_local_memory_pool(fMemPool.get()); +} + +void Pool::detachFromThread() { + MemoryPool* memPool = get_thread_local_memory_pool(); + VLOG("DETACH Pool:0x%016llX\n", (uint64_t)memPool); + SkASSERT(memPool == fMemPool.get()); + memPool->resetScratchSpace(); + set_thread_local_memory_pool(nullptr); +} + +void* Pool::AllocMemory(size_t size) { + // Is a pool attached? + MemoryPool* memPool = get_thread_local_memory_pool(); + if (memPool) { + void* ptr = memPool->allocate(size); + VLOG("ALLOC Pool:0x%016llX 0x%016llX\n", (uint64_t)memPool, (uint64_t)ptr); + return ptr; + } + + // There's no pool attached. Allocate memory using the system allocator. + void* ptr = ::operator new(size); + VLOG("ALLOC Pool:__________________ 0x%016llX\n", (uint64_t)ptr); + return ptr; +} + +void Pool::FreeMemory(void* ptr) { + // Is a pool attached? + MemoryPool* memPool = get_thread_local_memory_pool(); + if (memPool) { + VLOG("FREE Pool:0x%016llX 0x%016llX\n", (uint64_t)memPool, (uint64_t)ptr); + memPool->release(ptr); + return; + } + + // There's no pool attached. Free it using the system allocator. + VLOG("FREE Pool:__________________ 0x%016llX\n", (uint64_t)ptr); + ::operator delete(ptr); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLPool.h b/gfx/skia/skia/src/sksl/SkSLPool.h new file mode 100644 index 0000000000..9e64d44b9a --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLPool.h @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_POOL +#define SKSL_POOL + +#include "src/sksl/SkSLMemoryPool.h" + +#include <cstddef> +#include <memory> + +namespace SkSL { + +/** + * Efficiently allocates memory in an SkSL program. Optimized for allocate/release performance over + * memory efficiency. + * + * All allocated memory must be released back to the pool before it can be destroyed or recycled. + */ + +class Pool { +public: + ~Pool(); + + // Creates a pool to store objects during program creation. Call attachToThread() to start using + // the pool for its allocations. When your program is complete, call pool->detachFromThread() to + // take ownership of the pool and its allocations. Before freeing any of the program's + // allocations, make sure to reattach the pool by calling pool->attachToThread() again. + static std::unique_ptr<Pool> Create(); + + // Attaches a pool to the current thread. + // It is an error to call this while a pool is already attached. + void attachToThread(); + + // Once you are done creating or destroying objects in the pool, detach it from the thread. + // It is an error to call this while no pool is attached. + void detachFromThread(); + + // Allocates memory from the thread pool. If the pool is exhausted, an additional block of pool + // storage will be created to hold the data. + static void* AllocMemory(size_t size); + + // Releases memory that was created by AllocMemory. All objects in the pool must be freed before + // the pool can be destroyed. + static void FreeMemory(void* ptr); + + static bool IsAttached(); + +private: + Pool() = default; // use Create to make a pool + std::unique_ptr<SkSL::MemoryPool> fMemPool; +}; + +/** + * If your class inherits from Poolable, its objects will be allocated from the pool. + */ +class Poolable { +public: + // Override operator new and delete to allow us to use a memory pool. + static void* operator new(const size_t size) { + return Pool::AllocMemory(size); + } + + static void operator delete(void* ptr) { + Pool::FreeMemory(ptr); + } +}; + +/** + * Temporarily attaches a pool to the current thread within a scope. + */ +class AutoAttachPoolToThread { +public: + AutoAttachPoolToThread(Pool* p) : fPool(p) { + if (fPool) { + fPool->attachToThread(); + } + } + ~AutoAttachPoolToThread() { + if (fPool) { + fPool->detachFromThread(); + } + } + +private: + Pool* fPool = nullptr; +}; + + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLPosition.cpp b/gfx/skia/skia/src/sksl/SkSLPosition.cpp new file mode 100644 index 0000000000..494accaf7b --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLPosition.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2022 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/SkSLPosition.h" + +#include <algorithm> + +namespace SkSL { + +int Position::line(std::string_view source) const { + SkASSERT(this->valid()); + if (fStartOffset == -1) { + return -1; + } + if (!source.data()) { + return -1; + } + // we allow the offset to equal the length, because that's where TK_END_OF_FILE is reported + SkASSERT(fStartOffset <= (int)source.length()); + int offset = std::min(fStartOffset, (int)source.length()); + int line = 1; + for (int i = 0; i < offset; i++) { + if (source[i] == '\n') { + ++line; + } + } + return line; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLProgramSettings.h b/gfx/skia/skia/src/sksl/SkSLProgramSettings.h new file mode 100644 index 0000000000..48622bb0c3 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLProgramSettings.h @@ -0,0 +1,160 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PROGRAMSETTINGS +#define SKSL_PROGRAMSETTINGS + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLProgramKind.h" +#include "include/sksl/SkSLVersion.h" + +#include <vector> + +namespace SkSL { + +/** + * Holds the compiler settings for a program. + */ +struct ProgramSettings { + // If true, the destination fragment color can be read from sk_FragColor. It must be declared + // inout. This is only supported in GLSL, when framebuffer-fetch is used. + bool fFragColorIsInOut = false; + // if true, all halfs are forced to be floats + bool fForceHighPrecision = false; + // if true, add -0.5 bias to LOD of all texture lookups + bool fSharpenTextures = false; + // If true, sk_FragCoord, the dFdy gradient, and sk_Clockwise won't be modified by the + // rtFlip. Additionally, the 'fUseFlipRTUniform' boolean will be forced to false so no rtFlip + // uniform will be emitted. + bool fForceNoRTFlip = false; + // if the program needs to create an RTFlip uniform, this is its offset in the uniform buffer + int fRTFlipOffset = -1; + // if the program needs to create an RTFlip uniform and is creating SPIR-V, this is the binding + // and set number of the uniform buffer. + int fRTFlipBinding = -1; + int fRTFlipSet = -1; + // If layout(set=S, binding=B) is not specified for a uniform, these values will be used. + // At present, zero is always used by our backends. + int fDefaultUniformSet = 0; + int fDefaultUniformBinding = 0; + // Enables the SkSL optimizer. Note that we never disable optimizations which are needed to + // fully evaluate constant-expressions, like constant folding or constant-intrinsic evaluation. + bool fOptimize = true; + // (Requires fOptimize = true) Removes any uncalled functions other than main(). Note that a + // function which starts out being used may end up being uncalled after optimization. + bool fRemoveDeadFunctions = true; + // (Requires fOptimize = true) Removes variables which are never used. + bool fRemoveDeadVariables = true; + // (Requires fOptimize = true) When greater than zero, enables the inliner. The threshold value + // sets an upper limit on the acceptable amount of code growth from inlining. + int fInlineThreshold = SkSL::kDefaultInlineThreshold; + // If true, every function in the generated program will be given the `noinline` modifier. + bool fForceNoInline = false; + // If true, implicit conversions to lower precision numeric types are allowed (e.g., float to + // half). These are always allowed when compiling Runtime Effects. + bool fAllowNarrowingConversions = false; + // If true, then Debug code will run SPIR-V output through the validator to ensure its + // correctness + bool fValidateSPIRV = true; + // If true, any synthetic uniforms must use push constant syntax + bool fUsePushConstants = false; + // TODO(skia:11209) - Replace this with a "promised" capabilities? + // Sets a maximum SkSL version. Compilation will fail if the program uses features that aren't + // allowed at the requested version. For instance, a valid program must have fully-unrollable + // `for` loops at version 100, but any loop structure is allowed at version 300. + SkSL::Version fMaxVersionAllowed = SkSL::Version::k100; + // If true, SkVM debug traces will contain the `trace_var` opcode. This opcode can cause the + // generated code to contain a lot of extra computations, because we need to explicitly compute + // every temporary value, even ones that would otherwise be optimized away entirely. The other + // debug opcodes are much less invasive on the generated code. + bool fAllowTraceVarInSkVMDebugTrace = true; + // If true, SkSL will use a memory pool for all IR nodes when compiling a program. This is + // usually a significant speed increase, but uses more memory, so it is a good idea for programs + // that will be freed shortly after compilation. It can also be useful to disable this flag when + // investigating memory corruption. (This controls behavior of the SkSL compiler, not the code + // we generate.) + bool fUseMemoryPool = true; + // If true, VarDeclaration can be cloned for testing purposes. See VarDeclaration::clone for + // more information. + bool fAllowVarDeclarationCloneForTesting = false; + // If true, SPIR-V codegen restricted to a subset supported by Dawn. + // TODO(skia:13840, skia:14023): Remove this setting when Skia can use WGSL on Dawn. + bool fSPIRVDawnCompatMode = false; +}; + +/** + * All the configuration data for a given program. + */ +struct ProgramConfig { + /** True if we are currently processing one of the built-in SkSL include modules. */ + bool fIsBuiltinCode; + ProgramKind fKind; + ProgramSettings fSettings; + + // When enforcesSkSLVersion() is true, this determines the available feature set that will be + // enforced. This is set automatically when the `#version` directive is parsed. + SkSL::Version fRequiredSkSLVersion = SkSL::Version::k100; + + bool enforcesSkSLVersion() const { + return IsRuntimeEffect(fKind); + } + + bool strictES2Mode() const { + // TODO(skia:11209): Remove the first condition - so this is just based on #version. + // Make it more generic (eg, isVersionLT) checking. + return fSettings.fMaxVersionAllowed == Version::k100 && + fRequiredSkSLVersion == Version::k100 && + this->enforcesSkSLVersion(); + } + + const char* versionDescription() const { + if (this->enforcesSkSLVersion()) { + switch (fRequiredSkSLVersion) { + case Version::k100: return "#version 100\n"; + case Version::k300: return "#version 300\n"; + } + } + return ""; + } + + static bool IsFragment(ProgramKind kind) { + return kind == ProgramKind::kFragment || + kind == ProgramKind::kGraphiteFragment; + } + + static bool IsVertex(ProgramKind kind) { + return kind == ProgramKind::kVertex || + kind == ProgramKind::kGraphiteVertex; + } + + static bool IsCompute(ProgramKind kind) { + return kind == ProgramKind::kCompute; + } + + static bool IsRuntimeEffect(ProgramKind kind) { + return (kind == ProgramKind::kRuntimeColorFilter || + kind == ProgramKind::kRuntimeShader || + kind == ProgramKind::kRuntimeBlender || + kind == ProgramKind::kPrivateRuntimeColorFilter || + kind == ProgramKind::kPrivateRuntimeShader || + kind == ProgramKind::kPrivateRuntimeBlender || + kind == ProgramKind::kMeshVertex || + kind == ProgramKind::kMeshFragment); + } + + static bool AllowsPrivateIdentifiers(ProgramKind kind) { + return (kind != ProgramKind::kRuntimeColorFilter && + kind != ProgramKind::kRuntimeShader && + kind != ProgramKind::kRuntimeBlender && + kind != ProgramKind::kMeshVertex && + kind != ProgramKind::kMeshFragment); + } +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLSampleUsage.cpp b/gfx/skia/skia/src/sksl/SkSLSampleUsage.cpp new file mode 100644 index 0000000000..9b908b35e8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLSampleUsage.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLSampleUsage.h" + +#include <algorithm> + +namespace SkSL { + +SampleUsage SampleUsage::merge(const SampleUsage& other) { + // This function is only used in Analysis::MergeSampleUsageVisitor to determine the combined + // SampleUsage for a child fp/shader/etc. We should never see matrix sampling here. + SkASSERT(fKind != Kind::kUniformMatrix && other.fKind != Kind::kUniformMatrix); + + static_assert(Kind::kExplicit > Kind::kPassThrough); + static_assert(Kind::kPassThrough > Kind::kNone); + fKind = std::max(fKind, other.fKind); + + return *this; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLString.cpp b/gfx/skia/skia/src/sksl/SkSLString.cpp new file mode 100644 index 0000000000..c746ff2823 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLString.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkAssert.h" +#include "src/base/SkStringView.h" + +#include <cerrno> +#include <cmath> +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <locale> +#include <memory> +#include <sstream> +#include <string> +#include <string_view> + +template <typename RoundtripType, int kFullPrecision> +static std::string to_string_impl(RoundtripType value) { + std::stringstream buffer; + buffer.imbue(std::locale::classic()); + buffer.precision(7); + buffer << value; + std::string text = buffer.str(); + + double roundtripped; + buffer >> roundtripped; + if (value != (RoundtripType)roundtripped && std::isfinite(value)) { + buffer.str({}); + buffer.clear(); + buffer.precision(kFullPrecision); + buffer << value; + text = buffer.str(); + SkASSERTF((buffer >> roundtripped, value == (RoundtripType)roundtripped), + "%.17g -> %s -> %.17g", value, text.c_str(), roundtripped); + } + + // We need to emit a decimal point to distinguish floats from ints. + if (!skstd::contains(text, '.') && !skstd::contains(text, 'e')) { + text += ".0"; + } + + return text; +} + +std::string skstd::to_string(float value) { + return to_string_impl<float, 9>(value); +} + +std::string skstd::to_string(double value) { + return to_string_impl<double, 17>(value); +} + +bool SkSL::stod(std::string_view s, SKSL_FLOAT* value) { + std::string str(s.data(), s.size()); + std::stringstream buffer(str); + buffer.imbue(std::locale::classic()); + buffer >> *value; + return !buffer.fail() && std::isfinite(*value); +} + +bool SkSL::stoi(std::string_view s, SKSL_INT* value) { + if (s.empty()) { + return false; + } + char suffix = s.back(); + if (suffix == 'u' || suffix == 'U') { + s.remove_suffix(1); + } + std::string str(s); // s is not null-terminated + const char* strEnd = str.data() + str.length(); + char* p; + errno = 0; + unsigned long long result = strtoull(str.data(), &p, /*base=*/0); + *value = static_cast<SKSL_INT>(result); + return p == strEnd && errno == 0 && result <= 0xFFFFFFFF; +} + +std::string SkSL::String::printf(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + std::string result; + vappendf(&result, fmt, args); + va_end(args); + return result; +} + +void SkSL::String::appendf(std::string *str, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + vappendf(str, fmt, args); + va_end(args); +} + +void SkSL::String::vappendf(std::string *str, const char* fmt, va_list args) { + #define BUFFER_SIZE 256 + char buffer[BUFFER_SIZE]; + va_list reuse; + va_copy(reuse, args); + size_t size = vsnprintf(buffer, BUFFER_SIZE, fmt, args); + if (BUFFER_SIZE >= size + 1) { + str->append(buffer, size); + } else { + auto newBuffer = std::unique_ptr<char[]>(new char[size + 1]); + vsnprintf(newBuffer.get(), size + 1, fmt, reuse); + str->append(newBuffer.get(), size); + } + va_end(reuse); +} diff --git a/gfx/skia/skia/src/sksl/SkSLStringStream.h b/gfx/skia/skia/src/sksl/SkSLStringStream.h new file mode 100644 index 0000000000..aabda3894e --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLStringStream.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_STRINGSTREAM +#define SKSL_STRINGSTREAM + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "src/sksl/SkSLOutputStream.h" + +namespace SkSL { + +class StringStream : public OutputStream { +public: + void write8(uint8_t b) override { + SkASSERT(fString.empty()); + fStream.write8(b); + } + + void writeText(const char* s) override { + SkASSERT(fString.empty()); + fStream.writeText(s); + } + + void write(const void* s, size_t size) override { + SkASSERT(fString.empty()); + fStream.write(s, size); + } + + size_t bytesWritten() const { + return fStream.bytesWritten(); + } + + const std::string& str() const { + if (!fString.size()) { + sk_sp<SkData> data = fStream.detachAsData(); + fString = std::string((const char*) data->data(), data->size()); + } + return fString; + } + + void reset() { + fStream.reset(); + fString = ""; + } + +private: + mutable SkDynamicMemoryWStream fStream; + mutable std::string fString; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLThreadContext.cpp b/gfx/skia/skia/src/sksl/SkSLThreadContext.cpp new file mode 100644 index 0000000000..58d9b40d83 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLThreadContext.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLThreadContext.h" + +#include "include/private/SkSLProgramElement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLPool.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#include <type_traits> + +namespace SkSL { + +ThreadContext::ThreadContext(SkSL::Compiler* compiler, + SkSL::ProgramKind kind, + const SkSL::ProgramSettings& settings, + const SkSL::Module* module, + bool isModule) + : fCompiler(compiler) + , fOldConfig(fCompiler->fContext->fConfig) + , fOldModifiersPool(fCompiler->fContext->fModifiersPool) + , fOldErrorReporter(*fCompiler->fContext->fErrors) + , fSettings(settings) { + if (!isModule) { + if (settings.fUseMemoryPool) { + fPool = Pool::Create(); + fPool->attachToThread(); + } + fModifiersPool = std::make_unique<SkSL::ModifiersPool>(); + fCompiler->fContext->fModifiersPool = fModifiersPool.get(); + } + + fConfig = std::make_unique<SkSL::ProgramConfig>(); + fConfig->fKind = kind; + fConfig->fSettings = settings; + fConfig->fIsBuiltinCode = isModule; + fCompiler->fContext->fConfig = fConfig.get(); + fCompiler->fContext->fErrors = &fDefaultErrorReporter; + fCompiler->fContext->fModule = module; + fCompiler->fSymbolTable = module->fSymbols; + this->setupSymbolTable(); +} + +ThreadContext::~ThreadContext() { + if (SymbolTable()) { + fCompiler->fSymbolTable = nullptr; + fProgramElements.clear(); + } else { + // We should only be here with a null symbol table if ReleaseProgram was called + SkASSERT(fProgramElements.empty()); + } + fCompiler->fContext->fErrors = &fOldErrorReporter; + fCompiler->fContext->fConfig = fOldConfig; + fCompiler->fContext->fModifiersPool = fOldModifiersPool; + if (fPool) { + fPool->detachFromThread(); + } +} + +void ThreadContext::setupSymbolTable() { + SkSL::Context& context = *fCompiler->fContext; + SymbolTable::Push(&fCompiler->fSymbolTable, context.fConfig->fIsBuiltinCode); + + SkSL::SymbolTable& symbolTable = *fCompiler->fSymbolTable; + symbolTable.markModuleBoundary(); +} + +SkSL::Context& ThreadContext::Context() { + return Compiler().context(); +} + +const SkSL::ProgramSettings& ThreadContext::Settings() { + return Context().fConfig->fSettings; +} + +std::shared_ptr<SkSL::SymbolTable>& ThreadContext::SymbolTable() { + return Compiler().fSymbolTable; +} + +const SkSL::Modifiers* ThreadContext::Modifiers(const SkSL::Modifiers& modifiers) { + return Context().fModifiersPool->add(modifiers); +} + +ThreadContext::RTAdjustData& ThreadContext::RTAdjustState() { + return Instance().fRTAdjust; +} + +void ThreadContext::SetErrorReporter(ErrorReporter* errorReporter) { + SkASSERT(errorReporter); + Context().fErrors = errorReporter; +} + +void ThreadContext::ReportError(std::string_view msg, Position pos) { + GetErrorReporter().error(pos, msg); +} + +void ThreadContext::DefaultErrorReporter::handleError(std::string_view msg, Position pos) { + SK_ABORT("error: %.*s\nNo SkSL error reporter configured, treating this as a fatal error\n", + (int)msg.length(), msg.data()); +} + +thread_local ThreadContext* instance = nullptr; + +bool ThreadContext::IsActive() { + return instance != nullptr; +} + +ThreadContext& ThreadContext::Instance() { + SkASSERTF(instance, "dsl::Start() has not been called"); + return *instance; +} + +void ThreadContext::SetInstance(std::unique_ptr<ThreadContext> newInstance) { + SkASSERT((instance == nullptr) != (newInstance == nullptr)); + delete instance; + instance = newInstance.release(); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLThreadContext.h b/gfx/skia/skia/src/sksl/SkSLThreadContext.h new file mode 100644 index 0000000000..853ec4ed98 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLThreadContext.h @@ -0,0 +1,177 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_THREADCONTEXT +#define SKSL_THREADCONTEXT + +#include "include/core/SkTypes.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLProgram.h" + +#include <cstdint> +#include <memory> +#include <string_view> +#include <vector> + +namespace SkSL { + +class Compiler; +class ModifiersPool; +class Pool; +class ProgramElement; +class SymbolTable; +class Variable; +enum class ProgramKind : int8_t; +struct Modifiers; +struct Module; + +namespace dsl { + +class DSLCore; + +} // namespace dsl + +/** + * Thread-safe class that tracks per-thread state associated with SkSL output. + */ +class ThreadContext { +public: + ThreadContext(SkSL::Compiler* compiler, + SkSL::ProgramKind kind, + const SkSL::ProgramSettings& settings, + const SkSL::Module* module, + bool isModule); + ~ThreadContext(); + + /** + * Returns true if the DSL has been started. + */ + static bool IsActive(); + + /** + * Returns the Compiler used by DSL operations in the current thread. + */ + static SkSL::Compiler& Compiler() { return *Instance().fCompiler; } + + /** + * Returns the Context used by DSL operations in the current thread. + */ + static SkSL::Context& Context(); + + /** + * Returns the Settings used by DSL operations in the current thread. + */ + static const SkSL::ProgramSettings& Settings(); + + /** + * Returns the Program::Inputs used by the current thread. + */ + static SkSL::Program::Inputs& Inputs() { return Instance().fInputs; } + + /** + * Returns the collection to which DSL program elements in this thread should be appended. + */ + static std::vector<std::unique_ptr<SkSL::ProgramElement>>& ProgramElements() { + return Instance().fProgramElements; + } + + static std::vector<const ProgramElement*>& SharedElements() { + return Instance().fSharedElements; + } + + /** + * Returns the current SymbolTable. + */ + static std::shared_ptr<SkSL::SymbolTable>& SymbolTable(); + + /** + * Returns the current memory pool. + */ + static std::unique_ptr<Pool>& MemoryPool() { return Instance().fPool; } + + /** + * Returns the current modifiers pool. + */ + static std::unique_ptr<ModifiersPool>& GetModifiersPool() { return Instance().fModifiersPool; } + + /** + * Returns the current ProgramConfig. + */ + static const std::unique_ptr<ProgramConfig>& GetProgramConfig() { return Instance().fConfig; } + + static bool IsModule() { return GetProgramConfig()->fIsBuiltinCode; } + + /** + * Returns the final pointer to a pooled Modifiers object that should be used to represent the + * given modifiers. + */ + static const SkSL::Modifiers* Modifiers(const SkSL::Modifiers& modifiers); + + struct RTAdjustData { + // Points to a standalone sk_RTAdjust variable, if one exists. + const Variable* fVar = nullptr; + // Points to the interface block containing an sk_RTAdjust field, if one exists. + const Variable* fInterfaceBlock = nullptr; + // If fInterfaceBlock is non-null, contains the index of the sk_RTAdjust field within it. + int fFieldIndex = -1; + }; + + /** + * Returns a struct containing information about the RTAdjust variable. + */ + static RTAdjustData& RTAdjustState(); + + /** + * Returns the ErrorReporter associated with the current thread. This object will be notified + * when any DSL errors occur. + */ + static ErrorReporter& GetErrorReporter() { + return *Context().fErrors; + } + + static void SetErrorReporter(ErrorReporter* errorReporter); + + /** + * Notifies the current ErrorReporter that an error has occurred. The default error handler + * prints the message to stderr and aborts. + */ + static void ReportError(std::string_view msg, Position pos = Position{}); + + static ThreadContext& Instance(); + + static void SetInstance(std::unique_ptr<ThreadContext> instance); + +private: + class DefaultErrorReporter : public ErrorReporter { + void handleError(std::string_view msg, Position pos) override; + }; + + void setupSymbolTable(); + + std::unique_ptr<SkSL::ProgramConfig> fConfig; + std::unique_ptr<SkSL::ModifiersPool> fModifiersPool; + SkSL::Compiler* fCompiler; + std::unique_ptr<Pool> fPool; + SkSL::ProgramConfig* fOldConfig; + SkSL::ModifiersPool* fOldModifiersPool; + std::vector<std::unique_ptr<SkSL::ProgramElement>> fProgramElements; + std::vector<const SkSL::ProgramElement*> fSharedElements; + DefaultErrorReporter fDefaultErrorReporter; + ErrorReporter& fOldErrorReporter; + ProgramSettings fSettings; + RTAdjustData fRTAdjust; + Program::Inputs fInputs; + + friend class dsl::DSLCore; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/SkSLUtil.cpp b/gfx/skia/skia/src/sksl/SkSLUtil.cpp new file mode 100644 index 0000000000..8efeb21790 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLUtil.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLUtil.h" + +#include "src/core/SkSLTypeShared.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/ir/SkSLType.h" + +#include <string> + +namespace SkSL { + +// TODO: Once Graphite has its own GPU-caps system, SK_GRAPHITE should get its own mode. +// At the moment, it either mimics what GrShaderCaps reports, or it uses these hard-coded values +// depending on the build. +#if defined(SKSL_STANDALONE) || !defined(SK_GANESH) +std::unique_ptr<ShaderCaps> ShaderCapsFactory::MakeShaderCaps() { + std::unique_ptr<ShaderCaps> standalone = std::make_unique<ShaderCaps>(); + standalone->fShaderDerivativeSupport = true; + standalone->fExplicitTextureLodSupport = true; + standalone->fFlatInterpolationSupport = true; + standalone->fNoPerspectiveInterpolationSupport = true; + standalone->fSampleMaskSupport = true; + standalone->fExternalTextureSupport = true; + return standalone; +} +#else +std::unique_ptr<ShaderCaps> ShaderCapsFactory::MakeShaderCaps() { + return std::make_unique<ShaderCaps>(); +} +#endif // defined(SKSL_STANDALONE) || !defined(SK_GANESH) + +void write_stringstream(const StringStream& s, OutputStream& out) { + out.write(s.str().c_str(), s.str().size()); +} + +#if !defined(SKSL_STANDALONE) && (defined(SK_GANESH) || SK_SUPPORT_GRAPHITE) +bool type_to_sksltype(const Context& context, const Type& type, SkSLType* outType) { + // If a new GrSL type is added, this function will need to be updated. + static_assert(kSkSLTypeCount == 41); + + if (type.matches(*context.fTypes.fVoid )) { *outType = SkSLType::kVoid; return true; } + if (type.matches(*context.fTypes.fBool )) { *outType = SkSLType::kBool; return true; } + if (type.matches(*context.fTypes.fBool2 )) { *outType = SkSLType::kBool2; return true; } + if (type.matches(*context.fTypes.fBool3 )) { *outType = SkSLType::kBool3; return true; } + if (type.matches(*context.fTypes.fBool4 )) { *outType = SkSLType::kBool4; return true; } + if (type.matches(*context.fTypes.fShort )) { *outType = SkSLType::kShort; return true; } + if (type.matches(*context.fTypes.fShort2 )) { *outType = SkSLType::kShort2; return true; } + if (type.matches(*context.fTypes.fShort3 )) { *outType = SkSLType::kShort3; return true; } + if (type.matches(*context.fTypes.fShort4 )) { *outType = SkSLType::kShort4; return true; } + if (type.matches(*context.fTypes.fUShort )) { *outType = SkSLType::kUShort; return true; } + if (type.matches(*context.fTypes.fUShort2 )) { *outType = SkSLType::kUShort2; return true; } + if (type.matches(*context.fTypes.fUShort3 )) { *outType = SkSLType::kUShort3; return true; } + if (type.matches(*context.fTypes.fUShort4 )) { *outType = SkSLType::kUShort4; return true; } + if (type.matches(*context.fTypes.fFloat )) { *outType = SkSLType::kFloat; return true; } + if (type.matches(*context.fTypes.fFloat2 )) { *outType = SkSLType::kFloat2; return true; } + if (type.matches(*context.fTypes.fFloat3 )) { *outType = SkSLType::kFloat3; return true; } + if (type.matches(*context.fTypes.fFloat4 )) { *outType = SkSLType::kFloat4; return true; } + if (type.matches(*context.fTypes.fFloat2x2)) { *outType = SkSLType::kFloat2x2; return true; } + if (type.matches(*context.fTypes.fFloat3x3)) { *outType = SkSLType::kFloat3x3; return true; } + if (type.matches(*context.fTypes.fFloat4x4)) { *outType = SkSLType::kFloat4x4; return true; } + if (type.matches(*context.fTypes.fHalf )) { *outType = SkSLType::kHalf; return true; } + if (type.matches(*context.fTypes.fHalf2 )) { *outType = SkSLType::kHalf2; return true; } + if (type.matches(*context.fTypes.fHalf3 )) { *outType = SkSLType::kHalf3; return true; } + if (type.matches(*context.fTypes.fHalf4 )) { *outType = SkSLType::kHalf4; return true; } + if (type.matches(*context.fTypes.fHalf2x2 )) { *outType = SkSLType::kHalf2x2; return true; } + if (type.matches(*context.fTypes.fHalf3x3 )) { *outType = SkSLType::kHalf3x3; return true; } + if (type.matches(*context.fTypes.fHalf4x4 )) { *outType = SkSLType::kHalf4x4; return true; } + if (type.matches(*context.fTypes.fInt )) { *outType = SkSLType::kInt; return true; } + if (type.matches(*context.fTypes.fInt2 )) { *outType = SkSLType::kInt2; return true; } + if (type.matches(*context.fTypes.fInt3 )) { *outType = SkSLType::kInt3; return true; } + if (type.matches(*context.fTypes.fInt4 )) { *outType = SkSLType::kInt4; return true; } + if (type.matches(*context.fTypes.fUInt )) { *outType = SkSLType::kUInt; return true; } + if (type.matches(*context.fTypes.fUInt2 )) { *outType = SkSLType::kUInt2; return true; } + if (type.matches(*context.fTypes.fUInt3 )) { *outType = SkSLType::kUInt3; return true; } + if (type.matches(*context.fTypes.fUInt4 )) { *outType = SkSLType::kUInt4; return true; } + return false; +} +#endif + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/SkSLUtil.h b/gfx/skia/skia/src/sksl/SkSLUtil.h new file mode 100644 index 0000000000..92dfe537a9 --- /dev/null +++ b/gfx/skia/skia/src/sksl/SkSLUtil.h @@ -0,0 +1,187 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_UTIL +#define SKSL_UTIL + +#include "include/core/SkTypes.h" +#include "include/sksl/SkSLVersion.h" +#include "src/sksl/SkSLGLSL.h" + +#include <memory> + +enum class SkSLType : char; + +namespace SkSL { + +class Context; +class OutputStream; +class StringStream; +class Type; + +struct ShaderCaps { + /** + * Indicates how GLSL must interact with advanced blend equations. The KHR extension requires + * special layout qualifiers in the fragment shader. + */ + enum AdvBlendEqInteraction { + kNotSupported_AdvBlendEqInteraction, //<! No _blend_equation_advanced extension + kAutomatic_AdvBlendEqInteraction, //<! No interaction required + kGeneralEnable_AdvBlendEqInteraction, //<! layout(blend_support_all_equations) out + + kLast_AdvBlendEqInteraction = kGeneralEnable_AdvBlendEqInteraction + }; + + bool mustEnableAdvBlendEqs() const { + return fAdvBlendEqInteraction >= kGeneralEnable_AdvBlendEqInteraction; + } + + bool mustDeclareFragmentShaderOutput() const { + return fGLSLGeneration > SkSL::GLSLGeneration::k110; + } + + // Returns the string of an extension that must be enabled in the shader to support + // derivatives. If nullptr is returned then no extension needs to be enabled. Before calling + // this function, the caller should check that shaderDerivativeSupport exists. + const char* shaderDerivativeExtensionString() const { + SkASSERT(this->fShaderDerivativeSupport); + return fShaderDerivativeExtensionString; + } + + // This returns the name of an extension that must be enabled in the shader to support external + // textures. In some cases, two extensions must be enabled - the second extension is returned + // by secondExternalTextureExtensionString(). If that function returns nullptr, then only one + // extension is required. + const char* externalTextureExtensionString() const { + SkASSERT(this->fExternalTextureSupport); + return fExternalTextureExtensionString; + } + + const char* secondExternalTextureExtensionString() const { + SkASSERT(this->fExternalTextureSupport); + return fSecondExternalTextureExtensionString; + } + + /** + * SkSL 300 requires support for derivatives, nonsquare matrices and bitwise integer operations. + */ + SkSL::Version supportedSkSLVerion() const { + if (fShaderDerivativeSupport && fNonsquareMatrixSupport && fIntegerSupport && + fGLSLGeneration >= SkSL::GLSLGeneration::k330) { + return SkSL::Version::k300; + } + return SkSL::Version::k100; + } + + bool supportsDistanceFieldText() const { return fShaderDerivativeSupport; } + + SkSL::GLSLGeneration fGLSLGeneration = SkSL::GLSLGeneration::k330; + + bool fShaderDerivativeSupport = false; + /** Enables sampleGrad and sampleLod functions that don't rely on implicit derivatives */ + bool fExplicitTextureLodSupport = false; + /** Indicates true 32-bit integer support, with unsigned types and bitwise operations */ + bool fIntegerSupport = false; + bool fNonsquareMatrixSupport = false; + /** asinh(), acosh(), atanh() */ + bool fInverseHyperbolicSupport = false; + bool fFBFetchSupport = false; + bool fFBFetchNeedsCustomOutput = false; + bool fUsesPrecisionModifiers = false; + bool fFlatInterpolationSupport = false; + bool fNoPerspectiveInterpolationSupport = false; + bool fSampleMaskSupport = false; + bool fExternalTextureSupport = false; + bool fFloatIs32Bits = true; + + // isinf() is defined, and floating point infinities are handled according to IEEE standards. + bool fInfinitySupport = false; + + // Used by SkSL to know when to generate polyfills. + bool fBuiltinFMASupport = true; + bool fBuiltinDeterminantSupport = true; + + // Used for specific driver bug work arounds + bool fCanUseMinAndAbsTogether = true; + bool fCanUseFractForNegativeValues = true; + bool fMustForceNegatedAtanParamToFloat = false; + bool fMustForceNegatedLdexpParamToMultiply = false; // http://skbug.com/12076 + // Returns whether a device incorrectly implements atan(y,x) as atan(y/x) + bool fAtan2ImplementedAsAtanYOverX = false; + // If this returns true some operation (could be a no op) must be called between floor and abs + // to make sure the driver compiler doesn't inline them together which can cause a driver bug in + // the shader. + bool fMustDoOpBetweenFloorAndAbs = false; + // The D3D shader compiler, when targeting PS 3.0 (ie within ANGLE) fails to compile certain + // constructs. See detailed comments in GrGLCaps.cpp. + bool fMustGuardDivisionEvenAfterExplicitZeroCheck = false; + // If false, SkSL uses a workaround so that sk_FragCoord doesn't actually query gl_FragCoord + bool fCanUseFragCoord = true; + // If true, short ints can't represent every integer in the 16-bit two's complement range as + // required by the spec. SKSL will always emit full ints. + bool fIncompleteShortIntPrecision = false; + // If true, then conditions in for loops need "&& true" to work around driver bugs. + bool fAddAndTrueToLoopCondition = false; + // If true, then expressions such as "x && y" or "x || y" are rewritten as ternary to work + // around driver bugs. + bool fUnfoldShortCircuitAsTernary = false; + bool fEmulateAbsIntFunction = false; + bool fRewriteDoWhileLoops = false; + bool fRewriteSwitchStatements = false; + bool fRemovePowWithConstantExponent = false; + // The Android emulator claims samplerExternalOES is an unknown type if a default precision + // statement is made for the type. + bool fNoDefaultPrecisionForExternalSamplers = false; + // ARM GPUs calculate `matrix * vector` in SPIR-V at full precision, even when the inputs are + // RelaxedPrecision. Rewriting the multiply as a sum of vector*scalar fixes this. (skia:11769) + bool fRewriteMatrixVectorMultiply = false; + // Rewrites matrix equality comparisons to avoid an Adreno driver bug. (skia:11308) + bool fRewriteMatrixComparisons = false; + // Strips const from function parameters in the GLSL code generator. (skia:13858) + bool fRemoveConstFromFunctionParameters = false; + + const char* fVersionDeclString = ""; + + const char* fShaderDerivativeExtensionString = nullptr; + const char* fExternalTextureExtensionString = nullptr; + const char* fSecondExternalTextureExtensionString = nullptr; + const char* fFBFetchColorName = nullptr; + + AdvBlendEqInteraction fAdvBlendEqInteraction = kNotSupported_AdvBlendEqInteraction; +}; + +// Various sets of caps for use in tests +class ShaderCapsFactory { +public: + static const ShaderCaps* Default() { + static const SkSL::ShaderCaps* sCaps = [] { + std::unique_ptr<ShaderCaps> caps = MakeShaderCaps(); + caps->fVersionDeclString = "#version 400"; + caps->fShaderDerivativeSupport = true; + return caps.release(); + }(); + return sCaps; + } + + static const ShaderCaps* Standalone() { + static const SkSL::ShaderCaps* sCaps = MakeShaderCaps().release(); + return sCaps; + } + +protected: + static std::unique_ptr<ShaderCaps> MakeShaderCaps(); +}; + +#if !defined(SKSL_STANDALONE) && (defined(SK_GANESH) || defined(SK_GRAPHITE)) +bool type_to_sksltype(const Context& context, const Type& type, SkSLType* outType); +#endif + +void write_stringstream(const StringStream& d, OutputStream& out); + +} // namespace SkSL + +#endif // SKSL_UTIL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp new file mode 100644 index 0000000000..015f233c4c --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp @@ -0,0 +1,176 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLType.h" + +#include <memory> + +namespace SkSL { +class Expression; +namespace { + +class ReturnsOnAllPathsVisitor : public ProgramVisitor { +public: + bool visitExpression(const Expression& expr) override { + // We can avoid processing expressions entirely. + return false; + } + + bool visitStatement(const Statement& stmt) override { + switch (stmt.kind()) { + // Returns, breaks, or continues will stop the scan, so only one of these should ever be + // true. + case Statement::Kind::kReturn: + fFoundReturn = true; + return true; + + case Statement::Kind::kBreak: + fFoundBreak = true; + return true; + + case Statement::Kind::kContinue: + fFoundContinue = true; + return true; + + case Statement::Kind::kIf: { + const IfStatement& i = stmt.as<IfStatement>(); + ReturnsOnAllPathsVisitor trueVisitor; + ReturnsOnAllPathsVisitor falseVisitor; + trueVisitor.visitStatement(*i.ifTrue()); + if (i.ifFalse()) { + falseVisitor.visitStatement(*i.ifFalse()); + } + // If either branch leads to a break or continue, we report the entire if as + // containing a break or continue, since we don't know which side will be reached. + fFoundBreak = (trueVisitor.fFoundBreak || falseVisitor.fFoundBreak); + fFoundContinue = (trueVisitor.fFoundContinue || falseVisitor.fFoundContinue); + // On the other hand, we only want to report returns that definitely happen, so we + // require those to be found on both sides. + fFoundReturn = (trueVisitor.fFoundReturn && falseVisitor.fFoundReturn); + return fFoundBreak || fFoundContinue || fFoundReturn; + } + case Statement::Kind::kFor: { + const ForStatement& f = stmt.as<ForStatement>(); + // We assume a for/while loop runs for at least one iteration; this isn't strictly + // guaranteed, but it's better to be slightly over-permissive here than to fail on + // reasonable code. + ReturnsOnAllPathsVisitor forVisitor; + forVisitor.visitStatement(*f.statement()); + // A for loop that contains a break or continue is safe; it won't exit the entire + // function, just the loop. So we disregard those signals. + fFoundReturn = forVisitor.fFoundReturn; + return fFoundReturn; + } + case Statement::Kind::kDo: { + const DoStatement& d = stmt.as<DoStatement>(); + // Do-while blocks are always entered at least once. + ReturnsOnAllPathsVisitor doVisitor; + doVisitor.visitStatement(*d.statement()); + // A do-while loop that contains a break or continue is safe; it won't exit the + // entire function, just the loop. So we disregard those signals. + fFoundReturn = doVisitor.fFoundReturn; + return fFoundReturn; + } + case Statement::Kind::kBlock: + // Blocks are definitely entered and don't imply any additional control flow. + // If the block contains a break, continue or return, we want to keep that. + return INHERITED::visitStatement(stmt); + + case Statement::Kind::kSwitch: { + // Switches are the most complex control flow we need to deal with; fortunately we + // already have good primitives for dissecting them. We need to verify that: + // - a default case exists, so that every possible input value is covered + // - every switch-case either (a) returns unconditionally, or + // (b) falls through to another case that does + const SwitchStatement& s = stmt.as<SwitchStatement>(); + bool foundDefault = false; + bool fellThrough = false; + for (const std::unique_ptr<Statement>& switchStmt : s.cases()) { + // The default case is indicated by a null value. A switch without a default + // case cannot definitively return, as its value might not be in the cases list. + const SwitchCase& sc = switchStmt->as<SwitchCase>(); + if (sc.isDefault()) { + foundDefault = true; + } + // Scan this switch-case for any exit (break, continue or return). + ReturnsOnAllPathsVisitor caseVisitor; + caseVisitor.visitStatement(sc); + + // If we found a break or continue, whether conditional or not, this switch case + // can't be called an unconditional return. Switches absorb breaks but not + // continues. + if (caseVisitor.fFoundContinue) { + fFoundContinue = true; + return false; + } + if (caseVisitor.fFoundBreak) { + return false; + } + // We just confirmed that there weren't any breaks or continues. If we didn't + // find an unconditional return either, the switch is considered fallen-through. + // (There might be a conditional return, but that doesn't count.) + fellThrough = !caseVisitor.fFoundReturn; + } + + // If we didn't find a default case, or the very last case fell through, this switch + // doesn't meet our criteria. + if (fellThrough || !foundDefault) { + return false; + } + + // We scanned the entire switch, found a default case, and every section either fell + // through or contained an unconditional return. + fFoundReturn = true; + return true; + } + + case Statement::Kind::kSwitchCase: + // Recurse into the switch-case. + return INHERITED::visitStatement(stmt); + + case Statement::Kind::kDiscard: + case Statement::Kind::kExpression: + case Statement::Kind::kNop: + case Statement::Kind::kVarDeclaration: + // None of these statements could contain a return. + break; + } + + return false; + } + + bool fFoundReturn = false; + bool fFoundBreak = false; + bool fFoundContinue = false; + + using INHERITED = ProgramVisitor; +}; + +} // namespace + +bool Analysis::CanExitWithoutReturningValue(const FunctionDeclaration& funcDecl, + const Statement& body) { + if (funcDecl.returnType().isVoid()) { + return false; + } + ReturnsOnAllPathsVisitor visitor; + visitor.visitStatement(body); + return !visitor.fFoundReturn; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp new file mode 100644 index 0000000000..5407d820d8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp @@ -0,0 +1,223 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkSafeMath.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLProgram.h" + +#include <cstddef> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +namespace SkSL { + +bool Analysis::CheckProgramStructure(const Program& program, bool enforceSizeLimit) { + // We check the size of strict-ES2 programs; since SkVM will completely unroll them, it's + // important to know how large the result will be. For non-ES2 code, we compute an approximate + // lower bound by assuming all non-unrollable loops will execute one time only. + const Context& context = *program.fContext; + + // If we decide that expressions are cheaper than statements, or that certain statements are + // more expensive than others, etc., we can always tweak these ratios as needed. A very rough + // ballpark estimate is currently good enough for our purposes. + static constexpr size_t kExpressionCost = 1; + static constexpr size_t kStatementCost = 1; + static constexpr size_t kUnknownCost = -1; + static constexpr size_t kProgramSizeLimit = 100000; + static constexpr size_t kProgramStackDepthLimit = 50; + + class ProgramSizeVisitor : public ProgramVisitor { + public: + ProgramSizeVisitor(const Context& c) : fContext(c) {} + + using ProgramVisitor::visitProgramElement; + + size_t functionSize() const { + return fFunctionSize; + } + + bool visitProgramElement(const ProgramElement& pe) override { + if (pe.is<FunctionDefinition>()) { + // Check the function-size cache map first. We don't need to visit this function if + // we already processed it before. + const FunctionDeclaration* decl = &pe.as<FunctionDefinition>().declaration(); + if (size_t *cachedCost = fFunctionCostMap.find(decl)) { + // We already have this function in our map. We don't need to check it again. + if (*cachedCost == kUnknownCost) { + // If the function is present in the map with an unknown cost, we're + // recursively processing it--in other words, we found a cycle in the code. + // Unwind our stack into a string. + std::string msg = "\n\t" + decl->description(); + for (auto unwind = fStack.rbegin(); unwind != fStack.rend(); ++unwind) { + msg = "\n\t" + (*unwind)->description() + msg; + if (*unwind == decl) { + break; + } + } + msg = "potential recursion (function call cycle) not allowed:" + msg; + fContext.fErrors->error(pe.fPosition, std::move(msg)); + fFunctionSize = 0; + *cachedCost = 0; + return true; + } + // Set the size to its known value. + fFunctionSize = *cachedCost; + return false; + } + + // If the function-call stack has gotten too deep, stop the analysis. + if (fStack.size() >= kProgramStackDepthLimit) { + std::string msg = "exceeded max function call depth:"; + for (auto unwind = fStack.begin(); unwind != fStack.end(); ++unwind) { + msg += "\n\t" + (*unwind)->description(); + } + msg += "\n\t" + decl->description(); + fContext.fErrors->error(pe.fPosition, std::move(msg)); + fFunctionSize = 0; + fFunctionCostMap.set(decl, 0); + return true; + } + + // Calculate the function cost and store it in our cache. + fFunctionCostMap.set(decl, kUnknownCost); + fStack.push_back(decl); + fFunctionSize = 0; + bool result = INHERITED::visitProgramElement(pe); + fFunctionCostMap.set(decl, fFunctionSize); + fStack.pop_back(); + + return result; + } + + return INHERITED::visitProgramElement(pe); + } + + bool visitStatement(const Statement& stmt) override { + switch (stmt.kind()) { + case Statement::Kind::kFor: { + // We count a for-loop's unrolled size here. We expect that the init statement + // will be emitted once, and the test-expr, next-expr and statement will be + // repeated in the output for every iteration of the loop. + bool earlyExit = false; + const ForStatement& forStmt = stmt.as<ForStatement>(); + if (forStmt.initializer() && this->visitStatement(*forStmt.initializer())) { + earlyExit = true; + } + + size_t originalFunctionSize = fFunctionSize; + fFunctionSize = 0; + + if (forStmt.next() && this->visitExpression(*forStmt.next())) { + earlyExit = true; + } + if (forStmt.test() && this->visitExpression(*forStmt.test())) { + earlyExit = true; + } + if (this->visitStatement(*forStmt.statement())) { + earlyExit = true; + } + + // ES2 programs always have a known unroll count. Non-ES2 programs don't enforce + // a maximum program size, so it's fine to treat the loop as executing once. + if (const LoopUnrollInfo* unrollInfo = forStmt.unrollInfo()) { + fFunctionSize = SkSafeMath::Mul(fFunctionSize, unrollInfo->fCount); + } + fFunctionSize = SkSafeMath::Add(fFunctionSize, originalFunctionSize); + return earlyExit; + } + + case Statement::Kind::kExpression: + // The cost of an expression-statement is counted in visitExpression. It would + // be double-dipping to count it here too. + break; + + case Statement::Kind::kNop: + case Statement::Kind::kVarDeclaration: + // These statements don't directly consume any space in a compiled program. + break; + + default: + // Note that we don't make any attempt to estimate the number of iterations of + // do-while loops here. Those aren't an ES2 construct so we aren't enforcing + // program size on them. + fFunctionSize = SkSafeMath::Add(fFunctionSize, kStatementCost); + break; + } + + return INHERITED::visitStatement(stmt); + } + + bool visitExpression(const Expression& expr) override { + // Other than function calls, all expressions are assumed to have a fixed unit cost. + bool earlyExit = false; + size_t expressionCost = kExpressionCost; + + if (expr.is<FunctionCall>()) { + // Visit this function call to calculate its size. If we've already sized it, this + // will retrieve the size from our cache. + const FunctionCall& call = expr.as<FunctionCall>(); + const FunctionDeclaration* decl = &call.function(); + if (decl->definition() && !decl->isIntrinsic()) { + size_t originalFunctionSize = fFunctionSize; + fFunctionSize = 0; + + earlyExit = this->visitProgramElement(*decl->definition()); + expressionCost = fFunctionSize; + + fFunctionSize = originalFunctionSize; + } + } + + fFunctionSize = SkSafeMath::Add(fFunctionSize, expressionCost); + return earlyExit || INHERITED::visitExpression(expr); + } + + private: + using INHERITED = ProgramVisitor; + + const Context& fContext; + size_t fFunctionSize = 0; + SkTHashMap<const FunctionDeclaration*, size_t> fFunctionCostMap; + std::vector<const FunctionDeclaration*> fStack; + }; + + // Process every function in our program. + ProgramSizeVisitor visitor{context}; + for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) { + if (element->is<FunctionDefinition>()) { + // Visit every function--we want to detect static recursion and report it as an error, + // even in unreferenced functions. + visitor.visitProgramElement(*element); + // Report an error when main()'s flattened size is larger than our program limit. + if (enforceSizeLimit && + visitor.functionSize() > kProgramSizeLimit && + element->as<FunctionDefinition>().declaration().isMain()) { + context.fErrors->error(Position(), "program is too large"); + } + } + } + + return true; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp new file mode 100644 index 0000000000..f1c0b4cdfa --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp @@ -0,0 +1,172 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/base/SkSafeMath.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +namespace SkSL { +namespace { + +class FinalizationVisitor : public ProgramVisitor { +public: + FinalizationVisitor(const Context& c, const ProgramUsage& u) : fContext(c), fUsage(u) {} + + bool visitProgramElement(const ProgramElement& pe) override { + switch (pe.kind()) { + case ProgramElement::Kind::kGlobalVar: + this->checkGlobalVariableSizeLimit(pe.as<GlobalVarDeclaration>()); + break; + case ProgramElement::Kind::kInterfaceBlock: + // TODO(skia:13664): Enforce duplicate checks universally. This is currently not + // possible without changes to the binding index assignment logic in graphite. + this->checkBindUniqueness(pe.as<InterfaceBlock>()); + break; + case ProgramElement::Kind::kFunction: + this->checkOutParamsAreAssigned(pe.as<FunctionDefinition>()); + break; + default: + break; + } + return INHERITED::visitProgramElement(pe); + } + + void checkGlobalVariableSizeLimit(const GlobalVarDeclaration& globalDecl) { + if (!ProgramConfig::IsRuntimeEffect(fContext.fConfig->fKind)) { + return; + } + const VarDeclaration& decl = globalDecl.varDeclaration(); + + size_t prevSlotsUsed = fGlobalSlotsUsed; + fGlobalSlotsUsed = SkSafeMath::Add(fGlobalSlotsUsed, decl.var()->type().slotCount()); + // To avoid overzealous error reporting, only trigger the error at the first place where the + // global limit is exceeded. + if (prevSlotsUsed < kVariableSlotLimit && fGlobalSlotsUsed >= kVariableSlotLimit) { + fContext.fErrors->error(decl.fPosition, + "global variable '" + std::string(decl.var()->name()) + + "' exceeds the size limit"); + } + } + + void checkBindUniqueness(const InterfaceBlock& block) { + const Variable* var = block.var(); + int32_t set = var->modifiers().fLayout.fSet; + int32_t binding = var->modifiers().fLayout.fBinding; + if (binding != -1) { + // TODO(skia:13664): This should map a `set` value of -1 to the default settings value + // used by codegen backends to prevent duplicates that may arise from the effective + // default set value. + uint64_t key = ((uint64_t)set << 32) + binding; + if (!fBindings.contains(key)) { + fBindings.add(key); + } else { + if (set != -1) { + fContext.fErrors->error(block.fPosition, + "layout(set=" + std::to_string(set) + + ", binding=" + std::to_string(binding) + + ") has already been defined"); + } else { + fContext.fErrors->error(block.fPosition, + "layout(binding=" + std::to_string(binding) + + ") has already been defined"); + } + } + } + } + + void checkOutParamsAreAssigned(const FunctionDefinition& funcDef) { + const FunctionDeclaration& funcDecl = funcDef.declaration(); + + // Searches for `out` parameters that are not written to. According to the GLSL spec, + // the value of an out-param that's never assigned to is unspecified, so report it. + for (const Variable* param : funcDecl.parameters()) { + const int paramInout = param->modifiers().fFlags & (Modifiers::Flag::kIn_Flag | + Modifiers::Flag::kOut_Flag); + if (paramInout == Modifiers::Flag::kOut_Flag) { + ProgramUsage::VariableCounts counts = fUsage.get(*param); + if (counts.fWrite <= 0) { + fContext.fErrors->error(param->fPosition, + "function '" + std::string(funcDecl.name()) + + "' never assigns a value to out parameter '" + + std::string(param->name()) + "'"); + } + } + } + } + + bool visitExpression(const Expression& expr) override { + switch (expr.kind()) { + case Expression::Kind::kFunctionCall: { + const FunctionDeclaration& decl = expr.as<FunctionCall>().function(); + if (!decl.isBuiltin() && !decl.definition()) { + fContext.fErrors->error(expr.fPosition, "function '" + decl.description() + + "' is not defined"); + } + break; + } + case Expression::Kind::kFunctionReference: + case Expression::Kind::kMethodReference: + case Expression::Kind::kTypeReference: + SkDEBUGFAIL("invalid reference-expr, should have been reported by coerce()"); + fContext.fErrors->error(expr.fPosition, "invalid expression"); + break; + default: + if (expr.type().matches(*fContext.fTypes.fInvalid)) { + fContext.fErrors->error(expr.fPosition, "invalid expression"); + } + break; + } + return INHERITED::visitExpression(expr); + } + +private: + using INHERITED = ProgramVisitor; + size_t fGlobalSlotsUsed = 0; + const Context& fContext; + const ProgramUsage& fUsage; + // we pack the set/binding pair into a single 64 bit int + SkTHashSet<uint64_t> fBindings; +}; + +} // namespace + +void Analysis::DoFinalizationChecks(const Program& program) { + // Check all of the program's owned elements. (Built-in elements are assumed to be valid.) + FinalizationVisitor visitor{*program.fContext, *program.usage()}; + for (const std::unique_ptr<ProgramElement>& element : program.fOwnedElements) { + visitor.visitProgramElement(*element); + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopControlFlowInfo.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopControlFlowInfo.cpp new file mode 100644 index 0000000000..65c9e5e424 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopControlFlowInfo.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLAnalysis.h" + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" + +namespace SkSL { + +class Expression; + +namespace Analysis { +namespace { + +class LoopControlFlowVisitor : public ProgramVisitor { +public: + LoopControlFlowVisitor() {} + + bool visitExpression(const Expression& expr) override { + // We can avoid processing expressions entirely. + return false; + } + + bool visitStatement(const Statement& stmt) override { + switch (stmt.kind()) { + case Statement::Kind::kContinue: + // A continue only affects the control flow of the loop if it's not nested inside + // another looping structure. (Inside a switch, SkSL disallows continue entirely.) + fResult.fHasContinue |= (fDepth == 0); + break; + + case Statement::Kind::kBreak: + // A break only affects the control flow of the loop if it's not nested inside + // another loop/switch structure. + fResult.fHasBreak |= (fDepth == 0); + break; + + case Statement::Kind::kReturn: + // A return will abort the loop's control flow no matter how deeply it is nested. + fResult.fHasReturn = true; + break; + + case Statement::Kind::kFor: + case Statement::Kind::kDo: + case Statement::Kind::kSwitch: { + ++fDepth; + bool done = ProgramVisitor::visitStatement(stmt); + --fDepth; + return done; + } + + default: + return ProgramVisitor::visitStatement(stmt); + } + + // If we've already found everything we're hunting for, we can stop searching early. + return fResult.fHasContinue && fResult.fHasBreak && fResult.fHasReturn; + } + + LoopControlFlowInfo fResult; + int fDepth = 0; +}; + +} // namespace + +LoopControlFlowInfo GetLoopControlFlowInfo(const Statement& stmt) { + LoopControlFlowVisitor visitor; + visitor.visitStatement(stmt); + return visitor.fResult; +} + +} // namespace Analysis +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp new file mode 100644 index 0000000000..cf49867392 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLGetLoopUnrollInfo.cpp @@ -0,0 +1,280 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "include/private/base/SkFloatingPoint.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/analysis/SkSLNoOpErrorReporter.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <cmath> +#include <memory> + +namespace SkSL { + +// Loops that run for 100000+ iterations will exceed our program size limit. +static constexpr int kLoopTerminationLimit = 100000; + +static int calculate_count(double start, double end, double delta, bool forwards, bool inclusive) { + if (forwards != (start < end)) { + // The loop starts in a completed state (the start has already advanced past the end). + return 0; + } + if ((delta == 0.0) || forwards != (delta > 0.0)) { + // The loop does not progress toward a completed state, and will never terminate. + return kLoopTerminationLimit; + } + double iterations = sk_ieee_double_divide(end - start, delta); + double count = std::ceil(iterations); + if (inclusive && (count == iterations)) { + count += 1.0; + } + if (count > kLoopTerminationLimit || !std::isfinite(count)) { + // The loop runs for more iterations than we can safely unroll. + return kLoopTerminationLimit; + } + return (int)count; +} + +std::unique_ptr<LoopUnrollInfo> Analysis::GetLoopUnrollInfo(Position loopPos, + const ForLoopPositions& positions, + const Statement* loopInitializer, + const Expression* loopTest, + const Expression* loopNext, + const Statement* loopStatement, + ErrorReporter* errorPtr) { + NoOpErrorReporter unused; + ErrorReporter& errors = errorPtr ? *errorPtr : unused; + auto loopInfo = std::make_unique<LoopUnrollInfo>(); + + // + // init_declaration has the form: type_specifier identifier = constant_expression + // + if (!loopInitializer) { + Position pos = positions.initPosition.valid() ? positions.initPosition : loopPos; + errors.error(pos, "missing init declaration"); + return nullptr; + } + if (!loopInitializer->is<VarDeclaration>()) { + errors.error(loopInitializer->fPosition, "invalid init declaration"); + return nullptr; + } + const VarDeclaration& initDecl = loopInitializer->as<VarDeclaration>(); + if (!initDecl.baseType().isNumber()) { + errors.error(loopInitializer->fPosition, "invalid type for loop index"); + return nullptr; + } + if (initDecl.arraySize() != 0) { + errors.error(loopInitializer->fPosition, "invalid type for loop index"); + return nullptr; + } + if (!initDecl.value()) { + errors.error(loopInitializer->fPosition, "missing loop index initializer"); + return nullptr; + } + if (!ConstantFolder::GetConstantValue(*initDecl.value(), &loopInfo->fStart)) { + errors.error(loopInitializer->fPosition, + "loop index initializer must be a constant expression"); + return nullptr; + } + + loopInfo->fIndex = initDecl.var(); + + auto is_loop_index = [&](const std::unique_ptr<Expression>& expr) { + return expr->is<VariableReference>() && + expr->as<VariableReference>().variable() == loopInfo->fIndex; + }; + + // + // condition has the form: loop_index relational_operator constant_expression + // + if (!loopTest) { + Position pos = positions.conditionPosition.valid() ? positions.conditionPosition : loopPos; + errors.error(pos, "missing condition"); + return nullptr; + } + if (!loopTest->is<BinaryExpression>()) { + errors.error(loopTest->fPosition, "invalid condition"); + return nullptr; + } + const BinaryExpression& cond = loopTest->as<BinaryExpression>(); + if (!is_loop_index(cond.left())) { + errors.error(loopTest->fPosition, "expected loop index on left hand side of condition"); + return nullptr; + } + // relational_operator is one of: > >= < <= == or != + switch (cond.getOperator().kind()) { + case Operator::Kind::GT: + case Operator::Kind::GTEQ: + case Operator::Kind::LT: + case Operator::Kind::LTEQ: + case Operator::Kind::EQEQ: + case Operator::Kind::NEQ: + break; + default: + errors.error(loopTest->fPosition, "invalid relational operator"); + return nullptr; + } + double loopEnd = 0; + if (!ConstantFolder::GetConstantValue(*cond.right(), &loopEnd)) { + errors.error(loopTest->fPosition, "loop index must be compared with a constant expression"); + return nullptr; + } + + // + // expression has one of the following forms: + // loop_index++ + // loop_index-- + // loop_index += constant_expression + // loop_index -= constant_expression + // The spec doesn't mention prefix increment and decrement, but there is some consensus that + // it's an oversight, so we allow those as well. + // + if (!loopNext) { + Position pos = positions.nextPosition.valid() ? positions.nextPosition : loopPos; + errors.error(pos, "missing loop expression"); + return nullptr; + } + switch (loopNext->kind()) { + case Expression::Kind::kBinary: { + const BinaryExpression& next = loopNext->as<BinaryExpression>(); + if (!is_loop_index(next.left())) { + errors.error(loopNext->fPosition, "expected loop index in loop expression"); + return nullptr; + } + if (!ConstantFolder::GetConstantValue(*next.right(), &loopInfo->fDelta)) { + errors.error(loopNext->fPosition, + "loop index must be modified by a constant expression"); + return nullptr; + } + switch (next.getOperator().kind()) { + case Operator::Kind::PLUSEQ: break; + case Operator::Kind::MINUSEQ: loopInfo->fDelta = -loopInfo->fDelta; break; + default: + errors.error(loopNext->fPosition, "invalid operator in loop expression"); + return nullptr; + } + } break; + case Expression::Kind::kPrefix: { + const PrefixExpression& next = loopNext->as<PrefixExpression>(); + if (!is_loop_index(next.operand())) { + errors.error(loopNext->fPosition, "expected loop index in loop expression"); + return nullptr; + } + switch (next.getOperator().kind()) { + case Operator::Kind::PLUSPLUS: loopInfo->fDelta = 1; break; + case Operator::Kind::MINUSMINUS: loopInfo->fDelta = -1; break; + default: + errors.error(loopNext->fPosition, "invalid operator in loop expression"); + return nullptr; + } + } break; + case Expression::Kind::kPostfix: { + const PostfixExpression& next = loopNext->as<PostfixExpression>(); + if (!is_loop_index(next.operand())) { + errors.error(loopNext->fPosition, "expected loop index in loop expression"); + return nullptr; + } + switch (next.getOperator().kind()) { + case Operator::Kind::PLUSPLUS: loopInfo->fDelta = 1; break; + case Operator::Kind::MINUSMINUS: loopInfo->fDelta = -1; break; + default: + errors.error(loopNext->fPosition, "invalid operator in loop expression"); + return nullptr; + } + } break; + default: + errors.error(loopNext->fPosition, "invalid loop expression"); + return nullptr; + } + + // + // Within the body of the loop, the loop index is not statically assigned to, nor is it used as + // argument to a function 'out' or 'inout' parameter. + // + if (Analysis::StatementWritesToVariable(*loopStatement, *initDecl.var())) { + errors.error(loopStatement->fPosition, + "loop index must not be modified within body of the loop"); + return nullptr; + } + + // Finally, compute the iteration count, based on the bounds, and the termination operator. + loopInfo->fCount = 0; + + switch (cond.getOperator().kind()) { + case Operator::Kind::LT: + loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta, + /*forwards=*/true, /*inclusive=*/false); + break; + + case Operator::Kind::GT: + loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta, + /*forwards=*/false, /*inclusive=*/false); + break; + + case Operator::Kind::LTEQ: + loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta, + /*forwards=*/true, /*inclusive=*/true); + break; + + case Operator::Kind::GTEQ: + loopInfo->fCount = calculate_count(loopInfo->fStart, loopEnd, loopInfo->fDelta, + /*forwards=*/false, /*inclusive=*/true); + break; + + case Operator::Kind::NEQ: { + float iterations = sk_ieee_double_divide(loopEnd - loopInfo->fStart, loopInfo->fDelta); + loopInfo->fCount = std::ceil(iterations); + if (loopInfo->fCount < 0 || loopInfo->fCount != iterations || + !std::isfinite(iterations)) { + // The loop doesn't reach the exact endpoint and so will never terminate. + loopInfo->fCount = kLoopTerminationLimit; + } + break; + } + case Operator::Kind::EQEQ: { + if (loopInfo->fStart == loopEnd) { + // Start and end begin in the same place, so we can run one iteration... + if (loopInfo->fDelta) { + // ... and then they diverge, so the loop terminates. + loopInfo->fCount = 1; + } else { + // ... but they never diverge, so the loop runs forever. + loopInfo->fCount = kLoopTerminationLimit; + } + } else { + // Start never equals end, so the loop will not run a single iteration. + loopInfo->fCount = 0; + } + break; + } + default: SkUNREACHABLE; + } + + SkASSERT(loopInfo->fCount >= 0); + if (loopInfo->fCount >= kLoopTerminationLimit) { + errors.error(loopPos, "loop must guarantee termination in fewer iterations"); + return nullptr; + } + + return loopInfo; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLGetReturnComplexity.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLGetReturnComplexity.cpp new file mode 100644 index 0000000000..a5b6e1132f --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLGetReturnComplexity.cpp @@ -0,0 +1,131 @@ +/* + * Copyright 2023 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" + +#include <algorithm> +#include <memory> + +namespace SkSL { + +class Expression; + +static int count_returns_at_end_of_control_flow(const FunctionDefinition& funcDef) { + class CountReturnsAtEndOfControlFlow : public ProgramVisitor { + public: + CountReturnsAtEndOfControlFlow(const FunctionDefinition& funcDef) { + this->visitProgramElement(funcDef); + } + + bool visitExpression(const Expression& expr) override { + // Do not recurse into expressions. + return false; + } + + bool visitStatement(const Statement& stmt) override { + switch (stmt.kind()) { + case Statement::Kind::kBlock: { + // Check only the last statement of a block. + const auto& block = stmt.as<Block>(); + return block.children().size() && + this->visitStatement(*block.children().back()); + } + case Statement::Kind::kSwitch: + case Statement::Kind::kDo: + case Statement::Kind::kFor: + // Don't introspect switches or loop structures at all. + return false; + + case Statement::Kind::kReturn: + ++fNumReturns; + [[fallthrough]]; + + default: + return INHERITED::visitStatement(stmt); + } + } + + int fNumReturns = 0; + using INHERITED = ProgramVisitor; + }; + + return CountReturnsAtEndOfControlFlow{funcDef}.fNumReturns; +} + +class CountReturnsWithLimit : public ProgramVisitor { +public: + CountReturnsWithLimit(const FunctionDefinition& funcDef, int limit) : fLimit(limit) { + this->visitProgramElement(funcDef); + } + + bool visitExpression(const Expression& expr) override { + // Do not recurse into expressions. + return false; + } + + bool visitStatement(const Statement& stmt) override { + switch (stmt.kind()) { + case Statement::Kind::kReturn: { + ++fNumReturns; + fDeepestReturn = std::max(fDeepestReturn, fScopedBlockDepth); + return (fNumReturns >= fLimit) || INHERITED::visitStatement(stmt); + } + case Statement::Kind::kVarDeclaration: { + if (fScopedBlockDepth > 1) { + fVariablesInBlocks = true; + } + return INHERITED::visitStatement(stmt); + } + case Statement::Kind::kBlock: { + int depthIncrement = stmt.as<Block>().isScope() ? 1 : 0; + fScopedBlockDepth += depthIncrement; + bool result = INHERITED::visitStatement(stmt); + fScopedBlockDepth -= depthIncrement; + if (fNumReturns == 0 && fScopedBlockDepth <= 1) { + // If closing this block puts us back at the top level, and we haven't + // encountered any return statements yet, any vardecls we may have encountered + // up until this point can be ignored. They are out of scope now, and they were + // never used in a return statement. + fVariablesInBlocks = false; + } + return result; + } + default: + return INHERITED::visitStatement(stmt); + } + } + + int fNumReturns = 0; + int fDeepestReturn = 0; + int fLimit = 0; + int fScopedBlockDepth = 0; + bool fVariablesInBlocks = false; + using INHERITED = ProgramVisitor; +}; + +Analysis::ReturnComplexity Analysis::GetReturnComplexity(const FunctionDefinition& funcDef) { + int returnsAtEndOfControlFlow = count_returns_at_end_of_control_flow(funcDef); + CountReturnsWithLimit counter{funcDef, returnsAtEndOfControlFlow + 1}; + if (counter.fNumReturns > returnsAtEndOfControlFlow) { + return ReturnComplexity::kEarlyReturns; + } + if (counter.fNumReturns > 1) { + return ReturnComplexity::kScopedReturns; + } + if (counter.fVariablesInBlocks && counter.fDeepestReturn > 1) { + return ReturnComplexity::kScopedReturns; + } + return ReturnComplexity::kSingleSafeReturn; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLHasSideEffects.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLHasSideEffects.cpp new file mode 100644 index 0000000000..0d328991e0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLHasSideEffects.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" + +namespace SkSL { + +bool Analysis::HasSideEffects(const Expression& expr) { + class HasSideEffectsVisitor : public ProgramVisitor { + public: + bool visitExpression(const Expression& expr) override { + switch (expr.kind()) { + case Expression::Kind::kFunctionCall: { + const FunctionCall& call = expr.as<FunctionCall>(); + if (!(call.function().modifiers().fFlags & Modifiers::kPure_Flag)) { + return true; + } + break; + } + case Expression::Kind::kPrefix: { + const PrefixExpression& prefix = expr.as<PrefixExpression>(); + if (prefix.getOperator().kind() == Operator::Kind::PLUSPLUS || + prefix.getOperator().kind() == Operator::Kind::MINUSMINUS) { + return true; + } + break; + } + case Expression::Kind::kBinary: { + const BinaryExpression& binary = expr.as<BinaryExpression>(); + if (binary.getOperator().isAssignment()) { + return true; + } + break; + } + case Expression::Kind::kPostfix: + return true; + + default: + break; + } + return INHERITED::visitExpression(expr); + } + + using INHERITED = ProgramVisitor; + }; + + HasSideEffectsVisitor visitor; + return visitor.visitExpression(expr); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsConstantExpression.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsConstantExpression.cpp new file mode 100644 index 0000000000..d0a54d2d4b --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsConstantExpression.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <set> + +namespace SkSL { + +// Checks for ES2 constant-expression rules, and (optionally) constant-index-expression rules +// (if loopIndices is non-nullptr) +class ConstantExpressionVisitor : public ProgramVisitor { +public: + ConstantExpressionVisitor(const std::set<const Variable*>* loopIndices) + : fLoopIndices(loopIndices) {} + + bool visitExpression(const Expression& e) override { + // A constant-(index)-expression is one of... + switch (e.kind()) { + // ... a literal value + case Expression::Kind::kLiteral: + return false; + + // ... settings can appear in fragment processors; they will resolve when compiled + case Expression::Kind::kSetting: + return false; + + // ... a global or local variable qualified as 'const', excluding function parameters. + // ... loop indices as defined in section 4. [constant-index-expression] + case Expression::Kind::kVariableReference: { + const Variable* v = e.as<VariableReference>().variable(); + if ((v->storage() == Variable::Storage::kGlobal || + v->storage() == Variable::Storage::kLocal) && + (v->modifiers().fFlags & Modifiers::kConst_Flag)) { + return false; + } + return !fLoopIndices || fLoopIndices->find(v) == fLoopIndices->end(); + } + + // ... not a sequence expression (skia:13311)... + case Expression::Kind::kBinary: + if (e.as<BinaryExpression>().getOperator().kind() == Operator::Kind::COMMA) { + return true; + } + [[fallthrough]]; + + // ... expressions composed of both of the above + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorStruct: + case Expression::Kind::kFieldAccess: + case Expression::Kind::kIndex: + case Expression::Kind::kPrefix: + case Expression::Kind::kPostfix: + case Expression::Kind::kSwizzle: + case Expression::Kind::kTernary: + return INHERITED::visitExpression(e); + + // Function calls are completely disallowed in SkSL constant-(index)-expressions. + // GLSL does mandate that calling a built-in function where the arguments are all + // constant-expressions should result in a constant-expression. SkSL handles this by + // optimizing fully-constant function calls into literals in FunctionCall::Make. + case Expression::Kind::kFunctionCall: + case Expression::Kind::kChildCall: + + // These shouldn't appear in a valid program at all, and definitely aren't + // constant-(index)-expressions. + case Expression::Kind::kPoison: + case Expression::Kind::kFunctionReference: + case Expression::Kind::kMethodReference: + case Expression::Kind::kTypeReference: + return true; + + default: + SkDEBUGFAIL("Unexpected expression type"); + return true; + } + } + +private: + const std::set<const Variable*>* fLoopIndices; + using INHERITED = ProgramVisitor; +}; + +bool Analysis::IsConstantExpression(const Expression& expr) { + return !ConstantExpressionVisitor{/*loopIndices=*/nullptr}.visitExpression(expr); +} + +bool Analysis::IsConstantIndexExpression(const Expression& expr, + const std::set<const Variable*>* loopIndices) { + return !ConstantExpressionVisitor{loopIndices}.visitExpression(expr); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsDynamicallyUniformExpression.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsDynamicallyUniformExpression.cpp new file mode 100644 index 0000000000..0e07fac6f5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsDynamicallyUniformExpression.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2023 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +namespace SkSL { + +bool Analysis::IsDynamicallyUniformExpression(const Expression& expr) { + class IsDynamicallyUniformExpressionVisitor : public ProgramVisitor { + public: + bool visitExpression(const Expression& expr) override { + switch (expr.kind()) { + case Expression::Kind::kBinary: + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorStruct: + case Expression::Kind::kFieldAccess: + case Expression::Kind::kIndex: + case Expression::Kind::kPostfix: + case Expression::Kind::kPrefix: + case Expression::Kind::kSwizzle: + case Expression::Kind::kTernary: + // These expressions might be dynamically uniform, if they are composed entirely + // of constants and uniforms. + break; + + case Expression::Kind::kVariableReference: { + // Verify that variable references are const or uniform. + const Variable* var = expr.as<VariableReference>().variable(); + if (!var || !(var->modifiers().fFlags & (Modifiers::Flag::kConst_Flag | + Modifiers::Flag::kUniform_Flag))) { + fIsDynamicallyUniform = false; + return true; + } + break; + } + case Expression::Kind::kFunctionCall: { + // Verify that function calls are pure. + const FunctionDeclaration& decl = expr.as<FunctionCall>().function(); + if (!(decl.modifiers().fFlags & Modifiers::Flag::kPure_Flag)) { + fIsDynamicallyUniform = false; + return true; + } + break; + } + case Expression::Kind::kLiteral: + // Literals are compile-time constants. + return false; + + default: + // This expression isn't dynamically uniform. + fIsDynamicallyUniform = false; + return true; + } + return INHERITED::visitExpression(expr); + } + + bool fIsDynamicallyUniform = true; + using INHERITED = ProgramVisitor; + }; + + IsDynamicallyUniformExpressionVisitor visitor; + visitor.visitExpression(expr); + return visitor.fIsDynamicallyUniform; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp new file mode 100644 index 0000000000..2c4506a725 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2022 Google LLC + * + * 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/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <cstddef> +#include <memory> + +namespace SkSL { + +bool Analysis::IsSameExpressionTree(const Expression& left, const Expression& right) { + if (left.kind() != right.kind() || !left.type().matches(right.type())) { + return false; + } + + // This isn't a fully exhaustive list of expressions by any stretch of the imagination; for + // instance, `x[y+1] = x[y+1]` isn't detected because we don't look at BinaryExpressions. + // Since this is intended to be used for optimization purposes, handling the common cases is + // sufficient. + switch (left.kind()) { + case Expression::Kind::kLiteral: + return left.as<Literal>().value() == right.as<Literal>().value(); + + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorStruct: + case Expression::Kind::kConstructorSplat: { + if (left.kind() != right.kind()) { + return false; + } + const AnyConstructor& leftCtor = left.asAnyConstructor(); + const AnyConstructor& rightCtor = right.asAnyConstructor(); + const auto leftSpan = leftCtor.argumentSpan(); + const auto rightSpan = rightCtor.argumentSpan(); + if (leftSpan.size() != rightSpan.size()) { + return false; + } + for (size_t index = 0; index < leftSpan.size(); ++index) { + if (!IsSameExpressionTree(*leftSpan[index], *rightSpan[index])) { + return false; + } + } + return true; + } + case Expression::Kind::kFieldAccess: + return left.as<FieldAccess>().fieldIndex() == right.as<FieldAccess>().fieldIndex() && + IsSameExpressionTree(*left.as<FieldAccess>().base(), + *right.as<FieldAccess>().base()); + + case Expression::Kind::kIndex: + return IsSameExpressionTree(*left.as<IndexExpression>().index(), + *right.as<IndexExpression>().index()) && + IsSameExpressionTree(*left.as<IndexExpression>().base(), + *right.as<IndexExpression>().base()); + + case Expression::Kind::kPrefix: + return (left.as<PrefixExpression>().getOperator().kind() == + right.as<PrefixExpression>().getOperator().kind()) && + IsSameExpressionTree(*left.as<PrefixExpression>().operand(), + *right.as<PrefixExpression>().operand()); + + case Expression::Kind::kSwizzle: + return left.as<Swizzle>().components() == right.as<Swizzle>().components() && + IsSameExpressionTree(*left.as<Swizzle>().base(), *right.as<Swizzle>().base()); + + case Expression::Kind::kVariableReference: + return left.as<VariableReference>().variable() == + right.as<VariableReference>().variable(); + + default: + return false; + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp new file mode 100644 index 0000000000..4479d1215d --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 2022 Google LLC + * + * 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/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLType.h" + +#include <memory> + +namespace SkSL { + +bool Analysis::IsTrivialExpression(const Expression& expr) { + switch (expr.kind()) { + case Expression::Kind::kLiteral: + case Expression::Kind::kVariableReference: + return true; + + case Expression::Kind::kSwizzle: + // All swizzles are considered to be trivial. + return IsTrivialExpression(*expr.as<Swizzle>().base()); + + case Expression::Kind::kFieldAccess: + // Accessing a field is trivial. + return IsTrivialExpression(*expr.as<FieldAccess>().base()); + + case Expression::Kind::kIndex: { + // Accessing a constant array index is trivial. + const IndexExpression& inner = expr.as<IndexExpression>(); + return inner.index()->isIntLiteral() && IsTrivialExpression(*inner.base()); + } + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorStruct: + // Only consider small arrays/structs of compile-time-constants to be trivial. + return expr.type().slotCount() <= 4 && IsCompileTimeConstant(expr); + + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorMatrixResize: + // These operations require function calls in Metal, so they're never trivial. + return false; + + case Expression::Kind::kConstructorCompound: + // Only compile-time-constant compound constructors are considered to be trivial. + return IsCompileTimeConstant(expr); + + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorDiagonalMatrix: { + // Single-argument constructors are trivial when their inner expression is trivial. + SkASSERT(expr.asAnyConstructor().argumentSpan().size() == 1); + const Expression& inner = *expr.asAnyConstructor().argumentSpan().front(); + return IsTrivialExpression(inner); + } + default: + return false; + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h b/gfx/skia/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h new file mode 100644 index 0000000000..16040d4d71 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSLNoOpErrorReporter_DEFINED +#define SkSLNoOpErrorReporter_DEFINED + +#include "include/sksl/SkSLErrorReporter.h" + +namespace SkSL { + +// We can use a no-op error reporter to silently ignore errors. +class NoOpErrorReporter : public ErrorReporter { +public: + void handleError(std::string_view, Position) override {} +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.cpp new file mode 100644 index 0000000000..46f0a452b6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.cpp @@ -0,0 +1,242 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/base/SkDebug.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <cstring> +#include <memory> +#include <string_view> +#include <vector> + +namespace SkSL { + +struct Program; + +namespace { + +class ProgramUsageVisitor : public ProgramVisitor { +public: + ProgramUsageVisitor(ProgramUsage* usage, int delta) : fUsage(usage), fDelta(delta) {} + + bool visitProgramElement(const ProgramElement& pe) override { + if (pe.is<FunctionDefinition>()) { + for (const Variable* param : pe.as<FunctionDefinition>().declaration().parameters()) { + // Ensure function-parameter variables exist in the variable usage map. They aren't + // otherwise declared, but ProgramUsage::get() should be able to find them, even if + // they are unread and unwritten. + fUsage->fVariableCounts[param]; + } + } else if (pe.is<InterfaceBlock>()) { + // Ensure interface-block variables exist in the variable usage map. + fUsage->fVariableCounts[pe.as<InterfaceBlock>().var()]; + } + return INHERITED::visitProgramElement(pe); + } + + bool visitStatement(const Statement& s) override { + if (s.is<VarDeclaration>()) { + // Add all declared variables to the usage map (even if never otherwise accessed). + const VarDeclaration& vd = s.as<VarDeclaration>(); + ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[vd.var()]; + counts.fVarExists += fDelta; + SkASSERT(counts.fVarExists >= 0 && counts.fVarExists <= 1); + if (vd.value()) { + // The initial-value expression, when present, counts as a write. + counts.fWrite += fDelta; + } + } + return INHERITED::visitStatement(s); + } + + bool visitExpression(const Expression& e) override { + if (e.is<FunctionCall>()) { + const FunctionDeclaration* f = &e.as<FunctionCall>().function(); + fUsage->fCallCounts[f] += fDelta; + SkASSERT(fUsage->fCallCounts[f] >= 0); + } else if (e.is<VariableReference>()) { + const VariableReference& ref = e.as<VariableReference>(); + ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[ref.variable()]; + switch (ref.refKind()) { + case VariableRefKind::kRead: + counts.fRead += fDelta; + break; + case VariableRefKind::kWrite: + counts.fWrite += fDelta; + break; + case VariableRefKind::kReadWrite: + case VariableRefKind::kPointer: + counts.fRead += fDelta; + counts.fWrite += fDelta; + break; + } + SkASSERT(counts.fRead >= 0 && counts.fWrite >= 0); + } + return INHERITED::visitExpression(e); + } + + using ProgramVisitor::visitProgramElement; + using ProgramVisitor::visitStatement; + + ProgramUsage* fUsage; + int fDelta; + using INHERITED = ProgramVisitor; +}; + +} // namespace + +std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Program& program) { + auto usage = std::make_unique<ProgramUsage>(); + ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1); + addRefs.visit(program); + return usage; +} + +std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Module& module) { + auto usage = std::make_unique<ProgramUsage>(); + ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1); + + for (const Module* m = &module; m != nullptr; m = m->fParent) { + for (const std::unique_ptr<ProgramElement>& element : m->fElements) { + addRefs.visitProgramElement(*element); + } + } + return usage; +} + +ProgramUsage::VariableCounts ProgramUsage::get(const Variable& v) const { + const VariableCounts* counts = fVariableCounts.find(&v); + SkASSERT(counts); + return *counts; +} + +bool ProgramUsage::isDead(const Variable& v) const { + const Modifiers& modifiers = v.modifiers(); + VariableCounts counts = this->get(v); + if ((v.storage() != Variable::Storage::kLocal && counts.fRead) || + (modifiers.fFlags & + (Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag))) { + return false; + } + // Consider the variable dead if it's never read and never written (besides the initial-value). + return !counts.fRead && (counts.fWrite <= (v.initialValue() ? 1 : 0)); +} + +int ProgramUsage::get(const FunctionDeclaration& f) const { + const int* count = fCallCounts.find(&f); + return count ? *count : 0; +} + +void ProgramUsage::add(const Expression* expr) { + ProgramUsageVisitor addRefs(this, /*delta=*/+1); + addRefs.visitExpression(*expr); +} + +void ProgramUsage::add(const Statement* stmt) { + ProgramUsageVisitor addRefs(this, /*delta=*/+1); + addRefs.visitStatement(*stmt); +} + +void ProgramUsage::add(const ProgramElement& element) { + ProgramUsageVisitor addRefs(this, /*delta=*/+1); + addRefs.visitProgramElement(element); +} + +void ProgramUsage::remove(const Expression* expr) { + ProgramUsageVisitor subRefs(this, /*delta=*/-1); + subRefs.visitExpression(*expr); +} + +void ProgramUsage::remove(const Statement* stmt) { + ProgramUsageVisitor subRefs(this, /*delta=*/-1); + subRefs.visitStatement(*stmt); +} + +void ProgramUsage::remove(const ProgramElement& element) { + ProgramUsageVisitor subRefs(this, /*delta=*/-1); + subRefs.visitProgramElement(element); +} + +static bool contains_matching_data(const ProgramUsage& a, const ProgramUsage& b) { + constexpr bool kReportMismatch = false; + + for (const auto& [varA, varCountA] : a.fVariableCounts) { + // Skip variable entries with zero reported usage. + if (!varCountA.fVarExists && !varCountA.fRead && !varCountA.fWrite) { + continue; + } + // Find the matching variable in the other map and ensure that its counts match. + const ProgramUsage::VariableCounts* varCountB = b.fVariableCounts.find(varA); + if (!varCountB || 0 != memcmp(&varCountA, varCountB, sizeof(varCountA))) { + if constexpr (kReportMismatch) { + SkDebugf("VariableCounts mismatch: '%.*s' (E%d R%d W%d != E%d R%d W%d)\n", + (int)varA->name().size(), varA->name().data(), + varCountA.fVarExists, + varCountA.fRead, + varCountA.fWrite, + varCountB ? varCountB->fVarExists : 0, + varCountB ? varCountB->fRead : 0, + varCountB ? varCountB->fWrite : 0); + } + return false; + } + } + + for (const auto& [callA, callCountA] : a.fCallCounts) { + // Skip function-call entries with zero reported usage. + if (!callCountA) { + continue; + } + // Find the matching function in the other map and ensure that its call-count matches. + const int* callCountB = b.fCallCounts.find(callA); + if (!callCountB || callCountA != *callCountB) { + if constexpr (kReportMismatch) { + SkDebugf("CallCounts mismatch: '%.*s' (%d != %d)\n", + (int)callA->name().size(), callA->name().data(), + callCountA, + callCountB ? *callCountB : 0); + } + return false; + } + } + + // Every non-zero entry in A has a matching non-zero entry in B. + return true; +} + +bool ProgramUsage::operator==(const ProgramUsage& that) const { + // ProgramUsage can be "equal" while the underlying hash maps look slightly different, because a + // dead-stripped variable or function will have a usage count of zero, but will still exist in + // the maps. If the program usage is re-analyzed from scratch, the maps will not contain an + // entry for these variables or functions at all. This means our maps can be "equal" while + // having different element counts. + // + // In order to check these maps, we compare map entries bi-directionally, skipping zero-usage + // entries. If all the non-zero elements in `this` match the elements in `that`, and all the + // non-zero elements in `that` match the elements in `this`, all the non-zero elements must be + // identical, and all the zero elements must be either zero or non-existent on both sides. + return contains_matching_data(*this, that) && + contains_matching_data(that, *this); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.h b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.h new file mode 100644 index 0000000000..991240ce2d --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLProgramUsage.h @@ -0,0 +1,53 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PROGRAMUSAGE +#define SKSL_PROGRAMUSAGE + +#include "include/core/SkTypes.h" +#include "src/core/SkTHash.h" + +namespace SkSL { + +class Expression; +class FunctionDeclaration; +class ProgramElement; +class Statement; +class Variable; + +/** + * Side-car class holding mutable information about a Program's IR + */ +class ProgramUsage { +public: + struct VariableCounts { + int fVarExists = 0; // if this is zero, the Variable might have already been deleted + int fRead = 0; + int fWrite = 0; + }; + VariableCounts get(const Variable&) const; + bool isDead(const Variable&) const; + + int get(const FunctionDeclaration&) const; + + void add(const Expression* expr); + void add(const Statement* stmt); + void add(const ProgramElement& element); + void remove(const Expression* expr); + void remove(const Statement* stmt); + void remove(const ProgramElement& element); + + bool operator==(const ProgramUsage& that) const; + bool operator!=(const ProgramUsage& that) const { return !(*this == that); } + + SkTHashMap<const Variable*, VariableCounts> fVariableCounts; + SkTHashMap<const FunctionDeclaration*, int> fCallCounts; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLProgramVisitor.h b/gfx/skia/skia/src/sksl/analysis/SkSLProgramVisitor.h new file mode 100644 index 0000000000..3a27ef35d1 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLProgramVisitor.h @@ -0,0 +1,77 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSLProgramVisitor_DEFINED +#define SkSLProgramVisitor_DEFINED + +#include <memory> + +namespace SkSL { + +struct Program; +class Expression; +class Statement; +class ProgramElement; + +/** + * Utility class to visit every element, statement, and expression in an SkSL program IR. + * This is intended for simple analysis and accumulation, where custom visitation behavior is only + * needed for a limited set of expression kinds. + * + * Subclasses should override visitExpression/visitStatement/visitProgramElement as needed and + * intercept elements of interest. They can then invoke the base class's function to visit all + * sub expressions. They can also choose not to call the base function to arrest recursion, or + * implement custom recursion. + * + * The visit functions return a bool that determines how the default implementation recurses. Once + * any visit call returns true, the default behavior stops recursing and propagates true up the + * stack. + */ +template <typename T> +class TProgramVisitor { +public: + virtual ~TProgramVisitor() = default; + +protected: + virtual bool visitExpression(typename T::Expression& expression); + virtual bool visitStatement(typename T::Statement& statement); + virtual bool visitProgramElement(typename T::ProgramElement& programElement); + + virtual bool visitExpressionPtr(typename T::UniquePtrExpression& expr) = 0; + virtual bool visitStatementPtr(typename T::UniquePtrStatement& stmt) = 0; +}; + +// ProgramVisitors take const types; ProgramWriters do not. +struct ProgramVisitorTypes { + using Program = const SkSL::Program; + using Expression = const SkSL::Expression; + using Statement = const SkSL::Statement; + using ProgramElement = const SkSL::ProgramElement; + using UniquePtrExpression = const std::unique_ptr<SkSL::Expression>; + using UniquePtrStatement = const std::unique_ptr<SkSL::Statement>; +}; + +extern template class TProgramVisitor<ProgramVisitorTypes>; + +class ProgramVisitor : public TProgramVisitor<ProgramVisitorTypes> { +public: + bool visit(const Program& program); + +private: + // ProgramVisitors shouldn't need access to unique_ptrs, and marking these as final should help + // these accessors inline away. Use ProgramWriter if you need the unique_ptrs. + bool visitExpressionPtr(const std::unique_ptr<Expression>& e) final { + return this->visitExpression(*e); + } + bool visitStatementPtr(const std::unique_ptr<Statement>& s) final { + return this->visitStatement(*s); + } +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLSwitchCaseContainsExit.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLSwitchCaseContainsExit.cpp new file mode 100644 index 0000000000..992df2adde --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLSwitchCaseContainsExit.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLAnalysis.h" + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" + +namespace SkSL { + +class Expression; + +namespace { + +class SwitchCaseContainsExit : public ProgramVisitor { +public: + SwitchCaseContainsExit(bool conditionalExits) : fConditionalExits(conditionalExits) {} + + bool visitExpression(const Expression& expr) override { + // We can avoid processing expressions entirely. + return false; + } + + bool visitStatement(const Statement& stmt) override { + switch (stmt.kind()) { + case Statement::Kind::kBlock: + case Statement::Kind::kSwitchCase: + return INHERITED::visitStatement(stmt); + + case Statement::Kind::kReturn: + // Returns are an early exit regardless of the surrounding control structures. + return fConditionalExits ? fInConditional : !fInConditional; + + case Statement::Kind::kContinue: + // Continues are an early exit from switches, but not loops. + return !fInLoop && + (fConditionalExits ? fInConditional : !fInConditional); + + case Statement::Kind::kBreak: + // Breaks cannot escape from switches or loops. + return !fInLoop && !fInSwitch && + (fConditionalExits ? fInConditional : !fInConditional); + + case Statement::Kind::kIf: { + ++fInConditional; + bool result = INHERITED::visitStatement(stmt); + --fInConditional; + return result; + } + + case Statement::Kind::kFor: + case Statement::Kind::kDo: { + // Loops are treated as conditionals because a loop could potentially execute zero + // times. We don't have a straightforward way to determine that a loop definitely + // executes at least once. + ++fInConditional; + ++fInLoop; + bool result = INHERITED::visitStatement(stmt); + --fInLoop; + --fInConditional; + return result; + } + + case Statement::Kind::kSwitch: { + ++fInSwitch; + bool result = INHERITED::visitStatement(stmt); + --fInSwitch; + return result; + } + + default: + return false; + } + } + + bool fConditionalExits = false; + int fInConditional = 0; + int fInLoop = 0; + int fInSwitch = 0; + using INHERITED = ProgramVisitor; +}; + +} // namespace + +bool Analysis::SwitchCaseContainsUnconditionalExit(Statement& stmt) { + return SwitchCaseContainsExit{/*conditionalExits=*/false}.visitStatement(stmt); +} + +bool Analysis::SwitchCaseContainsConditionalExit(Statement& stmt) { + return SwitchCaseContainsExit{/*conditionalExits=*/true}.visitStatement(stmt); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/analysis/SkSLSymbolTableStackBuilder.cpp b/gfx/skia/skia/src/sksl/analysis/SkSLSymbolTableStackBuilder.cpp new file mode 100644 index 0000000000..ada31aa000 --- /dev/null +++ b/gfx/skia/skia/src/sksl/analysis/SkSLSymbolTableStackBuilder.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" + +#include <memory> +#include <utility> +#include <vector> + +namespace SkSL { + +class SymbolTable; + +namespace Analysis { + +SymbolTableStackBuilder::SymbolTableStackBuilder(const Statement* stmt, + std::vector<std::shared_ptr<SymbolTable>>* stack) { + if (stmt) { + switch (stmt->kind()) { + case Statement::Kind::kBlock: + if (std::shared_ptr<SymbolTable> symbols = stmt->as<Block>().symbolTable()) { + stack->push_back(std::move(symbols)); + fStackToPop = stack; + } + break; + + case Statement::Kind::kFor: + if (std::shared_ptr<SymbolTable> symbols = stmt->as<ForStatement>().symbols()) { + stack->push_back(std::move(symbols)); + fStackToPop = stack; + } + break; + + case Statement::Kind::kSwitch: + if (std::shared_ptr<SymbolTable> symbols = stmt->as<SwitchStatement>().symbols()) { + stack->push_back(std::move(symbols)); + fStackToPop = stack; + } + break; + + default: + break; + } + } +} + +SymbolTableStackBuilder::~SymbolTableStackBuilder() { + if (fStackToPop) { + fStackToPop->pop_back(); + } +} + +} // namespace Analysis +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLCodeGenerator.h new file mode 100644 index 0000000000..fd58648cd9 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLCodeGenerator.h @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CODEGENERATOR +#define SKSL_CODEGENERATOR + +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/ir/SkSLProgram.h" + +namespace SkSL { + +/** + * Abstract superclass of all code generators, which take a Program as input and produce code as + * output. + */ +class CodeGenerator { +public: + CodeGenerator(const Context* context, const Program* program, OutputStream* out) + : fContext(*context) + , fProgram(*program) + , fOut(out) {} + + virtual ~CodeGenerator() {} + + virtual bool generateCode() = 0; + + // Intended for use by AutoOutputStream. + OutputStream* outputStream() { return fOut; } + void setOutputStream(OutputStream* output) { fOut = output; } + +protected: +#if defined(SK_USE_LEGACY_MIPMAP_LOD_BIAS) + static constexpr float kSharpenTexturesBias = -.5f; +#else + // For SkMipmapMode::kLinear we want a bias such that when the unbiased LOD value is + // midway between levels we select just the larger level, i.e. a bias of -.5. However, using + // this bias with kNearest mode with a draw that is a perfect power of two downscale puts us + // right on the rounding edge where it could go up or down depending on the particular GPU. + // Experimentally we found that at -.49 most iOS devices (iPhone 7, 8, and iPad Pro + // [PowerVRGT7800 version]) all round to the level twice as big as the device space footprint + // for some such draws in our unit tests on GLES. However, the iPhone 11 still fails and so + // we are using -.475. They do not at -.48. All other GPUs passed tests with -.499. Though, at + // this time the bias is not implemented in the MSL codegen and so iOS/Metal was not tested. + static constexpr float kSharpenTexturesBias = -.475f; +#endif + + const Context& fContext; + const Program& fProgram; + OutputStream* fOut; +}; + +class AutoOutputStream { +public: + // Maintains the current indentation level while writing to the new output stream. + AutoOutputStream(CodeGenerator* codeGen, OutputStream* newOutput) + : fCodeGen(codeGen) + , fOldOutput(codeGen->outputStream()) { + fCodeGen->setOutputStream(newOutput); + } + // Resets the indentation when entering the scope, and restores it when leaving. + AutoOutputStream(CodeGenerator* codeGen, OutputStream* newOutput, int *indentationPtr) + : fCodeGen(codeGen) + , fOldOutput(codeGen->outputStream()) + , fIndentationPtr(indentationPtr) + , fOldIndentation(indentationPtr ? *indentationPtr : 0) { + fCodeGen->setOutputStream(newOutput); + *fIndentationPtr = 0; + } + ~AutoOutputStream() { + fCodeGen->setOutputStream(fOldOutput); + if (fIndentationPtr) { + *fIndentationPtr = fOldIndentation; + } + } + +private: + CodeGenerator* fCodeGen = nullptr; + OutputStream* fOldOutput = nullptr; + int *fIndentationPtr = nullptr; + int fOldIndentation = 0; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp new file mode 100644 index 0000000000..c953a1a46c --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp @@ -0,0 +1,1774 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/codegen/SkSLGLSLCodeGenerator.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLGLSL.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorArrayCast.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLFunctionPrototype.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLModifiersDeclaration.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLStructDefinition.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/spirv.h" + +#include <cstddef> +#include <memory> +#include <vector> + +namespace SkSL { + +void GLSLCodeGenerator::write(std::string_view s) { + if (!s.length()) { + return; + } + if (fAtLineStart) { + for (int i = 0; i < fIndentation; i++) { + fOut->writeText(" "); + } + } + fOut->write(s.data(), s.length()); + fAtLineStart = false; +} + +void GLSLCodeGenerator::writeLine(std::string_view s) { + this->write(s); + fOut->writeText("\n"); + fAtLineStart = true; +} + +void GLSLCodeGenerator::finishLine() { + if (!fAtLineStart) { + this->writeLine(); + } +} + +void GLSLCodeGenerator::writeExtension(std::string_view name, bool require) { + fExtensions.writeText("#extension "); + fExtensions.write(name.data(), name.length()); + fExtensions.writeText(require ? " : require\n" : " : enable\n"); +} + +bool GLSLCodeGenerator::usesPrecisionModifiers() const { + return this->caps().fUsesPrecisionModifiers; +} + +void GLSLCodeGenerator::writeIdentifier(std::string_view identifier) { + // GLSL forbids two underscores in a row. + // If an identifier contains "__" or "_X", replace each "_" in the identifier with "_X". + if (skstd::contains(identifier, "__") || skstd::contains(identifier, "_X")) { + for (const char c : identifier) { + if (c == '_') { + this->write("_X"); + } else { + this->write(std::string_view(&c, 1)); + } + } + } else { + this->write(identifier); + } +} + +// Returns the name of the type with array dimensions, e.g. `float[2]`. +std::string GLSLCodeGenerator::getTypeName(const Type& raw) { + const Type& type = raw.resolve(); + switch (type.typeKind()) { + case Type::TypeKind::kVector: { + const Type& component = type.componentType(); + std::string result; + if (component.matches(*fContext.fTypes.fFloat) || + component.matches(*fContext.fTypes.fHalf)) { + result = "vec"; + } + else if (component.isSigned()) { + result = "ivec"; + } + else if (component.isUnsigned()) { + result = "uvec"; + } + else if (component.matches(*fContext.fTypes.fBool)) { + result = "bvec"; + } + else { + SK_ABORT("unsupported vector type"); + } + result += std::to_string(type.columns()); + return result; + } + case Type::TypeKind::kMatrix: { + std::string result; + const Type& component = type.componentType(); + if (component.matches(*fContext.fTypes.fFloat) || + component.matches(*fContext.fTypes.fHalf)) { + result = "mat"; + } + else { + SK_ABORT("unsupported matrix type"); + } + result += std::to_string(type.columns()); + if (type.columns() != type.rows()) { + result += "x"; + result += std::to_string(type.rows()); + } + return result; + } + case Type::TypeKind::kArray: { + std::string baseTypeName = this->getTypeName(type.componentType()); + if (type.isUnsizedArray()) { + return String::printf("%s[]", baseTypeName.c_str()); + } + return String::printf("%s[%d]", baseTypeName.c_str(), type.columns()); + } + case Type::TypeKind::kScalar: { + if (type.matches(*fContext.fTypes.fHalf)) { + return "float"; + } + else if (type.matches(*fContext.fTypes.fShort)) { + return "int"; + } + else if (type.matches(*fContext.fTypes.fUShort)) { + return "uint"; + } + + return std::string(type.name()); + } + default: + return std::string(type.name()); + } +} + +void GLSLCodeGenerator::writeStructDefinition(const StructDefinition& s) { + const Type& type = s.type(); + this->write("struct "); + this->writeIdentifier(type.name()); + this->writeLine(" {"); + fIndentation++; + for (const auto& f : type.fields()) { + this->writeModifiers(f.fModifiers, false); + this->writeTypePrecision(*f.fType); + const Type& baseType = f.fType->isArray() ? f.fType->componentType() : *f.fType; + this->writeType(baseType); + this->write(" "); + this->writeIdentifier(f.fName); + if (f.fType->isArray()) { + this->write("[" + std::to_string(f.fType->columns()) + "]"); + } + this->writeLine(";"); + } + fIndentation--; + this->writeLine("};"); +} + +void GLSLCodeGenerator::writeType(const Type& type) { + this->writeIdentifier(this->getTypeName(type)); +} + +void GLSLCodeGenerator::writeExpression(const Expression& expr, Precedence parentPrecedence) { + switch (expr.kind()) { + case Expression::Kind::kBinary: + this->writeBinaryExpression(expr.as<BinaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kConstructorDiagonalMatrix: + this->writeConstructorDiagonalMatrix(expr.as<ConstructorDiagonalMatrix>(), + parentPrecedence); + break; + case Expression::Kind::kConstructorArrayCast: + this->writeExpression(*expr.as<ConstructorArrayCast>().argument(), parentPrecedence); + break; + case Expression::Kind::kConstructorCompound: + this->writeConstructorCompound(expr.as<ConstructorCompound>(), parentPrecedence); + break; + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorStruct: + this->writeAnyConstructor(expr.asAnyConstructor(), parentPrecedence); + break; + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorCompoundCast: + this->writeCastConstructor(expr.asAnyConstructor(), parentPrecedence); + break; + case Expression::Kind::kFieldAccess: + this->writeFieldAccess(expr.as<FieldAccess>()); + break; + case Expression::Kind::kFunctionCall: + this->writeFunctionCall(expr.as<FunctionCall>()); + break; + case Expression::Kind::kLiteral: + this->writeLiteral(expr.as<Literal>()); + break; + case Expression::Kind::kPrefix: + this->writePrefixExpression(expr.as<PrefixExpression>(), parentPrecedence); + break; + case Expression::Kind::kPostfix: + this->writePostfixExpression(expr.as<PostfixExpression>(), parentPrecedence); + break; + case Expression::Kind::kSetting: + this->writeExpression(*expr.as<Setting>().toLiteral(fContext), parentPrecedence); + break; + case Expression::Kind::kSwizzle: + this->writeSwizzle(expr.as<Swizzle>()); + break; + case Expression::Kind::kVariableReference: + this->writeVariableReference(expr.as<VariableReference>()); + break; + case Expression::Kind::kTernary: + this->writeTernaryExpression(expr.as<TernaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kIndex: + this->writeIndexExpression(expr.as<IndexExpression>()); + break; + default: + SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str()); + break; + } +} + +static bool is_abs(Expression& expr) { + return expr.is<FunctionCall>() && + expr.as<FunctionCall>().function().intrinsicKind() == k_abs_IntrinsicKind; +} + +// turns min(abs(x), y) into ((tmpVar1 = abs(x)) < (tmpVar2 = y) ? tmpVar1 : tmpVar2) to avoid a +// Tegra3 compiler bug. +void GLSLCodeGenerator::writeMinAbsHack(Expression& absExpr, Expression& otherExpr) { + SkASSERT(!this->caps().fCanUseMinAndAbsTogether); + std::string tmpVar1 = "minAbsHackVar" + std::to_string(fVarCount++); + std::string tmpVar2 = "minAbsHackVar" + std::to_string(fVarCount++); + this->fFunctionHeader += std::string(" ") + this->getTypePrecision(absExpr.type()) + + this->getTypeName(absExpr.type()) + " " + tmpVar1 + ";\n"; + this->fFunctionHeader += std::string(" ") + this->getTypePrecision(otherExpr.type()) + + this->getTypeName(otherExpr.type()) + " " + tmpVar2 + ";\n"; + this->write("((" + tmpVar1 + " = "); + this->writeExpression(absExpr, Precedence::kTopLevel); + this->write(") < (" + tmpVar2 + " = "); + this->writeExpression(otherExpr, Precedence::kAssignment); + this->write(") ? " + tmpVar1 + " : " + tmpVar2 + ")"); +} + +void GLSLCodeGenerator::writeInverseSqrtHack(const Expression& x) { + this->write("(1.0 / sqrt("); + this->writeExpression(x, Precedence::kTopLevel); + this->write("))"); +} + +static constexpr char kDeterminant2[] = R"( +float _determinant2(mat2 m) { +return m[0].x*m[1].y - m[0].y*m[1].x; +} +)"; + +static constexpr char kDeterminant3[] = R"( +float _determinant3(mat3 m) { +float + a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, + a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, + a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, + b01 = a22*a11 - a12*a21, + b11 =-a22*a10 + a12*a20, + b21 = a21*a10 - a11*a20; +return a00*b01 + a01*b11 + a02*b21; +} +)"; + +static constexpr char kDeterminant4[] = R"( +mat4 _determinant4(mat4 m) { +float + a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, a03 = m[0].w, + a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, a13 = m[1].w, + a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, a23 = m[2].w, + a30 = m[3].x, a31 = m[3].y, a32 = m[3].z, a33 = m[3].w, + b00 = a00*a11 - a01*a10, + b01 = a00*a12 - a02*a10, + b02 = a00*a13 - a03*a10, + b03 = a01*a12 - a02*a11, + b04 = a01*a13 - a03*a11, + b05 = a02*a13 - a03*a12, + b06 = a20*a31 - a21*a30, + b07 = a20*a32 - a22*a30, + b08 = a20*a33 - a23*a30, + b09 = a21*a32 - a22*a31, + b10 = a21*a33 - a23*a31, + b11 = a22*a33 - a23*a32; +return b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06; +} +)"; + +void GLSLCodeGenerator::writeDeterminantHack(const Expression& mat) { + const Type& type = mat.type(); + if (type.matches(*fContext.fTypes.fFloat2x2) || + type.matches(*fContext.fTypes.fHalf2x2)) { + this->write("_determinant2("); + if (!fWrittenDeterminant2) { + fWrittenDeterminant2 = true; + fExtraFunctions.writeText(kDeterminant2); + } + } else if (type.matches(*fContext.fTypes.fFloat3x3) || + type.matches(*fContext.fTypes.fHalf3x3)) { + this->write("_determinant3("); + if (!fWrittenDeterminant3) { + fWrittenDeterminant3 = true; + fExtraFunctions.writeText(kDeterminant3); + } + } else if (type.matches(*fContext.fTypes.fFloat4x4) || + type.matches(*fContext.fTypes.fHalf4x4)) { + this->write("_determinant4("); + if (!fWrittenDeterminant4) { + fWrittenDeterminant4 = true; + fExtraFunctions.writeText(kDeterminant4); + } + } else { + SkDEBUGFAILF("no polyfill for determinant(%s)", type.description().c_str()); + this->write("determinant("); + } + this->writeExpression(mat, Precedence::kTopLevel); + this->write(")"); +} + +static constexpr char kInverse2[] = R"( +mat2 _inverse2(mat2 m) { +return mat2(m[1].y, -m[0].y, -m[1].x, m[0].x) / (m[0].x * m[1].y - m[0].y * m[1].x); +} +)"; + +static constexpr char kInverse3[] = R"( +mat3 _inverse3(mat3 m) { +float + a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, + a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, + a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, + b01 = a22*a11 - a12*a21, + b11 =-a22*a10 + a12*a20, + b21 = a21*a10 - a11*a20, + det = a00*b01 + a01*b11 + a02*b21; +return mat3( + b01, (-a22*a01 + a02*a21), ( a12*a01 - a02*a11), + b11, ( a22*a00 - a02*a20), (-a12*a00 + a02*a10), + b21, (-a21*a00 + a01*a20), ( a11*a00 - a01*a10)) / det; +} +)"; + +static constexpr char kInverse4[] = R"( +mat4 _inverse4(mat4 m) { +float + a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, a03 = m[0].w, + a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, a13 = m[1].w, + a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, a23 = m[2].w, + a30 = m[3].x, a31 = m[3].y, a32 = m[3].z, a33 = m[3].w, + b00 = a00*a11 - a01*a10, + b01 = a00*a12 - a02*a10, + b02 = a00*a13 - a03*a10, + b03 = a01*a12 - a02*a11, + b04 = a01*a13 - a03*a11, + b05 = a02*a13 - a03*a12, + b06 = a20*a31 - a21*a30, + b07 = a20*a32 - a22*a30, + b08 = a20*a33 - a23*a30, + b09 = a21*a32 - a22*a31, + b10 = a21*a33 - a23*a31, + b11 = a22*a33 - a23*a32, + det = b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06; +return mat4( + a11*b11 - a12*b10 + a13*b09, + a02*b10 - a01*b11 - a03*b09, + a31*b05 - a32*b04 + a33*b03, + a22*b04 - a21*b05 - a23*b03, + a12*b08 - a10*b11 - a13*b07, + a00*b11 - a02*b08 + a03*b07, + a32*b02 - a30*b05 - a33*b01, + a20*b05 - a22*b02 + a23*b01, + a10*b10 - a11*b08 + a13*b06, + a01*b08 - a00*b10 - a03*b06, + a30*b04 - a31*b02 + a33*b00, + a21*b02 - a20*b04 - a23*b00, + a11*b07 - a10*b09 - a12*b06, + a00*b09 - a01*b07 + a02*b06, + a31*b01 - a30*b03 - a32*b00, + a20*b03 - a21*b01 + a22*b00) / det; +} +)"; + +void GLSLCodeGenerator::writeInverseHack(const Expression& mat) { + const Type& type = mat.type(); + if (type.matches(*fContext.fTypes.fFloat2x2) || type.matches(*fContext.fTypes.fHalf2x2)) { + this->write("_inverse2("); + if (!fWrittenInverse2) { + fWrittenInverse2 = true; + fExtraFunctions.writeText(kInverse2); + } + } else if (type.matches(*fContext.fTypes.fFloat3x3) || + type.matches(*fContext.fTypes.fHalf3x3)) { + this->write("_inverse3("); + if (!fWrittenInverse3) { + fWrittenInverse3 = true; + fExtraFunctions.writeText(kInverse3); + } + } else if (type.matches(*fContext.fTypes.fFloat4x4) || + type.matches(*fContext.fTypes.fHalf4x4)) { + this->write("_inverse4("); + if (!fWrittenInverse4) { + fWrittenInverse4 = true; + fExtraFunctions.writeText(kInverse4); + } + } else { + SkDEBUGFAILF("no polyfill for inverse(%s)", type.description().c_str()); + this->write("inverse("); + } + this->writeExpression(mat, Precedence::kTopLevel); + this->write(")"); +} + +void GLSLCodeGenerator::writeTransposeHack(const Expression& mat) { + const Type& type = mat.type(); + int c = type.columns(); + int r = type.rows(); + std::string name = "transpose" + std::to_string(c) + std::to_string(r); + + SkASSERT(c >= 2 && c <= 4); + SkASSERT(r >= 2 && r <= 4); + bool* writtenThisTranspose = &fWrittenTranspose[c - 2][r - 2]; + if (!*writtenThisTranspose) { + *writtenThisTranspose = true; + std::string typeName = this->getTypeName(type); + const Type& base = type.componentType(); + std::string transposed = this->getTypeName(base.toCompound(fContext, r, c)); + fExtraFunctions.writeText((transposed + " " + name + "(" + typeName + " m) { return " + + transposed + "(").c_str()); + auto separator = SkSL::String::Separator(); + for (int row = 0; row < r; ++row) { + for (int column = 0; column < c; ++column) { + fExtraFunctions.writeText(separator().c_str()); + fExtraFunctions.writeText(("m[" + std::to_string(column) + "][" + + std::to_string(row) + "]").c_str()); + } + } + fExtraFunctions.writeText("); }\n"); + } + this->write(name + "("); + this->writeExpression(mat, Precedence::kTopLevel); + this->write(")"); +} + +void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) { + const FunctionDeclaration& function = c.function(); + const ExpressionArray& arguments = c.arguments(); + bool isTextureFunctionWithBias = false; + bool nameWritten = false; + const char* closingParen = ")"; + switch (c.function().intrinsicKind()) { + case k_abs_IntrinsicKind: { + if (!this->caps().fEmulateAbsIntFunction) + break; + SkASSERT(arguments.size() == 1); + if (!arguments[0]->type().matches(*fContext.fTypes.fInt)) { + break; + } + // abs(int) on Intel OSX is incorrect, so emulate it: + this->write("_absemulation"); + nameWritten = true; + if (!fWrittenAbsEmulation) { + fWrittenAbsEmulation = true; + fExtraFunctions.writeText("int _absemulation(int x) { return x * sign(x); }\n"); + } + break; + } + case k_atan_IntrinsicKind: + if (this->caps().fMustForceNegatedAtanParamToFloat && + arguments.size() == 2 && + arguments[1]->kind() == Expression::Kind::kPrefix) { + const PrefixExpression& p = (PrefixExpression&) *arguments[1]; + if (p.getOperator().kind() == Operator::Kind::MINUS) { + this->write("atan("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(", -1.0 * "); + this->writeExpression(*p.operand(), Precedence::kMultiplicative); + this->write(")"); + return; + } + } + break; + case k_ldexp_IntrinsicKind: + if (this->caps().fMustForceNegatedLdexpParamToMultiply && + arguments.size() == 2 && + arguments[1]->is<PrefixExpression>()) { + const PrefixExpression& p = arguments[1]->as<PrefixExpression>(); + if (p.getOperator().kind() == Operator::Kind::MINUS) { + this->write("ldexp("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(", "); + this->writeExpression(*p.operand(), Precedence::kMultiplicative); + this->write(" * -1)"); + return; + } + } + break; + case k_dFdy_IntrinsicKind: + // Flipping Y also negates the Y derivatives. + closingParen = "))"; + this->write("("); + if (!fProgram.fConfig->fSettings.fForceNoRTFlip) { + this->write(SKSL_RTFLIP_NAME ".y * "); + } + this->write("dFdy"); + nameWritten = true; + [[fallthrough]]; + case k_dFdx_IntrinsicKind: + case k_fwidth_IntrinsicKind: + if (!fFoundDerivatives && + this->caps().shaderDerivativeExtensionString()) { + this->writeExtension(this->caps().shaderDerivativeExtensionString()); + fFoundDerivatives = true; + } + break; + case k_determinant_IntrinsicKind: + if (!this->caps().fBuiltinDeterminantSupport) { + SkASSERT(arguments.size() == 1); + this->writeDeterminantHack(*arguments[0]); + return; + } + break; + case k_fma_IntrinsicKind: + if (!this->caps().fBuiltinFMASupport) { + SkASSERT(arguments.size() == 3); + this->write("(("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(") * ("); + this->writeExpression(*arguments[1], Precedence::kSequence); + this->write(") + ("); + this->writeExpression(*arguments[2], Precedence::kSequence); + this->write("))"); + return; + } + break; + case k_fract_IntrinsicKind: + if (!this->caps().fCanUseFractForNegativeValues) { + SkASSERT(arguments.size() == 1); + this->write("(0.5 - sign("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(") * (0.5 - fract(abs("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("))))"); + return; + } + break; + case k_inverse_IntrinsicKind: + if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k140) { + SkASSERT(arguments.size() == 1); + this->writeInverseHack(*arguments[0]); + return; + } + break; + case k_inversesqrt_IntrinsicKind: + if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) { + SkASSERT(arguments.size() == 1); + this->writeInverseSqrtHack(*arguments[0]); + return; + } + break; + case k_min_IntrinsicKind: + if (!this->caps().fCanUseMinAndAbsTogether) { + SkASSERT(arguments.size() == 2); + if (is_abs(*arguments[0])) { + this->writeMinAbsHack(*arguments[0], *arguments[1]); + return; + } + if (is_abs(*arguments[1])) { + // note that this violates the GLSL left-to-right evaluation semantics. + // I doubt it will ever end up mattering, but it's worth calling out. + this->writeMinAbsHack(*arguments[1], *arguments[0]); + return; + } + } + break; + case k_pow_IntrinsicKind: + if (!this->caps().fRemovePowWithConstantExponent) { + break; + } + // pow(x, y) on some NVIDIA drivers causes crashes if y is a constant. + // It's hard to tell what constitutes "constant" here, so just replace in all cases. + + // Change pow(x, y) into exp2(y * log2(x)) + this->write("exp2("); + this->writeExpression(*arguments[1], Precedence::kMultiplicative); + this->write(" * log2("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("))"); + return; + case k_saturate_IntrinsicKind: + SkASSERT(arguments.size() == 1); + this->write("clamp("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(", 0.0, 1.0)"); + return; + case k_sample_IntrinsicKind: { + const char* dim = ""; + bool proj = false; + const Type& arg0Type = arguments[0]->type(); + const Type& arg1Type = arguments[1]->type(); + switch (arg0Type.dimensions()) { + case SpvDim1D: + dim = "1D"; + isTextureFunctionWithBias = true; + if (arg1Type.matches(*fContext.fTypes.fFloat)) { + proj = false; + } else { + SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2)); + proj = true; + } + break; + case SpvDim2D: + dim = "2D"; + if (!arg0Type.matches(*fContext.fTypes.fSamplerExternalOES)) { + isTextureFunctionWithBias = true; + } + if (arg1Type.matches(*fContext.fTypes.fFloat2)) { + proj = false; + } else { + SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat3)); + proj = true; + } + break; + case SpvDim3D: + dim = "3D"; + isTextureFunctionWithBias = true; + if (arg1Type.matches(*fContext.fTypes.fFloat3)) { + proj = false; + } else { + SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat4)); + proj = true; + } + break; + case SpvDimCube: + dim = "Cube"; + isTextureFunctionWithBias = true; + proj = false; + break; + case SpvDimRect: + dim = "2DRect"; + proj = false; + break; + case SpvDimBuffer: + SkASSERT(false); // doesn't exist + dim = "Buffer"; + proj = false; + break; + case SpvDimSubpassData: + SkASSERT(false); // doesn't exist + dim = "SubpassData"; + proj = false; + break; + } + this->write("texture"); + if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) { + this->write(dim); + } + if (proj) { + this->write("Proj"); + } + nameWritten = true; + break; + } + case k_sampleGrad_IntrinsicKind: { + SkASSERT(arguments.size() == 4); + this->write("textureGrad"); + nameWritten = true; + break; + } + case k_sampleLod_IntrinsicKind: { + SkASSERT(arguments.size() == 3); + this->write("textureLod"); + nameWritten = true; + break; + } + case k_transpose_IntrinsicKind: + if (this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) { + SkASSERT(arguments.size() == 1); + this->writeTransposeHack(*arguments[0]); + return; + } + break; + default: + break; + } + + if (!nameWritten) { + this->writeIdentifier(function.mangledName()); + } + this->write("("); + auto separator = SkSL::String::Separator(); + for (const auto& arg : arguments) { + this->write(separator()); + this->writeExpression(*arg, Precedence::kSequence); + } + if (fProgram.fConfig->fSettings.fSharpenTextures && isTextureFunctionWithBias) { + this->write(String::printf(", %g", kSharpenTexturesBias)); + } + this->write(closingParen); +} + +void GLSLCodeGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, + Precedence parentPrecedence) { + if (c.type().columns() == 4 && c.type().rows() == 2) { + // Due to a longstanding bug in glslang and Mesa, several GPU drivers generate diagonal 4x2 + // matrices incorrectly. (skia:12003, https://github.com/KhronosGroup/glslang/pull/2646) + // We can work around this issue by multiplying a scalar by the identity matrix. + // In practice, this doesn't come up naturally in real code and we don't know every affected + // driver, so we just apply this workaround everywhere. + this->write("("); + this->writeType(c.type()); + this->write("(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0) * "); + this->writeExpression(*c.argument(), Precedence::kMultiplicative); + this->write(")"); + return; + } + this->writeAnyConstructor(c, parentPrecedence); +} + +void GLSLCodeGenerator::writeConstructorCompound(const ConstructorCompound& c, + Precedence parentPrecedence) { + // If this is a 2x2 matrix constructor containing a single argument... + if (c.type().isMatrix() && c.arguments().size() == 1) { + // ... and that argument is a vec4... + const Expression& expr = *c.arguments().front(); + if (expr.type().isVector() && expr.type().columns() == 4) { + // ... let's rewrite the cast to dodge issues on very old GPUs. (skia:13559) + if (Analysis::IsTrivialExpression(expr)) { + this->writeType(c.type()); + this->write("("); + this->writeExpression(expr, Precedence::kPostfix); + this->write(".xy, "); + this->writeExpression(expr, Precedence::kPostfix); + this->write(".zw)"); + } else { + std::string tempVec = "_tempVec" + std::to_string(fVarCount++); + this->fFunctionHeader += std::string(" ") + this->getTypePrecision(expr.type()) + + this->getTypeName(expr.type()) + " " + tempVec + ";\n"; + this->write("(("); + this->write(tempVec); + this->write(" = "); + this->writeExpression(expr, Precedence::kAssignment); + this->write("), "); + this->writeType(c.type()); + this->write("("); + this->write(tempVec); + this->write(".xy, "); + this->write(tempVec); + this->write(".zw))"); + } + return; + } + } + this->writeAnyConstructor(c, parentPrecedence); +} + +void GLSLCodeGenerator::writeCastConstructor(const AnyConstructor& c, Precedence parentPrecedence) { + const auto arguments = c.argumentSpan(); + SkASSERT(arguments.size() == 1); + + const Expression& argument = *arguments.front(); + if ((this->getTypeName(c.type()) == this->getTypeName(argument.type()) || + (argument.type().matches(*fContext.fTypes.fFloatLiteral)))) { + // In cases like half(float), they're different types as far as SkSL is concerned but + // the same type as far as GLSL is concerned. We avoid a redundant float(float) by just + // writing out the inner expression here. + this->writeExpression(argument, parentPrecedence); + return; + } + + // This cast should be emitted as-is. + return this->writeAnyConstructor(c, parentPrecedence); +} + +void GLSLCodeGenerator::writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence) { + this->writeType(c.type()); + this->write("("); + auto separator = SkSL::String::Separator(); + for (const auto& arg : c.argumentSpan()) { + this->write(separator()); + this->writeExpression(*arg, Precedence::kSequence); + } + this->write(")"); +} + +void GLSLCodeGenerator::writeFragCoord() { + if (!this->caps().fCanUseFragCoord) { + if (!fSetupFragCoordWorkaround) { + const char* precision = this->usesPrecisionModifiers() ? "highp " : ""; + fFunctionHeader += precision; + fFunctionHeader += " float sk_FragCoord_InvW = 1. / sk_FragCoord_Workaround.w;\n"; + fFunctionHeader += precision; + fFunctionHeader += " vec4 sk_FragCoord_Resolved = " + "vec4(sk_FragCoord_Workaround.xyz * sk_FragCoord_InvW, sk_FragCoord_InvW);\n"; + // Ensure that we get exact .5 values for x and y. + fFunctionHeader += " sk_FragCoord_Resolved.xy = floor(sk_FragCoord_Resolved.xy) + " + "vec2(.5);\n"; + fSetupFragCoordWorkaround = true; + } + this->writeIdentifier("sk_FragCoord_Resolved"); + return; + } + + if (!fSetupFragPosition) { + fFunctionHeader += this->usesPrecisionModifiers() ? "highp " : ""; + fFunctionHeader += " vec4 sk_FragCoord = vec4(" + "gl_FragCoord.x, "; + if (fProgram.fConfig->fSettings.fForceNoRTFlip) { + fFunctionHeader += "gl_FragCoord.y, "; + } else { + fFunctionHeader += SKSL_RTFLIP_NAME ".x + " SKSL_RTFLIP_NAME ".y * gl_FragCoord.y, "; + } + fFunctionHeader += + "gl_FragCoord.z, " + "gl_FragCoord.w);\n"; + fSetupFragPosition = true; + } + this->writeIdentifier("sk_FragCoord"); +} + +void GLSLCodeGenerator::writeVariableReference(const VariableReference& ref) { + switch (ref.variable()->modifiers().fLayout.fBuiltin) { + case SK_FRAGCOLOR_BUILTIN: + if (this->caps().mustDeclareFragmentShaderOutput()) { + this->writeIdentifier("sk_FragColor"); + } else { + this->writeIdentifier("gl_FragColor"); + } + break; + case SK_SECONDARYFRAGCOLOR_BUILTIN: + this->writeIdentifier("gl_SecondaryFragColorEXT"); + break; + case SK_FRAGCOORD_BUILTIN: + this->writeFragCoord(); + break; + case SK_CLOCKWISE_BUILTIN: + if (!fSetupClockwise) { + fFunctionHeader += " bool sk_Clockwise = gl_FrontFacing;\n"; + if (!fProgram.fConfig->fSettings.fForceNoRTFlip) { + fFunctionHeader += " if (" SKSL_RTFLIP_NAME ".y < 0.0) {\n" + " sk_Clockwise = !sk_Clockwise;\n" + " }\n"; + } + fSetupClockwise = true; + } + this->writeIdentifier("sk_Clockwise"); + break; + case SK_VERTEXID_BUILTIN: + this->writeIdentifier("gl_VertexID"); + break; + case SK_INSTANCEID_BUILTIN: + this->writeIdentifier("gl_InstanceID"); + break; + case SK_LASTFRAGCOLOR_BUILTIN: + if (this->caps().fFBFetchSupport) { + this->write(this->caps().fFBFetchColorName); + } else { + fContext.fErrors->error(ref.fPosition, + "sk_LastFragColor requires framebuffer fetch support"); + } + break; + default: + this->writeIdentifier(ref.variable()->mangledName()); + break; + } +} + +void GLSLCodeGenerator::writeIndexExpression(const IndexExpression& expr) { + this->writeExpression(*expr.base(), Precedence::kPostfix); + this->write("["); + this->writeExpression(*expr.index(), Precedence::kTopLevel); + this->write("]"); +} + +bool is_sk_position(const FieldAccess& f) { + return f.base()->type().fields()[f.fieldIndex()].fModifiers.fLayout.fBuiltin == + SK_POSITION_BUILTIN; +} + +void GLSLCodeGenerator::writeFieldAccess(const FieldAccess& f) { + if (f.ownerKind() == FieldAccess::OwnerKind::kDefault) { + this->writeExpression(*f.base(), Precedence::kPostfix); + this->write("."); + } + const Type& baseType = f.base()->type(); + int builtin = baseType.fields()[f.fieldIndex()].fModifiers.fLayout.fBuiltin; + if (builtin == SK_POSITION_BUILTIN) { + this->writeIdentifier("gl_Position"); + } else if (builtin == SK_POINTSIZE_BUILTIN) { + this->writeIdentifier("gl_PointSize"); + } else { + this->writeIdentifier(baseType.fields()[f.fieldIndex()].fName); + } +} + +void GLSLCodeGenerator::writeSwizzle(const Swizzle& swizzle) { + this->writeExpression(*swizzle.base(), Precedence::kPostfix); + this->write("."); + for (int c : swizzle.components()) { + SkASSERT(c >= 0 && c <= 3); + this->write(&("x\0y\0z\0w\0"[c * 2])); + } +} + +void GLSLCodeGenerator::writeMatrixComparisonWorkaround(const BinaryExpression& b) { + const Expression& left = *b.left(); + const Expression& right = *b.right(); + Operator op = b.getOperator(); + + SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ); + SkASSERT(left.type().isMatrix()); + SkASSERT(right.type().isMatrix()); + + std::string tempMatrix1 = "_tempMatrix" + std::to_string(fVarCount++); + std::string tempMatrix2 = "_tempMatrix" + std::to_string(fVarCount++); + + this->fFunctionHeader += std::string(" ") + this->getTypePrecision(left.type()) + + this->getTypeName(left.type()) + " " + tempMatrix1 + ";\n " + + this->getTypePrecision(right.type()) + + this->getTypeName(right.type()) + " " + tempMatrix2 + ";\n"; + this->write("((" + tempMatrix1 + " = "); + this->writeExpression(left, Precedence::kAssignment); + this->write("), (" + tempMatrix2 + " = "); + this->writeExpression(right, Precedence::kAssignment); + this->write("), (" + tempMatrix1); + this->write(op.operatorName()); + this->write(tempMatrix2 + "))"); +} + +void GLSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + const Expression& left = *b.left(); + const Expression& right = *b.right(); + Operator op = b.getOperator(); + if (this->caps().fUnfoldShortCircuitAsTernary && + (op.kind() == Operator::Kind::LOGICALAND || op.kind() == Operator::Kind::LOGICALOR)) { + this->writeShortCircuitWorkaroundExpression(b, parentPrecedence); + return; + } + + if (this->caps().fRewriteMatrixComparisons && + left.type().isMatrix() && right.type().isMatrix() && + (op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ)) { + this->writeMatrixComparisonWorkaround(b); + return; + } + + Precedence precedence = op.getBinaryPrecedence(); + if (precedence >= parentPrecedence) { + this->write("("); + } + bool positionWorkaround = ProgramConfig::IsVertex(fProgram.fConfig->fKind) && + op.isAssignment() && + left.is<FieldAccess>() && + is_sk_position(left.as<FieldAccess>()) && + !Analysis::ContainsRTAdjust(right) && + !this->caps().fCanUseFragCoord; + if (positionWorkaround) { + this->write("sk_FragCoord_Workaround = ("); + } + this->writeExpression(left, precedence); + this->write(op.operatorName()); + this->writeExpression(right, precedence); + if (positionWorkaround) { + this->write(")"); + } + if (precedence >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writeShortCircuitWorkaroundExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + if (Precedence::kTernary >= parentPrecedence) { + this->write("("); + } + + // Transform: + // a && b => a ? b : false + // a || b => a ? true : b + this->writeExpression(*b.left(), Precedence::kTernary); + this->write(" ? "); + if (b.getOperator().kind() == Operator::Kind::LOGICALAND) { + this->writeExpression(*b.right(), Precedence::kTernary); + } else { + Literal boolTrue(Position(), /*value=*/1, fContext.fTypes.fBool.get()); + this->writeLiteral(boolTrue); + } + this->write(" : "); + if (b.getOperator().kind() == Operator::Kind::LOGICALAND) { + Literal boolFalse(Position(), /*value=*/0, fContext.fTypes.fBool.get()); + this->writeLiteral(boolFalse); + } else { + this->writeExpression(*b.right(), Precedence::kTernary); + } + if (Precedence::kTernary >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writeTernaryExpression(const TernaryExpression& t, + Precedence parentPrecedence) { + if (Precedence::kTernary >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*t.test(), Precedence::kTernary); + this->write(" ? "); + this->writeExpression(*t.ifTrue(), Precedence::kTernary); + this->write(" : "); + this->writeExpression(*t.ifFalse(), Precedence::kTernary); + if (Precedence::kTernary >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writePrefixExpression(const PrefixExpression& p, + Precedence parentPrecedence) { + if (Precedence::kPrefix >= parentPrecedence) { + this->write("("); + } + this->write(p.getOperator().tightOperatorName()); + this->writeExpression(*p.operand(), Precedence::kPrefix); + if (Precedence::kPrefix >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writePostfixExpression(const PostfixExpression& p, + Precedence parentPrecedence) { + if (Precedence::kPostfix >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*p.operand(), Precedence::kPostfix); + this->write(p.getOperator().tightOperatorName()); + if (Precedence::kPostfix >= parentPrecedence) { + this->write(")"); + } +} + +void GLSLCodeGenerator::writeLiteral(const Literal& l) { + const Type& type = l.type(); + if (type.isInteger()) { + if (type.matches(*fContext.fTypes.fUInt)) { + this->write(std::to_string(l.intValue() & 0xffffffff) + "u"); + } else if (type.matches(*fContext.fTypes.fUShort)) { + this->write(std::to_string(l.intValue() & 0xffff) + "u"); + } else { + this->write(std::to_string(l.intValue())); + } + return; + } + this->write(l.description(OperatorPrecedence::kTopLevel)); +} + +void GLSLCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) { + this->writeTypePrecision(f.returnType()); + this->writeType(f.returnType()); + this->write(" "); + this->writeIdentifier(f.mangledName()); + this->write("("); + auto separator = SkSL::String::Separator(); + for (size_t index = 0; index < f.parameters().size(); ++index) { + const Variable* param = f.parameters()[index]; + + // This is a workaround for our test files. They use the runtime effect signature, so main + // takes a coords parameter. The IR generator tags those with a builtin ID (sk_FragCoord), + // and we omit them from the declaration here, so the function is valid GLSL. + if (f.isMain() && param->modifiers().fLayout.fBuiltin != -1) { + continue; + } + this->write(separator()); + Modifiers modifiers = param->modifiers(); + if (this->caps().fRemoveConstFromFunctionParameters) { + modifiers.fFlags &= ~Modifiers::kConst_Flag; + } + this->writeModifiers(modifiers, false); + std::vector<int> sizes; + const Type* type = ¶m->type(); + if (type->isArray()) { + sizes.push_back(type->columns()); + type = &type->componentType(); + } + this->writeTypePrecision(*type); + this->writeType(*type); + this->write(" "); + if (!param->name().empty()) { + this->writeIdentifier(param->mangledName()); + } else { + // By the spec, GLSL does not require function parameters to be named (see + // `single_declaration` in the Shading Language Grammar), but some older versions of + // GLSL report "formal parameter lacks a name" if a parameter is not named. + this->write("_skAnonymousParam"); + this->write(std::to_string(index)); + } + for (int s : sizes) { + this->write("[" + std::to_string(s) + "]"); + } + } + this->write(")"); +} + +void GLSLCodeGenerator::writeFunction(const FunctionDefinition& f) { + fSetupFragPosition = false; + fSetupFragCoordWorkaround = false; + + this->writeFunctionDeclaration(f.declaration()); + this->writeLine(" {"); + fIndentation++; + + fFunctionHeader.clear(); + OutputStream* oldOut = fOut; + StringStream buffer; + fOut = &buffer; + for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) { + if (!stmt->isEmpty()) { + this->writeStatement(*stmt); + this->finishLine(); + } + } + + fIndentation--; + this->writeLine("}"); + + fOut = oldOut; + this->write(fFunctionHeader); + this->write(buffer.str()); +} + +void GLSLCodeGenerator::writeFunctionPrototype(const FunctionPrototype& f) { + this->writeFunctionDeclaration(f.declaration()); + this->writeLine(";"); +} + +void GLSLCodeGenerator::writeModifiers(const Modifiers& modifiers, + bool globalContext) { + std::string layout = modifiers.fLayout.description(); + if (layout.size()) { + this->write(layout + " "); + } + + // For GLSL 4.1 and below, qualifier-order matters! These are written out in Modifier-bit order. + if (modifiers.fFlags & Modifiers::kFlat_Flag) { + this->write("flat "); + } + if (modifiers.fFlags & Modifiers::kNoPerspective_Flag) { + this->write("noperspective "); + } + + if (modifiers.fFlags & Modifiers::kConst_Flag) { + this->write("const "); + } + if (modifiers.fFlags & Modifiers::kUniform_Flag) { + this->write("uniform "); + } + if ((modifiers.fFlags & Modifiers::kIn_Flag) && + (modifiers.fFlags & Modifiers::kOut_Flag)) { + this->write("inout "); + } else if (modifiers.fFlags & Modifiers::kIn_Flag) { + if (globalContext && this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) { + this->write(ProgramConfig::IsVertex(fProgram.fConfig->fKind) ? "attribute " + : "varying "); + } else { + this->write("in "); + } + } else if (modifiers.fFlags & Modifiers::kOut_Flag) { + if (globalContext && + this->caps().fGLSLGeneration < SkSL::GLSLGeneration::k130) { + this->write("varying "); + } else { + this->write("out "); + } + } + + if (modifiers.fFlags & Modifiers::kReadOnly_Flag) { + this->write("readonly "); + } + if (modifiers.fFlags & Modifiers::kWriteOnly_Flag) { + this->write("writeonly "); + } + if (modifiers.fFlags & Modifiers::kBuffer_Flag) { + this->write("buffer "); + } +} + +void GLSLCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf) { + if (intf.typeName() == "sk_PerVertex") { + return; + } + const Type* structType = &intf.var()->type().componentType(); + this->writeModifiers(intf.var()->modifiers(), true); + this->writeType(*structType); + this->writeLine(" {"); + fIndentation++; + for (const auto& f : structType->fields()) { + this->writeModifiers(f.fModifiers, false); + this->writeTypePrecision(*f.fType); + this->writeType(*f.fType); + this->write(" "); + this->writeIdentifier(f.fName); + this->writeLine(";"); + } + fIndentation--; + this->write("}"); + if (intf.instanceName().size()) { + this->write(" "); + this->writeIdentifier(intf.instanceName()); + if (intf.arraySize() > 0) { + this->write("["); + this->write(std::to_string(intf.arraySize())); + this->write("]"); + } + } + this->writeLine(";"); +} + +void GLSLCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) { + this->writeExpression(value, Precedence::kTopLevel); +} + +const char* GLSLCodeGenerator::getTypePrecision(const Type& type) { + if (this->usesPrecisionModifiers()) { + switch (type.typeKind()) { + case Type::TypeKind::kScalar: + if (type.matches(*fContext.fTypes.fShort) || + type.matches(*fContext.fTypes.fUShort)) { + if (fProgram.fConfig->fSettings.fForceHighPrecision || + this->caps().fIncompleteShortIntPrecision) { + return "highp "; + } + return "mediump "; + } + if (type.matches(*fContext.fTypes.fHalf)) { + return fProgram.fConfig->fSettings.fForceHighPrecision ? "highp " : "mediump "; + } + if (type.matches(*fContext.fTypes.fFloat) || type.matches(*fContext.fTypes.fInt) || + type.matches(*fContext.fTypes.fUInt)) { + return "highp "; + } + return ""; + case Type::TypeKind::kVector: // fall through + case Type::TypeKind::kMatrix: + case Type::TypeKind::kArray: + return this->getTypePrecision(type.componentType()); + default: + break; + } + } + return ""; +} + +void GLSLCodeGenerator::writeTypePrecision(const Type& type) { + this->write(this->getTypePrecision(type)); +} + +void GLSLCodeGenerator::writeVarDeclaration(const VarDeclaration& var, bool global) { + this->writeModifiers(var.var()->modifiers(), global); + this->writeTypePrecision(var.baseType()); + this->writeType(var.baseType()); + this->write(" "); + this->writeIdentifier(var.var()->mangledName()); + if (var.arraySize() > 0) { + this->write("["); + this->write(std::to_string(var.arraySize())); + this->write("]"); + } + if (var.value()) { + this->write(" = "); + this->writeVarInitializer(*var.var(), *var.value()); + } + if (!fFoundExternalSamplerDecl && + var.var()->type().matches(*fContext.fTypes.fSamplerExternalOES)) { + if (this->caps().externalTextureExtensionString()) { + this->writeExtension(this->caps().externalTextureExtensionString()); + } + if (this->caps().secondExternalTextureExtensionString()) { + this->writeExtension(this->caps().secondExternalTextureExtensionString()); + } + fFoundExternalSamplerDecl = true; + } + if (!fFoundRectSamplerDecl && var.var()->type().matches(*fContext.fTypes.fSampler2DRect)) { + fFoundRectSamplerDecl = true; + } + this->write(";"); +} + +void GLSLCodeGenerator::writeStatement(const Statement& s) { + switch (s.kind()) { + case Statement::Kind::kBlock: + this->writeBlock(s.as<Block>()); + break; + case Statement::Kind::kExpression: + this->writeExpressionStatement(s.as<ExpressionStatement>()); + break; + case Statement::Kind::kReturn: + this->writeReturnStatement(s.as<ReturnStatement>()); + break; + case Statement::Kind::kVarDeclaration: + this->writeVarDeclaration(s.as<VarDeclaration>(), false); + break; + case Statement::Kind::kIf: + this->writeIfStatement(s.as<IfStatement>()); + break; + case Statement::Kind::kFor: + this->writeForStatement(s.as<ForStatement>()); + break; + case Statement::Kind::kDo: + this->writeDoStatement(s.as<DoStatement>()); + break; + case Statement::Kind::kSwitch: + this->writeSwitchStatement(s.as<SwitchStatement>()); + break; + case Statement::Kind::kBreak: + this->write("break;"); + break; + case Statement::Kind::kContinue: + this->write("continue;"); + break; + case Statement::Kind::kDiscard: + this->write("discard;"); + break; + case Statement::Kind::kNop: + this->write(";"); + break; + default: + SkDEBUGFAILF("unsupported statement: %s", s.description().c_str()); + break; + } +} + +void GLSLCodeGenerator::writeBlock(const Block& b) { + // Write scope markers if this block is a scope, or if the block is empty (since we need to emit + // something here to make the code valid). + bool isScope = b.isScope() || b.isEmpty(); + if (isScope) { + this->writeLine("{"); + fIndentation++; + } + for (const std::unique_ptr<Statement>& stmt : b.children()) { + if (!stmt->isEmpty()) { + this->writeStatement(*stmt); + this->finishLine(); + } + } + if (isScope) { + fIndentation--; + this->write("}"); + } +} + +void GLSLCodeGenerator::writeIfStatement(const IfStatement& stmt) { + this->write("if ("); + this->writeExpression(*stmt.test(), Precedence::kTopLevel); + this->write(") "); + this->writeStatement(*stmt.ifTrue()); + if (stmt.ifFalse()) { + this->write(" else "); + this->writeStatement(*stmt.ifFalse()); + } +} + +void GLSLCodeGenerator::writeForStatement(const ForStatement& f) { + // Emit loops of the form 'for(;test;)' as 'while(test)', which is probably how they started + if (!f.initializer() && f.test() && !f.next()) { + this->write("while ("); + this->writeExpression(*f.test(), Precedence::kTopLevel); + this->write(") "); + this->writeStatement(*f.statement()); + return; + } + + this->write("for ("); + if (f.initializer() && !f.initializer()->isEmpty()) { + this->writeStatement(*f.initializer()); + } else { + this->write("; "); + } + if (f.test()) { + if (this->caps().fAddAndTrueToLoopCondition) { + std::unique_ptr<Expression> and_true(new BinaryExpression( + Position(), f.test()->clone(), Operator::Kind::LOGICALAND, + Literal::MakeBool(fContext, Position(), /*value=*/true), + fContext.fTypes.fBool.get())); + this->writeExpression(*and_true, Precedence::kTopLevel); + } else { + this->writeExpression(*f.test(), Precedence::kTopLevel); + } + } + this->write("; "); + if (f.next()) { + this->writeExpression(*f.next(), Precedence::kTopLevel); + } + this->write(") "); + this->writeStatement(*f.statement()); +} + +void GLSLCodeGenerator::writeDoStatement(const DoStatement& d) { + if (!this->caps().fRewriteDoWhileLoops) { + this->write("do "); + this->writeStatement(*d.statement()); + this->write(" while ("); + this->writeExpression(*d.test(), Precedence::kTopLevel); + this->write(");"); + return; + } + + // Otherwise, do the do while loop workaround, to rewrite loops of the form: + // do { + // CODE; + // } while (CONDITION) + // + // to loops of the form + // bool temp = false; + // while (true) { + // if (temp) { + // if (!CONDITION) { + // break; + // } + // } + // temp = true; + // CODE; + // } + std::string tmpVar = "_tmpLoopSeenOnce" + std::to_string(fVarCount++); + this->write("bool "); + this->write(tmpVar); + this->writeLine(" = false;"); + this->writeLine("while (true) {"); + fIndentation++; + this->write("if ("); + this->write(tmpVar); + this->writeLine(") {"); + fIndentation++; + this->write("if (!"); + this->writeExpression(*d.test(), Precedence::kPrefix); + this->writeLine(") {"); + fIndentation++; + this->writeLine("break;"); + fIndentation--; + this->writeLine("}"); + fIndentation--; + this->writeLine("}"); + this->write(tmpVar); + this->writeLine(" = true;"); + this->writeStatement(*d.statement()); + this->finishLine(); + fIndentation--; + this->write("}"); +} + +void GLSLCodeGenerator::writeExpressionStatement(const ExpressionStatement& s) { + if (fProgram.fConfig->fSettings.fOptimize && !Analysis::HasSideEffects(*s.expression())) { + // Don't emit dead expressions. + return; + } + this->writeExpression(*s.expression(), Precedence::kTopLevel); + this->write(";"); +} + +void GLSLCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { + if (this->caps().fRewriteSwitchStatements) { + std::string fallthroughVar = "_tmpSwitchFallthrough" + std::to_string(fVarCount++); + std::string valueVar = "_tmpSwitchValue" + std::to_string(fVarCount++); + std::string loopVar = "_tmpSwitchLoop" + std::to_string(fVarCount++); + this->write("int "); + this->write(valueVar); + this->write(" = "); + this->writeExpression(*s.value(), Precedence::kAssignment); + this->write(", "); + this->write(fallthroughVar); + this->writeLine(" = 0;"); + this->write("for (int "); + this->write(loopVar); + this->write(" = 0; "); + this->write(loopVar); + this->write(" < 1; "); + this->write(loopVar); + this->writeLine("++) {"); + fIndentation++; + + bool firstCase = true; + for (const std::unique_ptr<Statement>& stmt : s.cases()) { + const SwitchCase& c = stmt->as<SwitchCase>(); + if (!c.isDefault()) { + this->write("if (("); + if (firstCase) { + firstCase = false; + } else { + this->write(fallthroughVar); + this->write(" > 0) || ("); + } + this->write(valueVar); + this->write(" == "); + this->write(std::to_string(c.value())); + this->writeLine(")) {"); + fIndentation++; + + // We write the entire case-block statement here, and then set `switchFallthrough` + // to 1. If the case-block had a break statement in it, we break out of the outer + // for-loop entirely, meaning the `switchFallthrough` assignment never occurs, nor + // does any code after it inside the switch. We've forbidden `continue` statements + // inside switch case-blocks entirely, so we don't need to consider their effect on + // control flow; see the Finalizer in FunctionDefinition::Convert. + this->writeStatement(*c.statement()); + this->finishLine(); + this->write(fallthroughVar); + this->write(" = 1;"); + this->writeLine(); + + fIndentation--; + this->writeLine("}"); + } else { + // This is the default case. Since it's always last, we can just dump in the code. + this->writeStatement(*c.statement()); + this->finishLine(); + } + } + + fIndentation--; + this->writeLine("}"); + return; + } + + this->write("switch ("); + this->writeExpression(*s.value(), Precedence::kTopLevel); + this->writeLine(") {"); + fIndentation++; + // If a switch contains only a `default` case and nothing else, this confuses some drivers and + // can lead to a crash. Adding a real case before the default seems to work around the bug, + // and doesn't change the meaning of the switch. (skia:12465) + if (s.cases().size() == 1 && s.cases().front()->as<SwitchCase>().isDefault()) { + this->writeLine("case 0:"); + } + + // The GLSL spec insists that the last case in a switch statement must have an associated + // statement. In practice, the Apple GLSL compiler crashes if that statement is a no-op, such as + // a semicolon or an empty brace pair. (This is filed as FB11992149.) It also crashes if we put + // two `break` statements in a row. To work around this while honoring the rules of the + // standard, we inject an extra break if and only if the last switch-case block is empty. + bool foundEmptyCase = false; + + for (const std::unique_ptr<Statement>& stmt : s.cases()) { + const SwitchCase& c = stmt->as<SwitchCase>(); + if (c.isDefault()) { + this->writeLine("default:"); + } else { + this->write("case "); + this->write(std::to_string(c.value())); + this->writeLine(":"); + } + if (c.statement()->isEmpty()) { + foundEmptyCase = true; + } else { + foundEmptyCase = false; + fIndentation++; + this->writeStatement(*c.statement()); + this->finishLine(); + fIndentation--; + } + } + if (foundEmptyCase) { + fIndentation++; + this->writeLine("break;"); + fIndentation--; + } + fIndentation--; + this->finishLine(); + this->write("}"); +} + +void GLSLCodeGenerator::writeReturnStatement(const ReturnStatement& r) { + this->write("return"); + if (r.expression()) { + this->write(" "); + this->writeExpression(*r.expression(), Precedence::kTopLevel); + } + this->write(";"); +} + +void GLSLCodeGenerator::writeHeader() { + if (this->caps().fVersionDeclString) { + this->write(this->caps().fVersionDeclString); + this->finishLine(); + } +} + +void GLSLCodeGenerator::writeProgramElement(const ProgramElement& e) { + switch (e.kind()) { + case ProgramElement::Kind::kExtension: + this->writeExtension(e.as<Extension>().name()); + break; + case ProgramElement::Kind::kGlobalVar: { + const VarDeclaration& decl = e.as<GlobalVarDeclaration>().varDeclaration(); + int builtin = decl.var()->modifiers().fLayout.fBuiltin; + if (builtin == -1) { + // normal var + this->writeVarDeclaration(decl, true); + this->finishLine(); + } else if (builtin == SK_FRAGCOLOR_BUILTIN && + this->caps().mustDeclareFragmentShaderOutput()) { + if (fProgram.fConfig->fSettings.fFragColorIsInOut) { + this->write("inout "); + } else { + this->write("out "); + } + if (this->usesPrecisionModifiers()) { + this->write("mediump "); + } + this->writeLine("vec4 sk_FragColor;"); + } + break; + } + case ProgramElement::Kind::kInterfaceBlock: + this->writeInterfaceBlock(e.as<InterfaceBlock>()); + break; + case ProgramElement::Kind::kFunction: + this->writeFunction(e.as<FunctionDefinition>()); + break; + case ProgramElement::Kind::kFunctionPrototype: + this->writeFunctionPrototype(e.as<FunctionPrototype>()); + break; + case ProgramElement::Kind::kModifiers: { + const Modifiers& modifiers = e.as<ModifiersDeclaration>().modifiers(); + this->writeModifiers(modifiers, true); + this->writeLine(";"); + break; + } + case ProgramElement::Kind::kStructDefinition: + this->writeStructDefinition(e.as<StructDefinition>()); + break; + default: + SkDEBUGFAILF("unsupported program element %s\n", e.description().c_str()); + break; + } +} + +void GLSLCodeGenerator::writeInputVars() { + if (fProgram.fInputs.fUseFlipRTUniform) { + const char* precision = this->usesPrecisionModifiers() ? "highp " : ""; + fGlobals.writeText("uniform "); + fGlobals.writeText(precision); + fGlobals.writeText("vec2 " SKSL_RTFLIP_NAME ";\n"); + } +} + +bool GLSLCodeGenerator::generateCode() { + this->writeHeader(); + OutputStream* rawOut = fOut; + StringStream body; + fOut = &body; + // Write all the program elements except for functions. + for (const ProgramElement* e : fProgram.elements()) { + if (!e->is<FunctionDefinition>()) { + this->writeProgramElement(*e); + } + } + // Emit prototypes for every built-in function; these aren't always added in perfect order. + for (const ProgramElement* e : fProgram.fSharedElements) { + if (e->is<FunctionDefinition>()) { + this->writeFunctionDeclaration(e->as<FunctionDefinition>().declaration()); + this->writeLine(";"); + } + } + // Write the functions last. + // Why don't we write things in their original order? Because the Inliner likes to move function + // bodies around. After inlining, code can inadvertently move upwards, above ProgramElements + // that the code relies on. + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<FunctionDefinition>()) { + this->writeProgramElement(*e); + } + } + fOut = rawOut; + + write_stringstream(fExtensions, *rawOut); + this->writeInputVars(); + write_stringstream(fGlobals, *rawOut); + + if (!this->caps().fCanUseFragCoord) { + Layout layout; + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + Modifiers modifiers(layout, Modifiers::kOut_Flag); + this->writeModifiers(modifiers, true); + if (this->usesPrecisionModifiers()) { + this->write("highp "); + } + this->write("vec4 sk_FragCoord_Workaround;\n"); + } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + Modifiers modifiers(layout, Modifiers::kIn_Flag); + this->writeModifiers(modifiers, true); + if (this->usesPrecisionModifiers()) { + this->write("highp "); + } + this->write("vec4 sk_FragCoord_Workaround;\n"); + } + } + + if (this->usesPrecisionModifiers()) { + const char* precision = + fProgram.fConfig->fSettings.fForceHighPrecision ? "highp" : "mediump"; + this->write(String::printf("precision %s float;\n", precision)); + this->write(String::printf("precision %s sampler2D;\n", precision)); + if (fFoundExternalSamplerDecl && !this->caps().fNoDefaultPrecisionForExternalSamplers) { + this->write(String::printf("precision %s samplerExternalOES;\n", precision)); + } + if (fFoundRectSamplerDecl) { + this->write(String::printf("precision %s sampler2DRect;\n", precision)); + } + } + write_stringstream(fExtraFunctions, *rawOut); + write_stringstream(body, *rawOut); + return fContext.fErrors->errorCount() == 0; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.h new file mode 100644 index 0000000000..e6672ab45f --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLGLSLCodeGenerator.h @@ -0,0 +1,210 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_GLSLCODEGENERATOR +#define SKSL_GLSLCODEGENERATOR + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/codegen/SkSLCodeGenerator.h" + +#include <cstdint> +#include <string> +#include <string_view> + +namespace SkSL { + +class AnyConstructor; +class BinaryExpression; +class Block; +class ConstructorCompound; +class ConstructorDiagonalMatrix; +class DoStatement; +class Expression; +class ExpressionStatement; +class FieldAccess; +class ForStatement; +class FunctionCall; +class FunctionDeclaration; +class FunctionDefinition; +class FunctionPrototype; +class IfStatement; +class IndexExpression; +class InterfaceBlock; +class Literal; +class OutputStream; +class PostfixExpression; +class PrefixExpression; +class ProgramElement; +class ReturnStatement; +class Statement; +class StructDefinition; +class SwitchStatement; +class Swizzle; +class TernaryExpression; +class Type; +class VarDeclaration; +class Variable; +class VariableReference; +enum class OperatorPrecedence : uint8_t; +struct Layout; +struct Modifiers; +struct Program; +struct ShaderCaps; + +/** + * Converts a Program into GLSL code. + */ +class GLSLCodeGenerator : public CodeGenerator { +public: + GLSLCodeGenerator(const Context* context, const Program* program, OutputStream* out) + : INHERITED(context, program, out) {} + + bool generateCode() override; + +protected: + using Precedence = OperatorPrecedence; + + void write(std::string_view s); + + void writeLine(std::string_view s = std::string_view()); + + void finishLine(); + + virtual void writeHeader(); + + bool usesPrecisionModifiers() const; + + void writeIdentifier(std::string_view identifier); + + virtual std::string getTypeName(const Type& type); + + void writeStructDefinition(const StructDefinition& s); + + void writeType(const Type& type); + + void writeExtension(std::string_view name, bool require = true); + + void writeInterfaceBlock(const InterfaceBlock& intf); + + void writeFunctionDeclaration(const FunctionDeclaration& f); + + void writeFunctionPrototype(const FunctionPrototype& f); + + virtual void writeFunction(const FunctionDefinition& f); + + void writeLayout(const Layout& layout); + + void writeModifiers(const Modifiers& modifiers, bool globalContext); + + virtual void writeInputVars(); + + virtual void writeVarInitializer(const Variable& var, const Expression& value); + + const char* getTypePrecision(const Type& type); + + void writeTypePrecision(const Type& type); + + void writeVarDeclaration(const VarDeclaration& var, bool global); + + void writeFragCoord(); + + virtual void writeVariableReference(const VariableReference& ref); + + void writeExpression(const Expression& expr, Precedence parentPrecedence); + + void writeIntrinsicCall(const FunctionCall& c); + + void writeMinAbsHack(Expression& absExpr, Expression& otherExpr); + + void writeDeterminantHack(const Expression& mat); + + void writeInverseHack(const Expression& mat); + + void writeTransposeHack(const Expression& mat); + + void writeInverseSqrtHack(const Expression& x); + + void writeMatrixComparisonWorkaround(const BinaryExpression& x); + + virtual void writeFunctionCall(const FunctionCall& c); + + void writeConstructorCompound(const ConstructorCompound& c, Precedence parentPrecedence); + + void writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, + Precedence parentPrecedence); + + virtual void writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence); + + virtual void writeCastConstructor(const AnyConstructor& c, Precedence parentPrecedence); + + virtual void writeFieldAccess(const FieldAccess& f); + + virtual void writeSwizzle(const Swizzle& swizzle); + + virtual void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence); + + void writeShortCircuitWorkaroundExpression(const BinaryExpression& b, + Precedence parentPrecedence); + + virtual void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence); + + virtual void writeIndexExpression(const IndexExpression& expr); + + void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence); + + void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence); + + virtual void writeLiteral(const Literal& l); + + void writeStatement(const Statement& s); + + void writeBlock(const Block& b); + + virtual void writeIfStatement(const IfStatement& stmt); + + void writeForStatement(const ForStatement& f); + + void writeDoStatement(const DoStatement& d); + + void writeExpressionStatement(const ExpressionStatement& s); + + virtual void writeSwitchStatement(const SwitchStatement& s); + + virtual void writeReturnStatement(const ReturnStatement& r); + + virtual void writeProgramElement(const ProgramElement& e); + + const ShaderCaps& caps() const { return *fContext.fCaps; } + + StringStream fExtensions; + StringStream fGlobals; + StringStream fExtraFunctions; + std::string fFunctionHeader; + int fVarCount = 0; + int fIndentation = 0; + bool fAtLineStart = false; + // true if we have run into usages of dFdx / dFdy + bool fFoundDerivatives = false; + bool fFoundExternalSamplerDecl = false; + bool fFoundRectSamplerDecl = false; + bool fSetupClockwise = false; + bool fSetupFragPosition = false; + bool fSetupFragCoordWorkaround = false; + + // Workaround/polyfill flags + bool fWrittenAbsEmulation = false; + bool fWrittenDeterminant2 = false, fWrittenDeterminant3 = false, fWrittenDeterminant4 = false; + bool fWrittenInverse2 = false, fWrittenInverse3 = false, fWrittenInverse4 = false; + bool fWrittenTranspose[3][3] = {}; + + using INHERITED = CodeGenerator; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.cpp new file mode 100644 index 0000000000..d173fae687 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.cpp @@ -0,0 +1,3226 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/codegen/SkSLMetalCodeGenerator.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTo.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkScopeExit.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLMemoryLayout.h" +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorArrayCast.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLFunctionPrototype.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLModifiersDeclaration.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLStructDefinition.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/spirv.h" + +#include <algorithm> +#include <cstddef> +#include <functional> +#include <limits> +#include <memory> + +namespace SkSL { + +static const char* operator_name(Operator op) { + switch (op.kind()) { + case Operator::Kind::LOGICALXOR: return " != "; + default: return op.operatorName(); + } +} + +class MetalCodeGenerator::GlobalStructVisitor { +public: + virtual ~GlobalStructVisitor() = default; + virtual void visitInterfaceBlock(const InterfaceBlock& block, std::string_view blockName) {} + virtual void visitTexture(const Type& type, const Modifiers& modifiers, + std::string_view name) {} + virtual void visitSampler(const Type& type, std::string_view name) {} + virtual void visitConstantVariable(const VarDeclaration& decl) {} + virtual void visitNonconstantVariable(const Variable& var, const Expression* value) {} +}; + +class MetalCodeGenerator::ThreadgroupStructVisitor { +public: + virtual ~ThreadgroupStructVisitor() = default; + virtual void visitNonconstantVariable(const Variable& var) = 0; +}; + +void MetalCodeGenerator::write(std::string_view s) { + if (s.empty()) { + return; + } + if (fAtLineStart) { + for (int i = 0; i < fIndentation; i++) { + fOut->writeText(" "); + } + } + fOut->writeText(std::string(s).c_str()); + fAtLineStart = false; +} + +void MetalCodeGenerator::writeLine(std::string_view s) { + this->write(s); + fOut->writeText(fLineEnding); + fAtLineStart = true; +} + +void MetalCodeGenerator::finishLine() { + if (!fAtLineStart) { + this->writeLine(); + } +} + +void MetalCodeGenerator::writeExtension(const Extension& ext) { + this->writeLine("#extension " + std::string(ext.name()) + " : enable"); +} + +std::string MetalCodeGenerator::typeName(const Type& type) { + // we need to know the modifiers for textures + switch (type.typeKind()) { + case Type::TypeKind::kArray: + SkASSERT(!type.isUnsizedArray()); + SkASSERTF(type.columns() > 0, "invalid array size: %s", type.description().c_str()); + return String::printf("array<%s, %d>", + this->typeName(type.componentType()).c_str(), type.columns()); + + case Type::TypeKind::kVector: + return this->typeName(type.componentType()) + std::to_string(type.columns()); + + case Type::TypeKind::kMatrix: + return this->typeName(type.componentType()) + std::to_string(type.columns()) + "x" + + std::to_string(type.rows()); + + case Type::TypeKind::kSampler: + if (type.dimensions() != SpvDim2D) { + fContext.fErrors->error(Position(), "Unsupported texture dimensions"); + } + return "sampler2D"; + + case Type::TypeKind::kTexture: + switch (type.textureAccess()) { + case Type::TextureAccess::kSample: return "texture2d<half>"; + case Type::TextureAccess::kRead: return "texture2d<half, access::read>"; + case Type::TextureAccess::kWrite: return "texture2d<half, access::write>"; + case Type::TextureAccess::kReadWrite: return "texture2d<half, access::read_write>"; + default: break; + } + SkUNREACHABLE; + case Type::TypeKind::kAtomic: + // SkSL currently only supports the atomicUint type. + SkASSERT(type.matches(*fContext.fTypes.fAtomicUInt)); + return "atomic_uint"; + default: + return std::string(type.name()); + } +} + +void MetalCodeGenerator::writeStructDefinition(const StructDefinition& s) { + const Type& type = s.type(); + this->writeLine("struct " + type.displayName() + " {"); + fIndentation++; + this->writeFields(type.fields(), type.fPosition); + fIndentation--; + this->writeLine("};"); +} + +void MetalCodeGenerator::writeType(const Type& type) { + this->write(this->typeName(type)); +} + +void MetalCodeGenerator::writeExpression(const Expression& expr, Precedence parentPrecedence) { + switch (expr.kind()) { + case Expression::Kind::kBinary: + this->writeBinaryExpression(expr.as<BinaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorStruct: + this->writeAnyConstructor(expr.asAnyConstructor(), "{", "}", parentPrecedence); + break; + case Expression::Kind::kConstructorArrayCast: + this->writeConstructorArrayCast(expr.as<ConstructorArrayCast>(), parentPrecedence); + break; + case Expression::Kind::kConstructorCompound: + this->writeConstructorCompound(expr.as<ConstructorCompound>(), parentPrecedence); + break; + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorSplat: + this->writeAnyConstructor(expr.asAnyConstructor(), "(", ")", parentPrecedence); + break; + case Expression::Kind::kConstructorMatrixResize: + this->writeConstructorMatrixResize(expr.as<ConstructorMatrixResize>(), + parentPrecedence); + break; + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorCompoundCast: + this->writeCastConstructor(expr.asAnyConstructor(), "(", ")", parentPrecedence); + break; + case Expression::Kind::kFieldAccess: + this->writeFieldAccess(expr.as<FieldAccess>()); + break; + case Expression::Kind::kLiteral: + this->writeLiteral(expr.as<Literal>()); + break; + case Expression::Kind::kFunctionCall: + this->writeFunctionCall(expr.as<FunctionCall>()); + break; + case Expression::Kind::kPrefix: + this->writePrefixExpression(expr.as<PrefixExpression>(), parentPrecedence); + break; + case Expression::Kind::kPostfix: + this->writePostfixExpression(expr.as<PostfixExpression>(), parentPrecedence); + break; + case Expression::Kind::kSetting: + this->writeExpression(*expr.as<Setting>().toLiteral(fContext), parentPrecedence); + break; + case Expression::Kind::kSwizzle: + this->writeSwizzle(expr.as<Swizzle>()); + break; + case Expression::Kind::kVariableReference: + this->writeVariableReference(expr.as<VariableReference>()); + break; + case Expression::Kind::kTernary: + this->writeTernaryExpression(expr.as<TernaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kIndex: + this->writeIndexExpression(expr.as<IndexExpression>()); + break; + default: + SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str()); + break; + } +} + +// returns true if we should pass by reference instead of by value +static bool pass_by_reference(const Type& type, const Modifiers& modifiers) { + return (modifiers.fFlags & Modifiers::kOut_Flag) && !type.isUnsizedArray(); +} + +// returns true if we need to specify an address space modifier +static bool needs_address_space(const Type& type, const Modifiers& modifiers) { + return type.isUnsizedArray() || pass_by_reference(type, modifiers); +} + +// returns true if the InterfaceBlock has the `buffer` modifier +static bool is_buffer(const InterfaceBlock& block) { + return block.var()->modifiers().fFlags & Modifiers::kBuffer_Flag; +} + +// returns true if the InterfaceBlock has the `readonly` modifier +static bool is_readonly(const InterfaceBlock& block) { + return block.var()->modifiers().fFlags & Modifiers::kReadOnly_Flag; +} + +std::string MetalCodeGenerator::getOutParamHelper(const FunctionCall& call, + const ExpressionArray& arguments, + const SkTArray<VariableReference*>& outVars) { + // It's possible for out-param function arguments to contain an out-param function call + // expression. Emit the function into a temporary stream to prevent the nested helper from + // clobbering the current helper as we recursively evaluate argument expressions. + StringStream tmpStream; + AutoOutputStream outputToExtraFunctions(this, &tmpStream, &fIndentation); + + const FunctionDeclaration& function = call.function(); + + std::string name = "_skOutParamHelper" + std::to_string(fSwizzleHelperCount++) + + "_" + function.mangledName(); + const char* separator = ""; + + // Emit a prototype for the function we'll be calling through to in our helper. + if (!function.isBuiltin()) { + this->writeFunctionDeclaration(function); + this->writeLine(";"); + } + + // Synthesize a helper function that takes the same inputs as `function`, except in places where + // `outVars` is non-null; in those places, we take the type of the VariableReference. + // + // float _skOutParamHelper0_originalFuncName(float _var0, float _var1, float& outParam) { + this->writeType(call.type()); + this->write(" "); + this->write(name); + this->write("("); + this->writeFunctionRequirementParams(function, separator); + + SkASSERT(outVars.size() == arguments.size()); + SkASSERT(SkToSizeT(outVars.size()) == function.parameters().size()); + + // We need to detect cases where the caller passes the same variable as an out-param more than + // once, and avoid reusing the variable name. (In those cases we can actually just ignore the + // redundant input parameter entirely, and not give it any name.) + SkTHashSet<const Variable*> writtenVars; + + for (int index = 0; index < arguments.size(); ++index) { + this->write(separator); + separator = ", "; + + const Variable* param = function.parameters()[index]; + this->writeModifiers(param->modifiers()); + + const Type* type = outVars[index] ? &outVars[index]->type() : &arguments[index]->type(); + this->writeType(*type); + + if (pass_by_reference(param->type(), param->modifiers())) { + this->write("&"); + } + if (outVars[index]) { + const Variable* var = outVars[index]->variable(); + if (!writtenVars.contains(var)) { + writtenVars.add(var); + + this->write(" "); + fIgnoreVariableReferenceModifiers = true; + this->writeVariableReference(*outVars[index]); + fIgnoreVariableReferenceModifiers = false; + } + } else { + this->write(" _var"); + this->write(std::to_string(index)); + } + } + this->writeLine(") {"); + + ++fIndentation; + for (int index = 0; index < outVars.size(); ++index) { + if (!outVars[index]) { + continue; + } + // float3 _var2[ = outParam.zyx]; + this->writeType(arguments[index]->type()); + this->write(" _var"); + this->write(std::to_string(index)); + + const Variable* param = function.parameters()[index]; + if (param->modifiers().fFlags & Modifiers::kIn_Flag) { + this->write(" = "); + fIgnoreVariableReferenceModifiers = true; + this->writeExpression(*arguments[index], Precedence::kAssignment); + fIgnoreVariableReferenceModifiers = false; + } + + this->writeLine(";"); + } + + // [int _skResult = ] myFunction(inputs, outputs, _globals, _var0, _var1, _var2, _var3); + bool hasResult = (call.type().name() != "void"); + if (hasResult) { + this->writeType(call.type()); + this->write(" _skResult = "); + } + + this->writeName(function.mangledName()); + this->write("("); + separator = ""; + this->writeFunctionRequirementArgs(function, separator); + + for (int index = 0; index < arguments.size(); ++index) { + this->write(separator); + separator = ", "; + + this->write("_var"); + this->write(std::to_string(index)); + } + this->writeLine(");"); + + for (int index = 0; index < outVars.size(); ++index) { + if (!outVars[index]) { + continue; + } + // outParam.zyx = _var2; + fIgnoreVariableReferenceModifiers = true; + this->writeExpression(*arguments[index], Precedence::kAssignment); + fIgnoreVariableReferenceModifiers = false; + this->write(" = _var"); + this->write(std::to_string(index)); + this->writeLine(";"); + } + + if (hasResult) { + this->writeLine("return _skResult;"); + } + + --fIndentation; + this->writeLine("}"); + + // Write the function out to `fExtraFunctions`. + write_stringstream(tmpStream, fExtraFunctions); + + return name; +} + +std::string MetalCodeGenerator::getBitcastIntrinsic(const Type& outType) { + return "as_type<" + outType.displayName() + ">"; +} + +void MetalCodeGenerator::writeFunctionCall(const FunctionCall& c) { + const FunctionDeclaration& function = c.function(); + + // Many intrinsics need to be rewritten in Metal. + if (function.isIntrinsic()) { + if (this->writeIntrinsicCall(c, function.intrinsicKind())) { + return; + } + } + + // Determine whether or not we need to emulate GLSL's out-param semantics for Metal using a + // helper function. (Specifically, out-parameters in GLSL are only written back to the original + // variable at the end of the function call; also, swizzles are supported, whereas Metal doesn't + // allow a swizzle to be passed to a `floatN&`.) + const ExpressionArray& arguments = c.arguments(); + const std::vector<Variable*>& parameters = function.parameters(); + SkASSERT(SkToSizeT(arguments.size()) == parameters.size()); + + bool foundOutParam = false; + SkSTArray<16, VariableReference*> outVars; + outVars.push_back_n(arguments.size(), (VariableReference*)nullptr); + + for (int index = 0; index < arguments.size(); ++index) { + // If this is an out parameter... + if (parameters[index]->modifiers().fFlags & Modifiers::kOut_Flag) { + // Find the expression's inner variable being written to. + Analysis::AssignmentInfo info; + // Assignability was verified at IRGeneration time, so this should always succeed. + SkAssertResult(Analysis::IsAssignable(*arguments[index], &info)); + outVars[index] = info.fAssignedVar; + foundOutParam = true; + } + } + + if (foundOutParam) { + // Out parameters need to be written back to at the end of the function. To do this, we + // synthesize a helper function which evaluates the out-param expression into a temporary + // variable, calls the original function, then writes the temp var back into the out param + // using the original out-param expression. (This lets us support things like swizzles and + // array indices.) + this->write(getOutParamHelper(c, arguments, outVars)); + } else { + this->write(function.mangledName()); + } + + this->write("("); + const char* separator = ""; + this->writeFunctionRequirementArgs(function, separator); + for (int i = 0; i < arguments.size(); ++i) { + this->write(separator); + separator = ", "; + + if (outVars[i]) { + this->writeExpression(*outVars[i], Precedence::kSequence); + } else { + this->writeExpression(*arguments[i], Precedence::kSequence); + } + } + this->write(")"); +} + +static constexpr char kInverse2x2[] = R"( +template <typename T> +matrix<T, 2, 2> mat2_inverse(matrix<T, 2, 2> m) { +return matrix<T, 2, 2>(m[1].y, -m[0].y, -m[1].x, m[0].x) * (1/determinant(m)); +} +)"; + +static constexpr char kInverse3x3[] = R"( +template <typename T> +matrix<T, 3, 3> mat3_inverse(matrix<T, 3, 3> m) { +T + a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, + a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, + a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, + b01 = a22*a11 - a12*a21, + b11 = -a22*a10 + a12*a20, + b21 = a21*a10 - a11*a20, + det = a00*b01 + a01*b11 + a02*b21; +return matrix<T, 3, 3>( + b01, (-a22*a01 + a02*a21), ( a12*a01 - a02*a11), + b11, ( a22*a00 - a02*a20), (-a12*a00 + a02*a10), + b21, (-a21*a00 + a01*a20), ( a11*a00 - a01*a10)) * (1/det); +} +)"; + +static constexpr char kInverse4x4[] = R"( +template <typename T> +matrix<T, 4, 4> mat4_inverse(matrix<T, 4, 4> m) { +T + a00 = m[0].x, a01 = m[0].y, a02 = m[0].z, a03 = m[0].w, + a10 = m[1].x, a11 = m[1].y, a12 = m[1].z, a13 = m[1].w, + a20 = m[2].x, a21 = m[2].y, a22 = m[2].z, a23 = m[2].w, + a30 = m[3].x, a31 = m[3].y, a32 = m[3].z, a33 = m[3].w, + b00 = a00*a11 - a01*a10, + b01 = a00*a12 - a02*a10, + b02 = a00*a13 - a03*a10, + b03 = a01*a12 - a02*a11, + b04 = a01*a13 - a03*a11, + b05 = a02*a13 - a03*a12, + b06 = a20*a31 - a21*a30, + b07 = a20*a32 - a22*a30, + b08 = a20*a33 - a23*a30, + b09 = a21*a32 - a22*a31, + b10 = a21*a33 - a23*a31, + b11 = a22*a33 - a23*a32, + det = b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06; +return matrix<T, 4, 4>( + a11*b11 - a12*b10 + a13*b09, + a02*b10 - a01*b11 - a03*b09, + a31*b05 - a32*b04 + a33*b03, + a22*b04 - a21*b05 - a23*b03, + a12*b08 - a10*b11 - a13*b07, + a00*b11 - a02*b08 + a03*b07, + a32*b02 - a30*b05 - a33*b01, + a20*b05 - a22*b02 + a23*b01, + a10*b10 - a11*b08 + a13*b06, + a01*b08 - a00*b10 - a03*b06, + a30*b04 - a31*b02 + a33*b00, + a21*b02 - a20*b04 - a23*b00, + a11*b07 - a10*b09 - a12*b06, + a00*b09 - a01*b07 + a02*b06, + a31*b01 - a30*b03 - a32*b00, + a20*b03 - a21*b01 + a22*b00) * (1/det); +} +)"; + +std::string MetalCodeGenerator::getInversePolyfill(const ExpressionArray& arguments) { + // Only use polyfills for a function taking a single-argument square matrix. + SkASSERT(arguments.size() == 1); + const Type& type = arguments.front()->type(); + if (type.isMatrix() && type.rows() == type.columns()) { + switch (type.rows()) { + case 2: + if (!fWrittenInverse2) { + fWrittenInverse2 = true; + fExtraFunctions.writeText(kInverse2x2); + } + return "mat2_inverse"; + case 3: + if (!fWrittenInverse3) { + fWrittenInverse3 = true; + fExtraFunctions.writeText(kInverse3x3); + } + return "mat3_inverse"; + case 4: + if (!fWrittenInverse4) { + fWrittenInverse4 = true; + fExtraFunctions.writeText(kInverse4x4); + } + return "mat4_inverse"; + } + } + SkDEBUGFAILF("no polyfill for inverse(%s)", type.description().c_str()); + return "inverse"; +} + +void MetalCodeGenerator::writeMatrixCompMult() { + static constexpr char kMatrixCompMult[] = R"( +template <typename T, int C, int R> +matrix<T, C, R> matrixCompMult(matrix<T, C, R> a, const matrix<T, C, R> b) { + for (int c = 0; c < C; ++c) { a[c] *= b[c]; } + return a; +} +)"; + if (!fWrittenMatrixCompMult) { + fWrittenMatrixCompMult = true; + fExtraFunctions.writeText(kMatrixCompMult); + } +} + +void MetalCodeGenerator::writeOuterProduct() { + static constexpr char kOuterProduct[] = R"( +template <typename T, int C, int R> +matrix<T, C, R> outerProduct(const vec<T, R> a, const vec<T, C> b) { + matrix<T, C, R> m; + for (int c = 0; c < C; ++c) { m[c] = a * b[c]; } + return m; +} +)"; + if (!fWrittenOuterProduct) { + fWrittenOuterProduct = true; + fExtraFunctions.writeText(kOuterProduct); + } +} + +std::string MetalCodeGenerator::getTempVariable(const Type& type) { + std::string tempVar = "_skTemp" + std::to_string(fVarCount++); + this->fFunctionHeader += " " + this->typeName(type) + " " + tempVar + ";\n"; + return tempVar; +} + +void MetalCodeGenerator::writeSimpleIntrinsic(const FunctionCall& c) { + // Write out an intrinsic function call exactly as-is. No muss no fuss. + this->write(c.function().name()); + this->writeArgumentList(c.arguments()); +} + +void MetalCodeGenerator::writeArgumentList(const ExpressionArray& arguments) { + this->write("("); + const char* separator = ""; + for (const std::unique_ptr<Expression>& arg : arguments) { + this->write(separator); + separator = ", "; + this->writeExpression(*arg, Precedence::kSequence); + } + this->write(")"); +} + +bool MetalCodeGenerator::writeIntrinsicCall(const FunctionCall& c, IntrinsicKind kind) { + const ExpressionArray& arguments = c.arguments(); + switch (kind) { + case k_read_IntrinsicKind: { + this->writeExpression(*arguments[0], Precedence::kTopLevel); + this->write(".read("); + this->writeExpression(*arguments[1], Precedence::kSequence); + this->write(")"); + return true; + } + case k_write_IntrinsicKind: { + this->writeExpression(*arguments[0], Precedence::kTopLevel); + this->write(".write("); + this->writeExpression(*arguments[2], Precedence::kSequence); + this->write(", "); + this->writeExpression(*arguments[1], Precedence::kSequence); + this->write(")"); + return true; + } + case k_width_IntrinsicKind: { + this->writeExpression(*arguments[0], Precedence::kTopLevel); + this->write(".get_width()"); + return true; + } + case k_height_IntrinsicKind: { + this->writeExpression(*arguments[0], Precedence::kTopLevel); + this->write(".get_height()"); + return true; + } + case k_mod_IntrinsicKind: { + // fmod(x, y) in metal calculates x - y * trunc(x / y) instead of x - y * floor(x / y) + std::string tmpX = this->getTempVariable(arguments[0]->type()); + std::string tmpY = this->getTempVariable(arguments[1]->type()); + this->write("(" + tmpX + " = "); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(", " + tmpY + " = "); + this->writeExpression(*arguments[1], Precedence::kSequence); + this->write(", " + tmpX + " - " + tmpY + " * floor(" + tmpX + " / " + tmpY + "))"); + return true; + } + // GLSL declares scalar versions of most geometric intrinsics, but these don't exist in MSL + case k_distance_IntrinsicKind: { + if (arguments[0]->type().columns() == 1) { + this->write("abs("); + this->writeExpression(*arguments[0], Precedence::kAdditive); + this->write(" - "); + this->writeExpression(*arguments[1], Precedence::kAdditive); + this->write(")"); + } else { + this->writeSimpleIntrinsic(c); + } + return true; + } + case k_dot_IntrinsicKind: { + if (arguments[0]->type().columns() == 1) { + this->write("("); + this->writeExpression(*arguments[0], Precedence::kMultiplicative); + this->write(" * "); + this->writeExpression(*arguments[1], Precedence::kMultiplicative); + this->write(")"); + } else { + this->writeSimpleIntrinsic(c); + } + return true; + } + case k_faceforward_IntrinsicKind: { + if (arguments[0]->type().columns() == 1) { + // ((((Nref) * (I) < 0) ? 1 : -1) * (N)) + this->write("(((("); + this->writeExpression(*arguments[2], Precedence::kSequence); + this->write(") * ("); + this->writeExpression(*arguments[1], Precedence::kSequence); + this->write(") < 0) ? 1 : -1) * ("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("))"); + } else { + this->writeSimpleIntrinsic(c); + } + return true; + } + case k_length_IntrinsicKind: { + this->write(arguments[0]->type().columns() == 1 ? "abs(" : "length("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_normalize_IntrinsicKind: { + this->write(arguments[0]->type().columns() == 1 ? "sign(" : "normalize("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_packUnorm2x16_IntrinsicKind: { + this->write("pack_float_to_unorm2x16("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_unpackUnorm2x16_IntrinsicKind: { + this->write("unpack_unorm2x16_to_float("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_packSnorm2x16_IntrinsicKind: { + this->write("pack_float_to_snorm2x16("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_unpackSnorm2x16_IntrinsicKind: { + this->write("unpack_snorm2x16_to_float("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_packUnorm4x8_IntrinsicKind: { + this->write("pack_float_to_unorm4x8("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_unpackUnorm4x8_IntrinsicKind: { + this->write("unpack_unorm4x8_to_float("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_packSnorm4x8_IntrinsicKind: { + this->write("pack_float_to_snorm4x8("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_unpackSnorm4x8_IntrinsicKind: { + this->write("unpack_snorm4x8_to_float("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_packHalf2x16_IntrinsicKind: { + this->write("as_type<uint>(half2("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("))"); + return true; + } + case k_unpackHalf2x16_IntrinsicKind: { + this->write("float2(as_type<half2>("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("))"); + return true; + } + case k_floatBitsToInt_IntrinsicKind: + case k_floatBitsToUint_IntrinsicKind: + case k_intBitsToFloat_IntrinsicKind: + case k_uintBitsToFloat_IntrinsicKind: { + this->write(this->getBitcastIntrinsic(c.type())); + this->write("("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_degrees_IntrinsicKind: { + this->write("(("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(") * 57.2957795)"); + return true; + } + case k_radians_IntrinsicKind: { + this->write("(("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(") * 0.0174532925)"); + return true; + } + case k_dFdx_IntrinsicKind: { + this->write("dfdx"); + this->writeArgumentList(c.arguments()); + return true; + } + case k_dFdy_IntrinsicKind: { + if (!fRTFlipName.empty()) { + this->write("(" + fRTFlipName + ".y * dfdy"); + } else { + this->write("(dfdy"); + } + this->writeArgumentList(c.arguments()); + this->write(")"); + return true; + } + case k_inverse_IntrinsicKind: { + this->write(this->getInversePolyfill(arguments)); + this->writeArgumentList(c.arguments()); + return true; + } + case k_inversesqrt_IntrinsicKind: { + this->write("rsqrt"); + this->writeArgumentList(c.arguments()); + return true; + } + case k_atan_IntrinsicKind: { + this->write(c.arguments().size() == 2 ? "atan2" : "atan"); + this->writeArgumentList(c.arguments()); + return true; + } + case k_reflect_IntrinsicKind: { + if (arguments[0]->type().columns() == 1) { + // We need to synthesize `I - 2 * N * I * N`. + std::string tmpI = this->getTempVariable(arguments[0]->type()); + std::string tmpN = this->getTempVariable(arguments[1]->type()); + + // (_skTempI = ... + this->write("(" + tmpI + " = "); + this->writeExpression(*arguments[0], Precedence::kSequence); + + // , _skTempN = ... + this->write(", " + tmpN + " = "); + this->writeExpression(*arguments[1], Precedence::kSequence); + + // , _skTempI - 2 * _skTempN * _skTempI * _skTempN) + this->write(", " + tmpI + " - 2 * " + tmpN + " * " + tmpI + " * " + tmpN + ")"); + } else { + this->writeSimpleIntrinsic(c); + } + return true; + } + case k_refract_IntrinsicKind: { + if (arguments[0]->type().columns() == 1) { + // Metal does implement refract for vectors; rather than reimplementing refract from + // scratch, we can replace the call with `refract(float2(I,0), float2(N,0), eta).x`. + this->write("(refract(float2("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(", 0), float2("); + this->writeExpression(*arguments[1], Precedence::kSequence); + this->write(", 0), "); + this->writeExpression(*arguments[2], Precedence::kSequence); + this->write(").x)"); + } else { + this->writeSimpleIntrinsic(c); + } + return true; + } + case k_roundEven_IntrinsicKind: { + this->write("rint"); + this->writeArgumentList(c.arguments()); + return true; + } + case k_bitCount_IntrinsicKind: { + this->write("popcount("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write(")"); + return true; + } + case k_findLSB_IntrinsicKind: { + // Create a temp variable to store the expression, to avoid double-evaluating it. + std::string skTemp = this->getTempVariable(arguments[0]->type()); + std::string exprType = this->typeName(arguments[0]->type()); + + // ctz returns numbits(type) on zero inputs; GLSL documents it as generating -1 instead. + // Use select to detect zero inputs and force a -1 result. + + // (_skTemp1 = (.....), select(ctz(_skTemp1), int4(-1), _skTemp1 == int4(0))) + this->write("("); + this->write(skTemp); + this->write(" = ("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("), select(ctz("); + this->write(skTemp); + this->write("), "); + this->write(exprType); + this->write("(-1), "); + this->write(skTemp); + this->write(" == "); + this->write(exprType); + this->write("(0)))"); + return true; + } + case k_findMSB_IntrinsicKind: { + // Create a temp variable to store the expression, to avoid double-evaluating it. + std::string skTemp1 = this->getTempVariable(arguments[0]->type()); + std::string exprType = this->typeName(arguments[0]->type()); + + // GLSL findMSB is actually quite different from Metal's clz: + // - For signed negative numbers, it returns the first zero bit, not the first one bit! + // - For an empty input (0/~0 depending on sign), findMSB gives -1; clz is numbits(type) + + // (_skTemp1 = (.....), + this->write("("); + this->write(skTemp1); + this->write(" = ("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("), "); + + // Signed input types might be negative; we need another helper variable to negate the + // input (since we can only find one bits, not zero bits). + std::string skTemp2; + if (arguments[0]->type().isSigned()) { + // ... _skTemp2 = (select(_skTemp1, ~_skTemp1, _skTemp1 < 0)), + skTemp2 = this->getTempVariable(arguments[0]->type()); + this->write(skTemp2); + this->write(" = (select("); + this->write(skTemp1); + this->write(", ~"); + this->write(skTemp1); + this->write(", "); + this->write(skTemp1); + this->write(" < 0)), "); + } else { + skTemp2 = skTemp1; + } + + // ... select(int4(clz(_skTemp2)), int4(-1), _skTemp2 == int4(0))) + this->write("select("); + this->write(this->typeName(c.type())); + this->write("(clz("); + this->write(skTemp2); + this->write(")), "); + this->write(this->typeName(c.type())); + this->write("(-1), "); + this->write(skTemp2); + this->write(" == "); + this->write(exprType); + this->write("(0)))"); + return true; + } + case k_sign_IntrinsicKind: { + if (arguments[0]->type().componentType().isInteger()) { + // Create a temp variable to store the expression, to avoid double-evaluating it. + std::string skTemp = this->getTempVariable(arguments[0]->type()); + std::string exprType = this->typeName(arguments[0]->type()); + + // (_skTemp = (.....), + this->write("("); + this->write(skTemp); + this->write(" = ("); + this->writeExpression(*arguments[0], Precedence::kSequence); + this->write("), "); + + // ... select(select(int4(0), int4(-1), _skTemp < 0), int4(1), _skTemp > 0)) + this->write("select(select("); + this->write(exprType); + this->write("(0), "); + this->write(exprType); + this->write("(-1), "); + this->write(skTemp); + this->write(" < 0), "); + this->write(exprType); + this->write("(1), "); + this->write(skTemp); + this->write(" > 0))"); + } else { + this->writeSimpleIntrinsic(c); + } + return true; + } + case k_matrixCompMult_IntrinsicKind: { + this->writeMatrixCompMult(); + this->writeSimpleIntrinsic(c); + return true; + } + case k_outerProduct_IntrinsicKind: { + this->writeOuterProduct(); + this->writeSimpleIntrinsic(c); + return true; + } + case k_mix_IntrinsicKind: { + SkASSERT(c.arguments().size() == 3); + if (arguments[2]->type().componentType().isBoolean()) { + // The Boolean forms of GLSL mix() use the select() intrinsic in Metal. + this->write("select"); + this->writeArgumentList(c.arguments()); + return true; + } + // The basic form of mix() is supported by Metal as-is. + this->writeSimpleIntrinsic(c); + return true; + } + case k_equal_IntrinsicKind: + case k_greaterThan_IntrinsicKind: + case k_greaterThanEqual_IntrinsicKind: + case k_lessThan_IntrinsicKind: + case k_lessThanEqual_IntrinsicKind: + case k_notEqual_IntrinsicKind: { + this->write("("); + this->writeExpression(*c.arguments()[0], Precedence::kRelational); + switch (kind) { + case k_equal_IntrinsicKind: + this->write(" == "); + break; + case k_notEqual_IntrinsicKind: + this->write(" != "); + break; + case k_lessThan_IntrinsicKind: + this->write(" < "); + break; + case k_lessThanEqual_IntrinsicKind: + this->write(" <= "); + break; + case k_greaterThan_IntrinsicKind: + this->write(" > "); + break; + case k_greaterThanEqual_IntrinsicKind: + this->write(" >= "); + break; + default: + SK_ABORT("unsupported comparison intrinsic kind"); + } + this->writeExpression(*c.arguments()[1], Precedence::kRelational); + this->write(")"); + return true; + } + case k_storageBarrier_IntrinsicKind: + this->write("threadgroup_barrier(mem_flags::mem_device)"); + return true; + case k_workgroupBarrier_IntrinsicKind: + this->write("threadgroup_barrier(mem_flags::mem_threadgroup)"); + return true; + case k_atomicAdd_IntrinsicKind: + this->write("atomic_fetch_add_explicit(&"); + this->writeExpression(*c.arguments()[0], Precedence::kSequence); + this->write(", "); + this->writeExpression(*c.arguments()[1], Precedence::kSequence); + this->write(", memory_order_relaxed)"); + return true; + case k_atomicLoad_IntrinsicKind: + this->write("atomic_load_explicit(&"); + this->writeExpression(*c.arguments()[0], Precedence::kSequence); + this->write(", memory_order_relaxed)"); + return true; + case k_atomicStore_IntrinsicKind: + this->write("atomic_store_explicit(&"); + this->writeExpression(*c.arguments()[0], Precedence::kSequence); + this->write(", "); + this->writeExpression(*c.arguments()[1], Precedence::kSequence); + this->write(", memory_order_relaxed)"); + return true; + default: + return false; + } +} + +// Assembles a matrix of type floatRxC by resizing another matrix named `x0`. +// Cells that don't exist in the source matrix will be populated with identity-matrix values. +void MetalCodeGenerator::assembleMatrixFromMatrix(const Type& sourceMatrix, int rows, int columns) { + SkASSERT(rows <= 4); + SkASSERT(columns <= 4); + + std::string matrixType = this->typeName(sourceMatrix.componentType()); + + const char* separator = ""; + for (int c = 0; c < columns; ++c) { + fExtraFunctions.printf("%s%s%d(", separator, matrixType.c_str(), rows); + separator = "), "; + + // Determine how many values to take from the source matrix for this row. + int swizzleLength = 0; + if (c < sourceMatrix.columns()) { + swizzleLength = std::min<>(rows, sourceMatrix.rows()); + } + + // Emit all the values from the source matrix row. + bool firstItem; + switch (swizzleLength) { + case 0: firstItem = true; break; + case 1: firstItem = false; fExtraFunctions.printf("x0[%d].x", c); break; + case 2: firstItem = false; fExtraFunctions.printf("x0[%d].xy", c); break; + case 3: firstItem = false; fExtraFunctions.printf("x0[%d].xyz", c); break; + case 4: firstItem = false; fExtraFunctions.printf("x0[%d].xyzw", c); break; + default: SkUNREACHABLE; + } + + // Emit the placeholder identity-matrix cells. + for (int r = swizzleLength; r < rows; ++r) { + fExtraFunctions.printf("%s%s", firstItem ? "" : ", ", (r == c) ? "1.0" : "0.0"); + firstItem = false; + } + } + + fExtraFunctions.writeText(")"); +} + +// Assembles a matrix of type floatCxR by concatenating an arbitrary mix of values, named `x0`, +// `x1`, etc. An error is written if the expression list don't contain exactly C*R scalars. +void MetalCodeGenerator::assembleMatrixFromExpressions(const AnyConstructor& ctor, + int columns, int rows) { + SkASSERT(rows <= 4); + SkASSERT(columns <= 4); + + std::string matrixType = this->typeName(ctor.type().componentType()); + size_t argIndex = 0; + int argPosition = 0; + auto args = ctor.argumentSpan(); + + static constexpr char kSwizzle[] = "xyzw"; + const char* separator = ""; + for (int c = 0; c < columns; ++c) { + fExtraFunctions.printf("%s%s%d(", separator, matrixType.c_str(), rows); + separator = "), "; + + const char* columnSeparator = ""; + for (int r = 0; r < rows;) { + fExtraFunctions.writeText(columnSeparator); + columnSeparator = ", "; + + if (argIndex < args.size()) { + const Type& argType = args[argIndex]->type(); + switch (argType.typeKind()) { + case Type::TypeKind::kScalar: { + fExtraFunctions.printf("x%zu", argIndex); + ++r; + ++argPosition; + break; + } + case Type::TypeKind::kVector: { + fExtraFunctions.printf("x%zu.", argIndex); + do { + fExtraFunctions.write8(kSwizzle[argPosition]); + ++r; + ++argPosition; + } while (r < rows && argPosition < argType.columns()); + break; + } + case Type::TypeKind::kMatrix: { + fExtraFunctions.printf("x%zu[%d].", argIndex, argPosition / argType.rows()); + do { + fExtraFunctions.write8(kSwizzle[argPosition]); + ++r; + ++argPosition; + } while (r < rows && (argPosition % argType.rows()) != 0); + break; + } + default: { + SkDEBUGFAIL("incorrect type of argument for matrix constructor"); + fExtraFunctions.writeText("<error>"); + break; + } + } + + if (argPosition >= argType.columns() * argType.rows()) { + ++argIndex; + argPosition = 0; + } + } else { + SkDEBUGFAIL("not enough arguments for matrix constructor"); + fExtraFunctions.writeText("<error>"); + } + } + } + + if (argPosition != 0 || argIndex != args.size()) { + SkDEBUGFAIL("incorrect number of arguments for matrix constructor"); + fExtraFunctions.writeText(", <error>"); + } + + fExtraFunctions.writeText(")"); +} + +// Generates a constructor for 'matrix' which reorganizes the input arguments into the proper shape. +// Keeps track of previously generated constructors so that we won't generate more than one +// constructor for any given permutation of input argument types. Returns the name of the +// generated constructor method. +std::string MetalCodeGenerator::getMatrixConstructHelper(const AnyConstructor& c) { + const Type& type = c.type(); + int columns = type.columns(); + int rows = type.rows(); + auto args = c.argumentSpan(); + std::string typeName = this->typeName(type); + + // Create the helper-method name and use it as our lookup key. + std::string name = String::printf("%s_from", typeName.c_str()); + for (const std::unique_ptr<Expression>& expr : args) { + String::appendf(&name, "_%s", this->typeName(expr->type()).c_str()); + } + + // If a helper-method has not been synthesized yet, create it now. + if (!fHelpers.contains(name)) { + fHelpers.add(name); + + // Unlike GLSL, Metal requires that matrices are initialized with exactly R vectors of C + // components apiece. (In Metal 2.0, you can also supply R*C scalars, but you still cannot + // supply a mixture of scalars and vectors.) + fExtraFunctions.printf("%s %s(", typeName.c_str(), name.c_str()); + + size_t argIndex = 0; + const char* argSeparator = ""; + for (const std::unique_ptr<Expression>& expr : args) { + fExtraFunctions.printf("%s%s x%zu", argSeparator, + this->typeName(expr->type()).c_str(), argIndex++); + argSeparator = ", "; + } + + fExtraFunctions.printf(") {\n return %s(", typeName.c_str()); + + if (args.size() == 1 && args.front()->type().isMatrix()) { + this->assembleMatrixFromMatrix(args.front()->type(), rows, columns); + } else { + this->assembleMatrixFromExpressions(c, columns, rows); + } + + fExtraFunctions.writeText(");\n}\n"); + } + return name; +} + +bool MetalCodeGenerator::matrixConstructHelperIsNeeded(const ConstructorCompound& c) { + SkASSERT(c.type().isMatrix()); + + // GLSL is fairly free-form about inputs to its matrix constructors, but Metal is not; it + // expects exactly R vectors of C components apiece. (Metal 2.0 also allows a list of R*C + // scalars.) Some cases are simple to translate and so we handle those inline--e.g. a list of + // scalars can be constructed trivially. In more complex cases, we generate a helper function + // that converts our inputs into a properly-shaped matrix. + // A matrix construct helper method is always used if any input argument is a matrix. + // Helper methods are also necessary when any argument would span multiple rows. For instance: + // + // float2 x = (1, 2); + // float3x2(x, 3, 4, 5, 6) = | 1 3 5 | = no helper needed; conversion can be done inline + // | 2 4 6 | + // + // float2 x = (2, 3); + // float3x2(1, x, 4, 5, 6) = | 1 3 5 | = x spans multiple rows; a helper method will be used + // | 2 4 6 | + // + // float4 x = (1, 2, 3, 4); + // float2x2(x) = | 1 3 | = x spans multiple rows; a helper method will be used + // | 2 4 | + // + + int position = 0; + for (const std::unique_ptr<Expression>& expr : c.arguments()) { + // If an input argument is a matrix, we need a helper function. + if (expr->type().isMatrix()) { + return true; + } + position += expr->type().columns(); + if (position > c.type().rows()) { + // An input argument would span multiple rows; a helper function is required. + return true; + } + if (position == c.type().rows()) { + // We've advanced to the end of a row. Wrap to the start of the next row. + position = 0; + } + } + + return false; +} + +void MetalCodeGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& c, + Precedence parentPrecedence) { + // Matrix-resize via casting doesn't natively exist in Metal at all, so we always need to use a + // matrix-construct helper here. + this->write(this->getMatrixConstructHelper(c)); + this->write("("); + this->writeExpression(*c.argument(), Precedence::kSequence); + this->write(")"); +} + +void MetalCodeGenerator::writeConstructorCompound(const ConstructorCompound& c, + Precedence parentPrecedence) { + if (c.type().isVector()) { + this->writeConstructorCompoundVector(c, parentPrecedence); + } else if (c.type().isMatrix()) { + this->writeConstructorCompoundMatrix(c, parentPrecedence); + } else { + fContext.fErrors->error(c.fPosition, "unsupported compound constructor"); + } +} + +void MetalCodeGenerator::writeConstructorArrayCast(const ConstructorArrayCast& c, + Precedence parentPrecedence) { + const Type& inType = c.argument()->type().componentType(); + const Type& outType = c.type().componentType(); + std::string inTypeName = this->typeName(inType); + std::string outTypeName = this->typeName(outType); + + std::string name = "array_of_" + outTypeName + "_from_" + inTypeName; + if (!fHelpers.contains(name)) { + fHelpers.add(name); + fExtraFunctions.printf(R"( +template <size_t N> +array<%s, N> %s(thread const array<%s, N>& x) { + array<%s, N> result; + for (int i = 0; i < N; ++i) { + result[i] = %s(x[i]); + } + return result; +} +)", + outTypeName.c_str(), name.c_str(), inTypeName.c_str(), + outTypeName.c_str(), + outTypeName.c_str()); + } + + this->write(name); + this->write("("); + this->writeExpression(*c.argument(), Precedence::kSequence); + this->write(")"); +} + +std::string MetalCodeGenerator::getVectorFromMat2x2ConstructorHelper(const Type& matrixType) { + SkASSERT(matrixType.isMatrix()); + SkASSERT(matrixType.rows() == 2); + SkASSERT(matrixType.columns() == 2); + + std::string baseType = this->typeName(matrixType.componentType()); + std::string name = String::printf("%s4_from_%s2x2", baseType.c_str(), baseType.c_str()); + if (!fHelpers.contains(name)) { + fHelpers.add(name); + + fExtraFunctions.printf(R"( +%s4 %s(%s2x2 x) { + return %s4(x[0].xy, x[1].xy); +} +)", baseType.c_str(), name.c_str(), baseType.c_str(), baseType.c_str()); + } + + return name; +} + +void MetalCodeGenerator::writeConstructorCompoundVector(const ConstructorCompound& c, + Precedence parentPrecedence) { + SkASSERT(c.type().isVector()); + + // Metal supports constructing vectors from a mix of scalars and vectors, but not matrices. + // GLSL supports vec4(mat2x2), so we detect that case here and emit a helper function. + if (c.type().columns() == 4 && c.argumentSpan().size() == 1) { + const Expression& expr = *c.argumentSpan().front(); + if (expr.type().isMatrix()) { + this->write(this->getVectorFromMat2x2ConstructorHelper(expr.type())); + this->write("("); + this->writeExpression(expr, Precedence::kSequence); + this->write(")"); + return; + } + } + + this->writeAnyConstructor(c, "(", ")", parentPrecedence); +} + +void MetalCodeGenerator::writeConstructorCompoundMatrix(const ConstructorCompound& c, + Precedence parentPrecedence) { + SkASSERT(c.type().isMatrix()); + + // Emit and invoke a matrix-constructor helper method if one is necessary. + if (this->matrixConstructHelperIsNeeded(c)) { + this->write(this->getMatrixConstructHelper(c)); + this->write("("); + const char* separator = ""; + for (const std::unique_ptr<Expression>& expr : c.arguments()) { + this->write(separator); + separator = ", "; + this->writeExpression(*expr, Precedence::kSequence); + } + this->write(")"); + return; + } + + // Metal doesn't allow creating matrices by passing in scalars and vectors in a jumble; it + // requires your scalars to be grouped up into columns. Because `matrixConstructHelperIsNeeded` + // returned false, we know that none of our scalars/vectors "wrap" across across a column, so we + // can group our inputs up and synthesize a constructor for each column. + const Type& matrixType = c.type(); + const Type& columnType = matrixType.componentType().toCompound( + fContext, /*columns=*/matrixType.rows(), /*rows=*/1); + + this->writeType(matrixType); + this->write("("); + const char* separator = ""; + int scalarCount = 0; + for (const std::unique_ptr<Expression>& arg : c.arguments()) { + this->write(separator); + separator = ", "; + if (arg->type().columns() < matrixType.rows()) { + // Write a `floatN(` constructor to group scalars and smaller vectors together. + if (!scalarCount) { + this->writeType(columnType); + this->write("("); + } + scalarCount += arg->type().columns(); + } + this->writeExpression(*arg, Precedence::kSequence); + if (scalarCount && scalarCount == matrixType.rows()) { + // Close our `floatN(...` constructor block from above. + this->write(")"); + scalarCount = 0; + } + } + this->write(")"); +} + +void MetalCodeGenerator::writeAnyConstructor(const AnyConstructor& c, + const char* leftBracket, + const char* rightBracket, + Precedence parentPrecedence) { + this->writeType(c.type()); + this->write(leftBracket); + const char* separator = ""; + for (const std::unique_ptr<Expression>& arg : c.argumentSpan()) { + this->write(separator); + separator = ", "; + this->writeExpression(*arg, Precedence::kSequence); + } + this->write(rightBracket); +} + +void MetalCodeGenerator::writeCastConstructor(const AnyConstructor& c, + const char* leftBracket, + const char* rightBracket, + Precedence parentPrecedence) { + return this->writeAnyConstructor(c, leftBracket, rightBracket, parentPrecedence); +} + +void MetalCodeGenerator::writeFragCoord() { + if (!fRTFlipName.empty()) { + this->write("float4(_fragCoord.x, "); + this->write(fRTFlipName.c_str()); + this->write(".x + "); + this->write(fRTFlipName.c_str()); + this->write(".y * _fragCoord.y, 0.0, _fragCoord.w)"); + } else { + this->write("float4(_fragCoord.x, _fragCoord.y, 0.0, _fragCoord.w)"); + } +} + +static bool is_compute_builtin(const Variable& var) { + switch (var.modifiers().fLayout.fBuiltin) { + case SK_NUMWORKGROUPS_BUILTIN: + case SK_WORKGROUPID_BUILTIN: + case SK_LOCALINVOCATIONID_BUILTIN: + case SK_GLOBALINVOCATIONID_BUILTIN: + case SK_LOCALINVOCATIONINDEX_BUILTIN: + return true; + default: + break; + } + return false; +} + +// true if the var is part of the Inputs struct +static bool is_input(const Variable& var) { + SkASSERT(var.storage() == VariableStorage::kGlobal); + return var.modifiers().fFlags & Modifiers::kIn_Flag && + (var.modifiers().fLayout.fBuiltin == -1 || is_compute_builtin(var)) && + var.type().typeKind() != Type::TypeKind::kTexture; +} + +// true if the var is part of the Outputs struct +static bool is_output(const Variable& var) { + SkASSERT(var.storage() == VariableStorage::kGlobal); + // inout vars get written into the Inputs struct, so we exclude them from Outputs + return (var.modifiers().fFlags & Modifiers::kOut_Flag) && + !(var.modifiers().fFlags & Modifiers::kIn_Flag) && + var.modifiers().fLayout.fBuiltin == -1 && + var.type().typeKind() != Type::TypeKind::kTexture; +} + +// true if the var is part of the Uniforms struct +static bool is_uniforms(const Variable& var) { + SkASSERT(var.storage() == VariableStorage::kGlobal); + return var.modifiers().fFlags & Modifiers::kUniform_Flag && + var.type().typeKind() != Type::TypeKind::kSampler; +} + +// true if the var is part of the Threadgroups struct +static bool is_threadgroup(const Variable& var) { + SkASSERT(var.storage() == VariableStorage::kGlobal); + return var.modifiers().fFlags & Modifiers::kWorkgroup_Flag; +} + +// true if the var is part of the Globals struct +static bool is_in_globals(const Variable& var) { + SkASSERT(var.storage() == VariableStorage::kGlobal); + return !(var.modifiers().fFlags & Modifiers::kConst_Flag); +} + +void MetalCodeGenerator::writeVariableReference(const VariableReference& ref) { + // When assembling out-param helper functions, we copy variables into local clones with matching + // names. We never want to prepend "_in." or "_globals." when writing these variables since + // we're actually targeting the clones. + if (fIgnoreVariableReferenceModifiers) { + this->writeName(ref.variable()->mangledName()); + return; + } + + switch (ref.variable()->modifiers().fLayout.fBuiltin) { + case SK_FRAGCOLOR_BUILTIN: + this->write("_out.sk_FragColor"); + break; + case SK_FRAGCOORD_BUILTIN: + this->writeFragCoord(); + break; + case SK_VERTEXID_BUILTIN: + this->write("sk_VertexID"); + break; + case SK_INSTANCEID_BUILTIN: + this->write("sk_InstanceID"); + break; + case SK_CLOCKWISE_BUILTIN: + // We'd set the front facing winding in the MTLRenderCommandEncoder to be counter + // clockwise to match Skia convention. + if (!fRTFlipName.empty()) { + this->write("(" + fRTFlipName + ".y < 0 ? _frontFacing : !_frontFacing)"); + } else { + this->write("_frontFacing"); + } + break; + default: + const Variable& var = *ref.variable(); + if (var.storage() == Variable::Storage::kGlobal) { + if (is_input(var)) { + this->write("_in."); + } else if (is_output(var)) { + this->write("_out."); + } else if (is_uniforms(var)) { + this->write("_uniforms."); + } else if (is_threadgroup(var)) { + this->write("_threadgroups."); + } else if (is_in_globals(var)) { + this->write("_globals."); + } + } + this->writeName(var.mangledName()); + } +} + +void MetalCodeGenerator::writeIndexExpression(const IndexExpression& expr) { + // Metal does not seem to handle assignment into `vec.zyx[i]` properly--it compiles, but the + // results are wrong. We rewrite the expression as `vec[uint3(2,1,0)[i]]` instead. (Filed with + // Apple as FB12055941.) + if (expr.base()->is<Swizzle>()) { + const Swizzle& swizzle = expr.base()->as<Swizzle>(); + if (swizzle.components().size() > 1) { + this->writeExpression(*swizzle.base(), Precedence::kPostfix); + this->write("[uint" + std::to_string(swizzle.components().size()) + "("); + auto separator = SkSL::String::Separator(); + for (int8_t component : swizzle.components()) { + this->write(separator()); + this->write(std::to_string(component)); + } + this->write(")["); + this->writeExpression(*expr.index(), Precedence::kTopLevel); + this->write("]]"); + return; + } + } + + this->writeExpression(*expr.base(), Precedence::kPostfix); + this->write("["); + this->writeExpression(*expr.index(), Precedence::kTopLevel); + this->write("]"); +} + +void MetalCodeGenerator::writeFieldAccess(const FieldAccess& f) { + const Type::Field* field = &f.base()->type().fields()[f.fieldIndex()]; + if (FieldAccess::OwnerKind::kDefault == f.ownerKind()) { + this->writeExpression(*f.base(), Precedence::kPostfix); + this->write("."); + } + switch (field->fModifiers.fLayout.fBuiltin) { + case SK_POSITION_BUILTIN: + this->write("_out.sk_Position"); + break; + case SK_POINTSIZE_BUILTIN: + this->write("_out.sk_PointSize"); + break; + default: + if (FieldAccess::OwnerKind::kAnonymousInterfaceBlock == f.ownerKind()) { + this->write("_globals."); + this->write(fInterfaceBlockNameMap[fInterfaceBlockMap[field]]); + this->write("->"); + } + this->writeName(field->fName); + } +} + +void MetalCodeGenerator::writeSwizzle(const Swizzle& swizzle) { + this->writeExpression(*swizzle.base(), Precedence::kPostfix); + this->write("."); + for (int c : swizzle.components()) { + SkASSERT(c >= 0 && c <= 3); + this->write(&("x\0y\0z\0w\0"[c * 2])); + } +} + +void MetalCodeGenerator::writeMatrixTimesEqualHelper(const Type& left, const Type& right, + const Type& result) { + SkASSERT(left.isMatrix()); + SkASSERT(right.isMatrix()); + SkASSERT(result.isMatrix()); + + std::string key = "Matrix *= " + this->typeName(left) + ":" + this->typeName(right); + + if (!fHelpers.contains(key)) { + fHelpers.add(key); + fExtraFunctions.printf("thread %s& operator*=(thread %s& left, thread const %s& right) {\n" + " left = left * right;\n" + " return left;\n" + "}\n", + this->typeName(result).c_str(), this->typeName(left).c_str(), + this->typeName(right).c_str()); + } +} + +void MetalCodeGenerator::writeMatrixEqualityHelpers(const Type& left, const Type& right) { + SkASSERT(left.isMatrix()); + SkASSERT(right.isMatrix()); + SkASSERT(left.rows() == right.rows()); + SkASSERT(left.columns() == right.columns()); + + std::string key = "Matrix == " + this->typeName(left) + ":" + this->typeName(right); + + if (!fHelpers.contains(key)) { + fHelpers.add(key); + fExtraFunctionPrototypes.printf(R"( +thread bool operator==(const %s left, const %s right); +thread bool operator!=(const %s left, const %s right); +)", + this->typeName(left).c_str(), + this->typeName(right).c_str(), + this->typeName(left).c_str(), + this->typeName(right).c_str()); + + fExtraFunctions.printf( + "thread bool operator==(const %s left, const %s right) {\n" + " return ", + this->typeName(left).c_str(), this->typeName(right).c_str()); + + const char* separator = ""; + for (int index=0; index<left.columns(); ++index) { + fExtraFunctions.printf("%sall(left[%d] == right[%d])", separator, index, index); + separator = " &&\n "; + } + + fExtraFunctions.printf( + ";\n" + "}\n" + "thread bool operator!=(const %s left, const %s right) {\n" + " return !(left == right);\n" + "}\n", + this->typeName(left).c_str(), this->typeName(right).c_str()); + } +} + +void MetalCodeGenerator::writeMatrixDivisionHelpers(const Type& type) { + SkASSERT(type.isMatrix()); + + std::string key = "Matrix / " + this->typeName(type); + + if (!fHelpers.contains(key)) { + fHelpers.add(key); + std::string typeName = this->typeName(type); + + fExtraFunctions.printf( + "thread %s operator/(const %s left, const %s right) {\n" + " return %s(", + typeName.c_str(), typeName.c_str(), typeName.c_str(), typeName.c_str()); + + const char* separator = ""; + for (int index=0; index<type.columns(); ++index) { + fExtraFunctions.printf("%sleft[%d] / right[%d]", separator, index, index); + separator = ", "; + } + + fExtraFunctions.printf(");\n" + "}\n" + "thread %s& operator/=(thread %s& left, thread const %s& right) {\n" + " left = left / right;\n" + " return left;\n" + "}\n", + typeName.c_str(), typeName.c_str(), typeName.c_str()); + } +} + +void MetalCodeGenerator::writeArrayEqualityHelpers(const Type& type) { + SkASSERT(type.isArray()); + + // If the array's component type needs a helper as well, we need to emit that one first. + this->writeEqualityHelpers(type.componentType(), type.componentType()); + + std::string key = "ArrayEquality []"; + if (!fHelpers.contains(key)) { + fHelpers.add(key); + fExtraFunctionPrototypes.writeText(R"( +template <typename T1, typename T2> +bool operator==(const array_ref<T1> left, const array_ref<T2> right); +template <typename T1, typename T2> +bool operator!=(const array_ref<T1> left, const array_ref<T2> right); +)"); + fExtraFunctions.writeText(R"( +template <typename T1, typename T2> +bool operator==(const array_ref<T1> left, const array_ref<T2> right) { + if (left.size() != right.size()) { + return false; + } + for (size_t index = 0; index < left.size(); ++index) { + if (!all(left[index] == right[index])) { + return false; + } + } + return true; +} + +template <typename T1, typename T2> +bool operator!=(const array_ref<T1> left, const array_ref<T2> right) { + return !(left == right); +} +)"); + } +} + +void MetalCodeGenerator::writeStructEqualityHelpers(const Type& type) { + SkASSERT(type.isStruct()); + std::string key = "StructEquality " + this->typeName(type); + + if (!fHelpers.contains(key)) { + fHelpers.add(key); + // If one of the struct's fields needs a helper as well, we need to emit that one first. + for (const Type::Field& field : type.fields()) { + this->writeEqualityHelpers(*field.fType, *field.fType); + } + + // Write operator== and operator!= for this struct, since those are assumed to exist in SkSL + // and GLSL but do not exist by default in Metal. + fExtraFunctionPrototypes.printf(R"( +thread bool operator==(thread const %s& left, thread const %s& right); +thread bool operator!=(thread const %s& left, thread const %s& right); +)", + this->typeName(type).c_str(), + this->typeName(type).c_str(), + this->typeName(type).c_str(), + this->typeName(type).c_str()); + + fExtraFunctions.printf( + "thread bool operator==(thread const %s& left, thread const %s& right) {\n" + " return ", + this->typeName(type).c_str(), + this->typeName(type).c_str()); + + const char* separator = ""; + for (const Type::Field& field : type.fields()) { + if (field.fType->isArray()) { + fExtraFunctions.printf( + "%s(make_array_ref(left.%.*s) == make_array_ref(right.%.*s))", + separator, + (int)field.fName.size(), field.fName.data(), + (int)field.fName.size(), field.fName.data()); + } else { + fExtraFunctions.printf("%sall(left.%.*s == right.%.*s)", + separator, + (int)field.fName.size(), field.fName.data(), + (int)field.fName.size(), field.fName.data()); + } + separator = " &&\n "; + } + fExtraFunctions.printf( + ";\n" + "}\n" + "thread bool operator!=(thread const %s& left, thread const %s& right) {\n" + " return !(left == right);\n" + "}\n", + this->typeName(type).c_str(), + this->typeName(type).c_str()); + } +} + +void MetalCodeGenerator::writeEqualityHelpers(const Type& leftType, const Type& rightType) { + if (leftType.isArray() && rightType.isArray()) { + this->writeArrayEqualityHelpers(leftType); + return; + } + if (leftType.isStruct() && rightType.isStruct()) { + this->writeStructEqualityHelpers(leftType); + return; + } + if (leftType.isMatrix() && rightType.isMatrix()) { + this->writeMatrixEqualityHelpers(leftType, rightType); + return; + } +} + +void MetalCodeGenerator::writeNumberAsMatrix(const Expression& expr, const Type& matrixType) { + SkASSERT(expr.type().isNumber()); + SkASSERT(matrixType.isMatrix()); + + // Componentwise multiply the scalar against a matrix of the desired size which contains all 1s. + this->write("("); + this->writeType(matrixType); + this->write("("); + + const char* separator = ""; + for (int index = matrixType.slotCount(); index--;) { + this->write(separator); + this->write("1.0"); + separator = ", "; + } + + this->write(") * "); + this->writeExpression(expr, Precedence::kMultiplicative); + this->write(")"); +} + +void MetalCodeGenerator::writeBinaryExpressionElement(const Expression& expr, + Operator op, + const Expression& other, + Precedence precedence) { + bool needMatrixSplatOnScalar = other.type().isMatrix() && expr.type().isNumber() && + op.isValidForMatrixOrVector() && + op.removeAssignment().kind() != Operator::Kind::STAR; + if (needMatrixSplatOnScalar) { + this->writeNumberAsMatrix(expr, other.type()); + } else if (op.isEquality() && expr.type().isArray()) { + this->write("make_array_ref("); + this->writeExpression(expr, precedence); + this->write(")"); + } else { + this->writeExpression(expr, precedence); + } +} + +void MetalCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + const Expression& left = *b.left(); + const Expression& right = *b.right(); + const Type& leftType = left.type(); + const Type& rightType = right.type(); + Operator op = b.getOperator(); + Precedence precedence = op.getBinaryPrecedence(); + bool needParens = precedence >= parentPrecedence; + switch (op.kind()) { + case Operator::Kind::EQEQ: + this->writeEqualityHelpers(leftType, rightType); + if (leftType.isVector()) { + this->write("all"); + needParens = true; + } + break; + case Operator::Kind::NEQ: + this->writeEqualityHelpers(leftType, rightType); + if (leftType.isVector()) { + this->write("any"); + needParens = true; + } + break; + default: + break; + } + if (leftType.isMatrix() && rightType.isMatrix() && op.kind() == Operator::Kind::STAREQ) { + this->writeMatrixTimesEqualHelper(leftType, rightType, b.type()); + } + if (op.removeAssignment().kind() == Operator::Kind::SLASH && + ((leftType.isMatrix() && rightType.isMatrix()) || + (leftType.isScalar() && rightType.isMatrix()) || + (leftType.isMatrix() && rightType.isScalar()))) { + this->writeMatrixDivisionHelpers(leftType.isMatrix() ? leftType : rightType); + } + + if (needParens) { + this->write("("); + } + + this->writeBinaryExpressionElement(left, op, right, precedence); + + if (op.kind() != Operator::Kind::EQ && op.isAssignment() && + left.kind() == Expression::Kind::kSwizzle && !Analysis::HasSideEffects(left)) { + // This doesn't compile in Metal: + // float4 x = float4(1); + // x.xy *= float2x2(...); + // with the error message "non-const reference cannot bind to vector element", + // but switching it to x.xy = x.xy * float2x2(...) fixes it. We perform this tranformation + // as long as the LHS has no side effects, and hope for the best otherwise. + this->write(" = "); + this->writeExpression(left, Precedence::kAssignment); + this->write(operator_name(op.removeAssignment())); + precedence = op.removeAssignment().getBinaryPrecedence(); + } else { + this->write(operator_name(op)); + } + + this->writeBinaryExpressionElement(right, op, left, precedence); + + if (needParens) { + this->write(")"); + } +} + +void MetalCodeGenerator::writeTernaryExpression(const TernaryExpression& t, + Precedence parentPrecedence) { + if (Precedence::kTernary >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*t.test(), Precedence::kTernary); + this->write(" ? "); + this->writeExpression(*t.ifTrue(), Precedence::kTernary); + this->write(" : "); + this->writeExpression(*t.ifFalse(), Precedence::kTernary); + if (Precedence::kTernary >= parentPrecedence) { + this->write(")"); + } +} + +void MetalCodeGenerator::writePrefixExpression(const PrefixExpression& p, + Precedence parentPrecedence) { + // According to the MSL specification, the arithmetic unary operators (+ and –) do not act + // upon matrix type operands. We treat the unary "+" as NOP for all operands. + const Operator op = p.getOperator(); + if (op.kind() == Operator::Kind::PLUS) { + return this->writeExpression(*p.operand(), Precedence::kPrefix); + } + + const bool matrixNegation = + op.kind() == Operator::Kind::MINUS && p.operand()->type().isMatrix(); + const bool needParens = Precedence::kPrefix >= parentPrecedence || matrixNegation; + + if (needParens) { + this->write("("); + } + + // Transform the unary "-" on a matrix type to a multiplication by -1. + if (matrixNegation) { + this->write("-1.0 * "); + } else { + this->write(p.getOperator().tightOperatorName()); + } + this->writeExpression(*p.operand(), Precedence::kPrefix); + + if (needParens) { + this->write(")"); + } +} + +void MetalCodeGenerator::writePostfixExpression(const PostfixExpression& p, + Precedence parentPrecedence) { + if (Precedence::kPostfix >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*p.operand(), Precedence::kPostfix); + this->write(p.getOperator().tightOperatorName()); + if (Precedence::kPostfix >= parentPrecedence) { + this->write(")"); + } +} + +void MetalCodeGenerator::writeLiteral(const Literal& l) { + const Type& type = l.type(); + if (type.isFloat()) { + this->write(l.description(OperatorPrecedence::kTopLevel)); + if (!l.type().highPrecision()) { + this->write("h"); + } + return; + } + if (type.isInteger()) { + if (type.matches(*fContext.fTypes.fUInt)) { + this->write(std::to_string(l.intValue() & 0xffffffff)); + this->write("u"); + } else if (type.matches(*fContext.fTypes.fUShort)) { + this->write(std::to_string(l.intValue() & 0xffff)); + this->write("u"); + } else { + this->write(std::to_string(l.intValue())); + } + return; + } + SkASSERT(type.isBoolean()); + this->write(l.description(OperatorPrecedence::kTopLevel)); +} + +void MetalCodeGenerator::writeFunctionRequirementArgs(const FunctionDeclaration& f, + const char*& separator) { + Requirements requirements = this->requirements(f); + if (requirements & kInputs_Requirement) { + this->write(separator); + this->write("_in"); + separator = ", "; + } + if (requirements & kOutputs_Requirement) { + this->write(separator); + this->write("_out"); + separator = ", "; + } + if (requirements & kUniforms_Requirement) { + this->write(separator); + this->write("_uniforms"); + separator = ", "; + } + if (requirements & kGlobals_Requirement) { + this->write(separator); + this->write("_globals"); + separator = ", "; + } + if (requirements & kFragCoord_Requirement) { + this->write(separator); + this->write("_fragCoord"); + separator = ", "; + } + if (requirements & kThreadgroups_Requirement) { + this->write(separator); + this->write("_threadgroups"); + separator = ", "; + } +} + +void MetalCodeGenerator::writeFunctionRequirementParams(const FunctionDeclaration& f, + const char*& separator) { + Requirements requirements = this->requirements(f); + if (requirements & kInputs_Requirement) { + this->write(separator); + this->write("Inputs _in"); + separator = ", "; + } + if (requirements & kOutputs_Requirement) { + this->write(separator); + this->write("thread Outputs& _out"); + separator = ", "; + } + if (requirements & kUniforms_Requirement) { + this->write(separator); + this->write("Uniforms _uniforms"); + separator = ", "; + } + if (requirements & kGlobals_Requirement) { + this->write(separator); + this->write("thread Globals& _globals"); + separator = ", "; + } + if (requirements & kFragCoord_Requirement) { + this->write(separator); + this->write("float4 _fragCoord"); + separator = ", "; + } + if (requirements & kThreadgroups_Requirement) { + this->write(separator); + this->write("threadgroup Threadgroups& _threadgroups"); + separator = ", "; + } +} + +int MetalCodeGenerator::getUniformBinding(const Modifiers& m) { + return (m.fLayout.fBinding >= 0) ? m.fLayout.fBinding + : fProgram.fConfig->fSettings.fDefaultUniformBinding; +} + +int MetalCodeGenerator::getUniformSet(const Modifiers& m) { + return (m.fLayout.fSet >= 0) ? m.fLayout.fSet + : fProgram.fConfig->fSettings.fDefaultUniformSet; +} + +bool MetalCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) { + fRTFlipName = fProgram.fInputs.fUseFlipRTUniform + ? "_globals._anonInterface0->" SKSL_RTFLIP_NAME + : ""; + const char* separator = ""; + if (f.isMain()) { + if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + this->write("fragment Outputs fragmentMain"); + } else if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + this->write("vertex Outputs vertexMain"); + } else if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) { + this->write("kernel void computeMain"); + } else { + fContext.fErrors->error(Position(), "unsupported kind of program"); + return false; + } + this->write("("); + if (!ProgramConfig::IsCompute(fProgram.fConfig->fKind)) { + this->write("Inputs _in [[stage_in]]"); + separator = ", "; + } + if (-1 != fUniformBuffer) { + this->write(separator); + this->write("constant Uniforms& _uniforms [[buffer(" + + std::to_string(fUniformBuffer) + ")]]"); + separator = ", "; + } + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>(); + const VarDeclaration& decl = decls.varDeclaration(); + const Variable* var = decl.var(); + const SkSL::Type::TypeKind varKind = var->type().typeKind(); + + if (varKind == Type::TypeKind::kSampler || varKind == Type::TypeKind::kTexture) { + if (var->type().dimensions() != SpvDim2D) { + // Not yet implemented--Skia currently only uses 2D textures. + fContext.fErrors->error(decls.fPosition, "Unsupported texture dimensions"); + return false; + } + + int binding = getUniformBinding(var->modifiers()); + this->write(separator); + separator = ", "; + + if (varKind == Type::TypeKind::kSampler) { + this->writeType(var->type().textureType()); + this->write(" "); + this->writeName(var->mangledName()); + this->write(kTextureSuffix); + this->write(" [[texture("); + this->write(std::to_string(binding)); + this->write(")]], sampler "); + this->writeName(var->mangledName()); + this->write(kSamplerSuffix); + this->write(" [[sampler("); + this->write(std::to_string(binding)); + this->write(")]]"); + } else { + SkASSERT(varKind == Type::TypeKind::kTexture); + this->writeType(var->type()); + this->write(" "); + this->writeName(var->mangledName()); + this->write(" [[texture("); + this->write(std::to_string(binding)); + this->write(")]]"); + } + } else if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) { + std::string type, attr; + switch (var->modifiers().fLayout.fBuiltin) { + case SK_NUMWORKGROUPS_BUILTIN: + type = "uint3 "; + attr = " [[threadgroups_per_grid]]"; + break; + case SK_WORKGROUPID_BUILTIN: + type = "uint3 "; + attr = " [[threadgroup_position_in_grid]]"; + break; + case SK_LOCALINVOCATIONID_BUILTIN: + type = "uint3 "; + attr = " [[thread_position_in_threadgroup]]"; + break; + case SK_GLOBALINVOCATIONID_BUILTIN: + type = "uint3 "; + attr = " [[thread_position_in_grid]]"; + break; + case SK_LOCALINVOCATIONINDEX_BUILTIN: + type = "uint "; + attr = " [[thread_index_in_threadgroup]]"; + break; + default: + break; + } + if (!attr.empty()) { + this->write(separator); + this->write(type); + this->write(var->name()); + this->write(attr); + separator = ", "; + } + } + } else if (e->is<InterfaceBlock>()) { + const InterfaceBlock& intf = e->as<InterfaceBlock>(); + if (intf.typeName() == "sk_PerVertex") { + continue; + } + this->write(separator); + if (is_readonly(intf)) { + this->write("const "); + } + this->write(is_buffer(intf) ? "device " : "constant "); + this->writeType(intf.var()->type()); + this->write("& " ); + this->write(fInterfaceBlockNameMap[&intf]); + this->write(" [[buffer("); + this->write(std::to_string(this->getUniformBinding(intf.var()->modifiers()))); + this->write(")]]"); + separator = ", "; + } + } + if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + if (fProgram.fInputs.fUseFlipRTUniform && fInterfaceBlockNameMap.empty()) { + this->write(separator); + this->write("constant sksl_synthetic_uniforms& _anonInterface0 [[buffer(1)]]"); + fRTFlipName = "_anonInterface0." SKSL_RTFLIP_NAME; + separator = ", "; + } + this->write(separator); + this->write("bool _frontFacing [[front_facing]]"); + this->write(", float4 _fragCoord [[position]]"); + separator = ", "; + } else if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + this->write(separator); + this->write("uint sk_VertexID [[vertex_id]], uint sk_InstanceID [[instance_id]]"); + separator = ", "; + } + } else { + this->writeType(f.returnType()); + this->write(" "); + this->writeName(f.mangledName()); + this->write("("); + this->writeFunctionRequirementParams(f, separator); + } + for (const Variable* param : f.parameters()) { + if (f.isMain() && param->modifiers().fLayout.fBuiltin != -1) { + continue; + } + this->write(separator); + separator = ", "; + this->writeModifiers(param->modifiers()); + this->writeType(param->type()); + if (pass_by_reference(param->type(), param->modifiers())) { + this->write("&"); + } + this->write(" "); + this->writeName(param->mangledName()); + } + this->write(")"); + return true; +} + +void MetalCodeGenerator::writeFunctionPrototype(const FunctionPrototype& f) { + this->writeFunctionDeclaration(f.declaration()); + this->writeLine(";"); +} + +static bool is_block_ending_with_return(const Statement* stmt) { + // This function detects (potentially nested) blocks that end in a return statement. + if (!stmt->is<Block>()) { + return false; + } + const StatementArray& block = stmt->as<Block>().children(); + for (int index = block.size(); index--; ) { + stmt = block[index].get(); + if (stmt->is<ReturnStatement>()) { + return true; + } + if (stmt->is<Block>()) { + return is_block_ending_with_return(stmt); + } + if (!stmt->is<Nop>()) { + break; + } + } + return false; +} + +void MetalCodeGenerator::writeComputeMainInputs() { + // Compute shaders only have input variables (e.g. sk_GlobalInvocationID) and access program + // inputs/outputs via the Globals and Uniforms structs. We collect the allowed "in" parameters + // into an Input struct here, since the rest of the code expects the normal _in / _out pattern. + this->write("Inputs _in = { "); + const char* separator = ""; + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>(); + const Variable* var = decls.varDeclaration().var(); + if (is_input(*var)) { + this->write(separator); + separator = ", "; + this->writeName(var->mangledName()); + } + } + } + this->writeLine(" };"); +} + +void MetalCodeGenerator::writeFunction(const FunctionDefinition& f) { + SkASSERT(!fProgram.fConfig->fSettings.fFragColorIsInOut); + + if (!this->writeFunctionDeclaration(f.declaration())) { + return; + } + + fCurrentFunction = &f.declaration(); + SkScopeExit clearCurrentFunction([&] { fCurrentFunction = nullptr; }); + + this->writeLine(" {"); + + if (f.declaration().isMain()) { + fIndentation++; + this->writeGlobalInit(); + if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) { + this->writeThreadgroupInit(); + this->writeComputeMainInputs(); + } + else { + this->writeLine("Outputs _out;"); + this->writeLine("(void)_out;"); + } + fIndentation--; + } + + fFunctionHeader.clear(); + StringStream buffer; + { + AutoOutputStream outputToBuffer(this, &buffer); + fIndentation++; + for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) { + if (!stmt->isEmpty()) { + this->writeStatement(*stmt); + this->finishLine(); + } + } + if (f.declaration().isMain()) { + // If the main function doesn't end with a return, we need to synthesize one here. + if (!is_block_ending_with_return(f.body().get())) { + this->writeReturnStatementFromMain(); + this->finishLine(); + } + } + fIndentation--; + this->writeLine("}"); + } + this->write(fFunctionHeader); + this->write(buffer.str()); +} + +void MetalCodeGenerator::writeModifiers(const Modifiers& modifiers) { + if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) && + (modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag))) { + this->write("device "); + } else if (modifiers.fFlags & Modifiers::kOut_Flag) { + this->write("thread "); + } + if (modifiers.fFlags & Modifiers::kConst_Flag) { + this->write("const "); + } +} + +void MetalCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf) { + if (intf.typeName() == "sk_PerVertex") { + return; + } + const Type* structType = &intf.var()->type().componentType(); + this->writeModifiers(intf.var()->modifiers()); + this->write("struct "); + this->writeType(*structType); + this->writeLine(" {"); + fIndentation++; + this->writeFields(structType->fields(), structType->fPosition, &intf); + if (fProgram.fInputs.fUseFlipRTUniform) { + this->writeLine("float2 " SKSL_RTFLIP_NAME ";"); + } + fIndentation--; + this->write("}"); + if (intf.instanceName().size()) { + this->write(" "); + this->write(intf.instanceName()); + if (intf.arraySize() > 0) { + this->write("["); + this->write(std::to_string(intf.arraySize())); + this->write("]"); + } + fInterfaceBlockNameMap.set(&intf, intf.instanceName()); + } else { + fInterfaceBlockNameMap.set(&intf, *fProgram.fSymbols->takeOwnershipOfString( + "_anonInterface" + std::to_string(fAnonInterfaceCount++))); + } + this->writeLine(";"); +} + +void MetalCodeGenerator::writeFields(const std::vector<Type::Field>& fields, Position parentPos, + const InterfaceBlock* parentIntf) { + MemoryLayout memoryLayout(MemoryLayout::Standard::kMetal); + int currentOffset = 0; + for (const Type::Field& field : fields) { + int fieldOffset = field.fModifiers.fLayout.fOffset; + const Type* fieldType = field.fType; + if (!memoryLayout.isSupported(*fieldType)) { + fContext.fErrors->error(parentPos, "type '" + std::string(fieldType->name()) + + "' is not permitted here"); + return; + } + if (fieldOffset != -1) { + if (currentOffset > fieldOffset) { + fContext.fErrors->error(field.fPosition, + "offset of field '" + std::string(field.fName) + + "' must be at least " + std::to_string(currentOffset)); + return; + } else if (currentOffset < fieldOffset) { + this->write("char pad"); + this->write(std::to_string(fPaddingCount++)); + this->write("["); + this->write(std::to_string(fieldOffset - currentOffset)); + this->writeLine("];"); + currentOffset = fieldOffset; + } + int alignment = memoryLayout.alignment(*fieldType); + if (fieldOffset % alignment) { + fContext.fErrors->error(field.fPosition, + "offset of field '" + std::string(field.fName) + + "' must be a multiple of " + std::to_string(alignment)); + return; + } + } + if (fieldType->isUnsizedArray()) { + // An unsized array always appears as the last member of a storage block. We declare + // it as a one-element array and allow dereferencing past the capacity. + // TODO(armansito): This is because C++ does not support flexible array members like C99 + // does. This generally works but it can lead to UB as compilers are free to insert + // padding past the first element of the array. An alternative approach is to declare + // the struct without the unsized array member and replace variable references with a + // buffer offset calculation based on sizeof(). + this->writeModifiers(field.fModifiers); + this->writeType(fieldType->componentType()); + this->write(" "); + this->writeName(field.fName); + this->write("[1]"); + } else { + size_t fieldSize = memoryLayout.size(*fieldType); + if (fieldSize > static_cast<size_t>(std::numeric_limits<int>::max() - currentOffset)) { + fContext.fErrors->error(parentPos, "field offset overflow"); + return; + } + currentOffset += fieldSize; + this->writeModifiers(field.fModifiers); + this->writeType(*fieldType); + this->write(" "); + this->writeName(field.fName); + } + this->writeLine(";"); + if (parentIntf) { + fInterfaceBlockMap.set(&field, parentIntf); + } + } +} + +void MetalCodeGenerator::writeVarInitializer(const Variable& var, const Expression& value) { + this->writeExpression(value, Precedence::kTopLevel); +} + +void MetalCodeGenerator::writeName(std::string_view name) { + if (fReservedWords.contains(name)) { + this->write("_"); // adding underscore before name to avoid conflict with reserved words + } + this->write(name); +} + +void MetalCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl) { + this->writeModifiers(varDecl.var()->modifiers()); + this->writeType(varDecl.var()->type()); + this->write(" "); + this->writeName(varDecl.var()->mangledName()); + if (varDecl.value()) { + this->write(" = "); + this->writeVarInitializer(*varDecl.var(), *varDecl.value()); + } + this->write(";"); +} + +void MetalCodeGenerator::writeStatement(const Statement& s) { + switch (s.kind()) { + case Statement::Kind::kBlock: + this->writeBlock(s.as<Block>()); + break; + case Statement::Kind::kExpression: + this->writeExpressionStatement(s.as<ExpressionStatement>()); + break; + case Statement::Kind::kReturn: + this->writeReturnStatement(s.as<ReturnStatement>()); + break; + case Statement::Kind::kVarDeclaration: + this->writeVarDeclaration(s.as<VarDeclaration>()); + break; + case Statement::Kind::kIf: + this->writeIfStatement(s.as<IfStatement>()); + break; + case Statement::Kind::kFor: + this->writeForStatement(s.as<ForStatement>()); + break; + case Statement::Kind::kDo: + this->writeDoStatement(s.as<DoStatement>()); + break; + case Statement::Kind::kSwitch: + this->writeSwitchStatement(s.as<SwitchStatement>()); + break; + case Statement::Kind::kBreak: + this->write("break;"); + break; + case Statement::Kind::kContinue: + this->write("continue;"); + break; + case Statement::Kind::kDiscard: + this->write("discard_fragment();"); + break; + case Statement::Kind::kNop: + this->write(";"); + break; + default: + SkDEBUGFAILF("unsupported statement: %s", s.description().c_str()); + break; + } +} + +void MetalCodeGenerator::writeBlock(const Block& b) { + // Write scope markers if this block is a scope, or if the block is empty (since we need to emit + // something here to make the code valid). + bool isScope = b.isScope() || b.isEmpty(); + if (isScope) { + this->writeLine("{"); + fIndentation++; + } + for (const std::unique_ptr<Statement>& stmt : b.children()) { + if (!stmt->isEmpty()) { + this->writeStatement(*stmt); + this->finishLine(); + } + } + if (isScope) { + fIndentation--; + this->write("}"); + } +} + +void MetalCodeGenerator::writeIfStatement(const IfStatement& stmt) { + this->write("if ("); + this->writeExpression(*stmt.test(), Precedence::kTopLevel); + this->write(") "); + this->writeStatement(*stmt.ifTrue()); + if (stmt.ifFalse()) { + this->write(" else "); + this->writeStatement(*stmt.ifFalse()); + } +} + +void MetalCodeGenerator::writeForStatement(const ForStatement& f) { + // Emit loops of the form 'for(;test;)' as 'while(test)', which is probably how they started + if (!f.initializer() && f.test() && !f.next()) { + this->write("while ("); + this->writeExpression(*f.test(), Precedence::kTopLevel); + this->write(") "); + this->writeStatement(*f.statement()); + return; + } + + this->write("for ("); + if (f.initializer() && !f.initializer()->isEmpty()) { + this->writeStatement(*f.initializer()); + } else { + this->write("; "); + } + if (f.test()) { + this->writeExpression(*f.test(), Precedence::kTopLevel); + } + this->write("; "); + if (f.next()) { + this->writeExpression(*f.next(), Precedence::kTopLevel); + } + this->write(") "); + this->writeStatement(*f.statement()); +} + +void MetalCodeGenerator::writeDoStatement(const DoStatement& d) { + this->write("do "); + this->writeStatement(*d.statement()); + this->write(" while ("); + this->writeExpression(*d.test(), Precedence::kTopLevel); + this->write(");"); +} + +void MetalCodeGenerator::writeExpressionStatement(const ExpressionStatement& s) { + if (fProgram.fConfig->fSettings.fOptimize && !Analysis::HasSideEffects(*s.expression())) { + // Don't emit dead expressions. + return; + } + this->writeExpression(*s.expression(), Precedence::kTopLevel); + this->write(";"); +} + +void MetalCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { + this->write("switch ("); + this->writeExpression(*s.value(), Precedence::kTopLevel); + this->writeLine(") {"); + fIndentation++; + for (const std::unique_ptr<Statement>& stmt : s.cases()) { + const SwitchCase& c = stmt->as<SwitchCase>(); + if (c.isDefault()) { + this->writeLine("default:"); + } else { + this->write("case "); + this->write(std::to_string(c.value())); + this->writeLine(":"); + } + if (!c.statement()->isEmpty()) { + fIndentation++; + this->writeStatement(*c.statement()); + this->finishLine(); + fIndentation--; + } + } + fIndentation--; + this->write("}"); +} + +void MetalCodeGenerator::writeReturnStatementFromMain() { + // main functions in Metal return a magic _out parameter that doesn't exist in SkSL. + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) || + ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + this->write("return _out;"); + } else if (ProgramConfig::IsCompute(fProgram.fConfig->fKind)) { + this->write("return;"); + } else { + SkDEBUGFAIL("unsupported kind of program"); + } +} + +void MetalCodeGenerator::writeReturnStatement(const ReturnStatement& r) { + if (fCurrentFunction && fCurrentFunction->isMain()) { + if (r.expression()) { + if (r.expression()->type().matches(*fContext.fTypes.fHalf4)) { + this->write("_out.sk_FragColor = "); + this->writeExpression(*r.expression(), Precedence::kTopLevel); + this->writeLine(";"); + } else { + fContext.fErrors->error(r.fPosition, + "Metal does not support returning '" + + r.expression()->type().description() + "' from main()"); + } + } + this->writeReturnStatementFromMain(); + return; + } + + this->write("return"); + if (r.expression()) { + this->write(" "); + this->writeExpression(*r.expression(), Precedence::kTopLevel); + } + this->write(";"); +} + +void MetalCodeGenerator::writeHeader() { + this->write("#include <metal_stdlib>\n"); + this->write("#include <simd/simd.h>\n"); + this->write("using namespace metal;\n"); +} + +void MetalCodeGenerator::writeSampler2DPolyfill() { + class : public GlobalStructVisitor { + public: + void visitSampler(const Type&, std::string_view) override { + if (fWrotePolyfill) { + return; + } + fWrotePolyfill = true; + + std::string polyfill = SkSL::String::printf(R"( +struct sampler2D { + texture2d<half> tex; + sampler smp; +}; +half4 sample(sampler2D i, float2 p, float b=%g) { return i.tex.sample(i.smp, p, bias(b)); } +half4 sample(sampler2D i, float3 p, float b=%g) { return i.tex.sample(i.smp, p.xy / p.z, bias(b)); } +half4 sampleLod(sampler2D i, float2 p, float lod) { return i.tex.sample(i.smp, p, level(lod)); } +half4 sampleLod(sampler2D i, float3 p, float lod) { + return i.tex.sample(i.smp, p.xy / p.z, level(lod)); +} +half4 sampleGrad(sampler2D i, float2 p, float2 dPdx, float2 dPdy) { + return i.tex.sample(i.smp, p, gradient2d(dPdx, dPdy)); +} + +)", + fTextureBias, + fTextureBias); + fCodeGen->write(polyfill.c_str()); + } + + MetalCodeGenerator* fCodeGen = nullptr; + float fTextureBias = 0.0f; + bool fWrotePolyfill = false; + } visitor; + + visitor.fCodeGen = this; + visitor.fTextureBias = fProgram.fConfig->fSettings.fSharpenTextures ? kSharpenTexturesBias + : 0.0f; + this->visitGlobalStruct(&visitor); +} + +void MetalCodeGenerator::writeUniformStruct() { + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>(); + const Variable& var = *decls.varDeclaration().var(); + if (var.modifiers().fFlags & Modifiers::kUniform_Flag && + var.type().typeKind() != Type::TypeKind::kSampler && + var.type().typeKind() != Type::TypeKind::kTexture) { + int uniformSet = this->getUniformSet(var.modifiers()); + // Make sure that the program's uniform-set value is consistent throughout. + if (-1 == fUniformBuffer) { + this->write("struct Uniforms {\n"); + fUniformBuffer = uniformSet; + } else if (uniformSet != fUniformBuffer) { + fContext.fErrors->error(decls.fPosition, + "Metal backend requires all uniforms to have the same " + "'layout(set=...)'"); + } + this->write(" "); + this->writeType(var.type()); + this->write(" "); + this->writeName(var.mangledName()); + this->write(";\n"); + } + } + } + if (-1 != fUniformBuffer) { + this->write("};\n"); + } +} + +void MetalCodeGenerator::writeInputStruct() { + this->write("struct Inputs {\n"); + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>(); + const Variable& var = *decls.varDeclaration().var(); + if (is_input(var)) { + this->write(" "); + if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) && + needs_address_space(var.type(), var.modifiers())) { + // TODO: address space support + this->write("device "); + } + this->writeType(var.type()); + if (pass_by_reference(var.type(), var.modifiers())) { + this->write("&"); + } + this->write(" "); + this->writeName(var.mangledName()); + if (-1 != var.modifiers().fLayout.fLocation) { + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + this->write(" [[attribute(" + + std::to_string(var.modifiers().fLayout.fLocation) + ")]]"); + } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + this->write(" [[user(locn" + + std::to_string(var.modifiers().fLayout.fLocation) + ")]]"); + } + } + this->write(";\n"); + } + } + } + this->write("};\n"); +} + +void MetalCodeGenerator::writeOutputStruct() { + this->write("struct Outputs {\n"); + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + this->write(" float4 sk_Position [[position]];\n"); + } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + this->write(" half4 sk_FragColor [[color(0)]];\n"); + } + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>(); + const Variable& var = *decls.varDeclaration().var(); + if (is_output(var)) { + this->write(" "); + if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) && + needs_address_space(var.type(), var.modifiers())) { + // TODO: address space support + this->write("device "); + } + this->writeType(var.type()); + if (ProgramConfig::IsCompute(fProgram.fConfig->fKind) && + pass_by_reference(var.type(), var.modifiers())) { + this->write("&"); + } + this->write(" "); + this->writeName(var.mangledName()); + + int location = var.modifiers().fLayout.fLocation; + if (!ProgramConfig::IsCompute(fProgram.fConfig->fKind) && location < 0 && + var.type().typeKind() != Type::TypeKind::kTexture) { + fContext.fErrors->error(var.fPosition, + "Metal out variables must have 'layout(location=...)'"); + } else if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + this->write(" [[user(locn" + std::to_string(location) + ")]]"); + } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + this->write(" [[color(" + std::to_string(location) + ")"); + int colorIndex = var.modifiers().fLayout.fIndex; + if (colorIndex) { + this->write(", index(" + std::to_string(colorIndex) + ")"); + } + this->write("]]"); + } + this->write(";\n"); + } + } + } + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + this->write(" float sk_PointSize [[point_size]];\n"); + } + this->write("};\n"); +} + +void MetalCodeGenerator::writeInterfaceBlocks() { + bool wroteInterfaceBlock = false; + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<InterfaceBlock>()) { + this->writeInterfaceBlock(e->as<InterfaceBlock>()); + wroteInterfaceBlock = true; + } + } + if (!wroteInterfaceBlock && fProgram.fInputs.fUseFlipRTUniform) { + this->writeLine("struct sksl_synthetic_uniforms {"); + this->writeLine(" float2 " SKSL_RTFLIP_NAME ";"); + this->writeLine("};"); + } +} + +void MetalCodeGenerator::writeStructDefinitions() { + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<StructDefinition>()) { + this->writeStructDefinition(e->as<StructDefinition>()); + } + } +} + +void MetalCodeGenerator::writeConstantVariables() { + class : public GlobalStructVisitor { + public: + void visitConstantVariable(const VarDeclaration& decl) override { + fCodeGen->write("constant "); + fCodeGen->writeVarDeclaration(decl); + fCodeGen->finishLine(); + } + + MetalCodeGenerator* fCodeGen = nullptr; + } visitor; + + visitor.fCodeGen = this; + this->visitGlobalStruct(&visitor); +} + +void MetalCodeGenerator::visitGlobalStruct(GlobalStructVisitor* visitor) { + for (const ProgramElement* element : fProgram.elements()) { + if (element->is<InterfaceBlock>()) { + const auto* ib = &element->as<InterfaceBlock>(); + if (ib->typeName() != "sk_PerVertex") { + visitor->visitInterfaceBlock(*ib, fInterfaceBlockNameMap[ib]); + } + continue; + } + if (!element->is<GlobalVarDeclaration>()) { + continue; + } + const GlobalVarDeclaration& global = element->as<GlobalVarDeclaration>(); + const VarDeclaration& decl = global.varDeclaration(); + const Variable& var = *decl.var(); + if (var.type().typeKind() == Type::TypeKind::kSampler) { + visitor->visitSampler(var.type(), var.mangledName()); + continue; + } + if (var.type().typeKind() == Type::TypeKind::kTexture) { + visitor->visitTexture(var.type(), var.modifiers(), var.mangledName()); + continue; + } + if (!(var.modifiers().fFlags & ~Modifiers::kConst_Flag) && + var.modifiers().fLayout.fBuiltin == -1) { + if (is_in_globals(var)) { + // Visit a regular global variable. + visitor->visitNonconstantVariable(var, decl.value().get()); + } else { + // Visit a constant-expression variable. + SkASSERT(var.modifiers().fFlags & Modifiers::kConst_Flag); + visitor->visitConstantVariable(decl); + } + } + } +} + +void MetalCodeGenerator::writeGlobalStruct() { + class : public GlobalStructVisitor { + public: + void visitInterfaceBlock(const InterfaceBlock& block, + std::string_view blockName) override { + this->addElement(); + fCodeGen->write(" "); + if (is_readonly(block)) { + fCodeGen->write("const "); + } + fCodeGen->write(is_buffer(block) ? "device " : "constant "); + fCodeGen->write(block.typeName()); + fCodeGen->write("* "); + fCodeGen->writeName(blockName); + fCodeGen->write(";\n"); + } + void visitTexture(const Type& type, const Modifiers& modifiers, + std::string_view name) override { + this->addElement(); + fCodeGen->write(" "); + fCodeGen->writeType(type); + fCodeGen->write(" "); + fCodeGen->writeName(name); + fCodeGen->write(";\n"); + } + void visitSampler(const Type&, std::string_view name) override { + this->addElement(); + fCodeGen->write(" sampler2D "); + fCodeGen->writeName(name); + fCodeGen->write(";\n"); + } + void visitConstantVariable(const VarDeclaration& decl) override { + // Constants aren't added to the global struct. + } + void visitNonconstantVariable(const Variable& var, const Expression* value) override { + this->addElement(); + fCodeGen->write(" "); + fCodeGen->writeModifiers(var.modifiers()); + fCodeGen->writeType(var.type()); + fCodeGen->write(" "); + fCodeGen->writeName(var.mangledName()); + fCodeGen->write(";\n"); + } + void addElement() { + if (fFirst) { + fCodeGen->write("struct Globals {\n"); + fFirst = false; + } + } + void finish() { + if (!fFirst) { + fCodeGen->writeLine("};"); + fFirst = true; + } + } + + MetalCodeGenerator* fCodeGen = nullptr; + bool fFirst = true; + } visitor; + + visitor.fCodeGen = this; + this->visitGlobalStruct(&visitor); + visitor.finish(); +} + +void MetalCodeGenerator::writeGlobalInit() { + class : public GlobalStructVisitor { + public: + void visitInterfaceBlock(const InterfaceBlock& blockType, + std::string_view blockName) override { + this->addElement(); + fCodeGen->write("&"); + fCodeGen->writeName(blockName); + } + void visitTexture(const Type&, const Modifiers& modifiers, std::string_view name) override { + this->addElement(); + fCodeGen->writeName(name); + } + void visitSampler(const Type&, std::string_view name) override { + this->addElement(); + fCodeGen->write("{"); + fCodeGen->writeName(name); + fCodeGen->write(kTextureSuffix); + fCodeGen->write(", "); + fCodeGen->writeName(name); + fCodeGen->write(kSamplerSuffix); + fCodeGen->write("}"); + } + void visitConstantVariable(const VarDeclaration& decl) override { + // Constant-expression variables aren't put in the global struct. + } + void visitNonconstantVariable(const Variable& var, const Expression* value) override { + this->addElement(); + if (value) { + fCodeGen->writeVarInitializer(var, *value); + } else { + fCodeGen->write("{}"); + } + } + void addElement() { + if (fFirst) { + fCodeGen->write("Globals _globals{"); + fFirst = false; + } else { + fCodeGen->write(", "); + } + } + void finish() { + if (!fFirst) { + fCodeGen->writeLine("};"); + fCodeGen->writeLine("(void)_globals;"); + } + } + MetalCodeGenerator* fCodeGen = nullptr; + bool fFirst = true; + } visitor; + + visitor.fCodeGen = this; + this->visitGlobalStruct(&visitor); + visitor.finish(); +} + +void MetalCodeGenerator::visitThreadgroupStruct(ThreadgroupStructVisitor* visitor) { + for (const ProgramElement* element : fProgram.elements()) { + if (!element->is<GlobalVarDeclaration>()) { + continue; + } + const GlobalVarDeclaration& global = element->as<GlobalVarDeclaration>(); + const VarDeclaration& decl = global.varDeclaration(); + const Variable& var = *decl.var(); + if (var.modifiers().fFlags & Modifiers::kWorkgroup_Flag) { + SkASSERT(!decl.value()); + SkASSERT(!(var.modifiers().fFlags & Modifiers::kConst_Flag)); + visitor->visitNonconstantVariable(var); + } + } +} + +void MetalCodeGenerator::writeThreadgroupStruct() { + class : public ThreadgroupStructVisitor { + public: + void visitNonconstantVariable(const Variable& var) override { + this->addElement(); + fCodeGen->write(" "); + fCodeGen->writeModifiers(var.modifiers()); + fCodeGen->writeType(var.type()); + fCodeGen->write(" "); + fCodeGen->writeName(var.mangledName()); + fCodeGen->write(";\n"); + } + void addElement() { + if (fFirst) { + fCodeGen->write("struct Threadgroups {\n"); + fFirst = false; + } + } + void finish() { + if (!fFirst) { + fCodeGen->writeLine("};"); + fFirst = true; + } + } + + MetalCodeGenerator* fCodeGen = nullptr; + bool fFirst = true; + } visitor; + + visitor.fCodeGen = this; + this->visitThreadgroupStruct(&visitor); + visitor.finish(); +} + +void MetalCodeGenerator::writeThreadgroupInit() { + class : public ThreadgroupStructVisitor { + public: + void visitNonconstantVariable(const Variable& var) override { + this->addElement(); + fCodeGen->write("{}"); + } + void addElement() { + if (fFirst) { + fCodeGen->write("threadgroup Threadgroups _threadgroups{"); + fFirst = false; + } else { + fCodeGen->write(", "); + } + } + void finish() { + if (!fFirst) { + fCodeGen->writeLine("};"); + fCodeGen->writeLine("(void)_threadgroups;"); + } + } + MetalCodeGenerator* fCodeGen = nullptr; + bool fFirst = true; + } visitor; + + visitor.fCodeGen = this; + this->visitThreadgroupStruct(&visitor); + visitor.finish(); +} + +void MetalCodeGenerator::writeProgramElement(const ProgramElement& e) { + switch (e.kind()) { + case ProgramElement::Kind::kExtension: + break; + case ProgramElement::Kind::kGlobalVar: + break; + case ProgramElement::Kind::kInterfaceBlock: + // handled in writeInterfaceBlocks, do nothing + break; + case ProgramElement::Kind::kStructDefinition: + // Handled in writeStructDefinitions. Do nothing. + break; + case ProgramElement::Kind::kFunction: + this->writeFunction(e.as<FunctionDefinition>()); + break; + case ProgramElement::Kind::kFunctionPrototype: + this->writeFunctionPrototype(e.as<FunctionPrototype>()); + break; + case ProgramElement::Kind::kModifiers: + this->writeModifiers(e.as<ModifiersDeclaration>().modifiers()); + this->writeLine(";"); + break; + default: + SkDEBUGFAILF("unsupported program element: %s\n", e.description().c_str()); + break; + } +} + +MetalCodeGenerator::Requirements MetalCodeGenerator::requirements(const Statement* s) { + class RequirementsVisitor : public ProgramVisitor { + public: + using ProgramVisitor::visitStatement; + + bool visitExpression(const Expression& e) override { + switch (e.kind()) { + case Expression::Kind::kFunctionCall: { + const FunctionCall& f = e.as<FunctionCall>(); + fRequirements |= fCodeGen->requirements(f.function()); + break; + } + case Expression::Kind::kFieldAccess: { + const FieldAccess& f = e.as<FieldAccess>(); + if (f.ownerKind() == FieldAccess::OwnerKind::kAnonymousInterfaceBlock) { + fRequirements |= kGlobals_Requirement; + return false; // don't recurse into the base variable + } + break; + } + case Expression::Kind::kVariableReference: { + const Variable& var = *e.as<VariableReference>().variable(); + + if (var.modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) { + fRequirements |= kGlobals_Requirement | kFragCoord_Requirement; + } else if (var.storage() == Variable::Storage::kGlobal) { + if (is_input(var)) { + fRequirements |= kInputs_Requirement; + } else if (is_output(var)) { + fRequirements |= kOutputs_Requirement; + } else if (is_uniforms(var)) { + fRequirements |= kUniforms_Requirement; + } else if (is_threadgroup(var)) { + fRequirements |= kThreadgroups_Requirement; + } else if (is_in_globals(var)) { + fRequirements |= kGlobals_Requirement; + } + } + break; + } + default: + break; + } + return INHERITED::visitExpression(e); + } + + MetalCodeGenerator* fCodeGen; + Requirements fRequirements = kNo_Requirements; + using INHERITED = ProgramVisitor; + }; + + RequirementsVisitor visitor; + if (s) { + visitor.fCodeGen = this; + visitor.visitStatement(*s); + } + return visitor.fRequirements; +} + +MetalCodeGenerator::Requirements MetalCodeGenerator::requirements(const FunctionDeclaration& f) { + Requirements* found = fRequirements.find(&f); + if (!found) { + fRequirements.set(&f, kNo_Requirements); + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<FunctionDefinition>()) { + const FunctionDefinition& def = e->as<FunctionDefinition>(); + if (&def.declaration() == &f) { + Requirements reqs = this->requirements(def.body().get()); + fRequirements.set(&f, reqs); + return reqs; + } + } + } + // We never found a definition for this declared function, but it's legal to prototype a + // function without ever giving a definition, as long as you don't call it. + return kNo_Requirements; + } + return *found; +} + +bool MetalCodeGenerator::generateCode() { + StringStream header; + { + AutoOutputStream outputToHeader(this, &header, &fIndentation); + this->writeHeader(); + this->writeConstantVariables(); + this->writeSampler2DPolyfill(); + this->writeStructDefinitions(); + this->writeUniformStruct(); + this->writeInputStruct(); + if (!ProgramConfig::IsCompute(fProgram.fConfig->fKind)) { + this->writeOutputStruct(); + } + this->writeInterfaceBlocks(); + this->writeGlobalStruct(); + this->writeThreadgroupStruct(); + + // Emit prototypes for every built-in function; these aren't always added in perfect order. + for (const ProgramElement* e : fProgram.fSharedElements) { + if (e->is<FunctionDefinition>()) { + this->writeFunctionDeclaration(e->as<FunctionDefinition>().declaration()); + this->writeLine(";"); + } + } + } + StringStream body; + { + AutoOutputStream outputToBody(this, &body, &fIndentation); + + for (const ProgramElement* e : fProgram.elements()) { + this->writeProgramElement(*e); + } + } + write_stringstream(header, *fOut); + write_stringstream(fExtraFunctionPrototypes, *fOut); + write_stringstream(fExtraFunctions, *fOut); + write_stringstream(body, *fOut); + return fContext.fErrors->errorCount() == 0; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.h new file mode 100644 index 0000000000..621b99edb4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLMetalCodeGenerator.h @@ -0,0 +1,330 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_METALCODEGENERATOR +#define SKSL_METALCODEGENERATOR + +#include "include/private/SkSLDefines.h" +#include "include/private/base/SkTArray.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/codegen/SkSLCodeGenerator.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <initializer_list> +#include <string> +#include <string_view> +#include <vector> + +namespace SkSL { + +class AnyConstructor; +class BinaryExpression; +class Block; +class ConstructorArrayCast; +class ConstructorCompound; +class ConstructorMatrixResize; +class Context; +class DoStatement; +class Expression; +class ExpressionStatement; +class Extension; +class FieldAccess; +class ForStatement; +class FunctionCall; +class FunctionDeclaration; +class FunctionDefinition; +class FunctionPrototype; +class IfStatement; +class IndexExpression; +class InterfaceBlock; +class Literal; +class Operator; +class OutputStream; +class Position; +class PostfixExpression; +class PrefixExpression; +class ProgramElement; +class ReturnStatement; +class Statement; +class StructDefinition; +class SwitchStatement; +class Swizzle; +class TernaryExpression; +class VarDeclaration; +class Variable; +class VariableReference; +enum class OperatorPrecedence : uint8_t; +enum IntrinsicKind : int8_t; +struct Layout; +struct Modifiers; +struct Program; + +/** + * Converts a Program into Metal code. + */ +class MetalCodeGenerator : public CodeGenerator { +public: + MetalCodeGenerator(const Context* context, const Program* program, OutputStream* out) + : INHERITED(context, program, out) + , fReservedWords({"atan2", "rsqrt", "rint", "dfdx", "dfdy", "vertex", "fragment"}) + , fLineEnding("\n") {} + + bool generateCode() override; + +protected: + using Precedence = OperatorPrecedence; + + typedef int Requirements; + inline static constexpr Requirements kNo_Requirements = 0; + inline static constexpr Requirements kInputs_Requirement = 1 << 0; + inline static constexpr Requirements kOutputs_Requirement = 1 << 1; + inline static constexpr Requirements kUniforms_Requirement = 1 << 2; + inline static constexpr Requirements kGlobals_Requirement = 1 << 3; + inline static constexpr Requirements kFragCoord_Requirement = 1 << 4; + inline static constexpr Requirements kThreadgroups_Requirement = 1 << 5; + + class GlobalStructVisitor; + void visitGlobalStruct(GlobalStructVisitor* visitor); + + class ThreadgroupStructVisitor; + void visitThreadgroupStruct(ThreadgroupStructVisitor* visitor); + + void write(std::string_view s); + + void writeLine(std::string_view s = std::string_view()); + + void finishLine(); + + void writeHeader(); + + void writeSampler2DPolyfill(); + + void writeUniformStruct(); + + void writeInputStruct(); + + void writeOutputStruct(); + + void writeInterfaceBlocks(); + + void writeStructDefinitions(); + + void writeConstantVariables(); + + void writeFields(const std::vector<Type::Field>& fields, Position pos, + const InterfaceBlock* parentIntf = nullptr); + + int size(const Type* type, bool isPacked) const; + + int alignment(const Type* type, bool isPacked) const; + + void writeGlobalStruct(); + + void writeGlobalInit(); + + void writeThreadgroupStruct(); + + void writeThreadgroupInit(); + + void writePrecisionModifier(); + + std::string typeName(const Type& type); + + void writeStructDefinition(const StructDefinition& s); + + void writeType(const Type& type); + + void writeExtension(const Extension& ext); + + void writeInterfaceBlock(const InterfaceBlock& intf); + + void writeFunctionRequirementParams(const FunctionDeclaration& f, + const char*& separator); + + void writeFunctionRequirementArgs(const FunctionDeclaration& f, const char*& separator); + + bool writeFunctionDeclaration(const FunctionDeclaration& f); + + void writeFunction(const FunctionDefinition& f); + + void writeFunctionPrototype(const FunctionPrototype& f); + + void writeLayout(const Layout& layout); + + void writeModifiers(const Modifiers& modifiers); + + void writeVarInitializer(const Variable& var, const Expression& value); + + void writeName(std::string_view name); + + void writeVarDeclaration(const VarDeclaration& decl); + + void writeFragCoord(); + + void writeVariableReference(const VariableReference& ref); + + void writeExpression(const Expression& expr, Precedence parentPrecedence); + + void writeMinAbsHack(Expression& absExpr, Expression& otherExpr); + + std::string getOutParamHelper(const FunctionCall& c, + const ExpressionArray& arguments, + const SkTArray<VariableReference*>& outVars); + + std::string getInversePolyfill(const ExpressionArray& arguments); + + std::string getBitcastIntrinsic(const Type& outType); + + std::string getTempVariable(const Type& varType); + + void writeFunctionCall(const FunctionCall& c); + + bool matrixConstructHelperIsNeeded(const ConstructorCompound& c); + std::string getMatrixConstructHelper(const AnyConstructor& c); + void assembleMatrixFromMatrix(const Type& sourceMatrix, int rows, int columns); + void assembleMatrixFromExpressions(const AnyConstructor& ctor, int rows, int columns); + + void writeMatrixCompMult(); + + void writeOuterProduct(); + + void writeMatrixTimesEqualHelper(const Type& left, const Type& right, const Type& result); + + void writeMatrixDivisionHelpers(const Type& type); + + void writeMatrixEqualityHelpers(const Type& left, const Type& right); + + std::string getVectorFromMat2x2ConstructorHelper(const Type& matrixType); + + void writeArrayEqualityHelpers(const Type& type); + + void writeStructEqualityHelpers(const Type& type); + + void writeEqualityHelpers(const Type& leftType, const Type& rightType); + + void writeArgumentList(const ExpressionArray& arguments); + + void writeSimpleIntrinsic(const FunctionCall& c); + + bool writeIntrinsicCall(const FunctionCall& c, IntrinsicKind kind); + + void writeConstructorCompound(const ConstructorCompound& c, Precedence parentPrecedence); + + void writeConstructorCompoundVector(const ConstructorCompound& c, Precedence parentPrecedence); + + void writeConstructorCompoundMatrix(const ConstructorCompound& c, Precedence parentPrecedence); + + void writeConstructorMatrixResize(const ConstructorMatrixResize& c, + Precedence parentPrecedence); + + void writeAnyConstructor(const AnyConstructor& c, + const char* leftBracket, + const char* rightBracket, + Precedence parentPrecedence); + + void writeCastConstructor(const AnyConstructor& c, + const char* leftBracket, + const char* rightBracket, + Precedence parentPrecedence); + + void writeConstructorArrayCast(const ConstructorArrayCast& c, Precedence parentPrecedence); + + void writeFieldAccess(const FieldAccess& f); + + void writeSwizzle(const Swizzle& swizzle); + + // Splats a scalar expression across a matrix of arbitrary size. + void writeNumberAsMatrix(const Expression& expr, const Type& matrixType); + + void writeBinaryExpressionElement(const Expression& expr, + Operator op, + const Expression& other, + Precedence precedence); + + void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence); + + void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence); + + void writeIndexExpression(const IndexExpression& expr); + + void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence); + + void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence); + + void writeLiteral(const Literal& f); + + void writeStatement(const Statement& s); + + void writeStatements(const StatementArray& statements); + + void writeBlock(const Block& b); + + void writeIfStatement(const IfStatement& stmt); + + void writeForStatement(const ForStatement& f); + + void writeDoStatement(const DoStatement& d); + + void writeExpressionStatement(const ExpressionStatement& s); + + void writeSwitchStatement(const SwitchStatement& s); + + void writeReturnStatementFromMain(); + + void writeReturnStatement(const ReturnStatement& r); + + void writeProgramElement(const ProgramElement& e); + + Requirements requirements(const FunctionDeclaration& f); + + Requirements requirements(const Statement* s); + + // For compute shader main functions, writes and initializes the _in and _out structs (the + // instances, not the types themselves) + void writeComputeMainInputs(); + + int getUniformBinding(const Modifiers& m); + + int getUniformSet(const Modifiers& m); + + SkTHashSet<std::string_view> fReservedWords; + SkTHashMap<const Type::Field*, const InterfaceBlock*> fInterfaceBlockMap; + SkTHashMap<const InterfaceBlock*, std::string_view> fInterfaceBlockNameMap; + int fAnonInterfaceCount = 0; + int fPaddingCount = 0; + const char* fLineEnding; + std::string fFunctionHeader; + StringStream fExtraFunctions; + StringStream fExtraFunctionPrototypes; + int fVarCount = 0; + int fIndentation = 0; + bool fAtLineStart = false; + // true if we have run into usages of dFdx / dFdy + bool fFoundDerivatives = false; + SkTHashMap<const FunctionDeclaration*, Requirements> fRequirements; + SkTHashSet<std::string> fHelpers; + int fUniformBuffer = -1; + std::string fRTFlipName; + const FunctionDeclaration* fCurrentFunction = nullptr; + int fSwizzleHelperCount = 0; + bool fIgnoreVariableReferenceModifiers = false; + static constexpr char kTextureSuffix[] = "_Tex"; + static constexpr char kSamplerSuffix[] = "_Smplr"; + + // Workaround/polyfill flags + bool fWrittenInverse2 = false, fWrittenInverse3 = false, fWrittenInverse4 = false; + bool fWrittenMatrixCompMult = false; + bool fWrittenOuterProduct = false; + + using INHERITED = CodeGenerator; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp new file mode 100644 index 0000000000..20466a922d --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.cpp @@ -0,0 +1,814 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/codegen/SkSLPipelineStageCodeGenerator.h" + +#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE) + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLProgramKind.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLOperator.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLChildCall.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLStructDefinition.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <memory> +#include <string_view> +#include <utility> +#include <vector> + +namespace SkSL { +namespace PipelineStage { + +class PipelineStageCodeGenerator { +public: + PipelineStageCodeGenerator(const Program& program, + const char* sampleCoords, + const char* inputColor, + const char* destColor, + Callbacks* callbacks) + : fProgram(program) + , fSampleCoords(sampleCoords) + , fInputColor(inputColor) + , fDestColor(destColor) + , fCallbacks(callbacks) {} + + void generateCode(); + +private: + using Precedence = OperatorPrecedence; + + void write(std::string_view s); + void writeLine(std::string_view s = std::string_view()); + + std::string typeName(const Type& type); + void writeType(const Type& type); + + std::string functionName(const FunctionDeclaration& decl); + void writeFunction(const FunctionDefinition& f); + void writeFunctionDeclaration(const FunctionDeclaration& decl); + + std::string modifierString(const Modifiers& modifiers); + std::string functionDeclaration(const FunctionDeclaration& decl); + + // Handles arrays correctly, eg: `float x[2]` + std::string typedVariable(const Type& type, std::string_view name); + + void writeVarDeclaration(const VarDeclaration& var); + void writeGlobalVarDeclaration(const GlobalVarDeclaration& g); + void writeStructDefinition(const StructDefinition& s); + + void writeExpression(const Expression& expr, Precedence parentPrecedence); + void writeChildCall(const ChildCall& c); + void writeFunctionCall(const FunctionCall& c); + void writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence); + void writeFieldAccess(const FieldAccess& f); + void writeSwizzle(const Swizzle& swizzle); + void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence); + void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence); + void writeIndexExpression(const IndexExpression& expr); + void writePrefixExpression(const PrefixExpression& p, Precedence parentPrecedence); + void writePostfixExpression(const PostfixExpression& p, Precedence parentPrecedence); + void writeVariableReference(const VariableReference& ref); + + void writeStatement(const Statement& s); + void writeBlock(const Block& b); + void writeIfStatement(const IfStatement& stmt); + void writeDoStatement(const DoStatement& d); + void writeForStatement(const ForStatement& f); + void writeReturnStatement(const ReturnStatement& r); + void writeSwitchStatement(const SwitchStatement& s); + + void writeProgramElementFirstPass(const ProgramElement& e); + void writeProgramElementSecondPass(const ProgramElement& e); + + struct AutoOutputBuffer { + AutoOutputBuffer(PipelineStageCodeGenerator* generator) : fGenerator(generator) { + fOldBuffer = fGenerator->fBuffer; + fGenerator->fBuffer = &fBuffer; + } + + ~AutoOutputBuffer() { + fGenerator->fBuffer = fOldBuffer; + } + + PipelineStageCodeGenerator* fGenerator; + StringStream* fOldBuffer; + StringStream fBuffer; + }; + + const Program& fProgram; + const char* fSampleCoords; + const char* fInputColor; + const char* fDestColor; + Callbacks* fCallbacks; + + SkTHashMap<const Variable*, std::string> fVariableNames; + SkTHashMap<const FunctionDeclaration*, std::string> fFunctionNames; + SkTHashMap<const Type*, std::string> fStructNames; + + StringStream* fBuffer = nullptr; + bool fCastReturnsToHalf = false; +}; + + +void PipelineStageCodeGenerator::write(std::string_view s) { + fBuffer->write(s.data(), s.length()); +} + +void PipelineStageCodeGenerator::writeLine(std::string_view s) { + fBuffer->write(s.data(), s.length()); + fBuffer->writeText("\n"); +} + +void PipelineStageCodeGenerator::writeChildCall(const ChildCall& c) { + const ExpressionArray& arguments = c.arguments(); + SkASSERT(arguments.size() >= 1); + int index = 0; + bool found = false; + for (const ProgramElement* p : fProgram.elements()) { + if (p->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& global = p->as<GlobalVarDeclaration>(); + const VarDeclaration& decl = global.varDeclaration(); + if (decl.var() == &c.child()) { + found = true; + } else if (decl.var()->type().isEffectChild()) { + ++index; + } + } + if (found) { + break; + } + } + SkASSERT(found); + + // Shaders require a coordinate argument. Color filters require a color argument. + // Blenders require two color arguments. + std::string sampleOutput; + { + AutoOutputBuffer exprBuffer(this); + this->writeExpression(*arguments[0], Precedence::kSequence); + + switch (c.child().type().typeKind()) { + case Type::TypeKind::kShader: { + SkASSERT(arguments.size() == 1); + SkASSERT(arguments[0]->type().matches(*fProgram.fContext->fTypes.fFloat2)); + sampleOutput = fCallbacks->sampleShader(index, exprBuffer.fBuffer.str()); + break; + } + case Type::TypeKind::kColorFilter: { + SkASSERT(arguments.size() == 1); + SkASSERT(arguments[0]->type().matches(*fProgram.fContext->fTypes.fHalf4) || + arguments[0]->type().matches(*fProgram.fContext->fTypes.fFloat4)); + sampleOutput = fCallbacks->sampleColorFilter(index, exprBuffer.fBuffer.str()); + break; + } + case Type::TypeKind::kBlender: { + SkASSERT(arguments.size() == 2); + SkASSERT(arguments[0]->type().matches(*fProgram.fContext->fTypes.fHalf4) || + arguments[0]->type().matches(*fProgram.fContext->fTypes.fFloat4)); + SkASSERT(arguments[1]->type().matches(*fProgram.fContext->fTypes.fHalf4) || + arguments[1]->type().matches(*fProgram.fContext->fTypes.fFloat4)); + + AutoOutputBuffer exprBuffer2(this); + this->writeExpression(*arguments[1], Precedence::kSequence); + + sampleOutput = fCallbacks->sampleBlender(index, exprBuffer.fBuffer.str(), + exprBuffer2.fBuffer.str()); + break; + } + default: { + SkDEBUGFAILF("cannot sample from type '%s'", + c.child().type().description().c_str()); + } + } + } + this->write(sampleOutput); + return; +} + +void PipelineStageCodeGenerator::writeFunctionCall(const FunctionCall& c) { + const FunctionDeclaration& function = c.function(); + + if (function.intrinsicKind() == IntrinsicKind::k_toLinearSrgb_IntrinsicKind || + function.intrinsicKind() == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) { + SkASSERT(c.arguments().size() == 1); + std::string colorArg; + { + AutoOutputBuffer exprBuffer(this); + this->writeExpression(*c.arguments()[0], Precedence::kSequence); + colorArg = exprBuffer.fBuffer.str(); + } + + switch (function.intrinsicKind()) { + case IntrinsicKind::k_toLinearSrgb_IntrinsicKind: + this->write(fCallbacks->toLinearSrgb(std::move(colorArg))); + break; + case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind: + this->write(fCallbacks->fromLinearSrgb(std::move(colorArg))); + break; + default: + SkUNREACHABLE; + } + + return; + } + + if (function.isBuiltin()) { + this->write(function.name()); + } else { + this->write(this->functionName(function)); + } + + this->write("("); + auto separator = SkSL::String::Separator(); + for (const auto& arg : c.arguments()) { + this->write(separator()); + this->writeExpression(*arg, Precedence::kSequence); + } + this->write(")"); +} + +void PipelineStageCodeGenerator::writeVariableReference(const VariableReference& ref) { + const Variable* var = ref.variable(); + const Modifiers& modifiers = var->modifiers(); + + if (modifiers.fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) { + this->write(fSampleCoords); + return; + } else if (modifiers.fLayout.fBuiltin == SK_INPUT_COLOR_BUILTIN) { + this->write(fInputColor); + return; + } else if (modifiers.fLayout.fBuiltin == SK_DEST_COLOR_BUILTIN) { + this->write(fDestColor); + return; + } + + std::string* name = fVariableNames.find(var); + this->write(name ? *name : var->name()); +} + +void PipelineStageCodeGenerator::writeIfStatement(const IfStatement& stmt) { + this->write("if ("); + this->writeExpression(*stmt.test(), Precedence::kTopLevel); + this->write(") "); + this->writeStatement(*stmt.ifTrue()); + if (stmt.ifFalse()) { + this->write(" else "); + this->writeStatement(*stmt.ifFalse()); + } +} + +void PipelineStageCodeGenerator::writeReturnStatement(const ReturnStatement& r) { + this->write("return"); + if (r.expression()) { + this->write(" "); + if (fCastReturnsToHalf) { + this->write("half4("); + } + this->writeExpression(*r.expression(), Precedence::kTopLevel); + if (fCastReturnsToHalf) { + this->write(")"); + } + } + this->write(";"); +} + +void PipelineStageCodeGenerator::writeSwitchStatement(const SwitchStatement& s) { + this->write("switch ("); + this->writeExpression(*s.value(), Precedence::kTopLevel); + this->writeLine(") {"); + for (const std::unique_ptr<Statement>& stmt : s.cases()) { + const SwitchCase& c = stmt->as<SwitchCase>(); + if (c.isDefault()) { + this->writeLine("default:"); + } else { + this->write("case "); + this->write(std::to_string(c.value())); + this->writeLine(":"); + } + if (!c.statement()->isEmpty()) { + this->writeStatement(*c.statement()); + this->writeLine(); + } + } + this->writeLine(); + this->write("}"); +} + +std::string PipelineStageCodeGenerator::functionName(const FunctionDeclaration& decl) { + if (decl.isMain()) { + return std::string(fCallbacks->getMainName()); + } + + std::string* name = fFunctionNames.find(&decl); + if (name) { + return *name; + } + + std::string mangledName = fCallbacks->getMangledName(std::string(decl.name()).c_str()); + fFunctionNames.set(&decl, mangledName); + return mangledName; +} + +void PipelineStageCodeGenerator::writeFunction(const FunctionDefinition& f) { + if (f.declaration().isBuiltin()) { + // Don't re-emit builtin functions. + return; + } + + AutoOutputBuffer body(this); + + // We allow public SkSL's main() to return half4 -or- float4 (ie vec4). When we emit + // our code in the processor, the surrounding code is going to expect half4, so we + // explicitly cast any returns (from main) to half4. This is only strictly necessary + // if the return type is float4 - injecting it unconditionally reduces the risk of an + // obscure bug. + const FunctionDeclaration& decl = f.declaration(); + if (decl.isMain() && + fProgram.fConfig->fKind != SkSL::ProgramKind::kMeshVertex && + fProgram.fConfig->fKind != SkSL::ProgramKind::kMeshFragment) { + fCastReturnsToHalf = true; + } + + for (const std::unique_ptr<Statement>& stmt : f.body()->as<Block>().children()) { + this->writeStatement(*stmt); + this->writeLine(); + } + + if (decl.isMain()) { + fCastReturnsToHalf = false; + } + + fCallbacks->defineFunction(this->functionDeclaration(decl).c_str(), + body.fBuffer.str().c_str(), + decl.isMain()); +} + +std::string PipelineStageCodeGenerator::functionDeclaration(const FunctionDeclaration& decl) { + // This is similar to decl.description(), but substitutes a mangled name, and handles modifiers + // on the function (e.g. `inline`) and its parameters (e.g. `inout`). + std::string declString = + String::printf("%s%s%s %s(", + (decl.modifiers().fFlags & Modifiers::kInline_Flag) ? "inline " : "", + (decl.modifiers().fFlags & Modifiers::kNoInline_Flag) ? "noinline " : "", + this->typeName(decl.returnType()).c_str(), + this->functionName(decl).c_str()); + auto separator = SkSL::String::Separator(); + for (const Variable* p : decl.parameters()) { + declString.append(separator()); + declString.append(this->modifierString(p->modifiers())); + declString.append(this->typedVariable(p->type(), p->name()).c_str()); + } + + return declString + ")"; +} + +void PipelineStageCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& decl) { + if (!decl.isMain() && !decl.isBuiltin()) { + fCallbacks->declareFunction(this->functionDeclaration(decl).c_str()); + } +} + +void PipelineStageCodeGenerator::writeGlobalVarDeclaration(const GlobalVarDeclaration& g) { + const VarDeclaration& decl = g.varDeclaration(); + const Variable& var = *decl.var(); + + if (var.isBuiltin() || var.type().isOpaque()) { + // Don't re-declare these. (eg, sk_FragCoord, or fragmentProcessor children) + } else if (var.modifiers().fFlags & Modifiers::kUniform_Flag) { + std::string uniformName = fCallbacks->declareUniform(&decl); + fVariableNames.set(&var, std::move(uniformName)); + } else { + std::string mangledName = fCallbacks->getMangledName(std::string(var.name()).c_str()); + std::string declaration = this->modifierString(var.modifiers()) + + this->typedVariable(var.type(), + std::string_view(mangledName.c_str())); + if (decl.value()) { + AutoOutputBuffer outputToBuffer(this); + this->writeExpression(*decl.value(), Precedence::kTopLevel); + declaration += " = "; + declaration += outputToBuffer.fBuffer.str(); + } + declaration += ";\n"; + fCallbacks->declareGlobal(declaration.c_str()); + fVariableNames.set(&var, std::move(mangledName)); + } +} + +void PipelineStageCodeGenerator::writeStructDefinition(const StructDefinition& s) { + const Type& type = s.type(); + std::string mangledName = fCallbacks->getMangledName(type.displayName().c_str()); + std::string definition = "struct " + mangledName + " {\n"; + for (const auto& f : type.fields()) { + definition += this->typedVariable(*f.fType, f.fName) + ";\n"; + } + definition += "};\n"; + fStructNames.set(&type, std::move(mangledName)); + fCallbacks->defineStruct(definition.c_str()); +} + +void PipelineStageCodeGenerator::writeProgramElementFirstPass(const ProgramElement& e) { + switch (e.kind()) { + case ProgramElement::Kind::kGlobalVar: + this->writeGlobalVarDeclaration(e.as<GlobalVarDeclaration>()); + break; + case ProgramElement::Kind::kFunction: + this->writeFunctionDeclaration(e.as<FunctionDefinition>().declaration()); + break; + case ProgramElement::Kind::kFunctionPrototype: + // Skip this; we're already emitting prototypes for every FunctionDefinition. + // (See case kFunction, directly above.) + break; + case ProgramElement::Kind::kStructDefinition: + this->writeStructDefinition(e.as<StructDefinition>()); + break; + + case ProgramElement::Kind::kExtension: + case ProgramElement::Kind::kInterfaceBlock: + case ProgramElement::Kind::kModifiers: + default: + SkDEBUGFAILF("unsupported program element %s\n", e.description().c_str()); + break; + } +} + +void PipelineStageCodeGenerator::writeProgramElementSecondPass(const ProgramElement& e) { + if (e.is<FunctionDefinition>()) { + this->writeFunction(e.as<FunctionDefinition>()); + } +} + +std::string PipelineStageCodeGenerator::typeName(const Type& raw) { + const Type& type = raw.resolve(); + if (type.isArray()) { + // This is necessary so that name mangling on arrays-of-structs works properly. + std::string arrayName = this->typeName(type.componentType()); + arrayName.push_back('['); + arrayName += std::to_string(type.columns()); + arrayName.push_back(']'); + return arrayName; + } + + std::string* name = fStructNames.find(&type); + return name ? *name : std::string(type.name()); +} + +void PipelineStageCodeGenerator::writeType(const Type& type) { + this->write(this->typeName(type)); +} + +void PipelineStageCodeGenerator::writeExpression(const Expression& expr, + Precedence parentPrecedence) { + switch (expr.kind()) { + case Expression::Kind::kBinary: + this->writeBinaryExpression(expr.as<BinaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kLiteral: + this->write(expr.description()); + break; + case Expression::Kind::kChildCall: + this->writeChildCall(expr.as<ChildCall>()); + break; + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorDiagonalMatrix: + case Expression::Kind::kConstructorMatrixResize: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorSplat: + case Expression::Kind::kConstructorStruct: + this->writeAnyConstructor(expr.asAnyConstructor(), parentPrecedence); + break; + case Expression::Kind::kFieldAccess: + this->writeFieldAccess(expr.as<FieldAccess>()); + break; + case Expression::Kind::kFunctionCall: + this->writeFunctionCall(expr.as<FunctionCall>()); + break; + case Expression::Kind::kPrefix: + this->writePrefixExpression(expr.as<PrefixExpression>(), parentPrecedence); + break; + case Expression::Kind::kPostfix: + this->writePostfixExpression(expr.as<PostfixExpression>(), parentPrecedence); + break; + case Expression::Kind::kSwizzle: + this->writeSwizzle(expr.as<Swizzle>()); + break; + case Expression::Kind::kVariableReference: + this->writeVariableReference(expr.as<VariableReference>()); + break; + case Expression::Kind::kTernary: + this->writeTernaryExpression(expr.as<TernaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kIndex: + this->writeIndexExpression(expr.as<IndexExpression>()); + break; + case Expression::Kind::kSetting: + default: + SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str()); + break; + } +} + +void PipelineStageCodeGenerator::writeAnyConstructor(const AnyConstructor& c, + Precedence parentPrecedence) { + this->writeType(c.type()); + this->write("("); + auto separator = SkSL::String::Separator(); + for (const auto& arg : c.argumentSpan()) { + this->write(separator()); + this->writeExpression(*arg, Precedence::kSequence); + } + this->write(")"); +} + +void PipelineStageCodeGenerator::writeIndexExpression(const IndexExpression& expr) { + this->writeExpression(*expr.base(), Precedence::kPostfix); + this->write("["); + this->writeExpression(*expr.index(), Precedence::kTopLevel); + this->write("]"); +} + +void PipelineStageCodeGenerator::writeFieldAccess(const FieldAccess& f) { + if (f.ownerKind() == FieldAccess::OwnerKind::kDefault) { + this->writeExpression(*f.base(), Precedence::kPostfix); + this->write("."); + } + const Type& baseType = f.base()->type(); + this->write(baseType.fields()[f.fieldIndex()].fName); +} + +void PipelineStageCodeGenerator::writeSwizzle(const Swizzle& swizzle) { + this->writeExpression(*swizzle.base(), Precedence::kPostfix); + this->write("."); + for (int c : swizzle.components()) { + SkASSERT(c >= 0 && c <= 3); + this->write(&("x\0y\0z\0w\0"[c * 2])); + } +} + +void PipelineStageCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + const Expression& left = *b.left(); + const Expression& right = *b.right(); + Operator op = b.getOperator(); + + Precedence precedence = op.getBinaryPrecedence(); + if (precedence >= parentPrecedence) { + this->write("("); + } + this->writeExpression(left, precedence); + this->write(op.operatorName()); + this->writeExpression(right, precedence); + if (precedence >= parentPrecedence) { + this->write(")"); + } +} + +void PipelineStageCodeGenerator::writeTernaryExpression(const TernaryExpression& t, + Precedence parentPrecedence) { + if (Precedence::kTernary >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*t.test(), Precedence::kTernary); + this->write(" ? "); + this->writeExpression(*t.ifTrue(), Precedence::kTernary); + this->write(" : "); + this->writeExpression(*t.ifFalse(), Precedence::kTernary); + if (Precedence::kTernary >= parentPrecedence) { + this->write(")"); + } +} + +void PipelineStageCodeGenerator::writePrefixExpression(const PrefixExpression& p, + Precedence parentPrecedence) { + if (Precedence::kPrefix >= parentPrecedence) { + this->write("("); + } + this->write(p.getOperator().tightOperatorName()); + this->writeExpression(*p.operand(), Precedence::kPrefix); + if (Precedence::kPrefix >= parentPrecedence) { + this->write(")"); + } +} + +void PipelineStageCodeGenerator::writePostfixExpression(const PostfixExpression& p, + Precedence parentPrecedence) { + if (Precedence::kPostfix >= parentPrecedence) { + this->write("("); + } + this->writeExpression(*p.operand(), Precedence::kPostfix); + this->write(p.getOperator().tightOperatorName()); + if (Precedence::kPostfix >= parentPrecedence) { + this->write(")"); + } +} + +std::string PipelineStageCodeGenerator::modifierString(const Modifiers& modifiers) { + std::string result; + if (modifiers.fFlags & Modifiers::kConst_Flag) { + result.append("const "); + } + + if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kOut_Flag)) { + result.append("inout "); + } else if (modifiers.fFlags & Modifiers::kIn_Flag) { + result.append("in "); + } else if (modifiers.fFlags & Modifiers::kOut_Flag) { + result.append("out "); + } + + return result; +} + +std::string PipelineStageCodeGenerator::typedVariable(const Type& type, std::string_view name) { + const Type& baseType = type.isArray() ? type.componentType() : type; + + std::string decl = this->typeName(baseType) + " " + std::string(name); + if (type.isArray()) { + decl += "[" + std::to_string(type.columns()) + "]"; + } + return decl; +} + +void PipelineStageCodeGenerator::writeVarDeclaration(const VarDeclaration& var) { + this->write(this->modifierString(var.var()->modifiers())); + this->write(this->typedVariable(var.var()->type(), var.var()->name())); + if (var.value()) { + this->write(" = "); + this->writeExpression(*var.value(), Precedence::kTopLevel); + } + this->write(";"); +} + +void PipelineStageCodeGenerator::writeStatement(const Statement& s) { + switch (s.kind()) { + case Statement::Kind::kBlock: + this->writeBlock(s.as<Block>()); + break; + case Statement::Kind::kBreak: + this->write("break;"); + break; + case Statement::Kind::kContinue: + this->write("continue;"); + break; + case Statement::Kind::kExpression: + this->writeExpression(*s.as<ExpressionStatement>().expression(), Precedence::kTopLevel); + this->write(";"); + break; + case Statement::Kind::kDo: + this->writeDoStatement(s.as<DoStatement>()); + break; + case Statement::Kind::kFor: + this->writeForStatement(s.as<ForStatement>()); + break; + case Statement::Kind::kIf: + this->writeIfStatement(s.as<IfStatement>()); + break; + case Statement::Kind::kReturn: + this->writeReturnStatement(s.as<ReturnStatement>()); + break; + case Statement::Kind::kSwitch: + this->writeSwitchStatement(s.as<SwitchStatement>()); + break; + case Statement::Kind::kVarDeclaration: + this->writeVarDeclaration(s.as<VarDeclaration>()); + break; + case Statement::Kind::kDiscard: + SkDEBUGFAIL("Unsupported control flow"); + break; + case Statement::Kind::kNop: + this->write(";"); + break; + default: + SkDEBUGFAILF("unsupported statement: %s", s.description().c_str()); + break; + } +} + +void PipelineStageCodeGenerator::writeBlock(const Block& b) { + // Write scope markers if this block is a scope, or if the block is empty (since we need to emit + // something here to make the code valid). + bool isScope = b.isScope() || b.isEmpty(); + if (isScope) { + this->writeLine("{"); + } + for (const std::unique_ptr<Statement>& stmt : b.children()) { + if (!stmt->isEmpty()) { + this->writeStatement(*stmt); + this->writeLine(); + } + } + if (isScope) { + this->write("}"); + } +} + +void PipelineStageCodeGenerator::writeDoStatement(const DoStatement& d) { + this->write("do "); + this->writeStatement(*d.statement()); + this->write(" while ("); + this->writeExpression(*d.test(), Precedence::kTopLevel); + this->write(");"); + return; +} + +void PipelineStageCodeGenerator::writeForStatement(const ForStatement& f) { + // Emit loops of the form 'for(;test;)' as 'while(test)', which is probably how they started + if (!f.initializer() && f.test() && !f.next()) { + this->write("while ("); + this->writeExpression(*f.test(), Precedence::kTopLevel); + this->write(") "); + this->writeStatement(*f.statement()); + return; + } + + this->write("for ("); + if (f.initializer() && !f.initializer()->isEmpty()) { + this->writeStatement(*f.initializer()); + } else { + this->write("; "); + } + if (f.test()) { + this->writeExpression(*f.test(), Precedence::kTopLevel); + } + this->write("; "); + if (f.next()) { + this->writeExpression(*f.next(), Precedence::kTopLevel); + } + this->write(") "); + this->writeStatement(*f.statement()); +} + +void PipelineStageCodeGenerator::generateCode() { + // Write all the program elements except for functions; prototype all the functions. + for (const ProgramElement* e : fProgram.elements()) { + this->writeProgramElementFirstPass(*e); + } + + // We always place FunctionDefinition elements last, because the inliner likes to move function + // bodies around. After inlining, code can inadvertently move upwards, above ProgramElements + // that the code relies on. + for (const ProgramElement* e : fProgram.elements()) { + this->writeProgramElementSecondPass(*e); + } +} + +void ConvertProgram(const Program& program, + const char* sampleCoords, + const char* inputColor, + const char* destColor, + Callbacks* callbacks) { + PipelineStageCodeGenerator generator(program, sampleCoords, inputColor, destColor, callbacks); + generator.generateCode(); +} + +} // namespace PipelineStage +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.h new file mode 100644 index 0000000000..7efb0e187e --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLPipelineStageCodeGenerator.h @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PIPELINESTAGECODEGENERATOR +#define SKSL_PIPELINESTAGECODEGENERATOR + +#include "include/core/SkTypes.h" + +#if defined(SKSL_STANDALONE) || defined(SK_GANESH) || defined(SK_GRAPHITE) + +#include <string> + +namespace SkSL { + +struct Program; +class VarDeclaration; + +namespace PipelineStage { + class Callbacks { + public: + virtual ~Callbacks() = default; + + virtual std::string getMainName() { return "main"; } + virtual std::string getMangledName(const char* name) { return name; } + virtual void defineFunction(const char* declaration, const char* body, bool isMain) = 0; + virtual void declareFunction(const char* declaration) = 0; + virtual void defineStruct(const char* definition) = 0; + virtual void declareGlobal(const char* declaration) = 0; + + virtual std::string declareUniform(const VarDeclaration*) = 0; + virtual std::string sampleShader(int index, std::string coords) = 0; + virtual std::string sampleColorFilter(int index, std::string color) = 0; + virtual std::string sampleBlender(int index, std::string src, std::string dst) = 0; + + virtual std::string toLinearSrgb(std::string color) = 0; + virtual std::string fromLinearSrgb(std::string color) = 0; + }; + + /* + * Processes 'program' for use in a GrFragmentProcessor, or other context that wants SkSL-like + * code as input. To support fragment processor usage, there are callbacks that allow elements + * to be declared programmatically and to rename those elements (mangling to avoid collisions). + * + * - Any reference to the main coords builtin variable will be replaced with 'sampleCoords'. + * - Any reference to the input color builtin variable will be replaced with 'inputColor'. + * - Any reference to the dest color builtin variable will be replaced with 'destColor'. + * Dest-color is used in blend programs. + * - Each uniform variable declaration triggers a call to 'declareUniform', which should emit + * the declaration, and return the (possibly different) name to use for the variable. + * - Each function definition triggers a call to 'defineFunction', which should emit the + * definition, and return the (possibly different) name to use for calls to that function. + * - Each invocation of sample() triggers a call to 'sampleChild', which should return the full + * text of the call expression. + */ + void ConvertProgram(const Program& program, + const char* sampleCoords, + const char* inputColor, + const char* destColor, + Callbacks* callbacks); +} // namespace PipelineStage + +} // namespace SkSL + +#endif + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.cpp new file mode 100644 index 0000000000..48d9f26d74 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.cpp @@ -0,0 +1,2861 @@ +/* + * 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/SkStream.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkMalloc.h" +#include "include/private/base/SkTo.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkArenaAlloc.h" +#include "src/core/SkOpts.h" +#include "src/core/SkRasterPipelineOpContexts.h" +#include "src/core/SkRasterPipelineOpList.h" +#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h" +#include "src/sksl/tracing/SkRPDebugTrace.h" +#include "src/sksl/tracing/SkSLDebugInfo.h" +#include "src/utils/SkBitSet.h" + +#if !defined(SKSL_STANDALONE) +#include "src/core/SkRasterPipeline.h" +#endif + +#include <algorithm> +#include <cmath> +#include <cstring> +#include <iterator> +#include <string> +#include <string_view> +#include <tuple> +#include <utility> +#include <vector> + +namespace SkSL { +namespace RP { + +#define ALL_SINGLE_SLOT_UNARY_OP_CASES \ + BuilderOp::acos_float: \ + case BuilderOp::asin_float: \ + case BuilderOp::atan_float: \ + case BuilderOp::cos_float: \ + case BuilderOp::exp_float: \ + case BuilderOp::exp2_float: \ + case BuilderOp::log_float: \ + case BuilderOp::log2_float: \ + case BuilderOp::sin_float: \ + case BuilderOp::sqrt_float: \ + case BuilderOp::tan_float + +#define ALL_MULTI_SLOT_UNARY_OP_CASES \ + BuilderOp::abs_float: \ + case BuilderOp::abs_int: \ + case BuilderOp::bitwise_not_int: \ + case BuilderOp::cast_to_float_from_int: \ + case BuilderOp::cast_to_float_from_uint: \ + case BuilderOp::cast_to_int_from_float: \ + case BuilderOp::cast_to_uint_from_float: \ + case BuilderOp::ceil_float: \ + case BuilderOp::floor_float: \ + case BuilderOp::invsqrt_float + +#define ALL_N_WAY_BINARY_OP_CASES \ + BuilderOp::atan2_n_floats: \ + case BuilderOp::pow_n_floats + +#define ALL_MULTI_SLOT_BINARY_OP_CASES \ + BuilderOp::add_n_floats: \ + case BuilderOp::add_n_ints: \ + case BuilderOp::sub_n_floats: \ + case BuilderOp::sub_n_ints: \ + case BuilderOp::mul_n_floats: \ + case BuilderOp::mul_n_ints: \ + case BuilderOp::div_n_floats: \ + case BuilderOp::div_n_ints: \ + case BuilderOp::div_n_uints: \ + case BuilderOp::bitwise_and_n_ints: \ + case BuilderOp::bitwise_or_n_ints: \ + case BuilderOp::bitwise_xor_n_ints: \ + case BuilderOp::mod_n_floats: \ + case BuilderOp::min_n_floats: \ + case BuilderOp::min_n_ints: \ + case BuilderOp::min_n_uints: \ + case BuilderOp::max_n_floats: \ + case BuilderOp::max_n_ints: \ + case BuilderOp::max_n_uints: \ + case BuilderOp::cmple_n_floats: \ + case BuilderOp::cmple_n_ints: \ + case BuilderOp::cmple_n_uints: \ + case BuilderOp::cmplt_n_floats: \ + case BuilderOp::cmplt_n_ints: \ + case BuilderOp::cmplt_n_uints: \ + case BuilderOp::cmpeq_n_floats: \ + case BuilderOp::cmpeq_n_ints: \ + case BuilderOp::cmpne_n_floats: \ + case BuilderOp::cmpne_n_ints + +#define ALL_N_WAY_TERNARY_OP_CASES \ + BuilderOp::smoothstep_n_floats + +#define ALL_MULTI_SLOT_TERNARY_OP_CASES \ + BuilderOp::mix_n_floats: \ + case BuilderOp::mix_n_ints + +void Builder::unary_op(BuilderOp op, int32_t slots) { + switch (op) { + case ALL_SINGLE_SLOT_UNARY_OP_CASES: + case ALL_MULTI_SLOT_UNARY_OP_CASES: + fInstructions.push_back({op, {}, slots}); + break; + + default: + SkDEBUGFAIL("not a unary op"); + break; + } +} + +void Builder::binary_op(BuilderOp op, int32_t slots) { + switch (op) { + case ALL_N_WAY_BINARY_OP_CASES: + case ALL_MULTI_SLOT_BINARY_OP_CASES: + fInstructions.push_back({op, {}, slots}); + break; + + default: + SkDEBUGFAIL("not a binary op"); + break; + } +} + +void Builder::ternary_op(BuilderOp op, int32_t slots) { + switch (op) { + case ALL_N_WAY_TERNARY_OP_CASES: + case ALL_MULTI_SLOT_TERNARY_OP_CASES: + fInstructions.push_back({op, {}, slots}); + break; + + default: + SkDEBUGFAIL("not a ternary op"); + break; + } +} + +void Builder::dot_floats(int32_t slots) { + switch (slots) { + case 1: fInstructions.push_back({BuilderOp::mul_n_floats, {}, slots}); break; + case 2: fInstructions.push_back({BuilderOp::dot_2_floats, {}, slots}); break; + case 3: fInstructions.push_back({BuilderOp::dot_3_floats, {}, slots}); break; + case 4: fInstructions.push_back({BuilderOp::dot_4_floats, {}, slots}); break; + + default: + SkDEBUGFAIL("invalid number of slots"); + break; + } +} + +void Builder::refract_floats() { + fInstructions.push_back({BuilderOp::refract_4_floats, {}}); +} + +void Builder::inverse_matrix(int32_t n) { + switch (n) { + case 2: fInstructions.push_back({BuilderOp::inverse_mat2, {}, 4}); break; + case 3: fInstructions.push_back({BuilderOp::inverse_mat3, {}, 9}); break; + case 4: fInstructions.push_back({BuilderOp::inverse_mat4, {}, 16}); break; + default: SkUNREACHABLE; + } +} + +void Builder::discard_stack(int32_t count) { + // If we pushed something onto the stack and then immediately discarded part of it, we can + // shrink or eliminate the push. + while (count > 0 && !fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + switch (lastInstruction.fOp) { + case BuilderOp::discard_stack: + // Our last op was actually a separate discard_stack; combine the discards. + lastInstruction.fImmA += count; + return; + + case BuilderOp::push_zeros: + case BuilderOp::push_clone: + case BuilderOp::push_clone_from_stack: + case BuilderOp::push_clone_indirect_from_stack: + case BuilderOp::push_slots: + case BuilderOp::push_slots_indirect: + case BuilderOp::push_uniform: + case BuilderOp::push_uniform_indirect: + // Our last op was a multi-slot push; cancel out one discard and eliminate the op + // if its count reached zero. + --count; + --lastInstruction.fImmA; + if (lastInstruction.fImmA == 0) { + fInstructions.pop_back(); + } + continue; + + case BuilderOp::push_literal: + case BuilderOp::push_condition_mask: + case BuilderOp::push_loop_mask: + case BuilderOp::push_return_mask: + // Our last op was a single-slot push; cancel out one discard and eliminate the op. + --count; + fInstructions.pop_back(); + continue; + + default: + break; + } + + // This instruction wasn't a push. + break; + } + + if (count > 0) { + fInstructions.push_back({BuilderOp::discard_stack, {}, count}); + } +} + +void Builder::label(int labelID) { + SkASSERT(labelID >= 0 && labelID < fNumLabels); + + // If the previous instruction was a branch to this label, it's a no-op; jumping to the very + // next instruction is effectively meaningless. + while (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + switch (lastInstruction.fOp) { + case BuilderOp::jump: + case BuilderOp::branch_if_all_lanes_active: + case BuilderOp::branch_if_any_lanes_active: + case BuilderOp::branch_if_no_lanes_active: + case BuilderOp::branch_if_no_active_lanes_on_stack_top_equal: + if (lastInstruction.fImmA == labelID) { + fInstructions.pop_back(); + continue; + } + break; + + default: + break; + } + break; + } + fInstructions.push_back({BuilderOp::label, {}, labelID}); +} + +void Builder::jump(int labelID) { + SkASSERT(labelID >= 0 && labelID < fNumLabels); + if (!fInstructions.empty() && fInstructions.back().fOp == BuilderOp::jump) { + // The previous instruction was also `jump`, so this branch could never possibly occur. + return; + } + fInstructions.push_back({BuilderOp::jump, {}, labelID}); +} + +void Builder::branch_if_any_lanes_active(int labelID) { + if (!this->executionMaskWritesAreEnabled()) { + this->jump(labelID); + return; + } + + SkASSERT(labelID >= 0 && labelID < fNumLabels); + if (!fInstructions.empty() && + (fInstructions.back().fOp == BuilderOp::branch_if_any_lanes_active || + fInstructions.back().fOp == BuilderOp::jump)) { + // The previous instruction was `jump` or `branch_if_any_lanes_active`, so this branch + // could never possibly occur. + return; + } + fInstructions.push_back({BuilderOp::branch_if_any_lanes_active, {}, labelID}); +} + +void Builder::branch_if_all_lanes_active(int labelID) { + if (!this->executionMaskWritesAreEnabled()) { + this->jump(labelID); + return; + } + + SkASSERT(labelID >= 0 && labelID < fNumLabels); + if (!fInstructions.empty() && + (fInstructions.back().fOp == BuilderOp::branch_if_all_lanes_active || + fInstructions.back().fOp == BuilderOp::jump)) { + // The previous instruction was `jump` or `branch_if_all_lanes_active`, so this branch + // could never possibly occur. + return; + } + fInstructions.push_back({BuilderOp::branch_if_all_lanes_active, {}, labelID}); +} + +void Builder::branch_if_no_lanes_active(int labelID) { + if (!this->executionMaskWritesAreEnabled()) { + return; + } + + SkASSERT(labelID >= 0 && labelID < fNumLabels); + if (!fInstructions.empty() && + (fInstructions.back().fOp == BuilderOp::branch_if_no_lanes_active || + fInstructions.back().fOp == BuilderOp::jump)) { + // The previous instruction was `jump` or `branch_if_no_lanes_active`, so this branch + // could never possibly occur. + return; + } + fInstructions.push_back({BuilderOp::branch_if_no_lanes_active, {}, labelID}); +} + +void Builder::branch_if_no_active_lanes_on_stack_top_equal(int value, int labelID) { + SkASSERT(labelID >= 0 && labelID < fNumLabels); + if (!fInstructions.empty() && + (fInstructions.back().fOp == BuilderOp::jump || + (fInstructions.back().fOp == BuilderOp::branch_if_no_active_lanes_on_stack_top_equal && + fInstructions.back().fImmB == value))) { + // The previous instruction was `jump` or `branch_if_no_active_lanes_on_stack_top_equal` + // (checking against the same value), so this branch could never possibly occur. + return; + } + fInstructions.push_back({BuilderOp::branch_if_no_active_lanes_on_stack_top_equal, + {}, labelID, value}); +} + +void Builder::push_slots(SlotRange src) { + SkASSERT(src.count >= 0); + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + // If the previous instruction was pushing slots contiguous to this range, we can collapse + // the two pushes into one larger push. + if (lastInstruction.fOp == BuilderOp::push_slots && + lastInstruction.fSlotA + lastInstruction.fImmA == src.index) { + lastInstruction.fImmA += src.count; + return; + } + + // If the previous instruction was discarding an equal number of slots... + if (lastInstruction.fOp == BuilderOp::discard_stack && lastInstruction.fImmA == src.count) { + // ... and the instruction before that was copying from the stack to the same slots... + Instruction& prevInstruction = fInstructions.fromBack(1); + if ((prevInstruction.fOp == BuilderOp::copy_stack_to_slots || + prevInstruction.fOp == BuilderOp::copy_stack_to_slots_unmasked) && + prevInstruction.fSlotA == src.index && + prevInstruction.fImmA == src.count) { + // ... we are emitting `copy stack to X, discard stack, copy X to stack`. This is a + // common pattern when multiple operations in a row affect the same variable. We can + // eliminate the discard and just leave X on the stack. + fInstructions.pop_back(); + return; + } + } + } + + if (src.count > 0) { + fInstructions.push_back({BuilderOp::push_slots, {src.index}, src.count}); + } +} + +void Builder::push_slots_indirect(SlotRange fixedRange, int dynamicStackID, SlotRange limitRange) { + // SlotA: fixed-range start + // SlotB: limit-range end + // immA: number of slots + // immB: dynamic stack ID + fInstructions.push_back({BuilderOp::push_slots_indirect, + {fixedRange.index, limitRange.index + limitRange.count}, + fixedRange.count, + dynamicStackID}); +} + +void Builder::push_uniform(SlotRange src) { + SkASSERT(src.count >= 0); + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + // If the previous instruction was pushing uniforms contiguous to this range, we can + // collapse the two pushes into one larger push. + if (lastInstruction.fOp == BuilderOp::push_uniform && + lastInstruction.fSlotA + lastInstruction.fImmA == src.index) { + lastInstruction.fImmA += src.count; + return; + } + } + + if (src.count > 0) { + fInstructions.push_back({BuilderOp::push_uniform, {src.index}, src.count}); + } +} + +void Builder::push_uniform_indirect(SlotRange fixedRange, + int dynamicStackID, + SlotRange limitRange) { + // SlotA: fixed-range start + // SlotB: limit-range end + // immA: number of slots + // immB: dynamic stack ID + fInstructions.push_back({BuilderOp::push_uniform_indirect, + {fixedRange.index, limitRange.index + limitRange.count}, + fixedRange.count, + dynamicStackID}); +} + +void Builder::push_duplicates(int count) { + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + // If the previous op is pushing a zero, we can just push more of them. + if (lastInstruction.fOp == BuilderOp::push_zeros) { + lastInstruction.fImmA += count; + return; + } + } + SkASSERT(count >= 0); + if (count >= 3) { + // Use a swizzle to splat the input into a 4-slot value. + this->swizzle(/*consumedSlots=*/1, {0, 0, 0, 0}); + count -= 3; + } + for (; count >= 4; count -= 4) { + // Clone the splatted value four slots at a time. + this->push_clone(/*numSlots=*/4); + } + // Use a swizzle or clone to handle the trailing items. + switch (count) { + case 3: this->swizzle(/*consumedSlots=*/1, {0, 0, 0, 0}); break; + case 2: this->swizzle(/*consumedSlots=*/1, {0, 0, 0}); break; + case 1: this->push_clone(/*numSlots=*/1); break; + default: break; + } +} + +void Builder::push_clone_from_stack(SlotRange range, int otherStackID, int offsetFromStackTop) { + // immA: number of slots + // immB: other stack ID + // immC: offset from stack top + offsetFromStackTop -= range.index; + + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + // If the previous op is also pushing a clone... + if (lastInstruction.fOp == BuilderOp::push_clone_from_stack && + // ... from the same stack... + lastInstruction.fImmB == otherStackID && + // ... and this clone starts at the same place that the last clone ends... + lastInstruction.fImmC - lastInstruction.fImmA == offsetFromStackTop) { + // ... just extend the existing clone-op. + lastInstruction.fImmA += range.count; + return; + } + } + + fInstructions.push_back({BuilderOp::push_clone_from_stack, {}, + range.count, otherStackID, offsetFromStackTop}); +} + +void Builder::push_clone_indirect_from_stack(SlotRange fixedOffset, + int dynamicStackID, + int otherStackID, + int offsetFromStackTop) { + // immA: number of slots + // immB: other stack ID + // immC: offset from stack top + // immD: dynamic stack ID + offsetFromStackTop -= fixedOffset.index; + + fInstructions.push_back({BuilderOp::push_clone_indirect_from_stack, {}, + fixedOffset.count, otherStackID, offsetFromStackTop, dynamicStackID}); +} + +void Builder::pop_slots(SlotRange dst) { + if (!this->executionMaskWritesAreEnabled()) { + this->pop_slots_unmasked(dst); + return; + } + + this->copy_stack_to_slots(dst); + this->discard_stack(dst.count); +} + +void Builder::simplifyPopSlotsUnmasked(SlotRange* dst) { + if (!dst->count || fInstructions.empty()) { + // There's nothing left to simplify. + return; + } + + Instruction& lastInstruction = fInstructions.back(); + + // If the last instruction is pushing a constant, we can simplify it by copying the constant + // directly into the destination slot. + if (lastInstruction.fOp == BuilderOp::push_literal) { + // Remove the constant-push instruction. + int value = lastInstruction.fImmA; + fInstructions.pop_back(); + + // Consume one destination slot. + dst->count--; + Slot destinationSlot = dst->index + dst->count; + + // Continue simplifying if possible. + this->simplifyPopSlotsUnmasked(dst); + + // Write the constant directly to the destination slot. + this->copy_constant(destinationSlot, value); + return; + } + + // If the last instruction is pushing a zero, we can save a step by directly zeroing out + // the destination slot. + if (lastInstruction.fOp == BuilderOp::push_zeros) { + // Remove one zero-push. + lastInstruction.fImmA--; + if (lastInstruction.fImmA == 0) { + fInstructions.pop_back(); + } + + // Consume one destination slot. + dst->count--; + Slot destinationSlot = dst->index + dst->count; + + // Continue simplifying if possible. + this->simplifyPopSlotsUnmasked(dst); + + // Zero the destination slot directly. + this->zero_slots_unmasked({destinationSlot, 1}); + return; + } + + // If the last instruction is pushing a slot, we can just copy that slot. + if (lastInstruction.fOp == BuilderOp::push_slots) { + // Get the last slot. + Slot sourceSlot = lastInstruction.fSlotA + lastInstruction.fImmA - 1; + lastInstruction.fImmA--; + if (lastInstruction.fImmA == 0) { + fInstructions.pop_back(); + } + + // Consume one destination slot. + dst->count--; + Slot destinationSlot = dst->index + dst->count; + + // Try once more. + this->simplifyPopSlotsUnmasked(dst); + + // Copy the slot directly. + if (destinationSlot != sourceSlot) { + this->copy_slots_unmasked({destinationSlot, 1}, {sourceSlot, 1}); + } + return; + } +} + +void Builder::pop_slots_unmasked(SlotRange dst) { + SkASSERT(dst.count >= 0); + + // If we are popping immediately after a push, we can simplify the code by writing the pushed + // value directly to the destination range. + this->simplifyPopSlotsUnmasked(&dst); + + // Pop from the stack normally. + if (dst.count > 0) { + this->copy_stack_to_slots_unmasked(dst); + this->discard_stack(dst.count); + } +} + +void Builder::copy_stack_to_slots(SlotRange dst, int offsetFromStackTop) { + // If the execution mask is known to be all-true, then we can ignore the write mask. + if (!this->executionMaskWritesAreEnabled()) { + this->copy_stack_to_slots_unmasked(dst, offsetFromStackTop); + return; + } + + // If the last instruction copied the previous stack slots, just extend it. + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + // If the last op is copy-stack-to-slots... + if (lastInstruction.fOp == BuilderOp::copy_stack_to_slots && + // and this op's destination is immediately after the last copy-slots-op's destination + lastInstruction.fSlotA + lastInstruction.fImmA == dst.index && + // and this op's source is immediately after the last copy-slots-op's source + lastInstruction.fImmB - lastInstruction.fImmA == offsetFromStackTop) { + // then we can just extend the copy! + lastInstruction.fImmA += dst.count; + return; + } + } + + fInstructions.push_back({BuilderOp::copy_stack_to_slots, {dst.index}, + dst.count, offsetFromStackTop}); +} + +void Builder::copy_stack_to_slots_indirect(SlotRange fixedRange, + int dynamicStackID, + SlotRange limitRange) { + // SlotA: fixed-range start + // SlotB: limit-range end + // immA: number of slots + // immB: dynamic stack ID + fInstructions.push_back({BuilderOp::copy_stack_to_slots_indirect, + {fixedRange.index, limitRange.index + limitRange.count}, + fixedRange.count, + dynamicStackID}); +} + +static bool slot_ranges_overlap(SlotRange x, SlotRange y) { + return x.index < y.index + y.count && + y.index < x.index + x.count; +} + +void Builder::copy_slots_unmasked(SlotRange dst, SlotRange src) { + // If the last instruction copied adjacent slots, just extend it. + if (!fInstructions.empty()) { + Instruction& lastInstr = fInstructions.back(); + + // If the last op is copy-slots-unmasked... + if (lastInstr.fOp == BuilderOp::copy_slot_unmasked && + // and this op's destination is immediately after the last copy-slots-op's destination + lastInstr.fSlotA + lastInstr.fImmA == dst.index && + // and this op's source is immediately after the last copy-slots-op's source + lastInstr.fSlotB + lastInstr.fImmA == src.index && + // and the source/dest ranges will not overlap + !slot_ranges_overlap({lastInstr.fSlotB, lastInstr.fImmA + dst.count}, + {lastInstr.fSlotA, lastInstr.fImmA + dst.count})) { + // then we can just extend the copy! + lastInstr.fImmA += dst.count; + return; + } + } + + SkASSERT(dst.count == src.count); + fInstructions.push_back({BuilderOp::copy_slot_unmasked, {dst.index, src.index}, dst.count}); +} + +void Builder::copy_stack_to_slots_unmasked(SlotRange dst, int offsetFromStackTop) { + // If the last instruction copied the previous stack slots, just extend it. + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + // If the last op is copy-stack-to-slots-unmasked... + if (lastInstruction.fOp == BuilderOp::copy_stack_to_slots_unmasked && + // and this op's destination is immediately after the last copy-slots-op's destination + lastInstruction.fSlotA + lastInstruction.fImmA == dst.index && + // and this op's source is immediately after the last copy-slots-op's source + lastInstruction.fImmB - lastInstruction.fImmA == offsetFromStackTop) { + // then we can just extend the copy! + lastInstruction.fImmA += dst.count; + return; + } + } + + fInstructions.push_back({BuilderOp::copy_stack_to_slots_unmasked, {dst.index}, + dst.count, offsetFromStackTop}); +} + +void Builder::pop_return_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + + // This instruction is going to overwrite the return mask. If the previous instruction was + // masking off the return mask, that's wasted work and it can be eliminated. + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + if (lastInstruction.fOp == BuilderOp::mask_off_return_mask) { + fInstructions.pop_back(); + } + } + + fInstructions.push_back({BuilderOp::pop_return_mask, {}}); +} + +void Builder::zero_slots_unmasked(SlotRange dst) { + if (!fInstructions.empty()) { + Instruction& lastInstruction = fInstructions.back(); + + if (lastInstruction.fOp == BuilderOp::zero_slot_unmasked) { + if (lastInstruction.fSlotA + lastInstruction.fImmA == dst.index) { + // The previous instruction was zeroing the range immediately before this range. + // Combine the ranges. + lastInstruction.fImmA += dst.count; + return; + } + } + + if (lastInstruction.fOp == BuilderOp::zero_slot_unmasked) { + if (lastInstruction.fSlotA == dst.index + dst.count) { + // The previous instruction was zeroing the range immediately after this range. + // Combine the ranges. + lastInstruction.fSlotA = dst.index; + lastInstruction.fImmA += dst.count; + return; + } + } + } + + fInstructions.push_back({BuilderOp::zero_slot_unmasked, {dst.index}, dst.count}); +} + +static int pack_nybbles(SkSpan<const int8_t> components) { + // Pack up to 8 elements into nybbles, in reverse order. + int packed = 0; + for (auto iter = components.rbegin(); iter != components.rend(); ++iter) { + SkASSERT(*iter >= 0 && *iter <= 0xF); + packed <<= 4; + packed |= *iter; + } + return packed; +} + +static void unpack_nybbles_to_offsets(uint32_t components, SkSpan<uint16_t> offsets) { + // Unpack component nybbles into byte-offsets pointing at stack slots. + for (size_t index = 0; index < offsets.size(); ++index) { + offsets[index] = (components & 0xF) * SkOpts::raster_pipeline_highp_stride * sizeof(float); + components >>= 4; + } +} + +static int max_packed_nybble(uint32_t components, size_t numComponents) { + int largest = 0; + for (size_t index = 0; index < numComponents; ++index) { + largest = std::max<int>(largest, components & 0xF); + components >>= 4; + } + return largest; +} + +void Builder::swizzle_copy_stack_to_slots(SlotRange dst, + SkSpan<const int8_t> components, + int offsetFromStackTop) { + // When the execution-mask writes-enabled flag is off, we could squeeze out a little bit of + // extra speed here by implementing and using an unmasked version of this op. + + // SlotA: fixed-range start + // immA: number of swizzle components + // immB: swizzle components + // immC: offset from stack top + fInstructions.push_back({BuilderOp::swizzle_copy_stack_to_slots, {dst.index}, + (int)components.size(), + pack_nybbles(components), + offsetFromStackTop}); +} + +void Builder::swizzle_copy_stack_to_slots_indirect(SlotRange fixedRange, + int dynamicStackID, + SlotRange limitRange, + SkSpan<const int8_t> components, + int offsetFromStackTop) { + // When the execution-mask writes-enabled flag is off, we could squeeze out a little bit of + // extra speed here by implementing and using an unmasked version of this op. + + // SlotA: fixed-range start + // SlotB: limit-range end + // immA: number of swizzle components + // immB: swizzle components + // immC: offset from stack top + // immD: dynamic stack ID + fInstructions.push_back({BuilderOp::swizzle_copy_stack_to_slots_indirect, + {fixedRange.index, limitRange.index + limitRange.count}, + (int)components.size(), + pack_nybbles(components), + offsetFromStackTop, + dynamicStackID}); +} + +void Builder::swizzle(int consumedSlots, SkSpan<const int8_t> components) { + // Consumes `consumedSlots` elements on the stack, then generates `elementSpan.size()` elements. + SkASSERT(consumedSlots >= 0); + + // We only allow up to 16 elements, and they can only reach 0-15 slots, due to nybble packing. + int numElements = components.size(); + SkASSERT(numElements <= 16); + SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t e){ return e >= 0; })); + SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t e){ return e <= 0xF; })); + + // Make a local copy of the element array. + int8_t elements[16] = {}; + std::copy(components.begin(), components.end(), std::begin(elements)); + + while (numElements > 0) { + // If the first element of the swizzle is zero... + if (elements[0] != 0) { + break; + } + // ...and zero isn't used elsewhere in the swizzle... + if (std::any_of(&elements[1], &elements[numElements], [](int8_t e) { return e == 0; })) { + break; + } + // We can omit the first slot from the swizzle entirely. + // Slide everything forward by one slot, and reduce the element index by one. + for (int index = 1; index < numElements; ++index) { + elements[index - 1] = elements[index] - 1; + } + elements[numElements - 1] = 0; + --consumedSlots; + --numElements; + } + + // A completely empty swizzle is a no-op. + if (numElements == 0) { + this->discard_stack(consumedSlots); + return; + } + + if (consumedSlots <= 4 && numElements <= 4) { + // We can fit everything into a little swizzle. + int op = (int)BuilderOp::swizzle_1 + numElements - 1; + fInstructions.push_back({(BuilderOp)op, {}, consumedSlots, + pack_nybbles(SkSpan(elements, numElements))}); + return; + } + + // This is a big swizzle. We use the `shuffle` op to handle these. + // Slot usage is packed into immA. The top 16 bits of immA count the consumed slots; the bottom + // 16 bits count the generated slots. + int slotUsage = consumedSlots << 16; + slotUsage |= numElements; + + // Pack immB and immC with the shuffle list in packed-nybble form. + fInstructions.push_back({BuilderOp::shuffle, {}, slotUsage, + pack_nybbles(SkSpan(&elements[0], 8)), + pack_nybbles(SkSpan(&elements[8], 8))}); +} + +void Builder::transpose(int columns, int rows) { + // Transposes a matrix of size CxR on the stack (into a matrix of size RxC). + int8_t elements[16] = {}; + size_t index = 0; + for (int r = 0; r < rows; ++r) { + for (int c = 0; c < columns; ++c) { + elements[index++] = (c * rows) + r; + } + } + this->swizzle(/*consumedSlots=*/columns * rows, SkSpan(elements, index)); +} + +void Builder::diagonal_matrix(int columns, int rows) { + // Generates a CxR diagonal matrix from the top two scalars on the stack. + int8_t elements[16] = {}; + size_t index = 0; + for (int c = 0; c < columns; ++c) { + for (int r = 0; r < rows; ++r) { + elements[index++] = (c == r) ? 1 : 0; + } + } + this->swizzle(/*consumedSlots=*/2, SkSpan(elements, index)); +} + +void Builder::matrix_resize(int origColumns, int origRows, int newColumns, int newRows) { + // Resizes a CxR matrix at the top of the stack to C'xR'. + int8_t elements[16] = {}; + size_t index = 0; + + size_t consumedSlots = origColumns * origRows; + size_t zeroOffset = 0, oneOffset = 0; + + for (int c = 0; c < newColumns; ++c) { + for (int r = 0; r < newRows; ++r) { + if (c < origColumns && r < origRows) { + // Push an element from the original matrix. + elements[index++] = (c * origRows) + r; + } else { + // This element is outside the original matrix; push 1 or 0. + if (c == r) { + // We need to synthesize a literal 1. + if (oneOffset == 0) { + this->push_literal_f(1.0f); + oneOffset = consumedSlots++; + } + elements[index++] = oneOffset; + } else { + // We need to synthesize a literal 0. + if (zeroOffset == 0) { + this->push_zeros(1); + zeroOffset = consumedSlots++; + } + elements[index++] = zeroOffset; + } + } + } + } + this->swizzle(consumedSlots, SkSpan(elements, index)); +} + +std::unique_ptr<Program> Builder::finish(int numValueSlots, + int numUniformSlots, + SkRPDebugTrace* debugTrace) { + // Verify that calls to enableExecutionMaskWrites and disableExecutionMaskWrites are balanced. + SkASSERT(fExecutionMaskWritesEnabled == 0); + + return std::make_unique<Program>(std::move(fInstructions), numValueSlots, numUniformSlots, + fNumLabels, debugTrace); +} + +void Program::optimize() { + // TODO(johnstiles): perform any last-minute cleanup of the instruction stream here +} + +static int stack_usage(const Instruction& inst) { + switch (inst.fOp) { + case BuilderOp::push_literal: + case BuilderOp::push_condition_mask: + case BuilderOp::push_loop_mask: + case BuilderOp::push_return_mask: + return 1; + + case BuilderOp::push_src_rgba: + case BuilderOp::push_dst_rgba: + return 4; + + case BuilderOp::push_slots: + case BuilderOp::push_slots_indirect: + case BuilderOp::push_uniform: + case BuilderOp::push_uniform_indirect: + case BuilderOp::push_zeros: + case BuilderOp::push_clone: + case BuilderOp::push_clone_from_stack: + case BuilderOp::push_clone_indirect_from_stack: + return inst.fImmA; + + case BuilderOp::pop_condition_mask: + case BuilderOp::pop_loop_mask: + case BuilderOp::pop_and_reenable_loop_mask: + case BuilderOp::pop_return_mask: + return -1; + + case BuilderOp::pop_src_rg: + return -2; + + case BuilderOp::pop_src_rgba: + case BuilderOp::pop_dst_rgba: + return -4; + + case ALL_N_WAY_BINARY_OP_CASES: + case ALL_MULTI_SLOT_BINARY_OP_CASES: + case BuilderOp::discard_stack: + case BuilderOp::select: + return -inst.fImmA; + + case ALL_N_WAY_TERNARY_OP_CASES: + case ALL_MULTI_SLOT_TERNARY_OP_CASES: + return 2 * -inst.fImmA; + + case BuilderOp::swizzle_1: + return 1 - inst.fImmA; // consumes immA slots and emits a scalar + case BuilderOp::swizzle_2: + return 2 - inst.fImmA; // consumes immA slots and emits a 2-slot vector + case BuilderOp::swizzle_3: + return 3 - inst.fImmA; // consumes immA slots and emits a 3-slot vector + case BuilderOp::swizzle_4: + return 4 - inst.fImmA; // consumes immA slots and emits a 4-slot vector + + case BuilderOp::dot_2_floats: + return -3; // consumes two 2-slot vectors and emits one scalar + case BuilderOp::dot_3_floats: + return -5; // consumes two 3-slot vectors and emits one scalar + case BuilderOp::dot_4_floats: + return -7; // consumes two 4-slot vectors and emits one scalar + + case BuilderOp::refract_4_floats: + return -5; // consumes nine slots (N + I + eta) and emits a 4-slot vector (R) + + case BuilderOp::shuffle: { + int consumed = inst.fImmA >> 16; + int generated = inst.fImmA & 0xFFFF; + return generated - consumed; + } + case ALL_SINGLE_SLOT_UNARY_OP_CASES: + case ALL_MULTI_SLOT_UNARY_OP_CASES: + default: + return 0; + } +} + +Program::StackDepthMap Program::tempStackMaxDepths() const { + StackDepthMap largest; + StackDepthMap current; + + int curIdx = 0; + for (const Instruction& inst : fInstructions) { + if (inst.fOp == BuilderOp::set_current_stack) { + curIdx = inst.fImmA; + } + current[curIdx] += stack_usage(inst); + largest[curIdx] = std::max(current[curIdx], largest[curIdx]); + SkASSERTF(current[curIdx] >= 0, "unbalanced temp stack push/pop on stack %d", curIdx); + } + + for (const auto& [stackIdx, depth] : current) { + (void)stackIdx; + SkASSERTF(depth == 0, "unbalanced temp stack push/pop"); + } + + return largest; +} + +Program::Program(SkTArray<Instruction> instrs, + int numValueSlots, + int numUniformSlots, + int numLabels, + SkRPDebugTrace* debugTrace) + : fInstructions(std::move(instrs)) + , fNumValueSlots(numValueSlots) + , fNumUniformSlots(numUniformSlots) + , fNumLabels(numLabels) + , fDebugTrace(debugTrace) { + this->optimize(); + + fTempStackMaxDepths = this->tempStackMaxDepths(); + + fNumTempStackSlots = 0; + for (const auto& [stackIdx, depth] : fTempStackMaxDepths) { + (void)stackIdx; + fNumTempStackSlots += depth; + } +} + +void Program::appendCopy(SkTArray<Stage>* pipeline, + SkArenaAlloc* alloc, + ProgramOp baseStage, + float* dst, int dstStride, + const float* src, int srcStride, + int numSlots) const { + SkASSERT(numSlots >= 0); + while (numSlots > 4) { + this->appendCopy(pipeline, alloc, baseStage, dst, dstStride, src, srcStride,/*numSlots=*/4); + dst += 4 * dstStride; + src += 4 * srcStride; + numSlots -= 4; + } + + if (numSlots > 0) { + SkASSERT(numSlots <= 4); + auto stage = (ProgramOp)((int)baseStage + numSlots - 1); + auto* ctx = alloc->make<SkRasterPipeline_BinaryOpCtx>(); + ctx->dst = dst; + ctx->src = src; + pipeline->push_back({stage, ctx}); + } +} + +void Program::appendCopySlotsUnmasked(SkTArray<Stage>* pipeline, + SkArenaAlloc* alloc, + float* dst, + const float* src, + int numSlots) const { + this->appendCopy(pipeline, alloc, + ProgramOp::copy_slot_unmasked, + dst, /*dstStride=*/SkOpts::raster_pipeline_highp_stride, + src, /*srcStride=*/SkOpts::raster_pipeline_highp_stride, + numSlots); +} + +void Program::appendCopySlotsMasked(SkTArray<Stage>* pipeline, + SkArenaAlloc* alloc, + float* dst, + const float* src, + int numSlots) const { + this->appendCopy(pipeline, alloc, + ProgramOp::copy_slot_masked, + dst, /*dstStride=*/SkOpts::raster_pipeline_highp_stride, + src, /*srcStride=*/SkOpts::raster_pipeline_highp_stride, + numSlots); +} + +void Program::appendCopyConstants(SkTArray<Stage>* pipeline, + SkArenaAlloc* alloc, + float* dst, + const float* src, + int numSlots) const { + this->appendCopy(pipeline, alloc, + ProgramOp::copy_constant, + dst, /*dstStride=*/SkOpts::raster_pipeline_highp_stride, + src, /*srcStride=*/1, + numSlots); +} + +void Program::appendSingleSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp stage, + float* dst, int numSlots) const { + SkASSERT(numSlots >= 0); + while (numSlots--) { + pipeline->push_back({stage, dst}); + dst += SkOpts::raster_pipeline_highp_stride; + } +} + +void Program::appendMultiSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp baseStage, + float* dst, int numSlots) const { + SkASSERT(numSlots >= 0); + while (numSlots > 4) { + this->appendMultiSlotUnaryOp(pipeline, baseStage, dst, /*numSlots=*/4); + dst += 4 * SkOpts::raster_pipeline_highp_stride; + numSlots -= 4; + } + + SkASSERT(numSlots <= 4); + auto stage = (ProgramOp)((int)baseStage + numSlots - 1); + pipeline->push_back({stage, dst}); +} + +void Program::appendAdjacentNWayBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp stage, + float* dst, const float* src, int numSlots) const { + // The source and destination must be directly next to one another. + SkASSERT(numSlots >= 0); + SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src); + + if (numSlots > 0) { + auto ctx = alloc->make<SkRasterPipeline_BinaryOpCtx>(); + ctx->dst = dst; + ctx->src = src; + pipeline->push_back({stage, ctx}); + } +} + +void Program::appendAdjacentMultiSlotBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp baseStage, + float* dst, const float* src, int numSlots) const { + // The source and destination must be directly next to one another. + SkASSERT(numSlots >= 0); + SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src); + + if (numSlots > 4) { + this->appendAdjacentNWayBinaryOp(pipeline, alloc, baseStage, dst, src, numSlots); + return; + } + if (numSlots > 0) { + auto specializedStage = (ProgramOp)((int)baseStage + numSlots); + pipeline->push_back({specializedStage, dst}); + } +} + +void Program::appendAdjacentNWayTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp stage, float* dst, const float* src0, + const float* src1, int numSlots) const { + // The float pointers must all be immediately adjacent to each other. + SkASSERT(numSlots >= 0); + SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src0); + SkASSERT((src0 + SkOpts::raster_pipeline_highp_stride * numSlots) == src1); + + if (numSlots > 0) { + auto ctx = alloc->make<SkRasterPipeline_TernaryOpCtx>(); + ctx->dst = dst; + ctx->src0 = src0; + ctx->src1 = src1; + pipeline->push_back({stage, ctx}); + } +} + +void Program::appendAdjacentMultiSlotTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp baseStage, float* dst, const float* src0, + const float* src1, int numSlots) const { + // The float pointers must all be immediately adjacent to each other. + SkASSERT(numSlots >= 0); + SkASSERT((dst + SkOpts::raster_pipeline_highp_stride * numSlots) == src0); + SkASSERT((src0 + SkOpts::raster_pipeline_highp_stride * numSlots) == src1); + + if (numSlots > 4) { + this->appendAdjacentNWayTernaryOp(pipeline, alloc, baseStage, dst, src0, src1, numSlots); + return; + } + if (numSlots > 0) { + auto specializedStage = (ProgramOp)((int)baseStage + numSlots); + pipeline->push_back({specializedStage, dst}); + } +} + +void Program::appendStackRewind(SkTArray<Stage>* pipeline) const { +#if defined(SKSL_STANDALONE) || !SK_HAS_MUSTTAIL + pipeline->push_back({ProgramOp::stack_rewind, nullptr}); +#endif +} + +static void* context_bit_pun(intptr_t val) { + return sk_bit_cast<void*>(val); +} + +Program::SlotData Program::allocateSlotData(SkArenaAlloc* alloc) const { + // Allocate a contiguous slab of slot data for values and stack entries. + const int N = SkOpts::raster_pipeline_highp_stride; + const int vectorWidth = N * sizeof(float); + const int allocSize = vectorWidth * (fNumValueSlots + fNumTempStackSlots); + float* slotPtr = static_cast<float*>(alloc->makeBytesAlignedTo(allocSize, vectorWidth)); + sk_bzero(slotPtr, allocSize); + + // Store the temp stack immediately after the values. + SlotData s; + s.values = SkSpan(slotPtr, N * fNumValueSlots); + s.stack = SkSpan(s.values.end(), N * fNumTempStackSlots); + return s; +} + +#if !defined(SKSL_STANDALONE) + +bool Program::appendStages(SkRasterPipeline* pipeline, + SkArenaAlloc* alloc, + RP::Callbacks* callbacks, + SkSpan<const float> uniforms) const { + // Convert our Instruction list to an array of ProgramOps. + SkTArray<Stage> stages; + this->makeStages(&stages, alloc, uniforms, this->allocateSlotData(alloc)); + + // Allocate buffers for branch targets and labels; these are needed to convert labels into + // actual offsets into the pipeline and fix up branches. + SkTArray<SkRasterPipeline_BranchCtx*> branchContexts; + branchContexts.reserve_back(fNumLabels); + SkTArray<int> labelOffsets; + labelOffsets.push_back_n(fNumLabels, -1); + SkTArray<int> branchGoesToLabel; + branchGoesToLabel.reserve_back(fNumLabels); + + for (const Stage& stage : stages) { + switch (stage.op) { + case ProgramOp::stack_rewind: + pipeline->append_stack_rewind(); + break; + + case ProgramOp::invoke_shader: + if (!callbacks || !callbacks->appendShader(sk_bit_cast<intptr_t>(stage.ctx))) { + return false; + } + break; + + case ProgramOp::invoke_color_filter: + if (!callbacks || !callbacks->appendColorFilter(sk_bit_cast<intptr_t>(stage.ctx))) { + return false; + } + break; + + case ProgramOp::invoke_blender: + if (!callbacks || !callbacks->appendBlender(sk_bit_cast<intptr_t>(stage.ctx))) { + return false; + } + break; + + case ProgramOp::invoke_to_linear_srgb: + if (!callbacks) { + return false; + } + callbacks->toLinearSrgb(); + break; + + case ProgramOp::invoke_from_linear_srgb: + if (!callbacks) { + return false; + } + callbacks->fromLinearSrgb(); + break; + + case ProgramOp::label: { + // Remember the absolute pipeline position of this label. + int labelID = sk_bit_cast<intptr_t>(stage.ctx); + SkASSERT(labelID >= 0 && labelID < fNumLabels); + labelOffsets[labelID] = pipeline->getNumStages(); + break; + } + case ProgramOp::jump: + case ProgramOp::branch_if_all_lanes_active: + case ProgramOp::branch_if_any_lanes_active: + case ProgramOp::branch_if_no_lanes_active: + case ProgramOp::branch_if_no_active_lanes_eq: { + // The branch context contain a valid label ID at this point. + auto* branchCtx = static_cast<SkRasterPipeline_BranchCtx*>(stage.ctx); + int labelID = branchCtx->offset; + SkASSERT(labelID >= 0 && labelID < fNumLabels); + + // Replace the label ID in the branch context with the absolute pipeline position. + // We will go back over the branch targets at the end and fix them up. + branchCtx->offset = pipeline->getNumStages(); + + SkASSERT(branchContexts.size() == branchGoesToLabel.size()); + branchContexts.push_back(branchCtx); + branchGoesToLabel.push_back(labelID); + [[fallthrough]]; + } + default: + // Append a regular op to the program. + SkASSERT((int)stage.op < kNumRasterPipelineHighpOps); + pipeline->append((SkRasterPipelineOp)stage.op, stage.ctx); + break; + } + } + + // Now that we have assembled the program and know the pipeline positions of each label and + // branch, fix up every branch target. + SkASSERT(branchContexts.size() == branchGoesToLabel.size()); + for (int index = 0; index < branchContexts.size(); ++index) { + int branchFromIdx = branchContexts[index]->offset; + int branchToIdx = labelOffsets[branchGoesToLabel[index]]; + branchContexts[index]->offset = branchToIdx - branchFromIdx; + } + + return true; +} + +#endif + +void Program::makeStages(SkTArray<Stage>* pipeline, + SkArenaAlloc* alloc, + SkSpan<const float> uniforms, + const SlotData& slots) const { + SkASSERT(fNumUniformSlots == SkToInt(uniforms.size())); + + const int N = SkOpts::raster_pipeline_highp_stride; + StackDepthMap tempStackDepth; + int currentStack = 0; + int mostRecentRewind = 0; + + // Assemble a map holding the current stack-top for each temporary stack. Position each temp + // stack immediately after the previous temp stack; temp stacks are never allowed to overlap. + int pos = 0; + SkTHashMap<int, float*> tempStackMap; + for (auto& [idx, depth] : fTempStackMaxDepths) { + tempStackMap[idx] = slots.stack.begin() + (pos * N); + pos += depth; + } + + // Track labels that we have reached in processing. + SkBitSet labelsEncountered(fNumLabels); + + auto EmitStackRewindForBackwardsBranch = [&](int labelID) { + // If we have already encountered the label associated with this branch, this is a + // backwards branch. Add a stack-rewind immediately before the branch to ensure that + // long-running loops don't use an unbounded amount of stack space. + if (labelsEncountered.test(labelID)) { + this->appendStackRewind(pipeline); + mostRecentRewind = pipeline->size(); + } + }; + + // We can reuse constants from our arena by placing them in this map. + SkTHashMap<int, int*> constantLookupMap; // <constant value, pointer into arena> + + // Write each BuilderOp to the pipeline array. + pipeline->reserve_back(fInstructions.size()); + for (const Instruction& inst : fInstructions) { + auto SlotA = [&]() { return &slots.values[N * inst.fSlotA]; }; + auto SlotB = [&]() { return &slots.values[N * inst.fSlotB]; }; + auto UniformA = [&]() { return &uniforms[inst.fSlotA]; }; + float*& tempStackPtr = tempStackMap[currentStack]; + + switch (inst.fOp) { + case BuilderOp::label: + SkASSERT(inst.fImmA >= 0 && inst.fImmA < fNumLabels); + labelsEncountered.set(inst.fImmA); + pipeline->push_back({ProgramOp::label, context_bit_pun(inst.fImmA)}); + break; + + case BuilderOp::jump: + case BuilderOp::branch_if_all_lanes_active: + case BuilderOp::branch_if_any_lanes_active: + case BuilderOp::branch_if_no_lanes_active: { + SkASSERT(inst.fImmA >= 0 && inst.fImmA < fNumLabels); + EmitStackRewindForBackwardsBranch(inst.fImmA); + + auto* ctx = alloc->make<SkRasterPipeline_BranchCtx>(); + ctx->offset = inst.fImmA; + pipeline->push_back({(ProgramOp)inst.fOp, ctx}); + break; + } + case BuilderOp::branch_if_no_active_lanes_on_stack_top_equal: { + SkASSERT(inst.fImmA >= 0 && inst.fImmA < fNumLabels); + EmitStackRewindForBackwardsBranch(inst.fImmA); + + auto* ctx = alloc->make<SkRasterPipeline_BranchIfEqualCtx>(); + ctx->offset = inst.fImmA; + ctx->value = inst.fImmB; + ctx->ptr = reinterpret_cast<int*>(tempStackPtr - N); + pipeline->push_back({ProgramOp::branch_if_no_active_lanes_eq, ctx}); + break; + } + case BuilderOp::init_lane_masks: + pipeline->push_back({ProgramOp::init_lane_masks, nullptr}); + break; + + case BuilderOp::store_src_rg: + pipeline->push_back({ProgramOp::store_src_rg, SlotA()}); + break; + + case BuilderOp::store_src: + pipeline->push_back({ProgramOp::store_src, SlotA()}); + break; + + case BuilderOp::store_dst: + pipeline->push_back({ProgramOp::store_dst, SlotA()}); + break; + + case BuilderOp::store_device_xy01: + pipeline->push_back({ProgramOp::store_device_xy01, SlotA()}); + break; + + case BuilderOp::load_src: + pipeline->push_back({ProgramOp::load_src, SlotA()}); + break; + + case BuilderOp::load_dst: + pipeline->push_back({ProgramOp::load_dst, SlotA()}); + break; + + case ALL_SINGLE_SLOT_UNARY_OP_CASES: { + float* dst = tempStackPtr - (inst.fImmA * N); + this->appendSingleSlotUnaryOp(pipeline, (ProgramOp)inst.fOp, dst, inst.fImmA); + break; + } + case ALL_MULTI_SLOT_UNARY_OP_CASES: { + float* dst = tempStackPtr - (inst.fImmA * N); + this->appendMultiSlotUnaryOp(pipeline, (ProgramOp)inst.fOp, dst, inst.fImmA); + break; + } + case ALL_N_WAY_BINARY_OP_CASES: { + float* src = tempStackPtr - (inst.fImmA * N); + float* dst = tempStackPtr - (inst.fImmA * 2 * N); + this->appendAdjacentNWayBinaryOp(pipeline, alloc, (ProgramOp)inst.fOp, + dst, src, inst.fImmA); + break; + } + case ALL_MULTI_SLOT_BINARY_OP_CASES: { + float* src = tempStackPtr - (inst.fImmA * N); + float* dst = tempStackPtr - (inst.fImmA * 2 * N); + this->appendAdjacentMultiSlotBinaryOp(pipeline, alloc, (ProgramOp)inst.fOp, + dst, src, inst.fImmA); + break; + } + case ALL_N_WAY_TERNARY_OP_CASES: { + float* src1 = tempStackPtr - (inst.fImmA * N); + float* src0 = tempStackPtr - (inst.fImmA * 2 * N); + float* dst = tempStackPtr - (inst.fImmA * 3 * N); + this->appendAdjacentNWayTernaryOp(pipeline, alloc, (ProgramOp)inst.fOp, + dst, src0, src1, inst.fImmA); + break; + } + case ALL_MULTI_SLOT_TERNARY_OP_CASES: { + float* src1 = tempStackPtr - (inst.fImmA * N); + float* src0 = tempStackPtr - (inst.fImmA * 2 * N); + float* dst = tempStackPtr - (inst.fImmA * 3 * N); + this->appendAdjacentMultiSlotTernaryOp(pipeline, alloc, (ProgramOp)inst.fOp, + dst, src0, src1, inst.fImmA); + break; + } + case BuilderOp::select: { + float* src = tempStackPtr - (inst.fImmA * N); + float* dst = tempStackPtr - (inst.fImmA * 2 * N); + this->appendCopySlotsMasked(pipeline, alloc, dst, src, inst.fImmA); + break; + } + case BuilderOp::copy_slot_masked: + this->appendCopySlotsMasked(pipeline, alloc, SlotA(), SlotB(), inst.fImmA); + break; + + case BuilderOp::copy_slot_unmasked: + this->appendCopySlotsUnmasked(pipeline, alloc, SlotA(), SlotB(), inst.fImmA); + break; + + case BuilderOp::zero_slot_unmasked: + this->appendMultiSlotUnaryOp(pipeline, ProgramOp::zero_slot_unmasked, + SlotA(), inst.fImmA); + break; + + case BuilderOp::refract_4_floats: { + float* dst = tempStackPtr - (9 * N); + pipeline->push_back({ProgramOp::refract_4_floats, dst}); + break; + } + case BuilderOp::inverse_mat2: + case BuilderOp::inverse_mat3: + case BuilderOp::inverse_mat4: { + float* dst = tempStackPtr - (inst.fImmA * N); + pipeline->push_back({(ProgramOp)inst.fOp, dst}); + break; + } + case BuilderOp::dot_2_floats: + case BuilderOp::dot_3_floats: + case BuilderOp::dot_4_floats: { + float* dst = tempStackPtr - (inst.fImmA * 2 * N); + pipeline->push_back({(ProgramOp)inst.fOp, dst}); + break; + } + case BuilderOp::swizzle_1: + case BuilderOp::swizzle_2: + case BuilderOp::swizzle_3: + case BuilderOp::swizzle_4: { + auto* ctx = alloc->make<SkRasterPipeline_SwizzleCtx>(); + ctx->ptr = tempStackPtr - (N * inst.fImmA); + // Unpack component nybbles into byte-offsets pointing at stack slots. + unpack_nybbles_to_offsets(inst.fImmB, SkSpan(ctx->offsets)); + pipeline->push_back({(ProgramOp)inst.fOp, ctx}); + break; + } + case BuilderOp::shuffle: { + int consumed = inst.fImmA >> 16; + int generated = inst.fImmA & 0xFFFF; + + auto* ctx = alloc->make<SkRasterPipeline_ShuffleCtx>(); + ctx->ptr = tempStackPtr - (N * consumed); + ctx->count = generated; + // Unpack immB and immC from nybble form into the offset array. + unpack_nybbles_to_offsets(inst.fImmB, SkSpan(&ctx->offsets[0], 8)); + unpack_nybbles_to_offsets(inst.fImmC, SkSpan(&ctx->offsets[8], 8)); + pipeline->push_back({ProgramOp::shuffle, ctx}); + break; + } + case BuilderOp::push_src_rgba: { + float* dst = tempStackPtr; + pipeline->push_back({ProgramOp::store_src, dst}); + break; + } + case BuilderOp::push_dst_rgba: { + float* dst = tempStackPtr; + pipeline->push_back({ProgramOp::store_dst, dst}); + break; + } + case BuilderOp::pop_src_rg: { + float* src = tempStackPtr - (2 * N); + pipeline->push_back({ProgramOp::load_src_rg, src}); + break; + } + case BuilderOp::pop_src_rgba: { + float* src = tempStackPtr - (4 * N); + pipeline->push_back({ProgramOp::load_src, src}); + break; + } + case BuilderOp::pop_dst_rgba: { + float* src = tempStackPtr - (4 * N); + pipeline->push_back({ProgramOp::load_dst, src}); + break; + } + case BuilderOp::push_slots: { + float* dst = tempStackPtr; + this->appendCopySlotsUnmasked(pipeline, alloc, dst, SlotA(), inst.fImmA); + break; + } + case BuilderOp::copy_stack_to_slots_indirect: + case BuilderOp::push_slots_indirect: + case BuilderOp::push_uniform_indirect: { + // SlotA: fixed-range start + // SlotB: limit-range end + // immA: number of slots to copy + // immB: dynamic stack ID + ProgramOp op; + auto* ctx = alloc->make<SkRasterPipeline_CopyIndirectCtx>(); + ctx->indirectOffset = + reinterpret_cast<const uint32_t*>(tempStackMap[inst.fImmB]) - (1 * N); + ctx->indirectLimit = inst.fSlotB - inst.fSlotA - inst.fImmA; + ctx->slots = inst.fImmA; + if (inst.fOp == BuilderOp::push_slots_indirect) { + op = ProgramOp::copy_from_indirect_unmasked; + ctx->src = SlotA(); + ctx->dst = tempStackPtr; + } else if (inst.fOp == BuilderOp::push_uniform_indirect) { + op = ProgramOp::copy_from_indirect_uniform_unmasked; + ctx->src = UniformA(); + ctx->dst = tempStackPtr; + } else { + op = ProgramOp::copy_to_indirect_masked; + ctx->src = tempStackPtr - (ctx->slots * N); + ctx->dst = SlotA(); + } + pipeline->push_back({op, ctx}); + break; + } + case BuilderOp::push_uniform: { + float* dst = tempStackPtr; + this->appendCopyConstants(pipeline, alloc, dst, UniformA(), inst.fImmA); + break; + } + case BuilderOp::push_zeros: { + float* dst = tempStackPtr; + this->appendMultiSlotUnaryOp(pipeline, ProgramOp::zero_slot_unmasked, dst, + inst.fImmA); + break; + } + case BuilderOp::push_condition_mask: { + float* dst = tempStackPtr; + pipeline->push_back({ProgramOp::store_condition_mask, dst}); + break; + } + case BuilderOp::pop_condition_mask: { + float* src = tempStackPtr - (1 * N); + pipeline->push_back({ProgramOp::load_condition_mask, src}); + break; + } + case BuilderOp::merge_condition_mask: { + float* ptr = tempStackPtr - (2 * N); + pipeline->push_back({ProgramOp::merge_condition_mask, ptr}); + break; + } + case BuilderOp::push_loop_mask: { + float* dst = tempStackPtr; + pipeline->push_back({ProgramOp::store_loop_mask, dst}); + break; + } + case BuilderOp::pop_loop_mask: { + float* src = tempStackPtr - (1 * N); + pipeline->push_back({ProgramOp::load_loop_mask, src}); + break; + } + case BuilderOp::pop_and_reenable_loop_mask: { + float* src = tempStackPtr - (1 * N); + pipeline->push_back({ProgramOp::reenable_loop_mask, src}); + break; + } + case BuilderOp::reenable_loop_mask: + pipeline->push_back({ProgramOp::reenable_loop_mask, SlotA()}); + break; + + case BuilderOp::mask_off_loop_mask: + pipeline->push_back({ProgramOp::mask_off_loop_mask, nullptr}); + break; + + case BuilderOp::merge_loop_mask: { + float* src = tempStackPtr - (1 * N); + pipeline->push_back({ProgramOp::merge_loop_mask, src}); + break; + } + case BuilderOp::push_return_mask: { + float* dst = tempStackPtr; + pipeline->push_back({ProgramOp::store_return_mask, dst}); + break; + } + case BuilderOp::pop_return_mask: { + float* src = tempStackPtr - (1 * N); + pipeline->push_back({ProgramOp::load_return_mask, src}); + break; + } + case BuilderOp::mask_off_return_mask: + pipeline->push_back({ProgramOp::mask_off_return_mask, nullptr}); + break; + + case BuilderOp::copy_constant: + case BuilderOp::push_literal: { + float* dst = (inst.fOp == BuilderOp::push_literal) ? tempStackPtr : SlotA(); + int* constantPtr; + if (int** lookup = constantLookupMap.find(inst.fImmA)) { + constantPtr = *lookup; + } else { + constantPtr = alloc->make<int>(inst.fImmA); + constantLookupMap[inst.fImmA] = constantPtr; + } + SkASSERT(constantPtr); + this->appendCopyConstants(pipeline, alloc, dst, (float*)constantPtr,/*numSlots=*/1); + break; + } + case BuilderOp::copy_stack_to_slots: { + float* src = tempStackPtr - (inst.fImmB * N); + this->appendCopySlotsMasked(pipeline, alloc, SlotA(), src, inst.fImmA); + break; + } + case BuilderOp::copy_stack_to_slots_unmasked: { + float* src = tempStackPtr - (inst.fImmB * N); + this->appendCopySlotsUnmasked(pipeline, alloc, SlotA(), src, inst.fImmA); + break; + } + case BuilderOp::swizzle_copy_stack_to_slots: { + // SlotA: fixed-range start + // immA: number of swizzle components + // immB: swizzle components + // immC: offset from stack top + auto stage = (ProgramOp)((int)ProgramOp::swizzle_copy_slot_masked + inst.fImmA - 1); + auto* ctx = alloc->make<SkRasterPipeline_SwizzleCopyCtx>(); + ctx->src = tempStackPtr - (inst.fImmC * N); + ctx->dst = SlotA(); + unpack_nybbles_to_offsets(inst.fImmB, SkSpan(ctx->offsets)); + pipeline->push_back({stage, ctx}); + break; + } + case BuilderOp::push_clone: { + float* src = tempStackPtr - (inst.fImmB * N); + float* dst = tempStackPtr; + this->appendCopySlotsUnmasked(pipeline, alloc, dst, src, inst.fImmA); + break; + } + case BuilderOp::push_clone_from_stack: { + // immA: number of slots + // immB: other stack ID + // immC: offset from stack top + float* sourceStackPtr = tempStackMap[inst.fImmB]; + float* src = sourceStackPtr - (inst.fImmC * N); + float* dst = tempStackPtr; + this->appendCopySlotsUnmasked(pipeline, alloc, dst, src, inst.fImmA); + break; + } + case BuilderOp::push_clone_indirect_from_stack: { + // immA: number of slots + // immB: other stack ID + // immC: offset from stack top + // immD: dynamic stack ID + float* sourceStackPtr = tempStackMap[inst.fImmB]; + + auto* ctx = alloc->make<SkRasterPipeline_CopyIndirectCtx>(); + ctx->dst = tempStackPtr; + ctx->src = sourceStackPtr - (inst.fImmC * N); + ctx->indirectOffset = + reinterpret_cast<const uint32_t*>(tempStackMap[inst.fImmD]) - (1 * N); + ctx->indirectLimit = inst.fImmC - inst.fImmA; + ctx->slots = inst.fImmA; + pipeline->push_back({ProgramOp::copy_from_indirect_unmasked, ctx}); + break; + } + case BuilderOp::swizzle_copy_stack_to_slots_indirect: { + // SlotA: fixed-range start + // SlotB: limit-range end + // immA: number of swizzle components + // immB: swizzle components + // immC: offset from stack top + // immD: dynamic stack ID + auto* ctx = alloc->make<SkRasterPipeline_SwizzleCopyIndirectCtx>(); + ctx->src = tempStackPtr - (inst.fImmC * N); + ctx->dst = SlotA(); + ctx->indirectOffset = + reinterpret_cast<const uint32_t*>(tempStackMap[inst.fImmD]) - (1 * N); + ctx->indirectLimit = + inst.fSlotB - inst.fSlotA - (max_packed_nybble(inst.fImmB, inst.fImmA) + 1); + ctx->slots = inst.fImmA; + unpack_nybbles_to_offsets(inst.fImmB, SkSpan(ctx->offsets)); + pipeline->push_back({ProgramOp::swizzle_copy_to_indirect_masked, ctx}); + break; + } + case BuilderOp::case_op: { + auto* ctx = alloc->make<SkRasterPipeline_CaseOpCtx>(); + ctx->ptr = reinterpret_cast<int*>(tempStackPtr - 2 * N); + ctx->expectedValue = inst.fImmA; + pipeline->push_back({ProgramOp::case_op, ctx}); + break; + } + case BuilderOp::discard_stack: + break; + + case BuilderOp::set_current_stack: + currentStack = inst.fImmA; + break; + + case BuilderOp::invoke_shader: + case BuilderOp::invoke_color_filter: + case BuilderOp::invoke_blender: + pipeline->push_back({(ProgramOp)inst.fOp, context_bit_pun(inst.fImmA)}); + break; + + case BuilderOp::invoke_to_linear_srgb: + case BuilderOp::invoke_from_linear_srgb: + pipeline->push_back({(ProgramOp)inst.fOp, nullptr}); + break; + + default: + SkDEBUGFAILF("Raster Pipeline: unsupported instruction %d", (int)inst.fOp); + break; + } + + tempStackPtr += stack_usage(inst) * N; + SkASSERT(tempStackPtr >= slots.stack.begin()); + SkASSERT(tempStackPtr <= slots.stack.end()); + + // Periodically rewind the stack every 500 instructions. When SK_HAS_MUSTTAIL is set, + // rewinds are not actually used; the appendStackRewind call becomes a no-op. On platforms + // that don't support SK_HAS_MUSTTAIL, rewinding the stack periodically can prevent a + // potential stack overflow when running a long program. + int numPipelineStages = pipeline->size(); + if (numPipelineStages - mostRecentRewind > 500) { + this->appendStackRewind(pipeline); + mostRecentRewind = numPipelineStages; + } + } +} + +// Finds duplicate names in the program and disambiguates them with subscripts. +SkTArray<std::string> build_unique_slot_name_list(const SkRPDebugTrace* debugTrace) { + SkTArray<std::string> slotName; + if (debugTrace) { + slotName.reserve_back(debugTrace->fSlotInfo.size()); + + // The map consists of <variable name, <source position, unique name>>. + SkTHashMap<std::string_view, SkTHashMap<int, std::string>> uniqueNameMap; + + for (const SlotDebugInfo& slotInfo : debugTrace->fSlotInfo) { + // Look up this variable by its name and source position. + int pos = slotInfo.pos.valid() ? slotInfo.pos.startOffset() : 0; + SkTHashMap<int, std::string>& positionMap = uniqueNameMap[slotInfo.name]; + std::string& uniqueName = positionMap[pos]; + + // Have we seen this variable name/position combination before? + if (uniqueName.empty()) { + // This is a unique name/position pair. + uniqueName = slotInfo.name; + + // But if it's not a unique _name_, it deserves a subscript to disambiguate it. + int subscript = positionMap.count() - 1; + if (subscript > 0) { + for (char digit : std::to_string(subscript)) { + // U+2080 through U+2089 (₀₁₂₃₄₅₆₇₈₉) in UTF8: + uniqueName.push_back((char)0xE2); + uniqueName.push_back((char)0x82); + uniqueName.push_back((char)(0x80 + digit - '0')); + } + } + } + + slotName.push_back(uniqueName); + } + } + return slotName; +} + +void Program::dump(SkWStream* out) const { + // Allocate memory for the slot and uniform data, even though the program won't ever be + // executed. The program requires pointer ranges for managing its data, and ASAN will report + // errors if those pointers are pointing at unallocated memory. + SkArenaAlloc alloc(/*firstHeapAllocation=*/1000); + const int N = SkOpts::raster_pipeline_highp_stride; + SlotData slots = this->allocateSlotData(&alloc); + float* uniformPtr = alloc.makeArray<float>(fNumUniformSlots); + SkSpan<float> uniforms = SkSpan(uniformPtr, fNumUniformSlots); + + // Turn this program into an array of Raster Pipeline stages. + SkTArray<Stage> stages; + this->makeStages(&stages, &alloc, uniforms, slots); + + // Find the labels in the program, and keep track of their offsets. + SkTHashMap<int, int> labelToStageMap; // <label ID, stage index> + for (int index = 0; index < stages.size(); ++index) { + if (stages[index].op == ProgramOp::label) { + int labelID = sk_bit_cast<intptr_t>(stages[index].ctx); + SkASSERT(!labelToStageMap.find(labelID)); + labelToStageMap[labelID] = index; + } + } + + // Assign unique names to each variable slot; our trace might have multiple variables with the + // same name, which can make a dump hard to read. + SkTArray<std::string> slotName = build_unique_slot_name_list(fDebugTrace); + + // Emit the program's instruction list. + for (int index = 0; index < stages.size(); ++index) { + const Stage& stage = stages[index]; + + // Interpret the context value as a branch offset. + auto BranchOffset = [&](const SkRasterPipeline_BranchCtx* ctx) -> std::string { + // The context's offset field contains a label ID + int labelID = ctx->offset; + SkASSERT(labelToStageMap.find(labelID)); + int labelIndex = labelToStageMap[labelID]; + return SkSL::String::printf("%+d (label %d at #%d)", + labelIndex - index, labelID, labelIndex + 1); + }; + + // Print a 32-bit immediate value of unknown type (int/float). + auto Imm = [&](float immFloat, bool showAsFloat = true) -> std::string { + // Start with `0x3F800000` as a baseline. + uint32_t immUnsigned; + memcpy(&immUnsigned, &immFloat, sizeof(uint32_t)); + auto text = SkSL::String::printf("0x%08X", immUnsigned); + + // Extend it to `0x3F800000 (1.0)` for finite floating point values. + if (showAsFloat && std::isfinite(immFloat)) { + text += " ("; + text += skstd::to_string(immFloat); + text += ")"; + } + return text; + }; + + // Interpret the context pointer as a 32-bit immediate value of unknown type (int/float). + auto ImmCtx = [&](const void* ctx, bool showAsFloat = true) -> std::string { + float f; + memcpy(&f, &ctx, sizeof(float)); + return Imm(f, showAsFloat); + }; + + // Print `1` for single slots and `1..3` for ranges of slots. + auto AsRange = [](int first, int count) -> std::string { + std::string text = std::to_string(first); + if (count > 1) { + text += ".." + std::to_string(first + count - 1); + } + return text; + }; + + // Come up with a reasonable name for a range of slots, e.g.: + // `val`: slot range points at one variable, named val + // `val(0..1)`: slot range points at the first and second slot of val (which has 3+ slots) + // `foo, bar`: slot range fully covers two variables, named foo and bar + // `foo(3), bar(0)`: slot range covers the fourth slot of foo and the first slot of bar + auto SlotName = [&](SkSpan<const SlotDebugInfo> debugInfo, + SkSpan<const std::string> names, + SlotRange range) -> std::string { + SkASSERT(range.index >= 0 && (range.index + range.count) <= (int)debugInfo.size()); + + std::string text; + auto separator = SkSL::String::Separator(); + while (range.count > 0) { + const SlotDebugInfo& slotInfo = debugInfo[range.index]; + text += separator(); + text += names.empty() ? slotInfo.name : names[range.index]; + + // Figure out how many slots we can chomp in this iteration. + int entireVariable = slotInfo.columns * slotInfo.rows; + int slotsToChomp = std::min(range.count, entireVariable - slotInfo.componentIndex); + // If we aren't consuming an entire variable, from first slot to last... + if (slotsToChomp != entireVariable) { + // ... decorate it with a range suffix. + text += "(" + AsRange(slotInfo.componentIndex, slotsToChomp) + ")"; + } + range.index += slotsToChomp; + range.count -= slotsToChomp; + } + + return text; + }; + + // Attempts to interpret the passed-in pointer as a uniform range. + auto UniformPtrCtx = [&](const float* ptr, int numSlots) -> std::string { + const float* end = ptr + numSlots; + if (ptr >= uniforms.begin() && end <= uniforms.end()) { + int uniformIdx = ptr - uniforms.begin(); + if (fDebugTrace) { + // Handle pointers to named uniform slots. + std::string name = SlotName(fDebugTrace->fUniformInfo, /*names=*/{}, + {uniformIdx, numSlots}); + if (!name.empty()) { + return name; + } + } + // Handle pointers to uniforms (when no debug info exists). + return "u" + AsRange(uniformIdx, numSlots); + } + return {}; + }; + + // Attempts to interpret the passed-in pointer as a value slot range. + auto ValuePtrCtx = [&](const float* ptr, int numSlots) -> std::string { + const float* end = ptr + (N * numSlots); + if (ptr >= slots.values.begin() && end <= slots.values.end()) { + int valueIdx = ptr - slots.values.begin(); + SkASSERT((valueIdx % N) == 0); + valueIdx /= N; + if (fDebugTrace) { + // Handle pointers to named value slots. + std::string name = SlotName(fDebugTrace->fSlotInfo, slotName, + {valueIdx, numSlots}); + if (!name.empty()) { + return name; + } + } + // Handle pointers to value slots (when no debug info exists). + return "v" + AsRange(valueIdx, numSlots); + } + return {}; + }; + + // Interpret the context value as a pointer to `count` immediate values. + auto MultiImmCtx = [&](const float* ptr, int count) -> std::string { + // If this is a uniform, print it by name. + if (std::string text = UniformPtrCtx(ptr, count); !text.empty()) { + return text; + } + // Emit a single unbracketed immediate. + if (count == 1) { + return Imm(*ptr); + } + // Emit a list like `[0x00000000 (0.0), 0x3F80000 (1.0)]`. + std::string text = "["; + auto separator = SkSL::String::Separator(); + while (count--) { + text += separator(); + text += Imm(*ptr++); + } + return text + "]"; + }; + + // Interpret the context value as a generic pointer. + auto PtrCtx = [&](const void* ctx, int numSlots) -> std::string { + const float *ctxAsSlot = static_cast<const float*>(ctx); + // Check for uniform and value pointers. + if (std::string uniform = UniformPtrCtx(ctxAsSlot, numSlots); !uniform.empty()) { + return uniform; + } + if (std::string value = ValuePtrCtx(ctxAsSlot, numSlots); !value.empty()) { + return value; + } + // Handle pointers to temporary stack slots. + if (ctxAsSlot >= slots.stack.begin() && ctxAsSlot < slots.stack.end()) { + int stackIdx = ctxAsSlot - slots.stack.begin(); + SkASSERT((stackIdx % N) == 0); + return "$" + AsRange(stackIdx / N, numSlots); + } + // This pointer is out of our expected bounds; this generally isn't expected to happen. + return "ExternalPtr(" + AsRange(0, numSlots) + ")"; + }; + + // Interpret the context value as a pointer to two adjacent values. + auto AdjacentPtrCtx = [&](const void* ctx, + int numSlots) -> std::tuple<std::string, std::string> { + const float *ctxAsSlot = static_cast<const float*>(ctx); + return std::make_tuple(PtrCtx(ctxAsSlot, numSlots), + PtrCtx(ctxAsSlot + (N * numSlots), numSlots)); + }; + + // Interpret the context value as a pointer to three adjacent values. + auto Adjacent3PtrCtx = [&](const void* ctx, int numSlots) -> + std::tuple<std::string, std::string, std::string> { + const float *ctxAsSlot = static_cast<const float*>(ctx); + return std::make_tuple(PtrCtx(ctxAsSlot, numSlots), + PtrCtx(ctxAsSlot + (N * numSlots), numSlots), + PtrCtx(ctxAsSlot + (2 * N * numSlots), numSlots)); + }; + + // Interpret the context value as a BinaryOp structure for copy_n_slots (numSlots is + // dictated by the op itself). + auto BinaryOpCtx = [&](const void* v, + int numSlots) -> std::tuple<std::string, std::string> { + const auto *ctx = static_cast<const SkRasterPipeline_BinaryOpCtx*>(v); + return std::make_tuple(PtrCtx(ctx->dst, numSlots), + PtrCtx(ctx->src, numSlots)); + }; + + // Interpret the context value as a BinaryOp structure for copy_n_constants (numSlots is + // dictated by the op itself). + auto CopyConstantCtx = [&](const void* v, + int numSlots) -> std::tuple<std::string, std::string> { + const auto *ctx = static_cast<const SkRasterPipeline_BinaryOpCtx*>(v); + return std::make_tuple(PtrCtx(ctx->dst, numSlots), + MultiImmCtx(ctx->src, numSlots)); + }; + + // Interpret the context value as a BinaryOp structure (numSlots is inferred from the + // distance between pointers). + auto AdjacentBinaryOpCtx = [&](const void* v) -> std::tuple<std::string, std::string> { + const auto *ctx = static_cast<const SkRasterPipeline_BinaryOpCtx*>(v); + int numSlots = (ctx->src - ctx->dst) / N; + return AdjacentPtrCtx(ctx->dst, numSlots); + }; + + // Interpret the context value as a TernaryOp structure (numSlots is inferred from the + // distance between pointers). + auto AdjacentTernaryOpCtx = [&](const void* v) -> + std::tuple<std::string, std::string, std::string> { + const auto* ctx = static_cast<const SkRasterPipeline_TernaryOpCtx*>(v); + int numSlots = (ctx->src0 - ctx->dst) / N; + return Adjacent3PtrCtx(ctx->dst, numSlots); + }; + + // Stringize a span of swizzle offsets to the textual equivalent (`xyzw`). + auto SwizzleOffsetSpan = [&](SkSpan<const uint16_t> offsets) { + std::string src; + for (uint16_t offset : offsets) { + if (offset == (0 * N * sizeof(float))) { + src.push_back('x'); + } else if (offset == (1 * N * sizeof(float))) { + src.push_back('y'); + } else if (offset == (2 * N * sizeof(float))) { + src.push_back('z'); + } else if (offset == (3 * N * sizeof(float))) { + src.push_back('w'); + } else { + src.push_back('?'); + } + } + return src; + }; + + // When we decode a swizzle, we don't know the slot width of the original value; that's not + // preserved in the instruction encoding. (e.g., myFloat4.y would be indistinguishable from + // myFloat2.y.) We do our best to make a readable dump using the data we have. + auto SwizzleWidth = [&](SkSpan<const uint16_t> offsets) { + size_t highestComponent = *std::max_element(offsets.begin(), offsets.end()) / + (N * sizeof(float)); + size_t swizzleWidth = offsets.size(); + return std::max(swizzleWidth, highestComponent + 1); + }; + + // Stringize a swizzled pointer. + auto SwizzlePtr = [&](const float* ptr, SkSpan<const uint16_t> offsets) { + return "(" + PtrCtx(ptr, SwizzleWidth(offsets)) + ")." + SwizzleOffsetSpan(offsets); + }; + + // Interpret the context value as a Swizzle structure. + auto SwizzleCtx = [&](ProgramOp op, const void* v) -> std::tuple<std::string, std::string> { + const auto* ctx = static_cast<const SkRasterPipeline_SwizzleCtx*>(v); + int destSlots = (int)op - (int)BuilderOp::swizzle_1 + 1; + + return std::make_tuple(PtrCtx(ctx->ptr, destSlots), + SwizzlePtr(ctx->ptr, SkSpan(ctx->offsets, destSlots))); + }; + + // Interpret the context value as a SwizzleCopy structure. + auto SwizzleCopyCtx = [&](ProgramOp op, + const void* v) -> std::tuple<std::string, std::string> { + const auto* ctx = static_cast<const SkRasterPipeline_SwizzleCopyCtx*>(v); + int destSlots = (int)op - (int)BuilderOp::swizzle_copy_slot_masked + 1; + + return std::make_tuple(SwizzlePtr(ctx->dst, SkSpan(ctx->offsets, destSlots)), + PtrCtx(ctx->src, destSlots)); + }; + + // Interpret the context value as a Shuffle structure. + auto ShuffleCtx = [&](const void* v) -> std::tuple<std::string, std::string> { + const auto* ctx = static_cast<const SkRasterPipeline_ShuffleCtx*>(v); + + std::string dst = PtrCtx(ctx->ptr, ctx->count); + std::string src = "(" + dst + ")["; + for (int index = 0; index < ctx->count; ++index) { + if (ctx->offsets[index] % (N * sizeof(float))) { + src.push_back('?'); + } else { + src += std::to_string(ctx->offsets[index] / (N * sizeof(float))); + } + src.push_back(' '); + } + src.back() = ']'; + return std::make_tuple(dst, src); + }; + + std::string opArg1, opArg2, opArg3, opSwizzle; + using POp = ProgramOp; + switch (stage.op) { + case POp::label: + case POp::invoke_shader: + case POp::invoke_color_filter: + case POp::invoke_blender: + opArg1 = ImmCtx(stage.ctx, /*showAsFloat=*/false); + break; + + case POp::case_op: { + const auto* ctx = static_cast<SkRasterPipeline_CaseOpCtx*>(stage.ctx); + opArg1 = PtrCtx(ctx->ptr, 1); + opArg2 = PtrCtx(ctx->ptr + N, 1); + opArg3 = Imm(sk_bit_cast<float>(ctx->expectedValue), /*showAsFloat=*/false); + break; + } + case POp::swizzle_1: + case POp::swizzle_2: + case POp::swizzle_3: + case POp::swizzle_4: + std::tie(opArg1, opArg2) = SwizzleCtx(stage.op, stage.ctx); + break; + + case POp::swizzle_copy_slot_masked: + case POp::swizzle_copy_2_slots_masked: + case POp::swizzle_copy_3_slots_masked: + case POp::swizzle_copy_4_slots_masked: + std::tie(opArg1, opArg2) = SwizzleCopyCtx(stage.op, stage.ctx); + break; + + case POp::refract_4_floats: + std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 4); + opArg3 = PtrCtx((const float*)(stage.ctx) + (8 * N), 1); + break; + + case POp::dot_2_floats: + opArg1 = PtrCtx(stage.ctx, 1); + std::tie(opArg2, opArg3) = AdjacentPtrCtx(stage.ctx, 2); + break; + + case POp::dot_3_floats: + opArg1 = PtrCtx(stage.ctx, 1); + std::tie(opArg2, opArg3) = AdjacentPtrCtx(stage.ctx, 3); + break; + + case POp::dot_4_floats: + opArg1 = PtrCtx(stage.ctx, 1); + std::tie(opArg2, opArg3) = AdjacentPtrCtx(stage.ctx, 4); + break; + + case POp::shuffle: + std::tie(opArg1, opArg2) = ShuffleCtx(stage.ctx); + break; + + case POp::load_condition_mask: + case POp::store_condition_mask: + case POp::load_loop_mask: + case POp::store_loop_mask: + case POp::merge_loop_mask: + case POp::reenable_loop_mask: + case POp::load_return_mask: + case POp::store_return_mask: + case POp::zero_slot_unmasked: + case POp::bitwise_not_int: + case POp::cast_to_float_from_int: case POp::cast_to_float_from_uint: + case POp::cast_to_int_from_float: case POp::cast_to_uint_from_float: + case POp::abs_float: case POp::abs_int: + case POp::acos_float: + case POp::asin_float: + case POp::atan_float: + case POp::ceil_float: + case POp::cos_float: + case POp::exp_float: + case POp::exp2_float: + case POp::log_float: + case POp::log2_float: + case POp::floor_float: + case POp::invsqrt_float: + case POp::sin_float: + case POp::sqrt_float: + case POp::tan_float: + opArg1 = PtrCtx(stage.ctx, 1); + break; + + case POp::zero_2_slots_unmasked: + case POp::bitwise_not_2_ints: + case POp::load_src_rg: case POp::store_src_rg: + case POp::cast_to_float_from_2_ints: case POp::cast_to_float_from_2_uints: + case POp::cast_to_int_from_2_floats: case POp::cast_to_uint_from_2_floats: + case POp::abs_2_floats: case POp::abs_2_ints: + case POp::ceil_2_floats: + case POp::floor_2_floats: + case POp::invsqrt_2_floats: + opArg1 = PtrCtx(stage.ctx, 2); + break; + + case POp::zero_3_slots_unmasked: + case POp::bitwise_not_3_ints: + case POp::cast_to_float_from_3_ints: case POp::cast_to_float_from_3_uints: + case POp::cast_to_int_from_3_floats: case POp::cast_to_uint_from_3_floats: + case POp::abs_3_floats: case POp::abs_3_ints: + case POp::ceil_3_floats: + case POp::floor_3_floats: + case POp::invsqrt_3_floats: + opArg1 = PtrCtx(stage.ctx, 3); + break; + + case POp::load_src: + case POp::load_dst: + case POp::store_src: + case POp::store_dst: + case POp::store_device_xy01: + case POp::zero_4_slots_unmasked: + case POp::bitwise_not_4_ints: + case POp::cast_to_float_from_4_ints: case POp::cast_to_float_from_4_uints: + case POp::cast_to_int_from_4_floats: case POp::cast_to_uint_from_4_floats: + case POp::abs_4_floats: case POp::abs_4_ints: + case POp::ceil_4_floats: + case POp::floor_4_floats: + case POp::invsqrt_4_floats: + case POp::inverse_mat2: + opArg1 = PtrCtx(stage.ctx, 4); + break; + + case POp::inverse_mat3: + opArg1 = PtrCtx(stage.ctx, 9); + break; + + case POp::inverse_mat4: + opArg1 = PtrCtx(stage.ctx, 16); + break; + + + case POp::copy_constant: + std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 1); + break; + + case POp::copy_2_constants: + std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 2); + break; + + case POp::copy_3_constants: + std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 3); + break; + + case POp::copy_4_constants: + std::tie(opArg1, opArg2) = CopyConstantCtx(stage.ctx, 4); + break; + + case POp::copy_slot_masked: + case POp::copy_slot_unmasked: + std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 1); + break; + + case POp::copy_2_slots_masked: + case POp::copy_2_slots_unmasked: + std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 2); + break; + + case POp::copy_3_slots_masked: + case POp::copy_3_slots_unmasked: + std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 3); + break; + + case POp::copy_4_slots_masked: + case POp::copy_4_slots_unmasked: + std::tie(opArg1, opArg2) = BinaryOpCtx(stage.ctx, 4); + break; + + case POp::copy_from_indirect_unmasked: + case POp::copy_to_indirect_masked: { + const auto* ctx = static_cast<SkRasterPipeline_CopyIndirectCtx*>(stage.ctx); + // We don't incorporate the indirect-limit in the output + opArg1 = PtrCtx(ctx->dst, ctx->slots); + opArg2 = PtrCtx(ctx->src, ctx->slots); + opArg3 = PtrCtx(ctx->indirectOffset, 1); + break; + } + case POp::copy_from_indirect_uniform_unmasked: { + const auto* ctx = static_cast<SkRasterPipeline_CopyIndirectCtx*>(stage.ctx); + opArg1 = PtrCtx(ctx->dst, ctx->slots); + opArg2 = UniformPtrCtx(ctx->src, ctx->slots); + opArg3 = PtrCtx(ctx->indirectOffset, 1); + break; + } + case POp::swizzle_copy_to_indirect_masked: { + const auto* ctx = static_cast<SkRasterPipeline_SwizzleCopyIndirectCtx*>(stage.ctx); + opArg1 = PtrCtx(ctx->dst, SwizzleWidth(SkSpan(ctx->offsets, ctx->slots))); + opArg2 = PtrCtx(ctx->src, ctx->slots); + opArg3 = PtrCtx(ctx->indirectOffset, 1); + opSwizzle = SwizzleOffsetSpan(SkSpan(ctx->offsets, ctx->slots)); + break; + } + case POp::merge_condition_mask: + case POp::add_float: case POp::add_int: + case POp::sub_float: case POp::sub_int: + case POp::mul_float: case POp::mul_int: + case POp::div_float: case POp::div_int: case POp::div_uint: + case POp::bitwise_and_int: + case POp::bitwise_or_int: + case POp::bitwise_xor_int: + case POp::mod_float: + case POp::min_float: case POp::min_int: case POp::min_uint: + case POp::max_float: case POp::max_int: case POp::max_uint: + case POp::cmplt_float: case POp::cmplt_int: case POp::cmplt_uint: + case POp::cmple_float: case POp::cmple_int: case POp::cmple_uint: + case POp::cmpeq_float: case POp::cmpeq_int: + case POp::cmpne_float: case POp::cmpne_int: + std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 1); + break; + + case POp::mix_float: case POp::mix_int: + std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 1); + break; + + case POp::add_2_floats: case POp::add_2_ints: + case POp::sub_2_floats: case POp::sub_2_ints: + case POp::mul_2_floats: case POp::mul_2_ints: + case POp::div_2_floats: case POp::div_2_ints: case POp::div_2_uints: + case POp::bitwise_and_2_ints: + case POp::bitwise_or_2_ints: + case POp::bitwise_xor_2_ints: + case POp::mod_2_floats: + case POp::min_2_floats: case POp::min_2_ints: case POp::min_2_uints: + case POp::max_2_floats: case POp::max_2_ints: case POp::max_2_uints: + case POp::cmplt_2_floats: case POp::cmplt_2_ints: case POp::cmplt_2_uints: + case POp::cmple_2_floats: case POp::cmple_2_ints: case POp::cmple_2_uints: + case POp::cmpeq_2_floats: case POp::cmpeq_2_ints: + case POp::cmpne_2_floats: case POp::cmpne_2_ints: + std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 2); + break; + + case POp::mix_2_floats: case POp::mix_2_ints: + std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 2); + break; + + case POp::add_3_floats: case POp::add_3_ints: + case POp::sub_3_floats: case POp::sub_3_ints: + case POp::mul_3_floats: case POp::mul_3_ints: + case POp::div_3_floats: case POp::div_3_ints: case POp::div_3_uints: + case POp::bitwise_and_3_ints: + case POp::bitwise_or_3_ints: + case POp::bitwise_xor_3_ints: + case POp::mod_3_floats: + case POp::min_3_floats: case POp::min_3_ints: case POp::min_3_uints: + case POp::max_3_floats: case POp::max_3_ints: case POp::max_3_uints: + case POp::cmplt_3_floats: case POp::cmplt_3_ints: case POp::cmplt_3_uints: + case POp::cmple_3_floats: case POp::cmple_3_ints: case POp::cmple_3_uints: + case POp::cmpeq_3_floats: case POp::cmpeq_3_ints: + case POp::cmpne_3_floats: case POp::cmpne_3_ints: + std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 3); + break; + + case POp::mix_3_floats: case POp::mix_3_ints: + std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 3); + break; + + case POp::add_4_floats: case POp::add_4_ints: + case POp::sub_4_floats: case POp::sub_4_ints: + case POp::mul_4_floats: case POp::mul_4_ints: + case POp::div_4_floats: case POp::div_4_ints: case POp::div_4_uints: + case POp::bitwise_and_4_ints: + case POp::bitwise_or_4_ints: + case POp::bitwise_xor_4_ints: + case POp::mod_4_floats: + case POp::min_4_floats: case POp::min_4_ints: case POp::min_4_uints: + case POp::max_4_floats: case POp::max_4_ints: case POp::max_4_uints: + case POp::cmplt_4_floats: case POp::cmplt_4_ints: case POp::cmplt_4_uints: + case POp::cmple_4_floats: case POp::cmple_4_ints: case POp::cmple_4_uints: + case POp::cmpeq_4_floats: case POp::cmpeq_4_ints: + case POp::cmpne_4_floats: case POp::cmpne_4_ints: + std::tie(opArg1, opArg2) = AdjacentPtrCtx(stage.ctx, 4); + break; + + case POp::mix_4_floats: case POp::mix_4_ints: + std::tie(opArg1, opArg2, opArg3) = Adjacent3PtrCtx(stage.ctx, 4); + break; + + case POp::add_n_floats: case POp::add_n_ints: + case POp::sub_n_floats: case POp::sub_n_ints: + case POp::mul_n_floats: case POp::mul_n_ints: + case POp::div_n_floats: case POp::div_n_ints: case POp::div_n_uints: + case POp::bitwise_and_n_ints: + case POp::bitwise_or_n_ints: + case POp::bitwise_xor_n_ints: + case POp::mod_n_floats: + case POp::min_n_floats: case POp::min_n_ints: case POp::min_n_uints: + case POp::max_n_floats: case POp::max_n_ints: case POp::max_n_uints: + case POp::cmplt_n_floats: case POp::cmplt_n_ints: case POp::cmplt_n_uints: + case POp::cmple_n_floats: case POp::cmple_n_ints: case POp::cmple_n_uints: + case POp::cmpeq_n_floats: case POp::cmpeq_n_ints: + case POp::cmpne_n_floats: case POp::cmpne_n_ints: + case POp::atan2_n_floats: + case POp::pow_n_floats: + std::tie(opArg1, opArg2) = AdjacentBinaryOpCtx(stage.ctx); + break; + + case POp::mix_n_floats: case POp::mix_n_ints: + case POp::smoothstep_n_floats: + std::tie(opArg1, opArg2, opArg3) = AdjacentTernaryOpCtx(stage.ctx); + break; + + case POp::jump: + case POp::branch_if_all_lanes_active: + case POp::branch_if_any_lanes_active: + case POp::branch_if_no_lanes_active: + opArg1 = BranchOffset(static_cast<SkRasterPipeline_BranchCtx*>(stage.ctx)); + break; + + case POp::branch_if_no_active_lanes_eq: { + const auto* ctx = static_cast<SkRasterPipeline_BranchIfEqualCtx*>(stage.ctx); + opArg1 = BranchOffset(ctx); + opArg2 = PtrCtx(ctx->ptr, 1); + opArg3 = Imm(sk_bit_cast<float>(ctx->value)); + break; + } + default: + break; + } + + std::string_view opName; + switch (stage.op) { + #define M(x) case POp::x: opName = #x; break; + SK_RASTER_PIPELINE_OPS_ALL(M) + #undef M + case POp::label: opName = "label"; break; + case POp::invoke_shader: opName = "invoke_shader"; break; + case POp::invoke_color_filter: opName = "invoke_color_filter"; break; + case POp::invoke_blender: opName = "invoke_blender"; break; + case POp::invoke_to_linear_srgb: opName = "invoke_to_linear_srgb"; break; + case POp::invoke_from_linear_srgb: opName = "invoke_from_linear_srgb"; break; + } + + std::string opText; + switch (stage.op) { + case POp::init_lane_masks: + opText = "CondMask = LoopMask = RetMask = true"; + break; + + case POp::load_condition_mask: + opText = "CondMask = " + opArg1; + break; + + case POp::store_condition_mask: + opText = opArg1 + " = CondMask"; + break; + + case POp::merge_condition_mask: + opText = "CondMask = " + opArg1 + " & " + opArg2; + break; + + case POp::load_loop_mask: + opText = "LoopMask = " + opArg1; + break; + + case POp::store_loop_mask: + opText = opArg1 + " = LoopMask"; + break; + + case POp::mask_off_loop_mask: + opText = "LoopMask &= ~(CondMask & LoopMask & RetMask)"; + break; + + case POp::reenable_loop_mask: + opText = "LoopMask |= " + opArg1; + break; + + case POp::merge_loop_mask: + opText = "LoopMask &= " + opArg1; + break; + + case POp::load_return_mask: + opText = "RetMask = " + opArg1; + break; + + case POp::store_return_mask: + opText = opArg1 + " = RetMask"; + break; + + case POp::mask_off_return_mask: + opText = "RetMask &= ~(CondMask & LoopMask & RetMask)"; + break; + + case POp::store_src_rg: + opText = opArg1 + " = src.rg"; + break; + + case POp::store_src: + opText = opArg1 + " = src.rgba"; + break; + + case POp::store_dst: + opText = opArg1 + " = dst.rgba"; + break; + + case POp::store_device_xy01: + opText = opArg1 + " = DeviceCoords.xy01"; + break; + + case POp::load_src_rg: + opText = "src.rg = " + opArg1; + break; + + case POp::load_src: + opText = "src.rgba = " + opArg1; + break; + + case POp::load_dst: + opText = "dst.rgba = " + opArg1; + break; + + case POp::bitwise_and_int: + case POp::bitwise_and_2_ints: + case POp::bitwise_and_3_ints: + case POp::bitwise_and_4_ints: + case POp::bitwise_and_n_ints: + opText = opArg1 + " &= " + opArg2; + break; + + case POp::bitwise_or_int: + case POp::bitwise_or_2_ints: + case POp::bitwise_or_3_ints: + case POp::bitwise_or_4_ints: + case POp::bitwise_or_n_ints: + opText = opArg1 + " |= " + opArg2; + break; + + case POp::bitwise_xor_int: + case POp::bitwise_xor_2_ints: + case POp::bitwise_xor_3_ints: + case POp::bitwise_xor_4_ints: + case POp::bitwise_xor_n_ints: + opText = opArg1 + " ^= " + opArg2; + break; + + case POp::bitwise_not_int: + case POp::bitwise_not_2_ints: + case POp::bitwise_not_3_ints: + case POp::bitwise_not_4_ints: + opText = opArg1 + " = ~" + opArg1; + break; + + case POp::cast_to_float_from_int: + case POp::cast_to_float_from_2_ints: + case POp::cast_to_float_from_3_ints: + case POp::cast_to_float_from_4_ints: + opText = opArg1 + " = IntToFloat(" + opArg1 + ")"; + break; + + case POp::cast_to_float_from_uint: + case POp::cast_to_float_from_2_uints: + case POp::cast_to_float_from_3_uints: + case POp::cast_to_float_from_4_uints: + opText = opArg1 + " = UintToFloat(" + opArg1 + ")"; + break; + + case POp::cast_to_int_from_float: + case POp::cast_to_int_from_2_floats: + case POp::cast_to_int_from_3_floats: + case POp::cast_to_int_from_4_floats: + opText = opArg1 + " = FloatToInt(" + opArg1 + ")"; + break; + + case POp::cast_to_uint_from_float: + case POp::cast_to_uint_from_2_floats: + case POp::cast_to_uint_from_3_floats: + case POp::cast_to_uint_from_4_floats: + opText = opArg1 + " = FloatToUint(" + opArg1 + ")"; + break; + + case POp::copy_slot_masked: case POp::copy_2_slots_masked: + case POp::copy_3_slots_masked: case POp::copy_4_slots_masked: + case POp::swizzle_copy_slot_masked: case POp::swizzle_copy_2_slots_masked: + case POp::swizzle_copy_3_slots_masked: case POp::swizzle_copy_4_slots_masked: + opText = opArg1 + " = Mask(" + opArg2 + ")"; + break; + + case POp::copy_constant: case POp::copy_2_constants: + case POp::copy_3_constants: case POp::copy_4_constants: + case POp::copy_slot_unmasked: case POp::copy_2_slots_unmasked: + case POp::copy_3_slots_unmasked: case POp::copy_4_slots_unmasked: + case POp::swizzle_1: case POp::swizzle_2: + case POp::swizzle_3: case POp::swizzle_4: + case POp::shuffle: + opText = opArg1 + " = " + opArg2; + break; + + case POp::copy_from_indirect_unmasked: + case POp::copy_from_indirect_uniform_unmasked: + opText = opArg1 + " = Indirect(" + opArg2 + " + " + opArg3 + ")"; + break; + + case POp::copy_to_indirect_masked: + opText = "Indirect(" + opArg1 + " + " + opArg3 + ") = Mask(" + opArg2 + ")"; + break; + + case POp::swizzle_copy_to_indirect_masked: + opText = "Indirect(" + opArg1 + " + " + opArg3 + ")." + opSwizzle + " = Mask(" + + opArg2 + ")"; + break; + + case POp::zero_slot_unmasked: case POp::zero_2_slots_unmasked: + case POp::zero_3_slots_unmasked: case POp::zero_4_slots_unmasked: + opText = opArg1 + " = 0"; + break; + + case POp::abs_float: case POp::abs_int: + case POp::abs_2_floats: case POp::abs_2_ints: + case POp::abs_3_floats: case POp::abs_3_ints: + case POp::abs_4_floats: case POp::abs_4_ints: + opText = opArg1 + " = abs(" + opArg1 + ")"; + break; + + case POp::acos_float: + opText = opArg1 + " = acos(" + opArg1 + ")"; + break; + + case POp::asin_float: + opText = opArg1 + " = asin(" + opArg1 + ")"; + break; + + case POp::atan_float: + opText = opArg1 + " = atan(" + opArg1 + ")"; + break; + + case POp::atan2_n_floats: + opText = opArg1 + " = atan2(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::ceil_float: + case POp::ceil_2_floats: + case POp::ceil_3_floats: + case POp::ceil_4_floats: + opText = opArg1 + " = ceil(" + opArg1 + ")"; + break; + + case POp::cos_float: + opText = opArg1 + " = cos(" + opArg1 + ")"; + break; + + case POp::refract_4_floats: + opText = opArg1 + " = refract(" + opArg1 + ", " + opArg2 + ", " + opArg3 + ")"; + break; + + case POp::dot_2_floats: + case POp::dot_3_floats: + case POp::dot_4_floats: + opText = opArg1 + " = dot(" + opArg2 + ", " + opArg3 + ")"; + break; + + case POp::exp_float: + opText = opArg1 + " = exp(" + opArg1 + ")"; + break; + + case POp::exp2_float: + opText = opArg1 + " = exp2(" + opArg1 + ")"; + break; + + case POp::log_float: + opText = opArg1 + " = log(" + opArg1 + ")"; + break; + + case POp::log2_float: + opText = opArg1 + " = log2(" + opArg1 + ")"; + break; + + case POp::pow_n_floats: + opText = opArg1 + " = pow(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::sin_float: + opText = opArg1 + " = sin(" + opArg1 + ")"; + break; + + case POp::sqrt_float: + opText = opArg1 + " = sqrt(" + opArg1 + ")"; + break; + + case POp::tan_float: + opText = opArg1 + " = tan(" + opArg1 + ")"; + break; + + case POp::floor_float: + case POp::floor_2_floats: + case POp::floor_3_floats: + case POp::floor_4_floats: + opText = opArg1 + " = floor(" + opArg1 + ")"; + break; + + case POp::invsqrt_float: + case POp::invsqrt_2_floats: + case POp::invsqrt_3_floats: + case POp::invsqrt_4_floats: + opText = opArg1 + " = inversesqrt(" + opArg1 + ")"; + break; + + case POp::inverse_mat2: + case POp::inverse_mat3: + case POp::inverse_mat4: + opText = opArg1 + " = inverse(" + opArg1 + ")"; + break; + + case POp::add_float: case POp::add_int: + case POp::add_2_floats: case POp::add_2_ints: + case POp::add_3_floats: case POp::add_3_ints: + case POp::add_4_floats: case POp::add_4_ints: + case POp::add_n_floats: case POp::add_n_ints: + opText = opArg1 + " += " + opArg2; + break; + + case POp::sub_float: case POp::sub_int: + case POp::sub_2_floats: case POp::sub_2_ints: + case POp::sub_3_floats: case POp::sub_3_ints: + case POp::sub_4_floats: case POp::sub_4_ints: + case POp::sub_n_floats: case POp::sub_n_ints: + opText = opArg1 + " -= " + opArg2; + break; + + case POp::mul_float: case POp::mul_int: + case POp::mul_2_floats: case POp::mul_2_ints: + case POp::mul_3_floats: case POp::mul_3_ints: + case POp::mul_4_floats: case POp::mul_4_ints: + case POp::mul_n_floats: case POp::mul_n_ints: + opText = opArg1 + " *= " + opArg2; + break; + + case POp::div_float: case POp::div_int: case POp::div_uint: + case POp::div_2_floats: case POp::div_2_ints: case POp::div_2_uints: + case POp::div_3_floats: case POp::div_3_ints: case POp::div_3_uints: + case POp::div_4_floats: case POp::div_4_ints: case POp::div_4_uints: + case POp::div_n_floats: case POp::div_n_ints: case POp::div_n_uints: + opText = opArg1 + " /= " + opArg2; + break; + + case POp::mod_float: + case POp::mod_2_floats: + case POp::mod_3_floats: + case POp::mod_4_floats: + case POp::mod_n_floats: + opText = opArg1 + " = mod(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::min_float: case POp::min_int: case POp::min_uint: + case POp::min_2_floats: case POp::min_2_ints: case POp::min_2_uints: + case POp::min_3_floats: case POp::min_3_ints: case POp::min_3_uints: + case POp::min_4_floats: case POp::min_4_ints: case POp::min_4_uints: + case POp::min_n_floats: case POp::min_n_ints: case POp::min_n_uints: + opText = opArg1 + " = min(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::max_float: case POp::max_int: case POp::max_uint: + case POp::max_2_floats: case POp::max_2_ints: case POp::max_2_uints: + case POp::max_3_floats: case POp::max_3_ints: case POp::max_3_uints: + case POp::max_4_floats: case POp::max_4_ints: case POp::max_4_uints: + case POp::max_n_floats: case POp::max_n_ints: case POp::max_n_uints: + opText = opArg1 + " = max(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::cmplt_float: case POp::cmplt_int: case POp::cmplt_uint: + case POp::cmplt_2_floats: case POp::cmplt_2_ints: case POp::cmplt_2_uints: + case POp::cmplt_3_floats: case POp::cmplt_3_ints: case POp::cmplt_3_uints: + case POp::cmplt_4_floats: case POp::cmplt_4_ints: case POp::cmplt_4_uints: + case POp::cmplt_n_floats: case POp::cmplt_n_ints: case POp::cmplt_n_uints: + opText = opArg1 + " = lessThan(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::cmple_float: case POp::cmple_int: case POp::cmple_uint: + case POp::cmple_2_floats: case POp::cmple_2_ints: case POp::cmple_2_uints: + case POp::cmple_3_floats: case POp::cmple_3_ints: case POp::cmple_3_uints: + case POp::cmple_4_floats: case POp::cmple_4_ints: case POp::cmple_4_uints: + case POp::cmple_n_floats: case POp::cmple_n_ints: case POp::cmple_n_uints: + opText = opArg1 + " = lessThanEqual(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::cmpeq_float: case POp::cmpeq_int: + case POp::cmpeq_2_floats: case POp::cmpeq_2_ints: + case POp::cmpeq_3_floats: case POp::cmpeq_3_ints: + case POp::cmpeq_4_floats: case POp::cmpeq_4_ints: + case POp::cmpeq_n_floats: case POp::cmpeq_n_ints: + opText = opArg1 + " = equal(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::cmpne_float: case POp::cmpne_int: + case POp::cmpne_2_floats: case POp::cmpne_2_ints: + case POp::cmpne_3_floats: case POp::cmpne_3_ints: + case POp::cmpne_4_floats: case POp::cmpne_4_ints: + case POp::cmpne_n_floats: case POp::cmpne_n_ints: + opText = opArg1 + " = notEqual(" + opArg1 + ", " + opArg2 + ")"; + break; + + case POp::mix_float: case POp::mix_int: + case POp::mix_2_floats: case POp::mix_2_ints: + case POp::mix_3_floats: case POp::mix_3_ints: + case POp::mix_4_floats: case POp::mix_4_ints: + case POp::mix_n_floats: case POp::mix_n_ints: + opText = opArg1 + " = mix(" + opArg2 + ", " + opArg3 + ", " + opArg1 + ")"; + break; + + case POp::smoothstep_n_floats: + opText = opArg1 + " = smoothstep(" + opArg1 + ", " + opArg2 + ", " + opArg3 + ")"; + break; + + case POp::jump: + case POp::branch_if_all_lanes_active: + case POp::branch_if_any_lanes_active: + case POp::branch_if_no_lanes_active: + case POp::invoke_shader: + case POp::invoke_color_filter: + case POp::invoke_blender: + opText = std::string(opName) + " " + opArg1; + break; + + case POp::invoke_to_linear_srgb: + opText = "src.rgba = toLinearSrgb(src.rgba)"; + break; + + case POp::invoke_from_linear_srgb: + opText = "src.rgba = fromLinearSrgb(src.rgba)"; + break; + + case POp::branch_if_no_active_lanes_eq: + opText = "branch " + opArg1 + " if no lanes of " + opArg2 + " == " + opArg3; + break; + + case POp::label: + opText = "label " + opArg1; + break; + + case POp::case_op: { + opText = "if (" + opArg1 + " == " + opArg3 + + ") { LoopMask = true; " + opArg2 + " = false; }"; + break; + } + default: + break; + } + + opName = opName.substr(0, 30); + if (!opText.empty()) { + out->writeText(SkSL::String::printf("% 5d. %-30.*s %s\n", + index + 1, + (int)opName.size(), opName.data(), + opText.c_str()).c_str()); + } else { + out->writeText(SkSL::String::printf("% 5d. %.*s\n", + index + 1, + (int)opName.size(), opName.data()).c_str()); + } + } +} + +} // namespace RP +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.h b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.h new file mode 100644 index 0000000000..a0717fa539 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineBuilder.h @@ -0,0 +1,655 @@ +/* + * 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_RASTERPIPELINECODEBUILDER +#define SKSL_RASTERPIPELINECODEBUILDER + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/base/SkTArray.h" +#include "src/base/SkUtils.h" +#include "src/core/SkRasterPipelineOpList.h" +#include "src/core/SkTHash.h" + +#include <cstdint> +#include <initializer_list> +#include <memory> + +class SkArenaAlloc; +class SkRasterPipeline; +class SkWStream; + +namespace SkSL { + +class SkRPDebugTrace; + +namespace RP { + +// A single scalar in our program consumes one slot. +using Slot = int; +constexpr Slot NA = -1; + +// Scalars, vectors, and matrices can be represented as a range of slot indices. +struct SlotRange { + Slot index = 0; + int count = 0; +}; + +// An RP::Program will consist entirely of ProgramOps. The ProgramOps list is a superset of the +// native SkRasterPipelineOps op-list. It also has a few extra ops to indicate child-effect +// invocation, and a `label` op to indicate branch targets. +enum class ProgramOp { + // A finished program can contain any native Raster Pipeline op... + #define M(stage) stage, + SK_RASTER_PIPELINE_OPS_ALL(M) + #undef M + + // ... has branch targets... + label, + + // ... can invoke child programs ... + invoke_shader, + invoke_color_filter, + invoke_blender, + + // ... and can invoke color space transforms. + invoke_to_linear_srgb, + invoke_from_linear_srgb, +}; + +// BuilderOps are a superset of ProgramOps. They are used by the RP::Builder, which works in terms +// of Instructions; Instructions are slightly more expressive than raw SkRasterPipelineOps. In +// particular, the Builder supports stacks for pushing and popping scratch values. +// RP::Program::makeStages is responsible for rewriting Instructions/BuilderOps into an array of +// RP::Program::Stages, which will contain only native SkRasterPipelineOps and (optionally) +// child-effect invocations. +enum class BuilderOp { + // An in-flight program can contain all the native Raster Pipeline ops... + #define M(stage) stage, + SK_RASTER_PIPELINE_OPS_ALL(M) + #undef M + + // ... has branch targets... + label, + + // ... can invoke child programs... + invoke_shader, + invoke_color_filter, + invoke_blender, + + // ... can invoke color space transforms ... + invoke_to_linear_srgb, + invoke_from_linear_srgb, + + // ... and also has Builder-specific ops. These ops generally interface with the stack, and are + // converted into ProgramOps during `makeStages`. + push_literal, + push_slots, + push_slots_indirect, + push_uniform, + push_uniform_indirect, + push_zeros, + push_clone, + push_clone_from_stack, + push_clone_indirect_from_stack, + copy_stack_to_slots, + copy_stack_to_slots_unmasked, + copy_stack_to_slots_indirect, + swizzle_copy_stack_to_slots, + swizzle_copy_stack_to_slots_indirect, + discard_stack, + select, + push_condition_mask, + pop_condition_mask, + push_loop_mask, + pop_loop_mask, + pop_and_reenable_loop_mask, + push_return_mask, + pop_return_mask, + push_src_rgba, + push_dst_rgba, + pop_src_rg, + pop_src_rgba, + pop_dst_rgba, + set_current_stack, + branch_if_no_active_lanes_on_stack_top_equal, + unsupported +}; + +// If the child-invocation enums are not in sync between enums, program creation will not work. +static_assert((int)ProgramOp::label == (int)BuilderOp::label); +static_assert((int)ProgramOp::invoke_shader == (int)BuilderOp::invoke_shader); +static_assert((int)ProgramOp::invoke_color_filter == (int)BuilderOp::invoke_color_filter); +static_assert((int)ProgramOp::invoke_blender == (int)BuilderOp::invoke_blender); +static_assert((int)ProgramOp::invoke_to_linear_srgb == (int)BuilderOp::invoke_to_linear_srgb); +static_assert((int)ProgramOp::invoke_from_linear_srgb == (int)BuilderOp::invoke_from_linear_srgb); + +// Represents a single raster-pipeline SkSL instruction. +struct Instruction { + Instruction(BuilderOp op, std::initializer_list<Slot> slots, + int a = 0, int b = 0, int c = 0, int d = 0) + : fOp(op), fImmA(a), fImmB(b), fImmC(c), fImmD(d) { + auto iter = slots.begin(); + if (iter != slots.end()) { fSlotA = *iter++; } + if (iter != slots.end()) { fSlotB = *iter++; } + SkASSERT(iter == slots.end()); + } + + BuilderOp fOp; + Slot fSlotA = NA; + Slot fSlotB = NA; + int fImmA = 0; + int fImmB = 0; + int fImmC = 0; + int fImmD = 0; +}; + +class Callbacks { +public: + virtual ~Callbacks() = default; + + virtual bool appendShader(int index) = 0; + virtual bool appendColorFilter(int index) = 0; + virtual bool appendBlender(int index) = 0; + + virtual void toLinearSrgb() = 0; + virtual void fromLinearSrgb() = 0; +}; + +class Program { +public: + Program(SkTArray<Instruction> instrs, + int numValueSlots, + int numUniformSlots, + int numLabels, + SkRPDebugTrace* debugTrace); + +#if !defined(SKSL_STANDALONE) + bool appendStages(SkRasterPipeline* pipeline, + SkArenaAlloc* alloc, + Callbacks* callbacks, + SkSpan<const float> uniforms) const; +#endif + + void dump(SkWStream* out) const; + +private: + using StackDepthMap = SkTHashMap<int, int>; // <stack index, depth of stack> + + struct SlotData { + SkSpan<float> values; + SkSpan<float> stack; + }; + SlotData allocateSlotData(SkArenaAlloc* alloc) const; + + struct Stage { + ProgramOp op; + void* ctx; + }; + void makeStages(SkTArray<Stage>* pipeline, + SkArenaAlloc* alloc, + SkSpan<const float> uniforms, + const SlotData& slots) const; + void optimize(); + StackDepthMap tempStackMaxDepths() const; + + // These methods are used to split up large multi-slot operations into multiple ops as needed. + void appendCopy(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp baseStage, + float* dst, int dstStride, const float* src, int srcStride, int numSlots) const; + void appendCopySlotsUnmasked(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + float* dst, const float* src, int numSlots) const; + void appendCopySlotsMasked(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + float* dst, const float* src, int numSlots) const; + void appendCopyConstants(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + float* dst, const float* src, int numSlots) const; + + // Appends a single-slot single-input math operation to the pipeline. The op `stage` will + // appended `numSlots` times, starting at position `dst` and advancing one slot for each + // subsequent invocation. + void appendSingleSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp stage, + float* dst, int numSlots) const; + + // Appends a multi-slot single-input math operation to the pipeline. `baseStage` must refer to + // an single-slot "apply_op" stage, which must be immediately followed by specializations for + // 2-4 slots. For instance, {`zero_slot`, `zero_2_slots`, `zero_3_slots`, `zero_4_slots`} + // must be contiguous ops in the stage list, listed in that order; pass `zero_slot` and we + // pick the appropriate op based on `numSlots`. + void appendMultiSlotUnaryOp(SkTArray<Stage>* pipeline, ProgramOp baseStage, + float* dst, int numSlots) const; + + // Appends a two-input math operation to the pipeline. `src` must be _immediately_ after `dst` + // in memory. `baseStage` must refer to an unbounded "apply_to_n_slots" stage. A BinaryOpCtx + // will be used to pass pointers to the destination and source; the delta between the two + // pointers implicitly gives the number of slots. + void appendAdjacentNWayBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp stage, + float* dst, const float* src, int numSlots) const; + + // Appends a multi-slot two-input math operation to the pipeline. `src` must be _immediately_ + // after `dst` in memory. `baseStage` must refer to an unbounded "apply_to_n_slots" stage, which + // must be immediately followed by specializations for 1-4 slots. For instance, {`add_n_floats`, + // `add_float`, `add_2_floats`, `add_3_floats`, `add_4_floats`} must be contiguous ops in the + // stage list, listed in that order; pass `add_n_floats` and we pick the appropriate op based on + // `numSlots`. + void appendAdjacentMultiSlotBinaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp baseStage, + float* dst, const float* src, int numSlots) const; + + // Appends a multi-slot math operation having three inputs (dst, src0, src1) and one output + // (dst) to the pipeline. The three inputs must be _immediately_ adjacent in memory. `baseStage` + // must refer to an unbounded "apply_to_n_slots" stage, which must be immediately followed by + // specializations for 1-4 slots. + void appendAdjacentMultiSlotTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp stage, float* dst, + const float* src0, const float* src1, int numSlots) const; + + // Appends a math operation having three inputs (dst, src0, src1) and one output (dst) to the + // pipeline. The three inputs must be _immediately_ adjacent in memory. `baseStage` must refer + // to an unbounded "apply_to_n_slots" stage. A TernaryOpCtx will be used to pass pointers to the + // destination and sources; the delta between the each pointer implicitly gives the slot count. + void appendAdjacentNWayTernaryOp(SkTArray<Stage>* pipeline, SkArenaAlloc* alloc, + ProgramOp stage, float* dst, + const float* src0, const float* src1, int numSlots) const; + + // Appends a stack_rewind op on platforms where it is needed (when SK_HAS_MUSTTAIL is not set). + void appendStackRewind(SkTArray<Stage>* pipeline) const; + + SkTArray<Instruction> fInstructions; + int fNumValueSlots = 0; + int fNumUniformSlots = 0; + int fNumTempStackSlots = 0; + int fNumLabels = 0; + SkTHashMap<int, int> fTempStackMaxDepths; + SkRPDebugTrace* fDebugTrace = nullptr; +}; + +class Builder { +public: + /** Finalizes and optimizes the program. */ + std::unique_ptr<Program> finish(int numValueSlots, + int numUniformSlots, + SkRPDebugTrace* debugTrace = nullptr); + /** + * Peels off a label ID for use in the program. Set the label's position in the program with + * the `label` instruction. Actually branch to the target with an instruction like + * `branch_if_any_lanes_active` or `jump`. + */ + int nextLabelID() { + return fNumLabels++; + } + + /** + * The builder keeps track of the state of execution masks; when we know that the execution + * mask is unaltered, we can generate simpler code. Code which alters the execution mask is + * required to enable this flag. + */ + void enableExecutionMaskWrites() { + ++fExecutionMaskWritesEnabled; + } + + void disableExecutionMaskWrites() { + SkASSERT(this->executionMaskWritesAreEnabled()); + --fExecutionMaskWritesEnabled; + } + + bool executionMaskWritesAreEnabled() { + return fExecutionMaskWritesEnabled > 0; + } + + /** Assemble a program from the Raster Pipeline instructions below. */ + void init_lane_masks() { + fInstructions.push_back({BuilderOp::init_lane_masks, {}}); + } + + void store_src_rg(SlotRange slots) { + SkASSERT(slots.count == 2); + fInstructions.push_back({BuilderOp::store_src_rg, {slots.index}}); + } + + void store_src(SlotRange slots) { + SkASSERT(slots.count == 4); + fInstructions.push_back({BuilderOp::store_src, {slots.index}}); + } + + void store_dst(SlotRange slots) { + SkASSERT(slots.count == 4); + fInstructions.push_back({BuilderOp::store_dst, {slots.index}}); + } + + void store_device_xy01(SlotRange slots) { + SkASSERT(slots.count == 4); + fInstructions.push_back({BuilderOp::store_device_xy01, {slots.index}}); + } + + void load_src(SlotRange slots) { + SkASSERT(slots.count == 4); + fInstructions.push_back({BuilderOp::load_src, {slots.index}}); + } + + void load_dst(SlotRange slots) { + SkASSERT(slots.count == 4); + fInstructions.push_back({BuilderOp::load_dst, {slots.index}}); + } + + void set_current_stack(int stackIdx) { + fInstructions.push_back({BuilderOp::set_current_stack, {}, stackIdx}); + } + + // Inserts a label into the instruction stream. + void label(int labelID); + + // Unconditionally branches to a label. + void jump(int labelID); + + // Branches to a label if the execution mask is active in every lane. + void branch_if_all_lanes_active(int labelID); + + // Branches to a label if the execution mask is active in any lane. + void branch_if_any_lanes_active(int labelID); + + // Branches to a label if the execution mask is inactive across all lanes. + void branch_if_no_lanes_active(int labelID); + + // Branches to a label if the top value on the stack is _not_ equal to `value` in any lane. + void branch_if_no_active_lanes_on_stack_top_equal(int value, int labelID); + + // We use the same SkRasterPipeline op regardless of the literal type, and bitcast the value. + void push_literal_f(float val) { + this->push_literal_i(sk_bit_cast<int32_t>(val)); + } + + void push_literal_i(int32_t val) { + if (val == 0) { + this->push_zeros(1); + } else { + fInstructions.push_back({BuilderOp::push_literal, {}, val}); + } + } + + void push_literal_u(uint32_t val) { + this->push_literal_i(sk_bit_cast<int32_t>(val)); + } + + // Translates into copy_constants (from uniforms into temp stack) in Raster Pipeline. + void push_uniform(SlotRange src); + + // Translates into copy_from_indirect_uniform_unmasked (from values into temp stack) in Raster + // Pipeline. `fixedRange` denotes a fixed set of slots; this range is pushed forward by the + // value at the top of stack `dynamicStack`. Pass the range of the uniform being indexed as + // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds. + void push_uniform_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange); + + void push_zeros(int count) { + // Translates into zero_slot_unmasked in Raster Pipeline. + SkASSERT(count >= 0); + if (count > 0) { + if (!fInstructions.empty() && fInstructions.back().fOp == BuilderOp::push_zeros) { + // Coalesce adjacent push_zero ops into a single op. + fInstructions.back().fImmA += count; + } else { + fInstructions.push_back({BuilderOp::push_zeros, {}, count}); + } + } + } + + // Translates into copy_slots_unmasked (from values into temp stack) in Raster Pipeline. + void push_slots(SlotRange src); + + // Translates into copy_from_indirect_unmasked (from values into temp stack) in Raster Pipeline. + // `fixedRange` denotes a fixed set of slots; this range is pushed forward by the value at the + // top of stack `dynamicStack`. Pass the slot range of the variable being indexed as + // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds. + void push_slots_indirect(SlotRange fixedRange, int dynamicStack, SlotRange limitRange); + + // Translates into copy_slots_masked (from temp stack to values) in Raster Pipeline. + // Does not discard any values on the temp stack. + void copy_stack_to_slots(SlotRange dst) { + this->copy_stack_to_slots(dst, /*offsetFromStackTop=*/dst.count); + } + + void copy_stack_to_slots(SlotRange dst, int offsetFromStackTop); + + // Translates into swizzle_copy_slots_masked (from temp stack to values) in Raster Pipeline. + // Does not discard any values on the temp stack. + void swizzle_copy_stack_to_slots(SlotRange dst, + SkSpan<const int8_t> components, + int offsetFromStackTop); + + // Translates into swizzle_copy_to_indirect_masked (from temp stack to values) in Raster + // Pipeline. Does not discard any values on the temp stack. + void swizzle_copy_stack_to_slots_indirect(SlotRange fixedRange, + int dynamicStackID, + SlotRange limitRange, + SkSpan<const int8_t> components, + int offsetFromStackTop); + + // Translates into copy_slots_unmasked (from temp stack to values) in Raster Pipeline. + // Does not discard any values on the temp stack. + void copy_stack_to_slots_unmasked(SlotRange dst) { + this->copy_stack_to_slots_unmasked(dst, /*offsetFromStackTop=*/dst.count); + } + + void copy_stack_to_slots_unmasked(SlotRange dst, int offsetFromStackTop); + + // Translates into copy_to_indirect_masked (from temp stack into values) in Raster Pipeline. + // `fixedRange` denotes a fixed set of slots; this range is pushed forward by the value at the + // top of stack `dynamicStack`. Pass the slot range of the variable being indexed as + // `limitRange`; this is used as a hard cap, to avoid indexing outside of bounds. + void copy_stack_to_slots_indirect(SlotRange fixedRange, + int dynamicStackID, + SlotRange limitRange); + + // Copies from temp stack to slots, including an indirect offset, then shrinks the temp stack. + void pop_slots_indirect(SlotRange fixedRange, int dynamicStackID, SlotRange limitRange) { + this->copy_stack_to_slots_indirect(fixedRange, dynamicStackID, limitRange); + this->discard_stack(fixedRange.count); + } + + // Performs a unary op (like `bitwise_not`), given a slot count of `slots`. The stack top is + // replaced with the result. + void unary_op(BuilderOp op, int32_t slots); + + // Performs a binary op (like `add_n_floats` or `cmpeq_n_ints`), given a slot count of + // `slots`. Two n-slot input values are consumed, and the result is pushed onto the stack. + void binary_op(BuilderOp op, int32_t slots); + + // Performs a ternary op (like `mix` or `smoothstep`), given a slot count of + // `slots`. Three n-slot input values are consumed, and the result is pushed onto the stack. + void ternary_op(BuilderOp op, int32_t slots); + + // Computes a dot product on the stack. The slots consumed (`slots`) must be between 1 and 4. + // Two n-slot input vectors are consumed, and a scalar result is pushed onto the stack. + void dot_floats(int32_t slots); + + // Computes refract(N, I, eta) on the stack. N and I are assumed to be 4-slot vectors, and can + // be padded with zeros for smaller inputs. Eta is a scalar. The result is a 4-slot vector. + void refract_floats(); + + // Computes inverse(matN) on the stack. Pass 2, 3 or 4 for n to specify matrix size. + void inverse_matrix(int32_t n); + + // Shrinks the temp stack, discarding values on top. + void discard_stack(int32_t count = 1); + + // Copies vales from the temp stack into slots, and then shrinks the temp stack. + void pop_slots(SlotRange dst); + + // Creates many clones of the top single-slot item on the temp stack. + void push_duplicates(int count); + + // Creates a single clone of an item on the current temp stack. The cloned item can consist of + // any number of slots, and can be copied from an earlier position on the stack. + void push_clone(int numSlots, int offsetFromStackTop = 0) { + fInstructions.push_back({BuilderOp::push_clone, {}, numSlots, + numSlots + offsetFromStackTop}); + } + + // Clones a range of slots from another stack onto this stack. + void push_clone_from_stack(SlotRange range, int otherStackID, int offsetFromStackTop); + + // Translates into copy_from_indirect_unmasked (from one temp stack to another) in Raster + // Pipeline. `fixedOffset` denotes a range of slots within the top `offsetFromStackTop` slots of + // `otherStackID`. This range is pushed forward by the value at the top of `dynamicStackID`. + void push_clone_indirect_from_stack(SlotRange fixedOffset, + int dynamicStackID, + int otherStackID, + int offsetFromStackTop); + + // Compares the stack top with the passed-in value; if it matches, enables the loop mask. + void case_op(int value) { + fInstructions.push_back({BuilderOp::case_op, {}, value}); + } + + void select(int slots) { + // Overlays the top two entries on the stack, making one hybrid entry. The execution mask + // is used to select which lanes are preserved. + SkASSERT(slots > 0); + fInstructions.push_back({BuilderOp::select, {}, slots}); + } + + // The opposite of push_slots; copies values from the temp stack into value slots, then + // shrinks the temp stack. + void pop_slots_unmasked(SlotRange dst); + + void copy_slots_masked(SlotRange dst, SlotRange src) { + SkASSERT(dst.count == src.count); + fInstructions.push_back({BuilderOp::copy_slot_masked, {dst.index, src.index}, dst.count}); + } + + void copy_slots_unmasked(SlotRange dst, SlotRange src); + + void copy_constant(Slot slot, int constantValue) { + fInstructions.push_back({BuilderOp::copy_constant, {slot}, constantValue}); + } + + // Stores zeros across the entire slot range. + void zero_slots_unmasked(SlotRange dst); + + // Consumes `consumedSlots` elements on the stack, then generates `components.size()` elements. + void swizzle(int consumedSlots, SkSpan<const int8_t> components); + + // Transposes a matrix of size CxR on the stack (into a matrix of size RxC). + void transpose(int columns, int rows); + + // Generates a CxR diagonal matrix from the top two scalars on the stack. The second scalar is + // used as the diagonal value; the first scalar (usually zero) fills in the rest of the slots. + void diagonal_matrix(int columns, int rows); + + // Resizes a CxR matrix at the top of the stack to C'xR'. + void matrix_resize(int origColumns, int origRows, int newColumns, int newRows); + + void push_condition_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::push_condition_mask, {}}); + } + + void pop_condition_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::pop_condition_mask, {}}); + } + + void merge_condition_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::merge_condition_mask, {}}); + } + + void push_loop_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::push_loop_mask, {}}); + } + + void pop_loop_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::pop_loop_mask, {}}); + } + + void push_src_rgba() { + fInstructions.push_back({BuilderOp::push_src_rgba, {}}); + } + + void push_dst_rgba() { + fInstructions.push_back({BuilderOp::push_dst_rgba, {}}); + } + + void pop_src_rg() { + fInstructions.push_back({BuilderOp::pop_src_rg, {}}); + } + + void pop_src_rgba() { + fInstructions.push_back({BuilderOp::pop_src_rgba, {}}); + } + + void pop_dst_rgba() { + fInstructions.push_back({BuilderOp::pop_dst_rgba, {}}); + } + + void mask_off_loop_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::mask_off_loop_mask, {}}); + } + + void reenable_loop_mask(SlotRange src) { + SkASSERT(this->executionMaskWritesAreEnabled()); + SkASSERT(src.count == 1); + fInstructions.push_back({BuilderOp::reenable_loop_mask, {src.index}}); + } + + void pop_and_reenable_loop_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::pop_and_reenable_loop_mask, {}}); + } + + void merge_loop_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::merge_loop_mask, {}}); + } + + void push_return_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::push_return_mask, {}}); + } + + void pop_return_mask(); + + void mask_off_return_mask() { + SkASSERT(this->executionMaskWritesAreEnabled()); + fInstructions.push_back({BuilderOp::mask_off_return_mask, {}}); + } + + void invoke_shader(int childIdx) { + fInstructions.push_back({BuilderOp::invoke_shader, {}, childIdx}); + } + + void invoke_color_filter(int childIdx) { + fInstructions.push_back({BuilderOp::invoke_color_filter, {}, childIdx}); + } + + void invoke_blender(int childIdx) { + fInstructions.push_back({BuilderOp::invoke_blender, {}, childIdx}); + } + + void invoke_to_linear_srgb() { + fInstructions.push_back({BuilderOp::invoke_to_linear_srgb, {}}); + } + + void invoke_from_linear_srgb() { + fInstructions.push_back({BuilderOp::invoke_from_linear_srgb, {}}); + } + +private: + void simplifyPopSlotsUnmasked(SlotRange* dst); + + SkTArray<Instruction> fInstructions; + int fNumLabels = 0; + int fExecutionMaskWritesEnabled = 0; +}; + +} // namespace RP +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp new file mode 100644 index 0000000000..4be7d38936 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp @@ -0,0 +1,3444 @@ +/* + * Copyright 2022 Google LLC + * + * 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/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkStringView.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h" +#include "src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLBreakStatement.h" +#include "src/sksl/ir/SkSLChildCall.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLContinueStatement.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/tracing/SkRPDebugTrace.h" +#include "src/sksl/tracing/SkSLDebugInfo.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <float.h> +#include <optional> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace SkSL { +namespace RP { + +static bool unsupported() { + // If MakeRasterPipelineProgram returns false, set a breakpoint here for more information. + return false; +} + +class SlotManager { +public: + SlotManager(std::vector<SlotDebugInfo>* i) : fSlotDebugInfo(i) {} + + /** Used by `create` to add this variable to SlotDebugInfo inside SkRPDebugTrace. */ + void addSlotDebugInfoForGroup(const std::string& varName, + const Type& type, + Position pos, + int* groupIndex, + bool isFunctionReturnValue); + void addSlotDebugInfo(const std::string& varName, + const Type& type, + Position pos, + bool isFunctionReturnValue); + + /** Creates slots associated with an SkSL variable or return value. */ + SlotRange createSlots(std::string name, + const Type& type, + Position pos, + bool isFunctionReturnValue); + + /** Looks up the slots associated with an SkSL variable; creates the slot if necessary. */ + SlotRange getVariableSlots(const Variable& v); + + /** + * Looks up the slots associated with an SkSL function's return value; creates the range if + * necessary. Note that recursion is never supported, so we don't need to maintain return values + * in a stack; we can just statically allocate one slot per function call-site. + */ + SlotRange getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f); + + /** Returns the total number of slots consumed. */ + int slotCount() const { return fSlotCount; } + +private: + SkTHashMap<const IRNode*, SlotRange> fSlotMap; + int fSlotCount = 0; + std::vector<SlotDebugInfo>* fSlotDebugInfo; +}; + +class AutoContinueMask; +class LValue; + +class Generator { +public: + Generator(const SkSL::Program& program, SkRPDebugTrace* debugTrace) + : fProgram(program) + , fContext(fProgram.fContext->fTypes, + fProgram.fContext->fCaps, + *fProgram.fContext->fErrors) + , fDebugTrace(debugTrace) + , fProgramSlots(debugTrace ? &debugTrace->fSlotInfo : nullptr) + , fUniformSlots(debugTrace ? &debugTrace->fUniformInfo : nullptr) { + fContext.fModifiersPool = &fModifiersPool; + fContext.fConfig = fProgram.fConfig.get(); + fContext.fModule = fProgram.fContext->fModule; + } + + /** Converts the SkSL main() function into a set of Instructions. */ + bool writeProgram(const FunctionDefinition& function); + + /** Returns the generated program. */ + std::unique_ptr<RP::Program> finish(); + + /** + * Converts an SkSL function into a set of Instructions. Returns nullopt if the function + * contained unsupported statements or expressions. + */ + std::optional<SlotRange> writeFunction(const IRNode& callSite, + const FunctionDefinition& function); + + /** + * Returns the slot index of this function inside the FunctionDebugInfo array in SkRPDebugTrace. + * The FunctionDebugInfo slot will be created if it doesn't already exist. + */ + int getFunctionDebugInfo(const FunctionDeclaration& decl); + + /** Looks up the slots associated with an SkSL variable; creates the slot if necessary. */ + SlotRange getVariableSlots(const Variable& v) { + SkASSERT(!IsUniform(v)); + return fProgramSlots.getVariableSlots(v); + } + + /** Looks up the slots associated with an SkSL uniform; creates the slot if necessary. */ + SlotRange getUniformSlots(const Variable& v) { + SkASSERT(IsUniform(v)); + return fUniformSlots.getVariableSlots(v); + } + + /** + * Looks up the slots associated with an SkSL function's return value; creates the range if + * necessary. Note that recursion is never supported, so we don't need to maintain return values + * in a stack; we can just statically allocate one slot per function call-site. + */ + SlotRange getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f) { + return fProgramSlots.getFunctionSlots(callSite, f); + } + + /** + * Creates an additional stack for the program to push values onto. The stack will not become + * actively in-use until `setCurrentStack` is called. + */ + int createStack(); + + /** Frees a stack generated by `createStack`. The freed stack must be completely empty. */ + void recycleStack(int stackID); + + /** Redirects builder ops to point to a different stack (created by `createStack`). */ + void setCurrentStack(int stackID); + + /** Reports the currently active stack. */ + int currentStack() { + return fCurrentStack; + } + + /** + * Returns an LValue for the passed-in expression; if the expression isn't supported as an + * LValue, returns nullptr. + */ + std::unique_ptr<LValue> makeLValue(const Expression& e, bool allowScratch = false); + + /** Copies the top-of-stack value into this lvalue, without discarding it from the stack. */ + [[nodiscard]] bool store(LValue& lvalue); + + /** Pushes the lvalue onto the top-of-stack. */ + [[nodiscard]] bool push(LValue& lvalue); + + /** The Builder stitches our instructions together into Raster Pipeline code. */ + Builder* builder() { return &fBuilder; } + + /** Appends a statement to the program. */ + [[nodiscard]] bool writeStatement(const Statement& s); + [[nodiscard]] bool writeBlock(const Block& b); + [[nodiscard]] bool writeBreakStatement(const BreakStatement& b); + [[nodiscard]] bool writeContinueStatement(const ContinueStatement& b); + [[nodiscard]] bool writeDoStatement(const DoStatement& d); + [[nodiscard]] bool writeExpressionStatement(const ExpressionStatement& e); + [[nodiscard]] bool writeMasklessForStatement(const ForStatement& f); + [[nodiscard]] bool writeForStatement(const ForStatement& f); + [[nodiscard]] bool writeGlobals(); + [[nodiscard]] bool writeIfStatement(const IfStatement& i); + [[nodiscard]] bool writeDynamicallyUniformIfStatement(const IfStatement& i); + [[nodiscard]] bool writeReturnStatement(const ReturnStatement& r); + [[nodiscard]] bool writeSwitchStatement(const SwitchStatement& s); + [[nodiscard]] bool writeVarDeclaration(const VarDeclaration& v); + + /** Pushes an expression to the value stack. */ + [[nodiscard]] bool pushBinaryExpression(const BinaryExpression& e); + [[nodiscard]] bool pushBinaryExpression(const Expression& left, + Operator op, + const Expression& right); + [[nodiscard]] bool pushChildCall(const ChildCall& c); + [[nodiscard]] bool pushConstructorCast(const AnyConstructor& c); + [[nodiscard]] bool pushConstructorCompound(const AnyConstructor& c); + [[nodiscard]] bool pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c); + [[nodiscard]] bool pushConstructorMatrixResize(const ConstructorMatrixResize& c); + [[nodiscard]] bool pushConstructorSplat(const ConstructorSplat& c); + [[nodiscard]] bool pushExpression(const Expression& e, bool usesResult = true); + [[nodiscard]] bool pushFieldAccess(const FieldAccess& f); + [[nodiscard]] bool pushFunctionCall(const FunctionCall& c); + [[nodiscard]] bool pushIndexExpression(const IndexExpression& i); + [[nodiscard]] bool pushIntrinsic(const FunctionCall& c); + [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0); + [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, + const Expression& arg0, + const Expression& arg1); + [[nodiscard]] bool pushIntrinsic(IntrinsicKind intrinsic, + const Expression& arg0, + const Expression& arg1, + const Expression& arg2); + [[nodiscard]] bool pushLiteral(const Literal& l); + [[nodiscard]] bool pushPostfixExpression(const PostfixExpression& p, bool usesResult); + [[nodiscard]] bool pushPrefixExpression(const PrefixExpression& p); + [[nodiscard]] bool pushPrefixExpression(Operator op, const Expression& expr); + [[nodiscard]] bool pushSwizzle(const Swizzle& s); + [[nodiscard]] bool pushTernaryExpression(const TernaryExpression& t); + [[nodiscard]] bool pushTernaryExpression(const Expression& test, + const Expression& ifTrue, + const Expression& ifFalse); + [[nodiscard]] bool pushDynamicallyUniformTernaryExpression(const Expression& test, + const Expression& ifTrue, + const Expression& ifFalse); + [[nodiscard]] bool pushVariableReference(const VariableReference& v); + + /** Pops an expression from the value stack and copies it into slots. */ + void popToSlotRange(SlotRange r) { fBuilder.pop_slots(r); } + void popToSlotRangeUnmasked(SlotRange r) { fBuilder.pop_slots_unmasked(r); } + + /** Pops an expression from the value stack and discards it. */ + void discardExpression(int slots) { fBuilder.discard_stack(slots); } + + /** Zeroes out a range of slots. */ + void zeroSlotRangeUnmasked(SlotRange r) { fBuilder.zero_slots_unmasked(r); } + + /** Expression utilities. */ + struct TypedOps { + BuilderOp fFloatOp; + BuilderOp fSignedOp; + BuilderOp fUnsignedOp; + BuilderOp fBooleanOp; + }; + + static BuilderOp GetTypedOp(const SkSL::Type& type, const TypedOps& ops); + + [[nodiscard]] bool unaryOp(const SkSL::Type& type, const TypedOps& ops); + [[nodiscard]] bool binaryOp(const SkSL::Type& type, const TypedOps& ops); + [[nodiscard]] bool ternaryOp(const SkSL::Type& type, const TypedOps& ops); + [[nodiscard]] bool pushIntrinsic(const TypedOps& ops, const Expression& arg0); + [[nodiscard]] bool pushIntrinsic(const TypedOps& ops, + const Expression& arg0, + const Expression& arg1); + [[nodiscard]] bool pushIntrinsic(BuilderOp builderOp, const Expression& arg0); + [[nodiscard]] bool pushIntrinsic(BuilderOp builderOp, + const Expression& arg0, + const Expression& arg1); + [[nodiscard]] bool pushLengthIntrinsic(int slotCount); + [[nodiscard]] bool pushVectorizedExpression(const Expression& expr, const Type& vectorType); + [[nodiscard]] bool pushVariableReferencePartial(const VariableReference& v, SlotRange subset); + [[nodiscard]] bool pushLValueOrExpression(LValue* lvalue, const Expression& expr); + [[nodiscard]] bool pushMatrixMultiply(LValue* lvalue, + const Expression& left, + const Expression& right, + int leftColumns, int leftRows, + int rightColumns, int rightRows); + [[nodiscard]] bool pushStructuredComparison(LValue* left, + Operator op, + LValue* right, + const Type& type); + + void foldWithMultiOp(BuilderOp op, int elements); + void foldComparisonOp(Operator op, int elements); + + BuilderOp getTypedOp(const SkSL::Type& type, const TypedOps& ops) const; + + Analysis::ReturnComplexity returnComplexity(const FunctionDefinition* func) { + Analysis::ReturnComplexity* complexity = fReturnComplexityMap.find(fCurrentFunction); + if (!complexity) { + complexity = fReturnComplexityMap.set(fCurrentFunction, + Analysis::GetReturnComplexity(*fCurrentFunction)); + } + return *complexity; + } + + bool needsReturnMask() { + return this->returnComplexity(fCurrentFunction) >= + Analysis::ReturnComplexity::kEarlyReturns; + } + + bool needsFunctionResultSlots() { + return this->returnComplexity(fCurrentFunction) > + Analysis::ReturnComplexity::kSingleSafeReturn; + } + + static bool IsUniform(const Variable& var) { + return var.modifiers().fFlags & Modifiers::kUniform_Flag; + } + + static bool IsOutParameter(const Variable& var) { + return (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) == + Modifiers::kOut_Flag; + } + + static bool IsInoutParameter(const Variable& var) { + return (var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) == + (Modifiers::kIn_Flag | Modifiers::kOut_Flag); + } + +private: + const SkSL::Program& fProgram; + SkSL::Context fContext; + SkSL::ModifiersPool fModifiersPool; + Builder fBuilder; + SkRPDebugTrace* fDebugTrace = nullptr; + SkTHashMap<const Variable*, int> fChildEffectMap; + + SlotManager fProgramSlots; + SlotManager fUniformSlots; + + const FunctionDefinition* fCurrentFunction = nullptr; + SlotRange fCurrentFunctionResult; + AutoContinueMask* fCurrentContinueMask = nullptr; + int fCurrentBreakTarget = -1; + int fCurrentStack = 0; + int fNextStackID = 0; + SkTArray<int> fRecycledStacks; + + SkTHashMap<const FunctionDefinition*, Analysis::ReturnComplexity> fReturnComplexityMap; + + static constexpr auto kAbsOps = TypedOps{BuilderOp::abs_float, + BuilderOp::abs_int, + BuilderOp::unsupported, + BuilderOp::unsupported}; + static constexpr auto kAddOps = TypedOps{BuilderOp::add_n_floats, + BuilderOp::add_n_ints, + BuilderOp::add_n_ints, + BuilderOp::unsupported}; + static constexpr auto kSubtractOps = TypedOps{BuilderOp::sub_n_floats, + BuilderOp::sub_n_ints, + BuilderOp::sub_n_ints, + BuilderOp::unsupported}; + static constexpr auto kMultiplyOps = TypedOps{BuilderOp::mul_n_floats, + BuilderOp::mul_n_ints, + BuilderOp::mul_n_ints, + BuilderOp::unsupported}; + static constexpr auto kDivideOps = TypedOps{BuilderOp::div_n_floats, + BuilderOp::div_n_ints, + BuilderOp::div_n_uints, + BuilderOp::unsupported}; + static constexpr auto kLessThanOps = TypedOps{BuilderOp::cmplt_n_floats, + BuilderOp::cmplt_n_ints, + BuilderOp::cmplt_n_uints, + BuilderOp::unsupported}; + static constexpr auto kLessThanEqualOps = TypedOps{BuilderOp::cmple_n_floats, + BuilderOp::cmple_n_ints, + BuilderOp::cmple_n_uints, + BuilderOp::unsupported}; + static constexpr auto kEqualOps = TypedOps{BuilderOp::cmpeq_n_floats, + BuilderOp::cmpeq_n_ints, + BuilderOp::cmpeq_n_ints, + BuilderOp::cmpeq_n_ints}; + static constexpr auto kNotEqualOps = TypedOps{BuilderOp::cmpne_n_floats, + BuilderOp::cmpne_n_ints, + BuilderOp::cmpne_n_ints, + BuilderOp::cmpne_n_ints}; + static constexpr auto kModOps = TypedOps{BuilderOp::mod_n_floats, + BuilderOp::unsupported, + BuilderOp::unsupported, + BuilderOp::unsupported}; + static constexpr auto kMinOps = TypedOps{BuilderOp::min_n_floats, + BuilderOp::min_n_ints, + BuilderOp::min_n_uints, + BuilderOp::min_n_uints}; + static constexpr auto kMaxOps = TypedOps{BuilderOp::max_n_floats, + BuilderOp::max_n_ints, + BuilderOp::max_n_uints, + BuilderOp::max_n_uints}; + static constexpr auto kMixOps = TypedOps{BuilderOp::mix_n_floats, + BuilderOp::unsupported, + BuilderOp::unsupported, + BuilderOp::unsupported}; + static constexpr auto kInverseSqrtOps = TypedOps{BuilderOp::invsqrt_float, + BuilderOp::unsupported, + BuilderOp::unsupported, + BuilderOp::unsupported}; + friend class AutoContinueMask; +}; + +class AutoStack { +public: + explicit AutoStack(Generator* g) + : fGenerator(g) + , fStackID(g->createStack()) {} + + ~AutoStack() { + fGenerator->recycleStack(fStackID); + } + + void enter() { + fParentStackID = fGenerator->currentStack(); + fGenerator->setCurrentStack(fStackID); + } + + void exit() { + SkASSERT(fGenerator->currentStack() == fStackID); + fGenerator->setCurrentStack(fParentStackID); + } + + void pushClone(int slots) { + this->pushClone(SlotRange{0, slots}, /*offsetFromStackTop=*/slots); + } + + void pushClone(SlotRange range, int offsetFromStackTop) { + fGenerator->builder()->push_clone_from_stack(range, fStackID, offsetFromStackTop); + } + + void pushCloneIndirect(SlotRange range, int dynamicStackID, int offsetFromStackTop) { + fGenerator->builder()->push_clone_indirect_from_stack( + range, dynamicStackID, /*otherStackID=*/fStackID, offsetFromStackTop); + } + + int stackID() const { + return fStackID; + } + +private: + Generator* fGenerator; + int fStackID = 0; + int fParentStackID = 0; +}; + +class AutoContinueMask { +public: + AutoContinueMask(Generator* gen) : fGenerator(gen) {} + + ~AutoContinueMask() { + if (fPreviousContinueMask) { + fGenerator->fCurrentContinueMask = fPreviousContinueMask; + } + } + + void enable() { + SkASSERT(!fContinueMaskStack.has_value()); + + fContinueMaskStack.emplace(fGenerator); + fPreviousContinueMask = fGenerator->fCurrentContinueMask; + fGenerator->fCurrentContinueMask = this; + } + + void enter() { + SkASSERT(fContinueMaskStack.has_value()); + fContinueMaskStack->enter(); + } + + void exit() { + SkASSERT(fContinueMaskStack.has_value()); + fContinueMaskStack->exit(); + } + + void enterLoopBody() { + if (fContinueMaskStack.has_value()) { + fContinueMaskStack->enter(); + fGenerator->builder()->push_literal_i(0); + fContinueMaskStack->exit(); + } + } + + void exitLoopBody() { + if (fContinueMaskStack.has_value()) { + fContinueMaskStack->enter(); + fGenerator->builder()->pop_and_reenable_loop_mask(); + fContinueMaskStack->exit(); + } + } + +private: + std::optional<AutoStack> fContinueMaskStack; + Generator* fGenerator = nullptr; + AutoContinueMask* fPreviousContinueMask = nullptr; +}; + +class AutoLoopTarget { +public: + AutoLoopTarget(Generator* gen, int* targetPtr) : fGenerator(gen), fLoopTargetPtr(targetPtr) { + fLabelID = fGenerator->builder()->nextLabelID(); + fPreviousLoopTarget = *fLoopTargetPtr; + *fLoopTargetPtr = fLabelID; + } + + ~AutoLoopTarget() { + *fLoopTargetPtr = fPreviousLoopTarget; + } + + int labelID() { + return fLabelID; + } + +private: + Generator* fGenerator = nullptr; + int* fLoopTargetPtr = nullptr; + int fPreviousLoopTarget; + int fLabelID; +}; + +class LValue { +public: + virtual ~LValue() = default; + + /** Returns true if this lvalue is actually writable--temporaries and uniforms are not. */ + virtual bool isWritable() const = 0; + + /** + * Returns the fixed slot range of the lvalue, after it is winnowed down to the selected + * field/index. The range is calculated assuming every dynamic index will evaluate to zero. + */ + virtual SlotRange fixedSlotRange(Generator* gen) = 0; + + /** + * Returns a stack which holds a single integer, representing the dynamic offset of the lvalue. + * This value does not incorporate the fixed offset. If null is returned, the lvalue doesn't + * have a dynamic offset. `evaluateDynamicIndices` must be called before this is used. + */ + virtual AutoStack* dynamicSlotRange() = 0; + + /** Returns the swizzle components of the lvalue, or an empty span for non-swizzle LValues. */ + virtual SkSpan<const int8_t> swizzle() { return {}; } + + /** Pushes values directly onto the stack. */ + [[nodiscard]] virtual bool push(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) = 0; + + /** Stores topmost values from the stack directly into the lvalue. */ + [[nodiscard]] virtual bool store(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) = 0; + /** + * Some lvalues refer to a temporary expression; these temps can be held in the + * scratch-expression field to ensure that they exist for the lifetime of the lvalue. + */ + std::unique_ptr<Expression> fScratchExpression; +}; + +class ScratchLValue final : public LValue { +public: + explicit ScratchLValue(const Expression& e) + : fExpression(&e) + , fNumSlots(e.type().slotCount()) {} + + ~ScratchLValue() override { + if (fGenerator && fDedicatedStack.has_value()) { + // Jettison the scratch expression. + fDedicatedStack->enter(); + fGenerator->discardExpression(fNumSlots); + fDedicatedStack->exit(); + } + } + + bool isWritable() const override { + return false; + } + + SlotRange fixedSlotRange(Generator* gen) override { + return SlotRange{0, fNumSlots}; + } + + AutoStack* dynamicSlotRange() override { + return nullptr; + } + + [[nodiscard]] bool push(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + if (!fDedicatedStack.has_value()) { + // Push the scratch expression onto a dedicated stack. + fGenerator = gen; + fDedicatedStack.emplace(fGenerator); + fDedicatedStack->enter(); + if (!fGenerator->pushExpression(*fExpression)) { + return unsupported(); + } + fDedicatedStack->exit(); + } + + if (dynamicOffset) { + fDedicatedStack->pushCloneIndirect(fixedOffset, dynamicOffset->stackID(), fNumSlots); + } else { + fDedicatedStack->pushClone(fixedOffset, fNumSlots); + } + if (!swizzle.empty()) { + gen->builder()->swizzle(fixedOffset.count, swizzle); + } + return true; + } + + [[nodiscard]] bool store(Generator*, SlotRange, AutoStack*, SkSpan<const int8_t>) override { + SkDEBUGFAIL("scratch lvalues cannot be stored into"); + return unsupported(); + } + +private: + Generator* fGenerator = nullptr; + const Expression* fExpression = nullptr; + std::optional<AutoStack> fDedicatedStack; + int fNumSlots = 0; +}; + +class VariableLValue final : public LValue { +public: + explicit VariableLValue(const Variable* v) : fVariable(v) {} + + bool isWritable() const override { + return !Generator::IsUniform(*fVariable); + } + + SlotRange fixedSlotRange(Generator* gen) override { + return Generator::IsUniform(*fVariable) ? gen->getUniformSlots(*fVariable) + : gen->getVariableSlots(*fVariable); + } + + AutoStack* dynamicSlotRange() override { + return nullptr; + } + + [[nodiscard]] bool push(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + if (Generator::IsUniform(*fVariable)) { + if (dynamicOffset) { + gen->builder()->push_uniform_indirect(fixedOffset, dynamicOffset->stackID(), + this->fixedSlotRange(gen)); + } else { + gen->builder()->push_uniform(fixedOffset); + } + } else { + if (dynamicOffset) { + gen->builder()->push_slots_indirect(fixedOffset, dynamicOffset->stackID(), + this->fixedSlotRange(gen)); + } else { + gen->builder()->push_slots(fixedOffset); + } + } + if (!swizzle.empty()) { + gen->builder()->swizzle(fixedOffset.count, swizzle); + } + return true; + } + + [[nodiscard]] bool store(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + SkASSERT(!Generator::IsUniform(*fVariable)); + + if (swizzle.empty()) { + if (dynamicOffset) { + gen->builder()->copy_stack_to_slots_indirect(fixedOffset, dynamicOffset->stackID(), + this->fixedSlotRange(gen)); + } else { + gen->builder()->copy_stack_to_slots(fixedOffset); + } + } else { + if (dynamicOffset) { + gen->builder()->swizzle_copy_stack_to_slots_indirect(fixedOffset, + dynamicOffset->stackID(), + this->fixedSlotRange(gen), + swizzle, + swizzle.size()); + } else { + gen->builder()->swizzle_copy_stack_to_slots(fixedOffset, swizzle, swizzle.size()); + } + } + return true; + } + +private: + const Variable* fVariable; +}; + +class SwizzleLValue final : public LValue { +public: + explicit SwizzleLValue(std::unique_ptr<LValue> p, const ComponentArray& c) + : fParent(std::move(p)) + , fComponents(c) { + SkASSERT(!fComponents.empty() && fComponents.size() <= 4); + } + + bool isWritable() const override { + return fParent->isWritable(); + } + + SlotRange fixedSlotRange(Generator* gen) override { + return fParent->fixedSlotRange(gen); + } + + AutoStack* dynamicSlotRange() override { + return fParent->dynamicSlotRange(); + } + + SkSpan<const int8_t> swizzle() override { + return fComponents; + } + + [[nodiscard]] bool push(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + if (!swizzle.empty()) { + SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end"); + return unsupported(); + } + return fParent->push(gen, fixedOffset, dynamicOffset, fComponents); + } + + [[nodiscard]] bool store(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + if (!swizzle.empty()) { + SkDEBUGFAIL("swizzle-of-a-swizzle should have been folded out in front end"); + return unsupported(); + } + return fParent->store(gen, fixedOffset, dynamicOffset, fComponents); + } + +private: + std::unique_ptr<LValue> fParent; + const ComponentArray& fComponents; +}; + +class UnownedLValueSlice : public LValue { +public: + explicit UnownedLValueSlice(LValue* p, int initialSlot, int numSlots) + : fParent(p) + , fInitialSlot(initialSlot) + , fNumSlots(numSlots) { + SkASSERT(fInitialSlot >= 0); + SkASSERT(fNumSlots > 0); + } + + bool isWritable() const override { + return fParent->isWritable(); + } + + SlotRange fixedSlotRange(Generator* gen) override { + SlotRange range = fParent->fixedSlotRange(gen); + SlotRange adjusted = range; + adjusted.index += fInitialSlot; + adjusted.count = fNumSlots; + SkASSERT((adjusted.index + adjusted.count) <= (range.index + range.count)); + return adjusted; + } + + AutoStack* dynamicSlotRange() override { + return fParent->dynamicSlotRange(); + } + + [[nodiscard]] bool push(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + return fParent->push(gen, fixedOffset, dynamicOffset, swizzle); + } + + [[nodiscard]] bool store(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + return fParent->store(gen, fixedOffset, dynamicOffset, swizzle); + } + +protected: + LValue* fParent; + +private: + int fInitialSlot = 0; + int fNumSlots = 0; +}; + +class LValueSlice final : public UnownedLValueSlice { +public: + explicit LValueSlice(std::unique_ptr<LValue> p, int initialSlot, int numSlots) + : UnownedLValueSlice(p.release(), initialSlot, numSlots) {} + + ~LValueSlice() override { + delete fParent; + } +}; + +class DynamicIndexLValue final : public LValue { +public: + explicit DynamicIndexLValue(std::unique_ptr<LValue> p, const IndexExpression& i) + : fParent(std::move(p)) + , fIndexExpr(&i) { + SkASSERT(fIndexExpr->index()->type().isInteger()); + } + + ~DynamicIndexLValue() override { + if (fDedicatedStack.has_value()) { + SkASSERT(fGenerator); + + // Jettison the index expression. + fDedicatedStack->enter(); + fGenerator->discardExpression(/*slots=*/1); + fDedicatedStack->exit(); + } + } + + bool isWritable() const override { + return fParent->isWritable(); + } + + [[nodiscard]] bool evaluateDynamicIndices(Generator* gen) { + // The index must only be computed once; the index-expression could have side effects. + // Once it has been computed, the offset lives on `fDedicatedStack`. + SkASSERT(!fDedicatedStack.has_value()); + SkASSERT(!fGenerator); + fGenerator = gen; + fDedicatedStack.emplace(fGenerator); + + if (!fParent->swizzle().empty()) { + SkDEBUGFAIL("an indexed-swizzle should have been handled by RewriteIndexedSwizzle"); + return unsupported(); + } + + // Push the index expression onto the dedicated stack. + fDedicatedStack->enter(); + if (!fGenerator->pushExpression(*fIndexExpr->index())) { + return unsupported(); + } + + // Multiply the index-expression result by the per-value slot count. + int slotCount = fIndexExpr->type().slotCount(); + if (slotCount != 1) { + fGenerator->builder()->push_literal_i(fIndexExpr->type().slotCount()); + fGenerator->builder()->binary_op(BuilderOp::mul_n_ints, 1); + } + + // Check to see if a parent LValue already has a dynamic index. If so, we need to + // incorporate its value into our own. + if (AutoStack* parentDynamicIndexStack = fParent->dynamicSlotRange()) { + parentDynamicIndexStack->pushClone(/*slots=*/1); + fGenerator->builder()->binary_op(BuilderOp::add_n_ints, 1); + } + fDedicatedStack->exit(); + return true; + } + + SlotRange fixedSlotRange(Generator* gen) override { + // Compute the fixed slot range as if we are indexing into position zero. + SlotRange range = fParent->fixedSlotRange(gen); + range.count = fIndexExpr->type().slotCount(); + return range; + } + + AutoStack* dynamicSlotRange() override { + // We incorporated any parent dynamic offsets when `evaluateDynamicIndices` was called. + SkASSERT(fDedicatedStack.has_value()); + return &*fDedicatedStack; + } + + [[nodiscard]] bool push(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + return fParent->push(gen, fixedOffset, dynamicOffset, swizzle); + } + + [[nodiscard]] bool store(Generator* gen, + SlotRange fixedOffset, + AutoStack* dynamicOffset, + SkSpan<const int8_t> swizzle) override { + return fParent->store(gen, fixedOffset, dynamicOffset, swizzle); + } + +private: + Generator* fGenerator = nullptr; + std::unique_ptr<LValue> fParent; + std::optional<AutoStack> fDedicatedStack; + const IndexExpression* fIndexExpr = nullptr; +}; + +void SlotManager::addSlotDebugInfoForGroup(const std::string& varName, + const Type& type, + Position pos, + int* groupIndex, + bool isFunctionReturnValue) { + SkASSERT(fSlotDebugInfo); + switch (type.typeKind()) { + case Type::TypeKind::kArray: { + int nslots = type.columns(); + const Type& elemType = type.componentType(); + for (int slot = 0; slot < nslots; ++slot) { + this->addSlotDebugInfoForGroup(varName + "[" + std::to_string(slot) + "]", elemType, + pos, groupIndex, isFunctionReturnValue); + } + break; + } + case Type::TypeKind::kStruct: { + for (const Type::Field& field : type.fields()) { + this->addSlotDebugInfoForGroup(varName + "." + std::string(field.fName), + *field.fType, pos, groupIndex, + isFunctionReturnValue); + } + break; + } + default: + SkASSERTF(0, "unsupported slot type %d", (int)type.typeKind()); + [[fallthrough]]; + + case Type::TypeKind::kScalar: + case Type::TypeKind::kVector: + case Type::TypeKind::kMatrix: { + Type::NumberKind numberKind = type.componentType().numberKind(); + int nslots = type.slotCount(); + + for (int slot = 0; slot < nslots; ++slot) { + SlotDebugInfo slotInfo; + slotInfo.name = varName; + slotInfo.columns = type.columns(); + slotInfo.rows = type.rows(); + slotInfo.componentIndex = slot; + slotInfo.groupIndex = (*groupIndex)++; + slotInfo.numberKind = numberKind; + slotInfo.pos = pos; + slotInfo.fnReturnValue = isFunctionReturnValue ? 1 : -1; + fSlotDebugInfo->push_back(std::move(slotInfo)); + } + break; + } + } +} + +void SlotManager::addSlotDebugInfo(const std::string& varName, + const Type& type, + Position pos, + bool isFunctionReturnValue) { + int groupIndex = 0; + this->addSlotDebugInfoForGroup(varName, type, pos, &groupIndex, isFunctionReturnValue); + SkASSERT((size_t)groupIndex == type.slotCount()); +} + +SlotRange SlotManager::createSlots(std::string name, + const Type& type, + Position pos, + bool isFunctionReturnValue) { + size_t nslots = type.slotCount(); + if (nslots == 0) { + return {}; + } + if (fSlotDebugInfo) { + // Our debug slot-info table should have the same length as the actual slot table. + SkASSERT(fSlotDebugInfo->size() == (size_t)fSlotCount); + + // Append slot names and types to our debug slot-info table. + fSlotDebugInfo->reserve(fSlotCount + nslots); + this->addSlotDebugInfo(name, type, pos, isFunctionReturnValue); + + // Confirm that we added the expected number of slots. + SkASSERT(fSlotDebugInfo->size() == (size_t)(fSlotCount + nslots)); + } + + SlotRange result = {fSlotCount, (int)nslots}; + fSlotCount += nslots; + return result; +} + +SlotRange SlotManager::getVariableSlots(const Variable& v) { + SlotRange* entry = fSlotMap.find(&v); + if (entry != nullptr) { + return *entry; + } + SlotRange range = this->createSlots(std::string(v.name()), + v.type(), + v.fPosition, + /*isFunctionReturnValue=*/false); + fSlotMap.set(&v, range); + return range; +} + +SlotRange SlotManager::getFunctionSlots(const IRNode& callSite, const FunctionDeclaration& f) { + SlotRange* entry = fSlotMap.find(&callSite); + if (entry != nullptr) { + return *entry; + } + SlotRange range = this->createSlots("[" + std::string(f.name()) + "].result", + f.returnType(), + f.fPosition, + /*isFunctionReturnValue=*/true); + fSlotMap.set(&callSite, range); + return range; +} + +static bool is_sliceable_swizzle(SkSpan<const int8_t> components) { + // Determine if the swizzle rearranges its elements, or if it's a simple subset of its elements. + // (A simple subset would be a sequential non-repeating range of components, like `.xyz` or + // `.yzw` or `.z`, but not `.xx` or `.xz`, which can be accessed as a slice of the variable.) + for (size_t index = 1; index < components.size(); ++index) { + if (components[index] != int8_t(components[0] + index)) { + return false; + } + } + return true; +} + +std::unique_ptr<LValue> Generator::makeLValue(const Expression& e, bool allowScratch) { + if (e.is<VariableReference>()) { + return std::make_unique<VariableLValue>(e.as<VariableReference>().variable()); + } + if (e.is<Swizzle>()) { + const Swizzle& swizzleExpr = e.as<Swizzle>(); + if (std::unique_ptr<LValue> base = this->makeLValue(*swizzleExpr.base(), + allowScratch)) { + const ComponentArray& components = swizzleExpr.components(); + if (is_sliceable_swizzle(components)) { + // If the swizzle is a contiguous subset, we can represent it with a fixed slice. + return std::make_unique<LValueSlice>(std::move(base), components[0], + components.size()); + } + return std::make_unique<SwizzleLValue>(std::move(base), components); + } + return nullptr; + } + if (e.is<FieldAccess>()) { + const FieldAccess& fieldExpr = e.as<FieldAccess>(); + if (std::unique_ptr<LValue> base = this->makeLValue(*fieldExpr.base(), + allowScratch)) { + // Represent field access with a slice. + return std::make_unique<LValueSlice>(std::move(base), fieldExpr.initialSlot(), + fieldExpr.type().slotCount()); + } + return nullptr; + } + if (e.is<IndexExpression>()) { + const IndexExpression& indexExpr = e.as<IndexExpression>(); + + // If the index base is swizzled (`vec.zyx[idx]`), rewrite it into an equivalent + // non-swizzled form (`vec[uint3(2,1,0)[idx]]`). + if (std::unique_ptr<Expression> rewritten = Transform::RewriteIndexedSwizzle(fContext, + indexExpr)) { + // Convert the rewritten expression into an lvalue. + std::unique_ptr<LValue> lvalue = this->makeLValue(*rewritten, allowScratch); + if (!lvalue) { + return nullptr; + } + // We need to hold onto the rewritten expression for the lifetime of the lvalue. + lvalue->fScratchExpression = std::move(rewritten); + return lvalue; + } + if (std::unique_ptr<LValue> base = this->makeLValue(*indexExpr.base(), + allowScratch)) { + // If the index is a compile-time constant, we can represent it with a fixed slice. + SKSL_INT indexValue; + if (ConstantFolder::GetConstantInt(*indexExpr.index(), &indexValue)) { + int numSlots = indexExpr.type().slotCount(); + return std::make_unique<LValueSlice>(std::move(base), numSlots * indexValue, + numSlots); + } + + // Represent non-constant indexing via a dynamic index. + auto dynLValue = std::make_unique<DynamicIndexLValue>(std::move(base), indexExpr); + return dynLValue->evaluateDynamicIndices(this) ? std::move(dynLValue) + : nullptr; + } + return nullptr; + } + if (allowScratch) { + // This path allows us to perform field- and index-accesses on an expression as if it were + // an lvalue, but is a temporary and shouldn't be written back to. + return std::make_unique<ScratchLValue>(e); + } + return nullptr; +} + +bool Generator::push(LValue& lvalue) { + return lvalue.push(this, + lvalue.fixedSlotRange(this), + lvalue.dynamicSlotRange(), + /*swizzle=*/{}); +} + +bool Generator::store(LValue& lvalue) { + SkASSERT(lvalue.isWritable()); + return lvalue.store(this, + lvalue.fixedSlotRange(this), + lvalue.dynamicSlotRange(), + /*swizzle=*/{}); +} + +int Generator::getFunctionDebugInfo(const FunctionDeclaration& decl) { + SkASSERT(fDebugTrace); + + std::string name = decl.description(); + + // When generating the debug trace, we typically mark every function as `noinline`. This makes + // the trace more confusing, since this isn't in the source program, so remove it. + static constexpr std::string_view kNoInline = "noinline "; + if (skstd::starts_with(name, kNoInline)) { + name = name.substr(kNoInline.size()); + } + + // Look for a matching FunctionDebugInfo slot. + for (size_t index = 0; index < fDebugTrace->fFuncInfo.size(); ++index) { + if (fDebugTrace->fFuncInfo[index].name == name) { + return index; + } + } + + // We've never called this function before; create a new slot to hold its information. + int slot = (int)fDebugTrace->fFuncInfo.size(); + fDebugTrace->fFuncInfo.push_back(FunctionDebugInfo{std::move(name)}); + return slot; +} + +int Generator::createStack() { + if (!fRecycledStacks.empty()) { + int stackID = fRecycledStacks.back(); + fRecycledStacks.pop_back(); + return stackID; + } + return ++fNextStackID; +} + +void Generator::recycleStack(int stackID) { + fRecycledStacks.push_back(stackID); +} + +void Generator::setCurrentStack(int stackID) { + if (fCurrentStack != stackID) { + fCurrentStack = stackID; + fBuilder.set_current_stack(stackID); + } +} + +std::optional<SlotRange> Generator::writeFunction(const IRNode& callSite, + const FunctionDefinition& function) { + [[maybe_unused]] int funcIndex = -1; + if (fDebugTrace) { + funcIndex = this->getFunctionDebugInfo(function.declaration()); + SkASSERT(funcIndex >= 0); + // TODO(debugger): add trace for function-enter + } + + SlotRange lastFunctionResult = fCurrentFunctionResult; + fCurrentFunctionResult = this->getFunctionSlots(callSite, function.declaration()); + + if (!this->writeStatement(*function.body())) { + return std::nullopt; + } + + SlotRange functionResult = fCurrentFunctionResult; + fCurrentFunctionResult = lastFunctionResult; + + if (fDebugTrace) { + // TODO(debugger): add trace for function-exit + } + + return functionResult; +} + +bool Generator::writeGlobals() { + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>(); + const VarDeclaration& decl = gvd.varDeclaration(); + const Variable* var = decl.var(); + + if (var->type().isEffectChild()) { + // Associate each child effect variable with its numeric index. + SkASSERT(!fChildEffectMap.find(var)); + int childEffectIndex = fChildEffectMap.count(); + fChildEffectMap[var] = childEffectIndex; + continue; + } + + // Opaque types include child processors and GL objects (samplers, textures, etc). + // Of those, only child processors are legal variables. + SkASSERT(!var->type().isVoid()); + SkASSERT(!var->type().isOpaque()); + + // Builtin variables are system-defined, with special semantics. + if (int builtin = var->modifiers().fLayout.fBuiltin; builtin >= 0) { + if (builtin == SK_FRAGCOORD_BUILTIN) { + fBuilder.store_device_xy01(this->getVariableSlots(*var)); + continue; + } + // The only builtin variable exposed to runtime effects is sk_FragCoord. + return unsupported(); + } + + if (IsUniform(*var)) { + // Create the uniform slot map in first-to-last order. + (void)this->getUniformSlots(*var); + continue; + } + + // Other globals are treated as normal variable declarations. + if (!this->writeVarDeclaration(decl)) { + return unsupported(); + } + } + } + + return true; +} + +bool Generator::writeStatement(const Statement& s) { + switch (s.kind()) { + case Statement::Kind::kBlock: + return this->writeBlock(s.as<Block>()); + + case Statement::Kind::kBreak: + return this->writeBreakStatement(s.as<BreakStatement>()); + + case Statement::Kind::kContinue: + return this->writeContinueStatement(s.as<ContinueStatement>()); + + case Statement::Kind::kDo: + return this->writeDoStatement(s.as<DoStatement>()); + + case Statement::Kind::kExpression: + return this->writeExpressionStatement(s.as<ExpressionStatement>()); + + case Statement::Kind::kFor: + return this->writeForStatement(s.as<ForStatement>()); + + case Statement::Kind::kIf: + return this->writeIfStatement(s.as<IfStatement>()); + + case Statement::Kind::kNop: + return true; + + case Statement::Kind::kReturn: + return this->writeReturnStatement(s.as<ReturnStatement>()); + + case Statement::Kind::kSwitch: + return this->writeSwitchStatement(s.as<SwitchStatement>()); + + case Statement::Kind::kVarDeclaration: + return this->writeVarDeclaration(s.as<VarDeclaration>()); + + default: + return unsupported(); + } +} + +bool Generator::writeBlock(const Block& b) { + for (const std::unique_ptr<Statement>& stmt : b.children()) { + if (!this->writeStatement(*stmt)) { + return unsupported(); + } + } + return true; +} + +bool Generator::writeBreakStatement(const BreakStatement&) { + // If all lanes have reached this break, we can just branch straight to the break target instead + // of updating masks. + fBuilder.branch_if_all_lanes_active(fCurrentBreakTarget); + fBuilder.mask_off_loop_mask(); + return true; +} + +bool Generator::writeContinueStatement(const ContinueStatement&) { + // This could be written as one hand-tuned RasterPipeline op, but for now, we reuse existing ops + // to assemble a continue op. + + // Set any currently-executing lanes in the continue-mask to true via `select.` + fCurrentContinueMask->enter(); + fBuilder.push_literal_i(~0); + fBuilder.select(/*slots=*/1); + + // Disable any currently-executing lanes from the loop mask. + fBuilder.mask_off_loop_mask(); + fCurrentContinueMask->exit(); + + return true; +} + +bool Generator::writeDoStatement(const DoStatement& d) { + // Set up a break target. + AutoLoopTarget breakTarget(this, &fCurrentBreakTarget); + + // Save off the original loop mask. + fBuilder.enableExecutionMaskWrites(); + fBuilder.push_loop_mask(); + + // If `continue` is used in the loop... + Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*d.statement()); + AutoContinueMask autoContinueMask(this); + if (loopInfo.fHasContinue) { + // ... create a temporary slot for continue-mask storage. + autoContinueMask.enable(); + } + + // Write the do-loop body. + int labelID = fBuilder.nextLabelID(); + fBuilder.label(labelID); + + autoContinueMask.enterLoopBody(); + + if (!this->writeStatement(*d.statement())) { + return false; + } + + autoContinueMask.exitLoopBody(); + + // Emit the test-expression, in order to combine it with the loop mask. + if (!this->pushExpression(*d.test())) { + return false; + } + + // Mask off any lanes in the loop mask where the test-expression is false; this breaks the loop. + // We don't use the test expression for anything else, so jettison it. + fBuilder.merge_loop_mask(); + this->discardExpression(/*slots=*/1); + + // If any lanes are still running, go back to the top and run the loop body again. + fBuilder.branch_if_any_lanes_active(labelID); + + // If we hit a break statement on all lanes, we will branch here to escape from the loop. + fBuilder.label(breakTarget.labelID()); + + // Restore the loop mask. + fBuilder.pop_loop_mask(); + fBuilder.disableExecutionMaskWrites(); + + return true; +} + +bool Generator::writeMasklessForStatement(const ForStatement& f) { + SkASSERT(f.unrollInfo()); + SkASSERT(f.unrollInfo()->fCount > 0); + SkASSERT(f.initializer()); + SkASSERT(f.test()); + SkASSERT(f.next()); + + // If no lanes are active, skip over the loop entirely. This guards against looping forever; + // with no lanes active, we wouldn't be able to write the loop variable back to its slot, so + // we'd never make forward progress. + int loopExitID = fBuilder.nextLabelID(); + int loopBodyID = fBuilder.nextLabelID(); + fBuilder.branch_if_no_lanes_active(loopExitID); + + // Run the loop initializer. + if (!this->writeStatement(*f.initializer())) { + return unsupported(); + } + + // Write the for-loop body. We know the for-loop has a standard ES2 unrollable structure, and + // that it runs for at least one iteration, so we can plow straight ahead into the loop body + // instead of running the loop-test first. + fBuilder.label(loopBodyID); + + if (!this->writeStatement(*f.statement())) { + return unsupported(); + } + + // If the loop only runs for a single iteration, we are already done. If not... + if (f.unrollInfo()->fCount > 1) { + // ... run the next-expression, and immediately discard its result. + if (!this->pushExpression(*f.next(), /*usesResult=*/false)) { + return unsupported(); + } + this->discardExpression(f.next()->type().slotCount()); + + // Run the test-expression, and repeat the loop until the test-expression evaluates false. + if (!this->pushExpression(*f.test())) { + return unsupported(); + } + fBuilder.branch_if_no_active_lanes_on_stack_top_equal(0, loopBodyID); + + // Jettison the test-expression. + this->discardExpression(/*slots=*/1); + } + + fBuilder.label(loopExitID); + return true; +} + +bool Generator::writeForStatement(const ForStatement& f) { + // If we've determined that the loop does not run, omit its code entirely. + if (f.unrollInfo() && f.unrollInfo()->fCount == 0) { + return true; + } + + // If the loop doesn't escape early due to a `continue`, `break` or `return`, and the loop + // conforms to ES2 structure, we know that we will run the full number of iterations across all + // lanes and don't need to use a loop mask. + Analysis::LoopControlFlowInfo loopInfo = Analysis::GetLoopControlFlowInfo(*f.statement()); + if (!loopInfo.fHasContinue && !loopInfo.fHasBreak && !loopInfo.fHasReturn && f.unrollInfo()) { + return this->writeMasklessForStatement(f); + } + + // Set up a break target. + AutoLoopTarget breakTarget(this, &fCurrentBreakTarget); + + // Run the loop initializer. + if (f.initializer() && !this->writeStatement(*f.initializer())) { + return unsupported(); + } + + AutoContinueMask autoContinueMask(this); + if (loopInfo.fHasContinue) { + // Acquire a temporary slot for continue-mask storage. + autoContinueMask.enable(); + } + + // Save off the original loop mask. + fBuilder.enableExecutionMaskWrites(); + fBuilder.push_loop_mask(); + + int loopTestID = fBuilder.nextLabelID(); + int loopBodyID = fBuilder.nextLabelID(); + + // Jump down to the loop test so we can fall out of the loop immediately if it's zero-iteration. + fBuilder.jump(loopTestID); + + // Write the for-loop body. + fBuilder.label(loopBodyID); + + autoContinueMask.enterLoopBody(); + + if (!this->writeStatement(*f.statement())) { + return unsupported(); + } + + autoContinueMask.exitLoopBody(); + + // Run the next-expression. Immediately discard its result. + if (f.next()) { + if (!this->pushExpression(*f.next(), /*usesResult=*/false)) { + return unsupported(); + } + this->discardExpression(f.next()->type().slotCount()); + } + + fBuilder.label(loopTestID); + if (f.test()) { + // Emit the test-expression, in order to combine it with the loop mask. + if (!this->pushExpression(*f.test())) { + return unsupported(); + } + // Mask off any lanes in the loop mask where the test-expression is false; this breaks the + // loop. We don't use the test expression for anything else, so jettison it. + fBuilder.merge_loop_mask(); + this->discardExpression(/*slots=*/1); + } + + // If any lanes are still running, go back to the top and run the loop body again. + fBuilder.branch_if_any_lanes_active(loopBodyID); + + // If we hit a break statement on all lanes, we will branch here to escape from the loop. + fBuilder.label(breakTarget.labelID()); + + // Restore the loop mask. + fBuilder.pop_loop_mask(); + fBuilder.disableExecutionMaskWrites(); + + return true; +} + +bool Generator::writeExpressionStatement(const ExpressionStatement& e) { + if (!this->pushExpression(*e.expression(), /*usesResult=*/false)) { + return unsupported(); + } + this->discardExpression(e.expression()->type().slotCount()); + return true; +} + +bool Generator::writeDynamicallyUniformIfStatement(const IfStatement& i) { + SkASSERT(Analysis::IsDynamicallyUniformExpression(*i.test())); + + int falseLabelID = fBuilder.nextLabelID(); + int exitLabelID = fBuilder.nextLabelID(); + + if (!this->pushExpression(*i.test())) { + return unsupported(); + } + + fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID); + + if (!this->writeStatement(*i.ifTrue())) { + return unsupported(); + } + + if (!i.ifFalse()) { + // We don't have an if-false condition at all. + fBuilder.label(falseLabelID); + } else { + // We do have an if-false condition. We've just completed the if-true block, so we need to + // jump past the if-false block to avoid executing it. + fBuilder.jump(exitLabelID); + + // The if-false block starts here. + fBuilder.label(falseLabelID); + + if (!this->writeStatement(*i.ifFalse())) { + return unsupported(); + } + + fBuilder.label(exitLabelID); + } + + // Jettison the test-expression. + this->discardExpression(/*slots=*/1); + return true; +} + +bool Generator::writeIfStatement(const IfStatement& i) { + // If the test condition is known to be uniform, we can skip over the untrue portion entirely. + if (Analysis::IsDynamicallyUniformExpression(*i.test())) { + return this->writeDynamicallyUniformIfStatement(i); + } + + // Save the current condition-mask. + fBuilder.enableExecutionMaskWrites(); + fBuilder.push_condition_mask(); + + // Push the test condition mask. + if (!this->pushExpression(*i.test())) { + return unsupported(); + } + + // Merge the current condition-mask with the test condition, then run the if-true branch. + fBuilder.merge_condition_mask(); + if (!this->writeStatement(*i.ifTrue())) { + return unsupported(); + } + + if (i.ifFalse()) { + // Negate the test-condition, then reapply it to the condition-mask. + // Then, run the if-false branch. + fBuilder.unary_op(BuilderOp::bitwise_not_int, /*slots=*/1); + fBuilder.merge_condition_mask(); + if (!this->writeStatement(*i.ifFalse())) { + return unsupported(); + } + } + + // Jettison the test-expression, and restore the the condition-mask. + this->discardExpression(/*slots=*/1); + fBuilder.pop_condition_mask(); + fBuilder.disableExecutionMaskWrites(); + + return true; +} + +bool Generator::writeReturnStatement(const ReturnStatement& r) { + if (r.expression()) { + if (!this->pushExpression(*r.expression())) { + return unsupported(); + } + if (this->needsFunctionResultSlots()) { + this->popToSlotRange(fCurrentFunctionResult); + } + } + if (fBuilder.executionMaskWritesAreEnabled() && this->needsReturnMask()) { + fBuilder.mask_off_return_mask(); + } + return true; +} + +bool Generator::writeSwitchStatement(const SwitchStatement& s) { + const StatementArray& cases = s.cases(); + SkASSERT(std::all_of(cases.begin(), cases.end(), [](const std::unique_ptr<Statement>& stmt) { + return stmt->is<SwitchCase>(); + })); + + // Set up a break target. + AutoLoopTarget breakTarget(this, &fCurrentBreakTarget); + + // Save off the original loop mask. + fBuilder.enableExecutionMaskWrites(); + fBuilder.push_loop_mask(); + + // Push the switch-case value, and write a default-mask that enables every lane which already + // has an active loop mask. As we match cases, the default mask will get pared down. + if (!this->pushExpression(*s.value())) { + return unsupported(); + } + fBuilder.push_loop_mask(); + + // Zero out the loop mask; each case op will re-enable it as we go. + fBuilder.mask_off_loop_mask(); + + // Write each switch-case. + bool foundDefaultCase = false; + for (const std::unique_ptr<Statement>& stmt : cases) { + int skipLabelID = fBuilder.nextLabelID(); + + const SwitchCase& sc = stmt->as<SwitchCase>(); + if (sc.isDefault()) { + foundDefaultCase = true; + if (stmt.get() != cases.back().get()) { + // We only support a default case when it is the very last case. If that changes, + // this logic will need to be updated. + return unsupported(); + } + // Keep whatever lanes are executing now, and also enable any lanes in the default mask. + fBuilder.pop_and_reenable_loop_mask(); + // Execute the switch-case block, if any lanes are alive to see it. + fBuilder.branch_if_no_lanes_active(skipLabelID); + if (!this->writeStatement(*sc.statement())) { + return unsupported(); + } + } else { + // The case-op will enable the loop mask if the switch-value matches, and mask off lanes + // from the default-mask. + fBuilder.case_op(sc.value()); + // Execute the switch-case block, if any lanes are alive to see it. + fBuilder.branch_if_no_lanes_active(skipLabelID); + if (!this->writeStatement(*sc.statement())) { + return unsupported(); + } + } + fBuilder.label(skipLabelID); + } + + // Jettison the switch value, and the default case mask if it was never consumed above. + this->discardExpression(/*slots=*/foundDefaultCase ? 1 : 2); + + // If we hit a break statement on all lanes, we will branch here to escape from the switch. + fBuilder.label(breakTarget.labelID()); + + // Restore the loop mask. + fBuilder.pop_loop_mask(); + fBuilder.disableExecutionMaskWrites(); + return true; +} + +bool Generator::writeVarDeclaration(const VarDeclaration& v) { + if (v.value()) { + if (!this->pushExpression(*v.value())) { + return unsupported(); + } + this->popToSlotRangeUnmasked(this->getVariableSlots(*v.var())); + } else { + this->zeroSlotRangeUnmasked(this->getVariableSlots(*v.var())); + } + return true; +} + +bool Generator::pushExpression(const Expression& e, bool usesResult) { + switch (e.kind()) { + case Expression::Kind::kBinary: + return this->pushBinaryExpression(e.as<BinaryExpression>()); + + case Expression::Kind::kChildCall: + return this->pushChildCall(e.as<ChildCall>()); + + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorArrayCast: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorStruct: + return this->pushConstructorCompound(e.asAnyConstructor()); + + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorScalarCast: + return this->pushConstructorCast(e.asAnyConstructor()); + + case Expression::Kind::kConstructorDiagonalMatrix: + return this->pushConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>()); + + case Expression::Kind::kConstructorMatrixResize: + return this->pushConstructorMatrixResize(e.as<ConstructorMatrixResize>()); + + case Expression::Kind::kConstructorSplat: + return this->pushConstructorSplat(e.as<ConstructorSplat>()); + + case Expression::Kind::kFieldAccess: + return this->pushFieldAccess(e.as<FieldAccess>()); + + case Expression::Kind::kFunctionCall: + return this->pushFunctionCall(e.as<FunctionCall>()); + + case Expression::Kind::kIndex: + return this->pushIndexExpression(e.as<IndexExpression>()); + + case Expression::Kind::kLiteral: + return this->pushLiteral(e.as<Literal>()); + + case Expression::Kind::kPrefix: + return this->pushPrefixExpression(e.as<PrefixExpression>()); + + case Expression::Kind::kPostfix: + return this->pushPostfixExpression(e.as<PostfixExpression>(), usesResult); + + case Expression::Kind::kSwizzle: + return this->pushSwizzle(e.as<Swizzle>()); + + case Expression::Kind::kTernary: + return this->pushTernaryExpression(e.as<TernaryExpression>()); + + case Expression::Kind::kVariableReference: + return this->pushVariableReference(e.as<VariableReference>()); + + default: + return unsupported(); + } +} + +BuilderOp Generator::GetTypedOp(const SkSL::Type& type, const TypedOps& ops) { + switch (type.componentType().numberKind()) { + case Type::NumberKind::kFloat: return ops.fFloatOp; + case Type::NumberKind::kSigned: return ops.fSignedOp; + case Type::NumberKind::kUnsigned: return ops.fUnsignedOp; + case Type::NumberKind::kBoolean: return ops.fBooleanOp; + default: return BuilderOp::unsupported; + } +} + +bool Generator::unaryOp(const SkSL::Type& type, const TypedOps& ops) { + BuilderOp op = GetTypedOp(type, ops); + if (op == BuilderOp::unsupported) { + return unsupported(); + } + fBuilder.unary_op(op, type.slotCount()); + return true; +} + +bool Generator::binaryOp(const SkSL::Type& type, const TypedOps& ops) { + BuilderOp op = GetTypedOp(type, ops); + if (op == BuilderOp::unsupported) { + return unsupported(); + } + fBuilder.binary_op(op, type.slotCount()); + return true; +} + +bool Generator::ternaryOp(const SkSL::Type& type, const TypedOps& ops) { + BuilderOp op = GetTypedOp(type, ops); + if (op == BuilderOp::unsupported) { + return unsupported(); + } + fBuilder.ternary_op(op, type.slotCount()); + return true; +} + +void Generator::foldWithMultiOp(BuilderOp op, int elements) { + // Fold the top N elements on the stack using an op that supports multiple slots, e.g.: + // (A + B + C + D) -> add_2_floats $0..1 += $2..3 + // add_float $0 += $1 + for (; elements >= 8; elements -= 4) { + fBuilder.binary_op(op, /*slots=*/4); + } + for (; elements >= 6; elements -= 3) { + fBuilder.binary_op(op, /*slots=*/3); + } + for (; elements >= 4; elements -= 2) { + fBuilder.binary_op(op, /*slots=*/2); + } + for (; elements >= 2; elements -= 1) { + fBuilder.binary_op(op, /*slots=*/1); + } +} + +bool Generator::pushLValueOrExpression(LValue* lvalue, const Expression& expr) { + return lvalue ? this->push(*lvalue) + : this->pushExpression(expr); +} + +bool Generator::pushMatrixMultiply(LValue* lvalue, + const Expression& left, + const Expression& right, + int leftColumns, + int leftRows, + int rightColumns, + int rightRows) { + SkASSERT(left.type().isMatrix() || left.type().isVector()); + SkASSERT(right.type().isMatrix() || right.type().isVector()); + + SkASSERT(leftColumns == rightRows); + int outColumns = rightColumns, + outRows = leftRows; + + // Push the left matrix onto the adjacent-neighbor stack. We transpose it so that we can copy + // rows from it in a single op, instead of gathering one element at a time. + AutoStack matrixStack(this); + matrixStack.enter(); + if (!this->pushLValueOrExpression(lvalue, left)) { + return unsupported(); + } + fBuilder.transpose(leftColumns, leftRows); + + // Push the right matrix as well, then go back to the primary stack. + if (!this->pushExpression(right)) { + return unsupported(); + } + matrixStack.exit(); + + // Calculate the offsets of the left- and right-matrix, relative to the stack-top. + int leftMtxBase = left.type().slotCount() + right.type().slotCount(); + int rightMtxBase = right.type().slotCount(); + + // Emit each matrix element. + for (int c = 0; c < outColumns; ++c) { + for (int r = 0; r < outRows; ++r) { + // Dot a vector from left[*][r] with right[c][*]. + // (Because the left=matrix has been transposed, we actually pull left[r][*], which + // allows us to clone a column at once instead of cloning each slot individually.) + matrixStack.pushClone(SlotRange{r * leftColumns, leftColumns}, leftMtxBase); + matrixStack.pushClone(SlotRange{c * leftColumns, leftColumns}, rightMtxBase); + fBuilder.dot_floats(leftColumns); + } + } + + // Dispose of the source matrices on the adjacent-neighbor stack. + matrixStack.enter(); + this->discardExpression(left.type().slotCount()); + this->discardExpression(right.type().slotCount()); + matrixStack.exit(); + + // If this multiply was actually an assignment (via *=), write the result back to the lvalue. + return lvalue ? this->store(*lvalue) + : true; +} + +void Generator::foldComparisonOp(Operator op, int elements) { + switch (op.kind()) { + case OperatorKind::EQEQ: + // equal(x,y) returns a vector; use & to fold into a scalar. + this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, elements); + break; + + case OperatorKind::NEQ: + // notEqual(x,y) returns a vector; use | to fold into a scalar. + this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, elements); + break; + + default: + SkDEBUGFAIL("comparison only allows == and !="); + break; + } +} + +bool Generator::pushStructuredComparison(LValue* left, + Operator op, + LValue* right, + const Type& type) { + if (type.isStruct()) { + // Compare every field in the struct. + SkSpan<const Type::Field> fields = type.fields(); + int currentSlot = 0; + for (size_t index = 0; index < fields.size(); ++index) { + const Type& fieldType = *fields[index].fType; + const int fieldSlotCount = fieldType.slotCount(); + UnownedLValueSlice fieldLeft {left, currentSlot, fieldSlotCount}; + UnownedLValueSlice fieldRight{right, currentSlot, fieldSlotCount}; + if (!this->pushStructuredComparison(&fieldLeft, op, &fieldRight, fieldType)) { + return unsupported(); + } + currentSlot += fieldSlotCount; + } + + this->foldComparisonOp(op, fields.size()); + return true; + } + + if (type.isArray()) { + const Type& indexedType = type.componentType(); + if (indexedType.numberKind() == Type::NumberKind::kNonnumeric) { + // Compare every element in the array. + const int indexedSlotCount = indexedType.slotCount(); + int currentSlot = 0; + for (int index = 0; index < type.columns(); ++index) { + UnownedLValueSlice indexedLeft {left, currentSlot, indexedSlotCount}; + UnownedLValueSlice indexedRight{right, currentSlot, indexedSlotCount}; + if (!this->pushStructuredComparison(&indexedLeft, op, &indexedRight, indexedType)) { + return unsupported(); + } + currentSlot += indexedSlotCount; + } + + this->foldComparisonOp(op, type.columns()); + return true; + } + } + + // We've winnowed down to a single element, or an array of homogeneous numeric elements. + // Push the elements onto the stack, then compare them. + if (!this->push(*left) || !this->push(*right)) { + return unsupported(); + } + switch (op.kind()) { + case OperatorKind::EQEQ: + if (!this->binaryOp(type, kEqualOps)) { + return unsupported(); + } + break; + + case OperatorKind::NEQ: + if (!this->binaryOp(type, kNotEqualOps)) { + return unsupported(); + } + break; + + default: + SkDEBUGFAIL("comparison only allows == and !="); + break; + } + + this->foldComparisonOp(op, type.slotCount()); + return true; +} + +bool Generator::pushBinaryExpression(const BinaryExpression& e) { + return this->pushBinaryExpression(*e.left(), e.getOperator(), *e.right()); +} + +bool Generator::pushBinaryExpression(const Expression& left, Operator op, const Expression& right) { + switch (op.kind()) { + // Rewrite greater-than ops as their less-than equivalents. + case OperatorKind::GT: + return this->pushBinaryExpression(right, OperatorKind::LT, left); + + case OperatorKind::GTEQ: + return this->pushBinaryExpression(right, OperatorKind::LTEQ, left); + + // Handle struct and array comparisons. + case OperatorKind::EQEQ: + case OperatorKind::NEQ: + if (left.type().isStruct() || left.type().isArray()) { + SkASSERT(left.type().matches(right.type())); + std::unique_ptr<LValue> lvLeft = this->makeLValue(left, /*allowScratch=*/true); + std::unique_ptr<LValue> lvRight = this->makeLValue(right, /*allowScratch=*/true); + return this->pushStructuredComparison(lvLeft.get(), op, lvRight.get(), left.type()); + } + break; + + // Emit comma expressions. + case OperatorKind::COMMA: + if (Analysis::HasSideEffects(left)) { + if (!this->pushExpression(left, /*usesResult=*/false)) { + return unsupported(); + } + this->discardExpression(left.type().slotCount()); + } + return this->pushExpression(right); + + default: + break; + } + + // Handle binary expressions with mismatched types. + bool vectorizeLeft = false, vectorizeRight = false; + if (!left.type().matches(right.type())) { + if (left.type().componentType().numberKind() != right.type().componentType().numberKind()) { + return unsupported(); + } + if (left.type().isScalar() && (right.type().isVector() || right.type().isMatrix())) { + vectorizeLeft = true; + } else if ((left.type().isVector() || left.type().isMatrix()) && right.type().isScalar()) { + vectorizeRight = true; + } + } + + const Type& type = vectorizeLeft ? right.type() : left.type(); + + // If this is an assignment... + std::unique_ptr<LValue> lvalue; + if (op.isAssignment()) { + // ... turn the left side into an lvalue. + lvalue = this->makeLValue(left); + if (!lvalue) { + return unsupported(); + } + + // Handle simple assignment (`var = expr`). + if (op.kind() == OperatorKind::EQ) { + return this->pushExpression(right) && + this->store(*lvalue); + } + + // Strip off the assignment from the op (turning += into +). + op = op.removeAssignment(); + } + + // Handle matrix multiplication (MxM/MxV/VxM). + if (op.kind() == OperatorKind::STAR) { + // Matrix * matrix: + if (left.type().isMatrix() && right.type().isMatrix()) { + return this->pushMatrixMultiply(lvalue.get(), left, right, + left.type().columns(), left.type().rows(), + right.type().columns(), right.type().rows()); + } + + // Vector * matrix: + if (left.type().isVector() && right.type().isMatrix()) { + return this->pushMatrixMultiply(lvalue.get(), left, right, + left.type().columns(), 1, + right.type().columns(), right.type().rows()); + } + + // Matrix * vector: + if (left.type().isMatrix() && right.type().isVector()) { + return this->pushMatrixMultiply(lvalue.get(), left, right, + left.type().columns(), left.type().rows(), + 1, right.type().columns()); + } + } + + if (!vectorizeLeft && !vectorizeRight && !type.matches(right.type())) { + // We have mismatched types but don't know how to handle them. + return unsupported(); + } + + // Handle binary ops which require short-circuiting. + switch (op.kind()) { + case OperatorKind::LOGICALAND: + if (Analysis::HasSideEffects(right)) { + // If the RHS has side effects, we rewrite `a && b` as `a ? b : false`. This + // generates pretty solid code and gives us the required short-circuit behavior. + SkASSERT(!op.isAssignment()); + SkASSERT(type.componentType().isBoolean()); + SkASSERT(type.slotCount() == 1); // operator&& only works with scalar types + Literal falseLiteral{Position{}, 0.0, &right.type()}; + return this->pushTernaryExpression(left, right, falseLiteral); + } + break; + + case OperatorKind::LOGICALOR: + if (Analysis::HasSideEffects(right)) { + // If the RHS has side effects, we rewrite `a || b` as `a ? true : b`. + SkASSERT(!op.isAssignment()); + SkASSERT(type.componentType().isBoolean()); + SkASSERT(type.slotCount() == 1); // operator|| only works with scalar types + Literal trueLiteral{Position{}, 1.0, &right.type()}; + return this->pushTernaryExpression(left, trueLiteral, right); + } + break; + + default: + break; + } + + // Push the left- and right-expressions onto the stack. + if (!this->pushLValueOrExpression(lvalue.get(), left)) { + return unsupported(); + } + if (vectorizeLeft) { + fBuilder.push_duplicates(right.type().slotCount() - 1); + } + if (!this->pushExpression(right)) { + return unsupported(); + } + if (vectorizeRight) { + fBuilder.push_duplicates(left.type().slotCount() - 1); + } + + switch (op.kind()) { + case OperatorKind::PLUS: + if (!this->binaryOp(type, kAddOps)) { + return unsupported(); + } + break; + + case OperatorKind::MINUS: + if (!this->binaryOp(type, kSubtractOps)) { + return unsupported(); + } + break; + + case OperatorKind::STAR: + if (!this->binaryOp(type, kMultiplyOps)) { + return unsupported(); + } + break; + + case OperatorKind::SLASH: + if (!this->binaryOp(type, kDivideOps)) { + return unsupported(); + } + break; + + case OperatorKind::LT: + case OperatorKind::GT: + if (!this->binaryOp(type, kLessThanOps)) { + return unsupported(); + } + SkASSERT(type.slotCount() == 1); // operator< only works with scalar types + break; + + case OperatorKind::LTEQ: + case OperatorKind::GTEQ: + if (!this->binaryOp(type, kLessThanEqualOps)) { + return unsupported(); + } + SkASSERT(type.slotCount() == 1); // operator<= only works with scalar types + break; + + case OperatorKind::EQEQ: + if (!this->binaryOp(type, kEqualOps)) { + return unsupported(); + } + this->foldComparisonOp(op, type.slotCount()); + break; + + case OperatorKind::NEQ: + if (!this->binaryOp(type, kNotEqualOps)) { + return unsupported(); + } + this->foldComparisonOp(op, type.slotCount()); + break; + + case OperatorKind::LOGICALAND: + case OperatorKind::BITWISEAND: + // For logical-and, we verified above that the RHS does not have side effects, so we + // don't need to worry about short-circuiting side effects. + fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, type.slotCount()); + break; + + case OperatorKind::LOGICALOR: + case OperatorKind::BITWISEOR: + // For logical-or, we verified above that the RHS does not have side effects. + fBuilder.binary_op(BuilderOp::bitwise_or_n_ints, type.slotCount()); + break; + + case OperatorKind::LOGICALXOR: + case OperatorKind::BITWISEXOR: + // Logical-xor does not short circuit. + fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, type.slotCount()); + break; + + default: + return unsupported(); + } + + // If we have an lvalue, we need to write the result back into it. + return lvalue ? this->store(*lvalue) + : true; +} + +bool Generator::pushConstructorCompound(const AnyConstructor& c) { + for (const std::unique_ptr<Expression> &arg : c.argumentSpan()) { + if (!this->pushExpression(*arg)) { + return unsupported(); + } + } + return true; +} + +bool Generator::pushChildCall(const ChildCall& c) { + int* childIdx = fChildEffectMap.find(&c.child()); + SkASSERT(childIdx != nullptr); + SkASSERT(!c.arguments().empty()); + + // Save the dst.rgba fields; these hold our execution masks, and could potentially be + // clobbered by the child effect. + fBuilder.push_dst_rgba(); + + // All child calls have at least one argument. + const Expression* arg = c.arguments()[0].get(); + if (!this->pushExpression(*arg)) { + return unsupported(); + } + + // Copy arguments from the stack into src/dst as required by this particular child-call. + switch (c.child().type().typeKind()) { + case Type::TypeKind::kShader: { + // The argument must be a float2. + SkASSERT(c.arguments().size() == 1); + SkASSERT(arg->type().matches(*fContext.fTypes.fFloat2)); + fBuilder.pop_src_rg(); + fBuilder.invoke_shader(*childIdx); + break; + } + case Type::TypeKind::kColorFilter: { + // The argument must be a half4/float4. + SkASSERT(c.arguments().size() == 1); + SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) || + arg->type().matches(*fContext.fTypes.fFloat4)); + fBuilder.pop_src_rgba(); + fBuilder.invoke_color_filter(*childIdx); + break; + } + case Type::TypeKind::kBlender: { + // The first argument must be a half4/float4. + SkASSERT(c.arguments().size() == 2); + SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) || + arg->type().matches(*fContext.fTypes.fFloat4)); + + // The second argument must also be a half4/float4. + arg = c.arguments()[1].get(); + SkASSERT(arg->type().matches(*fContext.fTypes.fHalf4) || + arg->type().matches(*fContext.fTypes.fFloat4)); + + if (!this->pushExpression(*arg)) { + return unsupported(); + } + + fBuilder.pop_dst_rgba(); + fBuilder.pop_src_rgba(); + fBuilder.invoke_blender(*childIdx); + break; + } + default: { + SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str()); + } + } + + // Restore dst.rgba so our execution masks are back to normal. + fBuilder.pop_dst_rgba(); + + // The child call has returned the result color via src.rgba; push it onto the stack. + fBuilder.push_src_rgba(); + return true; +} + +bool Generator::pushConstructorCast(const AnyConstructor& c) { + SkASSERT(c.argumentSpan().size() == 1); + const Expression& inner = *c.argumentSpan().front(); + SkASSERT(inner.type().slotCount() == c.type().slotCount()); + + if (!this->pushExpression(inner)) { + return unsupported(); + } + if (inner.type().componentType().numberKind() == c.type().componentType().numberKind()) { + // Since we ignore type precision, this cast is effectively a no-op. + return true; + } + if (inner.type().componentType().isSigned() && c.type().componentType().isUnsigned()) { + // Treat uint(int) as a no-op. + return true; + } + if (inner.type().componentType().isUnsigned() && c.type().componentType().isSigned()) { + // Treat int(uint) as a no-op. + return true; + } + + if (c.type().componentType().isBoolean()) { + // Converting int or float to boolean can be accomplished via `notEqual(x, 0)`. + fBuilder.push_zeros(c.type().slotCount()); + return this->binaryOp(inner.type(), kNotEqualOps); + } + if (inner.type().componentType().isBoolean()) { + // Converting boolean to int or float can be accomplished via bitwise-and. + if (c.type().componentType().isFloat()) { + fBuilder.push_literal_f(1.0f); + } else if (c.type().componentType().isSigned() || c.type().componentType().isUnsigned()) { + fBuilder.push_literal_i(1); + } else { + SkDEBUGFAILF("unexpected cast from bool to %s", c.type().description().c_str()); + return unsupported(); + } + fBuilder.push_duplicates(c.type().slotCount() - 1); + fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, c.type().slotCount()); + return true; + } + // We have dedicated ops to cast between float and integer types. + if (inner.type().componentType().isFloat()) { + if (c.type().componentType().isSigned()) { + fBuilder.unary_op(BuilderOp::cast_to_int_from_float, c.type().slotCount()); + return true; + } + if (c.type().componentType().isUnsigned()) { + fBuilder.unary_op(BuilderOp::cast_to_uint_from_float, c.type().slotCount()); + return true; + } + } else if (c.type().componentType().isFloat()) { + if (inner.type().componentType().isSigned()) { + fBuilder.unary_op(BuilderOp::cast_to_float_from_int, c.type().slotCount()); + return true; + } + if (inner.type().componentType().isUnsigned()) { + fBuilder.unary_op(BuilderOp::cast_to_float_from_uint, c.type().slotCount()); + return true; + } + } + + SkDEBUGFAILF("unexpected cast from %s to %s", + c.type().description().c_str(), inner.type().description().c_str()); + return unsupported(); +} + +bool Generator::pushConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c) { + fBuilder.push_zeros(1); + if (!this->pushExpression(*c.argument())) { + return unsupported(); + } + fBuilder.diagonal_matrix(c.type().columns(), c.type().rows()); + + return true; +} + +bool Generator::pushConstructorMatrixResize(const ConstructorMatrixResize& c) { + if (!this->pushExpression(*c.argument())) { + return unsupported(); + } + fBuilder.matrix_resize(c.argument()->type().columns(), + c.argument()->type().rows(), + c.type().columns(), + c.type().rows()); + return true; +} + +bool Generator::pushConstructorSplat(const ConstructorSplat& c) { + if (!this->pushExpression(*c.argument())) { + return unsupported(); + } + fBuilder.push_duplicates(c.type().slotCount() - 1); + return true; +} + +bool Generator::pushFieldAccess(const FieldAccess& f) { + // If possible, get direct field access via the lvalue. + std::unique_ptr<LValue> lvalue = this->makeLValue(f, /*allowScratch=*/true); + return lvalue && this->push(*lvalue); +} + +bool Generator::pushFunctionCall(const FunctionCall& c) { + if (c.function().isIntrinsic()) { + return this->pushIntrinsic(c); + } + + // Keep track of the current function. + const FunctionDefinition* lastFunction = fCurrentFunction; + fCurrentFunction = c.function().definition(); + + // Skip over the function body entirely if there are no active lanes. + // (If the function call was trivial, it would likely have been inlined in the frontend, so this + // is likely to save a significant amount of work if the lanes are all dead.) + int skipLabelID = fBuilder.nextLabelID(); + fBuilder.branch_if_no_lanes_active(skipLabelID); + + // Save off the return mask. + if (this->needsReturnMask()) { + fBuilder.enableExecutionMaskWrites(); + fBuilder.push_return_mask(); + } + + // Write all the arguments into their parameter's variable slots. Because we never allow + // recursion, we don't need to worry about overwriting any existing values in those slots. + // (In fact, we don't even need to apply the write mask.) + SkTArray<std::unique_ptr<LValue>> lvalues; + lvalues.resize(c.arguments().size()); + + for (int index = 0; index < c.arguments().size(); ++index) { + const Expression& arg = *c.arguments()[index]; + const Variable& param = *c.function().parameters()[index]; + + // Use LValues for out-parameters and inout-parameters, so we can store back to them later. + if (IsInoutParameter(param) || IsOutParameter(param)) { + lvalues[index] = this->makeLValue(arg); + if (!lvalues[index]) { + return unsupported(); + } + // There are no guarantees on the starting value of an out-parameter, so we only need to + // store the lvalues associated with an inout parameter. + if (IsInoutParameter(param)) { + if (!this->push(*lvalues[index])) { + return unsupported(); + } + this->popToSlotRangeUnmasked(this->getVariableSlots(param)); + } + } else { + // Copy input arguments into their respective parameter slots. + if (!this->pushExpression(arg)) { + return unsupported(); + } + this->popToSlotRangeUnmasked(this->getVariableSlots(param)); + } + } + + // Emit the function body. + std::optional<SlotRange> r = this->writeFunction(c, *fCurrentFunction); + if (!r.has_value()) { + return unsupported(); + } + + // Restore the original return mask. + if (this->needsReturnMask()) { + fBuilder.pop_return_mask(); + fBuilder.disableExecutionMaskWrites(); + } + + // If the function uses result slots, move its result from slots onto the stack. + if (this->needsFunctionResultSlots()) { + fBuilder.push_slots(*r); + } + + // We've returned back to the last function. + fCurrentFunction = lastFunction; + + // Copy out-parameters and inout-parameters back to their homes. + for (int index = 0; index < c.arguments().size(); ++index) { + if (lvalues[index]) { + // Only out- and inout-parameters should have an associated lvalue. + const Variable& param = *c.function().parameters()[index]; + SkASSERT(IsInoutParameter(param) || IsOutParameter(param)); + + // Copy the parameter's slots directly into the lvalue. + fBuilder.push_slots(this->getVariableSlots(param)); + if (!this->store(*lvalues[index])) { + return unsupported(); + } + this->discardExpression(param.type().slotCount()); + } + } + + // Copy the function result from its slots onto the stack. + fBuilder.label(skipLabelID); + return true; +} + +bool Generator::pushIndexExpression(const IndexExpression& i) { + std::unique_ptr<LValue> lvalue = this->makeLValue(i, /*allowScratch=*/true); + return lvalue && this->push(*lvalue); +} + +bool Generator::pushIntrinsic(const FunctionCall& c) { + const ExpressionArray& args = c.arguments(); + switch (args.size()) { + case 1: + return this->pushIntrinsic(c.function().intrinsicKind(), *args[0]); + + case 2: + return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1]); + + case 3: + return this->pushIntrinsic(c.function().intrinsicKind(), *args[0], *args[1], *args[2]); + + default: + break; + } + + return unsupported(); +} + +bool Generator::pushLengthIntrinsic(int slotCount) { + if (slotCount > 1) { + // Implement `length(vec)` as `sqrt(dot(x, x))`. + fBuilder.push_clone(slotCount); + fBuilder.dot_floats(slotCount); + fBuilder.unary_op(BuilderOp::sqrt_float, 1); + } else { + // `length(scalar)` is `sqrt(x^2)`, which is equivalent to `abs(x)`. + fBuilder.unary_op(BuilderOp::abs_float, 1); + } + return true; +} + +bool Generator::pushVectorizedExpression(const Expression& expr, const Type& vectorType) { + if (!this->pushExpression(expr)) { + return unsupported(); + } + if (vectorType.slotCount() > expr.type().slotCount()) { + SkASSERT(expr.type().slotCount() == 1); + fBuilder.push_duplicates(vectorType.slotCount() - expr.type().slotCount()); + } + return true; +} + +bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0) { + if (!this->pushExpression(arg0)) { + return unsupported(); + } + return this->unaryOp(arg0.type(), ops); +} + +bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0) { + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.unary_op(builderOp, arg0.type().slotCount()); + return true; +} + +bool Generator::pushIntrinsic(IntrinsicKind intrinsic, const Expression& arg0) { + switch (intrinsic) { + case IntrinsicKind::k_abs_IntrinsicKind: + return this->pushIntrinsic(kAbsOps, arg0); + + case IntrinsicKind::k_any_IntrinsicKind: + if (!this->pushExpression(arg0)) { + return unsupported(); + } + this->foldWithMultiOp(BuilderOp::bitwise_or_n_ints, arg0.type().slotCount()); + return true; + + case IntrinsicKind::k_all_IntrinsicKind: + if (!this->pushExpression(arg0)) { + return unsupported(); + } + this->foldWithMultiOp(BuilderOp::bitwise_and_n_ints, arg0.type().slotCount()); + return true; + + case IntrinsicKind::k_acos_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::acos_float, arg0); + + case IntrinsicKind::k_asin_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::asin_float, arg0); + + case IntrinsicKind::k_atan_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::atan_float, arg0); + + case IntrinsicKind::k_ceil_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::ceil_float, arg0); + + case IntrinsicKind::k_cos_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::cos_float, arg0); + + case IntrinsicKind::k_degrees_IntrinsicKind: { + Literal lit180OverPi{Position{}, 57.2957795131f, &arg0.type().componentType()}; + return this->pushBinaryExpression(arg0, OperatorKind::STAR, lit180OverPi); + } + case IntrinsicKind::k_floatBitsToInt_IntrinsicKind: + case IntrinsicKind::k_floatBitsToUint_IntrinsicKind: + case IntrinsicKind::k_intBitsToFloat_IntrinsicKind: + case IntrinsicKind::k_uintBitsToFloat_IntrinsicKind: + return this->pushExpression(arg0); + + case IntrinsicKind::k_exp_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::exp_float, arg0); + + case IntrinsicKind::k_exp2_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::exp2_float, arg0); + + case IntrinsicKind::k_floor_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::floor_float, arg0); + + case IntrinsicKind::k_fract_IntrinsicKind: + // Implement fract as `x - floor(x)`. + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.push_clone(arg0.type().slotCount()); + fBuilder.unary_op(BuilderOp::floor_float, arg0.type().slotCount()); + return this->binaryOp(arg0.type(), kSubtractOps); + + case IntrinsicKind::k_inverse_IntrinsicKind: + SkASSERT(arg0.type().isMatrix()); + SkASSERT(arg0.type().rows() == arg0.type().columns()); + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.inverse_matrix(arg0.type().rows()); + return true; + + case IntrinsicKind::k_inversesqrt_IntrinsicKind: + return this->pushIntrinsic(kInverseSqrtOps, arg0); + + case IntrinsicKind::k_length_IntrinsicKind: + return this->pushExpression(arg0) && + this->pushLengthIntrinsic(arg0.type().slotCount()); + + case IntrinsicKind::k_log_IntrinsicKind: + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.unary_op(BuilderOp::log_float, arg0.type().slotCount()); + return true; + + case IntrinsicKind::k_log2_IntrinsicKind: + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.unary_op(BuilderOp::log2_float, arg0.type().slotCount()); + return true; + + case IntrinsicKind::k_normalize_IntrinsicKind: { + // Implement normalize as `x / length(x)`. First, push the expression. + if (!this->pushExpression(arg0)) { + return unsupported(); + } + int slotCount = arg0.type().slotCount(); + if (slotCount > 1) { + // Instead of `x / sqrt(dot(x, x))`, we can get roughly the same result in less time + // by computing `x * invsqrt(dot(x, x))`. + fBuilder.push_clone(slotCount); + fBuilder.push_clone(slotCount); + fBuilder.dot_floats(slotCount); + + // Compute `vec(inversesqrt(dot(x, x)))`. + fBuilder.unary_op(BuilderOp::invsqrt_float, 1); + fBuilder.push_duplicates(slotCount - 1); + + // Return `x * vec(inversesqrt(dot(x, x)))`. + return this->binaryOp(arg0.type(), kMultiplyOps); + } else { + // For single-slot normalization, we can simplify `sqrt(x * x)` into `abs(x)`. + fBuilder.push_clone(slotCount); + fBuilder.unary_op(BuilderOp::abs_float, 1); + return this->binaryOp(arg0.type(), kDivideOps); + } + } + case IntrinsicKind::k_not_IntrinsicKind: + return this->pushPrefixExpression(OperatorKind::LOGICALNOT, arg0); + + case IntrinsicKind::k_radians_IntrinsicKind: { + Literal litPiOver180{Position{}, 0.01745329251f, &arg0.type().componentType()}; + return this->pushBinaryExpression(arg0, OperatorKind::STAR, litPiOver180); + } + case IntrinsicKind::k_saturate_IntrinsicKind: { + // Implement saturate as clamp(arg, 0, 1). + Literal zeroLiteral{Position{}, 0.0, &arg0.type().componentType()}; + Literal oneLiteral{Position{}, 1.0, &arg0.type().componentType()}; + return this->pushIntrinsic(k_clamp_IntrinsicKind, arg0, zeroLiteral, oneLiteral); + } + case IntrinsicKind::k_sign_IntrinsicKind: { + // Implement floating-point sign() as `clamp(arg * FLT_MAX, -1, 1)`. + // FLT_MIN * FLT_MAX evaluates to 4, so multiplying any float value against FLT_MAX is + // sufficient to ensure that |value| is always 1 or greater (excluding zero and nan). + // Integer sign() doesn't need to worry about fractional values or nans, and can simply + // be `clamp(arg, -1, 1)`. + if (!this->pushExpression(arg0)) { + return unsupported(); + } + if (arg0.type().componentType().isFloat()) { + Literal fltMaxLiteral{Position{}, FLT_MAX, &arg0.type().componentType()}; + if (!this->pushVectorizedExpression(fltMaxLiteral, arg0.type())) { + return unsupported(); + } + if (!this->binaryOp(arg0.type(), kMultiplyOps)) { + return unsupported(); + } + } + Literal neg1Literal{Position{}, -1.0, &arg0.type().componentType()}; + if (!this->pushVectorizedExpression(neg1Literal, arg0.type())) { + return unsupported(); + } + if (!this->binaryOp(arg0.type(), kMaxOps)) { + return unsupported(); + } + Literal pos1Literal{Position{}, 1.0, &arg0.type().componentType()}; + if (!this->pushVectorizedExpression(pos1Literal, arg0.type())) { + return unsupported(); + } + return this->binaryOp(arg0.type(), kMinOps); + } + case IntrinsicKind::k_sin_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::sin_float, arg0); + + case IntrinsicKind::k_sqrt_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::sqrt_float, arg0); + + case IntrinsicKind::k_tan_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::tan_float, arg0); + + case IntrinsicKind::k_transpose_IntrinsicKind: + SkASSERT(arg0.type().isMatrix()); + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.transpose(arg0.type().columns(), arg0.type().rows()); + return true; + + case IntrinsicKind::k_trunc_IntrinsicKind: + // Implement trunc as `float(int(x))`, since float-to-int rounds toward zero. + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.unary_op(BuilderOp::cast_to_int_from_float, arg0.type().slotCount()); + fBuilder.unary_op(BuilderOp::cast_to_float_from_int, arg0.type().slotCount()); + return true; + + case IntrinsicKind::k_fromLinearSrgb_IntrinsicKind: + case IntrinsicKind::k_toLinearSrgb_IntrinsicKind: { + // The argument must be a half3. + SkASSERT(arg0.type().matches(*fContext.fTypes.fHalf3)); + if (!this->pushExpression(arg0)) { + return unsupported(); + } + // The intrinsics accept a three-component value; add alpha for the push/pop_src_rgba + fBuilder.push_literal_f(1.0f); + // Copy arguments from the stack into src + fBuilder.pop_src_rgba(); + + if (intrinsic == IntrinsicKind::k_fromLinearSrgb_IntrinsicKind) { + fBuilder.invoke_from_linear_srgb(); + } else { + fBuilder.invoke_to_linear_srgb(); + } + + // The xform has left the result color in src.rgba; push it onto the stack + fBuilder.push_src_rgba(); + // The intrinsic returns a three-component value; discard alpha + this->discardExpression(/*slots=*/1); + return true; + } + + default: + break; + } + return unsupported(); +} + +bool Generator::pushIntrinsic(const TypedOps& ops, const Expression& arg0, const Expression& arg1) { + if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) { + return unsupported(); + } + return this->binaryOp(arg0.type(), ops); +} + +bool Generator::pushIntrinsic(BuilderOp builderOp, const Expression& arg0, const Expression& arg1) { + if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) { + return unsupported(); + } + fBuilder.binary_op(builderOp, arg0.type().slotCount()); + return true; +} + +bool Generator::pushIntrinsic(IntrinsicKind intrinsic, + const Expression& arg0, + const Expression& arg1) { + switch (intrinsic) { + case IntrinsicKind::k_atan_IntrinsicKind: + return this->pushIntrinsic(BuilderOp::atan2_n_floats, arg0, arg1); + + case IntrinsicKind::k_cross_IntrinsicKind: { + // Implement cross as `arg0.yzx * arg1.zxy - arg0.zxy * arg1.yzx`. We use two stacks so + // that each subexpression can be multiplied separately. + SkASSERT(arg0.type().matches(arg1.type())); + SkASSERT(arg0.type().slotCount() == 3); + SkASSERT(arg1.type().slotCount() == 3); + + // Push `arg0.yzx` onto this stack and `arg0.zxy` onto a separate subexpression stack. + AutoStack subexpressionStack(this); + subexpressionStack.enter(); + if (!this->pushExpression(arg0)) { + return unsupported(); + } + subexpressionStack.exit(); + subexpressionStack.pushClone(/*slots=*/3); + + fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0}); + subexpressionStack.enter(); + fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1}); + subexpressionStack.exit(); + + // Push `arg1.zxy` onto this stack and `arg1.yzx` onto the next stack. Perform the + // multiply on each subexpression (`arg0.yzx * arg1.zxy` on the first stack, and + // `arg0.zxy * arg1.yzx` on the next). + subexpressionStack.enter(); + if (!this->pushExpression(arg1)) { + return unsupported(); + } + subexpressionStack.exit(); + subexpressionStack.pushClone(/*slots=*/3); + + fBuilder.swizzle(/*consumedSlots=*/3, {2, 0, 1}); + fBuilder.binary_op(BuilderOp::mul_n_floats, 3); + + subexpressionStack.enter(); + fBuilder.swizzle(/*consumedSlots=*/3, {1, 2, 0}); + fBuilder.binary_op(BuilderOp::mul_n_floats, 3); + subexpressionStack.exit(); + + // Migrate the result of the second subexpression (`arg0.zxy * arg1.yzx`) back onto the + // main stack and subtract it from the first subexpression (`arg0.yzx * arg1.zxy`). + subexpressionStack.pushClone(/*slots=*/3); + fBuilder.binary_op(BuilderOp::sub_n_floats, 3); + + // Now that the calculation is complete, discard the subexpression on the next stack. + subexpressionStack.enter(); + this->discardExpression(/*slots=*/3); + subexpressionStack.exit(); + return true; + } + case IntrinsicKind::k_distance_IntrinsicKind: + // Implement distance as `length(a - b)`. + SkASSERT(arg0.type().slotCount() == arg1.type().slotCount()); + return this->pushBinaryExpression(arg0, OperatorKind::MINUS, arg1) && + this->pushLengthIntrinsic(arg0.type().slotCount()); + + case IntrinsicKind::k_dot_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { + return unsupported(); + } + fBuilder.dot_floats(arg0.type().slotCount()); + return true; + + case IntrinsicKind::k_equal_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(kEqualOps, arg0, arg1); + + case IntrinsicKind::k_notEqual_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(kNotEqualOps, arg0, arg1); + + case IntrinsicKind::k_lessThan_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(kLessThanOps, arg0, arg1); + + case IntrinsicKind::k_greaterThan_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(kLessThanOps, arg1, arg0); + + case IntrinsicKind::k_lessThanEqual_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(kLessThanEqualOps, arg0, arg1); + + case IntrinsicKind::k_greaterThanEqual_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(kLessThanEqualOps, arg1, arg0); + + case IntrinsicKind::k_min_IntrinsicKind: + SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); + return this->pushIntrinsic(kMinOps, arg0, arg1); + + case IntrinsicKind::k_matrixCompMult_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(kMultiplyOps, arg0, arg1); + + case IntrinsicKind::k_max_IntrinsicKind: + SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); + return this->pushIntrinsic(kMaxOps, arg0, arg1); + + case IntrinsicKind::k_mod_IntrinsicKind: + SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); + return this->pushIntrinsic(kModOps, arg0, arg1); + + case IntrinsicKind::k_pow_IntrinsicKind: + SkASSERT(arg0.type().matches(arg1.type())); + return this->pushIntrinsic(BuilderOp::pow_n_floats, arg0, arg1); + + case IntrinsicKind::k_reflect_IntrinsicKind: { + // Implement reflect as `I - (N * dot(I,N) * 2)`. + SkASSERT(arg0.type().matches(arg1.type())); + SkASSERT(arg0.type().slotCount() == arg1.type().slotCount()); + SkASSERT(arg0.type().componentType().isFloat()); + int slotCount = arg0.type().slotCount(); + + // Stack: I, N. + if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { + return unsupported(); + } + // Stack: I, N, I, N. + fBuilder.push_clone(2 * slotCount); + // Stack: I, N, dot(I,N) + fBuilder.dot_floats(slotCount); + // Stack: I, N, dot(I,N), 2 + fBuilder.push_literal_f(2.0); + // Stack: I, N, dot(I,N) * 2 + fBuilder.binary_op(BuilderOp::mul_n_floats, 1); + // Stack: I, N * dot(I,N) * 2 + fBuilder.push_duplicates(slotCount - 1); + fBuilder.binary_op(BuilderOp::mul_n_floats, slotCount); + // Stack: I - (N * dot(I,N) * 2) + fBuilder.binary_op(BuilderOp::sub_n_floats, slotCount); + return true; + } + case IntrinsicKind::k_step_IntrinsicKind: { + // Compute step as `float(lessThan(edge, x))`. We convert from boolean 0/~0 to floating + // point zero/one by using a bitwise-and against the bit-pattern of 1.0. + SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); + if (!this->pushVectorizedExpression(arg0, arg1.type()) || !this->pushExpression(arg1)) { + return unsupported(); + } + if (!this->binaryOp(arg1.type(), kLessThanOps)) { + return unsupported(); + } + Literal pos1Literal{Position{}, 1.0, &arg1.type().componentType()}; + if (!this->pushVectorizedExpression(pos1Literal, arg1.type())) { + return unsupported(); + } + fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, arg1.type().slotCount()); + return true; + } + + default: + break; + } + return unsupported(); +} + +bool Generator::pushIntrinsic(IntrinsicKind intrinsic, + const Expression& arg0, + const Expression& arg1, + const Expression& arg2) { + switch (intrinsic) { + case IntrinsicKind::k_clamp_IntrinsicKind: + // Implement clamp as min(max(arg, low), high). + SkASSERT(arg0.type().componentType().matches(arg1.type().componentType())); + SkASSERT(arg0.type().componentType().matches(arg2.type().componentType())); + if (!this->pushExpression(arg0) || !this->pushVectorizedExpression(arg1, arg0.type())) { + return unsupported(); + } + if (!this->binaryOp(arg0.type(), kMaxOps)) { + return unsupported(); + } + if (!this->pushVectorizedExpression(arg2, arg0.type())) { + return unsupported(); + } + if (!this->binaryOp(arg0.type(), kMinOps)) { + return unsupported(); + } + return true; + + case IntrinsicKind::k_faceforward_IntrinsicKind: { + // Implement faceforward as `N ^ ((0 <= dot(I, NRef)) & 0x80000000)`. + // In other words, flip the sign bit of N if `0 <= dot(I, NRef)`. + SkASSERT(arg0.type().matches(arg1.type())); + SkASSERT(arg0.type().matches(arg2.type())); + int slotCount = arg0.type().slotCount(); + + // Stack: N, 0, I, Nref + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.push_literal_f(0.0); + if (!this->pushExpression(arg1) || !this->pushExpression(arg2)) { + return unsupported(); + } + // Stack: N, 0, dot(I,NRef) + fBuilder.dot_floats(slotCount); + // Stack: N, (0 <= dot(I,NRef)) + fBuilder.binary_op(BuilderOp::cmple_n_floats, 1); + // Stack: N, (0 <= dot(I,NRef)), 0x80000000 + fBuilder.push_literal_i(0x80000000); + // Stack: N, (0 <= dot(I,NRef)) & 0x80000000) + fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, 1); + // Stack: N, vec(0 <= dot(I,NRef)) & 0x80000000) + fBuilder.push_duplicates(slotCount - 1); + // Stack: N ^ vec((0 <= dot(I,NRef)) & 0x80000000) + fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, slotCount); + return true; + } + case IntrinsicKind::k_mix_IntrinsicKind: + // Note: our SkRP mix op takes the interpolation point first, not the interpolants. + SkASSERT(arg0.type().matches(arg1.type())); + if (arg2.type().componentType().isFloat()) { + SkASSERT(arg0.type().componentType().matches(arg2.type().componentType())); + if (!this->pushVectorizedExpression(arg2, arg0.type())) { + return unsupported(); + } + if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { + return unsupported(); + } + return this->ternaryOp(arg0.type(), kMixOps); + } + if (arg2.type().componentType().isBoolean()) { + if (!this->pushExpression(arg2)) { + return unsupported(); + } + if (!this->pushExpression(arg0) || !this->pushExpression(arg1)) { + return unsupported(); + } + // The `mix_int` op isn't doing a lerp; it uses the third argument to select values + // from the first and second arguments. It's safe for use with any type in arguments + // 0 and 1. + fBuilder.ternary_op(BuilderOp::mix_n_ints, arg0.type().slotCount()); + return true; + } + return unsupported(); + + case IntrinsicKind::k_refract_IntrinsicKind: { + // We always calculate refraction using vec4s, so we pad out unused N/I slots with zero. + int padding = 4 - arg0.type().slotCount(); + if (!this->pushExpression(arg0)) { + return unsupported(); + } + fBuilder.push_zeros(padding); + + if (!this->pushExpression(arg1)) { + return unsupported(); + } + fBuilder.push_zeros(padding); + + // eta is always a scalar and doesn't need padding. + if (!this->pushExpression(arg2)) { + return unsupported(); + } + fBuilder.refract_floats(); + + // The result vector was returned as a vec4, so discard the extra columns. + fBuilder.discard_stack(padding); + return true; + } + case IntrinsicKind::k_smoothstep_IntrinsicKind: + SkASSERT(arg0.type().componentType().isFloat()); + SkASSERT(arg1.type().matches(arg0.type())); + SkASSERT(arg2.type().componentType().isFloat()); + + if (!this->pushVectorizedExpression(arg0, arg2.type()) || + !this->pushVectorizedExpression(arg1, arg2.type()) || + !this->pushExpression(arg2)) { + return unsupported(); + } + fBuilder.ternary_op(BuilderOp::smoothstep_n_floats, arg2.type().slotCount()); + return true; + + default: + break; + } + return unsupported(); +} + +bool Generator::pushLiteral(const Literal& l) { + switch (l.type().numberKind()) { + case Type::NumberKind::kFloat: + fBuilder.push_literal_f(l.floatValue()); + return true; + + case Type::NumberKind::kSigned: + fBuilder.push_literal_i(l.intValue()); + return true; + + case Type::NumberKind::kUnsigned: + fBuilder.push_literal_u(l.intValue()); + return true; + + case Type::NumberKind::kBoolean: + fBuilder.push_literal_i(l.boolValue() ? ~0 : 0); + return true; + + default: + SkUNREACHABLE; + } +} + +bool Generator::pushPostfixExpression(const PostfixExpression& p, bool usesResult) { + // If the result is ignored... + if (!usesResult) { + // ... just emit a prefix expression instead. + return this->pushPrefixExpression(p.getOperator(), *p.operand()); + } + // Get the operand as an lvalue, and push it onto the stack as-is. + std::unique_ptr<LValue> lvalue = this->makeLValue(*p.operand()); + if (!lvalue || !this->push(*lvalue)) { + return unsupported(); + } + + // Push a scratch copy of the operand. + fBuilder.push_clone(p.type().slotCount()); + + // Increment or decrement the scratch copy by one. + Literal oneLiteral{Position{}, 1.0, &p.type().componentType()}; + if (!this->pushVectorizedExpression(oneLiteral, p.type())) { + return unsupported(); + } + + switch (p.getOperator().kind()) { + case OperatorKind::PLUSPLUS: + if (!this->binaryOp(p.type(), kAddOps)) { + return unsupported(); + } + break; + + case OperatorKind::MINUSMINUS: + if (!this->binaryOp(p.type(), kSubtractOps)) { + return unsupported(); + } + break; + + default: + SkUNREACHABLE; + } + + // Write the new value back to the operand. + if (!this->store(*lvalue)) { + return unsupported(); + } + + // Discard the scratch copy, leaving only the original value as-is. + this->discardExpression(p.type().slotCount()); + return true; +} + +bool Generator::pushPrefixExpression(const PrefixExpression& p) { + return this->pushPrefixExpression(p.getOperator(), *p.operand()); +} + +bool Generator::pushPrefixExpression(Operator op, const Expression& expr) { + switch (op.kind()) { + case OperatorKind::BITWISENOT: + case OperatorKind::LOGICALNOT: + // Handle operators ! and ~. + if (!this->pushExpression(expr)) { + return unsupported(); + } + fBuilder.unary_op(BuilderOp::bitwise_not_int, expr.type().slotCount()); + return true; + + case OperatorKind::MINUS: + // Handle negation as a componentwise `0 - expr`. + fBuilder.push_zeros(expr.type().slotCount()); + if (!this->pushExpression(expr)) { + return unsupported(); + } + return this->binaryOp(expr.type(), kSubtractOps); + + case OperatorKind::PLUSPLUS: { + // Rewrite as `expr += 1`. + Literal oneLiteral{Position{}, 1.0, &expr.type().componentType()}; + return this->pushBinaryExpression(expr, OperatorKind::PLUSEQ, oneLiteral); + } + case OperatorKind::MINUSMINUS: { + // Rewrite as `expr -= 1`. + Literal oneLiteral{Position{}, 1.0, &expr.type().componentType()}; + return this->pushBinaryExpression(expr, OperatorKind::MINUSEQ, oneLiteral); + } + default: + break; + } + + return unsupported(); +} + +bool Generator::pushSwizzle(const Swizzle& s) { + SkASSERT(!s.components().empty() && s.components().size() <= 4); + + // If this is a simple subset of a variable's slots... + bool isSimpleSubset = is_sliceable_swizzle(s.components()); + if (isSimpleSubset && s.base()->is<VariableReference>()) { + // ... we can just push part of the variable directly onto the stack, rather than pushing + // the whole expression and then immediately cutting it down. (Either way works, but this + // saves a step.) + return this->pushVariableReferencePartial( + s.base()->as<VariableReference>(), + SlotRange{/*index=*/s.components()[0], /*count=*/s.components().size()}); + } + // Push the base expression. + if (!this->pushExpression(*s.base())) { + return false; + } + // An identity swizzle doesn't rearrange the data; it just (potentially) discards tail elements. + if (isSimpleSubset && s.components()[0] == 0) { + int discardedElements = s.base()->type().slotCount() - s.components().size(); + SkASSERT(discardedElements >= 0); + fBuilder.discard_stack(discardedElements); + return true; + } + // Perform the swizzle. + fBuilder.swizzle(s.base()->type().slotCount(), s.components()); + return true; +} + +bool Generator::pushTernaryExpression(const TernaryExpression& t) { + return this->pushTernaryExpression(*t.test(), *t.ifTrue(), *t.ifFalse()); +} + +bool Generator::pushDynamicallyUniformTernaryExpression(const Expression& test, + const Expression& ifTrue, + const Expression& ifFalse) { + SkASSERT(Analysis::IsDynamicallyUniformExpression(test)); + + int falseLabelID = fBuilder.nextLabelID(); + int exitLabelID = fBuilder.nextLabelID(); + + // First, push the test-expression into a separate stack. + AutoStack testStack(this); + testStack.enter(); + if (!this->pushExpression(test)) { + return unsupported(); + } + + // Branch to the true- or false-expression based on the test-expression. We can skip the + // non-true path entirely since the test is known to be uniform. + fBuilder.branch_if_no_active_lanes_on_stack_top_equal(~0, falseLabelID); + testStack.exit(); + + if (!this->pushExpression(ifTrue)) { + return unsupported(); + } + + fBuilder.jump(exitLabelID); + + // The builder doesn't understand control flow, and assumes that every push moves the stack-top + // forwards. We need to manually balance out the `pushExpression` from the if-true path by + // moving the stack position backwards, so that the if-false path pushes its expression into the + // same as the if-true result. + this->discardExpression(/*slots=*/ifTrue.type().slotCount()); + + fBuilder.label(falseLabelID); + + if (!this->pushExpression(ifFalse)) { + return unsupported(); + } + + fBuilder.label(exitLabelID); + + // Jettison the text-expression from the separate stack. + testStack.enter(); + this->discardExpression(/*slots=*/1); + testStack.exit(); + return true; +} + +bool Generator::pushTernaryExpression(const Expression& test, + const Expression& ifTrue, + const Expression& ifFalse) { + // If the test-expression is dynamically-uniform, we can skip over the non-true expressions + // entirely, and not need to involve the condition mask. + if (Analysis::IsDynamicallyUniformExpression(test)) { + return this->pushDynamicallyUniformTernaryExpression(test, ifTrue, ifFalse); + } + + // Analyze the ternary to see which corners we can safely cut. + bool ifFalseHasSideEffects = Analysis::HasSideEffects(ifFalse); + bool ifTrueHasSideEffects = Analysis::HasSideEffects(ifTrue); + bool ifTrueIsTrivial = Analysis::IsTrivialExpression(ifTrue); + int cleanupLabelID = fBuilder.nextLabelID(); + + // If the true- and false-expressions both lack side effects, we evaluate both of them safely + // without masking off their effects. In that case, we can emit both sides and use boolean mix + // to select the correct result without using the condition mask at all. + if (!ifFalseHasSideEffects && !ifTrueHasSideEffects && ifTrueIsTrivial) { + // Push all of the arguments to mix. + if (!this->pushVectorizedExpression(test, ifTrue.type())) { + return unsupported(); + } + if (!this->pushExpression(ifFalse)) { + return unsupported(); + } + if (!this->pushExpression(ifTrue)) { + return unsupported(); + } + // Use boolean mix to select the true- or false-expression via the test-expression. + fBuilder.ternary_op(BuilderOp::mix_n_ints, ifTrue.type().slotCount()); + return true; + } + + // First, push the current condition-mask and the test-expression into a separate stack. + fBuilder.enableExecutionMaskWrites(); + AutoStack testStack(this); + testStack.enter(); + fBuilder.push_condition_mask(); + if (!this->pushExpression(test)) { + return unsupported(); + } + testStack.exit(); + + // We can take some shortcuts with condition-mask handling if the false-expression is entirely + // side-effect free. (We can evaluate it without masking off its effects.) We always handle the + // condition mask properly for the test-expression and true-expression properly. + if (!ifFalseHasSideEffects) { + // Push the false-expression onto the primary stack. + if (!this->pushExpression(ifFalse)) { + return unsupported(); + } + + // Next, merge the condition mask (on the separate stack) with the test expression. + testStack.enter(); + fBuilder.merge_condition_mask(); + testStack.exit(); + + // If no lanes are active, we can skip the true-expression entirely. This isn't super likely + // to happen, so it's probably only a win for non-trivial true-expressions. + if (!ifTrueIsTrivial) { + fBuilder.branch_if_no_lanes_active(cleanupLabelID); + } + + // Push the true-expression onto the primary stack, immediately after the false-expression. + if (!this->pushExpression(ifTrue)) { + return unsupported(); + } + + // Use a select to conditionally mask-merge the true-expression and false-expression lanes. + fBuilder.select(/*slots=*/ifTrue.type().slotCount()); + fBuilder.label(cleanupLabelID); + } else { + // Merge the condition mask (on the separate stack) with the test expression. + testStack.enter(); + fBuilder.merge_condition_mask(); + testStack.exit(); + + // Push the true-expression onto the primary stack. + if (!this->pushExpression(ifTrue)) { + return unsupported(); + } + + // Switch back to the test-expression stack temporarily, and negate the test condition. + testStack.enter(); + fBuilder.unary_op(BuilderOp::bitwise_not_int, /*slots=*/1); + fBuilder.merge_condition_mask(); + testStack.exit(); + + // Push the false-expression onto the primary stack, immediately after the true-expression. + if (!this->pushExpression(ifFalse)) { + return unsupported(); + } + + // Use a select to conditionally mask-merge the true-expression and false-expression lanes; + // the mask is already set up for this. + fBuilder.select(/*slots=*/ifTrue.type().slotCount()); + } + + // Restore the condition-mask to its original state and jettison the test-expression. + testStack.enter(); + this->discardExpression(/*slots=*/1); + fBuilder.pop_condition_mask(); + testStack.exit(); + + fBuilder.disableExecutionMaskWrites(); + return true; +} + +bool Generator::pushVariableReference(const VariableReference& v) { + return this->pushVariableReferencePartial(v, SlotRange{0, (int)v.type().slotCount()}); +} + +bool Generator::pushVariableReferencePartial(const VariableReference& v, SlotRange subset) { + const Variable& var = *v.variable(); + SlotRange r; + if (IsUniform(var)) { + r = this->getUniformSlots(var); + SkASSERT(r.count == (int)var.type().slotCount()); + r.index += subset.index; + r.count = subset.count; + fBuilder.push_uniform(r); + } else { + r = this->getVariableSlots(var); + SkASSERT(r.count == (int)var.type().slotCount()); + r.index += subset.index; + r.count = subset.count; + fBuilder.push_slots(r); + } + return true; +} + +bool Generator::writeProgram(const FunctionDefinition& function) { + fCurrentFunction = &function; + + if (fDebugTrace) { + // Copy the program source into the debug info so that it will be written in the trace file. + fDebugTrace->setSource(*fProgram.fSource); + } + // Assign slots to the parameters of main; copy src and dst into those slots as appropriate. + for (const SkSL::Variable* param : function.declaration().parameters()) { + switch (param->modifiers().fLayout.fBuiltin) { + case SK_MAIN_COORDS_BUILTIN: { + // Coordinates are passed via RG. + SlotRange fragCoord = this->getVariableSlots(*param); + SkASSERT(fragCoord.count == 2); + fBuilder.store_src_rg(fragCoord); + break; + } + case SK_INPUT_COLOR_BUILTIN: { + // Input colors are passed via RGBA. + SlotRange srcColor = this->getVariableSlots(*param); + SkASSERT(srcColor.count == 4); + fBuilder.store_src(srcColor); + break; + } + case SK_DEST_COLOR_BUILTIN: { + // Dest colors are passed via dRGBA. + SlotRange destColor = this->getVariableSlots(*param); + SkASSERT(destColor.count == 4); + fBuilder.store_dst(destColor); + break; + } + default: { + SkDEBUGFAIL("Invalid parameter to main()"); + return unsupported(); + } + } + } + + // Initialize the program. + fBuilder.init_lane_masks(); + + // Emit global variables. + if (!this->writeGlobals()) { + return unsupported(); + } + + // Invoke main(). + if (this->needsReturnMask()) { + fBuilder.enableExecutionMaskWrites(); + } + + std::optional<SlotRange> mainResult = this->writeFunction(function, function); + if (!mainResult.has_value()) { + return unsupported(); + } + + if (this->needsReturnMask()) { + fBuilder.disableExecutionMaskWrites(); + } + + // Move the result of main() from slots into RGBA. Allow dRGBA to remain in a trashed state. + SkASSERT(mainResult->count == 4); + if (this->needsFunctionResultSlots()) { + fBuilder.load_src(*mainResult); + } else { + fBuilder.pop_src_rgba(); + } + return true; +} + +std::unique_ptr<RP::Program> Generator::finish() { + return fBuilder.finish(fProgramSlots.slotCount(), fUniformSlots.slotCount(), fDebugTrace); +} + +} // namespace RP + +std::unique_ptr<RP::Program> MakeRasterPipelineProgram(const SkSL::Program& program, + const FunctionDefinition& function, + SkRPDebugTrace* debugTrace) { + RP::Generator generator(program, debugTrace); + if (!generator.writeProgram(function)) { + return nullptr; + } + return generator.finish(); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h new file mode 100644 index 0000000000..c49a8d571d --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLRasterPipelineCodeGenerator.h @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_RASTERPIPELINECODEGENERATOR +#define SKSL_RASTERPIPELINECODEGENERATOR + +#include "include/core/SkTypes.h" +#include <memory> + +namespace SkSL { + +class FunctionDefinition; +struct Program; +class SkRPDebugTrace; +namespace RP { class Program; } + +// Convert 'function' to Raster Pipeline stages, for use by blends, shaders, and color filters. +// The arguments to the function are passed in registers: +// -- coordinates in src.rg for shaders +// -- color in src.rgba for color filters +// -- src/dst in src.rgba and dst.rgba for blenders +std::unique_ptr<RP::Program> MakeRasterPipelineProgram(const Program& program, + const FunctionDefinition& function, + SkRPDebugTrace* debugTrace = nullptr); + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp new file mode 100644 index 0000000000..f355a64a83 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp @@ -0,0 +1,4365 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/codegen/SkSLSPIRVCodeGenerator.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkOpts_spi.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLSymbol.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/DSLCore.h" +#include "include/sksl/DSLExpression.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/DSLVar.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/GLSL.std.450.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorArrayCast.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorCompoundCast.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" +#include "src/sksl/ir/SkSLConstructorScalarCast.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLField.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/transform/SkSLTransform.h" +#include "src/utils/SkBitSet.h" + +#include <cstring> +#include <set> +#include <string> +#include <utility> + +#define kLast_Capability SpvCapabilityMultiViewport + +constexpr int DEVICE_FRAGCOORDS_BUILTIN = -1000; +constexpr int DEVICE_CLOCKWISE_BUILTIN = -1001; + +namespace SkSL { + +// Equality and hash operators for Instructions. +bool SPIRVCodeGenerator::Instruction::operator==(const SPIRVCodeGenerator::Instruction& that) const { + return fOp == that.fOp && + fResultKind == that.fResultKind && + fWords == that.fWords; +} + +struct SPIRVCodeGenerator::Instruction::Hash { + uint32_t operator()(const SPIRVCodeGenerator::Instruction& key) const { + uint32_t hash = key.fResultKind; + hash = SkOpts::hash_fn(&key.fOp, sizeof(key.fOp), hash); + hash = SkOpts::hash_fn(key.fWords.data(), key.fWords.size() * sizeof(int32_t), hash); + return hash; + } +}; + +// This class is used to pass values and result placeholder slots to writeInstruction. +struct SPIRVCodeGenerator::Word { + enum Kind { + kNone, // intended for use as a sentinel, not part of any Instruction + kSpvId, + kNumber, + kDefaultPrecisionResult, + kRelaxedPrecisionResult, + kUniqueResult, + kKeyedResult, + }; + + Word(SpvId id) : fValue(id), fKind(Kind::kSpvId) {} + Word(int32_t val, Kind kind) : fValue(val), fKind(kind) {} + + static Word Number(int32_t val) { + return Word{val, Kind::kNumber}; + } + + static Word Result(const Type& type) { + return (type.hasPrecision() && !type.highPrecision()) ? RelaxedResult() : Result(); + } + + static Word RelaxedResult() { + return Word{(int32_t)NA, kRelaxedPrecisionResult}; + } + + static Word UniqueResult() { + return Word{(int32_t)NA, kUniqueResult}; + } + + static Word Result() { + return Word{(int32_t)NA, kDefaultPrecisionResult}; + } + + // Unlike a Result (where the result ID is always deduplicated to its first instruction) or a + // UniqueResult (which always produces a new instruction), a KeyedResult allows an instruction + // to be deduplicated among those that share the same `key`. + static Word KeyedResult(int32_t key) { return Word{key, Kind::kKeyedResult}; } + + bool isResult() const { return fKind >= Kind::kDefaultPrecisionResult; } + + int32_t fValue; + Kind fKind; +}; + +// Skia's magic number is 31 and goes in the top 16 bits. We can use the lower bits to version the +// sksl generator if we want. +// https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/spir-v.xml#L84 +static const int32_t SKSL_MAGIC = 0x001F0000; + +SPIRVCodeGenerator::Intrinsic SPIRVCodeGenerator::getIntrinsic(IntrinsicKind ik) const { + +#define ALL_GLSL(x) Intrinsic{kGLSL_STD_450_IntrinsicOpcodeKind, GLSLstd450 ## x, \ + GLSLstd450 ## x, GLSLstd450 ## x, GLSLstd450 ## x} +#define BY_TYPE_GLSL(ifFloat, ifInt, ifUInt) Intrinsic{kGLSL_STD_450_IntrinsicOpcodeKind, \ + GLSLstd450 ## ifFloat, \ + GLSLstd450 ## ifInt, \ + GLSLstd450 ## ifUInt, \ + SpvOpUndef} +#define ALL_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \ + SpvOp ## x, SpvOp ## x, SpvOp ## x, SpvOp ## x} +#define BOOL_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \ + SpvOpUndef, SpvOpUndef, SpvOpUndef, SpvOp ## x} +#define FLOAT_SPIRV(x) Intrinsic{kSPIRV_IntrinsicOpcodeKind, \ + SpvOp ## x, SpvOpUndef, SpvOpUndef, SpvOpUndef} +#define SPECIAL(x) Intrinsic{kSpecial_IntrinsicOpcodeKind, k ## x ## _SpecialIntrinsic, \ + k ## x ## _SpecialIntrinsic, k ## x ## _SpecialIntrinsic, \ + k ## x ## _SpecialIntrinsic} + + switch (ik) { + case k_round_IntrinsicKind: return ALL_GLSL(Round); + case k_roundEven_IntrinsicKind: return ALL_GLSL(RoundEven); + case k_trunc_IntrinsicKind: return ALL_GLSL(Trunc); + case k_abs_IntrinsicKind: return BY_TYPE_GLSL(FAbs, SAbs, SAbs); + case k_sign_IntrinsicKind: return BY_TYPE_GLSL(FSign, SSign, SSign); + case k_floor_IntrinsicKind: return ALL_GLSL(Floor); + case k_ceil_IntrinsicKind: return ALL_GLSL(Ceil); + case k_fract_IntrinsicKind: return ALL_GLSL(Fract); + case k_radians_IntrinsicKind: return ALL_GLSL(Radians); + case k_degrees_IntrinsicKind: return ALL_GLSL(Degrees); + case k_sin_IntrinsicKind: return ALL_GLSL(Sin); + case k_cos_IntrinsicKind: return ALL_GLSL(Cos); + case k_tan_IntrinsicKind: return ALL_GLSL(Tan); + case k_asin_IntrinsicKind: return ALL_GLSL(Asin); + case k_acos_IntrinsicKind: return ALL_GLSL(Acos); + case k_atan_IntrinsicKind: return SPECIAL(Atan); + case k_sinh_IntrinsicKind: return ALL_GLSL(Sinh); + case k_cosh_IntrinsicKind: return ALL_GLSL(Cosh); + case k_tanh_IntrinsicKind: return ALL_GLSL(Tanh); + case k_asinh_IntrinsicKind: return ALL_GLSL(Asinh); + case k_acosh_IntrinsicKind: return ALL_GLSL(Acosh); + case k_atanh_IntrinsicKind: return ALL_GLSL(Atanh); + case k_pow_IntrinsicKind: return ALL_GLSL(Pow); + case k_exp_IntrinsicKind: return ALL_GLSL(Exp); + case k_log_IntrinsicKind: return ALL_GLSL(Log); + case k_exp2_IntrinsicKind: return ALL_GLSL(Exp2); + case k_log2_IntrinsicKind: return ALL_GLSL(Log2); + case k_sqrt_IntrinsicKind: return ALL_GLSL(Sqrt); + case k_inverse_IntrinsicKind: return ALL_GLSL(MatrixInverse); + case k_outerProduct_IntrinsicKind: return ALL_SPIRV(OuterProduct); + case k_transpose_IntrinsicKind: return ALL_SPIRV(Transpose); + case k_isinf_IntrinsicKind: return ALL_SPIRV(IsInf); + case k_isnan_IntrinsicKind: return ALL_SPIRV(IsNan); + case k_inversesqrt_IntrinsicKind: return ALL_GLSL(InverseSqrt); + case k_determinant_IntrinsicKind: return ALL_GLSL(Determinant); + case k_matrixCompMult_IntrinsicKind: return SPECIAL(MatrixCompMult); + case k_matrixInverse_IntrinsicKind: return ALL_GLSL(MatrixInverse); + case k_mod_IntrinsicKind: return SPECIAL(Mod); + case k_modf_IntrinsicKind: return ALL_GLSL(Modf); + case k_min_IntrinsicKind: return SPECIAL(Min); + case k_max_IntrinsicKind: return SPECIAL(Max); + case k_clamp_IntrinsicKind: return SPECIAL(Clamp); + case k_saturate_IntrinsicKind: return SPECIAL(Saturate); + case k_dot_IntrinsicKind: return FLOAT_SPIRV(Dot); + case k_mix_IntrinsicKind: return SPECIAL(Mix); + case k_step_IntrinsicKind: return SPECIAL(Step); + case k_smoothstep_IntrinsicKind: return SPECIAL(SmoothStep); + case k_fma_IntrinsicKind: return ALL_GLSL(Fma); + case k_frexp_IntrinsicKind: return ALL_GLSL(Frexp); + case k_ldexp_IntrinsicKind: return ALL_GLSL(Ldexp); + +#define PACK(type) case k_pack##type##_IntrinsicKind: return ALL_GLSL(Pack##type); \ + case k_unpack##type##_IntrinsicKind: return ALL_GLSL(Unpack##type) + PACK(Snorm4x8); + PACK(Unorm4x8); + PACK(Snorm2x16); + PACK(Unorm2x16); + PACK(Half2x16); + PACK(Double2x32); +#undef PACK + + case k_length_IntrinsicKind: return ALL_GLSL(Length); + case k_distance_IntrinsicKind: return ALL_GLSL(Distance); + case k_cross_IntrinsicKind: return ALL_GLSL(Cross); + case k_normalize_IntrinsicKind: return ALL_GLSL(Normalize); + case k_faceforward_IntrinsicKind: return ALL_GLSL(FaceForward); + case k_reflect_IntrinsicKind: return ALL_GLSL(Reflect); + case k_refract_IntrinsicKind: return ALL_GLSL(Refract); + case k_bitCount_IntrinsicKind: return ALL_SPIRV(BitCount); + case k_findLSB_IntrinsicKind: return ALL_GLSL(FindILsb); + case k_findMSB_IntrinsicKind: return BY_TYPE_GLSL(FindSMsb, FindSMsb, FindUMsb); + case k_dFdx_IntrinsicKind: return FLOAT_SPIRV(DPdx); + case k_dFdy_IntrinsicKind: return SPECIAL(DFdy); + case k_fwidth_IntrinsicKind: return FLOAT_SPIRV(Fwidth); + case k_makeSampler2D_IntrinsicKind: return SPECIAL(SampledImage); + + case k_sample_IntrinsicKind: return SPECIAL(Texture); + case k_sampleGrad_IntrinsicKind: return SPECIAL(TextureGrad); + case k_sampleLod_IntrinsicKind: return SPECIAL(TextureLod); + case k_subpassLoad_IntrinsicKind: return SPECIAL(SubpassLoad); + + case k_floatBitsToInt_IntrinsicKind: return ALL_SPIRV(Bitcast); + case k_floatBitsToUint_IntrinsicKind: return ALL_SPIRV(Bitcast); + case k_intBitsToFloat_IntrinsicKind: return ALL_SPIRV(Bitcast); + case k_uintBitsToFloat_IntrinsicKind: return ALL_SPIRV(Bitcast); + + case k_any_IntrinsicKind: return BOOL_SPIRV(Any); + case k_all_IntrinsicKind: return BOOL_SPIRV(All); + case k_not_IntrinsicKind: return BOOL_SPIRV(LogicalNot); + + case k_equal_IntrinsicKind: + return Intrinsic{kSPIRV_IntrinsicOpcodeKind, + SpvOpFOrdEqual, + SpvOpIEqual, + SpvOpIEqual, + SpvOpLogicalEqual}; + case k_notEqual_IntrinsicKind: + return Intrinsic{kSPIRV_IntrinsicOpcodeKind, + SpvOpFUnordNotEqual, + SpvOpINotEqual, + SpvOpINotEqual, + SpvOpLogicalNotEqual}; + case k_lessThan_IntrinsicKind: + return Intrinsic{kSPIRV_IntrinsicOpcodeKind, + SpvOpFOrdLessThan, + SpvOpSLessThan, + SpvOpULessThan, + SpvOpUndef}; + case k_lessThanEqual_IntrinsicKind: + return Intrinsic{kSPIRV_IntrinsicOpcodeKind, + SpvOpFOrdLessThanEqual, + SpvOpSLessThanEqual, + SpvOpULessThanEqual, + SpvOpUndef}; + case k_greaterThan_IntrinsicKind: + return Intrinsic{kSPIRV_IntrinsicOpcodeKind, + SpvOpFOrdGreaterThan, + SpvOpSGreaterThan, + SpvOpUGreaterThan, + SpvOpUndef}; + case k_greaterThanEqual_IntrinsicKind: + return Intrinsic{kSPIRV_IntrinsicOpcodeKind, + SpvOpFOrdGreaterThanEqual, + SpvOpSGreaterThanEqual, + SpvOpUGreaterThanEqual, + SpvOpUndef}; + default: + return Intrinsic{kInvalid_IntrinsicOpcodeKind, 0, 0, 0, 0}; + } +} + +void SPIRVCodeGenerator::writeWord(int32_t word, OutputStream& out) { + out.write((const char*) &word, sizeof(word)); +} + +static bool is_float(const Type& type) { + return (type.isScalar() || type.isVector() || type.isMatrix()) && + type.componentType().isFloat(); +} + +static bool is_signed(const Type& type) { + return (type.isScalar() || type.isVector()) && type.componentType().isSigned(); +} + +static bool is_unsigned(const Type& type) { + return (type.isScalar() || type.isVector()) && type.componentType().isUnsigned(); +} + +static bool is_bool(const Type& type) { + return (type.isScalar() || type.isVector()) && type.componentType().isBoolean(); +} + +template <typename T> +static T pick_by_type(const Type& type, T ifFloat, T ifInt, T ifUInt, T ifBool) { + if (is_float(type)) { + return ifFloat; + } + if (is_signed(type)) { + return ifInt; + } + if (is_unsigned(type)) { + return ifUInt; + } + if (is_bool(type)) { + return ifBool; + } + SkDEBUGFAIL("unrecognized type"); + return ifFloat; +} + +static bool is_out(const Modifiers& m) { + return (m.fFlags & Modifiers::kOut_Flag) != 0; +} + +static bool is_in(const Modifiers& m) { + switch (m.fFlags & (Modifiers::kOut_Flag | Modifiers::kIn_Flag)) { + case Modifiers::kOut_Flag: // out + return false; + + case 0: // implicit in + case Modifiers::kIn_Flag: // explicit in + case Modifiers::kOut_Flag | Modifiers::kIn_Flag: // inout + return true; + + default: SkUNREACHABLE; + } +} + +static bool is_control_flow_op(SpvOp_ op) { + switch (op) { + case SpvOpReturn: + case SpvOpReturnValue: + case SpvOpKill: + case SpvOpSwitch: + case SpvOpBranch: + case SpvOpBranchConditional: + return true; + default: + return false; + } +} + +static bool is_globally_reachable_op(SpvOp_ op) { + switch (op) { + case SpvOpConstant: + case SpvOpConstantTrue: + case SpvOpConstantFalse: + case SpvOpConstantComposite: + case SpvOpTypeVoid: + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypeBool: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + case SpvOpTypeArray: + case SpvOpTypePointer: + case SpvOpTypeFunction: + case SpvOpTypeRuntimeArray: + case SpvOpTypeStruct: + case SpvOpTypeImage: + case SpvOpTypeSampledImage: + case SpvOpTypeSampler: + case SpvOpVariable: + case SpvOpFunction: + case SpvOpFunctionParameter: + case SpvOpFunctionEnd: + case SpvOpExecutionMode: + case SpvOpMemoryModel: + case SpvOpCapability: + case SpvOpExtInstImport: + case SpvOpEntryPoint: + case SpvOpSource: + case SpvOpSourceExtension: + case SpvOpName: + case SpvOpMemberName: + case SpvOpDecorate: + case SpvOpMemberDecorate: + return true; + default: + return false; + } +} + +void SPIRVCodeGenerator::writeOpCode(SpvOp_ opCode, int length, OutputStream& out) { + SkASSERT(opCode != SpvOpLoad || &out != &fConstantBuffer); + SkASSERT(opCode != SpvOpUndef); + bool foundDeadCode = false; + if (is_control_flow_op(opCode)) { + // This instruction causes us to leave the current block. + foundDeadCode = (fCurrentBlock == 0); + fCurrentBlock = 0; + } else if (!is_globally_reachable_op(opCode)) { + foundDeadCode = (fCurrentBlock == 0); + } + + if (foundDeadCode) { + // We just encountered dead code--an instruction that don't have an associated block. + // Synthesize a label if this happens; this is necessary to satisfy the validator. + this->writeLabel(this->nextId(nullptr), kBranchlessBlock, out); + } + + this->writeWord((length << 16) | opCode, out); +} + +void SPIRVCodeGenerator::writeLabel(SpvId label, StraightLineLabelType, OutputStream& out) { + // The straight-line label type is not important; in any case, no caches are invalidated. + SkASSERT(!fCurrentBlock); + fCurrentBlock = label; + this->writeInstruction(SpvOpLabel, label, out); +} + +void SPIRVCodeGenerator::writeLabel(SpvId label, BranchingLabelType type, + ConditionalOpCounts ops, OutputStream& out) { + switch (type) { + case kBranchIsBelow: + case kBranchesOnBothSides: + // With a backward or bidirectional branch, we haven't seen the code between the label + // and the branch yet, so any stored value is potentially suspect. Without scanning + // ahead to check, the only safe option is to ditch the store cache entirely. + fStoreCache.reset(); + [[fallthrough]]; + + case kBranchIsAbove: + // With a forward branch, we can rely on stores that we had cached at the start of the + // statement/expression, if they haven't been touched yet. Anything newer than that is + // pruned. + this->pruneConditionalOps(ops); + break; + } + + // Emit the label. + this->writeLabel(label, kBranchlessBlock, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, OutputStream& out) { + this->writeOpCode(opCode, 1, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out) { + this->writeOpCode(opCode, 2, out); + this->writeWord(word1, out); +} + +void SPIRVCodeGenerator::writeString(std::string_view s, OutputStream& out) { + out.write(s.data(), s.length()); + switch (s.length() % 4) { + case 1: + out.write8(0); + [[fallthrough]]; + case 2: + out.write8(0); + [[fallthrough]]; + case 3: + out.write8(0); + break; + default: + this->writeWord(0, out); + break; + } +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, std::string_view string, + OutputStream& out) { + this->writeOpCode(opCode, 1 + (string.length() + 4) / 4, out); + this->writeString(string, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, std::string_view string, + OutputStream& out) { + this->writeOpCode(opCode, 2 + (string.length() + 4) / 4, out); + this->writeWord(word1, out); + this->writeString(string, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + std::string_view string, OutputStream& out) { + this->writeOpCode(opCode, 3 + (string.length() + 4) / 4, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeString(string, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + OutputStream& out) { + this->writeOpCode(opCode, 3, out); + this->writeWord(word1, out); + this->writeWord(word2, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, OutputStream& out) { + this->writeOpCode(opCode, 4, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, OutputStream& out) { + this->writeOpCode(opCode, 5, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + OutputStream& out) { + this->writeOpCode(opCode, 6, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + int32_t word6, OutputStream& out) { + this->writeOpCode(opCode, 7, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); + this->writeWord(word6, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + int32_t word6, int32_t word7, OutputStream& out) { + this->writeOpCode(opCode, 8, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); + this->writeWord(word6, out); + this->writeWord(word7, out); +} + +void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, + int32_t word3, int32_t word4, int32_t word5, + int32_t word6, int32_t word7, int32_t word8, + OutputStream& out) { + this->writeOpCode(opCode, 9, out); + this->writeWord(word1, out); + this->writeWord(word2, out); + this->writeWord(word3, out); + this->writeWord(word4, out); + this->writeWord(word5, out); + this->writeWord(word6, out); + this->writeWord(word7, out); + this->writeWord(word8, out); +} + +SPIRVCodeGenerator::Instruction SPIRVCodeGenerator::BuildInstructionKey( + SpvOp_ opCode, const SkTArray<Word>& words) { + // Assemble a cache key for this instruction. + Instruction key; + key.fOp = opCode; + key.fWords.resize(words.size()); + key.fResultKind = Word::Kind::kNone; + + for (int index = 0; index < words.size(); ++index) { + const Word& word = words[index]; + key.fWords[index] = word.fValue; + if (word.isResult()) { + SkASSERT(key.fResultKind == Word::Kind::kNone); + key.fResultKind = word.fKind; + } + } + + return key; +} + +SpvId SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, + const SkTArray<Word>& words, + OutputStream& out) { + // writeOpLoad and writeOpStore have dedicated code. + SkASSERT(opCode != SpvOpLoad); + SkASSERT(opCode != SpvOpStore); + + // If this instruction exists in our op cache, return the cached SpvId. + Instruction key = BuildInstructionKey(opCode, words); + if (SpvId* cachedOp = fOpCache.find(key)) { + return *cachedOp; + } + + SpvId result = NA; + Precision precision = Precision::kDefault; + + switch (key.fResultKind) { + case Word::Kind::kUniqueResult: + // The instruction returns a SpvId, but we do not want deduplication. + result = this->nextId(Precision::kDefault); + fSpvIdCache.set(result, key); + break; + + case Word::Kind::kNone: + // The instruction doesn't return a SpvId, but we can still cache and deduplicate it. + fOpCache.set(key, result); + break; + + case Word::Kind::kRelaxedPrecisionResult: + precision = Precision::kRelaxed; + [[fallthrough]]; + + case Word::Kind::kKeyedResult: + [[fallthrough]]; + + case Word::Kind::kDefaultPrecisionResult: + // Consume a new SpvId. + result = this->nextId(precision); + fOpCache.set(key, result); + fSpvIdCache.set(result, key); + + // Globally-reachable ops are not subject to the whims of flow control. + if (!is_globally_reachable_op(opCode)) { + fReachableOps.push_back(result); + } + break; + + default: + SkDEBUGFAIL("unexpected result kind"); + break; + } + + // Write the requested instruction. + this->writeOpCode(opCode, words.size() + 1, out); + for (const Word& word : words) { + if (word.isResult()) { + SkASSERT(result != NA); + this->writeWord(result, out); + } else { + this->writeWord(word.fValue, out); + } + } + + // Return the result. + return result; +} + +SpvId SPIRVCodeGenerator::writeOpLoad(SpvId type, + Precision precision, + SpvId pointer, + OutputStream& out) { + // Look for this pointer in our load-cache. + if (SpvId* cachedOp = fStoreCache.find(pointer)) { + return *cachedOp; + } + + // Write the requested OpLoad instruction. + SpvId result = this->nextId(precision); + this->writeInstruction(SpvOpLoad, type, result, pointer, out); + return result; +} + +void SPIRVCodeGenerator::writeOpStore(SpvStorageClass_ storageClass, + SpvId pointer, + SpvId value, + OutputStream& out) { + // Write the uncached SpvOpStore directly. + this->writeInstruction(SpvOpStore, pointer, value, out); + + if (storageClass == SpvStorageClassFunction) { + // Insert a pointer-to-SpvId mapping into the load cache. A writeOpLoad to this pointer will + // return the cached value as-is. + fStoreCache.set(pointer, value); + fStoreOps.push_back(pointer); + } +} + +SpvId SPIRVCodeGenerator::writeOpConstantTrue(const Type& type) { + return this->writeInstruction(SpvOpConstantTrue, + Words{this->getType(type), Word::Result()}, + fConstantBuffer); +} + +SpvId SPIRVCodeGenerator::writeOpConstantFalse(const Type& type) { + return this->writeInstruction(SpvOpConstantFalse, + Words{this->getType(type), Word::Result()}, + fConstantBuffer); +} + +SpvId SPIRVCodeGenerator::writeOpConstant(const Type& type, int32_t valueBits) { + return this->writeInstruction( + SpvOpConstant, + Words{this->getType(type), Word::Result(), Word::Number(valueBits)}, + fConstantBuffer); +} + +SpvId SPIRVCodeGenerator::writeOpConstantComposite(const Type& type, + const SkTArray<SpvId>& values) { + SkASSERT(values.size() == (type.isStruct() ? (int)type.fields().size() : type.columns())); + + Words words; + words.push_back(this->getType(type)); + words.push_back(Word::Result()); + for (SpvId value : values) { + words.push_back(value); + } + return this->writeInstruction(SpvOpConstantComposite, words, fConstantBuffer); +} + +bool SPIRVCodeGenerator::toConstants(SpvId value, SkTArray<SpvId>* constants) { + Instruction* instr = fSpvIdCache.find(value); + if (!instr) { + return false; + } + switch (instr->fOp) { + case SpvOpConstant: + case SpvOpConstantTrue: + case SpvOpConstantFalse: + constants->push_back(value); + return true; + + case SpvOpConstantComposite: // OpConstantComposite ResultType ResultID Constituents... + // Start at word 2 to skip past ResultType and ResultID. + for (int i = 2; i < instr->fWords.size(); ++i) { + if (!this->toConstants(instr->fWords[i], constants)) { + return false; + } + } + return true; + + default: + return false; + } +} + +bool SPIRVCodeGenerator::toConstants(SkSpan<const SpvId> values, SkTArray<SpvId>* constants) { + for (SpvId value : values) { + if (!this->toConstants(value, constants)) { + return false; + } + } + return true; +} + +SpvId SPIRVCodeGenerator::writeOpCompositeConstruct(const Type& type, + const SkTArray<SpvId>& values, + OutputStream& out) { + // If this is a vector composed entirely of literals, write a constant-composite instead. + if (type.isVector()) { + SkSTArray<4, SpvId> constants; + if (this->toConstants(SkSpan(values), &constants)) { + // Create a vector from literals. + return this->writeOpConstantComposite(type, constants); + } + } + + // If this is a matrix composed entirely of literals, constant-composite them instead. + if (type.isMatrix()) { + SkSTArray<16, SpvId> constants; + if (this->toConstants(SkSpan(values), &constants)) { + // Create each matrix column. + SkASSERT(type.isMatrix()); + const Type& vecType = type.componentType().toCompound(fContext, + /*columns=*/type.rows(), + /*rows=*/1); + SkSTArray<4, SpvId> columnIDs; + for (int index=0; index < type.columns(); ++index) { + SkSTArray<4, SpvId> columnConstants(&constants[index * type.rows()], + type.rows()); + columnIDs.push_back(this->writeOpConstantComposite(vecType, columnConstants)); + } + // Compose the matrix from its columns. + return this->writeOpConstantComposite(type, columnIDs); + } + } + + Words words; + words.push_back(this->getType(type)); + words.push_back(Word::Result(type)); + for (SpvId value : values) { + words.push_back(value); + } + + return this->writeInstruction(SpvOpCompositeConstruct, words, out); +} + +SPIRVCodeGenerator::Instruction* SPIRVCodeGenerator::resultTypeForInstruction( + const Instruction& instr) { + // This list should contain every op that we cache that has a result and result-type. + // (If one is missing, we will not find some optimization opportunities.) + // Generally, the result type of an op is in the 0th word, but I'm not sure if this is + // universally true, so it's configurable on a per-op basis. + int resultTypeWord; + switch (instr.fOp) { + case SpvOpConstant: + case SpvOpConstantTrue: + case SpvOpConstantFalse: + case SpvOpConstantComposite: + case SpvOpCompositeConstruct: + case SpvOpCompositeExtract: + case SpvOpLoad: + resultTypeWord = 0; + break; + + default: + return nullptr; + } + + Instruction* typeInstr = fSpvIdCache.find(instr.fWords[resultTypeWord]); + SkASSERT(typeInstr); + return typeInstr; +} + +int SPIRVCodeGenerator::numComponentsForVecInstruction(const Instruction& instr) { + // If an instruction is in the op cache, its type should be as well. + Instruction* typeInstr = this->resultTypeForInstruction(instr); + SkASSERT(typeInstr); + SkASSERT(typeInstr->fOp == SpvOpTypeVector || typeInstr->fOp == SpvOpTypeFloat || + typeInstr->fOp == SpvOpTypeInt || typeInstr->fOp == SpvOpTypeBool); + + // For vectors, extract their column count. Scalars have one component by definition. + // SpvOpTypeVector ResultID ComponentType NumComponents + return (typeInstr->fOp == SpvOpTypeVector) ? typeInstr->fWords[2] + : 1; +} + +SpvId SPIRVCodeGenerator::toComponent(SpvId id, int component) { + Instruction* instr = fSpvIdCache.find(id); + if (!instr) { + return NA; + } + if (instr->fOp == SpvOpConstantComposite) { + // SpvOpConstantComposite ResultType ResultID [components...] + // Add 2 to the component index to skip past ResultType and ResultID. + return instr->fWords[2 + component]; + } + if (instr->fOp == SpvOpCompositeConstruct) { + // SpvOpCompositeConstruct ResultType ResultID [components...] + // Vectors have special rules; check to see if we are composing a vector. + Instruction* composedType = fSpvIdCache.find(instr->fWords[0]); + SkASSERT(composedType); + + // When composing a non-vector, each instruction word maps 1:1 to the component index. + // We can just extract out the associated component directly. + if (composedType->fOp != SpvOpTypeVector) { + return instr->fWords[2 + component]; + } + + // When composing a vector, components can be either scalars or vectors. + // This means we need to check the op type on each component. (+2 to skip ResultType/Result) + for (int index = 2; index < instr->fWords.size(); ++index) { + int32_t currentWord = instr->fWords[index]; + + // Retrieve the sub-instruction pointed to by OpCompositeConstruct. + Instruction* subinstr = fSpvIdCache.find(currentWord); + if (!subinstr) { + return NA; + } + // If this subinstruction contains the component we're looking for... + int numComponents = this->numComponentsForVecInstruction(*subinstr); + if (component < numComponents) { + if (numComponents == 1) { + // ... it's a scalar. Return it. + SkASSERT(component == 0); + return currentWord; + } else { + // ... it's a vector. Recurse into it. + return this->toComponent(currentWord, component); + } + } + // This sub-instruction doesn't contain our component. Keep walking forward. + component -= numComponents; + } + SkDEBUGFAIL("component index goes past the end of this composite value"); + return NA; + } + return NA; +} + +SpvId SPIRVCodeGenerator::writeOpCompositeExtract(const Type& type, + SpvId base, + int component, + OutputStream& out) { + // If the base op is a composite, we can extract from it directly. + SpvId result = this->toComponent(base, component); + if (result != NA) { + return result; + } + return this->writeInstruction( + SpvOpCompositeExtract, + {this->getType(type), Word::Result(type), base, Word::Number(component)}, + out); +} + +SpvId SPIRVCodeGenerator::writeOpCompositeExtract(const Type& type, + SpvId base, + int componentA, + int componentB, + OutputStream& out) { + // If the base op is a composite, we can extract from it directly. + SpvId result = this->toComponent(base, componentA); + if (result != NA) { + return this->writeOpCompositeExtract(type, result, componentB, out); + } + return this->writeInstruction(SpvOpCompositeExtract, + {this->getType(type), + Word::Result(type), + base, + Word::Number(componentA), + Word::Number(componentB)}, + out); +} + +void SPIRVCodeGenerator::writeCapabilities(OutputStream& out) { + for (uint64_t i = 0, bit = 1; i <= kLast_Capability; i++, bit <<= 1) { + if (fCapabilities & bit) { + this->writeInstruction(SpvOpCapability, (SpvId) i, out); + } + } + this->writeInstruction(SpvOpCapability, SpvCapabilityShader, out); +} + +SpvId SPIRVCodeGenerator::nextId(const Type* type) { + return this->nextId(type && type->hasPrecision() && !type->highPrecision() + ? Precision::kRelaxed + : Precision::kDefault); +} + +SpvId SPIRVCodeGenerator::nextId(Precision precision) { + if (precision == Precision::kRelaxed && !fProgram.fConfig->fSettings.fForceHighPrecision) { + this->writeInstruction(SpvOpDecorate, fIdCount, SpvDecorationRelaxedPrecision, + fDecorationBuffer); + } + return fIdCount++; +} + +SpvId SPIRVCodeGenerator::writeStruct(const Type& type, const MemoryLayout& memoryLayout) { + // If we've already written out this struct, return its existing SpvId. + if (SpvId* cachedStructId = fStructMap.find(&type)) { + return *cachedStructId; + } + + // Write all of the field types first, so we don't inadvertently write them while we're in the + // middle of writing the struct instruction. + Words words; + words.push_back(Word::UniqueResult()); + for (const auto& f : type.fields()) { + words.push_back(this->getType(*f.fType, memoryLayout)); + } + SpvId resultId = this->writeInstruction(SpvOpTypeStruct, words, fConstantBuffer); + this->writeInstruction(SpvOpName, resultId, type.name(), fNameBuffer); + fStructMap.set(&type, resultId); + + size_t offset = 0; + for (int32_t i = 0; i < (int32_t) type.fields().size(); i++) { + const Type::Field& field = type.fields()[i]; + if (!memoryLayout.isSupported(*field.fType)) { + fContext.fErrors->error(type.fPosition, "type '" + field.fType->displayName() + + "' is not permitted here"); + return resultId; + } + size_t size = memoryLayout.size(*field.fType); + size_t alignment = memoryLayout.alignment(*field.fType); + const Layout& fieldLayout = field.fModifiers.fLayout; + if (fieldLayout.fOffset >= 0) { + if (fieldLayout.fOffset < (int) offset) { + fContext.fErrors->error(field.fPosition, "offset of field '" + + std::string(field.fName) + "' must be at least " + std::to_string(offset)); + } + if (fieldLayout.fOffset % alignment) { + fContext.fErrors->error(field.fPosition, + "offset of field '" + std::string(field.fName) + + "' must be a multiple of " + std::to_string(alignment)); + } + offset = fieldLayout.fOffset; + } else { + size_t mod = offset % alignment; + if (mod) { + offset += alignment - mod; + } + } + this->writeInstruction(SpvOpMemberName, resultId, i, field.fName, fNameBuffer); + this->writeFieldLayout(fieldLayout, resultId, i); + if (field.fModifiers.fLayout.fBuiltin < 0) { + this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i, SpvDecorationOffset, + (SpvId) offset, fDecorationBuffer); + } + if (field.fType->isMatrix()) { + this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationColMajor, + fDecorationBuffer); + this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationMatrixStride, + (SpvId) memoryLayout.stride(*field.fType), + fDecorationBuffer); + } + if (!field.fType->highPrecision()) { + this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i, + SpvDecorationRelaxedPrecision, fDecorationBuffer); + } + offset += size; + if ((field.fType->isArray() || field.fType->isStruct()) && offset % alignment != 0) { + offset += alignment - offset % alignment; + } + } + + return resultId; +} + +SpvId SPIRVCodeGenerator::getType(const Type& type) { + return this->getType(type, fDefaultLayout); +} + +SpvId SPIRVCodeGenerator::getType(const Type& rawType, const MemoryLayout& layout) { + const Type* type = &rawType; + + switch (type->typeKind()) { + case Type::TypeKind::kVoid: { + return this->writeInstruction(SpvOpTypeVoid, Words{Word::Result()}, fConstantBuffer); + } + case Type::TypeKind::kScalar: + case Type::TypeKind::kLiteral: { + if (type->isBoolean()) { + return this->writeInstruction(SpvOpTypeBool, {Word::Result()}, fConstantBuffer); + } + if (type->isSigned()) { + return this->writeInstruction( + SpvOpTypeInt, + Words{Word::Result(), Word::Number(32), Word::Number(1)}, + fConstantBuffer); + } + if (type->isUnsigned()) { + return this->writeInstruction( + SpvOpTypeInt, + Words{Word::Result(), Word::Number(32), Word::Number(0)}, + fConstantBuffer); + } + if (type->isFloat()) { + return this->writeInstruction( + SpvOpTypeFloat, + Words{Word::Result(), Word::Number(32)}, + fConstantBuffer); + } + SkDEBUGFAILF("unrecognized scalar type '%s'", type->description().c_str()); + return (SpvId)-1; + } + case Type::TypeKind::kVector: { + SpvId scalarTypeId = this->getType(type->componentType(), layout); + return this->writeInstruction( + SpvOpTypeVector, + Words{Word::Result(), scalarTypeId, Word::Number(type->columns())}, + fConstantBuffer); + } + case Type::TypeKind::kMatrix: { + SpvId vectorTypeId = this->getType(IndexExpression::IndexType(fContext, *type), layout); + return this->writeInstruction( + SpvOpTypeMatrix, + Words{Word::Result(), vectorTypeId, Word::Number(type->columns())}, + fConstantBuffer); + } + case Type::TypeKind::kArray: { + if (!layout.isSupported(*type)) { + fContext.fErrors->error(type->fPosition, "type '" + type->displayName() + + "' is not permitted here"); + return NA; + } + size_t stride = layout.stride(*type); + SpvId typeId = this->getType(type->componentType(), layout); + SpvId result = NA; + if (type->isUnsizedArray()) { + result = this->writeInstruction(SpvOpTypeRuntimeArray, + Words{Word::KeyedResult(stride), typeId}, + fConstantBuffer); + } else { + SpvId countId = this->writeLiteral(type->columns(), *fContext.fTypes.fInt); + result = this->writeInstruction(SpvOpTypeArray, + Words{Word::KeyedResult(stride), typeId, countId}, + fConstantBuffer); + } + this->writeInstruction(SpvOpDecorate, + {result, SpvDecorationArrayStride, Word::Number(stride)}, + fDecorationBuffer); + return result; + } + case Type::TypeKind::kStruct: { + return this->writeStruct(*type, layout); + } + case Type::TypeKind::kSeparateSampler: { + return this->writeInstruction(SpvOpTypeSampler, Words{Word::Result()}, fConstantBuffer); + } + case Type::TypeKind::kSampler: { + // Subpass inputs should use the Texture type, not a Sampler. + SkASSERT(type->dimensions() != SpvDimSubpassData); + if (SpvDimBuffer == type->dimensions()) { + fCapabilities |= 1ULL << SpvCapabilitySampledBuffer; + } + SpvId imageTypeId = this->getType(type->textureType(), layout); + return this->writeInstruction(SpvOpTypeSampledImage, + Words{Word::Result(), imageTypeId}, + fConstantBuffer); + } + case Type::TypeKind::kTexture: { + SpvId floatTypeId = this->getType(*fContext.fTypes.fFloat, layout); + int sampled = (type->textureAccess() == Type::TextureAccess::kSample) ? 1 : 2; + return this->writeInstruction(SpvOpTypeImage, + Words{Word::Result(), + floatTypeId, + Word::Number(type->dimensions()), + Word::Number(type->isDepth()), + Word::Number(type->isArrayedTexture()), + Word::Number(type->isMultisampled()), + Word::Number(sampled), + SpvImageFormatUnknown}, + fConstantBuffer); + } + default: { + SkDEBUGFAILF("invalid type: %s", type->description().c_str()); + return NA; + } + } +} + +SpvId SPIRVCodeGenerator::getFunctionType(const FunctionDeclaration& function) { + Words words; + words.push_back(Word::Result()); + words.push_back(this->getType(function.returnType())); + for (const Variable* parameter : function.parameters()) { + if (parameter->type().typeKind() == Type::TypeKind::kSampler && + fProgram.fConfig->fSettings.fSPIRVDawnCompatMode) { + words.push_back(this->getFunctionParameterType(parameter->type().textureType())); + words.push_back(this->getFunctionParameterType(*fContext.fTypes.fSampler)); + } else { + words.push_back(this->getFunctionParameterType(parameter->type())); + } + } + return this->writeInstruction(SpvOpTypeFunction, words, fConstantBuffer); +} + +SpvId SPIRVCodeGenerator::getFunctionParameterType(const Type& parameterType) { + // glslang treats all function arguments as pointers whether they need to be or + // not. I was initially puzzled by this until I ran bizarre failures with certain + // patterns of function calls and control constructs, as exemplified by this minimal + // failure case: + // + // void sphere(float x) { + // } + // + // void map() { + // sphere(1.0); + // } + // + // void main() { + // for (int i = 0; i < 1; i++) { + // map(); + // } + // } + // + // As of this writing, compiling this in the "obvious" way (with sphere taking a float) + // crashes. Making it take a float* and storing the argument in a temporary variable, + // as glslang does, fixes it. + // + // The consensus among shader compiler authors seems to be that GPU driver generally don't + // handle value-based parameters consistently. It is highly likely that they fit their + // implementations to conform to glslang. We take care to do so ourselves. + // + // Our implementation first stores every parameter value into a function storage-class pointer + // before calling a function. The exception is for opaque handle types (samplers and textures) + // which must be stored in a pointer with UniformConstant storage-class. This prevents + // unnecessary temporaries (becuase opaque handles are always rooted in a pointer variable), + // matches glslang's behavior, and translates into WGSL more easily when targeting Dawn. + SpvStorageClass_ storageClass; + if (parameterType.typeKind() == Type::TypeKind::kSampler || + parameterType.typeKind() == Type::TypeKind::kSeparateSampler || + parameterType.typeKind() == Type::TypeKind::kTexture) { + storageClass = SpvStorageClassUniformConstant; + } else { + storageClass = SpvStorageClassFunction; + } + return this->getPointerType(parameterType, storageClass); +} + +SpvId SPIRVCodeGenerator::getPointerType(const Type& type, SpvStorageClass_ storageClass) { + return this->getPointerType( + type, this->memoryLayoutForStorageClass(storageClass), storageClass); +} + +SpvId SPIRVCodeGenerator::getPointerType(const Type& type, const MemoryLayout& layout, + SpvStorageClass_ storageClass) { + return this->writeInstruction( + SpvOpTypePointer, + Words{Word::Result(), Word::Number(storageClass), this->getType(type, layout)}, + fConstantBuffer); +} + +SpvId SPIRVCodeGenerator::writeExpression(const Expression& expr, OutputStream& out) { + switch (expr.kind()) { + case Expression::Kind::kBinary: + return this->writeBinaryExpression(expr.as<BinaryExpression>(), out); + case Expression::Kind::kConstructorArrayCast: + return this->writeExpression(*expr.as<ConstructorArrayCast>().argument(), out); + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorStruct: + return this->writeCompositeConstructor(expr.asAnyConstructor(), out); + case Expression::Kind::kConstructorDiagonalMatrix: + return this->writeConstructorDiagonalMatrix(expr.as<ConstructorDiagonalMatrix>(), out); + case Expression::Kind::kConstructorMatrixResize: + return this->writeConstructorMatrixResize(expr.as<ConstructorMatrixResize>(), out); + case Expression::Kind::kConstructorScalarCast: + return this->writeConstructorScalarCast(expr.as<ConstructorScalarCast>(), out); + case Expression::Kind::kConstructorSplat: + return this->writeConstructorSplat(expr.as<ConstructorSplat>(), out); + case Expression::Kind::kConstructorCompound: + return this->writeConstructorCompound(expr.as<ConstructorCompound>(), out); + case Expression::Kind::kConstructorCompoundCast: + return this->writeConstructorCompoundCast(expr.as<ConstructorCompoundCast>(), out); + case Expression::Kind::kFieldAccess: + return this->writeFieldAccess(expr.as<FieldAccess>(), out); + case Expression::Kind::kFunctionCall: + return this->writeFunctionCall(expr.as<FunctionCall>(), out); + case Expression::Kind::kLiteral: + return this->writeLiteral(expr.as<Literal>()); + case Expression::Kind::kPrefix: + return this->writePrefixExpression(expr.as<PrefixExpression>(), out); + case Expression::Kind::kPostfix: + return this->writePostfixExpression(expr.as<PostfixExpression>(), out); + case Expression::Kind::kSwizzle: + return this->writeSwizzle(expr.as<Swizzle>(), out); + case Expression::Kind::kVariableReference: + return this->writeVariableReference(expr.as<VariableReference>(), out); + case Expression::Kind::kTernary: + return this->writeTernaryExpression(expr.as<TernaryExpression>(), out); + case Expression::Kind::kIndex: + return this->writeIndexExpression(expr.as<IndexExpression>(), out); + case Expression::Kind::kSetting: + return this->writeExpression(*expr.as<Setting>().toLiteral(fContext), out); + default: + SkDEBUGFAILF("unsupported expression: %s", expr.description().c_str()); + break; + } + return NA; +} + +SpvId SPIRVCodeGenerator::writeIntrinsicCall(const FunctionCall& c, OutputStream& out) { + const FunctionDeclaration& function = c.function(); + Intrinsic intrinsic = this->getIntrinsic(function.intrinsicKind()); + if (intrinsic.opKind == kInvalid_IntrinsicOpcodeKind) { + fContext.fErrors->error(c.fPosition, "unsupported intrinsic '" + function.description() + + "'"); + return NA; + } + const ExpressionArray& arguments = c.arguments(); + int32_t intrinsicId = intrinsic.floatOp; + if (arguments.size() > 0) { + const Type& type = arguments[0]->type(); + if (intrinsic.opKind == kSpecial_IntrinsicOpcodeKind) { + // Keep the default float op. + } else { + intrinsicId = pick_by_type(type, intrinsic.floatOp, intrinsic.signedOp, + intrinsic.unsignedOp, intrinsic.boolOp); + } + } + switch (intrinsic.opKind) { + case kGLSL_STD_450_IntrinsicOpcodeKind: { + SpvId result = this->nextId(&c.type()); + SkTArray<SpvId> argumentIds; + std::vector<TempVar> tempVars; + argumentIds.reserve_back(arguments.size()); + for (int i = 0; i < arguments.size(); i++) { + argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out)); + } + this->writeOpCode(SpvOpExtInst, 5 + (int32_t) argumentIds.size(), out); + this->writeWord(this->getType(c.type()), out); + this->writeWord(result, out); + this->writeWord(fGLSLExtendedInstructions, out); + this->writeWord(intrinsicId, out); + for (SpvId id : argumentIds) { + this->writeWord(id, out); + } + this->copyBackTempVars(tempVars, out); + return result; + } + case kSPIRV_IntrinsicOpcodeKind: { + // GLSL supports dot(float, float), but SPIR-V does not. Convert it to FMul + if (intrinsicId == SpvOpDot && arguments[0]->type().isScalar()) { + intrinsicId = SpvOpFMul; + } + SpvId result = this->nextId(&c.type()); + SkTArray<SpvId> argumentIds; + std::vector<TempVar> tempVars; + argumentIds.reserve_back(arguments.size()); + for (int i = 0; i < arguments.size(); i++) { + argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out)); + } + if (!c.type().isVoid()) { + this->writeOpCode((SpvOp_) intrinsicId, 3 + (int32_t) arguments.size(), out); + this->writeWord(this->getType(c.type()), out); + this->writeWord(result, out); + } else { + this->writeOpCode((SpvOp_) intrinsicId, 1 + (int32_t) arguments.size(), out); + } + for (SpvId id : argumentIds) { + this->writeWord(id, out); + } + this->copyBackTempVars(tempVars, out); + return result; + } + case kSpecial_IntrinsicOpcodeKind: + return this->writeSpecialIntrinsic(c, (SpecialIntrinsic) intrinsicId, out); + default: + fContext.fErrors->error(c.fPosition, "unsupported intrinsic '" + + function.description() + "'"); + return NA; + } +} + +SpvId SPIRVCodeGenerator::vectorize(const Expression& arg, int vectorSize, OutputStream& out) { + SkASSERT(vectorSize >= 1 && vectorSize <= 4); + const Type& argType = arg.type(); + if (argType.isScalar() && vectorSize > 1) { + ConstructorSplat splat{arg.fPosition, + argType.toCompound(fContext, vectorSize, /*rows=*/1), + arg.clone()}; + return this->writeConstructorSplat(splat, out); + } + + SkASSERT(vectorSize == argType.columns()); + return this->writeExpression(arg, out); +} + +SkTArray<SpvId> SPIRVCodeGenerator::vectorize(const ExpressionArray& args, OutputStream& out) { + int vectorSize = 1; + for (const auto& a : args) { + if (a->type().isVector()) { + if (vectorSize > 1) { + SkASSERT(a->type().columns() == vectorSize); + } else { + vectorSize = a->type().columns(); + } + } + } + SkTArray<SpvId> result; + result.reserve_back(args.size()); + for (const auto& arg : args) { + result.push_back(this->vectorize(*arg, vectorSize, out)); + } + return result; +} + +void SPIRVCodeGenerator::writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst, + SpvId signedInst, SpvId unsignedInst, + const SkTArray<SpvId>& args, + OutputStream& out) { + this->writeOpCode(SpvOpExtInst, 5 + args.size(), out); + this->writeWord(this->getType(type), out); + this->writeWord(id, out); + this->writeWord(fGLSLExtendedInstructions, out); + this->writeWord(pick_by_type(type, floatInst, signedInst, unsignedInst, NA), out); + for (SpvId a : args) { + this->writeWord(a, out); + } +} + +SpvId SPIRVCodeGenerator::writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind, + OutputStream& out) { + const ExpressionArray& arguments = c.arguments(); + const Type& callType = c.type(); + SpvId result = this->nextId(nullptr); + switch (kind) { + case kAtan_SpecialIntrinsic: { + SkSTArray<2, SpvId> argumentIds; + for (const std::unique_ptr<Expression>& arg : arguments) { + argumentIds.push_back(this->writeExpression(*arg, out)); + } + this->writeOpCode(SpvOpExtInst, 5 + (int32_t) argumentIds.size(), out); + this->writeWord(this->getType(callType), out); + this->writeWord(result, out); + this->writeWord(fGLSLExtendedInstructions, out); + this->writeWord(argumentIds.size() == 2 ? GLSLstd450Atan2 : GLSLstd450Atan, out); + for (SpvId id : argumentIds) { + this->writeWord(id, out); + } + break; + } + case kSampledImage_SpecialIntrinsic: { + SkASSERT(arguments.size() == 2); + SpvId img = this->writeExpression(*arguments[0], out); + SpvId sampler = this->writeExpression(*arguments[1], out); + this->writeInstruction(SpvOpSampledImage, + this->getType(callType), + result, + img, + sampler, + out); + break; + } + case kSubpassLoad_SpecialIntrinsic: { + SpvId img = this->writeExpression(*arguments[0], out); + ExpressionArray args; + args.reserve_back(2); + args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0)); + args.push_back(Literal::MakeInt(fContext, Position(), /*value=*/0)); + ConstructorCompound ctor(Position(), *fContext.fTypes.fInt2, std::move(args)); + SpvId coords = this->writeExpression(ctor, out); + if (arguments.size() == 1) { + this->writeInstruction(SpvOpImageRead, + this->getType(callType), + result, + img, + coords, + out); + } else { + SkASSERT(arguments.size() == 2); + SpvId sample = this->writeExpression(*arguments[1], out); + this->writeInstruction(SpvOpImageRead, + this->getType(callType), + result, + img, + coords, + SpvImageOperandsSampleMask, + sample, + out); + } + break; + } + case kTexture_SpecialIntrinsic: { + SpvOp_ op = SpvOpImageSampleImplicitLod; + const Type& arg1Type = arguments[1]->type(); + switch (arguments[0]->type().dimensions()) { + case SpvDim1D: + if (arg1Type.matches(*fContext.fTypes.fFloat2)) { + op = SpvOpImageSampleProjImplicitLod; + } else { + SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat)); + } + break; + case SpvDim2D: + if (arg1Type.matches(*fContext.fTypes.fFloat3)) { + op = SpvOpImageSampleProjImplicitLod; + } else { + SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2)); + } + break; + case SpvDim3D: + if (arg1Type.matches(*fContext.fTypes.fFloat4)) { + op = SpvOpImageSampleProjImplicitLod; + } else { + SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat3)); + } + break; + case SpvDimCube: // fall through + case SpvDimRect: // fall through + case SpvDimBuffer: // fall through + case SpvDimSubpassData: + break; + } + SpvId type = this->getType(callType); + SpvId sampler = this->writeExpression(*arguments[0], out); + SpvId uv = this->writeExpression(*arguments[1], out); + if (arguments.size() == 3) { + this->writeInstruction(op, type, result, sampler, uv, + SpvImageOperandsBiasMask, + this->writeExpression(*arguments[2], out), + out); + } else { + SkASSERT(arguments.size() == 2); + if (fProgram.fConfig->fSettings.fSharpenTextures) { + SpvId lodBias = this->writeLiteral(kSharpenTexturesBias, + *fContext.fTypes.fFloat); + this->writeInstruction(op, type, result, sampler, uv, + SpvImageOperandsBiasMask, lodBias, out); + } else { + this->writeInstruction(op, type, result, sampler, uv, + out); + } + } + break; + } + case kTextureGrad_SpecialIntrinsic: { + SpvOp_ op = SpvOpImageSampleExplicitLod; + SkASSERT(arguments.size() == 4); + SkASSERT(arguments[0]->type().dimensions() == SpvDim2D); + SkASSERT(arguments[1]->type().matches(*fContext.fTypes.fFloat2)); + SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat2)); + SkASSERT(arguments[3]->type().matches(*fContext.fTypes.fFloat2)); + SpvId type = this->getType(callType); + SpvId sampler = this->writeExpression(*arguments[0], out); + SpvId uv = this->writeExpression(*arguments[1], out); + SpvId dPdx = this->writeExpression(*arguments[2], out); + SpvId dPdy = this->writeExpression(*arguments[3], out); + this->writeInstruction(op, type, result, sampler, uv, SpvImageOperandsGradMask, + dPdx, dPdy, out); + break; + } + case kTextureLod_SpecialIntrinsic: { + SpvOp_ op = SpvOpImageSampleExplicitLod; + SkASSERT(arguments.size() == 3); + SkASSERT(arguments[0]->type().dimensions() == SpvDim2D); + SkASSERT(arguments[2]->type().matches(*fContext.fTypes.fFloat)); + const Type& arg1Type = arguments[1]->type(); + if (arg1Type.matches(*fContext.fTypes.fFloat3)) { + op = SpvOpImageSampleProjExplicitLod; + } else { + SkASSERT(arg1Type.matches(*fContext.fTypes.fFloat2)); + } + SpvId type = this->getType(callType); + SpvId sampler = this->writeExpression(*arguments[0], out); + SpvId uv = this->writeExpression(*arguments[1], out); + this->writeInstruction(op, type, result, sampler, uv, + SpvImageOperandsLodMask, + this->writeExpression(*arguments[2], out), + out); + break; + } + case kMod_SpecialIntrinsic: { + SkTArray<SpvId> args = this->vectorize(arguments, out); + SkASSERT(args.size() == 2); + const Type& operandType = arguments[0]->type(); + SpvOp_ op = pick_by_type(operandType, SpvOpFMod, SpvOpSMod, SpvOpUMod, SpvOpUndef); + SkASSERT(op != SpvOpUndef); + this->writeOpCode(op, 5, out); + this->writeWord(this->getType(operandType), out); + this->writeWord(result, out); + this->writeWord(args[0], out); + this->writeWord(args[1], out); + break; + } + case kDFdy_SpecialIntrinsic: { + SpvId fn = this->writeExpression(*arguments[0], out); + this->writeOpCode(SpvOpDPdy, 4, out); + this->writeWord(this->getType(callType), out); + this->writeWord(result, out); + this->writeWord(fn, out); + if (!fProgram.fConfig->fSettings.fForceNoRTFlip) { + this->addRTFlipUniform(c.fPosition); + using namespace dsl; + DSLExpression rtFlip( + ThreadContext::Compiler().convertIdentifier(Position(), SKSL_RTFLIP_NAME)); + SpvId rtFlipY = this->vectorize(*rtFlip.y().release(), callType.columns(), out); + SpvId flipped = this->nextId(&callType); + this->writeInstruction( + SpvOpFMul, this->getType(callType), flipped, result, rtFlipY, out); + result = flipped; + } + break; + } + case kClamp_SpecialIntrinsic: { + SkTArray<SpvId> args = this->vectorize(arguments, out); + SkASSERT(args.size() == 3); + this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp, + GLSLstd450UClamp, args, out); + break; + } + case kMax_SpecialIntrinsic: { + SkTArray<SpvId> args = this->vectorize(arguments, out); + SkASSERT(args.size() == 2); + this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMax, GLSLstd450SMax, + GLSLstd450UMax, args, out); + break; + } + case kMin_SpecialIntrinsic: { + SkTArray<SpvId> args = this->vectorize(arguments, out); + SkASSERT(args.size() == 2); + this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMin, GLSLstd450SMin, + GLSLstd450UMin, args, out); + break; + } + case kMix_SpecialIntrinsic: { + SkTArray<SpvId> args = this->vectorize(arguments, out); + SkASSERT(args.size() == 3); + if (arguments[2]->type().componentType().isBoolean()) { + // Use OpSelect to implement Boolean mix(). + SpvId falseId = this->writeExpression(*arguments[0], out); + SpvId trueId = this->writeExpression(*arguments[1], out); + SpvId conditionId = this->writeExpression(*arguments[2], out); + this->writeInstruction(SpvOpSelect, this->getType(arguments[0]->type()), result, + conditionId, trueId, falseId, out); + } else { + this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FMix, SpvOpUndef, + SpvOpUndef, args, out); + } + break; + } + case kSaturate_SpecialIntrinsic: { + SkASSERT(arguments.size() == 1); + ExpressionArray finalArgs; + finalArgs.reserve_back(3); + finalArgs.push_back(arguments[0]->clone()); + finalArgs.push_back(Literal::MakeFloat(fContext, Position(), /*value=*/0)); + finalArgs.push_back(Literal::MakeFloat(fContext, Position(), /*value=*/1)); + SkTArray<SpvId> spvArgs = this->vectorize(finalArgs, out); + this->writeGLSLExtendedInstruction(callType, result, GLSLstd450FClamp, GLSLstd450SClamp, + GLSLstd450UClamp, spvArgs, out); + break; + } + case kSmoothStep_SpecialIntrinsic: { + SkTArray<SpvId> args = this->vectorize(arguments, out); + SkASSERT(args.size() == 3); + this->writeGLSLExtendedInstruction(callType, result, GLSLstd450SmoothStep, SpvOpUndef, + SpvOpUndef, args, out); + break; + } + case kStep_SpecialIntrinsic: { + SkTArray<SpvId> args = this->vectorize(arguments, out); + SkASSERT(args.size() == 2); + this->writeGLSLExtendedInstruction(callType, result, GLSLstd450Step, SpvOpUndef, + SpvOpUndef, args, out); + break; + } + case kMatrixCompMult_SpecialIntrinsic: { + SkASSERT(arguments.size() == 2); + SpvId lhs = this->writeExpression(*arguments[0], out); + SpvId rhs = this->writeExpression(*arguments[1], out); + result = this->writeComponentwiseMatrixBinary(callType, lhs, rhs, SpvOpFMul, out); + break; + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeFunctionCallArgument(const FunctionCall& call, + int argIndex, + std::vector<TempVar>* tempVars, + OutputStream& out, + SpvId* outSynthesizedSamplerId) { + const FunctionDeclaration& funcDecl = call.function(); + const Expression& arg = *call.arguments()[argIndex]; + const Modifiers& paramModifiers = funcDecl.parameters()[argIndex]->modifiers(); + + // ID of temporary variable that we will use to hold this argument, or 0 if it is being + // passed directly + SpvId tmpVar; + // if we need a temporary var to store this argument, this is the value to store in the var + SpvId tmpValueId = NA; + + if (is_out(paramModifiers)) { + std::unique_ptr<LValue> lv = this->getLValue(arg, out); + // We handle out params with a temp var that we copy back to the original variable at the + // end of the call. GLSL guarantees that the original variable will be unchanged until the + // end of the call, and also that out params are written back to their original variables in + // a specific order (left-to-right), so it's unsafe to pass a pointer to the original value. + if (is_in(paramModifiers)) { + tmpValueId = lv->load(out); + } + tmpVar = this->nextId(&arg.type()); + tempVars->push_back(TempVar{tmpVar, &arg.type(), std::move(lv)}); + } else if (funcDecl.isIntrinsic()) { + // Unlike user function calls, non-out intrinsic arguments don't need pointer parameters. + return this->writeExpression(arg, out); + } else if (arg.is<VariableReference>() && + (arg.type().typeKind() == Type::TypeKind::kSampler || + arg.type().typeKind() == Type::TypeKind::kSeparateSampler || + arg.type().typeKind() == Type::TypeKind::kTexture)) { + // Opaque handle (sampler/texture) arguments are always declared as pointers but never + // stored in intermediates when calling user-defined functions. + // + // The case for intrinsics (which take opaque arguments by value) is handled above just like + // regular pointers. + // + // See getFunctionParameterType for further explanation. + const Variable* var = arg.as<VariableReference>().variable(); + + // In Dawn-mode the texture and sampler arguments are forwarded to the helper function. + if (const auto* p = fSynthesizedSamplerMap.find(var)) { + SkASSERT(fProgram.fConfig->fSettings.fSPIRVDawnCompatMode); + SkASSERT(arg.type().typeKind() == Type::TypeKind::kSampler); + SkASSERT(outSynthesizedSamplerId); + + SpvId* img = fVariableMap.find((*p)->fTexture.get()); + SpvId* sampler = fVariableMap.find((*p)->fSampler.get()); + SkASSERT(img); + SkASSERT(sampler); + + *outSynthesizedSamplerId = *sampler; + return *img; + } + + SpvId* entry = fVariableMap.find(var); + SkASSERTF(entry, "%s", arg.description().c_str()); + return *entry; + } else { + // We always use pointer parameters when calling user functions. + // See getFunctionParameterType for further explanation. + tmpValueId = this->writeExpression(arg, out); + tmpVar = this->nextId(nullptr); + } + this->writeInstruction(SpvOpVariable, + this->getPointerType(arg.type(), SpvStorageClassFunction), + tmpVar, + SpvStorageClassFunction, + fVariableBuffer); + if (tmpValueId != NA) { + this->writeOpStore(SpvStorageClassFunction, tmpVar, tmpValueId, out); + } + return tmpVar; +} + +void SPIRVCodeGenerator::copyBackTempVars(const std::vector<TempVar>& tempVars, OutputStream& out) { + for (const TempVar& tempVar : tempVars) { + SpvId load = this->nextId(tempVar.type); + this->writeInstruction(SpvOpLoad, this->getType(*tempVar.type), load, tempVar.spvId, out); + tempVar.lvalue->store(load, out); + } +} + +SpvId SPIRVCodeGenerator::writeFunctionCall(const FunctionCall& c, OutputStream& out) { + const FunctionDeclaration& function = c.function(); + if (function.isIntrinsic() && !function.definition()) { + return this->writeIntrinsicCall(c, out); + } + const ExpressionArray& arguments = c.arguments(); + SpvId* entry = fFunctionMap.find(&function); + if (!entry) { + fContext.fErrors->error(c.fPosition, "function '" + function.description() + + "' is not defined"); + return NA; + } + // Temp variables are used to write back out-parameters after the function call is complete. + std::vector<TempVar> tempVars; + SkTArray<SpvId> argumentIds; + argumentIds.reserve_back(arguments.size()); + for (int i = 0; i < arguments.size(); i++) { + SpvId samplerId = NA; + argumentIds.push_back(this->writeFunctionCallArgument(c, i, &tempVars, out, &samplerId)); + if (samplerId != NA) { + argumentIds.push_back(samplerId); + } + } + SpvId result = this->nextId(nullptr); + this->writeOpCode(SpvOpFunctionCall, 4 + (int32_t)argumentIds.size(), out); + this->writeWord(this->getType(c.type()), out); + this->writeWord(result, out); + this->writeWord(*entry, out); + for (SpvId id : argumentIds) { + this->writeWord(id, out); + } + // Now that the call is complete, we copy temp out-variables back to their real lvalues. + this->copyBackTempVars(tempVars, out); + return result; +} + +SpvId SPIRVCodeGenerator::castScalarToType(SpvId inputExprId, + const Type& inputType, + const Type& outputType, + OutputStream& out) { + if (outputType.isFloat()) { + return this->castScalarToFloat(inputExprId, inputType, outputType, out); + } + if (outputType.isSigned()) { + return this->castScalarToSignedInt(inputExprId, inputType, outputType, out); + } + if (outputType.isUnsigned()) { + return this->castScalarToUnsignedInt(inputExprId, inputType, outputType, out); + } + if (outputType.isBoolean()) { + return this->castScalarToBoolean(inputExprId, inputType, outputType, out); + } + + fContext.fErrors->error(Position(), "unsupported cast: " + inputType.description() + " to " + + outputType.description()); + return inputExprId; +} + +SpvId SPIRVCodeGenerator::writeFloatConstructor(const AnyConstructor& c, OutputStream& out) { + SkASSERT(c.argumentSpan().size() == 1); + SkASSERT(c.type().isFloat()); + const Expression& ctorExpr = *c.argumentSpan().front(); + SpvId expressionId = this->writeExpression(ctorExpr, out); + return this->castScalarToFloat(expressionId, ctorExpr.type(), c.type(), out); +} + +SpvId SPIRVCodeGenerator::castScalarToFloat(SpvId inputId, const Type& inputType, + const Type& outputType, OutputStream& out) { + // Casting a float to float is a no-op. + if (inputType.isFloat()) { + return inputId; + } + + // Given the input type, generate the appropriate instruction to cast to float. + SpvId result = this->nextId(&outputType); + if (inputType.isBoolean()) { + // Use OpSelect to convert the boolean argument to a literal 1.0 or 0.0. + const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fFloat); + const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fFloat); + this->writeInstruction(SpvOpSelect, this->getType(outputType), result, + inputId, oneID, zeroID, out); + } else if (inputType.isSigned()) { + this->writeInstruction(SpvOpConvertSToF, this->getType(outputType), result, inputId, out); + } else if (inputType.isUnsigned()) { + this->writeInstruction(SpvOpConvertUToF, this->getType(outputType), result, inputId, out); + } else { + SkDEBUGFAILF("unsupported type for float typecast: %s", inputType.description().c_str()); + return NA; + } + return result; +} + +SpvId SPIRVCodeGenerator::writeIntConstructor(const AnyConstructor& c, OutputStream& out) { + SkASSERT(c.argumentSpan().size() == 1); + SkASSERT(c.type().isSigned()); + const Expression& ctorExpr = *c.argumentSpan().front(); + SpvId expressionId = this->writeExpression(ctorExpr, out); + return this->castScalarToSignedInt(expressionId, ctorExpr.type(), c.type(), out); +} + +SpvId SPIRVCodeGenerator::castScalarToSignedInt(SpvId inputId, const Type& inputType, + const Type& outputType, OutputStream& out) { + // Casting a signed int to signed int is a no-op. + if (inputType.isSigned()) { + return inputId; + } + + // Given the input type, generate the appropriate instruction to cast to signed int. + SpvId result = this->nextId(&outputType); + if (inputType.isBoolean()) { + // Use OpSelect to convert the boolean argument to a literal 1 or 0. + const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fInt); + const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fInt); + this->writeInstruction(SpvOpSelect, this->getType(outputType), result, + inputId, oneID, zeroID, out); + } else if (inputType.isFloat()) { + this->writeInstruction(SpvOpConvertFToS, this->getType(outputType), result, inputId, out); + } else if (inputType.isUnsigned()) { + this->writeInstruction(SpvOpBitcast, this->getType(outputType), result, inputId, out); + } else { + SkDEBUGFAILF("unsupported type for signed int typecast: %s", + inputType.description().c_str()); + return NA; + } + return result; +} + +SpvId SPIRVCodeGenerator::writeUIntConstructor(const AnyConstructor& c, OutputStream& out) { + SkASSERT(c.argumentSpan().size() == 1); + SkASSERT(c.type().isUnsigned()); + const Expression& ctorExpr = *c.argumentSpan().front(); + SpvId expressionId = this->writeExpression(ctorExpr, out); + return this->castScalarToUnsignedInt(expressionId, ctorExpr.type(), c.type(), out); +} + +SpvId SPIRVCodeGenerator::castScalarToUnsignedInt(SpvId inputId, const Type& inputType, + const Type& outputType, OutputStream& out) { + // Casting an unsigned int to unsigned int is a no-op. + if (inputType.isUnsigned()) { + return inputId; + } + + // Given the input type, generate the appropriate instruction to cast to unsigned int. + SpvId result = this->nextId(&outputType); + if (inputType.isBoolean()) { + // Use OpSelect to convert the boolean argument to a literal 1u or 0u. + const SpvId oneID = this->writeLiteral(1.0, *fContext.fTypes.fUInt); + const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fUInt); + this->writeInstruction(SpvOpSelect, this->getType(outputType), result, + inputId, oneID, zeroID, out); + } else if (inputType.isFloat()) { + this->writeInstruction(SpvOpConvertFToU, this->getType(outputType), result, inputId, out); + } else if (inputType.isSigned()) { + this->writeInstruction(SpvOpBitcast, this->getType(outputType), result, inputId, out); + } else { + SkDEBUGFAILF("unsupported type for unsigned int typecast: %s", + inputType.description().c_str()); + return NA; + } + return result; +} + +SpvId SPIRVCodeGenerator::writeBooleanConstructor(const AnyConstructor& c, OutputStream& out) { + SkASSERT(c.argumentSpan().size() == 1); + SkASSERT(c.type().isBoolean()); + const Expression& ctorExpr = *c.argumentSpan().front(); + SpvId expressionId = this->writeExpression(ctorExpr, out); + return this->castScalarToBoolean(expressionId, ctorExpr.type(), c.type(), out); +} + +SpvId SPIRVCodeGenerator::castScalarToBoolean(SpvId inputId, const Type& inputType, + const Type& outputType, OutputStream& out) { + // Casting a bool to bool is a no-op. + if (inputType.isBoolean()) { + return inputId; + } + + // Given the input type, generate the appropriate instruction to cast to bool. + SpvId result = this->nextId(nullptr); + if (inputType.isSigned()) { + // Synthesize a boolean result by comparing the input against a signed zero literal. + const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fInt); + this->writeInstruction(SpvOpINotEqual, this->getType(outputType), result, + inputId, zeroID, out); + } else if (inputType.isUnsigned()) { + // Synthesize a boolean result by comparing the input against an unsigned zero literal. + const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fUInt); + this->writeInstruction(SpvOpINotEqual, this->getType(outputType), result, + inputId, zeroID, out); + } else if (inputType.isFloat()) { + // Synthesize a boolean result by comparing the input against a floating-point zero literal. + const SpvId zeroID = this->writeLiteral(0.0, *fContext.fTypes.fFloat); + this->writeInstruction(SpvOpFUnordNotEqual, this->getType(outputType), result, + inputId, zeroID, out); + } else { + SkDEBUGFAILF("unsupported type for boolean typecast: %s", inputType.description().c_str()); + return NA; + } + return result; +} + +SpvId SPIRVCodeGenerator::writeMatrixCopy(SpvId src, const Type& srcType, const Type& dstType, + OutputStream& out) { + SkASSERT(srcType.isMatrix()); + SkASSERT(dstType.isMatrix()); + SkASSERT(srcType.componentType().matches(dstType.componentType())); + const Type& srcColumnType = srcType.componentType().toCompound(fContext, srcType.rows(), 1); + const Type& dstColumnType = dstType.componentType().toCompound(fContext, dstType.rows(), 1); + SkASSERT(dstType.componentType().isFloat()); + SpvId dstColumnTypeId = this->getType(dstColumnType); + const SpvId zeroId = this->writeLiteral(0.0, dstType.componentType()); + const SpvId oneId = this->writeLiteral(1.0, dstType.componentType()); + + SkSTArray<4, SpvId> columns; + for (int i = 0; i < dstType.columns(); i++) { + if (i < srcType.columns()) { + // we're still inside the src matrix, copy the column + SpvId srcColumn = this->writeOpCompositeExtract(srcColumnType, src, i, out); + SpvId dstColumn; + if (srcType.rows() == dstType.rows()) { + // columns are equal size, don't need to do anything + dstColumn = srcColumn; + } + else if (dstType.rows() > srcType.rows()) { + // dst column is bigger, need to zero-pad it + SkSTArray<4, SpvId> values; + values.push_back(srcColumn); + for (int j = srcType.rows(); j < dstType.rows(); ++j) { + values.push_back((i == j) ? oneId : zeroId); + } + dstColumn = this->writeOpCompositeConstruct(dstColumnType, values, out); + } + else { + // dst column is smaller, need to swizzle the src column + dstColumn = this->nextId(&dstType); + this->writeOpCode(SpvOpVectorShuffle, 5 + dstType.rows(), out); + this->writeWord(dstColumnTypeId, out); + this->writeWord(dstColumn, out); + this->writeWord(srcColumn, out); + this->writeWord(srcColumn, out); + for (int j = 0; j < dstType.rows(); j++) { + this->writeWord(j, out); + } + } + columns.push_back(dstColumn); + } else { + // we're past the end of the src matrix, need to synthesize an identity-matrix column + SkSTArray<4, SpvId> values; + for (int j = 0; j < dstType.rows(); ++j) { + values.push_back((i == j) ? oneId : zeroId); + } + columns.push_back(this->writeOpCompositeConstruct(dstColumnType, values, out)); + } + } + + return this->writeOpCompositeConstruct(dstType, columns, out); +} + +void SPIRVCodeGenerator::addColumnEntry(const Type& columnType, + SkTArray<SpvId>* currentColumn, + SkTArray<SpvId>* columnIds, + int rows, + SpvId entry, + OutputStream& out) { + SkASSERT(currentColumn->size() < rows); + currentColumn->push_back(entry); + if (currentColumn->size() == rows) { + // Synthesize this column into a vector. + SpvId columnId = this->writeOpCompositeConstruct(columnType, *currentColumn, out); + columnIds->push_back(columnId); + currentColumn->clear(); + } +} + +SpvId SPIRVCodeGenerator::writeMatrixConstructor(const ConstructorCompound& c, OutputStream& out) { + const Type& type = c.type(); + SkASSERT(type.isMatrix()); + SkASSERT(!c.arguments().empty()); + const Type& arg0Type = c.arguments()[0]->type(); + // go ahead and write the arguments so we don't try to write new instructions in the middle of + // an instruction + SkSTArray<16, SpvId> arguments; + for (const std::unique_ptr<Expression>& arg : c.arguments()) { + arguments.push_back(this->writeExpression(*arg, out)); + } + + if (arguments.size() == 1 && arg0Type.isVector()) { + // Special-case handling of float4 -> mat2x2. + SkASSERT(type.rows() == 2 && type.columns() == 2); + SkASSERT(arg0Type.columns() == 4); + SpvId v[4]; + for (int i = 0; i < 4; ++i) { + v[i] = this->writeOpCompositeExtract(type.componentType(), arguments[0], i, out); + } + const Type& vecType = type.componentType().toCompound(fContext, /*columns=*/2, /*rows=*/1); + SpvId v0v1 = this->writeOpCompositeConstruct(vecType, {v[0], v[1]}, out); + SpvId v2v3 = this->writeOpCompositeConstruct(vecType, {v[2], v[3]}, out); + return this->writeOpCompositeConstruct(type, {v0v1, v2v3}, out); + } + + int rows = type.rows(); + const Type& columnType = type.componentType().toCompound(fContext, + /*columns=*/rows, /*rows=*/1); + // SpvIds of completed columns of the matrix. + SkSTArray<4, SpvId> columnIds; + // SpvIds of scalars we have written to the current column so far. + SkSTArray<4, SpvId> currentColumn; + for (int i = 0; i < arguments.size(); i++) { + const Type& argType = c.arguments()[i]->type(); + if (currentColumn.empty() && argType.isVector() && argType.columns() == rows) { + // This vector is a complete matrix column by itself and can be used as-is. + columnIds.push_back(arguments[i]); + } else if (argType.columns() == 1) { + // This argument is a lone scalar and can be added to the current column as-is. + this->addColumnEntry(columnType, ¤tColumn, &columnIds, rows, arguments[i], out); + } else { + // This argument needs to be decomposed into its constituent scalars. + for (int j = 0; j < argType.columns(); ++j) { + SpvId swizzle = this->writeOpCompositeExtract(argType.componentType(), + arguments[i], j, out); + this->addColumnEntry(columnType, ¤tColumn, &columnIds, rows, swizzle, out); + } + } + } + SkASSERT(columnIds.size() == type.columns()); + return this->writeOpCompositeConstruct(type, columnIds, out); +} + +SpvId SPIRVCodeGenerator::writeConstructorCompound(const ConstructorCompound& c, + OutputStream& out) { + return c.type().isMatrix() ? this->writeMatrixConstructor(c, out) + : this->writeVectorConstructor(c, out); +} + +SpvId SPIRVCodeGenerator::writeVectorConstructor(const ConstructorCompound& c, OutputStream& out) { + const Type& type = c.type(); + const Type& componentType = type.componentType(); + SkASSERT(type.isVector()); + + SkSTArray<4, SpvId> arguments; + for (int i = 0; i < c.arguments().size(); i++) { + const Type& argType = c.arguments()[i]->type(); + SkASSERT(componentType.numberKind() == argType.componentType().numberKind()); + + SpvId arg = this->writeExpression(*c.arguments()[i], out); + if (argType.isMatrix()) { + // CompositeConstruct cannot take a 2x2 matrix as an input, so we need to extract out + // each scalar separately. + SkASSERT(argType.rows() == 2); + SkASSERT(argType.columns() == 2); + for (int j = 0; j < 4; ++j) { + arguments.push_back(this->writeOpCompositeExtract(componentType, arg, + j / 2, j % 2, out)); + } + } else if (argType.isVector()) { + // There's a bug in the Intel Vulkan driver where OpCompositeConstruct doesn't handle + // vector arguments at all, so we always extract each vector component and pass them + // into OpCompositeConstruct individually. + for (int j = 0; j < argType.columns(); j++) { + arguments.push_back(this->writeOpCompositeExtract(componentType, arg, j, out)); + } + } else { + arguments.push_back(arg); + } + } + + return this->writeOpCompositeConstruct(type, arguments, out); +} + +SpvId SPIRVCodeGenerator::writeConstructorSplat(const ConstructorSplat& c, OutputStream& out) { + // Write the splat argument. + SpvId argument = this->writeExpression(*c.argument(), out); + + // Generate a OpCompositeConstruct which repeats the argument N times. + SkSTArray<4, SpvId> values; + values.push_back_n(/*n=*/c.type().columns(), /*t=*/argument); + return this->writeOpCompositeConstruct(c.type(), values, out); +} + +SpvId SPIRVCodeGenerator::writeCompositeConstructor(const AnyConstructor& c, OutputStream& out) { + SkASSERT(c.type().isArray() || c.type().isStruct()); + auto ctorArgs = c.argumentSpan(); + + SkSTArray<4, SpvId> arguments; + for (const std::unique_ptr<Expression>& arg : ctorArgs) { + arguments.push_back(this->writeExpression(*arg, out)); + } + + return this->writeOpCompositeConstruct(c.type(), arguments, out); +} + +SpvId SPIRVCodeGenerator::writeConstructorScalarCast(const ConstructorScalarCast& c, + OutputStream& out) { + const Type& type = c.type(); + if (type.componentType().numberKind() == c.argument()->type().componentType().numberKind()) { + return this->writeExpression(*c.argument(), out); + } + + const Expression& ctorExpr = *c.argument(); + SpvId expressionId = this->writeExpression(ctorExpr, out); + return this->castScalarToType(expressionId, ctorExpr.type(), type, out); +} + +SpvId SPIRVCodeGenerator::writeConstructorCompoundCast(const ConstructorCompoundCast& c, + OutputStream& out) { + const Type& ctorType = c.type(); + const Type& argType = c.argument()->type(); + SkASSERT(ctorType.isVector() || ctorType.isMatrix()); + + // Write the composite that we are casting. If the actual type matches, we are done. + SpvId compositeId = this->writeExpression(*c.argument(), out); + if (ctorType.componentType().numberKind() == argType.componentType().numberKind()) { + return compositeId; + } + + // writeMatrixCopy can cast matrices to a different type. + if (ctorType.isMatrix()) { + return this->writeMatrixCopy(compositeId, argType, ctorType, out); + } + + // SPIR-V doesn't support vector(vector-of-different-type) directly, so we need to extract the + // components and convert each one manually. + const Type& srcType = argType.componentType(); + const Type& dstType = ctorType.componentType(); + + SkSTArray<4, SpvId> arguments; + for (int index = 0; index < argType.columns(); ++index) { + SpvId componentId = this->writeOpCompositeExtract(srcType, compositeId, index, out); + arguments.push_back(this->castScalarToType(componentId, srcType, dstType, out)); + } + + return this->writeOpCompositeConstruct(ctorType, arguments, out); +} + +SpvId SPIRVCodeGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, + OutputStream& out) { + const Type& type = c.type(); + SkASSERT(type.isMatrix()); + SkASSERT(c.argument()->type().isScalar()); + + // Write out the scalar argument. + SpvId diagonal = this->writeExpression(*c.argument(), out); + + // Build the diagonal matrix. + SpvId zeroId = this->writeLiteral(0.0, *fContext.fTypes.fFloat); + + const Type& vecType = type.componentType().toCompound(fContext, + /*columns=*/type.rows(), + /*rows=*/1); + SkSTArray<4, SpvId> columnIds; + SkSTArray<4, SpvId> arguments; + arguments.resize(type.rows()); + for (int column = 0; column < type.columns(); column++) { + for (int row = 0; row < type.rows(); row++) { + arguments[row] = (row == column) ? diagonal : zeroId; + } + columnIds.push_back(this->writeOpCompositeConstruct(vecType, arguments, out)); + } + return this->writeOpCompositeConstruct(type, columnIds, out); +} + +SpvId SPIRVCodeGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& c, + OutputStream& out) { + // Write the input matrix. + SpvId argument = this->writeExpression(*c.argument(), out); + + // Use matrix-copy to resize the input matrix to its new size. + return this->writeMatrixCopy(argument, c.argument()->type(), c.type(), out); +} + +static SpvStorageClass_ get_storage_class_for_global_variable( + const Variable& var, SpvStorageClass_ fallbackStorageClass) { + SkASSERT(var.storage() == Variable::Storage::kGlobal); + + const Modifiers& modifiers = var.modifiers(); + if (modifiers.fFlags & Modifiers::kIn_Flag) { + SkASSERT(!(modifiers.fLayout.fFlags & Layout::kPushConstant_Flag)); + return SpvStorageClassInput; + } + if (modifiers.fFlags & Modifiers::kOut_Flag) { + SkASSERT(!(modifiers.fLayout.fFlags & Layout::kPushConstant_Flag)); + return SpvStorageClassOutput; + } + if (modifiers.fFlags & Modifiers::kUniform_Flag) { + if (modifiers.fLayout.fFlags & Layout::kPushConstant_Flag) { + return SpvStorageClassPushConstant; + } + if (var.type().typeKind() == Type::TypeKind::kSampler || + var.type().typeKind() == Type::TypeKind::kSeparateSampler || + var.type().typeKind() == Type::TypeKind::kTexture) { + return SpvStorageClassUniformConstant; + } + return SpvStorageClassUniform; + } + if (modifiers.fFlags & Modifiers::kBuffer_Flag) { + // Note: In SPIR-V 1.3, a storage buffer can be declared with the "StorageBuffer" + // storage class and the "Block" decoration and the <1.3 approach we use here ("Uniform" + // storage class and the "BufferBlock" decoration) is deprecated. Since we target SPIR-V + // 1.0, we have to use the deprecated approach which is well supported in Vulkan and + // addresses SkSL use cases (notably SkSL currently doesn't support pointer features that + // would benefit from SPV_KHR_variable_pointers capabilities). + return SpvStorageClassUniform; + } + return fallbackStorageClass; +} + +static SpvStorageClass_ get_storage_class(const Expression& expr) { + switch (expr.kind()) { + case Expression::Kind::kVariableReference: { + const Variable& var = *expr.as<VariableReference>().variable(); + if (var.storage() != Variable::Storage::kGlobal) { + return SpvStorageClassFunction; + } + return get_storage_class_for_global_variable(var, SpvStorageClassPrivate); + } + case Expression::Kind::kFieldAccess: + return get_storage_class(*expr.as<FieldAccess>().base()); + case Expression::Kind::kIndex: + return get_storage_class(*expr.as<IndexExpression>().base()); + default: + return SpvStorageClassFunction; + } +} + +SkTArray<SpvId> SPIRVCodeGenerator::getAccessChain(const Expression& expr, OutputStream& out) { + switch (expr.kind()) { + case Expression::Kind::kIndex: { + const IndexExpression& indexExpr = expr.as<IndexExpression>(); + if (indexExpr.base()->is<Swizzle>()) { + // Access chains don't directly support dynamically indexing into a swizzle, but we + // can rewrite them into a supported form. + return this->getAccessChain(*Transform::RewriteIndexedSwizzle(fContext, indexExpr), + out); + } + // All other index-expressions can be represented as typical access chains. + SkTArray<SpvId> chain = this->getAccessChain(*indexExpr.base(), out); + chain.push_back(this->writeExpression(*indexExpr.index(), out)); + return chain; + } + case Expression::Kind::kFieldAccess: { + const FieldAccess& fieldExpr = expr.as<FieldAccess>(); + SkTArray<SpvId> chain = this->getAccessChain(*fieldExpr.base(), out); + chain.push_back(this->writeLiteral(fieldExpr.fieldIndex(), *fContext.fTypes.fInt)); + return chain; + } + default: { + SpvId id = this->getLValue(expr, out)->getPointer(); + SkASSERT(id != NA); + return SkTArray<SpvId>{id}; + } + } + SkUNREACHABLE; +} + +class PointerLValue : public SPIRVCodeGenerator::LValue { +public: + PointerLValue(SPIRVCodeGenerator& gen, SpvId pointer, bool isMemoryObject, SpvId type, + SPIRVCodeGenerator::Precision precision, SpvStorageClass_ storageClass) + : fGen(gen) + , fPointer(pointer) + , fIsMemoryObject(isMemoryObject) + , fType(type) + , fPrecision(precision) + , fStorageClass(storageClass) {} + + SpvId getPointer() override { + return fPointer; + } + + bool isMemoryObjectPointer() const override { + return fIsMemoryObject; + } + + SpvId load(OutputStream& out) override { + return fGen.writeOpLoad(fType, fPrecision, fPointer, out); + } + + void store(SpvId value, OutputStream& out) override { + if (!fIsMemoryObject) { + // We are going to write into an access chain; this could represent one component of a + // vector, or one element of an array. This has the potential to invalidate other, + // *unknown* elements of our store cache. (e.g. if the store cache holds `%50 = myVec4`, + // and we store `%60 = myVec4.z`, this invalidates the cached value for %50.) To avoid + // relying on stale data, reset the store cache entirely when this happens. + fGen.fStoreCache.reset(); + } + + fGen.writeOpStore(fStorageClass, fPointer, value, out); + } + +private: + SPIRVCodeGenerator& fGen; + const SpvId fPointer; + const bool fIsMemoryObject; + const SpvId fType; + const SPIRVCodeGenerator::Precision fPrecision; + const SpvStorageClass_ fStorageClass; +}; + +class SwizzleLValue : public SPIRVCodeGenerator::LValue { +public: + SwizzleLValue(SPIRVCodeGenerator& gen, SpvId vecPointer, const ComponentArray& components, + const Type& baseType, const Type& swizzleType, SpvStorageClass_ storageClass) + : fGen(gen) + , fVecPointer(vecPointer) + , fComponents(components) + , fBaseType(&baseType) + , fSwizzleType(&swizzleType) + , fStorageClass(storageClass) {} + + bool applySwizzle(const ComponentArray& components, const Type& newType) override { + ComponentArray updatedSwizzle; + for (int8_t component : components) { + if (component < 0 || component >= fComponents.size()) { + SkDEBUGFAILF("swizzle accessed nonexistent component %d", (int)component); + return false; + } + updatedSwizzle.push_back(fComponents[component]); + } + fComponents = updatedSwizzle; + fSwizzleType = &newType; + return true; + } + + SpvId load(OutputStream& out) override { + SpvId base = fGen.nextId(fBaseType); + fGen.writeInstruction(SpvOpLoad, fGen.getType(*fBaseType), base, fVecPointer, out); + SpvId result = fGen.nextId(fBaseType); + fGen.writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) fComponents.size(), out); + fGen.writeWord(fGen.getType(*fSwizzleType), out); + fGen.writeWord(result, out); + fGen.writeWord(base, out); + fGen.writeWord(base, out); + for (int component : fComponents) { + fGen.writeWord(component, out); + } + return result; + } + + void store(SpvId value, OutputStream& out) override { + // use OpVectorShuffle to mix and match the vector components. We effectively create + // a virtual vector out of the concatenation of the left and right vectors, and then + // select components from this virtual vector to make the result vector. For + // instance, given: + // float3L = ...; + // float3R = ...; + // L.xz = R.xy; + // we end up with the virtual vector (L.x, L.y, L.z, R.x, R.y, R.z). Then we want + // our result vector to look like (R.x, L.y, R.y), so we need to select indices + // (3, 1, 4). + SpvId base = fGen.nextId(fBaseType); + fGen.writeInstruction(SpvOpLoad, fGen.getType(*fBaseType), base, fVecPointer, out); + SpvId shuffle = fGen.nextId(fBaseType); + fGen.writeOpCode(SpvOpVectorShuffle, 5 + fBaseType->columns(), out); + fGen.writeWord(fGen.getType(*fBaseType), out); + fGen.writeWord(shuffle, out); + fGen.writeWord(base, out); + fGen.writeWord(value, out); + for (int i = 0; i < fBaseType->columns(); i++) { + // current offset into the virtual vector, defaults to pulling the unmodified + // value from the left side + int offset = i; + // check to see if we are writing this component + for (int j = 0; j < fComponents.size(); j++) { + if (fComponents[j] == i) { + // we're writing to this component, so adjust the offset to pull from + // the correct component of the right side instead of preserving the + // value from the left + offset = (int) (j + fBaseType->columns()); + break; + } + } + fGen.writeWord(offset, out); + } + fGen.writeOpStore(fStorageClass, fVecPointer, shuffle, out); + } + +private: + SPIRVCodeGenerator& fGen; + const SpvId fVecPointer; + ComponentArray fComponents; + const Type* fBaseType; + const Type* fSwizzleType; + const SpvStorageClass_ fStorageClass; +}; + +int SPIRVCodeGenerator::findUniformFieldIndex(const Variable& var) const { + int* fieldIndex = fTopLevelUniformMap.find(&var); + return fieldIndex ? *fieldIndex : -1; +} + +std::unique_ptr<SPIRVCodeGenerator::LValue> SPIRVCodeGenerator::getLValue(const Expression& expr, + OutputStream& out) { + const Type& type = expr.type(); + Precision precision = type.highPrecision() ? Precision::kDefault : Precision::kRelaxed; + switch (expr.kind()) { + case Expression::Kind::kVariableReference: { + const Variable& var = *expr.as<VariableReference>().variable(); + int uniformIdx = this->findUniformFieldIndex(var); + if (uniformIdx >= 0) { + SpvId memberId = this->nextId(nullptr); + SpvId typeId = this->getPointerType(type, SpvStorageClassUniform); + SpvId uniformIdxId = this->writeLiteral((double)uniformIdx, *fContext.fTypes.fInt); + this->writeInstruction(SpvOpAccessChain, typeId, memberId, fUniformBufferId, + uniformIdxId, out); + return std::make_unique<PointerLValue>( + *this, + memberId, + /*isMemoryObjectPointer=*/true, + this->getType(type, this->memoryLayoutForVariable(var)), + precision, + SpvStorageClassUniform); + } + SpvId typeId = this->getType(type, this->memoryLayoutForVariable(var)); + SpvId* entry = fVariableMap.find(&var); + SkASSERTF(entry, "%s", expr.description().c_str()); + return std::make_unique<PointerLValue>(*this, *entry, + /*isMemoryObjectPointer=*/true, + typeId, precision, get_storage_class(expr)); + } + case Expression::Kind::kIndex: // fall through + case Expression::Kind::kFieldAccess: { + SkTArray<SpvId> chain = this->getAccessChain(expr, out); + SpvId member = this->nextId(nullptr); + SpvStorageClass_ storageClass = get_storage_class(expr); + this->writeOpCode(SpvOpAccessChain, (SpvId) (3 + chain.size()), out); + this->writeWord(this->getPointerType(type, storageClass), out); + this->writeWord(member, out); + for (SpvId idx : chain) { + this->writeWord(idx, out); + } + return std::make_unique<PointerLValue>( + *this, + member, + /*isMemoryObjectPointer=*/false, + this->getType(type, this->memoryLayoutForStorageClass(storageClass)), + precision, + storageClass); + } + case Expression::Kind::kSwizzle: { + const Swizzle& swizzle = expr.as<Swizzle>(); + std::unique_ptr<LValue> lvalue = this->getLValue(*swizzle.base(), out); + if (lvalue->applySwizzle(swizzle.components(), type)) { + return lvalue; + } + SpvId base = lvalue->getPointer(); + if (base == NA) { + fContext.fErrors->error(swizzle.fPosition, + "unable to retrieve lvalue from swizzle"); + } + SpvStorageClass_ storageClass = get_storage_class(*swizzle.base()); + if (swizzle.components().size() == 1) { + SpvId member = this->nextId(nullptr); + SpvId typeId = this->getPointerType(type, storageClass); + SpvId indexId = this->writeLiteral(swizzle.components()[0], *fContext.fTypes.fInt); + this->writeInstruction(SpvOpAccessChain, typeId, member, base, indexId, out); + return std::make_unique<PointerLValue>(*this, member, + /*isMemoryObjectPointer=*/false, + this->getType(type), + precision, storageClass); + } else { + return std::make_unique<SwizzleLValue>(*this, base, swizzle.components(), + swizzle.base()->type(), type, storageClass); + } + } + default: { + // expr isn't actually an lvalue, create a placeholder variable for it. This case + // happens due to the need to store values in temporary variables during function + // calls (see comments in getFunctionParameterType); erroneous uses of rvalues as + // lvalues should have been caught before code generation. + // + // This is with the exception of opaque handle types (textures/samplers) which are + // always defined as UniformConstant pointers and don't need to be explicitly stored + // into a temporary (which is handled explicitly in writeFunctionCallArgument). + SpvId result = this->nextId(nullptr); + SpvId pointerType = this->getPointerType(type, SpvStorageClassFunction); + this->writeInstruction(SpvOpVariable, pointerType, result, SpvStorageClassFunction, + fVariableBuffer); + this->writeOpStore(SpvStorageClassFunction, result, this->writeExpression(expr, out), + out); + return std::make_unique<PointerLValue>(*this, result, /*isMemoryObjectPointer=*/true, + this->getType(type), precision, + SpvStorageClassFunction); + } + } +} + +SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, OutputStream& out) { + const Variable* variable = ref.variable(); + switch (variable->modifiers().fLayout.fBuiltin) { + case DEVICE_FRAGCOORDS_BUILTIN: { + // Down below, we rewrite raw references to sk_FragCoord with expressions that reference + // DEVICE_FRAGCOORDS_BUILTIN. This is a fake variable that means we need to directly + // access the fragcoord; do so now. + dsl::DSLGlobalVar fragCoord("sk_FragCoord"); + return this->getLValue(*dsl::DSLExpression(fragCoord).release(), out)->load(out); + } + case DEVICE_CLOCKWISE_BUILTIN: { + // Down below, we rewrite raw references to sk_Clockwise with expressions that reference + // DEVICE_CLOCKWISE_BUILTIN. This is a fake variable that means we need to directly + // access front facing; do so now. + dsl::DSLGlobalVar clockwise("sk_Clockwise"); + return this->getLValue(*dsl::DSLExpression(clockwise).release(), out)->load(out); + } + case SK_SECONDARYFRAGCOLOR_BUILTIN: { + // sk_SecondaryFragColor corresponds to gl_SecondaryFragColorEXT, which isn't supposed + // to appear in a SPIR-V program (it's only valid in ES2). Report an error. + fContext.fErrors->error(ref.fPosition, + "sk_SecondaryFragColor is not allowed in SPIR-V"); + return NA; + } + case SK_FRAGCOORD_BUILTIN: { + if (fProgram.fConfig->fSettings.fForceNoRTFlip) { + dsl::DSLGlobalVar fragCoord("sk_FragCoord"); + return this->getLValue(*dsl::DSLExpression(fragCoord).release(), out)->load(out); + } + + // Handle inserting use of uniform to flip y when referencing sk_FragCoord. + this->addRTFlipUniform(ref.fPosition); + // Use sk_RTAdjust to compute the flipped coordinate + using namespace dsl; + const char* DEVICE_COORDS_NAME = "$device_FragCoords"; + SymbolTable& symbols = *ThreadContext::SymbolTable(); + // Use a uniform to flip the Y coordinate. The new expression will be written in + // terms of $device_FragCoords, which is a fake variable that means "access the + // underlying fragcoords directly without flipping it". + DSLExpression rtFlip(ThreadContext::Compiler().convertIdentifier(Position(), + SKSL_RTFLIP_NAME)); + if (!symbols.find(DEVICE_COORDS_NAME)) { + AutoAttachPoolToThread attach(fProgram.fPool.get()); + Modifiers modifiers; + modifiers.fLayout.fBuiltin = DEVICE_FRAGCOORDS_BUILTIN; + auto coordsVar = std::make_unique<Variable>(/*pos=*/Position(), + /*modifiersPosition=*/Position(), + fContext.fModifiersPool->add(modifiers), + DEVICE_COORDS_NAME, + fContext.fTypes.fFloat4.get(), + /*builtin=*/true, + Variable::Storage::kGlobal); + fSPIRVBonusVariables.add(coordsVar.get()); + symbols.add(std::move(coordsVar)); + } + DSLGlobalVar deviceCoord(DEVICE_COORDS_NAME); + std::unique_ptr<Expression> rtFlipSkSLExpr = rtFlip.release(); + DSLExpression x = DSLExpression(rtFlipSkSLExpr->clone()).x(); + DSLExpression y = DSLExpression(std::move(rtFlipSkSLExpr)).y(); + return this->writeExpression(*dsl::Float4(deviceCoord.x(), + std::move(x) + std::move(y) * deviceCoord.y(), + deviceCoord.z(), + deviceCoord.w()).release(), + out); + } + case SK_CLOCKWISE_BUILTIN: { + if (fProgram.fConfig->fSettings.fForceNoRTFlip) { + dsl::DSLGlobalVar clockwise("sk_Clockwise"); + return this->getLValue(*dsl::DSLExpression(clockwise).release(), out)->load(out); + } + + // Handle flipping sk_Clockwise. + this->addRTFlipUniform(ref.fPosition); + using namespace dsl; + const char* DEVICE_CLOCKWISE_NAME = "$device_Clockwise"; + SymbolTable& symbols = *ThreadContext::SymbolTable(); + // Use a uniform to flip the Y coordinate. The new expression will be written in + // terms of $device_Clockwise, which is a fake variable that means "access the + // underlying FrontFacing directly". + DSLExpression rtFlip(ThreadContext::Compiler().convertIdentifier(Position(), + SKSL_RTFLIP_NAME)); + if (!symbols.find(DEVICE_CLOCKWISE_NAME)) { + AutoAttachPoolToThread attach(fProgram.fPool.get()); + Modifiers modifiers; + modifiers.fLayout.fBuiltin = DEVICE_CLOCKWISE_BUILTIN; + auto clockwiseVar = std::make_unique<Variable>(/*pos=*/Position(), + /*modifiersPosition=*/Position(), + fContext.fModifiersPool->add(modifiers), + DEVICE_CLOCKWISE_NAME, + fContext.fTypes.fBool.get(), + /*builtin=*/true, + Variable::Storage::kGlobal); + fSPIRVBonusVariables.add(clockwiseVar.get()); + symbols.add(std::move(clockwiseVar)); + } + DSLGlobalVar deviceClockwise(DEVICE_CLOCKWISE_NAME); + // FrontFacing in Vulkan is defined in terms of a top-down render target. In skia, + // we use the default convention of "counter-clockwise face is front". + return this->writeExpression(*dsl::Bool(Select(rtFlip.y() > 0, + !deviceClockwise, + deviceClockwise)).release(), + out); + } + default: { + // Constant-propagate variables that have a known compile-time value. + if (const Expression* expr = ConstantFolder::GetConstantValueOrNullForVariable(ref)) { + return this->writeExpression(*expr, out); + } + + // A reference to a sampler variable at global scope with synthesized texture/sampler + // backing should construct a function-scope combined image-sampler from the synthesized + // constituents. This is the case in which a sample intrinsic was invoked. + // + // Variable references to opaque handles (texture/sampler) that appear as the argument + // of a user-defined function call are explicitly handled in writeFunctionCallArgument. + if (const auto* p = fSynthesizedSamplerMap.find(variable)) { + SkASSERT(fProgram.fConfig->fSettings.fSPIRVDawnCompatMode); + + SpvId* imgPtr = fVariableMap.find((*p)->fTexture.get()); + SpvId* samplerPtr = fVariableMap.find((*p)->fSampler.get()); + SkASSERT(imgPtr); + SkASSERT(samplerPtr); + + SpvId img = this->writeOpLoad( + this->getType((*p)->fTexture->type()), Precision::kDefault, *imgPtr, out); + SpvId sampler = this->writeOpLoad(this->getType((*p)->fSampler->type()), + Precision::kDefault, + *samplerPtr, + out); + + SpvId result = this->nextId(nullptr); + this->writeInstruction(SpvOpSampledImage, + this->getType(variable->type()), + result, + img, + sampler, + out); + + return result; + } + + return this->getLValue(ref, out)->load(out); + } + } +} + +SpvId SPIRVCodeGenerator::writeIndexExpression(const IndexExpression& expr, OutputStream& out) { + if (expr.base()->type().isVector()) { + SpvId base = this->writeExpression(*expr.base(), out); + SpvId index = this->writeExpression(*expr.index(), out); + SpvId result = this->nextId(nullptr); + this->writeInstruction(SpvOpVectorExtractDynamic, this->getType(expr.type()), result, base, + index, out); + return result; + } + return getLValue(expr, out)->load(out); +} + +SpvId SPIRVCodeGenerator::writeFieldAccess(const FieldAccess& f, OutputStream& out) { + return getLValue(f, out)->load(out); +} + +SpvId SPIRVCodeGenerator::writeSwizzle(const Swizzle& swizzle, OutputStream& out) { + SpvId base = this->writeExpression(*swizzle.base(), out); + size_t count = swizzle.components().size(); + if (count == 1) { + return this->writeOpCompositeExtract(swizzle.type(), base, swizzle.components()[0], out); + } + + SpvId result = this->nextId(&swizzle.type()); + this->writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) count, out); + this->writeWord(this->getType(swizzle.type()), out); + this->writeWord(result, out); + this->writeWord(base, out); + this->writeWord(base, out); + for (int component : swizzle.components()) { + this->writeWord(component, out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeBinaryOperation(const Type& resultType, + const Type& operandType, SpvId lhs, + SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, + SpvOp_ ifUInt, SpvOp_ ifBool, OutputStream& out) { + SpvId result = this->nextId(&resultType); + SpvOp_ op = pick_by_type(operandType, ifFloat, ifInt, ifUInt, ifBool); + if (op == SpvOpUndef) { + fContext.fErrors->error(operandType.fPosition, + "unsupported operand for binary expression: " + operandType.description()); + return NA; + } + this->writeInstruction(op, this->getType(resultType), result, lhs, rhs, out); + return result; +} + +SpvId SPIRVCodeGenerator::foldToBool(SpvId id, const Type& operandType, SpvOp op, + OutputStream& out) { + if (operandType.isVector()) { + SpvId result = this->nextId(nullptr); + this->writeInstruction(op, this->getType(*fContext.fTypes.fBool), result, id, out); + return result; + } + return id; +} + +SpvId SPIRVCodeGenerator::writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs, + SpvOp_ floatOperator, SpvOp_ intOperator, + SpvOp_ vectorMergeOperator, SpvOp_ mergeOperator, + OutputStream& out) { + SpvOp_ compareOp = is_float(operandType) ? floatOperator : intOperator; + SkASSERT(operandType.isMatrix()); + const Type& columnType = operandType.componentType().toCompound(fContext, + operandType.rows(), + 1); + SpvId bvecType = this->getType(fContext.fTypes.fBool->toCompound(fContext, + operandType.rows(), + 1)); + SpvId boolType = this->getType(*fContext.fTypes.fBool); + SpvId result = 0; + for (int i = 0; i < operandType.columns(); i++) { + SpvId columnL = this->writeOpCompositeExtract(columnType, lhs, i, out); + SpvId columnR = this->writeOpCompositeExtract(columnType, rhs, i, out); + SpvId compare = this->nextId(&operandType); + this->writeInstruction(compareOp, bvecType, compare, columnL, columnR, out); + SpvId merge = this->nextId(nullptr); + this->writeInstruction(vectorMergeOperator, boolType, merge, compare, out); + if (result != 0) { + SpvId next = this->nextId(nullptr); + this->writeInstruction(mergeOperator, boolType, next, result, merge, out); + result = next; + } else { + result = merge; + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeComponentwiseMatrixUnary(const Type& operandType, + SpvId operand, + SpvOp_ op, + OutputStream& out) { + SkASSERT(operandType.isMatrix()); + const Type& columnType = operandType.componentType().toCompound(fContext, + /*columns=*/operandType.rows(), + /*rows=*/1); + SpvId columnTypeId = this->getType(columnType); + + SkSTArray<4, SpvId> columns; + for (int i = 0; i < operandType.columns(); i++) { + SpvId srcColumn = this->writeOpCompositeExtract(columnType, operand, i, out); + SpvId dstColumn = this->nextId(&operandType); + this->writeInstruction(op, columnTypeId, dstColumn, srcColumn, out); + columns.push_back(dstColumn); + } + + return this->writeOpCompositeConstruct(operandType, columns, out); +} + +SpvId SPIRVCodeGenerator::writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, + SpvId rhs, SpvOp_ op, OutputStream& out) { + SkASSERT(operandType.isMatrix()); + const Type& columnType = operandType.componentType().toCompound(fContext, + /*columns=*/operandType.rows(), + /*rows=*/1); + SpvId columnTypeId = this->getType(columnType); + + SkSTArray<4, SpvId> columns; + for (int i = 0; i < operandType.columns(); i++) { + SpvId columnL = this->writeOpCompositeExtract(columnType, lhs, i, out); + SpvId columnR = this->writeOpCompositeExtract(columnType, rhs, i, out); + columns.push_back(this->nextId(&operandType)); + this->writeInstruction(op, columnTypeId, columns[i], columnL, columnR, out); + } + return this->writeOpCompositeConstruct(operandType, columns, out); +} + +SpvId SPIRVCodeGenerator::writeReciprocal(const Type& type, SpvId value, OutputStream& out) { + SkASSERT(type.isFloat()); + SpvId one = this->writeLiteral(1.0, type); + SpvId reciprocal = this->nextId(&type); + this->writeInstruction(SpvOpFDiv, this->getType(type), reciprocal, one, value, out); + return reciprocal; +} + +SpvId SPIRVCodeGenerator::writeScalarToMatrixSplat(const Type& matrixType, + SpvId scalarId, + OutputStream& out) { + // Splat the scalar into a vector. + const Type& vectorType = matrixType.componentType().toCompound(fContext, + /*columns=*/matrixType.rows(), + /*rows=*/1); + SkSTArray<4, SpvId> vecArguments; + vecArguments.push_back_n(/*n=*/matrixType.rows(), /*t=*/scalarId); + SpvId vectorId = this->writeOpCompositeConstruct(vectorType, vecArguments, out); + + // Splat the vector into a matrix. + SkSTArray<4, SpvId> matArguments; + matArguments.push_back_n(/*n=*/matrixType.columns(), /*t=*/vectorId); + return this->writeOpCompositeConstruct(matrixType, matArguments, out); +} + +static bool types_match(const Type& a, const Type& b) { + if (a.matches(b)) { + return true; + } + return (a.typeKind() == b.typeKind()) && + (a.isScalar() || a.isVector() || a.isMatrix()) && + (a.columns() == b.columns() && a.rows() == b.rows()) && + a.componentType().numberKind() == b.componentType().numberKind(); +} + +SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op, + const Type& rightType, SpvId rhs, + const Type& resultType, OutputStream& out) { + // The comma operator ignores the type of the left-hand side entirely. + if (op.kind() == Operator::Kind::COMMA) { + return rhs; + } + // overall type we are operating on: float2, int, uint4... + const Type* operandType; + if (types_match(leftType, rightType)) { + operandType = &leftType; + } else { + // IR allows mismatched types in expressions (e.g. float2 * float), but they need special + // handling in SPIR-V + if (leftType.isVector() && rightType.isNumber()) { + if (resultType.componentType().isFloat()) { + switch (op.kind()) { + case Operator::Kind::SLASH: { + rhs = this->writeReciprocal(rightType, rhs, out); + [[fallthrough]]; + } + case Operator::Kind::STAR: { + SpvId result = this->nextId(&resultType); + this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType), + result, lhs, rhs, out); + return result; + } + default: + break; + } + } + // Vectorize the right-hand side. + SkSTArray<4, SpvId> arguments; + arguments.push_back_n(/*n=*/leftType.columns(), /*t=*/rhs); + rhs = this->writeOpCompositeConstruct(leftType, arguments, out); + operandType = &leftType; + } else if (rightType.isVector() && leftType.isNumber()) { + if (resultType.componentType().isFloat()) { + if (op.kind() == Operator::Kind::STAR) { + SpvId result = this->nextId(&resultType); + this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType), + result, rhs, lhs, out); + return result; + } + } + // Vectorize the left-hand side. + SkSTArray<4, SpvId> arguments; + arguments.push_back_n(/*n=*/rightType.columns(), /*t=*/lhs); + lhs = this->writeOpCompositeConstruct(rightType, arguments, out); + operandType = &rightType; + } else if (leftType.isMatrix()) { + if (op.kind() == Operator::Kind::STAR) { + // Matrix-times-vector and matrix-times-scalar have dedicated ops in SPIR-V. + SpvOp_ spvop; + if (rightType.isMatrix()) { + spvop = SpvOpMatrixTimesMatrix; + } else if (rightType.isVector()) { + spvop = SpvOpMatrixTimesVector; + } else { + SkASSERT(rightType.isScalar()); + spvop = SpvOpMatrixTimesScalar; + } + SpvId result = this->nextId(&resultType); + this->writeInstruction(spvop, this->getType(resultType), result, lhs, rhs, out); + return result; + } else { + // Matrix-op-vector is not supported in GLSL/SkSL for non-multiplication ops; we + // expect to have a scalar here. + SkASSERT(rightType.isScalar()); + + // Splat rhs across an entire matrix so we can reuse the matrix-op-matrix path. + SpvId rhsMatrix = this->writeScalarToMatrixSplat(leftType, rhs, out); + + // Perform this operation as matrix-op-matrix. + return this->writeBinaryExpression(leftType, lhs, op, leftType, rhsMatrix, + resultType, out); + } + } else if (rightType.isMatrix()) { + if (op.kind() == Operator::Kind::STAR) { + // Matrix-times-vector and matrix-times-scalar have dedicated ops in SPIR-V. + SpvId result = this->nextId(&resultType); + if (leftType.isVector()) { + this->writeInstruction(SpvOpVectorTimesMatrix, this->getType(resultType), + result, lhs, rhs, out); + } else { + SkASSERT(leftType.isScalar()); + this->writeInstruction(SpvOpMatrixTimesScalar, this->getType(resultType), + result, rhs, lhs, out); + } + return result; + } else { + // Vector-op-matrix is not supported in GLSL/SkSL for non-multiplication ops; we + // expect to have a scalar here. + SkASSERT(leftType.isScalar()); + + // Splat lhs across an entire matrix so we can reuse the matrix-op-matrix path. + SpvId lhsMatrix = this->writeScalarToMatrixSplat(rightType, lhs, out); + + // Perform this operation as matrix-op-matrix. + return this->writeBinaryExpression(rightType, lhsMatrix, op, rightType, rhs, + resultType, out); + } + } else { + fContext.fErrors->error(leftType.fPosition, "unsupported mixed-type expression"); + return NA; + } + } + + switch (op.kind()) { + case Operator::Kind::EQEQ: { + if (operandType->isMatrix()) { + return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdEqual, + SpvOpIEqual, SpvOpAll, SpvOpLogicalAnd, out); + } + if (operandType->isStruct()) { + return this->writeStructComparison(*operandType, lhs, op, rhs, out); + } + if (operandType->isArray()) { + return this->writeArrayComparison(*operandType, lhs, op, rhs, out); + } + SkASSERT(resultType.isBoolean()); + const Type* tmpType; + if (operandType->isVector()) { + tmpType = &fContext.fTypes.fBool->toCompound(fContext, + operandType->columns(), + operandType->rows()); + } else { + tmpType = &resultType; + } + if (lhs == rhs) { + // This ignores the effects of NaN. + return this->writeOpConstantTrue(*fContext.fTypes.fBool); + } + return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs, + SpvOpFOrdEqual, SpvOpIEqual, + SpvOpIEqual, SpvOpLogicalEqual, out), + *operandType, SpvOpAll, out); + } + case Operator::Kind::NEQ: + if (operandType->isMatrix()) { + return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFUnordNotEqual, + SpvOpINotEqual, SpvOpAny, SpvOpLogicalOr, out); + } + if (operandType->isStruct()) { + return this->writeStructComparison(*operandType, lhs, op, rhs, out); + } + if (operandType->isArray()) { + return this->writeArrayComparison(*operandType, lhs, op, rhs, out); + } + [[fallthrough]]; + case Operator::Kind::LOGICALXOR: + SkASSERT(resultType.isBoolean()); + const Type* tmpType; + if (operandType->isVector()) { + tmpType = &fContext.fTypes.fBool->toCompound(fContext, + operandType->columns(), + operandType->rows()); + } else { + tmpType = &resultType; + } + if (lhs == rhs) { + // This ignores the effects of NaN. + return this->writeOpConstantFalse(*fContext.fTypes.fBool); + } + return this->foldToBool(this->writeBinaryOperation(*tmpType, *operandType, lhs, rhs, + SpvOpFUnordNotEqual, SpvOpINotEqual, + SpvOpINotEqual, SpvOpLogicalNotEqual, + out), + *operandType, SpvOpAny, out); + case Operator::Kind::GT: + SkASSERT(resultType.isBoolean()); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, + SpvOpFOrdGreaterThan, SpvOpSGreaterThan, + SpvOpUGreaterThan, SpvOpUndef, out); + case Operator::Kind::LT: + SkASSERT(resultType.isBoolean()); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFOrdLessThan, + SpvOpSLessThan, SpvOpULessThan, SpvOpUndef, out); + case Operator::Kind::GTEQ: + SkASSERT(resultType.isBoolean()); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, + SpvOpFOrdGreaterThanEqual, SpvOpSGreaterThanEqual, + SpvOpUGreaterThanEqual, SpvOpUndef, out); + case Operator::Kind::LTEQ: + SkASSERT(resultType.isBoolean()); + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, + SpvOpFOrdLessThanEqual, SpvOpSLessThanEqual, + SpvOpULessThanEqual, SpvOpUndef, out); + case Operator::Kind::PLUS: + if (leftType.isMatrix() && rightType.isMatrix()) { + SkASSERT(leftType.matches(rightType)); + return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFAdd, out); + } + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFAdd, + SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out); + case Operator::Kind::MINUS: + if (leftType.isMatrix() && rightType.isMatrix()) { + SkASSERT(leftType.matches(rightType)); + return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFSub, out); + } + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFSub, + SpvOpISub, SpvOpISub, SpvOpUndef, out); + case Operator::Kind::STAR: + if (leftType.isMatrix() && rightType.isMatrix()) { + // matrix multiply + SpvId result = this->nextId(&resultType); + this->writeInstruction(SpvOpMatrixTimesMatrix, this->getType(resultType), result, + lhs, rhs, out); + return result; + } + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMul, + SpvOpIMul, SpvOpIMul, SpvOpUndef, out); + case Operator::Kind::SLASH: + if (leftType.isMatrix() && rightType.isMatrix()) { + SkASSERT(leftType.matches(rightType)); + return this->writeComponentwiseMatrixBinary(leftType, lhs, rhs, SpvOpFDiv, out); + } + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFDiv, + SpvOpSDiv, SpvOpUDiv, SpvOpUndef, out); + case Operator::Kind::PERCENT: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMod, + SpvOpSMod, SpvOpUMod, SpvOpUndef, out); + case Operator::Kind::SHL: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpShiftLeftLogical, SpvOpShiftLeftLogical, + SpvOpUndef, out); + case Operator::Kind::SHR: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpShiftRightArithmetic, SpvOpShiftRightLogical, + SpvOpUndef, out); + case Operator::Kind::BITWISEAND: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpBitwiseAnd, SpvOpBitwiseAnd, SpvOpUndef, out); + case Operator::Kind::BITWISEOR: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpBitwiseOr, SpvOpBitwiseOr, SpvOpUndef, out); + case Operator::Kind::BITWISEXOR: + return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpUndef, + SpvOpBitwiseXor, SpvOpBitwiseXor, SpvOpUndef, out); + default: + fContext.fErrors->error(Position(), "unsupported token"); + return NA; + } +} + +SpvId SPIRVCodeGenerator::writeArrayComparison(const Type& arrayType, SpvId lhs, Operator op, + SpvId rhs, OutputStream& out) { + // The inputs must be arrays, and the op must be == or !=. + SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ); + SkASSERT(arrayType.isArray()); + const Type& componentType = arrayType.componentType(); + const int arraySize = arrayType.columns(); + SkASSERT(arraySize > 0); + + // Synthesize equality checks for each item in the array. + const Type& boolType = *fContext.fTypes.fBool; + SpvId allComparisons = NA; + for (int index = 0; index < arraySize; ++index) { + // Get the left and right item in the array. + SpvId itemL = this->writeOpCompositeExtract(componentType, lhs, index, out); + SpvId itemR = this->writeOpCompositeExtract(componentType, rhs, index, out); + // Use `writeBinaryExpression` with the requested == or != operator on these items. + SpvId comparison = this->writeBinaryExpression(componentType, itemL, op, + componentType, itemR, boolType, out); + // Merge this comparison result with all the other comparisons we've done. + allComparisons = this->mergeComparisons(comparison, allComparisons, op, out); + } + return allComparisons; +} + +SpvId SPIRVCodeGenerator::writeStructComparison(const Type& structType, SpvId lhs, Operator op, + SpvId rhs, OutputStream& out) { + // The inputs must be structs containing fields, and the op must be == or !=. + SkASSERT(op.kind() == Operator::Kind::EQEQ || op.kind() == Operator::Kind::NEQ); + SkASSERT(structType.isStruct()); + const std::vector<Type::Field>& fields = structType.fields(); + SkASSERT(!fields.empty()); + + // Synthesize equality checks for each field in the struct. + const Type& boolType = *fContext.fTypes.fBool; + SpvId allComparisons = NA; + for (int index = 0; index < (int)fields.size(); ++index) { + // Get the left and right versions of this field. + const Type& fieldType = *fields[index].fType; + + SpvId fieldL = this->writeOpCompositeExtract(fieldType, lhs, index, out); + SpvId fieldR = this->writeOpCompositeExtract(fieldType, rhs, index, out); + // Use `writeBinaryExpression` with the requested == or != operator on these fields. + SpvId comparison = this->writeBinaryExpression(fieldType, fieldL, op, fieldType, fieldR, + boolType, out); + // Merge this comparison result with all the other comparisons we've done. + allComparisons = this->mergeComparisons(comparison, allComparisons, op, out); + } + return allComparisons; +} + +SpvId SPIRVCodeGenerator::mergeComparisons(SpvId comparison, SpvId allComparisons, Operator op, + OutputStream& out) { + // If this is the first entry, we don't need to merge comparison results with anything. + if (allComparisons == NA) { + return comparison; + } + // Use LogicalAnd or LogicalOr to combine the comparison with all the other comparisons. + const Type& boolType = *fContext.fTypes.fBool; + SpvId boolTypeId = this->getType(boolType); + SpvId logicalOp = this->nextId(&boolType); + switch (op.kind()) { + case Operator::Kind::EQEQ: + this->writeInstruction(SpvOpLogicalAnd, boolTypeId, logicalOp, + comparison, allComparisons, out); + break; + case Operator::Kind::NEQ: + this->writeInstruction(SpvOpLogicalOr, boolTypeId, logicalOp, + comparison, allComparisons, out); + break; + default: + SkDEBUGFAILF("mergeComparisons only supports == and !=, not %s", op.operatorName()); + return NA; + } + return logicalOp; +} + +SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, OutputStream& out) { + const Expression* left = b.left().get(); + const Expression* right = b.right().get(); + Operator op = b.getOperator(); + + switch (op.kind()) { + case Operator::Kind::EQ: { + // Handles assignment. + SpvId rhs = this->writeExpression(*right, out); + this->getLValue(*left, out)->store(rhs, out); + return rhs; + } + case Operator::Kind::LOGICALAND: + // Handles short-circuiting; we don't necessarily evaluate both LHS and RHS. + return this->writeLogicalAnd(*b.left(), *b.right(), out); + + case Operator::Kind::LOGICALOR: + // Handles short-circuiting; we don't necessarily evaluate both LHS and RHS. + return this->writeLogicalOr(*b.left(), *b.right(), out); + + default: + break; + } + + std::unique_ptr<LValue> lvalue; + SpvId lhs; + if (op.isAssignment()) { + lvalue = this->getLValue(*left, out); + lhs = lvalue->load(out); + } else { + lvalue = nullptr; + lhs = this->writeExpression(*left, out); + } + + SpvId rhs = this->writeExpression(*right, out); + SpvId result = this->writeBinaryExpression(left->type(), lhs, op.removeAssignment(), + right->type(), rhs, b.type(), out); + if (lvalue) { + lvalue->store(result, out); + } + return result; +} + +SpvId SPIRVCodeGenerator::writeLogicalAnd(const Expression& left, const Expression& right, + OutputStream& out) { + SpvId falseConstant = this->writeLiteral(0.0, *fContext.fTypes.fBool); + SpvId lhs = this->writeExpression(left, out); + + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + SpvId rhsLabel = this->nextId(nullptr); + SpvId end = this->nextId(nullptr); + SpvId lhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, lhs, rhsLabel, end, out); + this->writeLabel(rhsLabel, kBranchIsOnPreviousLine, out); + SpvId rhs = this->writeExpression(right, out); + SpvId rhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(end, kBranchIsAbove, conditionalOps, out); + SpvId result = this->nextId(nullptr); + this->writeInstruction(SpvOpPhi, this->getType(*fContext.fTypes.fBool), result, falseConstant, + lhsBlock, rhs, rhsBlock, out); + + return result; +} + +SpvId SPIRVCodeGenerator::writeLogicalOr(const Expression& left, const Expression& right, + OutputStream& out) { + SpvId trueConstant = this->writeLiteral(1.0, *fContext.fTypes.fBool); + SpvId lhs = this->writeExpression(left, out); + + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + SpvId rhsLabel = this->nextId(nullptr); + SpvId end = this->nextId(nullptr); + SpvId lhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, lhs, end, rhsLabel, out); + this->writeLabel(rhsLabel, kBranchIsOnPreviousLine, out); + SpvId rhs = this->writeExpression(right, out); + SpvId rhsBlock = fCurrentBlock; + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(end, kBranchIsAbove, conditionalOps, out); + SpvId result = this->nextId(nullptr); + this->writeInstruction(SpvOpPhi, this->getType(*fContext.fTypes.fBool), result, trueConstant, + lhsBlock, rhs, rhsBlock, out); + + return result; +} + +SpvId SPIRVCodeGenerator::writeTernaryExpression(const TernaryExpression& t, OutputStream& out) { + const Type& type = t.type(); + SpvId test = this->writeExpression(*t.test(), out); + if (t.ifTrue()->type().columns() == 1 && + Analysis::IsCompileTimeConstant(*t.ifTrue()) && + Analysis::IsCompileTimeConstant(*t.ifFalse())) { + // both true and false are constants, can just use OpSelect + SpvId result = this->nextId(nullptr); + SpvId trueId = this->writeExpression(*t.ifTrue(), out); + SpvId falseId = this->writeExpression(*t.ifFalse(), out); + this->writeInstruction(SpvOpSelect, this->getType(type), result, test, trueId, falseId, + out); + return result; + } + + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + // was originally using OpPhi to choose the result, but for some reason that is crashing on + // Adreno. Switched to storing the result in a temp variable as glslang does. + SpvId var = this->nextId(nullptr); + this->writeInstruction(SpvOpVariable, this->getPointerType(type, SpvStorageClassFunction), + var, SpvStorageClassFunction, fVariableBuffer); + SpvId trueLabel = this->nextId(nullptr); + SpvId falseLabel = this->nextId(nullptr); + SpvId end = this->nextId(nullptr); + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, test, trueLabel, falseLabel, out); + this->writeLabel(trueLabel, kBranchIsOnPreviousLine, out); + this->writeOpStore(SpvStorageClassFunction, var, this->writeExpression(*t.ifTrue(), out), out); + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(falseLabel, kBranchIsAbove, conditionalOps, out); + this->writeOpStore(SpvStorageClassFunction, var, this->writeExpression(*t.ifFalse(), out), out); + this->writeInstruction(SpvOpBranch, end, out); + this->writeLabel(end, kBranchIsAbove, conditionalOps, out); + SpvId result = this->nextId(&type); + this->writeInstruction(SpvOpLoad, this->getType(type), result, var, out); + + return result; +} + +SpvId SPIRVCodeGenerator::writePrefixExpression(const PrefixExpression& p, OutputStream& out) { + const Type& type = p.type(); + if (p.getOperator().kind() == Operator::Kind::MINUS) { + SpvOp_ negateOp = pick_by_type(type, SpvOpFNegate, SpvOpSNegate, SpvOpSNegate, SpvOpUndef); + SkASSERT(negateOp != SpvOpUndef); + SpvId expr = this->writeExpression(*p.operand(), out); + if (type.isMatrix()) { + return this->writeComponentwiseMatrixUnary(type, expr, negateOp, out); + } + SpvId result = this->nextId(&type); + SpvId typeId = this->getType(type); + this->writeInstruction(negateOp, typeId, result, expr, out); + return result; + } + switch (p.getOperator().kind()) { + case Operator::Kind::PLUS: + return this->writeExpression(*p.operand(), out); + case Operator::Kind::PLUSPLUS: { + std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out); + SpvId one = this->writeLiteral(1.0, type); + SpvId result = this->writeBinaryOperation(type, type, lv->load(out), one, + SpvOpFAdd, SpvOpIAdd, SpvOpIAdd, SpvOpUndef, + out); + lv->store(result, out); + return result; + } + case Operator::Kind::MINUSMINUS: { + std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out); + SpvId one = this->writeLiteral(1.0, type); + SpvId result = this->writeBinaryOperation(type, type, lv->load(out), one, SpvOpFSub, + SpvOpISub, SpvOpISub, SpvOpUndef, out); + lv->store(result, out); + return result; + } + case Operator::Kind::LOGICALNOT: { + SkASSERT(p.operand()->type().isBoolean()); + SpvId result = this->nextId(nullptr); + this->writeInstruction(SpvOpLogicalNot, this->getType(type), result, + this->writeExpression(*p.operand(), out), out); + return result; + } + case Operator::Kind::BITWISENOT: { + SpvId result = this->nextId(nullptr); + this->writeInstruction(SpvOpNot, this->getType(type), result, + this->writeExpression(*p.operand(), out), out); + return result; + } + default: + SkDEBUGFAILF("unsupported prefix expression: %s", + p.description(OperatorPrecedence::kTopLevel).c_str()); + return NA; + } +} + +SpvId SPIRVCodeGenerator::writePostfixExpression(const PostfixExpression& p, OutputStream& out) { + const Type& type = p.type(); + std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out); + SpvId result = lv->load(out); + SpvId one = this->writeLiteral(1.0, type); + switch (p.getOperator().kind()) { + case Operator::Kind::PLUSPLUS: { + SpvId temp = this->writeBinaryOperation(type, type, result, one, SpvOpFAdd, + SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out); + lv->store(temp, out); + return result; + } + case Operator::Kind::MINUSMINUS: { + SpvId temp = this->writeBinaryOperation(type, type, result, one, SpvOpFSub, + SpvOpISub, SpvOpISub, SpvOpUndef, out); + lv->store(temp, out); + return result; + } + default: + SkDEBUGFAILF("unsupported postfix expression %s", + p.description(OperatorPrecedence::kTopLevel).c_str()); + return NA; + } +} + +SpvId SPIRVCodeGenerator::writeLiteral(const Literal& l) { + return this->writeLiteral(l.value(), l.type()); +} + +SpvId SPIRVCodeGenerator::writeLiteral(double value, const Type& type) { + switch (type.numberKind()) { + case Type::NumberKind::kFloat: { + float floatVal = value; + int32_t valueBits; + memcpy(&valueBits, &floatVal, sizeof(valueBits)); + return this->writeOpConstant(type, valueBits); + } + case Type::NumberKind::kBoolean: { + return value ? this->writeOpConstantTrue(type) + : this->writeOpConstantFalse(type); + } + default: { + return this->writeOpConstant(type, (SKSL_INT)value); + } + } +} + +SpvId SPIRVCodeGenerator::writeFunctionStart(const FunctionDeclaration& f, OutputStream& out) { + SpvId result = fFunctionMap[&f]; + SpvId returnTypeId = this->getType(f.returnType()); + SpvId functionTypeId = this->getFunctionType(f); + this->writeInstruction(SpvOpFunction, returnTypeId, result, + SpvFunctionControlMaskNone, functionTypeId, out); + std::string mangledName = f.mangledName(); + this->writeInstruction(SpvOpName, + result, + std::string_view(mangledName.c_str(), mangledName.size()), + fNameBuffer); + for (const Variable* parameter : f.parameters()) { + if (parameter->type().typeKind() == Type::TypeKind::kSampler && + fProgram.fConfig->fSettings.fSPIRVDawnCompatMode) { + auto [texture, sampler] = this->synthesizeTextureAndSampler(*parameter); + + SpvId textureId = this->nextId(nullptr); + SpvId samplerId = this->nextId(nullptr); + fVariableMap.set(texture, textureId); + fVariableMap.set(sampler, samplerId); + + SpvId textureType = this->getFunctionParameterType(texture->type()); + SpvId samplerType = this->getFunctionParameterType(sampler->type()); + + this->writeInstruction(SpvOpFunctionParameter, textureType, textureId, out); + this->writeInstruction(SpvOpFunctionParameter, samplerType, samplerId, out); + } else { + SpvId id = this->nextId(nullptr); + fVariableMap.set(parameter, id); + + SpvId type = this->getFunctionParameterType(parameter->type()); + this->writeInstruction(SpvOpFunctionParameter, type, id, out); + } + } + return result; +} + +SpvId SPIRVCodeGenerator::writeFunction(const FunctionDefinition& f, OutputStream& out) { + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + fVariableBuffer.reset(); + SpvId result = this->writeFunctionStart(f.declaration(), out); + fCurrentBlock = 0; + this->writeLabel(this->nextId(nullptr), kBranchlessBlock, out); + StringStream bodyBuffer; + this->writeBlock(f.body()->as<Block>(), bodyBuffer); + write_stringstream(fVariableBuffer, out); + if (f.declaration().isMain()) { + write_stringstream(fGlobalInitializersBuffer, out); + } + write_stringstream(bodyBuffer, out); + if (fCurrentBlock) { + if (f.declaration().returnType().isVoid()) { + this->writeInstruction(SpvOpReturn, out); + } else { + this->writeInstruction(SpvOpUnreachable, out); + } + } + this->writeInstruction(SpvOpFunctionEnd, out); + this->pruneConditionalOps(conditionalOps); + return result; +} + +void SPIRVCodeGenerator::writeLayout(const Layout& layout, SpvId target, Position pos) { + bool isPushConstant = (layout.fFlags & Layout::kPushConstant_Flag); + if (layout.fLocation >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationLocation, layout.fLocation, + fDecorationBuffer); + } + if (layout.fBinding >= 0) { + if (isPushConstant) { + fContext.fErrors->error(pos, "Can't apply 'binding' to push constants"); + } else { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationBinding, layout.fBinding, + fDecorationBuffer); + } + } + if (layout.fIndex >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationIndex, layout.fIndex, + fDecorationBuffer); + } + if (layout.fSet >= 0) { + if (isPushConstant) { + fContext.fErrors->error(pos, "Can't apply 'set' to push constants"); + } else { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationDescriptorSet, layout.fSet, + fDecorationBuffer); + } + } + if (layout.fInputAttachmentIndex >= 0) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationInputAttachmentIndex, + layout.fInputAttachmentIndex, fDecorationBuffer); + fCapabilities |= (((uint64_t) 1) << SpvCapabilityInputAttachment); + } + if (layout.fBuiltin >= 0 && layout.fBuiltin != SK_FRAGCOLOR_BUILTIN) { + this->writeInstruction(SpvOpDecorate, target, SpvDecorationBuiltIn, layout.fBuiltin, + fDecorationBuffer); + } +} + +void SPIRVCodeGenerator::writeFieldLayout(const Layout& layout, SpvId target, int member) { + // 'binding' and 'set' can not be applied to struct members + SkASSERT(layout.fBinding == -1); + SkASSERT(layout.fSet == -1); + if (layout.fLocation >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationLocation, + layout.fLocation, fDecorationBuffer); + } + if (layout.fIndex >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationIndex, + layout.fIndex, fDecorationBuffer); + } + if (layout.fInputAttachmentIndex >= 0) { + this->writeInstruction(SpvOpDecorate, target, member, SpvDecorationInputAttachmentIndex, + layout.fInputAttachmentIndex, fDecorationBuffer); + } + if (layout.fBuiltin >= 0) { + this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationBuiltIn, + layout.fBuiltin, fDecorationBuffer); + } +} + +MemoryLayout SPIRVCodeGenerator::memoryLayoutForStorageClass(SpvStorageClass_ storageClass) { + return storageClass == SpvStorageClassPushConstant ? MemoryLayout(MemoryLayout::Standard::k430) + : fDefaultLayout; +} + +MemoryLayout SPIRVCodeGenerator::memoryLayoutForVariable(const Variable& v) const { + bool pushConstant = ((v.modifiers().fLayout.fFlags & Layout::kPushConstant_Flag) != 0); + return pushConstant ? MemoryLayout(MemoryLayout::Standard::k430) : fDefaultLayout; +} + +SpvId SPIRVCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip) { + MemoryLayout memoryLayout = this->memoryLayoutForVariable(*intf.var()); + SpvId result = this->nextId(nullptr); + const Variable& intfVar = *intf.var(); + const Type& type = intfVar.type(); + if (!memoryLayout.isSupported(type)) { + fContext.fErrors->error(type.fPosition, "type '" + type.displayName() + + "' is not permitted here"); + return this->nextId(nullptr); + } + SpvStorageClass_ storageClass = + get_storage_class_for_global_variable(intfVar, SpvStorageClassFunction); + if (fProgram.fInputs.fUseFlipRTUniform && appendRTFlip && type.isStruct()) { + // We can only have one interface block (because we use push_constant and that is limited + // to one per program), so we need to append rtflip to this one rather than synthesize an + // entirely new block when the variable is referenced. And we can't modify the existing + // block, so we instead create a modified copy of it and write that. + std::vector<Type::Field> fields = type.fields(); + fields.emplace_back(Position(), + Modifiers(Layout(/*flags=*/0, + /*location=*/-1, + fProgram.fConfig->fSettings.fRTFlipOffset, + /*binding=*/-1, + /*index=*/-1, + /*set=*/-1, + /*builtin=*/-1, + /*inputAttachmentIndex=*/-1), + /*flags=*/0), + SKSL_RTFLIP_NAME, + fContext.fTypes.fFloat2.get()); + { + AutoAttachPoolToThread attach(fProgram.fPool.get()); + const Type* rtFlipStructType = fProgram.fSymbols->takeOwnershipOfSymbol( + Type::MakeStructType(fContext, + type.fPosition, + type.name(), + std::move(fields), + /*interfaceBlock=*/true)); + InterfaceBlockVariable* modifiedVar = fProgram.fSymbols->takeOwnershipOfSymbol( + std::make_unique<InterfaceBlockVariable>(intfVar.fPosition, + intfVar.modifiersPosition(), + &intfVar.modifiers(), + intfVar.name(), + rtFlipStructType, + intfVar.isBuiltin(), + intfVar.storage())); + fSPIRVBonusVariables.add(modifiedVar); + InterfaceBlock modifiedCopy(intf.fPosition, modifiedVar, intf.typeOwner()); + result = this->writeInterfaceBlock(modifiedCopy, /*appendRTFlip=*/false); + fProgram.fSymbols->add(std::make_unique<Field>( + Position(), modifiedVar, rtFlipStructType->fields().size() - 1)); + } + fVariableMap.set(&intfVar, result); + fWroteRTFlip = true; + return result; + } + const Modifiers& intfModifiers = intfVar.modifiers(); + SpvId typeId = this->getType(type, memoryLayout); + if (intfModifiers.fLayout.fBuiltin == -1) { + // Note: In SPIR-V 1.3, a storage buffer can be declared with the "StorageBuffer" + // storage class and the "Block" decoration and the <1.3 approach we use here ("Uniform" + // storage class and the "BufferBlock" decoration) is deprecated. Since we target SPIR-V + // 1.0, we have to use the deprecated approach which is well supported in Vulkan and + // addresses SkSL use cases (notably SkSL currently doesn't support pointer features that + // would benefit from SPV_KHR_variable_pointers capabilities). + bool isStorageBuffer = intfModifiers.fFlags & Modifiers::kBuffer_Flag; + this->writeInstruction(SpvOpDecorate, + typeId, + isStorageBuffer ? SpvDecorationBufferBlock : SpvDecorationBlock, + fDecorationBuffer); + } + SpvId ptrType = this->nextId(nullptr); + this->writeInstruction(SpvOpTypePointer, ptrType, storageClass, typeId, fConstantBuffer); + this->writeInstruction(SpvOpVariable, ptrType, result, storageClass, fConstantBuffer); + Layout layout = intfModifiers.fLayout; + if (storageClass == SpvStorageClassUniform && layout.fSet < 0) { + layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet; + } + this->writeLayout(layout, result, intfVar.fPosition); + fVariableMap.set(&intfVar, result); + return result; +} + +bool SPIRVCodeGenerator::isDead(const Variable& var) const { + // During SPIR-V code generation, we synthesize some extra bonus variables that don't actually + // exist in the Program at all and aren't tracked by the ProgramUsage. They aren't dead, though. + if (fSPIRVBonusVariables.contains(&var)) { + return false; + } + ProgramUsage::VariableCounts counts = fProgram.usage()->get(var); + if (counts.fRead || counts.fWrite) { + return false; + } + // It's not entirely clear what the rules are for eliding interface variables. Generally, it + // causes problems to elide them, even when they're dead. + return !(var.modifiers().fFlags & + (Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag)); +} + +// This function determines whether to skip an OpVariable (of pointer type) declaration for +// compile-time constant scalars and vectors which we turn into OpConstant/OpConstantComposite and +// always reference by value. +// +// Accessing a matrix or array member with a dynamic index requires the use of OpAccessChain which +// requires a base operand of pointer type. However, a vector can always be accessed by value using +// OpVectorExtractDynamic (see writeIndexExpression). +// +// This is why we always emit an OpVariable for all non-scalar and non-vector types in case they get +// accessed via a dynamic index. +static bool is_vardecl_compile_time_constant(const VarDeclaration& varDecl) { + return varDecl.var()->modifiers().fFlags & Modifiers::kConst_Flag && + (varDecl.var()->type().isScalar() || varDecl.var()->type().isVector()) && + (ConstantFolder::GetConstantValueOrNullForVariable(*varDecl.value()) || + Analysis::IsCompileTimeConstant(*varDecl.value())); +} + +bool SPIRVCodeGenerator::writeGlobalVarDeclaration(ProgramKind kind, + const VarDeclaration& varDecl) { + const Variable* var = varDecl.var(); + const bool inDawnMode = fProgram.fConfig->fSettings.fSPIRVDawnCompatMode; + const int backendFlags = var->modifiers().fLayout.fFlags & Layout::kAllBackendFlagsMask; + const int permittedBackendFlags = Layout::kSPIRV_Flag | (inDawnMode ? Layout::kWGSL_Flag : 0); + if (backendFlags & ~permittedBackendFlags) { + fContext.fErrors->error(var->fPosition, "incompatible backend flag in SPIR-V codegen"); + return false; + } + + // If this global variable is a compile-time constant then we'll emit OpConstant or + // OpConstantComposite later when the variable is referenced. Avoid declaring an OpVariable now. + if (is_vardecl_compile_time_constant(varDecl)) { + return true; + } + + SpvStorageClass_ storageClass = + get_storage_class_for_global_variable(*var, SpvStorageClassPrivate); + if (storageClass == SpvStorageClassUniform) { + // Top-level uniforms are emitted in writeUniformBuffer. + fTopLevelUniforms.push_back(&varDecl); + return true; + } + + if (this->isDead(*var)) { + return true; + } + + if (var->type().typeKind() == Type::TypeKind::kSampler && inDawnMode) { + if (var->modifiers().fLayout.fTexture == -1 || var->modifiers().fLayout.fSampler == -1 || + !(var->modifiers().fLayout.fFlags & Layout::kWGSL_Flag)) { + fContext.fErrors->error(var->fPosition, + "SPIR-V dawn compatibility mode requires an explicit texture " + "and sampler index"); + return false; + } + SkASSERT(storageClass == SpvStorageClassUniformConstant); + + auto [texture, sampler] = this->synthesizeTextureAndSampler(*var); + this->writeGlobalVar(kind, storageClass, *texture); + this->writeGlobalVar(kind, storageClass, *sampler); + + return true; + } + + SpvId id = this->writeGlobalVar(kind, storageClass, *var); + if (id != NA && varDecl.value()) { + SkASSERT(!fCurrentBlock); + fCurrentBlock = NA; + SpvId value = this->writeExpression(*varDecl.value(), fGlobalInitializersBuffer); + this->writeOpStore(storageClass, id, value, fGlobalInitializersBuffer); + fCurrentBlock = 0; + } + return true; +} + +SpvId SPIRVCodeGenerator::writeGlobalVar(ProgramKind kind, + SpvStorageClass_ storageClass, + const Variable& var) { + if (var.modifiers().fLayout.fBuiltin == SK_FRAGCOLOR_BUILTIN && + !ProgramConfig::IsFragment(kind)) { + SkASSERT(!fProgram.fConfig->fSettings.fFragColorIsInOut); + return NA; + } + + // Add this global to the variable map. + const Type& type = var.type(); + SpvId id = this->nextId(&type); + fVariableMap.set(&var, id); + + Layout layout = var.modifiers().fLayout; + if (layout.fSet < 0 && storageClass == SpvStorageClassUniformConstant) { + layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet; + } + + SpvId typeId = this->getPointerType(type, storageClass); + this->writeInstruction(SpvOpVariable, typeId, id, storageClass, fConstantBuffer); + this->writeInstruction(SpvOpName, id, var.name(), fNameBuffer); + this->writeLayout(layout, id, var.fPosition); + if (var.modifiers().fFlags & Modifiers::kFlat_Flag) { + this->writeInstruction(SpvOpDecorate, id, SpvDecorationFlat, fDecorationBuffer); + } + if (var.modifiers().fFlags & Modifiers::kNoPerspective_Flag) { + this->writeInstruction(SpvOpDecorate, id, SpvDecorationNoPerspective, + fDecorationBuffer); + } + + return id; +} + +void SPIRVCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl, OutputStream& out) { + // If this variable is a compile-time constant then we'll emit OpConstant or + // OpConstantComposite later when the variable is referenced. Avoid declaring an OpVariable now. + if (is_vardecl_compile_time_constant(varDecl)) { + return; + } + + const Variable* var = varDecl.var(); + SpvId id = this->nextId(&var->type()); + fVariableMap.set(var, id); + SpvId type = this->getPointerType(var->type(), SpvStorageClassFunction); + this->writeInstruction(SpvOpVariable, type, id, SpvStorageClassFunction, fVariableBuffer); + this->writeInstruction(SpvOpName, id, var->name(), fNameBuffer); + if (varDecl.value()) { + SpvId value = this->writeExpression(*varDecl.value(), out); + this->writeOpStore(SpvStorageClassFunction, id, value, out); + } +} + +void SPIRVCodeGenerator::writeStatement(const Statement& s, OutputStream& out) { + switch (s.kind()) { + case Statement::Kind::kNop: + break; + case Statement::Kind::kBlock: + this->writeBlock(s.as<Block>(), out); + break; + case Statement::Kind::kExpression: + this->writeExpression(*s.as<ExpressionStatement>().expression(), out); + break; + case Statement::Kind::kReturn: + this->writeReturnStatement(s.as<ReturnStatement>(), out); + break; + case Statement::Kind::kVarDeclaration: + this->writeVarDeclaration(s.as<VarDeclaration>(), out); + break; + case Statement::Kind::kIf: + this->writeIfStatement(s.as<IfStatement>(), out); + break; + case Statement::Kind::kFor: + this->writeForStatement(s.as<ForStatement>(), out); + break; + case Statement::Kind::kDo: + this->writeDoStatement(s.as<DoStatement>(), out); + break; + case Statement::Kind::kSwitch: + this->writeSwitchStatement(s.as<SwitchStatement>(), out); + break; + case Statement::Kind::kBreak: + this->writeInstruction(SpvOpBranch, fBreakTarget.back(), out); + break; + case Statement::Kind::kContinue: + this->writeInstruction(SpvOpBranch, fContinueTarget.back(), out); + break; + case Statement::Kind::kDiscard: + this->writeInstruction(SpvOpKill, out); + break; + default: + SkDEBUGFAILF("unsupported statement: %s", s.description().c_str()); + break; + } +} + +void SPIRVCodeGenerator::writeBlock(const Block& b, OutputStream& out) { + for (const std::unique_ptr<Statement>& stmt : b.children()) { + this->writeStatement(*stmt, out); + } +} + +SPIRVCodeGenerator::ConditionalOpCounts SPIRVCodeGenerator::getConditionalOpCounts() { + return {fReachableOps.size(), fStoreOps.size()}; +} + +void SPIRVCodeGenerator::pruneConditionalOps(ConditionalOpCounts ops) { + // Remove ops which are no longer reachable. + while (fReachableOps.size() > ops.numReachableOps) { + SpvId prunableSpvId = fReachableOps.back(); + const Instruction* prunableOp = fSpvIdCache.find(prunableSpvId); + + if (prunableOp) { + fOpCache.remove(*prunableOp); + fSpvIdCache.remove(prunableSpvId); + } else { + SkDEBUGFAIL("reachable-op list contains unrecognized SpvId"); + } + + fReachableOps.pop_back(); + } + + // Remove any cached stores that occurred during the conditional block. + while (fStoreOps.size() > ops.numStoreOps) { + if (fStoreCache.find(fStoreOps.back())) { + fStoreCache.remove(fStoreOps.back()); + } + fStoreOps.pop_back(); + } +} + +void SPIRVCodeGenerator::writeIfStatement(const IfStatement& stmt, OutputStream& out) { + SpvId test = this->writeExpression(*stmt.test(), out); + SpvId ifTrue = this->nextId(nullptr); + SpvId ifFalse = this->nextId(nullptr); + + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + if (stmt.ifFalse()) { + SpvId end = this->nextId(nullptr); + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out); + this->writeLabel(ifTrue, kBranchIsOnPreviousLine, out); + this->writeStatement(*stmt.ifTrue(), out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, end, out); + } + this->writeLabel(ifFalse, kBranchIsAbove, conditionalOps, out); + this->writeStatement(*stmt.ifFalse(), out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, end, out); + } + this->writeLabel(end, kBranchIsAbove, conditionalOps, out); + } else { + this->writeInstruction(SpvOpSelectionMerge, ifFalse, SpvSelectionControlMaskNone, out); + this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out); + this->writeLabel(ifTrue, kBranchIsOnPreviousLine, out); + this->writeStatement(*stmt.ifTrue(), out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, ifFalse, out); + } + this->writeLabel(ifFalse, kBranchIsAbove, conditionalOps, out); + } +} + +void SPIRVCodeGenerator::writeForStatement(const ForStatement& f, OutputStream& out) { + if (f.initializer()) { + this->writeStatement(*f.initializer(), out); + } + + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + // The store cache isn't trustworthy in the presence of branches; store caching only makes sense + // in the context of linear straight-line execution. If we wanted to be more clever, we could + // only invalidate store cache entries for variables affected by the loop body, but for now we + // simply clear the entire cache whenever branching occurs. + SpvId header = this->nextId(nullptr); + SpvId start = this->nextId(nullptr); + SpvId body = this->nextId(nullptr); + SpvId next = this->nextId(nullptr); + fContinueTarget.push_back(next); + SpvId end = this->nextId(nullptr); + fBreakTarget.push_back(end); + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(header, kBranchIsBelow, conditionalOps, out); + this->writeInstruction(SpvOpLoopMerge, end, next, SpvLoopControlMaskNone, out); + this->writeInstruction(SpvOpBranch, start, out); + this->writeLabel(start, kBranchIsOnPreviousLine, out); + if (f.test()) { + SpvId test = this->writeExpression(*f.test(), out); + this->writeInstruction(SpvOpBranchConditional, test, body, end, out); + } else { + this->writeInstruction(SpvOpBranch, body, out); + } + this->writeLabel(body, kBranchIsOnPreviousLine, out); + this->writeStatement(*f.statement(), out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, next, out); + } + this->writeLabel(next, kBranchIsAbove, conditionalOps, out); + if (f.next()) { + this->writeExpression(*f.next(), out); + } + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(end, kBranchIsAbove, conditionalOps, out); + fBreakTarget.pop_back(); + fContinueTarget.pop_back(); +} + +void SPIRVCodeGenerator::writeDoStatement(const DoStatement& d, OutputStream& out) { + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + // The store cache isn't trustworthy in the presence of branches; store caching only makes sense + // in the context of linear straight-line execution. If we wanted to be more clever, we could + // only invalidate store cache entries for variables affected by the loop body, but for now we + // simply clear the entire cache whenever branching occurs. + SpvId header = this->nextId(nullptr); + SpvId start = this->nextId(nullptr); + SpvId next = this->nextId(nullptr); + SpvId continueTarget = this->nextId(nullptr); + fContinueTarget.push_back(continueTarget); + SpvId end = this->nextId(nullptr); + fBreakTarget.push_back(end); + this->writeInstruction(SpvOpBranch, header, out); + this->writeLabel(header, kBranchIsBelow, conditionalOps, out); + this->writeInstruction(SpvOpLoopMerge, end, continueTarget, SpvLoopControlMaskNone, out); + this->writeInstruction(SpvOpBranch, start, out); + this->writeLabel(start, kBranchIsOnPreviousLine, out); + this->writeStatement(*d.statement(), out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, next, out); + this->writeLabel(next, kBranchIsOnPreviousLine, out); + this->writeInstruction(SpvOpBranch, continueTarget, out); + } + this->writeLabel(continueTarget, kBranchIsAbove, conditionalOps, out); + SpvId test = this->writeExpression(*d.test(), out); + this->writeInstruction(SpvOpBranchConditional, test, header, end, out); + this->writeLabel(end, kBranchIsAbove, conditionalOps, out); + fBreakTarget.pop_back(); + fContinueTarget.pop_back(); +} + +void SPIRVCodeGenerator::writeSwitchStatement(const SwitchStatement& s, OutputStream& out) { + SpvId value = this->writeExpression(*s.value(), out); + + ConditionalOpCounts conditionalOps = this->getConditionalOpCounts(); + + // The store cache isn't trustworthy in the presence of branches; store caching only makes sense + // in the context of linear straight-line execution. If we wanted to be more clever, we could + // only invalidate store cache entries for variables affected by the switch body, but for now we + // simply clear the entire cache whenever branching occurs. + SkTArray<SpvId> labels; + SpvId end = this->nextId(nullptr); + SpvId defaultLabel = end; + fBreakTarget.push_back(end); + int size = 3; + const StatementArray& cases = s.cases(); + for (const std::unique_ptr<Statement>& stmt : cases) { + const SwitchCase& c = stmt->as<SwitchCase>(); + SpvId label = this->nextId(nullptr); + labels.push_back(label); + if (!c.isDefault()) { + size += 2; + } else { + defaultLabel = label; + } + } + + // We should have exactly one label for each case. + SkASSERT(labels.size() == cases.size()); + + // Collapse adjacent switch-cases into one; that is, reduce `case 1: case 2: case 3:` into a + // single OpLabel. The Tint SPIR-V reader does not support switch-case fallthrough, but it + // does support multiple switch-cases branching to the same label. + SkBitSet caseIsCollapsed(cases.size()); + for (int index = cases.size() - 2; index >= 0; index--) { + if (cases[index]->as<SwitchCase>().statement()->isEmpty()) { + caseIsCollapsed.set(index); + labels[index] = labels[index + 1]; + } + } + + labels.push_back(end); + + this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out); + this->writeOpCode(SpvOpSwitch, size, out); + this->writeWord(value, out); + this->writeWord(defaultLabel, out); + for (int i = 0; i < cases.size(); ++i) { + const SwitchCase& c = cases[i]->as<SwitchCase>(); + if (c.isDefault()) { + continue; + } + this->writeWord(c.value(), out); + this->writeWord(labels[i], out); + } + for (int i = 0; i < cases.size(); ++i) { + if (caseIsCollapsed.test(i)) { + continue; + } + const SwitchCase& c = cases[i]->as<SwitchCase>(); + if (i == 0) { + this->writeLabel(labels[i], kBranchIsOnPreviousLine, out); + } else { + this->writeLabel(labels[i], kBranchIsAbove, conditionalOps, out); + } + this->writeStatement(*c.statement(), out); + if (fCurrentBlock) { + this->writeInstruction(SpvOpBranch, labels[i + 1], out); + } + } + this->writeLabel(end, kBranchIsAbove, conditionalOps, out); + fBreakTarget.pop_back(); +} + +void SPIRVCodeGenerator::writeReturnStatement(const ReturnStatement& r, OutputStream& out) { + if (r.expression()) { + this->writeInstruction(SpvOpReturnValue, this->writeExpression(*r.expression(), out), + out); + } else { + this->writeInstruction(SpvOpReturn, out); + } +} + +// Given any function, returns the top-level symbol table (OUTSIDE of the function's scope). +static std::shared_ptr<SymbolTable> get_top_level_symbol_table(const FunctionDeclaration& anyFunc) { + return anyFunc.definition()->body()->as<Block>().symbolTable()->fParent; +} + +SPIRVCodeGenerator::EntrypointAdapter SPIRVCodeGenerator::writeEntrypointAdapter( + const FunctionDeclaration& main) { + // Our goal is to synthesize a tiny helper function which looks like this: + // void _entrypoint() { sk_FragColor = main(); } + + // Fish a symbol table out of main(). + std::shared_ptr<SymbolTable> symbolTable = get_top_level_symbol_table(main); + + // Get `sk_FragColor` as a writable reference. + const Symbol* skFragColorSymbol = symbolTable->find("sk_FragColor"); + SkASSERT(skFragColorSymbol); + const Variable& skFragColorVar = skFragColorSymbol->as<Variable>(); + auto skFragColorRef = std::make_unique<VariableReference>(Position(), &skFragColorVar, + VariableReference::RefKind::kWrite); + // Synthesize a call to the `main()` function. + if (!main.returnType().matches(skFragColorRef->type())) { + fContext.fErrors->error(main.fPosition, "SPIR-V does not support returning '" + + main.returnType().description() + "' from main()"); + return {}; + } + ExpressionArray args; + if (main.parameters().size() == 1) { + if (!main.parameters()[0]->type().matches(*fContext.fTypes.fFloat2)) { + fContext.fErrors->error(main.fPosition, + "SPIR-V does not support parameter of type '" + + main.parameters()[0]->type().description() + "' to main()"); + return {}; + } + args.push_back(dsl::Float2(0).release()); + } + auto callMainFn = std::make_unique<FunctionCall>(Position(), &main.returnType(), &main, + std::move(args)); + + // Synthesize `skFragColor = main()` as a BinaryExpression. + auto assignmentStmt = std::make_unique<ExpressionStatement>(std::make_unique<BinaryExpression>( + Position(), + std::move(skFragColorRef), + Operator::Kind::EQ, + std::move(callMainFn), + &main.returnType())); + + // Function bodies are always wrapped in a Block. + StatementArray entrypointStmts; + entrypointStmts.push_back(std::move(assignmentStmt)); + auto entrypointBlock = Block::Make(Position(), std::move(entrypointStmts), + Block::Kind::kBracedScope, symbolTable); + // Declare an entrypoint function. + EntrypointAdapter adapter; + adapter.fLayout = {}; + adapter.fModifiers = Modifiers{adapter.fLayout, Modifiers::kNo_Flag}; + adapter.entrypointDecl = + std::make_unique<FunctionDeclaration>(Position(), + &adapter.fModifiers, + "_entrypoint", + /*parameters=*/std::vector<Variable*>{}, + /*returnType=*/fContext.fTypes.fVoid.get(), + /*builtin=*/false); + // Define it. + adapter.entrypointDef = FunctionDefinition::Convert(fContext, + Position(), + *adapter.entrypointDecl, + std::move(entrypointBlock), + /*builtin=*/false); + + adapter.entrypointDecl->setDefinition(adapter.entrypointDef.get()); + return adapter; +} + +void SPIRVCodeGenerator::writeUniformBuffer(std::shared_ptr<SymbolTable> topLevelSymbolTable) { + SkASSERT(!fTopLevelUniforms.empty()); + static constexpr char kUniformBufferName[] = "_UniformBuffer"; + + // Convert the list of top-level uniforms into a matching struct named _UniformBuffer, and build + // a lookup table of variables to UniformBuffer field indices. + std::vector<Type::Field> fields; + fields.reserve(fTopLevelUniforms.size()); + for (const VarDeclaration* topLevelUniform : fTopLevelUniforms) { + const Variable* var = topLevelUniform->var(); + fTopLevelUniformMap.set(var, (int)fields.size()); + Modifiers modifiers = var->modifiers(); + modifiers.fFlags &= ~Modifiers::kUniform_Flag; + fields.emplace_back(var->fPosition, modifiers, var->name(), &var->type()); + } + fUniformBuffer.fStruct = Type::MakeStructType(fContext, + Position(), + kUniformBufferName, + std::move(fields), + /*interfaceBlock=*/true); + + // Create a global variable to contain this struct. + Layout layout; + layout.fBinding = fProgram.fConfig->fSettings.fDefaultUniformBinding; + layout.fSet = fProgram.fConfig->fSettings.fDefaultUniformSet; + Modifiers modifiers{layout, Modifiers::kUniform_Flag}; + + fUniformBuffer.fInnerVariable = std::make_unique<InterfaceBlockVariable>( + /*pos=*/Position(), /*modifiersPosition=*/Position(), + fContext.fModifiersPool->add(modifiers), kUniformBufferName, + fUniformBuffer.fStruct.get(), /*builtin=*/false, Variable::Storage::kGlobal); + + // Create an interface block object for this global variable. + fUniformBuffer.fInterfaceBlock = + std::make_unique<InterfaceBlock>(Position(), + fUniformBuffer.fInnerVariable.get(), + topLevelSymbolTable); + + // Generate an interface block and hold onto its ID. + fUniformBufferId = this->writeInterfaceBlock(*fUniformBuffer.fInterfaceBlock); +} + +void SPIRVCodeGenerator::addRTFlipUniform(Position pos) { + SkASSERT(!fProgram.fConfig->fSettings.fForceNoRTFlip); + + if (fWroteRTFlip) { + return; + } + // Flip variable hasn't been written yet. This means we don't have an existing + // interface block, so we're free to just synthesize one. + fWroteRTFlip = true; + std::vector<Type::Field> fields; + if (fProgram.fConfig->fSettings.fRTFlipOffset < 0) { + fContext.fErrors->error(pos, "RTFlipOffset is negative"); + } + fields.emplace_back(pos, + Modifiers(Layout(/*flags=*/0, + /*location=*/-1, + fProgram.fConfig->fSettings.fRTFlipOffset, + /*binding=*/-1, + /*index=*/-1, + /*set=*/-1, + /*builtin=*/-1, + /*inputAttachmentIndex=*/-1), + /*flags=*/0), + SKSL_RTFLIP_NAME, + fContext.fTypes.fFloat2.get()); + std::string_view name = "sksl_synthetic_uniforms"; + const Type* intfStruct = fSynthetics.takeOwnershipOfSymbol( + Type::MakeStructType(fContext, Position(), name, fields, /*interfaceBlock=*/true)); + bool usePushConstants = fProgram.fConfig->fSettings.fUsePushConstants; + int binding = -1, set = -1; + if (!usePushConstants) { + binding = fProgram.fConfig->fSettings.fRTFlipBinding; + if (binding == -1) { + fContext.fErrors->error(pos, "layout(binding=...) is required in SPIR-V"); + } + set = fProgram.fConfig->fSettings.fRTFlipSet; + if (set == -1) { + fContext.fErrors->error(pos, "layout(set=...) is required in SPIR-V"); + } + } + int flags = usePushConstants ? Layout::Flag::kPushConstant_Flag : 0; + const Modifiers* modsPtr; + { + AutoAttachPoolToThread attach(fProgram.fPool.get()); + Modifiers modifiers(Layout(flags, + /*location=*/-1, + /*offset=*/-1, + binding, + /*index=*/-1, + set, + /*builtin=*/-1, + /*inputAttachmentIndex=*/-1), + Modifiers::kUniform_Flag); + modsPtr = fContext.fModifiersPool->add(modifiers); + } + InterfaceBlockVariable* intfVar = fSynthetics.takeOwnershipOfSymbol( + std::make_unique<InterfaceBlockVariable>(/*pos=*/Position(), + /*modifiersPosition=*/Position(), + modsPtr, + name, + intfStruct, + /*builtin=*/false, + Variable::Storage::kGlobal)); + fSPIRVBonusVariables.add(intfVar); + { + AutoAttachPoolToThread attach(fProgram.fPool.get()); + fProgram.fSymbols->add(std::make_unique<Field>(Position(), intfVar, /*field=*/0)); + } + InterfaceBlock intf(Position(), intfVar, std::make_shared<SymbolTable>(/*builtin=*/false)); + this->writeInterfaceBlock(intf, false); +} + +std::tuple<const Variable*, const Variable*> SPIRVCodeGenerator::synthesizeTextureAndSampler( + const Variable& combinedSampler) { + SkASSERT(fProgram.fConfig->fSettings.fSPIRVDawnCompatMode); + SkASSERT(combinedSampler.type().typeKind() == Type::TypeKind::kSampler); + + const Modifiers& modifiers = combinedSampler.modifiers(); + + auto data = std::make_unique<SynthesizedTextureSamplerPair>(); + + Modifiers texModifiers = modifiers; + texModifiers.fLayout.fBinding = modifiers.fLayout.fTexture; + data->fTextureName = std::string(combinedSampler.name()) + "_texture"; + auto texture = std::make_unique<Variable>(/*pos=*/Position(), + /*modifierPosition=*/Position(), + fContext.fModifiersPool->add(texModifiers), + data->fTextureName, + &combinedSampler.type().textureType(), + /*builtin=*/false, + Variable::Storage::kGlobal); + + Modifiers samplerModifiers = modifiers; + samplerModifiers.fLayout.fBinding = modifiers.fLayout.fSampler; + data->fSamplerName = std::string(combinedSampler.name()) + "_sampler"; + auto sampler = std::make_unique<Variable>(/*pos=*/Position(), + /*modifierPosition=*/Position(), + fContext.fModifiersPool->add(samplerModifiers), + data->fSamplerName, + fContext.fTypes.fSampler.get(), + /*builtin=*/false, + Variable::Storage::kGlobal); + + const Variable* t = texture.get(); + const Variable* s = sampler.get(); + data->fTexture = std::move(texture); + data->fSampler = std::move(sampler); + fSynthesizedSamplerMap.set(&combinedSampler, std::move(data)); + + return {t, s}; +} + +void SPIRVCodeGenerator::writeInstructions(const Program& program, OutputStream& out) { + fGLSLExtendedInstructions = this->nextId(nullptr); + StringStream body; + // Assign SpvIds to functions. + const FunctionDeclaration* main = nullptr; + for (const ProgramElement* e : program.elements()) { + if (e->is<FunctionDefinition>()) { + const FunctionDefinition& funcDef = e->as<FunctionDefinition>(); + const FunctionDeclaration& funcDecl = funcDef.declaration(); + fFunctionMap.set(&funcDecl, this->nextId(nullptr)); + if (funcDecl.isMain()) { + main = &funcDecl; + } + } + } + // Make sure we have a main() function. + if (!main) { + fContext.fErrors->error(Position(), "program does not contain a main() function"); + return; + } + // Emit interface blocks. + std::set<SpvId> interfaceVars; + for (const ProgramElement* e : program.elements()) { + if (e->is<InterfaceBlock>()) { + const InterfaceBlock& intf = e->as<InterfaceBlock>(); + SpvId id = this->writeInterfaceBlock(intf); + + const Modifiers& modifiers = intf.var()->modifiers(); + if ((modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) && + modifiers.fLayout.fBuiltin == -1 && !this->isDead(*intf.var())) { + interfaceVars.insert(id); + } + } + } + // Emit global variable declarations. + for (const ProgramElement* e : program.elements()) { + if (e->is<GlobalVarDeclaration>()) { + if (!this->writeGlobalVarDeclaration(program.fConfig->fKind, + e->as<GlobalVarDeclaration>().varDeclaration())) { + return; + } + } + } + // Emit top-level uniforms into a dedicated uniform buffer. + if (!fTopLevelUniforms.empty()) { + this->writeUniformBuffer(get_top_level_symbol_table(*main)); + } + // If main() returns a half4, synthesize a tiny entrypoint function which invokes the real + // main() and stores the result into sk_FragColor. + EntrypointAdapter adapter; + if (main->returnType().matches(*fContext.fTypes.fHalf4)) { + adapter = this->writeEntrypointAdapter(*main); + if (adapter.entrypointDecl) { + fFunctionMap.set(adapter.entrypointDecl.get(), this->nextId(nullptr)); + this->writeFunction(*adapter.entrypointDef, body); + main = adapter.entrypointDecl.get(); + } + } + // Emit all the functions. + for (const ProgramElement* e : program.elements()) { + if (e->is<FunctionDefinition>()) { + this->writeFunction(e->as<FunctionDefinition>(), body); + } + } + // Add global in/out variables to the list of interface variables. + for (const auto& [var, spvId] : fVariableMap) { + if (var->storage() == Variable::Storage::kGlobal && + (var->modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) && + !this->isDead(*var)) { + interfaceVars.insert(spvId); + } + } + this->writeCapabilities(out); + this->writeInstruction(SpvOpExtInstImport, fGLSLExtendedInstructions, "GLSL.std.450", out); + this->writeInstruction(SpvOpMemoryModel, SpvAddressingModelLogical, SpvMemoryModelGLSL450, out); + this->writeOpCode(SpvOpEntryPoint, (SpvId) (3 + (main->name().length() + 4) / 4) + + (int32_t) interfaceVars.size(), out); + if (ProgramConfig::IsVertex(program.fConfig->fKind)) { + this->writeWord(SpvExecutionModelVertex, out); + } else if (ProgramConfig::IsFragment(program.fConfig->fKind)) { + this->writeWord(SpvExecutionModelFragment, out); + } else { + SK_ABORT("cannot write this kind of program to SPIR-V\n"); + } + SpvId entryPoint = fFunctionMap[main]; + this->writeWord(entryPoint, out); + this->writeString(main->name(), out); + for (int var : interfaceVars) { + this->writeWord(var, out); + } + if (ProgramConfig::IsFragment(program.fConfig->fKind)) { + this->writeInstruction(SpvOpExecutionMode, + fFunctionMap[main], + SpvExecutionModeOriginUpperLeft, + out); + } + for (const ProgramElement* e : program.elements()) { + if (e->is<Extension>()) { + this->writeInstruction(SpvOpSourceExtension, e->as<Extension>().name(), out); + } + } + + write_stringstream(fNameBuffer, out); + write_stringstream(fDecorationBuffer, out); + write_stringstream(fConstantBuffer, out); + write_stringstream(body, out); +} + +bool SPIRVCodeGenerator::generateCode() { + SkASSERT(!fContext.fErrors->errorCount()); + this->writeWord(SpvMagicNumber, *fOut); + this->writeWord(SpvVersion, *fOut); + this->writeWord(SKSL_MAGIC, *fOut); + StringStream buffer; + this->writeInstructions(fProgram, buffer); + this->writeWord(fIdCount, *fOut); + this->writeWord(0, *fOut); // reserved, always zero + write_stringstream(buffer, *fOut); + return fContext.fErrors->errorCount() == 0; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.h new file mode 100644 index 0000000000..59fca83ddb --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVCodeGenerator.h @@ -0,0 +1,601 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SPIRVCODEGENERATOR +#define SKSL_SPIRVCODEGENERATOR + +#include "include/core/SkSpan.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/base/SkTArray.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLMemoryLayout.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/codegen/SkSLCodeGenerator.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/spirv.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <tuple> +#include <vector> + +namespace SkSL { + +class AnyConstructor; +class BinaryExpression; +class Block; +class ConstructorCompound; +class ConstructorCompoundCast; +class ConstructorDiagonalMatrix; +class ConstructorMatrixResize; +class ConstructorScalarCast; +class ConstructorSplat; +class Context; +class DoStatement; +class Expression; +class FieldAccess; +class ForStatement; +class FunctionCall; +class IfStatement; +class IndexExpression; +class Literal; +class Operator; +class OutputStream; +class Position; +class PostfixExpression; +class PrefixExpression; +class ProgramElement; +class ReturnStatement; +class Statement; +class SwitchStatement; +class Swizzle; +class TernaryExpression; +class VarDeclaration; +class VariableReference; +enum class ProgramKind : int8_t; +enum IntrinsicKind : int8_t; +struct Program; + +/** + * Converts a Program into a SPIR-V binary. + */ +class SPIRVCodeGenerator : public CodeGenerator { +public: + // We reserve an impossible SpvId as a sentinel. (NA meaning none, n/a, etc.) + static constexpr SpvId NA = (SpvId)-1; + + class LValue { + public: + virtual ~LValue() {} + + // returns a pointer to the lvalue, if possible. If the lvalue cannot be directly referenced + // by a pointer (e.g. vector swizzles), returns NA. + virtual SpvId getPointer() { return NA; } + + // Returns true if a valid pointer returned by getPointer represents a memory object + // (see https://github.com/KhronosGroup/SPIRV-Tools/issues/2892). Has no meaning if + // getPointer() returns NA. + virtual bool isMemoryObjectPointer() const { return true; } + + // Applies a swizzle to the components of the LValue, if possible. This is used to create + // LValues that are swizzes-of-swizzles. Non-swizzle LValues can just return false. + virtual bool applySwizzle(const ComponentArray& components, const Type& newType) { + return false; + } + + virtual SpvId load(OutputStream& out) = 0; + + virtual void store(SpvId value, OutputStream& out) = 0; + }; + + SPIRVCodeGenerator(const Context* context, const Program* program, OutputStream* out) + : INHERITED(context, program, out) + , fDefaultLayout(MemoryLayout::Standard::k140) + , fCapabilities(0) + , fIdCount(1) + , fCurrentBlock(0) + , fSynthetics(/*builtin=*/true) {} + + bool generateCode() override; + +private: + enum IntrinsicOpcodeKind { + kGLSL_STD_450_IntrinsicOpcodeKind, + kSPIRV_IntrinsicOpcodeKind, + kSpecial_IntrinsicOpcodeKind, + kInvalid_IntrinsicOpcodeKind, + }; + + enum SpecialIntrinsic { + kAtan_SpecialIntrinsic, + kClamp_SpecialIntrinsic, + kMatrixCompMult_SpecialIntrinsic, + kMax_SpecialIntrinsic, + kMin_SpecialIntrinsic, + kMix_SpecialIntrinsic, + kMod_SpecialIntrinsic, + kDFdy_SpecialIntrinsic, + kSaturate_SpecialIntrinsic, + kSampledImage_SpecialIntrinsic, + kSmoothStep_SpecialIntrinsic, + kStep_SpecialIntrinsic, + kSubpassLoad_SpecialIntrinsic, + kTexture_SpecialIntrinsic, + kTextureGrad_SpecialIntrinsic, + kTextureLod_SpecialIntrinsic, + }; + + enum class Precision { + kDefault, + kRelaxed, + }; + + struct TempVar { + SpvId spvId; + const Type* type; + std::unique_ptr<SPIRVCodeGenerator::LValue> lvalue; + }; + + /** + * Pass in the type to automatically add a RelaxedPrecision decoration for the id when + * appropriate, or null to never add one. + */ + SpvId nextId(const Type* type); + + SpvId nextId(Precision precision); + + SpvId getType(const Type& type); + + SpvId getType(const Type& type, const MemoryLayout& layout); + + SpvId getFunctionType(const FunctionDeclaration& function); + + SpvId getFunctionParameterType(const Type& parameterType); + + SpvId getPointerType(const Type& type, SpvStorageClass_ storageClass); + + SpvId getPointerType(const Type& type, const MemoryLayout& layout, + SpvStorageClass_ storageClass); + + SkTArray<SpvId> getAccessChain(const Expression& expr, OutputStream& out); + + void writeLayout(const Layout& layout, SpvId target, Position pos); + + void writeFieldLayout(const Layout& layout, SpvId target, int member); + + SpvId writeStruct(const Type& type, const MemoryLayout& memoryLayout); + + void writeProgramElement(const ProgramElement& pe, OutputStream& out); + + SpvId writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip = true); + + SpvId writeFunctionStart(const FunctionDeclaration& f, OutputStream& out); + + SpvId writeFunctionDeclaration(const FunctionDeclaration& f, OutputStream& out); + + SpvId writeFunction(const FunctionDefinition& f, OutputStream& out); + + bool writeGlobalVarDeclaration(ProgramKind kind, const VarDeclaration& v); + + SpvId writeGlobalVar(ProgramKind kind, SpvStorageClass_, const Variable& v); + + void writeVarDeclaration(const VarDeclaration& var, OutputStream& out); + + SpvId writeVariableReference(const VariableReference& ref, OutputStream& out); + + int findUniformFieldIndex(const Variable& var) const; + + std::unique_ptr<LValue> getLValue(const Expression& value, OutputStream& out); + + SpvId writeExpression(const Expression& expr, OutputStream& out); + + SpvId writeIntrinsicCall(const FunctionCall& c, OutputStream& out); + + SpvId writeFunctionCallArgument(const FunctionCall& call, + int argIndex, + std::vector<TempVar>* tempVars, + OutputStream& out, + SpvId* outSynthesizedSamplerId = nullptr); + + void copyBackTempVars(const std::vector<TempVar>& tempVars, OutputStream& out); + + SpvId writeFunctionCall(const FunctionCall& c, OutputStream& out); + + + void writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst, + SpvId signedInst, SpvId unsignedInst, + const SkTArray<SpvId>& args, OutputStream& out); + + /** + * Promotes an expression to a vector. If the expression is already a vector with vectorSize + * columns, returns it unmodified. If the expression is a scalar, either promotes it to a + * vector (if vectorSize > 1) or returns it unmodified (if vectorSize == 1). Asserts if the + * expression is already a vector and it does not have vectorSize columns. + */ + SpvId vectorize(const Expression& expr, int vectorSize, OutputStream& out); + + /** + * Given a list of potentially mixed scalars and vectors, promotes the scalars to match the + * size of the vectors and returns the ids of the written expressions. e.g. given (float, vec2), + * returns (vec2(float), vec2). It is an error to use mismatched vector sizes, e.g. (float, + * vec2, vec3). + */ + SkTArray<SpvId> vectorize(const ExpressionArray& args, OutputStream& out); + + SpvId writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind, OutputStream& out); + + SpvId writeScalarToMatrixSplat(const Type& matrixType, SpvId scalarId, OutputStream& out); + + SpvId writeFloatConstructor(const AnyConstructor& c, OutputStream& out); + + SpvId castScalarToFloat(SpvId inputId, const Type& inputType, const Type& outputType, + OutputStream& out); + + SpvId writeIntConstructor(const AnyConstructor& c, OutputStream& out); + + SpvId castScalarToSignedInt(SpvId inputId, const Type& inputType, const Type& outputType, + OutputStream& out); + + SpvId writeUIntConstructor(const AnyConstructor& c, OutputStream& out); + + SpvId castScalarToUnsignedInt(SpvId inputId, const Type& inputType, const Type& outputType, + OutputStream& out); + + SpvId writeBooleanConstructor(const AnyConstructor& c, OutputStream& out); + + SpvId castScalarToBoolean(SpvId inputId, const Type& inputType, const Type& outputType, + OutputStream& out); + + SpvId castScalarToType(SpvId inputExprId, const Type& inputType, const Type& outputType, + OutputStream& out); + + /** + * Writes a potentially-different-sized copy of a matrix. Entries which do not exist in the + * source matrix are filled with zero; entries which do not exist in the destination matrix are + * ignored. + */ + SpvId writeMatrixCopy(SpvId src, const Type& srcType, const Type& dstType, OutputStream& out); + + void addColumnEntry(const Type& columnType, SkTArray<SpvId>* currentColumn, + SkTArray<SpvId>* columnIds, int rows, SpvId entry, OutputStream& out); + + SpvId writeConstructorCompound(const ConstructorCompound& c, OutputStream& out); + + SpvId writeMatrixConstructor(const ConstructorCompound& c, OutputStream& out); + + SpvId writeVectorConstructor(const ConstructorCompound& c, OutputStream& out); + + SpvId writeCompositeConstructor(const AnyConstructor& c, OutputStream& out); + + SpvId writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, OutputStream& out); + + SpvId writeConstructorMatrixResize(const ConstructorMatrixResize& c, OutputStream& out); + + SpvId writeConstructorScalarCast(const ConstructorScalarCast& c, OutputStream& out); + + SpvId writeConstructorSplat(const ConstructorSplat& c, OutputStream& out); + + SpvId writeConstructorCompoundCast(const ConstructorCompoundCast& c, OutputStream& out); + + SpvId writeFieldAccess(const FieldAccess& f, OutputStream& out); + + SpvId writeSwizzle(const Swizzle& swizzle, OutputStream& out); + + /** + * Folds the potentially-vector result of a logical operation down to a single bool. If + * operandType is a vector type, assumes that the intermediate result in id is a bvec of the + * same dimensions, and applys all() to it to fold it down to a single bool value. Otherwise, + * returns the original id value. + */ + SpvId foldToBool(SpvId id, const Type& operandType, SpvOp op, OutputStream& out); + + SpvId writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs, SpvOp_ floatOperator, + SpvOp_ intOperator, SpvOp_ vectorMergeOperator, + SpvOp_ mergeOperator, OutputStream& out); + + SpvId writeStructComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs, + OutputStream& out); + + SpvId writeArrayComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs, + OutputStream& out); + + // Used by writeStructComparison and writeArrayComparison to logically combine field-by-field + // comparisons into an overall comparison result. + // - `a.x == b.x` merged with `a.y == b.y` generates `(a.x == b.x) && (a.y == b.y)` + // - `a.x != b.x` merged with `a.y != b.y` generates `(a.x != b.x) || (a.y != b.y)` + SpvId mergeComparisons(SpvId comparison, SpvId allComparisons, Operator op, OutputStream& out); + + SpvId writeComponentwiseMatrixUnary(const Type& operandType, + SpvId operand, + SpvOp_ op, + OutputStream& out); + + SpvId writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, SpvId rhs, + SpvOp_ op, OutputStream& out); + + SpvId writeBinaryOperation(const Type& resultType, const Type& operandType, SpvId lhs, + SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, SpvOp_ ifUInt, + SpvOp_ ifBool, OutputStream& out); + + SpvId writeReciprocal(const Type& type, SpvId value, OutputStream& out); + + SpvId writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op, + const Type& rightType, SpvId rhs, const Type& resultType, + OutputStream& out); + + SpvId writeBinaryExpression(const BinaryExpression& b, OutputStream& out); + + SpvId writeTernaryExpression(const TernaryExpression& t, OutputStream& out); + + SpvId writeIndexExpression(const IndexExpression& expr, OutputStream& out); + + SpvId writeLogicalAnd(const Expression& left, const Expression& right, OutputStream& out); + + SpvId writeLogicalOr(const Expression& left, const Expression& right, OutputStream& out); + + SpvId writePrefixExpression(const PrefixExpression& p, OutputStream& out); + + SpvId writePostfixExpression(const PostfixExpression& p, OutputStream& out); + + SpvId writeLiteral(const Literal& f); + + SpvId writeLiteral(double value, const Type& type); + + void writeStatement(const Statement& s, OutputStream& out); + + void writeBlock(const Block& b, OutputStream& out); + + void writeIfStatement(const IfStatement& stmt, OutputStream& out); + + void writeForStatement(const ForStatement& f, OutputStream& out); + + void writeDoStatement(const DoStatement& d, OutputStream& out); + + void writeSwitchStatement(const SwitchStatement& s, OutputStream& out); + + void writeReturnStatement(const ReturnStatement& r, OutputStream& out); + + void writeCapabilities(OutputStream& out); + + void writeInstructions(const Program& program, OutputStream& out); + + void writeOpCode(SpvOp_ opCode, int length, OutputStream& out); + + void writeWord(int32_t word, OutputStream& out); + + void writeString(std::string_view s, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, std::string_view string, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, std::string_view string, + OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, std::string_view string, + OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, + OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, int32_t word6, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, int32_t word6, int32_t word7, OutputStream& out); + + void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4, + int32_t word5, int32_t word6, int32_t word7, int32_t word8, + OutputStream& out); + + // This form of writeInstruction can deduplicate redundant ops. + struct Word; + // 8 Words is enough for nearly all instructions (except variable-length instructions like + // OpAccessChain or OpConstantComposite). + using Words = SkSTArray<8, Word, true>; + SpvId writeInstruction(SpvOp_ opCode, const SkTArray<Word, true>& words, OutputStream& out); + + struct Instruction { + SpvId fOp; + int32_t fResultKind; + SkSTArray<8, int32_t> fWords; + + bool operator==(const Instruction& that) const; + struct Hash; + }; + + static Instruction BuildInstructionKey(SpvOp_ opCode, const SkTArray<Word, true>& words); + + // The writeOpXxxxx calls will simplify and deduplicate ops where possible. + SpvId writeOpConstantTrue(const Type& type); + SpvId writeOpConstantFalse(const Type& type); + SpvId writeOpConstant(const Type& type, int32_t valueBits); + SpvId writeOpConstantComposite(const Type& type, const SkTArray<SpvId>& values); + SpvId writeOpCompositeConstruct(const Type& type, const SkTArray<SpvId>&, OutputStream& out); + SpvId writeOpCompositeExtract(const Type& type, SpvId base, int component, OutputStream& out); + SpvId writeOpCompositeExtract(const Type& type, SpvId base, int componentA, int componentB, + OutputStream& out); + SpvId writeOpLoad(SpvId type, Precision precision, SpvId pointer, OutputStream& out); + void writeOpStore(SpvStorageClass_ storageClass, SpvId pointer, SpvId value, OutputStream& out); + + // Converts the provided SpvId(s) into an array of scalar OpConstants, if it can be done. + bool toConstants(SpvId value, SkTArray<SpvId>* constants); + bool toConstants(SkSpan<const SpvId> values, SkTArray<SpvId>* constants); + + // Extracts the requested component SpvId from a composite instruction, if it can be done. + Instruction* resultTypeForInstruction(const Instruction& instr); + int numComponentsForVecInstruction(const Instruction& instr); + SpvId toComponent(SpvId id, int component); + + struct ConditionalOpCounts { + int numReachableOps; + int numStoreOps; + }; + ConditionalOpCounts getConditionalOpCounts(); + void pruneConditionalOps(ConditionalOpCounts ops); + + enum StraightLineLabelType { + // Use "BranchlessBlock" for blocks which are never explicitly branched-to at all. This + // happens at the start of a function, or when we find unreachable code. + kBranchlessBlock, + + // Use "BranchIsOnPreviousLine" when writing a label that comes immediately after its + // associated branch. Example usage: + // - SPIR-V does not implicitly fall through from one block to the next, so you may need to + // use an OpBranch to explicitly jump to the next block, even when they are adjacent in + // the code. + // - The block immediately following an OpBranchConditional or OpSwitch. + kBranchIsOnPreviousLine, + }; + + enum BranchingLabelType { + // Use "BranchIsAbove" for labels which are referenced by OpBranch or OpBranchConditional + // ops that are above the label in the code--i.e., the branch skips forward in the code. + kBranchIsAbove, + + // Use "BranchIsBelow" for labels which are referenced by OpBranch or OpBranchConditional + // ops below the label in the code--i.e., the branch jumps backward in the code. + kBranchIsBelow, + + // Use "BranchesOnBothSides" for labels which have branches coming from both directions. + kBranchesOnBothSides, + }; + void writeLabel(SpvId label, StraightLineLabelType type, OutputStream& out); + void writeLabel(SpvId label, BranchingLabelType type, ConditionalOpCounts ops, + OutputStream& out); + + bool isDead(const Variable& var) const; + + MemoryLayout memoryLayoutForStorageClass(SpvStorageClass_ storageClass); + MemoryLayout memoryLayoutForVariable(const Variable&) const; + + struct EntrypointAdapter { + std::unique_ptr<FunctionDefinition> entrypointDef; + std::unique_ptr<FunctionDeclaration> entrypointDecl; + Layout fLayout; + Modifiers fModifiers; + }; + + EntrypointAdapter writeEntrypointAdapter(const FunctionDeclaration& main); + + struct UniformBuffer { + std::unique_ptr<InterfaceBlock> fInterfaceBlock; + std::unique_ptr<Variable> fInnerVariable; + std::unique_ptr<Type> fStruct; + }; + + void writeUniformBuffer(std::shared_ptr<SymbolTable> topLevelSymbolTable); + + void addRTFlipUniform(Position pos); + + std::tuple<const Variable*, const Variable*> synthesizeTextureAndSampler( + const Variable& combinedSampler); + + const MemoryLayout fDefaultLayout; + + uint64_t fCapabilities; + SpvId fIdCount; + SpvId fGLSLExtendedInstructions; + struct Intrinsic { + IntrinsicOpcodeKind opKind; + int32_t floatOp; + int32_t signedOp; + int32_t unsignedOp; + int32_t boolOp; + }; + Intrinsic getIntrinsic(IntrinsicKind) const; + SkTHashMap<const FunctionDeclaration*, SpvId> fFunctionMap; + SkTHashMap<const Variable*, SpvId> fVariableMap; + SkTHashMap<const Type*, SpvId> fStructMap; + StringStream fGlobalInitializersBuffer; + StringStream fConstantBuffer; + StringStream fVariableBuffer; + StringStream fNameBuffer; + StringStream fDecorationBuffer; + + // Mapping from combined sampler declarations to synthesized texture/sampler variables. + // This is only used if the SPIRVDawnCompatMode setting is enabled. + // TODO(skia:14023): Remove when WGSL codegen is complete + struct SynthesizedTextureSamplerPair { + // The names of the synthesized variables. The Variable objects themselves store string + // views referencing these strings. It is important for the std::string instances to have a + // fixed memory location after the string views get created, which is why + // `fSynthesizedSamplerMap` stores unique_ptr instead of values. + std::string fTextureName; + std::string fSamplerName; + std::unique_ptr<Variable> fTexture; + std::unique_ptr<Variable> fSampler; + }; + SkTHashMap<const Variable*, std::unique_ptr<SynthesizedTextureSamplerPair>> + fSynthesizedSamplerMap; + + // These caches map SpvIds to Instructions, and vice-versa. This enables us to deduplicate code + // (by detecting an Instruction we've already issued and reusing the SpvId), and to introspect + // and simplify code we've already emitted (by taking a SpvId from an Instruction and following + // it back to its source). + SkTHashMap<Instruction, SpvId, Instruction::Hash> fOpCache; // maps instruction -> SpvId + SkTHashMap<SpvId, Instruction> fSpvIdCache; // maps SpvId -> instruction + SkTHashMap<SpvId, SpvId> fStoreCache; // maps ptr SpvId -> value SpvId + + // "Reachable" ops are instructions which can safely be accessed from the current block. + // For instance, if our SPIR-V contains `%3 = OpFAdd %1 %2`, we would be able to access and + // reuse that computation on following lines. However, if that Add operation occurred inside an + // `if` block, then its SpvId becomes inaccessible once we complete the if statement (since + // depending on the if condition, we may or may not have actually done that computation). The + // same logic applies to other control-flow blocks as well. Once an instruction becomes + // unreachable, we remove it from both op-caches. + SkTArray<SpvId> fReachableOps; + + // The "store-ops" list contains a running list of all the pointers in the store cache. If a + // store occurs inside of a conditional block, once that block exits, we no longer know what is + // stored in that particular SpvId. At that point, we must remove any associated entry from the + // store cache. + SkTArray<SpvId> fStoreOps; + + // label of the current block, or 0 if we are not in a block + SpvId fCurrentBlock; + SkTArray<SpvId> fBreakTarget; + SkTArray<SpvId> fContinueTarget; + bool fWroteRTFlip = false; + // holds variables synthesized during output, for lifetime purposes + SymbolTable fSynthetics; + // Holds a list of uniforms that were declared as globals at the top-level instead of in an + // interface block. + UniformBuffer fUniformBuffer; + std::vector<const VarDeclaration*> fTopLevelUniforms; + SkTHashMap<const Variable*, int> fTopLevelUniformMap; // <var, UniformBuffer field index> + SkTHashSet<const Variable*> fSPIRVBonusVariables; + SpvId fUniformBufferId = NA; + + friend class PointerLValue; + friend class SwizzleLValue; + + using INHERITED = CodeGenerator; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.cpp new file mode 100644 index 0000000000..98bb969c5b --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/codegen/SkSLSPIRVtoHLSL.h" + +#if defined(SK_ENABLE_SPIRV_CROSS) + +#include <spirv_hlsl.hpp> + +/* + * This translation unit serves as a bridge between Skia/SkSL and SPIRV-Cross. + * Each library is built with a separate copy of spirv.h (or spirv.hpp), so we + * avoid conflicts by never including both in the same cpp. + */ + +namespace SkSL { + +bool SPIRVtoHLSL(const std::string& spirv, std::string* hlsl) { + spirv_cross::CompilerHLSL hlslCompiler((const uint32_t*)spirv.c_str(), + spirv.size() / sizeof(uint32_t)); + + spirv_cross::CompilerGLSL::Options optionsGLSL; + // Force all uninitialized variables to be 0, otherwise they will fail to compile + // by FXC. + optionsGLSL.force_zero_initialized_variables = true; + + spirv_cross::CompilerHLSL::Options optionsHLSL; + optionsHLSL.shader_model = 51; + // PointCoord and PointSize are not supported in HLSL + optionsHLSL.point_coord_compat = true; + optionsHLSL.point_size_compat = true; + + hlslCompiler.set_common_options(optionsGLSL); + hlslCompiler.set_hlsl_options(optionsHLSL); + hlsl->assign(hlslCompiler.compile()); + return true; +} + +} + +#else + +namespace SkSL { bool SPIRVtoHLSL(const std::string&, std::string*) { return false; } } + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.h b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.h new file mode 100644 index 0000000000..5207546a67 --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLSPIRVtoHLSL.h @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SPIRVTOHLSL +#define SKSL_SPIRVTOHLSL + +#include <string> + +namespace SkSL { + +bool SPIRVtoHLSL(const std::string& spirv, std::string* hlsl); + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.cpp new file mode 100644 index 0000000000..33eab93b9a --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.cpp @@ -0,0 +1,2302 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/codegen/SkSLVMCodeGenerator.h" + +#include "include/core/SkBlendMode.h" +#include "include/core/SkColor.h" +#include "include/core/SkColorType.h" +#include "include/core/SkPoint.h" +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/base/SkFloatingPoint.h" +#include "include/private/base/SkTArray.h" +#include "include/private/base/SkTPin.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkStringView.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLChildCall.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorArrayCast.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/tracing/SkSLDebugInfo.h" +#include "src/sksl/tracing/SkSLTraceHook.h" +#include "src/sksl/tracing/SkVMDebugTrace.h" + +#include <algorithm> +#include <functional> +#include <iterator> +#include <memory> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + // sksl allows the optimizations of fast_mul(), so we want to use that most of the time. + // This little sneaky snippet of code lets us use ** as a fast multiply infix operator. + struct FastF32 { skvm::F32 val; }; + static FastF32 operator*(skvm::F32 y) { return {y}; } + static skvm::F32 operator*(skvm::F32 x, FastF32 y) { return fast_mul(x, y.val); } + static skvm::F32 operator*(float x, FastF32 y) { return fast_mul(x, y.val); } +} + +namespace SkSL { + +namespace { + +// Holds scalars, vectors, or matrices +struct Value { + Value() = default; + explicit Value(size_t slots) { + fVals.resize(slots); + } + Value(skvm::F32 x) : fVals({ x.id }) {} + Value(skvm::I32 x) : fVals({ x.id }) {} + + explicit operator bool() const { return !fVals.empty(); } + + size_t slots() const { return fVals.size(); } + + struct ValRef { + ValRef(skvm::Val& val) : fVal(val) {} + + ValRef& operator=(ValRef v) { fVal = v.fVal; return *this; } + ValRef& operator=(skvm::Val v) { fVal = v; return *this; } + ValRef& operator=(skvm::F32 v) { fVal = v.id; return *this; } + ValRef& operator=(skvm::I32 v) { fVal = v.id; return *this; } + + operator skvm::Val() { return fVal; } + + skvm::Val& fVal; + }; + + ValRef operator[](int i) { + // These redundant asserts work around what we think is a codegen bug in GCC 8.x for + // 32-bit x86 Debug builds. + SkASSERT(i < fVals.size()); + return fVals[i]; + } + skvm::Val operator[](int i) const { + // These redundant asserts work around what we think is a codegen bug in GCC 8.x for + // 32-bit x86 Debug builds. + SkASSERT(i < fVals.size()); + return fVals[i]; + } + + SkSpan<skvm::Val> asSpan() { return SkSpan(fVals); } + +private: + SkSTArray<4, skvm::Val, true> fVals; +}; + +} // namespace + +class SkVMGenerator { +public: + SkVMGenerator(const Program& program, + skvm::Builder* builder, + SkVMDebugTrace* debugTrace, + SkVMCallbacks* callbacks); + + void writeProgram(SkSpan<skvm::Val> uniforms, + skvm::Coord device, + const FunctionDefinition& function, + SkSpan<skvm::Val> arguments, + SkSpan<skvm::Val> outReturn); + +private: + /** + * In SkSL, a Variable represents a named, typed value (along with qualifiers, etc). + * Every Variable is mapped to one (or several, contiguous) indices into our vector of + * skvm::Val. Those skvm::Val entries hold the current actual value of that variable. + * + * NOTE: Conceptually, each Variable is just mapped to a Value. We could implement it that way, + * (and eliminate the indirection), but it would add overhead for each Variable, + * and add additional (different) bookkeeping for things like lvalue-swizzles. + * + * Any time a variable appears in an expression, that's a VariableReference, which is a kind of + * Expression. Evaluating that VariableReference (or any other Expression) produces a Value, + * which is a set of skvm::Val. (This allows an Expression to produce a vector or matrix, in + * addition to a scalar). + * + * For a VariableReference, producing a Value is straightforward - we get the slot of the + * Variable (from fSlotMap), use that to look up the current skvm::Vals holding the variable's + * contents, and construct a Value with those ids. + */ + + /** Creates a Value from a collection of adjacent slots. */ + Value getSlotValue(size_t slot, size_t nslots); + + /** + * Returns the slot index of this function inside the FunctionDebugInfo array in SkVMDebugTrace. + * The FunctionDebugInfo slot will be created if it doesn't already exist. + */ + int getDebugFunctionInfo(const FunctionDeclaration& decl); + + /** Used by `createSlot` to add this variable to SlotDebugInfo inside SkVMDebugTrace. */ + void addDebugSlotInfo(const std::string& varName, const Type& type, int line, + int fnReturnValue); + + void addDebugSlotInfoForGroup(const std::string& varName, const Type& type, int line, + int* groupIndex, int fnReturnValue); + + /** Used by `getSlot` to create a new slot on its first access. */ + size_t createSlot(const std::string& name, const Type& type, int line, int fnReturnValue); + + /** + * Returns the slot holding v's Val(s). Allocates storage if this is first time 'v' is + * referenced. Compound variables (e.g. vectors) will consume more than one slot, with + * getSlot returning the start of the contiguous chunk of slots. + */ + size_t getSlot(const Variable& v); + + /** + * Returns the slot holding fn's return value. Each call site is given a distinct slot, since + * multiple calls to the same function can occur in a single statement. This is generally the + * FunctionCall or ChildCall node, but main() doesn't have one of these so it uses the + * FunctionDefinition. Allocates storage if this is first time accessing the slot. + */ + size_t getFunctionSlot(const IRNode& callSite, const FunctionDefinition& fn); + + /** + * Writes a value to a slot previously created by getSlot. + */ + void writeToSlot(int slot, skvm::Val value); + + /** + * Returns the line number corresponding to a position. + */ + int getLine(Position pos); + + /** + * Emits an trace_line opcode. writeStatement does this, and statements that alter control flow + * may need to explicitly add additional traces. + */ + void emitTraceLine(int line); + + /** Emits an trace_scope opcode, which alters the SkSL variable-scope depth. */ + void emitTraceScope(skvm::I32 executionMask, int delta); + + /** Initializes uniforms and global variables at the start of main(). */ + void setupGlobals(SkSpan<skvm::Val> uniforms, skvm::Coord device); + + /** Emits an SkSL function. Returns the slot index of the SkSL function's return value. */ + size_t writeFunction(const IRNode& caller, + const FunctionDefinition& function, + SkSpan<skvm::Val> arguments); + + skvm::F32 f32(skvm::Val id) { SkASSERT(id != skvm::NA); return {fBuilder, id}; } + skvm::I32 i32(skvm::Val id) { SkASSERT(id != skvm::NA); return {fBuilder, id}; } + + // Shorthand for scalars + skvm::F32 f32(const Value& v) { SkASSERT(v.slots() == 1); return f32(v[0]); } + skvm::I32 i32(const Value& v) { SkASSERT(v.slots() == 1); return i32(v[0]); } + + template <typename Fn> + Value unary(const Value& v, Fn&& fn) { + Value result(v.slots()); + for (size_t i = 0; i < v.slots(); ++i) { + result[i] = fn({fBuilder, v[i]}); + } + return result; + } + + skvm::I32 mask() { + // Mask off execution if we have encountered `break` or `continue` on this path. + skvm::I32 result = fConditionMask & fLoopMask; + if (!fFunctionStack.empty()) { + // As we encounter (possibly conditional) return statements, fReturned is updated to + // store the lanes that have already returned. For the remainder of the current + // function, those lanes should be disabled. + result = result & ~currentFunction().fReturned; + } + return result; + } + + size_t indexSlotOffset(const IndexExpression& expr); + + Value writeExpression(const Expression& expr); + Value writeBinaryExpression(const BinaryExpression& b); + Value writeAggregationConstructor(const AnyConstructor& c); + Value writeChildCall(const ChildCall& c); + Value writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c); + Value writeConstructorMatrixResize(const ConstructorMatrixResize& c); + Value writeConstructorCast(const AnyConstructor& c); + Value writeConstructorSplat(const ConstructorSplat& c); + Value writeFunctionCall(const FunctionCall& c); + Value writeFieldAccess(const FieldAccess& expr); + Value writeLiteral(const Literal& l); + Value writeIndexExpression(const IndexExpression& expr); + Value writeIntrinsicCall(const FunctionCall& c); + Value writePostfixExpression(const PostfixExpression& p); + Value writePrefixExpression(const PrefixExpression& p); + Value writeSwizzle(const Swizzle& swizzle); + Value writeTernaryExpression(const TernaryExpression& t); + Value writeVariableExpression(const VariableReference& expr); + + Value writeTypeConversion(const Value& src, Type::NumberKind srcKind, Type::NumberKind dstKind); + + void writeStatement(const Statement& s); + void writeBlock(const Block& b); + void writeBreakStatement(); + void writeContinueStatement(); + void writeForStatement(const ForStatement& f); + void writeIfStatement(const IfStatement& stmt); + void writeReturnStatement(const ReturnStatement& r); + void writeSwitchStatement(const SwitchStatement& s); + void writeVarDeclaration(const VarDeclaration& decl); + + Value writeStore(const Expression& lhs, const Value& rhs); + skvm::Val writeConditionalStore(skvm::Val lhs, skvm::Val rhs, skvm::I32 mask); + + Value writeMatrixInverse2x2(const Value& m); + Value writeMatrixInverse3x3(const Value& m); + Value writeMatrixInverse4x4(const Value& m); + + void recursiveBinaryCompare(const Value& lVal, const Type& lType, + const Value& rVal, const Type& rType, + size_t* slotOffset, Value* result, + const std::function <Value(skvm::F32 x, skvm::F32 y)>& float_comp, + const std::function <Value(skvm::I32 x, skvm::I32 y)>& int_comp); + + void determineLineOffsets(); + + // + // Global state for the lifetime of the generator: + // + const Program& fProgram; + skvm::Builder* fBuilder; + SkVMDebugTrace* fDebugTrace; + int fTraceHookID = -1; + SkVMCallbacks* fCallbacks; + // contains the position of each newline in the source, plus a zero at the beginning and the + // total source length at the end as sentinels + std::vector<int> fLineOffsets; + + struct Slot { + skvm::Val val; + bool writtenTo = false; + }; + std::vector<Slot> fSlots; + + // [Variable/Function, first slot in fSlots] + SkTHashMap<const IRNode*, size_t> fSlotMap; + + // Debug trace mask (set to true when fTraceCoord matches device coordinates) + skvm::I32 fTraceMask; + + // Conditional execution mask (managed by ScopedCondition, and tied to control-flow scopes) + skvm::I32 fConditionMask; + + // Similar: loop execution masks. Each loop starts with all lanes active (fLoopMask). + // 'break' disables a lane in fLoopMask until the loop finishes + // 'continue' disables a lane in fLoopMask, and sets fContinueMask to be re-enabled on the next + // iteration + skvm::I32 fLoopMask; + skvm::I32 fContinueMask; + + // `fInsideCompoundStatement` will be nonzero if we are currently writing statements inside of a + // compound-statement Block. (Conceptually those statements should all count as one.) + int fInsideCompoundStatement = 0; + + // + // State that's local to the generation of a single function: + // + struct Function { + size_t fReturnSlot; + skvm::I32 fReturned; + }; + std::vector<Function> fFunctionStack; + Function& currentFunction() { return fFunctionStack.back(); } + + class ScopedCondition { + public: + ScopedCondition(SkVMGenerator* generator, skvm::I32 mask) + : fGenerator(generator), fOldConditionMask(fGenerator->fConditionMask) { + fGenerator->fConditionMask &= mask; + } + + ~ScopedCondition() { fGenerator->fConditionMask = fOldConditionMask; } + + private: + SkVMGenerator* fGenerator; + skvm::I32 fOldConditionMask; + }; +}; + +static Type::NumberKind base_number_kind(const Type& type) { + if (type.typeKind() == Type::TypeKind::kMatrix || type.typeKind() == Type::TypeKind::kVector) { + return base_number_kind(type.componentType()); + } + return type.numberKind(); +} + +static inline bool is_uniform(const SkSL::Variable& var) { + return var.modifiers().fFlags & Modifiers::kUniform_Flag; +} + +SkVMGenerator::SkVMGenerator(const Program& program, + skvm::Builder* builder, + SkVMDebugTrace* debugTrace, + SkVMCallbacks* callbacks) + : fProgram(program) + , fBuilder(builder) + , fDebugTrace(debugTrace) + , fCallbacks(callbacks) {} + +void SkVMGenerator::writeProgram(SkSpan<skvm::Val> uniforms, + skvm::Coord device, + const FunctionDefinition& function, + SkSpan<skvm::Val> arguments, + SkSpan<skvm::Val> outReturn) { + this->determineLineOffsets(); + fConditionMask = fLoopMask = fBuilder->splat(0xffff'ffff); + + this->setupGlobals(uniforms, device); + size_t returnSlot = this->writeFunction(function, function, arguments); + + // Copy the value from the return slot into outReturn. + SkASSERT(function.declaration().returnType().slotCount() == outReturn.size()); + for (size_t i = 0; i < outReturn.size(); ++i) { + outReturn[i] = fSlots[returnSlot + i].val; + } +} + +void SkVMGenerator::determineLineOffsets() { + SkASSERT(fLineOffsets.empty()); + fLineOffsets.push_back(0); + for (size_t i = 0; i < fProgram.fSource->length(); ++i) { + if ((*fProgram.fSource)[i] == '\n') { + fLineOffsets.push_back(i); + } + } + fLineOffsets.push_back(fProgram.fSource->length()); +} + +void SkVMGenerator::setupGlobals(SkSpan<skvm::Val> uniforms, skvm::Coord device) { + if (fDebugTrace) { + // Copy the program source into the debug info so that it will be written in the trace file. + fDebugTrace->setSource(*fProgram.fSource); + + // Create a trace hook and attach it to the builder. + fDebugTrace->fTraceHook = SkSL::Tracer::Make(&fDebugTrace->fTraceInfo); + fTraceHookID = fBuilder->attachTraceHook(fDebugTrace->fTraceHook.get()); + + // The SkVM blitter generates centered pixel coordinates. (0.5, 1.5, 2.5, 3.5, etc.) + // Add 0.5 to the requested trace coordinate to match this. + skvm::Coord traceCoord = {to_F32(fBuilder->splat(fDebugTrace->fTraceCoord.fX)) + 0.5f, + to_F32(fBuilder->splat(fDebugTrace->fTraceCoord.fY)) + 0.5f}; + + // If we are debugging, we need to create a trace mask. This will be true when the current + // device coordinates match the requested trace coordinates. We calculate each mask + // individually to guarantee consistent order-of-evaluation. + skvm::I32 xMask = (device.x == traceCoord.x), + yMask = (device.y == traceCoord.y); + fTraceMask = xMask & yMask; + } + + // Add storage for each global variable (including uniforms) to fSlots, and entries in + // fSlotMap to remember where every variable is stored. + const skvm::Val* uniformIter = uniforms.begin(); + size_t fpCount = 0; + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& gvd = e->as<GlobalVarDeclaration>(); + const VarDeclaration& decl = gvd.varDeclaration(); + const Variable* var = decl.var(); + SkASSERT(!fSlotMap.find(var)); + + // For most variables, fSlotMap stores an index into fSlots, but for children, + // fSlotMap stores the index to pass to fSample(Shader|ColorFilter|Blender) + if (var->type().isEffectChild()) { + fSlotMap.set(var, fpCount++); + continue; + } + + // Opaque types include child processors and GL objects (samplers, textures, etc). + // Of those, only child processors are legal variables. + SkASSERT(!var->type().isVoid()); + SkASSERT(!var->type().isOpaque()); + + // getSlot() allocates space for the variable's value in fSlots, initializes it to zero, + // and populates fSlotMap. + size_t slot = this->getSlot(*var), + nslots = var->type().slotCount(); + + // builtin variables are system-defined, with special semantics. The only builtin + // variable exposed to runtime effects is sk_FragCoord. + if (int builtin = var->modifiers().fLayout.fBuiltin; builtin >= 0) { + switch (builtin) { + case SK_FRAGCOORD_BUILTIN: + SkASSERT(nslots == 4); + this->writeToSlot(slot + 0, device.x.id); + this->writeToSlot(slot + 1, device.y.id); + this->writeToSlot(slot + 2, fBuilder->splat(0.0f).id); + this->writeToSlot(slot + 3, fBuilder->splat(1.0f).id); + break; + default: + SkDEBUGFAILF("Unsupported builtin %d", builtin); + } + continue; + } + + // For uniforms, copy the supplied IDs over + if (is_uniform(*var)) { + SkASSERT(uniformIter + nslots <= uniforms.end()); + for (size_t i = 0; i < nslots; ++i) { + this->writeToSlot(slot + i, uniformIter[i]); + } + uniformIter += nslots; + continue; + } + + // For other globals, populate with the initializer expression (if there is one) + if (decl.value()) { + Value val = this->writeExpression(*decl.value()); + for (size_t i = 0; i < nslots; ++i) { + this->writeToSlot(slot + i, val[i]); + } + } + } + } + SkASSERT(uniformIter == uniforms.end()); +} + +Value SkVMGenerator::getSlotValue(size_t slot, size_t nslots) { + Value val(nslots); + for (size_t i = 0; i < nslots; ++i) { + val[i] = fSlots[slot + i].val; + } + return val; +} + +int SkVMGenerator::getDebugFunctionInfo(const FunctionDeclaration& decl) { + SkASSERT(fDebugTrace); + + std::string name = decl.description(); + + // When generating the debug trace, we typically mark every function as `noinline`. This makes + // the trace more confusing, since this isn't in the source program, so remove it. + static constexpr std::string_view kNoInline = "noinline "; + if (skstd::starts_with(name, kNoInline)) { + name = name.substr(kNoInline.size()); + } + + // Look for a matching FunctionDebugInfo slot. + for (size_t index = 0; index < fDebugTrace->fFuncInfo.size(); ++index) { + if (fDebugTrace->fFuncInfo[index].name == name) { + return index; + } + } + + // We've never called this function before; create a new slot to hold its information. + int slot = (int)fDebugTrace->fFuncInfo.size(); + fDebugTrace->fFuncInfo.push_back(FunctionDebugInfo{std::move(name)}); + return slot; +} + +size_t SkVMGenerator::writeFunction(const IRNode& caller, + const FunctionDefinition& function, + SkSpan<skvm::Val> arguments) { + const FunctionDeclaration& decl = function.declaration(); + + int funcIndex = -1; + if (fDebugTrace) { + funcIndex = this->getDebugFunctionInfo(decl); + fBuilder->trace_enter(fTraceHookID, this->mask(), fTraceMask, funcIndex); + } + + size_t returnSlot = this->getFunctionSlot(caller, function); + fFunctionStack.push_back({/*fReturnSlot=*/returnSlot, /*fReturned=*/fBuilder->splat(0)}); + + // For all parameters, copy incoming argument IDs to our vector of (all) variable IDs + size_t argIdx = 0; + for (const Variable* p : decl.parameters()) { + size_t paramSlot = this->getSlot(*p), + nslots = p->type().slotCount(); + + for (size_t i = 0; i < nslots; ++i) { + fSlots[paramSlot + i].writtenTo = false; + this->writeToSlot(paramSlot + i, arguments[argIdx + i]); + } + argIdx += nslots; + } + SkASSERT(argIdx == arguments.size()); + + this->writeBlock(function.body()->as<Block>()); + + // Copy 'out' and 'inout' parameters back to their caller-supplied argument storage + argIdx = 0; + for (const Variable* p : decl.parameters()) { + size_t nslots = p->type().slotCount(); + + if (p->modifiers().fFlags & Modifiers::kOut_Flag) { + size_t paramSlot = this->getSlot(*p); + for (size_t i = 0; i < nslots; ++i) { + arguments[argIdx + i] = fSlots[paramSlot + i].val; + } + } + argIdx += nslots; + } + SkASSERT(argIdx == arguments.size()); + + fFunctionStack.pop_back(); + + if (fDebugTrace) { + fBuilder->trace_exit(fTraceHookID, this->mask(), fTraceMask, funcIndex); + } + + return returnSlot; +} + +void SkVMGenerator::writeToSlot(int slot, skvm::Val value) { + if (fDebugTrace && (!fSlots[slot].writtenTo || fSlots[slot].val != value)) { + if (fProgram.fConfig->fSettings.fAllowTraceVarInSkVMDebugTrace) { + fBuilder->trace_var(fTraceHookID, this->mask(), fTraceMask, slot, i32(value)); + } + fSlots[slot].writtenTo = true; + } + + fSlots[slot].val = value; +} + +void SkVMGenerator::addDebugSlotInfoForGroup(const std::string& varName, const Type& type, int line, + int* groupIndex, int fnReturnValue) { + SkASSERT(fDebugTrace); + switch (type.typeKind()) { + case Type::TypeKind::kArray: { + int nslots = type.columns(); + const Type& elemType = type.componentType(); + for (int slot = 0; slot < nslots; ++slot) { + this->addDebugSlotInfoForGroup(varName + "[" + std::to_string(slot) + "]", elemType, + line, groupIndex, fnReturnValue); + } + break; + } + case Type::TypeKind::kStruct: { + for (const Type::Field& field : type.fields()) { + this->addDebugSlotInfoForGroup(varName + "." + std::string(field.fName), + *field.fType, line, groupIndex, fnReturnValue); + } + break; + } + default: + SkASSERTF(0, "unsupported slot type %d", (int)type.typeKind()); + [[fallthrough]]; + + case Type::TypeKind::kScalar: + case Type::TypeKind::kVector: + case Type::TypeKind::kMatrix: { + Type::NumberKind numberKind = type.componentType().numberKind(); + int nslots = type.slotCount(); + + for (int slot = 0; slot < nslots; ++slot) { + SlotDebugInfo slotInfo; + slotInfo.name = varName; + slotInfo.columns = type.columns(); + slotInfo.rows = type.rows(); + slotInfo.componentIndex = slot; + slotInfo.groupIndex = (*groupIndex)++; + slotInfo.numberKind = numberKind; + slotInfo.line = line; + slotInfo.fnReturnValue = fnReturnValue; + fDebugTrace->fSlotInfo.push_back(std::move(slotInfo)); + } + break; + } + } +} + +void SkVMGenerator::addDebugSlotInfo(const std::string& varName, const Type& type, int line, + int fnReturnValue) { + int groupIndex = 0; + this->addDebugSlotInfoForGroup(varName, type, line, &groupIndex, fnReturnValue); + SkASSERT((size_t)groupIndex == type.slotCount()); +} + +size_t SkVMGenerator::createSlot(const std::string& name, + const Type& type, + int line, + int fnReturnValue) { + size_t slot = fSlots.size(), + nslots = type.slotCount(); + + if (nslots > 0) { + if (fDebugTrace) { + // Our debug slot-info table should have the same length as the actual slot table. + SkASSERT(fDebugTrace->fSlotInfo.size() == slot); + + // Append slot names and types to our debug slot-info table. + fDebugTrace->fSlotInfo.reserve(slot + nslots); + this->addDebugSlotInfo(name, type, line, fnReturnValue); + + // Confirm that we added the expected number of slots. + SkASSERT(fDebugTrace->fSlotInfo.size() == (slot + nslots)); + } + + // Create brand new slots initialized to zero. + skvm::Val initialValue = fBuilder->splat(0.0f).id; + fSlots.insert(fSlots.end(), nslots, Slot{initialValue}); + } + return slot; +} + +// TODO(skia:13058): remove this and track positions directly +int SkVMGenerator::getLine(Position pos) { + if (pos.valid()) { + // Binary search within fLineOffets to find the line. + SkASSERT(fLineOffsets.size() >= 2); + SkASSERT(fLineOffsets[0] == 0); + SkASSERT(fLineOffsets.back() == (int)fProgram.fSource->length()); + return std::distance(fLineOffsets.begin(), std::upper_bound(fLineOffsets.begin(), + fLineOffsets.end(), pos.startOffset())); + } else { + return -1; + } +} + +size_t SkVMGenerator::getSlot(const Variable& v) { + size_t* entry = fSlotMap.find(&v); + if (entry != nullptr) { + return *entry; + } + + size_t slot = this->createSlot(std::string(v.name()), v.type(), this->getLine(v.fPosition), + /*fnReturnValue=*/-1); + fSlotMap.set(&v, slot); + return slot; +} + +size_t SkVMGenerator::getFunctionSlot(const IRNode& callSite, const FunctionDefinition& fn) { + size_t* entry = fSlotMap.find(&callSite); + if (entry != nullptr) { + return *entry; + } + + const FunctionDeclaration& decl = fn.declaration(); + size_t slot = this->createSlot("[" + std::string(decl.name()) + "].result", + decl.returnType(), + this->getLine(fn.fPosition), + /*fnReturnValue=*/1); + fSlotMap.set(&callSite, slot); + return slot; +} + +void SkVMGenerator::recursiveBinaryCompare( + const Value& lVal, + const Type& lType, + const Value& rVal, + const Type& rType, + size_t* slotOffset, + Value* result, + const std::function<Value(skvm::F32 x, skvm::F32 y)>& float_comp, + const std::function<Value(skvm::I32 x, skvm::I32 y)>& int_comp) { + switch (lType.typeKind()) { + case Type::TypeKind::kStruct: + SkASSERT(rType.typeKind() == Type::TypeKind::kStruct); + // Go through all the fields + for (size_t f = 0; f < lType.fields().size(); ++f) { + const Type::Field& lField = lType.fields()[f]; + const Type::Field& rField = rType.fields()[f]; + this->recursiveBinaryCompare(lVal, + *lField.fType, + rVal, + *rField.fType, + slotOffset, + result, + float_comp, + int_comp); + } + break; + + case Type::TypeKind::kArray: + case Type::TypeKind::kVector: + case Type::TypeKind::kMatrix: + SkASSERT(lType.typeKind() == rType.typeKind()); + // Go through all the elements + for (int c = 0; c < lType.columns(); ++c) { + this->recursiveBinaryCompare(lVal, + lType.componentType(), + rVal, + rType.componentType(), + slotOffset, + result, + float_comp, + int_comp); + } + break; + default: + SkASSERT(lType.typeKind() == rType.typeKind() && + lType.slotCount() == rType.slotCount()); + Type::NumberKind nk = base_number_kind(lType); + auto L = lVal[*slotOffset]; + auto R = rVal[*slotOffset]; + (*result)[*slotOffset] = + i32(nk == Type::NumberKind::kFloat + ? float_comp(f32(L), f32(R)) + : int_comp(i32(L), i32(R))).id; + *slotOffset += lType.slotCount(); + break; + } +} + +Value SkVMGenerator::writeBinaryExpression(const BinaryExpression& b) { + const Expression& left = *b.left(); + const Expression& right = *b.right(); + Operator op = b.getOperator(); + if (op.kind() == Operator::Kind::EQ) { + return this->writeStore(left, this->writeExpression(right)); + } + + const Type& lType = left.type(); + const Type& rType = right.type(); + bool lVecOrMtx = (lType.isVector() || lType.isMatrix()); + bool rVecOrMtx = (rType.isVector() || rType.isMatrix()); + bool isAssignment = op.isAssignment(); + if (isAssignment) { + op = op.removeAssignment(); + } + Type::NumberKind nk = base_number_kind(lType); + + // A few ops require special treatment: + switch (op.kind()) { + case Operator::Kind::LOGICALAND: { + SkASSERT(!isAssignment); + SkASSERT(nk == Type::NumberKind::kBoolean); + skvm::I32 lVal = i32(this->writeExpression(left)); + ScopedCondition shortCircuit(this, lVal); + skvm::I32 rVal = i32(this->writeExpression(right)); + return lVal & rVal; + } + case Operator::Kind::LOGICALOR: { + SkASSERT(!isAssignment); + SkASSERT(nk == Type::NumberKind::kBoolean); + skvm::I32 lVal = i32(this->writeExpression(left)); + ScopedCondition shortCircuit(this, ~lVal); + skvm::I32 rVal = i32(this->writeExpression(right)); + return lVal | rVal; + } + case Operator::Kind::COMMA: + // We write the left side of the expression to preserve its side effects, even though we + // immediately discard the result. + this->writeExpression(left); + return this->writeExpression(right); + default: + break; + } + + // All of the other ops always evaluate both sides of the expression + Value lVal = this->writeExpression(left), + rVal = this->writeExpression(right); + + // Special case for M*V, V*M, M*M (but not V*V!) + if (op.kind() == Operator::Kind::STAR + && lVecOrMtx && rVecOrMtx && !(lType.isVector() && rType.isVector())) { + int rCols = rType.columns(), + rRows = rType.rows(), + lCols = lType.columns(), + lRows = lType.rows(); + // M*V treats the vector as a column + if (rType.isVector()) { + std::swap(rCols, rRows); + } + SkASSERT(lCols == rRows); + SkASSERT(b.type().slotCount() == static_cast<size_t>(lRows * rCols)); + Value result(lRows * rCols); + size_t resultIdx = 0; + const skvm::F32 zero = fBuilder->splat(0.0f); + for (int c = 0; c < rCols; ++c) + for (int r = 0; r < lRows; ++r) { + skvm::F32 sum = zero; + for (int j = 0; j < lCols; ++j) { + sum += f32(lVal[j*lRows + r]) * f32(rVal[c*rRows + j]); + } + result[resultIdx++] = sum; + } + SkASSERT(resultIdx == result.slots()); + return isAssignment ? this->writeStore(left, result) : result; + } + + size_t nslots = std::max(lVal.slots(), rVal.slots()); + + auto binary = [&](const std::function <Value(skvm::F32 x, skvm::F32 y)>& f_fn, + const std::function <Value(skvm::I32 x, skvm::I32 y)>& i_fn, + bool foldResults = false) -> Value { + + Value result(nslots); + if (op.isEquality() && (lType.isStruct() || lType.isArray())) { + // Shifting over lVal and rVal + size_t slotOffset = 0; + this->recursiveBinaryCompare( + lVal, lType, rVal, rType, &slotOffset, &result, f_fn, i_fn); + SkASSERT(slotOffset == nslots); + } else { + for (size_t slot = 0; slot < nslots; ++slot) { + // If one side is scalar, replicate it to all channels + skvm::Val L = lVal.slots() == 1 ? lVal[0] : lVal[slot], + R = rVal.slots() == 1 ? rVal[0] : rVal[slot]; + + if (nk == Type::NumberKind::kFloat) { + result[slot] = i32(f_fn(f32(L), f32(R))); + } else { + result[slot] = i32(i_fn(i32(L), i32(R))); + } + } + } + + if (foldResults && nslots > 1) { + SkASSERT(op.isEquality()); + skvm::I32 folded = i32(result[0]); + for (size_t i = 1; i < nslots; ++i) { + if (op.kind() == Operator::Kind::NEQ) { + folded |= i32(result[i]); + } else { + folded &= i32(result[i]); + } + } + return folded; + } + + return isAssignment ? this->writeStore(left, result) : result; + }; + + auto unsupported_f = [&](skvm::F32, skvm::F32) { + SkDEBUGFAIL("Unsupported operator"); + return skvm::F32{}; + }; + + switch (op.kind()) { + case Operator::Kind::EQEQ: + SkASSERT(!isAssignment); + return binary([](skvm::F32 x, skvm::F32 y) { return x == y; }, + [](skvm::I32 x, skvm::I32 y) { return x == y; }, /*foldResults=*/ true); + case Operator::Kind::NEQ: + SkASSERT(!isAssignment); + return binary([](skvm::F32 x, skvm::F32 y) { return x != y; }, + [](skvm::I32 x, skvm::I32 y) { return x != y; }, /*foldResults=*/ true); + case Operator::Kind::GT: + return binary([](skvm::F32 x, skvm::F32 y) { return x > y; }, + [](skvm::I32 x, skvm::I32 y) { return x > y; }); + case Operator::Kind::GTEQ: + return binary([](skvm::F32 x, skvm::F32 y) { return x >= y; }, + [](skvm::I32 x, skvm::I32 y) { return x >= y; }); + case Operator::Kind::LT: + return binary([](skvm::F32 x, skvm::F32 y) { return x < y; }, + [](skvm::I32 x, skvm::I32 y) { return x < y; }); + case Operator::Kind::LTEQ: + return binary([](skvm::F32 x, skvm::F32 y) { return x <= y; }, + [](skvm::I32 x, skvm::I32 y) { return x <= y; }); + + case Operator::Kind::PLUS: + return binary([](skvm::F32 x, skvm::F32 y) { return x + y; }, + [](skvm::I32 x, skvm::I32 y) { return x + y; }); + case Operator::Kind::MINUS: + return binary([](skvm::F32 x, skvm::F32 y) { return x - y; }, + [](skvm::I32 x, skvm::I32 y) { return x - y; }); + case Operator::Kind::STAR: + return binary([](skvm::F32 x, skvm::F32 y) { return x ** y; }, + [](skvm::I32 x, skvm::I32 y) { return x * y; }); + case Operator::Kind::SLASH: + // Minimum spec (GLSL ES 1.0) has very loose requirements for integer operations. + // (Low-end GPUs may not have integer ALUs). Given that, we are allowed to do floating + // point division plus rounding. Section 10.28 of the spec even clarifies that the + // rounding mode is undefined (but round-towards-zero is the obvious/common choice). + return binary([](skvm::F32 x, skvm::F32 y) { return x / y; }, + [](skvm::I32 x, skvm::I32 y) { + return skvm::trunc(skvm::to_F32(x) / skvm::to_F32(y)); + }); + + case Operator::Kind::BITWISEXOR: + case Operator::Kind::LOGICALXOR: + return binary(unsupported_f, [](skvm::I32 x, skvm::I32 y) { return x ^ y; }); + case Operator::Kind::BITWISEAND: + return binary(unsupported_f, [](skvm::I32 x, skvm::I32 y) { return x & y; }); + case Operator::Kind::BITWISEOR: + return binary(unsupported_f, [](skvm::I32 x, skvm::I32 y) { return x | y; }); + + // These three operators are all 'reserved' (illegal) in our minimum spec, but will require + // implementation in the future. + case Operator::Kind::PERCENT: + case Operator::Kind::SHL: + case Operator::Kind::SHR: + default: + SkDEBUGFAIL("Unsupported operator"); + return {}; + } +} + +Value SkVMGenerator::writeAggregationConstructor(const AnyConstructor& c) { + Value result(c.type().slotCount()); + size_t resultIdx = 0; + for (const auto &arg : c.argumentSpan()) { + Value tmp = this->writeExpression(*arg); + for (size_t tmpSlot = 0; tmpSlot < tmp.slots(); ++tmpSlot) { + result[resultIdx++] = tmp[tmpSlot]; + } + } + return result; +} + +Value SkVMGenerator::writeTypeConversion(const Value& src, + Type::NumberKind srcKind, + Type::NumberKind dstKind) { + // Conversion among "similar" types (floatN <-> halfN), (shortN <-> intN), etc. is a no-op. + if (srcKind == dstKind) { + return src; + } + + // TODO: Handle signed vs. unsigned. GLSL ES 1.0 only has 'int', so no problem yet. + Value dst(src.slots()); + switch (dstKind) { + case Type::NumberKind::kFloat: + if (srcKind == Type::NumberKind::kSigned) { + // int -> float + for (size_t i = 0; i < src.slots(); ++i) { + dst[i] = skvm::to_F32(i32(src[i])); + } + return dst; + } + if (srcKind == Type::NumberKind::kBoolean) { + // bool -> float + for (size_t i = 0; i < src.slots(); ++i) { + dst[i] = skvm::select(i32(src[i]), 1.0f, 0.0f); + } + return dst; + } + break; + + case Type::NumberKind::kSigned: + if (srcKind == Type::NumberKind::kFloat) { + // float -> int + for (size_t i = 0; i < src.slots(); ++i) { + dst[i] = skvm::trunc(f32(src[i])); + } + return dst; + } + if (srcKind == Type::NumberKind::kBoolean) { + // bool -> int + for (size_t i = 0; i < src.slots(); ++i) { + dst[i] = skvm::select(i32(src[i]), 1, 0); + } + return dst; + } + break; + + case Type::NumberKind::kBoolean: + if (srcKind == Type::NumberKind::kSigned) { + // int -> bool + for (size_t i = 0; i < src.slots(); ++i) { + dst[i] = i32(src[i]) != 0; + } + return dst; + } + if (srcKind == Type::NumberKind::kFloat) { + // float -> bool + for (size_t i = 0; i < src.slots(); ++i) { + dst[i] = f32(src[i]) != 0.0; + } + return dst; + } + break; + + default: + break; + } + SkDEBUGFAILF("Unsupported type conversion: %d -> %d", (int)srcKind, (int)dstKind); + return {}; +} + +Value SkVMGenerator::writeConstructorCast(const AnyConstructor& c) { + auto arguments = c.argumentSpan(); + SkASSERT(arguments.size() == 1); + const Expression& argument = *arguments.front(); + + const Type& srcType = argument.type(); + const Type& dstType = c.type(); + Type::NumberKind srcKind = base_number_kind(srcType); + Type::NumberKind dstKind = base_number_kind(dstType); + Value src = this->writeExpression(argument); + return this->writeTypeConversion(src, srcKind, dstKind); +} + +Value SkVMGenerator::writeConstructorSplat(const ConstructorSplat& c) { + SkASSERT(c.type().isVector()); + SkASSERT(c.argument()->type().isScalar()); + int columns = c.type().columns(); + + // Splat the argument across all components of a vector. + Value src = this->writeExpression(*c.argument()); + Value dst(columns); + for (int i = 0; i < columns; ++i) { + dst[i] = src[0]; + } + return dst; +} + +Value SkVMGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& ctor) { + const Type& dstType = ctor.type(); + SkASSERT(dstType.isMatrix()); + SkASSERT(ctor.argument()->type().matches(dstType.componentType())); + + Value src = this->writeExpression(*ctor.argument()); + Value dst(dstType.rows() * dstType.columns()); + size_t dstIndex = 0; + + // Matrix-from-scalar builds a diagonal scale matrix + const skvm::F32 zero = fBuilder->splat(0.0f); + for (int c = 0; c < dstType.columns(); ++c) { + for (int r = 0; r < dstType.rows(); ++r) { + dst[dstIndex++] = (c == r ? f32(src) : zero); + } + } + + SkASSERT(dstIndex == dst.slots()); + return dst; +} + +Value SkVMGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& ctor) { + const Type& srcType = ctor.argument()->type(); + const Type& dstType = ctor.type(); + Value src = this->writeExpression(*ctor.argument()); + Value dst(dstType.rows() * dstType.columns()); + + // Matrix-from-matrix uses src where it overlaps, and fills in missing fields with identity. + size_t dstIndex = 0; + for (int c = 0; c < dstType.columns(); ++c) { + for (int r = 0; r < dstType.rows(); ++r) { + if (c < srcType.columns() && r < srcType.rows()) { + dst[dstIndex++] = src[c * srcType.rows() + r]; + } else { + dst[dstIndex++] = fBuilder->splat(c == r ? 1.0f : 0.0f); + } + } + } + + SkASSERT(dstIndex == dst.slots()); + return dst; +} + +Value SkVMGenerator::writeFieldAccess(const FieldAccess& expr) { + Value base = this->writeExpression(*expr.base()); + Value field(expr.type().slotCount()); + size_t offset = expr.initialSlot(); + for (size_t i = 0; i < field.slots(); ++i) { + field[i] = base[offset + i]; + } + return field; +} + +size_t SkVMGenerator::indexSlotOffset(const IndexExpression& expr) { + Value index = this->writeExpression(*expr.index()); + int indexValue = -1; + SkAssertResult(fBuilder->allImm(index[0], &indexValue)); + + // When indexing by a literal, the front-end guarantees that we don't go out of bounds. + // But when indexing by a loop variable, it's possible to generate out-of-bounds access. + // The GLSL spec leaves that behavior undefined - we'll just clamp everything here. + indexValue = SkTPin(indexValue, 0, expr.base()->type().columns() - 1); + + size_t stride = expr.type().slotCount(); + return indexValue * stride; +} + +Value SkVMGenerator::writeIndexExpression(const IndexExpression& expr) { + Value base = this->writeExpression(*expr.base()); + Value element(expr.type().slotCount()); + size_t offset = this->indexSlotOffset(expr); + for (size_t i = 0; i < element.slots(); ++i) { + element[i] = base[offset + i]; + } + return element; +} + +Value SkVMGenerator::writeVariableExpression(const VariableReference& expr) { + size_t slot = this->getSlot(*expr.variable()); + return this->getSlotValue(slot, expr.type().slotCount()); +} + +Value SkVMGenerator::writeMatrixInverse2x2(const Value& m) { + SkASSERT(m.slots() == 4); + skvm::F32 a = f32(m[0]), + b = f32(m[1]), + c = f32(m[2]), + d = f32(m[3]); + skvm::F32 idet = 1.0f / (a*d - b*c); + + Value result(m.slots()); + result[0] = ( d ** idet); + result[1] = (-b ** idet); + result[2] = (-c ** idet); + result[3] = ( a ** idet); + return result; +} + +Value SkVMGenerator::writeMatrixInverse3x3(const Value& m) { + SkASSERT(m.slots() == 9); + skvm::F32 a11 = f32(m[0]), a12 = f32(m[3]), a13 = f32(m[6]), + a21 = f32(m[1]), a22 = f32(m[4]), a23 = f32(m[7]), + a31 = f32(m[2]), a32 = f32(m[5]), a33 = f32(m[8]); + skvm::F32 idet = 1.0f / (a11*a22*a33 + a12*a23*a31 + a13*a21*a32 - + a11*a23*a32 - a12*a21*a33 - a13*a22*a31); + + Value result(m.slots()); + result[0] = ((a22**a33 - a23**a32) ** idet); + result[1] = ((a23**a31 - a21**a33) ** idet); + result[2] = ((a21**a32 - a22**a31) ** idet); + result[3] = ((a13**a32 - a12**a33) ** idet); + result[4] = ((a11**a33 - a13**a31) ** idet); + result[5] = ((a12**a31 - a11**a32) ** idet); + result[6] = ((a12**a23 - a13**a22) ** idet); + result[7] = ((a13**a21 - a11**a23) ** idet); + result[8] = ((a11**a22 - a12**a21) ** idet); + return result; +} + +Value SkVMGenerator::writeMatrixInverse4x4(const Value& m) { + SkASSERT(m.slots() == 16); + skvm::F32 a00 = f32(m[0]), a10 = f32(m[4]), a20 = f32(m[ 8]), a30 = f32(m[12]), + a01 = f32(m[1]), a11 = f32(m[5]), a21 = f32(m[ 9]), a31 = f32(m[13]), + a02 = f32(m[2]), a12 = f32(m[6]), a22 = f32(m[10]), a32 = f32(m[14]), + a03 = f32(m[3]), a13 = f32(m[7]), a23 = f32(m[11]), a33 = f32(m[15]); + + skvm::F32 b00 = a00**a11 - a01**a10, + b01 = a00**a12 - a02**a10, + b02 = a00**a13 - a03**a10, + b03 = a01**a12 - a02**a11, + b04 = a01**a13 - a03**a11, + b05 = a02**a13 - a03**a12, + b06 = a20**a31 - a21**a30, + b07 = a20**a32 - a22**a30, + b08 = a20**a33 - a23**a30, + b09 = a21**a32 - a22**a31, + b10 = a21**a33 - a23**a31, + b11 = a22**a33 - a23**a32; + + skvm::F32 idet = 1.0f / (b00**b11 - b01**b10 + b02**b09 + b03**b08 - b04**b07 + b05**b06); + + b00 *= idet; + b01 *= idet; + b02 *= idet; + b03 *= idet; + b04 *= idet; + b05 *= idet; + b06 *= idet; + b07 *= idet; + b08 *= idet; + b09 *= idet; + b10 *= idet; + b11 *= idet; + + Value result(m.slots()); + result[ 0] = (a11*b11 - a12*b10 + a13*b09); + result[ 1] = (a02*b10 - a01*b11 - a03*b09); + result[ 2] = (a31*b05 - a32*b04 + a33*b03); + result[ 3] = (a22*b04 - a21*b05 - a23*b03); + result[ 4] = (a12*b08 - a10*b11 - a13*b07); + result[ 5] = (a00*b11 - a02*b08 + a03*b07); + result[ 6] = (a32*b02 - a30*b05 - a33*b01); + result[ 7] = (a20*b05 - a22*b02 + a23*b01); + result[ 8] = (a10*b10 - a11*b08 + a13*b06); + result[ 9] = (a01*b08 - a00*b10 - a03*b06); + result[10] = (a30*b04 - a31*b02 + a33*b00); + result[11] = (a21*b02 - a20*b04 - a23*b00); + result[12] = (a11*b07 - a10*b09 - a12*b06); + result[13] = (a00*b09 - a01*b07 + a02*b06); + result[14] = (a31*b01 - a30*b03 - a32*b00); + result[15] = (a20*b03 - a21*b01 + a22*b00); + return result; +} + +Value SkVMGenerator::writeChildCall(const ChildCall& c) { + size_t* childPtr = fSlotMap.find(&c.child()); + SkASSERT(childPtr != nullptr); + + const Expression* arg = c.arguments()[0].get(); + Value argVal = this->writeExpression(*arg); + skvm::Color color; + + switch (c.child().type().typeKind()) { + case Type::TypeKind::kShader: { + SkASSERT(c.arguments().size() == 1); + SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fFloat2)); + skvm::Coord coord = {f32(argVal[0]), f32(argVal[1])}; + color = fCallbacks->sampleShader(*childPtr, coord); + break; + } + case Type::TypeKind::kColorFilter: { + SkASSERT(c.arguments().size() == 1); + SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fHalf4) || + arg->type().matches(*fProgram.fContext->fTypes.fFloat4)); + skvm::Color inColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])}; + color = fCallbacks->sampleColorFilter(*childPtr, inColor); + break; + } + case Type::TypeKind::kBlender: { + SkASSERT(c.arguments().size() == 2); + SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fHalf4) || + arg->type().matches(*fProgram.fContext->fTypes.fFloat4)); + skvm::Color srcColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])}; + + arg = c.arguments()[1].get(); + argVal = this->writeExpression(*arg); + SkASSERT(arg->type().matches(*fProgram.fContext->fTypes.fHalf4) || + arg->type().matches(*fProgram.fContext->fTypes.fFloat4)); + skvm::Color dstColor = {f32(argVal[0]), f32(argVal[1]), f32(argVal[2]), f32(argVal[3])}; + + color = fCallbacks->sampleBlender(*childPtr, srcColor, dstColor); + break; + } + default: { + SkDEBUGFAILF("cannot sample from type '%s'", c.child().type().description().c_str()); + } + } + + Value result(4); + result[0] = color.r; + result[1] = color.g; + result[2] = color.b; + result[3] = color.a; + return result; +} + +Value SkVMGenerator::writeIntrinsicCall(const FunctionCall& c) { + IntrinsicKind intrinsicKind = c.function().intrinsicKind(); + SkASSERT(intrinsicKind != kNotIntrinsic); + + const size_t nargs = c.arguments().size(); + const size_t kMaxArgs = 3; // eg: clamp, mix, smoothstep + Value args[kMaxArgs]; + SkASSERT(nargs >= 1 && nargs <= std::size(args)); + + // All other intrinsics have at most three args, and those can all be evaluated up front: + for (size_t i = 0; i < nargs; ++i) { + args[i] = this->writeExpression(*c.arguments()[i]); + } + Type::NumberKind nk = base_number_kind(c.arguments()[0]->type()); + + auto binary = [&](auto&& fn) { + // Binary intrinsics are (vecN, vecN), (vecN, float), or (float, vecN) + size_t nslots = std::max(args[0].slots(), args[1].slots()); + Value result(nslots); + SkASSERT(args[0].slots() == nslots || args[0].slots() == 1); + SkASSERT(args[1].slots() == nslots || args[1].slots() == 1); + + for (size_t i = 0; i < nslots; ++i) { + result[i] = fn({fBuilder, args[0][args[0].slots() == 1 ? 0 : i]}, + {fBuilder, args[1][args[1].slots() == 1 ? 0 : i]}); + } + return result; + }; + + auto ternary = [&](auto&& fn) { + // Ternary intrinsics are some combination of vecN and float + size_t nslots = std::max({args[0].slots(), args[1].slots(), args[2].slots()}); + Value result(nslots); + SkASSERT(args[0].slots() == nslots || args[0].slots() == 1); + SkASSERT(args[1].slots() == nslots || args[1].slots() == 1); + SkASSERT(args[2].slots() == nslots || args[2].slots() == 1); + + for (size_t i = 0; i < nslots; ++i) { + result[i] = fn({fBuilder, args[0][args[0].slots() == 1 ? 0 : i]}, + {fBuilder, args[1][args[1].slots() == 1 ? 0 : i]}, + {fBuilder, args[2][args[2].slots() == 1 ? 0 : i]}); + } + return result; + }; + + auto dot = [&](const Value& x, const Value& y) { + SkASSERT(x.slots() == y.slots()); + skvm::F32 result = f32(x[0]) * f32(y[0]); + for (size_t i = 1; i < x.slots(); ++i) { + result += f32(x[i]) * f32(y[i]); + } + return result; + }; + + switch (intrinsicKind) { + case k_radians_IntrinsicKind: + return unary(args[0], [](skvm::F32 deg) { return deg * (SK_FloatPI / 180); }); + case k_degrees_IntrinsicKind: + return unary(args[0], [](skvm::F32 rad) { return rad * (180 / SK_FloatPI); }); + + case k_sin_IntrinsicKind: return unary(args[0], skvm::approx_sin); + case k_cos_IntrinsicKind: return unary(args[0], skvm::approx_cos); + case k_tan_IntrinsicKind: return unary(args[0], skvm::approx_tan); + + case k_asin_IntrinsicKind: return unary(args[0], skvm::approx_asin); + case k_acos_IntrinsicKind: return unary(args[0], skvm::approx_acos); + + case k_atan_IntrinsicKind: return nargs == 1 ? unary(args[0], skvm::approx_atan) + : binary(skvm::approx_atan2); + + case k_pow_IntrinsicKind: + return binary([](skvm::F32 x, skvm::F32 y) { return skvm::approx_powf(x, y); }); + case k_exp_IntrinsicKind: return unary(args[0], skvm::approx_exp); + case k_log_IntrinsicKind: return unary(args[0], skvm::approx_log); + case k_exp2_IntrinsicKind: return unary(args[0], skvm::approx_pow2); + case k_log2_IntrinsicKind: return unary(args[0], skvm::approx_log2); + + case k_sqrt_IntrinsicKind: return unary(args[0], skvm::sqrt); + case k_inversesqrt_IntrinsicKind: + return unary(args[0], [](skvm::F32 x) { return 1.0f / skvm::sqrt(x); }); + + case k_abs_IntrinsicKind: return unary(args[0], skvm::abs); + case k_sign_IntrinsicKind: + return unary(args[0], [](skvm::F32 x) { return select(x < 0, -1.0f, + select(x > 0, +1.0f, 0.0f)); }); + case k_floor_IntrinsicKind: return unary(args[0], skvm::floor); + case k_ceil_IntrinsicKind: return unary(args[0], skvm::ceil); + case k_fract_IntrinsicKind: return unary(args[0], skvm::fract); + case k_mod_IntrinsicKind: + return binary([](skvm::F32 x, skvm::F32 y) { return x - y*skvm::floor(x / y); }); + + case k_min_IntrinsicKind: + return binary([](skvm::F32 x, skvm::F32 y) { return skvm::min(x, y); }); + case k_max_IntrinsicKind: + return binary([](skvm::F32 x, skvm::F32 y) { return skvm::max(x, y); }); + case k_clamp_IntrinsicKind: + return ternary( + [](skvm::F32 x, skvm::F32 lo, skvm::F32 hi) { return skvm::clamp(x, lo, hi); }); + case k_saturate_IntrinsicKind: + return unary(args[0], [](skvm::F32 x) { return skvm::clamp01(x); }); + case k_mix_IntrinsicKind: + return ternary( + [](skvm::F32 x, skvm::F32 y, skvm::F32 t) { return skvm::lerp(x, y, t); }); + case k_step_IntrinsicKind: + return binary([](skvm::F32 edge, skvm::F32 x) { return select(x < edge, 0.0f, 1.0f); }); + case k_smoothstep_IntrinsicKind: + return ternary([](skvm::F32 edge0, skvm::F32 edge1, skvm::F32 x) { + skvm::F32 t = skvm::clamp01((x - edge0) / (edge1 - edge0)); + return t ** t ** (3 - 2 ** t); + }); + + case k_length_IntrinsicKind: return skvm::sqrt(dot(args[0], args[0])); + case k_distance_IntrinsicKind: { + Value vec = binary([](skvm::F32 x, skvm::F32 y) { return x - y; }); + return skvm::sqrt(dot(vec, vec)); + } + case k_dot_IntrinsicKind: return dot(args[0], args[1]); + case k_cross_IntrinsicKind: { + skvm::F32 ax = f32(args[0][0]), ay = f32(args[0][1]), az = f32(args[0][2]), + bx = f32(args[1][0]), by = f32(args[1][1]), bz = f32(args[1][2]); + Value result(3); + result[0] = ay**bz - az**by; + result[1] = az**bx - ax**bz; + result[2] = ax**by - ay**bx; + return result; + } + case k_normalize_IntrinsicKind: { + skvm::F32 invLen = 1.0f / skvm::sqrt(dot(args[0], args[0])); + return unary(args[0], [&](skvm::F32 x) { return x ** invLen; }); + } + case k_faceforward_IntrinsicKind: { + const Value &N = args[0], + &I = args[1], + &Nref = args[2]; + + skvm::F32 dotNrefI = dot(Nref, I); + return unary(N, [&](skvm::F32 n) { return select(dotNrefI<0, n, -n); }); + } + case k_reflect_IntrinsicKind: { + const Value &I = args[0], + &N = args[1]; + + skvm::F32 dotNI = dot(N, I); + return binary([&](skvm::F32 i, skvm::F32 n) { + return i - 2**dotNI**n; + }); + } + case k_refract_IntrinsicKind: { + const Value &I = args[0], + &N = args[1]; + skvm::F32 eta = f32(args[2]); + + skvm::F32 dotNI = dot(N, I), + k = 1 - eta**eta**(1 - dotNI**dotNI); + return binary([&](skvm::F32 i, skvm::F32 n) { + return select(k<0, 0.0f, eta**i - (eta**dotNI + sqrt(k))**n); + }); + } + + case k_matrixCompMult_IntrinsicKind: + return binary([](skvm::F32 x, skvm::F32 y) { return x ** y; }); + case k_inverse_IntrinsicKind: { + switch (args[0].slots()) { + case 4: return this->writeMatrixInverse2x2(args[0]); + case 9: return this->writeMatrixInverse3x3(args[0]); + case 16: return this->writeMatrixInverse4x4(args[0]); + default: + SkDEBUGFAIL("Invalid call to inverse"); + return {}; + } + } + + case k_lessThan_IntrinsicKind: + return nk == Type::NumberKind::kFloat + ? binary([](skvm::F32 x, skvm::F32 y) { return x < y; }) + : binary([](skvm::I32 x, skvm::I32 y) { return x < y; }); + case k_lessThanEqual_IntrinsicKind: + return nk == Type::NumberKind::kFloat + ? binary([](skvm::F32 x, skvm::F32 y) { return x <= y; }) + : binary([](skvm::I32 x, skvm::I32 y) { return x <= y; }); + case k_greaterThan_IntrinsicKind: + return nk == Type::NumberKind::kFloat + ? binary([](skvm::F32 x, skvm::F32 y) { return x > y; }) + : binary([](skvm::I32 x, skvm::I32 y) { return x > y; }); + case k_greaterThanEqual_IntrinsicKind: + return nk == Type::NumberKind::kFloat + ? binary([](skvm::F32 x, skvm::F32 y) { return x >= y; }) + : binary([](skvm::I32 x, skvm::I32 y) { return x >= y; }); + + case k_equal_IntrinsicKind: + return nk == Type::NumberKind::kFloat + ? binary([](skvm::F32 x, skvm::F32 y) { return x == y; }) + : binary([](skvm::I32 x, skvm::I32 y) { return x == y; }); + case k_notEqual_IntrinsicKind: + return nk == Type::NumberKind::kFloat + ? binary([](skvm::F32 x, skvm::F32 y) { return x != y; }) + : binary([](skvm::I32 x, skvm::I32 y) { return x != y; }); + + case k_any_IntrinsicKind: { + skvm::I32 result = i32(args[0][0]); + for (size_t i = 1; i < args[0].slots(); ++i) { + result |= i32(args[0][i]); + } + return result; + } + case k_all_IntrinsicKind: { + skvm::I32 result = i32(args[0][0]); + for (size_t i = 1; i < args[0].slots(); ++i) { + result &= i32(args[0][i]); + } + return result; + } + case k_not_IntrinsicKind: return unary(args[0], [](skvm::I32 x) { return ~x; }); + + case k_toLinearSrgb_IntrinsicKind: { + skvm::Color color = { + f32(args[0][0]), f32(args[0][1]), f32(args[0][2]), fBuilder->splat(1.0f)}; + color = fCallbacks->toLinearSrgb(color); + Value result(3); + result[0] = color.r; + result[1] = color.g; + result[2] = color.b; + return result; + } + case k_fromLinearSrgb_IntrinsicKind: { + skvm::Color color = { + f32(args[0][0]), f32(args[0][1]), f32(args[0][2]), fBuilder->splat(1.0f)}; + color = fCallbacks->fromLinearSrgb(color); + Value result(3); + result[0] = color.r; + result[1] = color.g; + result[2] = color.b; + return result; + } + + default: + SkDEBUGFAILF("unsupported intrinsic %s", c.function().description().c_str()); + return {}; + } + SkUNREACHABLE; +} + +Value SkVMGenerator::writeFunctionCall(const FunctionCall& call) { + if (call.function().isIntrinsic() && !call.function().definition()) { + return this->writeIntrinsicCall(call); + } + + const FunctionDeclaration& decl = call.function(); + SkASSERTF(decl.definition(), "no definition for function '%s'", decl.description().c_str()); + const FunctionDefinition& funcDef = *decl.definition(); + + // Evaluate all arguments, gather the results into a contiguous list of IDs + std::vector<skvm::Val> argVals; + for (const auto& arg : call.arguments()) { + Value v = this->writeExpression(*arg); + for (size_t i = 0; i < v.slots(); ++i) { + argVals.push_back(v[i]); + } + } + + size_t returnSlot; + { + // This merges currentFunction().fReturned into fConditionMask. Lanes that conditionally + // returned in the current function would otherwise resume execution within the child. + ScopedCondition m(this, ~currentFunction().fReturned); + returnSlot = this->writeFunction(call, funcDef, SkSpan(argVals)); + } + + // Propagate new values of any 'out' params back to the original arguments + const std::unique_ptr<Expression>* argIter = call.arguments().begin(); + size_t valIdx = 0; + for (const Variable* p : decl.parameters()) { + size_t nslots = p->type().slotCount(); + if (p->modifiers().fFlags & Modifiers::kOut_Flag) { + Value v(nslots); + for (size_t i = 0; i < nslots; ++i) { + v[i] = argVals[valIdx + i]; + } + const std::unique_ptr<Expression>& arg = *argIter; + this->writeStore(*arg, v); + } + valIdx += nslots; + argIter++; + } + + // Create a result Value from the return slot + return this->getSlotValue(returnSlot, call.type().slotCount()); +} + +Value SkVMGenerator::writeLiteral(const Literal& l) { + if (l.type().isFloat()) { + return fBuilder->splat(l.as<Literal>().floatValue()); + } + if (l.type().isInteger()) { + return fBuilder->splat(static_cast<int>(l.as<Literal>().intValue())); + } + SkASSERT(l.type().isBoolean()); + return fBuilder->splat(l.as<Literal>().boolValue() ? ~0 : 0); +} + +Value SkVMGenerator::writePrefixExpression(const PrefixExpression& p) { + Value val = this->writeExpression(*p.operand()); + + switch (p.getOperator().kind()) { + case Operator::Kind::PLUSPLUS: + case Operator::Kind::MINUSMINUS: { + bool incr = p.getOperator().kind() == Operator::Kind::PLUSPLUS; + + switch (base_number_kind(p.type())) { + case Type::NumberKind::kFloat: + val = f32(val) + fBuilder->splat(incr ? 1.0f : -1.0f); + break; + case Type::NumberKind::kSigned: + val = i32(val) + fBuilder->splat(incr ? 1 : -1); + break; + default: + SkASSERT(false); + return {}; + } + return this->writeStore(*p.operand(), val); + } + case Operator::Kind::MINUS: { + switch (base_number_kind(p.type())) { + case Type::NumberKind::kFloat: + return this->unary(val, [](skvm::F32 x) { return -x; }); + case Type::NumberKind::kSigned: + return this->unary(val, [](skvm::I32 x) { return -x; }); + default: + SkASSERT(false); + return {}; + } + } + case Operator::Kind::LOGICALNOT: + case Operator::Kind::BITWISENOT: + return this->unary(val, [](skvm::I32 x) { return ~x; }); + default: + SkASSERT(false); + return {}; + } +} + +Value SkVMGenerator::writePostfixExpression(const PostfixExpression& p) { + switch (p.getOperator().kind()) { + case Operator::Kind::PLUSPLUS: + case Operator::Kind::MINUSMINUS: { + Value old = this->writeExpression(*p.operand()), + val = old; + SkASSERT(val.slots() == 1); + bool incr = p.getOperator().kind() == Operator::Kind::PLUSPLUS; + + switch (base_number_kind(p.type())) { + case Type::NumberKind::kFloat: + val = f32(val) + fBuilder->splat(incr ? 1.0f : -1.0f); + break; + case Type::NumberKind::kSigned: + val = i32(val) + fBuilder->splat(incr ? 1 : -1); + break; + default: + SkASSERT(false); + return {}; + } + this->writeStore(*p.operand(), val); + return old; + } + default: + SkASSERT(false); + return {}; + } +} + +Value SkVMGenerator::writeSwizzle(const Swizzle& s) { + Value base = this->writeExpression(*s.base()); + Value swizzled(s.components().size()); + for (int i = 0; i < s.components().size(); ++i) { + swizzled[i] = base[s.components()[i]]; + } + return swizzled; +} + +Value SkVMGenerator::writeTernaryExpression(const TernaryExpression& t) { + skvm::I32 test = i32(this->writeExpression(*t.test())); + Value ifTrue, ifFalse; + + { + ScopedCondition m(this, test); + ifTrue = this->writeExpression(*t.ifTrue()); + } + { + ScopedCondition m(this, ~test); + ifFalse = this->writeExpression(*t.ifFalse()); + } + + size_t nslots = ifTrue.slots(); + SkASSERT(nslots == ifFalse.slots()); + + Value result(nslots); + for (size_t i = 0; i < nslots; ++i) { + result[i] = skvm::select(test, i32(ifTrue[i]), i32(ifFalse[i])); + } + return result; +} + +Value SkVMGenerator::writeExpression(const Expression& e) { + switch (e.kind()) { + case Expression::Kind::kBinary: + return this->writeBinaryExpression(e.as<BinaryExpression>()); + case Expression::Kind::kChildCall: + return this->writeChildCall(e.as<ChildCall>()); + case Expression::Kind::kConstructorArray: + case Expression::Kind::kConstructorCompound: + case Expression::Kind::kConstructorStruct: + return this->writeAggregationConstructor(e.asAnyConstructor()); + case Expression::Kind::kConstructorArrayCast: + return this->writeExpression(*e.as<ConstructorArrayCast>().argument()); + case Expression::Kind::kConstructorDiagonalMatrix: + return this->writeConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>()); + case Expression::Kind::kConstructorMatrixResize: + return this->writeConstructorMatrixResize(e.as<ConstructorMatrixResize>()); + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorCompoundCast: + return this->writeConstructorCast(e.asAnyConstructor()); + case Expression::Kind::kConstructorSplat: + return this->writeConstructorSplat(e.as<ConstructorSplat>()); + case Expression::Kind::kFieldAccess: + return this->writeFieldAccess(e.as<FieldAccess>()); + case Expression::Kind::kIndex: + return this->writeIndexExpression(e.as<IndexExpression>()); + case Expression::Kind::kVariableReference: + return this->writeVariableExpression(e.as<VariableReference>()); + case Expression::Kind::kLiteral: + return this->writeLiteral(e.as<Literal>()); + case Expression::Kind::kFunctionCall: + return this->writeFunctionCall(e.as<FunctionCall>()); + case Expression::Kind::kPrefix: + return this->writePrefixExpression(e.as<PrefixExpression>()); + case Expression::Kind::kPostfix: + return this->writePostfixExpression(e.as<PostfixExpression>()); + case Expression::Kind::kSwizzle: + return this->writeSwizzle(e.as<Swizzle>()); + case Expression::Kind::kTernary: + return this->writeTernaryExpression(e.as<TernaryExpression>()); + default: + SkDEBUGFAIL("Unsupported expression"); + return {}; + } +} + +Value SkVMGenerator::writeStore(const Expression& lhs, const Value& rhs) { + SkASSERTF(rhs.slots() == lhs.type().slotCount(), + "lhs=%s (%s)\nrhs=%zu slot", + lhs.type().description().c_str(), lhs.description().c_str(), rhs.slots()); + + // We need to figure out the collection of slots that we're storing into. The l-value (lhs) + // is always a VariableReference, possibly wrapped by one or more Swizzle, FieldAccess, or + // IndexExpressions. The underlying VariableReference has a range of slots for its storage, + // and each expression wrapped around that selects a sub-set of those slots (Field/Index), + // or rearranges them (Swizzle). + SkSTArray<4, size_t, true> slots; + slots.resize(rhs.slots()); + + // Start with the identity slot map - this basically says that the values from rhs belong in + // slots [0, 1, 2 ... N] of the lhs. + for (int i = 0; i < slots.size(); ++i) { + slots[i] = i; + } + + // Now, as we peel off each outer expression, adjust 'slots' to be the locations relative to + // the next (inner) expression: + const Expression* expr = &lhs; + while (!expr->is<VariableReference>()) { + switch (expr->kind()) { + case Expression::Kind::kFieldAccess: { + const FieldAccess& fld = expr->as<FieldAccess>(); + size_t offset = fld.initialSlot(); + for (size_t& s : slots) { + s += offset; + } + expr = fld.base().get(); + } break; + case Expression::Kind::kIndex: { + const IndexExpression& idx = expr->as<IndexExpression>(); + size_t offset = this->indexSlotOffset(idx); + for (size_t& s : slots) { + s += offset; + } + expr = idx.base().get(); + } break; + case Expression::Kind::kSwizzle: { + const Swizzle& swz = expr->as<Swizzle>(); + for (size_t& s : slots) { + s = swz.components()[s]; + } + expr = swz.base().get(); + } break; + default: + // No other kinds of expressions are valid in lvalues. (see Analysis::IsAssignable) + SkDEBUGFAIL("Invalid expression type"); + return {}; + } + } + + // When we get here, 'slots' are all relative to the first slot holding 'var's storage + const Variable& var = *expr->as<VariableReference>().variable(); + size_t varSlot = this->getSlot(var); + for (size_t& slot : slots) { + SkASSERT(slot < var.type().slotCount()); + slot += varSlot; + } + + // `slots` are now absolute indices into `fSlots`. + skvm::I32 mask = this->mask(); + for (size_t i = 0; i < rhs.slots(); ++i) { + int slotNum = slots[i]; + skvm::Val conditionalStore = this->writeConditionalStore(fSlots[slotNum].val, rhs[i], mask); + this->writeToSlot(slotNum, conditionalStore); + } + + return rhs; +} + +skvm::Val SkVMGenerator::writeConditionalStore(skvm::Val lhs, skvm::Val rhs, skvm::I32 mask) { + return select(mask, f32(rhs), f32(lhs)).id; +} + +void SkVMGenerator::writeBlock(const Block& b) { + skvm::I32 mask = this->mask(); + if (b.blockKind() == Block::Kind::kCompoundStatement) { + this->emitTraceLine(this->getLine(b.fPosition)); + ++fInsideCompoundStatement; + } else { + this->emitTraceScope(mask, +1); + } + + for (const std::unique_ptr<Statement>& stmt : b.children()) { + this->writeStatement(*stmt); + } + + if (b.blockKind() == Block::Kind::kCompoundStatement) { + --fInsideCompoundStatement; + } else { + this->emitTraceScope(mask, -1); + } +} + +void SkVMGenerator::writeBreakStatement() { + // Any active lanes stop executing for the duration of the current loop + fLoopMask &= ~this->mask(); +} + +void SkVMGenerator::writeContinueStatement() { + // Any active lanes stop executing for the current iteration. + // Remember them in fContinueMask, to be re-enabled later. + skvm::I32 mask = this->mask(); + fLoopMask &= ~mask; + fContinueMask |= mask; +} + +void SkVMGenerator::writeForStatement(const ForStatement& f) { + // We require that all loops be ES2-compliant (unrollable), and actually unroll them here + SkASSERT(f.unrollInfo()); + const LoopUnrollInfo& loop = *f.unrollInfo(); + SkASSERT(loop.fIndex->type().slotCount() == 1); + + size_t indexSlot = this->getSlot(*loop.fIndex); + double val = loop.fStart; + + const skvm::I32 zero = fBuilder->splat(0); + skvm::I32 oldLoopMask = fLoopMask, + oldContinueMask = fContinueMask; + + const Type::NumberKind indexKind = base_number_kind(loop.fIndex->type()); + + // We want the loop index to disappear at the end of the loop, so wrap the for statement in a + // trace scope. + if (loop.fCount > 0) { + int line = this->getLine(f.test() ? f.test()->fPosition : f.fPosition); + skvm::I32 mask = this->mask(); + this->emitTraceScope(mask, +1); + + for (int i = 0; i < loop.fCount; ++i) { + this->writeToSlot(indexSlot, (indexKind == Type::NumberKind::kFloat) + ? fBuilder->splat(static_cast<float>(val)).id + : fBuilder->splat(static_cast<int>(val)).id); + + fContinueMask = zero; + this->writeStatement(*f.statement()); + fLoopMask |= fContinueMask; + + this->emitTraceLine(line); + val += loop.fDelta; + } + + this->emitTraceScope(mask, -1); + } + + fLoopMask = oldLoopMask; + fContinueMask = oldContinueMask; +} + +void SkVMGenerator::writeIfStatement(const IfStatement& i) { + Value test = this->writeExpression(*i.test()); + { + ScopedCondition ifTrue(this, i32(test)); + this->writeStatement(*i.ifTrue()); + } + if (i.ifFalse()) { + ScopedCondition ifFalse(this, ~i32(test)); + this->writeStatement(*i.ifFalse()); + } +} + +void SkVMGenerator::writeReturnStatement(const ReturnStatement& r) { + skvm::I32 returnsHere = this->mask(); + + if (r.expression()) { + Value val = this->writeExpression(*r.expression()); + + size_t slot = currentFunction().fReturnSlot; + size_t nslots = r.expression()->type().slotCount(); + for (size_t i = 0; i < nslots; ++i) { + fSlots[slot + i].writtenTo = false; + skvm::Val conditionalStore = this->writeConditionalStore(fSlots[slot + i].val, val[i], + returnsHere); + this->writeToSlot(slot + i, conditionalStore); + } + } + + currentFunction().fReturned |= returnsHere; +} + +void SkVMGenerator::writeSwitchStatement(const SwitchStatement& s) { + skvm::I32 falseValue = fBuilder->splat( 0); + skvm::I32 trueValue = fBuilder->splat(~0); + + // Create a "switchFallthough" scratch variable, initialized to false. + skvm::I32 switchFallthrough = falseValue; + + // Loop masks behave just like for statements. When a break is encountered, it masks off all + // lanes for the rest of the body of the switch. + skvm::I32 oldLoopMask = fLoopMask; + Value switchValue = this->writeExpression(*s.value()); + + for (const std::unique_ptr<Statement>& stmt : s.cases()) { + const SwitchCase& c = stmt->as<SwitchCase>(); + if (!c.isDefault()) { + Value caseValue = fBuilder->splat((int) c.value()); + + // We want to execute this switch case if we're falling through from a previous case, or + // if the case value matches. + ScopedCondition conditionalCaseBlock( + this, + switchFallthrough | (i32(caseValue) == i32(switchValue))); + this->writeStatement(*c.statement()); + + // If we are inside the case block, we set the fallthrough flag to true (`break` still + // works to stop the flow of execution regardless, since it zeroes out the loop-mask). + switchFallthrough.id = this->writeConditionalStore(switchFallthrough.id, trueValue.id, + this->mask()); + } else { + // This is the default case. Since it's always last, we can just dump in the code. + this->writeStatement(*c.statement()); + } + } + + // Restore state. + fLoopMask = oldLoopMask; +} + +void SkVMGenerator::writeVarDeclaration(const VarDeclaration& decl) { + size_t slot = this->getSlot(*decl.var()), + nslots = decl.var()->type().slotCount(); + + Value val = decl.value() ? this->writeExpression(*decl.value()) : Value{}; + for (size_t i = 0; i < nslots; ++i) { + fSlots[slot + i].writtenTo = false; + this->writeToSlot(slot + i, val ? val[i] : fBuilder->splat(0.0f).id); + } +} + +void SkVMGenerator::emitTraceLine(int line) { + if (fDebugTrace && line > 0 && fInsideCompoundStatement == 0) { + fBuilder->trace_line(fTraceHookID, this->mask(), fTraceMask, line); + } +} + +void SkVMGenerator::emitTraceScope(skvm::I32 executionMask, int delta) { + if (fDebugTrace) { + fBuilder->trace_scope(fTraceHookID, executionMask, fTraceMask, delta); + } +} + +void SkVMGenerator::writeStatement(const Statement& s) { + // The debugger should stop on all types of statements, except for Blocks. + if (!s.is<Block>()) { + this->emitTraceLine(this->getLine(s.fPosition)); + } + + switch (s.kind()) { + case Statement::Kind::kBlock: + this->writeBlock(s.as<Block>()); + break; + case Statement::Kind::kBreak: + this->writeBreakStatement(); + break; + case Statement::Kind::kContinue: + this->writeContinueStatement(); + break; + case Statement::Kind::kExpression: + this->writeExpression(*s.as<ExpressionStatement>().expression()); + break; + case Statement::Kind::kFor: + this->writeForStatement(s.as<ForStatement>()); + break; + case Statement::Kind::kIf: + this->writeIfStatement(s.as<IfStatement>()); + break; + case Statement::Kind::kReturn: + this->writeReturnStatement(s.as<ReturnStatement>()); + break; + case Statement::Kind::kSwitch: + this->writeSwitchStatement(s.as<SwitchStatement>()); + break; + case Statement::Kind::kVarDeclaration: + this->writeVarDeclaration(s.as<VarDeclaration>()); + break; + case Statement::Kind::kDiscard: + case Statement::Kind::kDo: + SkDEBUGFAIL("Unsupported control flow"); + break; + case Statement::Kind::kNop: + break; + default: + SkDEBUGFAIL("Unrecognized statement"); + break; + } +} + +skvm::Color ProgramToSkVM(const Program& program, + const FunctionDefinition& function, + skvm::Builder* builder, + SkVMDebugTrace* debugTrace, + SkSpan<skvm::Val> uniforms, + skvm::Coord device, + skvm::Coord local, + skvm::Color inputColor, + skvm::Color destColor, + SkVMCallbacks* callbacks) { + skvm::Val zero = builder->splat(0.0f).id; + skvm::Val result[4] = {zero,zero,zero,zero}; + + skvm::Val args[8]; // At most 8 arguments (half4 srcColor, half4 dstColor) + size_t argSlots = 0; + for (const SkSL::Variable* param : function.declaration().parameters()) { + switch (param->modifiers().fLayout.fBuiltin) { + case SK_MAIN_COORDS_BUILTIN: + SkASSERT(param->type().slotCount() == 2); + SkASSERT((argSlots + 2) <= std::size(args)); + args[argSlots++] = local.x.id; + args[argSlots++] = local.y.id; + break; + case SK_INPUT_COLOR_BUILTIN: + SkASSERT(param->type().slotCount() == 4); + SkASSERT((argSlots + 4) <= std::size(args)); + args[argSlots++] = inputColor.r.id; + args[argSlots++] = inputColor.g.id; + args[argSlots++] = inputColor.b.id; + args[argSlots++] = inputColor.a.id; + break; + case SK_DEST_COLOR_BUILTIN: + SkASSERT(param->type().slotCount() == 4); + SkASSERT((argSlots + 4) <= std::size(args)); + args[argSlots++] = destColor.r.id; + args[argSlots++] = destColor.g.id; + args[argSlots++] = destColor.b.id; + args[argSlots++] = destColor.a.id; + break; + default: + SkDEBUGFAIL("Invalid parameter to main()"); + return {}; + } + } + SkASSERT(argSlots <= std::size(args)); + + // Make sure that the SkVMDebugTrace starts from a clean slate. + if (debugTrace) { + debugTrace->fSlotInfo.clear(); + debugTrace->fFuncInfo.clear(); + debugTrace->fTraceInfo.clear(); + } + + SkVMGenerator generator(program, builder, debugTrace, callbacks); + generator.writeProgram(uniforms, device, function, {args, argSlots}, SkSpan(result)); + + return skvm::Color{{builder, result[0]}, + {builder, result[1]}, + {builder, result[2]}, + {builder, result[3]}}; +} + +bool ProgramToSkVM(const Program& program, + const FunctionDefinition& function, + skvm::Builder* b, + SkVMDebugTrace* debugTrace, + SkSpan<skvm::Val> uniforms, + SkVMSignature* outSignature) { + SkVMSignature ignored, + *signature = outSignature ? outSignature : &ignored; + + std::vector<skvm::Ptr> argPtrs; + std::vector<skvm::Val> argVals; + + for (const Variable* p : function.declaration().parameters()) { + size_t slots = p->type().slotCount(); + signature->fParameterSlots += slots; + for (size_t i = 0; i < slots; ++i) { + argPtrs.push_back(b->varying<float>()); + argVals.push_back(b->loadF(argPtrs.back()).id); + } + } + + std::vector<skvm::Ptr> returnPtrs; + std::vector<skvm::Val> returnVals; + + signature->fReturnSlots = function.declaration().returnType().slotCount(); + for (size_t i = 0; i < signature->fReturnSlots; ++i) { + returnPtrs.push_back(b->varying<float>()); + returnVals.push_back(b->splat(0.0f).id); + } + + class Callbacks : public SkVMCallbacks { + public: + Callbacks(skvm::Color color) : fColor(color) {} + + skvm::Color sampleShader(int, skvm::Coord) override { + fUsedUnsupportedFeatures = true; + return fColor; + } + skvm::Color sampleColorFilter(int, skvm::Color) override { + fUsedUnsupportedFeatures = true; + return fColor; + } + skvm::Color sampleBlender(int, skvm::Color, skvm::Color) override { + fUsedUnsupportedFeatures = true; + return fColor; + } + + skvm::Color toLinearSrgb(skvm::Color) override { + fUsedUnsupportedFeatures = true; + return fColor; + } + skvm::Color fromLinearSrgb(skvm::Color) override { + fUsedUnsupportedFeatures = true; + return fColor; + } + + bool fUsedUnsupportedFeatures = false; + const skvm::Color fColor; + }; + + // Set up device coordinates so that the rightmost evaluated pixel will be centered on (0, 0). + // (If the coordinates aren't used, dead-code elimination will optimize this away.) + skvm::F32 pixelCenter = b->splat(0.5f); + skvm::Coord device = {pixelCenter, pixelCenter}; + device.x += to_F32(b->splat(1) - b->index()); + + skvm::F32 zero = b->splat(0.0f); + skvm::Color sampledColor{zero, zero, zero, zero}; + Callbacks callbacks(sampledColor); + + SkVMGenerator generator(program, b, debugTrace, &callbacks); + generator.writeProgram(uniforms, device, function, SkSpan(argVals), SkSpan(returnVals)); + + // If the SkSL tried to use any shader, colorFilter, or blender objects - we don't have a + // mechanism (yet) for binding to those. + if (callbacks.fUsedUnsupportedFeatures) { + return false; + } + + // generateCode has updated the contents of 'argVals' for any 'out' or 'inout' parameters. + // Propagate those changes back to our varying buffers: + size_t argIdx = 0; + for (const Variable* p : function.declaration().parameters()) { + size_t nslots = p->type().slotCount(); + if (p->modifiers().fFlags & Modifiers::kOut_Flag) { + for (size_t i = 0; i < nslots; ++i) { + b->storeF(argPtrs[argIdx + i], skvm::F32{b, argVals[argIdx + i]}); + } + } + argIdx += nslots; + } + + // It's also updated the contents of 'returnVals' with the return value of the entry point. + // Store that as well: + for (size_t i = 0; i < signature->fReturnSlots; ++i) { + b->storeF(returnPtrs[i], skvm::F32{b, returnVals[i]}); + } + + return true; +} + +/* + * Testing utility function that emits program's "main" with a minimal harness. Used to create + * representative skvm op sequences for SkSL tests. + */ +bool testingOnly_ProgramToSkVMShader(const Program& program, + skvm::Builder* builder, + SkVMDebugTrace* debugTrace) { + const SkSL::FunctionDeclaration* main = program.getFunction("main"); + if (!main) { + return false; + } + + size_t uniformSlots = 0; + int childSlots = 0; + for (const SkSL::ProgramElement* e : program.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& decl = e->as<GlobalVarDeclaration>(); + const Variable& var = *decl.varDeclaration().var(); + if (var.type().isEffectChild()) { + childSlots++; + } else if (is_uniform(var)) { + uniformSlots += var.type().slotCount(); + } + } + } + + skvm::Uniforms uniforms(builder->uniform(), 0); + + auto new_uni = [&]() { return builder->uniformF(uniforms.pushF(0.0f)); }; + + // Assume identity CTM + skvm::Coord device = {pun_to_F32(builder->index()), new_uni()}; + // Position device coords at pixel centers, so debug traces will trigger + device.x += 0.5f; + device.y += 0.5f; + skvm::Coord local = device; + + class Callbacks : public SkVMCallbacks { + public: + Callbacks(skvm::Builder* builder, skvm::Uniforms* uniforms, int numChildren) { + for (int i = 0; i < numChildren; ++i) { + fChildren.push_back( + {uniforms->pushPtr(nullptr), builder->uniform32(uniforms->push(0))}); + } + } + + skvm::Color sampleShader(int i, skvm::Coord coord) override { + skvm::PixelFormat pixelFormat = skvm::SkColorType_to_PixelFormat(kRGBA_F32_SkColorType); + skvm::I32 index = trunc(coord.x); + index += trunc(coord.y) * fChildren[i].rowBytesAsPixels; + return gather(pixelFormat, fChildren[i].addr, index); + } + + skvm::Color sampleColorFilter(int i, skvm::Color color) override { + return color; + } + + skvm::Color sampleBlender(int i, skvm::Color src, skvm::Color dst) override { + return blend(SkBlendMode::kSrcOver, src, dst); + } + + // TODO(skia:10479): Make these actually convert to/from something like sRGB, for use in + // test files. + skvm::Color toLinearSrgb(skvm::Color color) override { + return color; + } + skvm::Color fromLinearSrgb(skvm::Color color) override { + return color; + } + + struct Child { + skvm::Uniform addr; + skvm::I32 rowBytesAsPixels; + }; + std::vector<Child> fChildren; + }; + Callbacks callbacks(builder, &uniforms, childSlots); + + std::vector<skvm::Val> uniformVals; + for (size_t i = 0; i < uniformSlots; ++i) { + uniformVals.push_back(new_uni().id); + } + + skvm::Color inColor = builder->uniformColor(SkColors::kWhite, &uniforms); + skvm::Color destColor = builder->uniformColor(SkColors::kBlack, &uniforms); + + skvm::Color result = SkSL::ProgramToSkVM(program, *main->definition(), builder, debugTrace, + SkSpan(uniformVals), device, local, inColor, + destColor, &callbacks); + + storeF(builder->varying<float>(), result.r); + storeF(builder->varying<float>(), result.g); + storeF(builder->varying<float>(), result.b); + storeF(builder->varying<float>(), result.a); + + return true; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.h new file mode 100644 index 0000000000..cfff7477bf --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLVMCodeGenerator.h @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VMGENERATOR +#define SKSL_VMGENERATOR + +#include "src/core/SkVM.h" + +#include <cstddef> + +template <typename T> class SkSpan; + +namespace SkSL { + +class FunctionDefinition; +struct Program; +class SkVMDebugTrace; + +class SkVMCallbacks { +public: + virtual ~SkVMCallbacks() = default; + + virtual skvm::Color sampleShader(int index, skvm::Coord coord) = 0; + virtual skvm::Color sampleColorFilter(int index, skvm::Color color) = 0; + virtual skvm::Color sampleBlender(int index, skvm::Color src, skvm::Color dst) = 0; + + virtual skvm::Color toLinearSrgb(skvm::Color color) = 0; + virtual skvm::Color fromLinearSrgb(skvm::Color color) = 0; +}; + +// Convert 'function' to skvm instructions in 'builder', for use by blends, shaders, & color filters +skvm::Color ProgramToSkVM(const Program& program, + const FunctionDefinition& function, + skvm::Builder* builder, + SkVMDebugTrace* debugTrace, + SkSpan<skvm::Val> uniforms, + skvm::Coord device, + skvm::Coord local, + skvm::Color inputColor, + skvm::Color destColor, + SkVMCallbacks* callbacks); + +struct SkVMSignature { + size_t fParameterSlots = 0; + size_t fReturnSlots = 0; +}; + +/* + * Converts 'function' to skvm instructions in 'builder'. Always adds one arg per value in the + * parameter list, then one per value in the return type. For example: + * + * float2 fn(float2 a, float b) { ... } + * + * ... is mapped so that it can be called as: + * + * p.eval(N, &a.x, &a.y, &b, &return.x, &return.y); + * + * The number of parameter and return slots (pointers) is placed in 'outSignature', if provided. + * If the program declares any uniforms, 'uniforms' should contain the IDs of each individual value + * (eg, one ID per component of a vector). + */ +bool ProgramToSkVM(const Program& program, + const FunctionDefinition& function, + skvm::Builder* b, + SkVMDebugTrace* debugTrace, + SkSpan<skvm::Val> uniforms, + SkVMSignature* outSignature = nullptr); + +bool testingOnly_ProgramToSkVMShader(const Program& program, + skvm::Builder* builder, + SkVMDebugTrace* debugTrace); + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp new file mode 100644 index 0000000000..f5f593b33c --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp @@ -0,0 +1,1939 @@ +/* + * 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 "src/sksl/codegen/SkSLWGSLCodeGenerator.h" + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <optional> +#include <string> +#include <vector> + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkBitmaskEnum.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLString.h" +#include "include/private/SkSLSymbol.h" +#include "include/private/base/SkTArray.h" +#include "include/private/base/SkTo.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLOutputStream.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/analysis/SkSLProgramVisitor.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLStructDefinition.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +// TODO(skia:13092): This is a temporary debug feature. Remove when the implementation is +// complete and this is no longer needed. +#define DUMP_SRC_IR 0 + +namespace SkSL { + +enum class ProgramKind : int8_t; + +namespace { + +// See https://www.w3.org/TR/WGSL/#memory-view-types +enum class PtrAddressSpace { + kFunction, + kPrivate, + kStorage, +}; + +std::string_view pipeline_struct_prefix(ProgramKind kind) { + if (ProgramConfig::IsVertex(kind)) { + return "VS"; + } + if (ProgramConfig::IsFragment(kind)) { + return "FS"; + } + return ""; +} + +std::string_view address_space_to_str(PtrAddressSpace addressSpace) { + switch (addressSpace) { + case PtrAddressSpace::kFunction: + return "function"; + case PtrAddressSpace::kPrivate: + return "private"; + case PtrAddressSpace::kStorage: + return "storage"; + } + SkDEBUGFAIL("unsupported ptr address space"); + return "unsupported"; +} + +std::string_view to_scalar_type(const Type& type) { + SkASSERT(type.typeKind() == Type::TypeKind::kScalar); + switch (type.numberKind()) { + // Floating-point numbers in WebGPU currently always have 32-bit footprint and + // relaxed-precision is not supported without extensions. f32 is the only floating-point + // number type in WGSL (see the discussion on https://github.com/gpuweb/gpuweb/issues/658). + case Type::NumberKind::kFloat: + return "f32"; + case Type::NumberKind::kSigned: + return "i32"; + case Type::NumberKind::kUnsigned: + return "u32"; + case Type::NumberKind::kBoolean: + return "bool"; + case Type::NumberKind::kNonnumeric: + [[fallthrough]]; + default: + break; + } + return type.name(); +} + +// Convert a SkSL type to a WGSL type. Handles all plain types except structure types +// (see https://www.w3.org/TR/WGSL/#plain-types-section). +std::string to_wgsl_type(const Type& type) { + switch (type.typeKind()) { + case Type::TypeKind::kScalar: + return std::string(to_scalar_type(type)); + case Type::TypeKind::kVector: { + std::string_view ct = to_scalar_type(type.componentType()); + return String::printf("vec%d<%.*s>", type.columns(), (int)ct.length(), ct.data()); + } + case Type::TypeKind::kMatrix: { + std::string_view ct = to_scalar_type(type.componentType()); + return String::printf( + "mat%dx%d<%.*s>", type.columns(), type.rows(), (int)ct.length(), ct.data()); + } + case Type::TypeKind::kArray: { + std::string elementType = to_wgsl_type(type.componentType()); + if (type.isUnsizedArray()) { + return String::printf("array<%s>", elementType.c_str()); + } + return String::printf("array<%s, %d>", elementType.c_str(), type.columns()); + } + default: + break; + } + return std::string(type.name()); +} + +// Create a mangled WGSL type name that can be used in function and variable declarations (regular +// type names cannot be used in this manner since they may contain tokens that are not allowed in +// symbol names). +std::string to_mangled_wgsl_type_name(const Type& type) { + switch (type.typeKind()) { + case Type::TypeKind::kScalar: + return std::string(to_scalar_type(type)); + case Type::TypeKind::kVector: { + std::string_view ct = to_scalar_type(type.componentType()); + return String::printf("vec%d%.*s", type.columns(), (int)ct.length(), ct.data()); + } + case Type::TypeKind::kMatrix: { + std::string_view ct = to_scalar_type(type.componentType()); + return String::printf( + "mat%dx%d%.*s", type.columns(), type.rows(), (int)ct.length(), ct.data()); + } + case Type::TypeKind::kArray: { + std::string elementType = to_wgsl_type(type.componentType()); + if (type.isUnsizedArray()) { + return String::printf("arrayof%s", elementType.c_str()); + } + return String::printf("array%dof%s", type.columns(), elementType.c_str()); + } + default: + break; + } + return std::string(type.name()); +} + +std::string to_ptr_type(const Type& type, + PtrAddressSpace addressSpace = PtrAddressSpace::kFunction) { + return "ptr<" + std::string(address_space_to_str(addressSpace)) + ", " + to_wgsl_type(type) + + ">"; +} + +std::string_view wgsl_builtin_name(WGSLCodeGenerator::Builtin builtin) { + using Builtin = WGSLCodeGenerator::Builtin; + switch (builtin) { + case Builtin::kVertexIndex: + return "vertex_index"; + case Builtin::kInstanceIndex: + return "instance_index"; + case Builtin::kPosition: + return "position"; + case Builtin::kFrontFacing: + return "front_facing"; + case Builtin::kSampleIndex: + return "sample_index"; + case Builtin::kFragDepth: + return "frag_depth"; + case Builtin::kSampleMask: + return "sample_mask"; + case Builtin::kLocalInvocationId: + return "local_invocation_id"; + case Builtin::kLocalInvocationIndex: + return "local_invocation_index"; + case Builtin::kGlobalInvocationId: + return "global_invocation_id"; + case Builtin::kWorkgroupId: + return "workgroup_id"; + case Builtin::kNumWorkgroups: + return "num_workgroups"; + default: + break; + } + + SkDEBUGFAIL("unsupported builtin"); + return "unsupported"; +} + +std::string_view wgsl_builtin_type(WGSLCodeGenerator::Builtin builtin) { + using Builtin = WGSLCodeGenerator::Builtin; + switch (builtin) { + case Builtin::kVertexIndex: + return "u32"; + case Builtin::kInstanceIndex: + return "u32"; + case Builtin::kPosition: + return "vec4<f32>"; + case Builtin::kFrontFacing: + return "bool"; + case Builtin::kSampleIndex: + return "u32"; + case Builtin::kFragDepth: + return "f32"; + case Builtin::kSampleMask: + return "u32"; + case Builtin::kLocalInvocationId: + return "vec3<u32>"; + case Builtin::kLocalInvocationIndex: + return "u32"; + case Builtin::kGlobalInvocationId: + return "vec3<u32>"; + case Builtin::kWorkgroupId: + return "vec3<u32>"; + case Builtin::kNumWorkgroups: + return "vec3<u32>"; + default: + break; + } + + SkDEBUGFAIL("unsupported builtin"); + return "unsupported"; +} + +// Some built-in variables have a type that differs from their SkSL counterpart (e.g. signed vs +// unsigned integer). We handle these cases with an explicit type conversion during a variable +// reference. Returns the WGSL type of the conversion target if conversion is needed, otherwise +// returns std::nullopt. +std::optional<std::string_view> needs_builtin_type_conversion(const Variable& v) { + switch (v.modifiers().fLayout.fBuiltin) { + case SK_VERTEXID_BUILTIN: + case SK_INSTANCEID_BUILTIN: + return {"i32"}; + default: + break; + } + return std::nullopt; +} + +// Map a SkSL builtin flag to a WGSL builtin kind. Returns std::nullopt if `builtin` is not +// not supported for WGSL. +// +// Also see //src/sksl/sksl_vert.sksl and //src/sksl/sksl_frag.sksl for supported built-ins. +std::optional<WGSLCodeGenerator::Builtin> builtin_from_sksl_name(int builtin) { + using Builtin = WGSLCodeGenerator::Builtin; + switch (builtin) { + case SK_POSITION_BUILTIN: + [[fallthrough]]; + case SK_FRAGCOORD_BUILTIN: + return {Builtin::kPosition}; + case SK_VERTEXID_BUILTIN: + return {Builtin::kVertexIndex}; + case SK_INSTANCEID_BUILTIN: + return {Builtin::kInstanceIndex}; + case SK_CLOCKWISE_BUILTIN: + // TODO(skia:13092): While `front_facing` is the corresponding built-in, it does not + // imply a particular winding order. We correctly compute the face orientation based + // on how Skia configured the render pipeline for all references to this built-in + // variable (see `SkSL::Program::Inputs::fUseFlipRTUniform`). + return {Builtin::kFrontFacing}; + default: + break; + } + return std::nullopt; +} + +const SymbolTable* top_level_symbol_table(const FunctionDefinition& f) { + return f.body()->as<Block>().symbolTable()->fParent.get(); +} + +const char* delimiter_to_str(WGSLCodeGenerator::Delimiter delimiter) { + using Delim = WGSLCodeGenerator::Delimiter; + switch (delimiter) { + case Delim::kComma: + return ","; + case Delim::kSemicolon: + return ";"; + case Delim::kNone: + default: + break; + } + return ""; +} + +// FunctionDependencyResolver visits the IR tree rooted at a particular function definition and +// computes that function's dependencies on pipeline stage IO parameters. These are later used to +// synthesize arguments when writing out function definitions. +class FunctionDependencyResolver : public ProgramVisitor { +public: + using Deps = WGSLCodeGenerator::FunctionDependencies; + using DepsMap = WGSLCodeGenerator::ProgramRequirements::DepsMap; + + FunctionDependencyResolver(const Program* p, + const FunctionDeclaration* f, + DepsMap* programDependencyMap) + : fProgram(p), fFunction(f), fDependencyMap(programDependencyMap) {} + + Deps resolve() { + fDeps = Deps::kNone; + this->visit(*fProgram); + return fDeps; + } + +private: + bool visitProgramElement(const ProgramElement& p) override { + // Only visit the program that matches the requested function. + if (p.is<FunctionDefinition>() && &p.as<FunctionDefinition>().declaration() == fFunction) { + return INHERITED::visitProgramElement(p); + } + // Continue visiting other program elements. + return false; + } + + bool visitExpression(const Expression& e) override { + if (e.is<VariableReference>()) { + const VariableReference& v = e.as<VariableReference>(); + const Modifiers& modifiers = v.variable()->modifiers(); + if (v.variable()->storage() == Variable::Storage::kGlobal) { + if (modifiers.fFlags & Modifiers::kIn_Flag) { + fDeps |= Deps::kPipelineInputs; + } + if (modifiers.fFlags & Modifiers::kOut_Flag) { + fDeps |= Deps::kPipelineOutputs; + } + } + } else if (e.is<FunctionCall>()) { + // The current function that we're processing (`fFunction`) inherits the dependencies of + // functions that it makes calls to, because the pipeline stage IO parameters need to be + // passed down as an argument. + const FunctionCall& callee = e.as<FunctionCall>(); + + // Don't process a function again if we have already resolved it. + Deps* found = fDependencyMap->find(&callee.function()); + if (found) { + fDeps |= *found; + } else { + // Store the dependencies that have been discovered for the current function so far. + // If `callee` directly or indirectly calls the current function, then this value + // will prevent an infinite recursion. + fDependencyMap->set(fFunction, fDeps); + + // Separately traverse the called function's definition and determine its + // dependencies. + FunctionDependencyResolver resolver(fProgram, &callee.function(), fDependencyMap); + Deps calleeDeps = resolver.resolve(); + + // Store the callee's dependencies in the global map to avoid processing + // the function again for future calls. + fDependencyMap->set(&callee.function(), calleeDeps); + + // Add to the current function's dependencies. + fDeps |= calleeDeps; + } + } + return INHERITED::visitExpression(e); + } + + const Program* const fProgram; + const FunctionDeclaration* const fFunction; + DepsMap* const fDependencyMap; + Deps fDeps = Deps::kNone; + + using INHERITED = ProgramVisitor; +}; + +WGSLCodeGenerator::ProgramRequirements resolve_program_requirements(const Program* program) { + bool mainNeedsCoordsArgument = false; + WGSLCodeGenerator::ProgramRequirements::DepsMap dependencies; + + for (const ProgramElement* e : program->elements()) { + if (!e->is<FunctionDefinition>()) { + continue; + } + + const FunctionDeclaration& decl = e->as<FunctionDefinition>().declaration(); + if (decl.isMain()) { + for (const Variable* v : decl.parameters()) { + if (v->modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) { + mainNeedsCoordsArgument = true; + break; + } + } + } + + FunctionDependencyResolver resolver(program, &decl, &dependencies); + dependencies.set(&decl, resolver.resolve()); + } + + return WGSLCodeGenerator::ProgramRequirements(std::move(dependencies), mainNeedsCoordsArgument); +} + +int count_pipeline_inputs(const Program* program) { + int inputCount = 0; + for (const ProgramElement* e : program->elements()) { + if (e->is<GlobalVarDeclaration>()) { + const Variable* v = e->as<GlobalVarDeclaration>().varDeclaration().var(); + if (v->modifiers().fFlags & Modifiers::kIn_Flag) { + inputCount++; + } + } else if (e->is<InterfaceBlock>()) { + const Variable* v = e->as<InterfaceBlock>().var(); + if (v->modifiers().fFlags & Modifiers::kIn_Flag) { + inputCount++; + } + } + } + return inputCount; +} + +static bool is_in_global_uniforms(const Variable& var) { + SkASSERT(var.storage() == VariableStorage::kGlobal); + return var.modifiers().fFlags & Modifiers::kUniform_Flag && !var.type().isOpaque(); +} + +} // namespace + +bool WGSLCodeGenerator::generateCode() { + // The resources of a WGSL program are structured in the following way: + // - Vertex and fragment stage attribute inputs and outputs are bundled + // inside synthetic structs called VSIn/VSOut/FSIn/FSOut. + // - All uniform and storage type resources are declared in global scope. + this->preprocessProgram(); + + StringStream header; + { + AutoOutputStream outputToHeader(this, &header, &fIndentation); + // TODO(skia:13092): Implement the following: + // - global uniform/storage resource declarations, including interface blocks. + this->writeStageInputStruct(); + this->writeStageOutputStruct(); + this->writeNonBlockUniformsForTests(); + } + StringStream body; + { + AutoOutputStream outputToBody(this, &body, &fIndentation); + for (const ProgramElement* e : fProgram.elements()) { + this->writeProgramElement(*e); + } + +// TODO(skia:13092): This is a temporary debug feature. Remove when the implementation is +// complete and this is no longer needed. +#if DUMP_SRC_IR + this->writeLine("\n----------"); + this->writeLine("Source IR:\n"); + for (const ProgramElement* e : fProgram.elements()) { + this->writeLine(e->description().c_str()); + } +#endif + } + + write_stringstream(header, *fOut); + write_stringstream(fExtraFunctions, *fOut); + write_stringstream(body, *fOut); + return fContext.fErrors->errorCount() == 0; +} + +void WGSLCodeGenerator::preprocessProgram() { + fRequirements = resolve_program_requirements(&fProgram); + fPipelineInputCount = count_pipeline_inputs(&fProgram); +} + +void WGSLCodeGenerator::write(std::string_view s) { + if (s.empty()) { + return; + } + if (fAtLineStart) { + for (int i = 0; i < fIndentation; i++) { + fOut->writeText(" "); + } + } + fOut->writeText(std::string(s).c_str()); + fAtLineStart = false; +} + +void WGSLCodeGenerator::writeLine(std::string_view s) { + this->write(s); + fOut->writeText("\n"); + fAtLineStart = true; +} + +void WGSLCodeGenerator::finishLine() { + if (!fAtLineStart) { + this->writeLine(); + } +} + +void WGSLCodeGenerator::writeName(std::string_view name) { + // Add underscore before name to avoid conflict with reserved words. + if (fReservedWords.contains(name)) { + this->write("_"); + } + this->write(name); +} + +void WGSLCodeGenerator::writeVariableDecl(const Type& type, + std::string_view name, + Delimiter delimiter) { + this->writeName(name); + this->write(": " + to_wgsl_type(type)); + this->writeLine(delimiter_to_str(delimiter)); +} + +void WGSLCodeGenerator::writePipelineIODeclaration(Modifiers modifiers, + const Type& type, + std::string_view name, + Delimiter delimiter) { + // In WGSL, an entry-point IO parameter is "one of either a built-in value or + // assigned a location". However, some SkSL declarations, specifically sk_FragColor, can + // contain both a location and a builtin modifier. In addition, WGSL doesn't have a built-in + // equivalent for sk_FragColor as it relies on the user-defined location for a render + // target. + // + // Instead of special-casing sk_FragColor, we just give higher precedence to a location + // modifier if a declaration happens to both have a location and it's a built-in. + // + // Also see: + // https://www.w3.org/TR/WGSL/#input-output-locations + // https://www.w3.org/TR/WGSL/#attribute-location + // https://www.w3.org/TR/WGSL/#builtin-inputs-outputs + int location = modifiers.fLayout.fLocation; + if (location >= 0) { + this->writeUserDefinedIODecl(type, name, location, delimiter); + } else if (modifiers.fLayout.fBuiltin >= 0) { + auto builtin = builtin_from_sksl_name(modifiers.fLayout.fBuiltin); + if (builtin.has_value()) { + this->writeBuiltinIODecl(type, name, *builtin, delimiter); + } + } +} + +void WGSLCodeGenerator::writeUserDefinedIODecl(const Type& type, + std::string_view name, + int location, + Delimiter delimiter) { + this->write("@location(" + std::to_string(location) + ") "); + + // "User-defined IO of scalar or vector integer type must always be specified as + // @interpolate(flat)" (see https://www.w3.org/TR/WGSL/#interpolation) + if (type.isInteger() || (type.isVector() && type.componentType().isInteger())) { + this->write("@interpolate(flat) "); + } + + this->writeVariableDecl(type, name, delimiter); +} + +void WGSLCodeGenerator::writeBuiltinIODecl(const Type& type, + std::string_view name, + Builtin builtin, + Delimiter delimiter) { + this->write("@builtin("); + this->write(wgsl_builtin_name(builtin)); + this->write(") "); + + this->writeName(name); + this->write(": "); + this->write(wgsl_builtin_type(builtin)); + this->writeLine(delimiter_to_str(delimiter)); +} + +void WGSLCodeGenerator::writeFunction(const FunctionDefinition& f) { + this->writeFunctionDeclaration(f.declaration()); + this->write(" "); + this->writeBlock(f.body()->as<Block>()); + + if (f.declaration().isMain()) { + // We just emitted the user-defined main function. Next, we generate a program entry point + // that calls the user-defined main. + this->writeEntryPoint(f); + } +} + +void WGSLCodeGenerator::writeFunctionDeclaration(const FunctionDeclaration& f) { + this->write("fn "); + this->write(f.mangledName()); + this->write("("); + auto separator = SkSL::String::Separator(); + if (this->writeFunctionDependencyParams(f)) { + separator(); // update the separator as parameters have been written + } + for (const Variable* param : f.parameters()) { + this->write(separator()); + this->writeName(param->mangledName()); + this->write(": "); + + // Declare an "out" function parameter as a pointer. + if (param->modifiers().fFlags & Modifiers::kOut_Flag) { + this->write(to_ptr_type(param->type())); + } else { + this->write(to_wgsl_type(param->type())); + } + } + this->write(")"); + if (!f.returnType().isVoid()) { + this->write(" -> "); + this->write(to_wgsl_type(f.returnType())); + } +} + +void WGSLCodeGenerator::writeEntryPoint(const FunctionDefinition& main) { + SkASSERT(main.declaration().isMain()); + + // The input and output parameters for a vertex/fragment stage entry point function have the + // FSIn/FSOut/VSIn/VSOut struct types that have been synthesized in generateCode(). An entry + // point always has the same signature and acts as a trampoline to the user-defined main + // function. + std::string outputType; + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind)) { + this->write("@vertex fn vertexMain("); + if (fPipelineInputCount > 0) { + this->write("_stageIn: VSIn"); + } + this->writeLine(") -> VSOut {"); + outputType = "VSOut"; + } else if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + this->write("@fragment fn fragmentMain("); + if (fPipelineInputCount > 0) { + this->write("_stageIn: FSIn"); + } + this->writeLine(") -> FSOut {"); + outputType = "FSOut"; + } else { + fContext.fErrors->error(Position(), "program kind not supported"); + return; + } + + // Declare the stage output struct. + fIndentation++; + this->write("var _stageOut: "); + this->write(outputType); + this->writeLine(";"); + + // Generate assignment to sk_FragColor built-in if the user-defined main returns a color. + if (ProgramConfig::IsFragment(fProgram.fConfig->fKind)) { + const SymbolTable* symbolTable = top_level_symbol_table(main); + const Symbol* symbol = symbolTable->find("sk_FragColor"); + SkASSERT(symbol); + if (main.declaration().returnType().matches(symbol->type())) { + this->write("_stageOut.sk_FragColor = "); + } + } + + // Generate the function call to the user-defined main: + this->write(main.declaration().mangledName()); + this->write("("); + auto separator = SkSL::String::Separator(); + FunctionDependencies* deps = fRequirements.dependencies.find(&main.declaration()); + if (deps) { + if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) { + this->write(separator()); + this->write("_stageIn"); + } + if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) { + this->write(separator()); + this->write("&_stageOut"); + } + } + // TODO(armansito): Handle arbitrary parameters. + if (main.declaration().parameters().size() != 0) { + const Variable* v = main.declaration().parameters()[0]; + const Type& type = v->type(); + if (v->modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN) { + if (!type.matches(*fContext.fTypes.fFloat2)) { + fContext.fErrors->error( + main.fPosition, + "main function has unsupported parameter: " + type.description()); + return; + } + + this->write(separator()); + this->write("_stageIn.sk_FragCoord.xy"); + } + } + this->writeLine(");"); + this->writeLine("return _stageOut;"); + + fIndentation--; + this->writeLine("}"); +} + +void WGSLCodeGenerator::writeStatement(const Statement& s) { + switch (s.kind()) { + case Statement::Kind::kBlock: + this->writeBlock(s.as<Block>()); + break; + case Statement::Kind::kExpression: + this->writeExpressionStatement(s.as<ExpressionStatement>()); + break; + case Statement::Kind::kIf: + this->writeIfStatement(s.as<IfStatement>()); + break; + case Statement::Kind::kReturn: + this->writeReturnStatement(s.as<ReturnStatement>()); + break; + case Statement::Kind::kVarDeclaration: + this->writeVarDeclaration(s.as<VarDeclaration>()); + break; + default: + SkDEBUGFAILF("unsupported statement (kind: %d) %s", + static_cast<int>(s.kind()), s.description().c_str()); + break; + } +} + +void WGSLCodeGenerator::writeStatements(const StatementArray& statements) { + for (const auto& s : statements) { + if (!s->isEmpty()) { + this->writeStatement(*s); + this->finishLine(); + } + } +} + +void WGSLCodeGenerator::writeBlock(const Block& b) { + // Write scope markers if this block is a scope, or if the block is empty (since we need to emit + // something here to make the code valid). + bool isScope = b.isScope() || b.isEmpty(); + if (isScope) { + this->writeLine("{"); + fIndentation++; + } + this->writeStatements(b.children()); + if (isScope) { + fIndentation--; + this->writeLine("}"); + } +} + +void WGSLCodeGenerator::writeExpressionStatement(const ExpressionStatement& s) { + if (Analysis::HasSideEffects(*s.expression())) { + this->writeExpression(*s.expression(), Precedence::kTopLevel); + this->write(";"); + } +} + +void WGSLCodeGenerator::writeIfStatement(const IfStatement& s) { + this->write("if ("); + this->writeExpression(*s.test(), Precedence::kTopLevel); + this->write(") "); + this->writeStatement(*s.ifTrue()); + if (s.ifFalse()) { + this->write("else "); + this->writeStatement(*s.ifFalse()); + } +} + +void WGSLCodeGenerator::writeReturnStatement(const ReturnStatement& s) { + this->write("return"); + if (s.expression()) { + this->write(" "); + this->writeExpression(*s.expression(), Precedence::kTopLevel); + } + this->write(";"); +} + +void WGSLCodeGenerator::writeVarDeclaration(const VarDeclaration& varDecl) { + bool isConst = varDecl.var()->modifiers().fFlags & Modifiers::kConst_Flag; + if (isConst) { + this->write("let "); + } else { + this->write("var "); + } + this->writeName(varDecl.var()->mangledName()); + this->write(": "); + this->write(to_wgsl_type(varDecl.var()->type())); + + if (varDecl.value()) { + this->write(" = "); + this->writeExpression(*varDecl.value(), Precedence::kTopLevel); + } else if (isConst) { + SkDEBUGFAILF("A let-declared constant must specify a value"); + } + + this->write(";"); +} + +void WGSLCodeGenerator::writeExpression(const Expression& e, Precedence parentPrecedence) { + switch (e.kind()) { + case Expression::Kind::kBinary: + this->writeBinaryExpression(e.as<BinaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kConstructorCompound: + this->writeConstructorCompound(e.as<ConstructorCompound>(), parentPrecedence); + break; + case Expression::Kind::kConstructorCompoundCast: + case Expression::Kind::kConstructorScalarCast: + case Expression::Kind::kConstructorSplat: + this->writeAnyConstructor(e.asAnyConstructor(), parentPrecedence); + break; + case Expression::Kind::kConstructorDiagonalMatrix: + this->writeConstructorDiagonalMatrix(e.as<ConstructorDiagonalMatrix>(), + parentPrecedence); + break; + case Expression::Kind::kConstructorMatrixResize: + this->writeConstructorMatrixResize(e.as<ConstructorMatrixResize>(), parentPrecedence); + break; + case Expression::Kind::kFieldAccess: + this->writeFieldAccess(e.as<FieldAccess>()); + break; + case Expression::Kind::kFunctionCall: + this->writeFunctionCall(e.as<FunctionCall>()); + break; + case Expression::Kind::kIndex: + this->writeIndexExpression(e.as<IndexExpression>()); + break; + case Expression::Kind::kLiteral: + this->writeLiteral(e.as<Literal>()); + break; + case Expression::Kind::kSwizzle: + this->writeSwizzle(e.as<Swizzle>()); + break; + case Expression::Kind::kTernary: + this->writeTernaryExpression(e.as<TernaryExpression>(), parentPrecedence); + break; + case Expression::Kind::kVariableReference: + this->writeVariableReference(e.as<VariableReference>()); + break; + default: + SkDEBUGFAILF("unsupported expression (kind: %d) %s", + static_cast<int>(e.kind()), + e.description().c_str()); + break; + } +} + +void WGSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b, + Precedence parentPrecedence) { + const Expression& left = *b.left(); + const Expression& right = *b.right(); + Operator op = b.getOperator(); + + // The equality and comparison operators are only supported for scalar and vector types. + if (op.isEquality() && !left.type().isScalar() && !left.type().isVector()) { + if (left.type().isMatrix()) { + if (op.kind() == OperatorKind::NEQ) { + this->write("!"); + } + this->writeMatrixEquality(left, right); + return; + } + + // TODO(skia:13092): Synthesize helper functions for structs and arrays. + return; + } + + Precedence precedence = op.getBinaryPrecedence(); + bool needParens = precedence >= parentPrecedence; + + // The equality operators ('=='/'!=') in WGSL apply component-wise to vectors and result in a + // vector. We need to reduce the value to a boolean. + if (left.type().isVector()) { + if (op.kind() == Operator::Kind::EQEQ) { + this->write("all"); + needParens = true; + } else if (op.kind() == Operator::Kind::NEQ) { + this->write("any"); + needParens = true; + } + } + + if (needParens) { + this->write("("); + } + + // TODO(skia:13092): Correctly handle the case when lhs is a pointer. + + this->writeExpression(left, precedence); + this->write(op.operatorName()); + this->writeExpression(right, precedence); + + if (needParens) { + this->write(")"); + } +} + +void WGSLCodeGenerator::writeFieldAccess(const FieldAccess& f) { + const Type::Field* field = &f.base()->type().fields()[f.fieldIndex()]; + if (FieldAccess::OwnerKind::kDefault == f.ownerKind()) { + this->writeExpression(*f.base(), Precedence::kPostfix); + this->write("."); + } else { + // We are accessing a field in an anonymous interface block. If the field refers to a + // pipeline IO parameter, then we access it via the synthesized IO structs. We make an + // explicit exception for `sk_PointSize` which we declare as a placeholder variable in + // global scope as it is not supported by WebGPU as a pipeline IO parameter (see comments + // in `writeStageOutputStruct`). + const Variable& v = *f.base()->as<VariableReference>().variable(); + if (v.modifiers().fFlags & Modifiers::kIn_Flag) { + this->write("_stageIn."); + } else if (v.modifiers().fFlags & Modifiers::kOut_Flag && + field->fModifiers.fLayout.fBuiltin != SK_POINTSIZE_BUILTIN) { + this->write("(*_stageOut)."); + } else { + // TODO(skia:13092): Reference the variable using the base name used for its + // uniform/storage block global declaration. + } + } + this->writeName(field->fName); +} + +void WGSLCodeGenerator::writeFunctionCall(const FunctionCall& c) { + const FunctionDeclaration& func = c.function(); + + // TODO(skia:13092): Handle intrinsic call as many of them need to be rewritten. + + // We implement function out-parameters by declaring them as pointers. SkSL follows GLSL's + // out-parameter semantics, in which out-parameters are only written back to the original + // variable after the function's execution is complete (see + // https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Parameters). + // + // In addition, SkSL supports swizzles and array index expressions to be passed into + // out-parameters however WGSL does not allow taking their address into a pointer. + // + // We support these by wrapping each function call in a special helper, which internally stores + // all out parameters in temporaries. + + // First detect which arguments are passed to out-parameters. + const ExpressionArray& args = c.arguments(); + const std::vector<Variable*>& params = func.parameters(); + SkASSERT(SkToSizeT(args.size()) == params.size()); + + bool foundOutParam = false; + SkSTArray<16, VariableReference*> outVars; + outVars.push_back_n(args.size(), static_cast<VariableReference*>(nullptr)); + + for (int i = 0; i < args.size(); ++i) { + if (params[i]->modifiers().fFlags & Modifiers::kOut_Flag) { + // Find the expression's inner variable being written to. Assignability was verified at + // IR generation time, so this should always succeed. + Analysis::AssignmentInfo info; + SkAssertResult(Analysis::IsAssignable(*args[i], &info)); + outVars[i] = info.fAssignedVar; + foundOutParam = true; + } + } + + if (foundOutParam) { + this->writeName(this->writeOutParamHelper(c, args, outVars)); + } else { + this->writeName(func.mangledName()); + } + + this->write("("); + auto separator = SkSL::String::Separator(); + if (this->writeFunctionDependencyArgs(func)) { + separator(); + } + for (int i = 0; i < args.size(); ++i) { + this->write(separator()); + if (outVars[i]) { + // We need to take the address of the variable and pass it down as a pointer. + this->write("&"); + this->writeExpression(*outVars[i], Precedence::kSequence); + } else { + this->writeExpression(*args[i], Precedence::kSequence); + } + } + this->write(")"); +} + +void WGSLCodeGenerator::writeIndexExpression(const IndexExpression& i) { + this->writeExpression(*i.base(), Precedence::kPostfix); + this->write("["); + this->writeExpression(*i.index(), Precedence::kTopLevel); + this->write("]"); +} + +void WGSLCodeGenerator::writeLiteral(const Literal& l) { + const Type& type = l.type(); + if (type.isFloat() || type.isBoolean()) { + this->write(l.description(OperatorPrecedence::kTopLevel)); + return; + } + SkASSERT(type.isInteger()); + if (type.matches(*fContext.fTypes.fUInt)) { + this->write(std::to_string(l.intValue() & 0xffffffff)); + this->write("u"); + } else if (type.matches(*fContext.fTypes.fUShort)) { + this->write(std::to_string(l.intValue() & 0xffff)); + this->write("u"); + } else { + this->write(std::to_string(l.intValue())); + } +} + +void WGSLCodeGenerator::writeSwizzle(const Swizzle& swizzle) { + this->writeExpression(*swizzle.base(), Precedence::kPostfix); + this->write("."); + for (int c : swizzle.components()) { + SkASSERT(c >= 0 && c <= 3); + this->write(&("x\0y\0z\0w\0"[c * 2])); + } +} + +void WGSLCodeGenerator::writeTernaryExpression(const TernaryExpression& t, + Precedence parentPrecedence) { + bool needParens = Precedence::kTernary >= parentPrecedence; + if (needParens) { + this->write("("); + } + + // The trivial case is when neither branch has side effects and evaluate to a scalar or vector + // type. This can be represented with a call to the WGSL `select` intrinsic although it doesn't + // support short-circuiting. + if ((t.type().isScalar() || t.type().isVector()) && !Analysis::HasSideEffects(*t.ifTrue()) && + !Analysis::HasSideEffects(*t.ifFalse())) { + this->write("select("); + this->writeExpression(*t.ifFalse(), Precedence::kTernary); + this->write(", "); + this->writeExpression(*t.ifTrue(), Precedence::kTernary); + this->write(", "); + + bool isVector = t.type().isVector(); + if (isVector) { + // Splat the condition expression into a vector. + this->write(String::printf("vec%d<bool>(", t.type().columns())); + } + this->writeExpression(*t.test(), Precedence::kTernary); + if (isVector) { + this->write(")"); + } + this->write(")"); + if (needParens) { + this->write(")"); + } + return; + } + + // TODO(skia:13092): WGSL does not support ternary expressions. To replicate the required + // short-circuting behavior we need to hoist the expression out into the surrounding block, + // convert it into an if statement that writes the result to a synthesized variable, and replace + // the original expression with a reference to that variable. + // + // Once hoisting is supported, we may want to use that for vector type expressions as well, + // since select above does a component-wise select +} + +void WGSLCodeGenerator::writeVariableReference(const VariableReference& r) { + // TODO(skia:13092): Correctly handle RTflip for built-ins. + const Variable& v = *r.variable(); + + // Insert a conversion expression if this is a built-in variable whose type differs from the + // SkSL. + std::optional<std::string_view> conversion = needs_builtin_type_conversion(v); + if (conversion.has_value()) { + this->write(*conversion); + this->write("("); + } + + bool needsDeref = false; + bool isSynthesizedOutParamArg = fOutParamArgVars.contains(&v); + + // When a variable is referenced in the context of a synthesized out-parameter helper argument, + // two special rules apply: + // 1. If it's accessed via a pipeline I/O or global uniforms struct, it should instead + // be referenced by name (since it's actually referring to a function parameter). + // 2. Its type should be treated as a pointer and should be dereferenced as such. + if (v.storage() == Variable::Storage::kGlobal && !isSynthesizedOutParamArg) { + if (v.modifiers().fFlags & Modifiers::kIn_Flag) { + this->write("_stageIn."); + } else if (v.modifiers().fFlags & Modifiers::kOut_Flag) { + this->write("(*_stageOut)."); + } else if (is_in_global_uniforms(v)) { + this->write("_globalUniforms."); + } + } else if ((v.storage() == Variable::Storage::kParameter && + v.modifiers().fFlags & Modifiers::kOut_Flag) || + isSynthesizedOutParamArg) { + // This is an out-parameter and its type is a pointer, which we need to dereference. + // We wrap the dereference in parentheses in case the value is used in an access expression + // later. + needsDeref = true; + this->write("(*"); + } + + this->writeName(v.mangledName()); + if (needsDeref) { + this->write(")"); + } + if (conversion.has_value()) { + this->write(")"); + } +} + +void WGSLCodeGenerator::writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence) { + this->write(to_wgsl_type(c.type())); + this->write("("); + auto separator = SkSL::String::Separator(); + for (const auto& e : c.argumentSpan()) { + this->write(separator()); + this->writeExpression(*e, Precedence::kSequence); + } + this->write(")"); +} + +void WGSLCodeGenerator::writeConstructorCompound(const ConstructorCompound& c, + Precedence parentPrecedence) { + if (c.type().isVector()) { + this->writeConstructorCompoundVector(c, parentPrecedence); + } else if (c.type().isMatrix()) { + this->writeConstructorCompoundMatrix(c, parentPrecedence); + } else { + fContext.fErrors->error(c.fPosition, "unsupported compound constructor"); + } +} + +void WGSLCodeGenerator::writeConstructorCompoundVector(const ConstructorCompound& c, + Precedence parentPrecedence) { + // WGSL supports constructing vectors from a mix of scalars and vectors but + // not matrices (see https://www.w3.org/TR/WGSL/#type-constructor-expr). + // + // SkSL supports vec4(mat2x2) which we handle specially. + if (c.type().columns() == 4 && c.argumentSpan().size() == 1) { + const Expression& arg = *c.argumentSpan().front(); + if (arg.type().isMatrix()) { + // This is the vec4(mat2x2) case. + SkASSERT(arg.type().columns() == 2); + SkASSERT(arg.type().rows() == 2); + + // Generate a helper so that the argument expression gets evaluated once. + std::string name = String::printf("%s_from_%s", + to_mangled_wgsl_type_name(c.type()).c_str(), + to_mangled_wgsl_type_name(arg.type()).c_str()); + if (!fHelpers.contains(name)) { + fHelpers.add(name); + std::string returnType = to_wgsl_type(c.type()); + std::string argType = to_wgsl_type(arg.type()); + fExtraFunctions.printf( + "fn %s(x: %s) -> %s {\n return %s(x[0].xy, x[1].xy);\n}\n", + name.c_str(), + argType.c_str(), + returnType.c_str(), + returnType.c_str()); + } + this->write(name); + this->write("("); + this->writeExpression(arg, Precedence::kSequence); + this->write(")"); + return; + } + } + this->writeAnyConstructor(c, parentPrecedence); +} + +void WGSLCodeGenerator::writeConstructorCompoundMatrix(const ConstructorCompound& c, + Precedence parentPrecedence) { + SkASSERT(c.type().isMatrix()); + + // Emit and invoke a matrix-constructor helper method if one is necessary. + if (this->isMatrixConstructorHelperNeeded(c)) { + this->write(this->getMatrixConstructorHelper(c)); + this->write("("); + auto separator = String::Separator(); + for (const std::unique_ptr<Expression>& expr : c.arguments()) { + this->write(separator()); + this->writeExpression(*expr, Precedence::kSequence); + } + this->write(")"); + return; + } + + // WGSL doesn't allow creating matrices by passing in scalars and vectors in a jumble; it + // requires your scalars to be grouped up into columns. As `isMatrixConstructorHelperNeeded` + // returned false, we know that none of our scalars/vectors "wrap" across across a column, so we + // can group our inputs up and synthesize a constructor for each column. + const Type& matrixType = c.type(); + const Type& columnType = matrixType.componentType().toCompound( + fContext, /*columns=*/matrixType.rows(), /*rows=*/1); + + this->write(to_wgsl_type(matrixType)); + this->write("("); + auto separator = String::Separator(); + int scalarCount = 0; + for (const std::unique_ptr<Expression>& arg : c.arguments()) { + this->write(separator()); + if (arg->type().columns() < matrixType.rows()) { + // Write a `floatN(` constructor to group scalars and smaller vectors together. + if (!scalarCount) { + this->write(to_wgsl_type(columnType)); + this->write("("); + } + scalarCount += arg->type().columns(); + } + this->writeExpression(*arg, Precedence::kSequence); + if (scalarCount && scalarCount == matrixType.rows()) { + // Close our `floatN(...` constructor block from above. + this->write(")"); + scalarCount = 0; + } + } + this->write(")"); +} + +void WGSLCodeGenerator::writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, + Precedence parentPrecedence) { + const Type& type = c.type(); + SkASSERT(type.isMatrix()); + SkASSERT(c.argument()->type().isScalar()); + + // Generate a helper so that the argument expression gets evaluated once. + std::string name = String::printf("%s_diagonal", to_mangled_wgsl_type_name(type).c_str()); + if (!fHelpers.contains(name)) { + fHelpers.add(name); + + std::string typeName = to_wgsl_type(type); + fExtraFunctions.printf("fn %s(x: %s) -> %s {\n", + name.c_str(), + to_wgsl_type(c.argument()->type()).c_str(), + typeName.c_str()); + fExtraFunctions.printf(" return %s(", typeName.c_str()); + auto separator = String::Separator(); + for (int col = 0; col < type.columns(); ++col) { + for (int row = 0; row < type.rows(); ++row) { + fExtraFunctions.printf("%s%s", separator().c_str(), (col == row) ? "x" : "0.0"); + } + } + fExtraFunctions.printf(");\n}\n"); + } + this->write(name); + this->write("("); + this->writeExpression(*c.argument(), Precedence::kSequence); + this->write(")"); +} + +void WGSLCodeGenerator::writeConstructorMatrixResize(const ConstructorMatrixResize& c, + Precedence parentPrecedence) { + this->write(this->getMatrixConstructorHelper(c)); + this->write("("); + this->writeExpression(*c.argument(), Precedence::kSequence); + this->write(")"); +} + +bool WGSLCodeGenerator::isMatrixConstructorHelperNeeded(const ConstructorCompound& c) { + // WGSL supports 3 categories of matrix constructors: + // 1. Identity construction from a matrix of identical dimensions (handled as + // ConstructorCompoundCast); + // 2. Column-major construction by elements (scalars); + // 3. Column-by-column construction from vectors. + // + // WGSL does not have a diagonal constructor. In addition, SkSL (like GLSL) supports free-form + // inputs that combine vectors, matrices, and scalars. + // + // Some cases are simple to translate and so we handle those inline--e.g. a list of scalars can + // be constructed trivially. In more complex cases, we generate a helper function that converts + // our inputs into a properly-shaped matrix. + // + // A matrix constructor helper method is always used if any input argument is a matrix. + // Helper methods are also necessary when any argument would span multiple rows. For instance: + // + // float2 x = (1, 2); + // float3x2(x, 3, 4, 5, 6) = | 1 3 5 | = no helper needed; conversion can be done inline + // | 2 4 6 | + // + // float2 x = (2, 3); + // float3x2(1, x, 4, 5, 6) = | 1 3 5 | = x spans multiple rows; a helper method will be used + // | 2 4 6 | + // + // float4 x = (1, 2, 3, 4); + // float2x2(x) = | 1 3 | = x spans multiple rows; a helper method will be used + // | 2 4 | + // + int position = 0; + for (const std::unique_ptr<Expression>& expr : c.arguments()) { + if (expr->type().isMatrix()) { + return true; + } + position += expr->type().columns(); + if (position > c.type().rows()) { + // An input argument would span multiple rows; a helper function is required. + return true; + } + if (position == c.type().rows()) { + // We've advanced to the end of a row. Wrap to the start of the next row. + position = 0; + } + } + return false; +} + +std::string WGSLCodeGenerator::getMatrixConstructorHelper(const AnyConstructor& c) { + const Type& type = c.type(); + int columns = type.columns(); + int rows = type.rows(); + auto args = c.argumentSpan(); + std::string typeName = to_wgsl_type(type); + + // Create the helper-method name and use it as our lookup key. + std::string name = String::printf("%s_from", to_mangled_wgsl_type_name(type).c_str()); + for (const std::unique_ptr<Expression>& expr : args) { + String::appendf(&name, "_%s", to_mangled_wgsl_type_name(expr->type()).c_str()); + } + + // If a helper-method has not been synthesized yet, create it now. + if (!fHelpers.contains(name)) { + fHelpers.add(name); + + fExtraFunctions.printf("fn %s(", name.c_str()); + + auto separator = String::Separator(); + for (size_t i = 0; i < args.size(); ++i) { + fExtraFunctions.printf( + "%sx%zu: %s", separator().c_str(), i, to_wgsl_type(args[i]->type()).c_str()); + } + + fExtraFunctions.printf(") -> %s {\n return %s(", typeName.c_str(), typeName.c_str()); + + if (args.size() == 1 && args.front()->type().isMatrix()) { + this->writeMatrixFromMatrixArgs(args.front()->type(), columns, rows); + } else { + this->writeMatrixFromScalarAndVectorArgs(c, columns, rows); + } + + fExtraFunctions.writeText(");\n}\n"); + } + return name; +} + +// Assembles a matrix by resizing another matrix named `x0`. +// Cells that don't exist in the source matrix will be populated with identity-matrix values. +void WGSLCodeGenerator::writeMatrixFromMatrixArgs(const Type& sourceMatrix, int columns, int rows) { + SkASSERT(rows <= 4); + SkASSERT(columns <= 4); + + const char* separator = ""; + std::string matrixType = to_wgsl_type(sourceMatrix.componentType()); + for (int c = 0; c < columns; ++c) { + fExtraFunctions.printf("%svec%d<%s>(", separator, rows, matrixType.c_str()); + separator = "), "; + + // Determine how many values to take from the source matrix for this row. + int swizzleLength = 0; + if (c < sourceMatrix.columns()) { + swizzleLength = std::min<>(rows, sourceMatrix.rows()); + } + + // Emit all the values from the source matrix row. + bool firstItem; + switch (swizzleLength) { + case 0: + firstItem = true; + break; + case 1: + firstItem = false; + fExtraFunctions.printf("x0[%d].x", c); + break; + case 2: + firstItem = false; + fExtraFunctions.printf("x0[%d].xy", c); + break; + case 3: + firstItem = false; + fExtraFunctions.printf("x0[%d].xyz", c); + break; + case 4: + firstItem = false; + fExtraFunctions.printf("x0[%d].xyzw", c); + break; + default: + SkUNREACHABLE; + } + + // Emit the placeholder identity-matrix cells. + for (int r = swizzleLength; r < rows; ++r) { + fExtraFunctions.printf("%s%s", firstItem ? "" : ", ", (r == c) ? "1.0" : "0.0"); + firstItem = false; + } + } + + fExtraFunctions.writeText(")"); +} + +// Assembles a matrix of type by concatenating an arbitrary mix of scalar and vector values, named +// `x0`, `x1`, etc. An error is written if the expression list don't contain exactly C*R scalars. +void WGSLCodeGenerator::writeMatrixFromScalarAndVectorArgs(const AnyConstructor& ctor, + int columns, + int rows) { + SkASSERT(rows <= 4); + SkASSERT(columns <= 4); + + std::string matrixType = to_wgsl_type(ctor.type().componentType()); + size_t argIndex = 0; + int argPosition = 0; + auto args = ctor.argumentSpan(); + + static constexpr char kSwizzle[] = "xyzw"; + const char* separator = ""; + for (int c = 0; c < columns; ++c) { + fExtraFunctions.printf("%svec%d<%s>(", separator, rows, matrixType.c_str()); + separator = "), "; + + auto columnSeparator = String::Separator(); + for (int r = 0; r < rows;) { + fExtraFunctions.writeText(columnSeparator().c_str()); + if (argIndex < args.size()) { + const Type& argType = args[argIndex]->type(); + switch (argType.typeKind()) { + case Type::TypeKind::kScalar: { + fExtraFunctions.printf("x%zu", argIndex); + ++r; + ++argPosition; + break; + } + case Type::TypeKind::kVector: { + fExtraFunctions.printf("x%zu.", argIndex); + do { + fExtraFunctions.write8(kSwizzle[argPosition]); + ++r; + ++argPosition; + } while (r < rows && argPosition < argType.columns()); + break; + } + case Type::TypeKind::kMatrix: { + fExtraFunctions.printf("x%zu[%d].", argIndex, argPosition / argType.rows()); + do { + fExtraFunctions.write8(kSwizzle[argPosition]); + ++r; + ++argPosition; + } while (r < rows && (argPosition % argType.rows()) != 0); + break; + } + default: { + SkDEBUGFAIL("incorrect type of argument for matrix constructor"); + fExtraFunctions.writeText("<error>"); + break; + } + } + + if (argPosition >= argType.columns() * argType.rows()) { + ++argIndex; + argPosition = 0; + } + } else { + SkDEBUGFAIL("not enough arguments for matrix constructor"); + fExtraFunctions.writeText("<error>"); + } + } + } + + if (argPosition != 0 || argIndex != args.size()) { + SkDEBUGFAIL("incorrect number of arguments for matrix constructor"); + fExtraFunctions.writeText(", <error>"); + } + + fExtraFunctions.writeText(")"); +} + +void WGSLCodeGenerator::writeMatrixEquality(const Expression& left, const Expression& right) { + const Type& leftType = left.type(); + const Type& rightType = right.type(); + SkASSERT(leftType.isMatrix()); + SkASSERT(rightType.isMatrix()); + SkASSERT(leftType.rows() == rightType.rows()); + SkASSERT(leftType.columns() == rightType.columns()); + + std::string name = String::printf("%s_eq_%s", + to_mangled_wgsl_type_name(leftType).c_str(), + to_mangled_wgsl_type_name(rightType).c_str()); + if (!fHelpers.contains(name)) { + fHelpers.add(name); + fExtraFunctions.printf("fn %s(left: %s, right: %s) -> bool {\n return ", + name.c_str(), + to_wgsl_type(leftType).c_str(), + to_wgsl_type(rightType).c_str()); + const char* separator = ""; + for (int i = 0; i < leftType.columns(); ++i) { + fExtraFunctions.printf("%sall(left[%d] == right[%d])", separator, i, i); + separator = " &&\n "; + } + fExtraFunctions.printf(";\n}\n"); + } + this->write(name); + this->write("("); + this->writeExpression(left, Precedence::kSequence); + this->write(", "); + this->writeExpression(right, Precedence::kSequence); + this->write(")"); +} + +void WGSLCodeGenerator::writeProgramElement(const ProgramElement& e) { + switch (e.kind()) { + case ProgramElement::Kind::kExtension: + // TODO(skia:13092): WGSL supports extensions via the "enable" directive + // (https://www.w3.org/TR/WGSL/#language-extensions). While we could easily emit this + // directive, we should first ensure that all possible SkSL extension names are + // converted to their appropriate WGSL extension. Currently there are no known supported + // WGSL extensions aside from the hypotheticals listed in the spec. + break; + case ProgramElement::Kind::kGlobalVar: + this->writeGlobalVarDeclaration(e.as<GlobalVarDeclaration>()); + break; + case ProgramElement::Kind::kInterfaceBlock: + // All interface block declarations are handled explicitly as the "program header" in + // generateCode(). + break; + case ProgramElement::Kind::kStructDefinition: + this->writeStructDefinition(e.as<StructDefinition>()); + break; + case ProgramElement::Kind::kFunctionPrototype: + // A WGSL function declaration must contain its body and the function name is in scope + // for the entire program (see https://www.w3.org/TR/WGSL/#function-declaration and + // https://www.w3.org/TR/WGSL/#declaration-and-scope). + // + // As such, we don't emit function prototypes. + break; + case ProgramElement::Kind::kFunction: + this->writeFunction(e.as<FunctionDefinition>()); + break; + default: + SkDEBUGFAILF("unsupported program element: %s\n", e.description().c_str()); + break; + } +} + +void WGSLCodeGenerator::writeGlobalVarDeclaration(const GlobalVarDeclaration& d) { + const Variable& var = *d.declaration()->as<VarDeclaration>().var(); + if ((var.modifiers().fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag)) || + is_in_global_uniforms(var)) { + // Pipeline stage I/O parameters and top-level (non-block) uniforms are handled specially + // in generateCode(). + return; + } + + // TODO(skia:13092): Implement workgroup variable decoration + this->write("var<private> "); + this->writeVariableDecl(var.type(), var.name(), Delimiter::kSemicolon); +} + +void WGSLCodeGenerator::writeStructDefinition(const StructDefinition& s) { + const Type& type = s.type(); + this->writeLine("struct " + type.displayName() + " {"); + fIndentation++; + this->writeFields(SkSpan(type.fields()), type.fPosition); + fIndentation--; + this->writeLine("};"); +} + +void WGSLCodeGenerator::writeFields(SkSpan<const Type::Field> fields, + Position parentPos, + const MemoryLayout*) { + // TODO(skia:13092): Check alignment against `layout` constraints, if present. A layout + // constraint will be specified for interface blocks and for structs that appear in a block. + for (const Type::Field& field : fields) { + const Type* fieldType = field.fType; + this->writeVariableDecl(*fieldType, field.fName, Delimiter::kComma); + } +} + +void WGSLCodeGenerator::writeStageInputStruct() { + std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind); + if (structNamePrefix.empty()) { + // There's no need to declare pipeline stage outputs. + return; + } + + // It is illegal to declare a struct with no members. + if (fPipelineInputCount < 1) { + return; + } + + this->write("struct "); + this->write(structNamePrefix); + this->writeLine("In {"); + fIndentation++; + + bool declaredFragCoordsBuiltin = false; + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const Variable* v = e->as<GlobalVarDeclaration>().declaration() + ->as<VarDeclaration>().var(); + if (v->modifiers().fFlags & Modifiers::kIn_Flag) { + this->writePipelineIODeclaration(v->modifiers(), v->type(), v->mangledName(), + Delimiter::kComma); + if (v->modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) { + declaredFragCoordsBuiltin = true; + } + } + } else if (e->is<InterfaceBlock>()) { + const Variable* v = e->as<InterfaceBlock>().var(); + // Merge all the members of `in` interface blocks to the input struct, which are + // specified as either "builtin" or with a "layout(location=". + // + // TODO(armansito): Is it legal to have an interface block without a storage qualifier + // but with members that have individual storage qualifiers? + if (v->modifiers().fFlags & Modifiers::kIn_Flag) { + for (const auto& f : v->type().fields()) { + this->writePipelineIODeclaration(f.fModifiers, *f.fType, f.fName, + Delimiter::kComma); + if (f.fModifiers.fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) { + declaredFragCoordsBuiltin = true; + } + } + } + } + } + + if (ProgramConfig::IsFragment(fProgram.fConfig->fKind) && + fRequirements.mainNeedsCoordsArgument && !declaredFragCoordsBuiltin) { + this->writeLine("@builtin(position) sk_FragCoord: vec4<f32>,"); + } + + fIndentation--; + this->writeLine("};"); +} + +void WGSLCodeGenerator::writeStageOutputStruct() { + std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind); + if (structNamePrefix.empty()) { + // There's no need to declare pipeline stage outputs. + return; + } + + this->write("struct "); + this->write(structNamePrefix); + this->writeLine("Out {"); + fIndentation++; + + // TODO(skia:13092): Remember all variables that are added to the output struct here so they + // can be referenced correctly when handling variable references. + bool declaredPositionBuiltin = false; + bool requiresPointSizeBuiltin = false; + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const Variable* v = e->as<GlobalVarDeclaration>().declaration() + ->as<VarDeclaration>().var(); + if (v->modifiers().fFlags & Modifiers::kOut_Flag) { + this->writePipelineIODeclaration(v->modifiers(), v->type(), v->mangledName(), + Delimiter::kComma); + } + } else if (e->is<InterfaceBlock>()) { + const Variable* v = e->as<InterfaceBlock>().var(); + // Merge all the members of `out` interface blocks to the output struct, which are + // specified as either "builtin" or with a "layout(location=". + // + // TODO(armansito): Is it legal to have an interface block without a storage qualifier + // but with members that have individual storage qualifiers? + if (v->modifiers().fFlags & Modifiers::kOut_Flag) { + for (const auto& f : v->type().fields()) { + this->writePipelineIODeclaration(f.fModifiers, *f.fType, f.fName, + Delimiter::kComma); + if (f.fModifiers.fLayout.fBuiltin == SK_POSITION_BUILTIN) { + declaredPositionBuiltin = true; + } else if (f.fModifiers.fLayout.fBuiltin == SK_POINTSIZE_BUILTIN) { + // sk_PointSize is explicitly not supported by `builtin_from_sksl_name` so + // writePipelineIODeclaration will never write it. We mark it here if the + // declaration is needed so we can synthesize it below. + requiresPointSizeBuiltin = true; + } + } + } + } + } + + // A vertex program must include the `position` builtin in its entry point return type. + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) && !declaredPositionBuiltin) { + this->writeLine("@builtin(position) sk_Position: vec4<f32>,"); + } + + fIndentation--; + this->writeLine("};"); + + // In WebGPU/WGSL, the vertex stage does not support a point-size output and the size + // of a point primitive is always 1 pixel (see https://github.com/gpuweb/gpuweb/issues/332). + // + // There isn't anything we can do to emulate this correctly at this stage so we + // synthesize a placeholder variable that has no effect. Programs should not rely on + // sk_PointSize when using the Dawn backend. + if (ProgramConfig::IsVertex(fProgram.fConfig->fKind) && requiresPointSizeBuiltin) { + this->writeLine("/* unsupported */ var<private> sk_PointSize: f32;"); + } +} + +void WGSLCodeGenerator::writeNonBlockUniformsForTests() { + for (const ProgramElement* e : fProgram.elements()) { + if (e->is<GlobalVarDeclaration>()) { + const GlobalVarDeclaration& decls = e->as<GlobalVarDeclaration>(); + const Variable& var = *decls.varDeclaration().var(); + if (is_in_global_uniforms(var)) { + if (!fDeclaredUniformsStruct) { + this->write("struct _GlobalUniforms {\n"); + fDeclaredUniformsStruct = true; + } + this->write(" "); + this->writeVariableDecl(var.type(), var.mangledName(), Delimiter::kComma); + } + } + } + if (fDeclaredUniformsStruct) { + int binding = fProgram.fConfig->fSettings.fDefaultUniformBinding; + int set = fProgram.fConfig->fSettings.fDefaultUniformSet; + this->write("};\n"); + this->write("@binding(" + std::to_string(binding) + ") "); + this->write("@group(" + std::to_string(set) + ") "); + this->writeLine("var<uniform> _globalUniforms: _GlobalUniforms;"); + } +} + +bool WGSLCodeGenerator::writeFunctionDependencyArgs(const FunctionDeclaration& f) { + FunctionDependencies* deps = fRequirements.dependencies.find(&f); + if (!deps || *deps == FunctionDependencies::kNone) { + return false; + } + + const char* separator = ""; + if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) { + this->write("_stageIn"); + separator = ", "; + } + if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) { + this->write(separator); + this->write("_stageOut"); + } + return true; +} + +bool WGSLCodeGenerator::writeFunctionDependencyParams(const FunctionDeclaration& f) { + FunctionDependencies* deps = fRequirements.dependencies.find(&f); + if (!deps || *deps == FunctionDependencies::kNone) { + return false; + } + + std::string_view structNamePrefix = pipeline_struct_prefix(fProgram.fConfig->fKind); + if (structNamePrefix.empty()) { + return false; + } + const char* separator = ""; + if ((*deps & FunctionDependencies::kPipelineInputs) != FunctionDependencies::kNone) { + this->write("_stageIn: "); + separator = ", "; + this->write(structNamePrefix); + this->write("In"); + } + if ((*deps & FunctionDependencies::kPipelineOutputs) != FunctionDependencies::kNone) { + this->write(separator); + this->write("_stageOut: ptr<function, "); + this->write(structNamePrefix); + this->write("Out>"); + } + return true; +} + +std::string WGSLCodeGenerator::writeOutParamHelper(const FunctionCall& c, + const ExpressionArray& args, + const SkTArray<VariableReference*>& outVars) { + // It's possible for out-param function arguments to contain an out-param function call + // expression. Emit the function into a temporary stream to prevent the nested helper from + // clobbering the current helper as we recursively evaluate argument expressions. + StringStream tmpStream; + AutoOutputStream outputToExtraFunctions(this, &tmpStream, &fIndentation); + + // Reset the line start state while the AutoOutputStream is active. We restore it later before + // the function returns. + bool atLineStart = fAtLineStart; + fAtLineStart = false; + const FunctionDeclaration& func = c.function(); + + // Synthesize a helper function that takes the same inputs as `function`, except in places where + // `outVars` is non-null; in those places, we take the type of the VariableReference. + // + // float _outParamHelper_0_originalFuncName(float _var0, float _var1, float& outParam) { + std::string name = + "_outParamHelper_" + std::to_string(fSwizzleHelperCount++) + "_" + func.mangledName(); + auto separator = SkSL::String::Separator(); + this->write("fn "); + this->write(name); + this->write("("); + if (this->writeFunctionDependencyParams(func)) { + separator(); + } + + SkASSERT(outVars.size() == args.size()); + SkASSERT(SkToSizeT(outVars.size()) == func.parameters().size()); + + // We need to detect cases where the caller passes the same variable as an out-param more than + // once and avoid redeclaring the variable name. This is also a situation that is not permitted + // by WGSL aliasing rules (see https://www.w3.org/TR/WGSL/#aliasing). Because the parameter is + // redundant and we don't actually ever reference it, we give it a placeholder name. + auto parentOutParamArgVars = std::move(fOutParamArgVars); + SkASSERT(fOutParamArgVars.empty()); + + for (int i = 0; i < args.size(); ++i) { + this->write(separator()); + + if (outVars[i]) { + const Variable* var = outVars[i]->variable(); + if (!fOutParamArgVars.contains(var)) { + fOutParamArgVars.add(var); + this->writeName(var->mangledName()); + } else { + this->write("_unused"); + this->write(std::to_string(i)); + } + } else { + this->write("_var"); + this->write(std::to_string(i)); + } + + this->write(": "); + + // Declare the parameter using the type of argument variable. If the complete argument is an + // access or swizzle expression, the target assignment will be resolved below when we copy + // the value to the out-parameter. + const Type& type = outVars[i] ? outVars[i]->type() : args[i]->type(); + + // Declare an out-parameter as a pointer. + if (func.parameters()[i]->modifiers().fFlags & Modifiers::kOut_Flag) { + this->write(to_ptr_type(type)); + } else { + this->write(to_wgsl_type(type)); + } + } + + this->write(")"); + if (!func.returnType().isVoid()) { + this->write(" -> "); + this->write(to_wgsl_type(func.returnType())); + } + this->writeLine(" {"); + ++fIndentation; + + // Declare a temporary variable for each out-parameter. + for (int i = 0; i < outVars.size(); ++i) { + if (!outVars[i]) { + continue; + } + this->write("var "); + this->write("_var"); + this->write(std::to_string(i)); + this->write(": "); + this->write(to_wgsl_type(args[i]->type())); + + // If this is an inout parameter then we need to copy the input argument into the parameter + // per https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Parameters. + if (func.parameters()[i]->modifiers().fFlags & Modifiers::kIn_Flag) { + this->write(" = "); + this->writeExpression(*args[i], Precedence::kAssignment); + } + + this->writeLine(";"); + } + + // Call the function we're wrapping. If it has a return type, then store it so it can be + // returned later. + bool hasReturn = !c.type().isVoid(); + if (hasReturn) { + this->write("var _return: "); + this->write(to_wgsl_type(c.type())); + this->write(" = "); + } + + // Write the function call. + this->writeName(func.mangledName()); + this->write("("); + auto newSeparator = SkSL::String::Separator(); + if (this->writeFunctionDependencyArgs(func)) { + newSeparator(); + } + for (int i = 0; i < args.size(); ++i) { + this->write(newSeparator()); + // All forwarded arguments now have a name that looks like "_var[i]" (e.g. _var0, var1, + // etc.). All such variables should be of value type and those that have been passed in as + // inout should have been dereferenced when they were stored in a local temporary. We need + // to take their address again when forwarding to a pointer. + if (outVars[i]) { + this->write("&"); + } + this->write("_var"); + this->write(std::to_string(i)); + } + this->writeLine(");"); + + // Copy the temporary variables back into the original out-parameters. + for (int i = 0; i < outVars.size(); ++i) { + if (!outVars[i]) { + continue; + } + // TODO(skia:13092): WGSL does not support assigning to a swizzle + // (see https://github.com/gpuweb/gpuweb/issues/737). These will require special treatment + // when they appear on the lhs of an assignment. + this->writeExpression(*args[i], Precedence::kAssignment); + this->write(" = _var"); + this->write(std::to_string(i)); + this->writeLine(";"); + } + + // Return + if (hasReturn) { + this->writeLine("return _return;"); + } + + --fIndentation; + this->writeLine("}"); + + // Write the function out to `fExtraFunctions`. + write_stringstream(tmpStream, fExtraFunctions); + + // Restore any global state + fOutParamArgVars = std::move(parentOutParamArgVars); + fAtLineStart = atLineStart; + return name; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h new file mode 100644 index 0000000000..9ec57ba17a --- /dev/null +++ b/gfx/skia/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h @@ -0,0 +1,289 @@ +/* + * 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/SkSLDefines.h" +#include "include/private/base/SkTArray.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLStringStream.h" +#include "src/sksl/codegen/SkSLCodeGenerator.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <initializer_list> +#include <string> +#include <string_view> +#include <utility> + +namespace sknonstd { +template <typename T> struct is_bitmask_enum; +} // namespace sknonstd + +namespace SkSL { + +class AnyConstructor; +class BinaryExpression; +class Block; +class Context; +class ConstructorCompound; +class ConstructorDiagonalMatrix; +class ConstructorMatrixResize; +class Expression; +class ExpressionStatement; +class FieldAccess; +class FunctionCall; +class FunctionDeclaration; +class FunctionDefinition; +class GlobalVarDeclaration; +class IfStatement; +class IndexExpression; +class Literal; +class MemoryLayout; +class OutputStream; +class Position; +class ProgramElement; +class ReturnStatement; +class Statement; +class StructDefinition; +class Swizzle; +class TernaryExpression; +class VarDeclaration; +class Variable; +class VariableReference; +enum class OperatorPrecedence : uint8_t; +struct Modifiers; +struct Program; + +/** + * 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 + kSampleMask, // input, output + + // Compute stage: + kLocalInvocationId, // input + kLocalInvocationIndex, // input + kGlobalInvocationId, // input + kWorkgroupId, // input + kNumWorkgroups, // input + }; + + // 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. + enum class FunctionDependencies : uint8_t { + kNone = 0, + kPipelineInputs = 1, + kPipelineOutputs = 2, + }; + + // 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 = SkTHashMap<const FunctionDeclaration*, FunctionDependencies>; + + ProgramRequirements() = default; + ProgramRequirements(DepsMap dependencies, bool mainNeedsCoordsArgument) + : dependencies(std::move(dependencies)) + , mainNeedsCoordsArgument(mainNeedsCoordsArgument) {} + + // Mappings used to synthesize function parameters according to dependencies on pipeline + // input/output variables. + DepsMap dependencies; + + // True, if the main function takes a coordinate parameter. This is used to ensure that + // sk_FragCoord is declared as part of pipeline inputs. + bool mainNeedsCoordsArgument; + }; + + WGSLCodeGenerator(const Context* context, const Program* program, OutputStream* out) + : INHERITED(context, program, out) + , fReservedWords({"array", + "FSIn", + "FSOut", + "_globalUniforms", + "_GlobalUniforms", + "_return", + "_stageIn", + "_stageOut", + "VSIn", + "VSOut"}) {} + + 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(); + void writeName(std::string_view name); + void writeVariableDecl(const Type& type, std::string_view name, Delimiter delimiter); + + // Helpers to declare a pipeline stage IO parameter declaration. + void writePipelineIODeclaration(Modifiers modifiers, + const Type& type, + std::string_view name, + Delimiter delimiter); + void writeUserDefinedIODecl(const Type& type, + std::string_view name, + int location, + Delimiter delimiter); + void writeBuiltinIODecl(const Type& type, + std::string_view name, + Builtin builtin, + Delimiter delimiter); + + // Write a function definition. + void writeFunction(const FunctionDefinition& f); + void writeFunctionDeclaration(const FunctionDeclaration& f); + + // 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 writeExpressionStatement(const ExpressionStatement& s); + void writeIfStatement(const IfStatement& s); + void writeReturnStatement(const ReturnStatement& s); + void writeVarDeclaration(const VarDeclaration& varDecl); + + // Writers for expressions. + void writeExpression(const Expression& e, Precedence parentPrecedence); + void writeBinaryExpression(const BinaryExpression& b, Precedence parentPrecedence); + void writeFieldAccess(const FieldAccess& f); + void writeFunctionCall(const FunctionCall&); + void writeIndexExpression(const IndexExpression& i); + void writeLiteral(const Literal& l); + void writeSwizzle(const Swizzle& swizzle); + void writeTernaryExpression(const TernaryExpression& t, Precedence parentPrecedence); + void writeVariableReference(const VariableReference& r); + + // Constructor expressions + void writeAnyConstructor(const AnyConstructor& c, Precedence parentPrecedence); + void writeConstructorCompound(const ConstructorCompound& c, Precedence parentPrecedence); + void writeConstructorCompoundVector(const ConstructorCompound& c, Precedence parentPrecedence); + void writeConstructorCompoundMatrix(const ConstructorCompound& c, Precedence parentPrecedence); + void writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, + Precedence parentPrecedence); + void writeConstructorMatrixResize(const ConstructorMatrixResize& c, + Precedence parentPrecedence); + + // Matrix constructor helpers. + bool isMatrixConstructorHelperNeeded(const ConstructorCompound& c); + std::string getMatrixConstructorHelper(const AnyConstructor& c); + void writeMatrixFromMatrixArgs(const Type& sourceMatrix, int columns, int rows); + void writeMatrixFromScalarAndVectorArgs(const AnyConstructor& ctor, int columns, int rows); + + // Synthesized helper functions for comparison operators that are not supported by WGSL. + void writeMatrixEquality(const Expression& left, const Expression& right); + + // Generic recursive ProgramElement visitor. + void writeProgramElement(const ProgramElement& e); + void writeGlobalVarDeclaration(const GlobalVarDeclaration& d); + void writeStructDefinition(const StructDefinition& s); + + // 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 Type::Field> fields, + Position parentPos, + const MemoryLayout* layout = nullptr); + + // We bundle all varying pipeline stage inputs and outputs in a struct. + void writeStageInputStruct(); + void writeStageOutputStruct(); + + // 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. + bool writeFunctionDependencyArgs(const FunctionDeclaration&); + bool writeFunctionDependencyParams(const FunctionDeclaration&); + + // Generate an out-parameter helper function for the given call and return its name. + std::string writeOutParamHelper(const FunctionCall&, + const ExpressionArray& args, + const SkTArray<VariableReference*>& outVars); + + // Stores the disallowed identifier names. + SkTHashSet<std::string_view> fReservedWords; + ProgramRequirements fRequirements; + int fPipelineInputCount = 0; + bool fDeclaredUniformsStruct = false; + + // Out-parameters to functions are declared as pointers. While we process the arguments to a + // out-parameter helper function, we need to temporarily track that they are re-declared as + // pointer-parameters in the helper, so that expression-tree processing can know to correctly + // dereference them when the variable is referenced. The contents of this set are expected to + // be uniquely scoped for each out-param helper and will be cleared every time a new out-param + // helper function has been emitted. + SkTHashSet<const Variable*> fOutParamArgVars; + + // Output processing state. + int fIndentation = 0; + bool fAtLineStart = false; + + int fSwizzleHelperCount = 0; + StringStream fExtraFunctions; // all internally synthesized helpers are written here + SkTHashSet<std::string> fHelpers; // all synthesized helper functions, by name +}; + +} // namespace SkSL + +namespace sknonstd { +template <> +struct is_bitmask_enum<SkSL::WGSLCodeGenerator::FunctionDependencies> : std::true_type {}; +} // namespace sknonstd + +#endif // SKSL_WGSLCODEGENERATOR diff --git a/gfx/skia/skia/src/sksl/dsl/DSLBlock.cpp b/gfx/skia/skia/src/sksl/dsl/DSLBlock.cpp new file mode 100644 index 0000000000..9a4a478e74 --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLBlock.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLBlock.h" + +#include "include/sksl/DSLStatement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLBlock.h" + +#include <utility> + +namespace SkSL { + +namespace dsl { + +DSLBlock::DSLBlock(SkSL::StatementArray statements, + std::shared_ptr<SymbolTable> symbols, + Position pos) + : fStatements(std::move(statements)) + , fSymbols(std::move(symbols)) + , fPosition(pos) {} + +DSLBlock::DSLBlock(SkTArray<DSLStatement> statements, + std::shared_ptr<SymbolTable> symbols, + Position pos) + : fSymbols(std::move(symbols)) + , fPosition(pos) { + fStatements.reserve_back(statements.size()); + for (DSLStatement& s : statements) { + fStatements.push_back(s.release()); + } +} + +std::unique_ptr<SkSL::Block> DSLBlock::release() { + return std::make_unique<SkSL::Block>(fPosition, std::move(fStatements), + Block::Kind::kBracedScope, std::move(fSymbols)); +} + +void DSLBlock::append(DSLStatement stmt) { + fStatements.push_back(stmt.release()); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLCase.cpp b/gfx/skia/skia/src/sksl/dsl/DSLCase.cpp new file mode 100644 index 0000000000..4730894824 --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLCase.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLCase.h" + +namespace SkSL { + +namespace dsl { + +DSLCase::DSLCase(DSLExpression value, SkSL::StatementArray statements, Position pos) + : fValue(std::move(value)) + , fStatements(std::move(statements)) + , fPosition(pos) {} + +DSLCase::DSLCase(DSLExpression value, SkTArray<DSLStatement> statements, Position pos) + : fValue(std::move(value)) + , fPosition(pos) { + fStatements.reserve_back(statements.size()); + for (DSLStatement& stmt : statements) { + fStatements.push_back(stmt.release()); + } +} + +DSLCase::DSLCase(DSLCase&& other) + : fValue(std::move(other.fValue)) + , fStatements(std::move(other.fStatements)) {} + +DSLCase::~DSLCase() {} + +DSLCase& DSLCase::operator=(DSLCase&& other) { + fValue.assign(std::move(other.fValue)); + fStatements = std::move(other.fStatements); + return *this; +} + +void DSLCase::append(DSLStatement stmt) { + fStatements.push_back(stmt.release()); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLCore.cpp b/gfx/skia/skia/src/sksl/dsl/DSLCore.cpp new file mode 100644 index 0000000000..21dfca49cf --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLCore.cpp @@ -0,0 +1,615 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLCore.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/DSLModifiers.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/DSLVar.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLModifiersPool.h" // IWYU pragma: keep +#include "src/sksl/SkSLPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/dsl/priv/DSLWriter.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLBreakStatement.h" +#include "src/sksl/ir/SkSLContinueStatement.h" +#include "src/sksl/ir/SkSLDiscardStatement.h" +#include "src/sksl/ir/SkSLDoStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExtension.h" +#include "src/sksl/ir/SkSLForStatement.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLModifiersDeclaration.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +#include <vector> + +namespace SkSL { + +class Variable; + +namespace dsl { + +void Start(SkSL::Compiler* compiler, ProgramKind kind) { + Start(compiler, kind, ProgramSettings()); +} + +void Start(SkSL::Compiler* compiler, ProgramKind kind, const ProgramSettings& settings) { + ThreadContext::SetInstance(std::make_unique<ThreadContext>(compiler, kind, settings, + compiler->moduleForProgramKind(kind), + /*isModule=*/false)); +} + +void StartModule(SkSL::Compiler* compiler, + ProgramKind kind, + const ProgramSettings& settings, + const SkSL::Module* parent) { + ThreadContext::SetInstance(std::make_unique<ThreadContext>(compiler, kind, settings, + parent, /*isModule=*/true)); +} + +void End() { + ThreadContext::SetInstance(nullptr); +} + +ErrorReporter& GetErrorReporter() { + return ThreadContext::GetErrorReporter(); +} + +void SetErrorReporter(ErrorReporter* errorReporter) { + SkASSERT(errorReporter); + ThreadContext::SetErrorReporter(errorReporter); +} + +class DSLCore { +public: + static std::unique_ptr<SkSL::Program> ReleaseProgram(std::unique_ptr<std::string> source) { + ThreadContext& instance = ThreadContext::Instance(); + SkSL::Compiler& compiler = *instance.fCompiler; + Pool* pool = instance.fPool.get(); + auto result = std::make_unique<SkSL::Program>(std::move(source), + std::move(instance.fConfig), + compiler.fContext, + std::move(instance.fProgramElements), + std::move(instance.fSharedElements), + std::move(instance.fModifiersPool), + std::move(compiler.fSymbolTable), + std::move(instance.fPool), + instance.fInputs); + bool success = false; + if (!compiler.finalize(*result)) { + // Do not return programs that failed to compile. + } else if (!compiler.optimize(*result)) { + // Do not return programs that failed to optimize. + } else { + // We have a successful program! + success = true; + } + if (pool) { + pool->detachFromThread(); + } + SkASSERT(instance.fProgramElements.empty()); + SkASSERT(!ThreadContext::SymbolTable()); + return success ? std::move(result) : nullptr; + } + + template <typename... Args> + static DSLExpression Call(const char* name, Position pos, Args... args) { + SkSL::ExpressionArray argArray; + argArray.reserve_back(sizeof...(args)); + ((void)argArray.push_back(args.release()), ...); + + return DSLExpression(SkSL::FunctionCall::Convert(ThreadContext::Context(), pos, + ThreadContext::Compiler().convertIdentifier(Position(), name), + std::move(argArray))); + } + + static DSLStatement Break(Position pos) { + return SkSL::BreakStatement::Make(pos); + } + + static DSLStatement Continue(Position pos) { + return SkSL::ContinueStatement::Make(pos); + } + + static void Declare(const DSLModifiers& modifiers) { + ThreadContext::ProgramElements().push_back(std::make_unique<SkSL::ModifiersDeclaration>( + ThreadContext::Modifiers(modifiers.fModifiers))); + } + + static DSLStatement Declare(DSLVar& var, Position pos) { + return DSLWriter::Declaration(var); + } + + static DSLStatement Declare(SkTArray<DSLVar>& vars, Position pos) { + StatementArray statements; + for (DSLVar& v : vars) { + statements.push_back(Declare(v, pos).release()); + } + return SkSL::Block::Make(pos, std::move(statements), Block::Kind::kCompoundStatement); + } + + static void Declare(DSLGlobalVar& var, Position pos) { + std::unique_ptr<SkSL::Statement> stmt = DSLWriter::Declaration(var); + if (stmt && !stmt->isEmpty()) { + ThreadContext::ProgramElements().push_back( + std::make_unique<SkSL::GlobalVarDeclaration>(std::move(stmt))); + } + } + + static void Declare(SkTArray<DSLGlobalVar>& vars, Position pos) { + for (DSLGlobalVar& v : vars) { + Declare(v, pos); + } + } + + static DSLStatement Discard(Position pos) { + return DSLStatement(SkSL::DiscardStatement::Convert(ThreadContext::Context(), pos), pos); + } + + static DSLStatement Do(DSLStatement stmt, DSLExpression test, Position pos) { + return DSLStatement(DoStatement::Convert(ThreadContext::Context(), pos, stmt.release(), + test.release()), pos); + } + + static DSLStatement For(DSLStatement initializer, DSLExpression test, + DSLExpression next, DSLStatement stmt, Position pos, + const ForLoopPositions& forLoopPositions) { + return DSLStatement(ForStatement::Convert(ThreadContext::Context(), pos, forLoopPositions, + initializer.releaseIfPossible(), + test.releaseIfPossible(), + next.releaseIfPossible(), + stmt.release(), + ThreadContext::SymbolTable()), pos); + } + + static DSLStatement If(DSLExpression test, DSLStatement ifTrue, DSLStatement ifFalse, + Position pos) { + return DSLStatement(IfStatement::Convert(ThreadContext::Context(), + pos, + test.release(), + ifTrue.release(), + ifFalse.releaseIfPossible()), pos); + } + + static DSLExpression InterfaceBlock(const DSLModifiers& modifiers, std::string_view typeName, + SkTArray<DSLField> fields, std::string_view varName, + int arraySize, Position pos) { + // Build a struct type corresponding to the passed-in fields and array size. + DSLType varType = StructType(typeName, fields, /*interfaceBlock=*/true, pos); + if (arraySize > 0) { + varType = Array(varType, arraySize); + } + + // Create a global variable to attach our interface block to. (The variable doesn't actually + // get a program element, though; the interface block does instead.) + DSLGlobalVar var(modifiers, varType, varName, DSLExpression(), pos); + if (SkSL::Variable* skslVar = DSLWriter::Var(var)) { + // Add an InterfaceBlock program element to the program. + if (std::unique_ptr<SkSL::InterfaceBlock> intf = SkSL::InterfaceBlock::Convert( + ThreadContext::Context(), pos, skslVar, ThreadContext::SymbolTable())) { + ThreadContext::ProgramElements().push_back(std::move(intf)); + // Return a VariableReference to the global variable tied to the interface block. + return DSLExpression(var); + } + } + + // The InterfaceBlock couldn't be created; return poison. + return DSLExpression(nullptr); + } + + static DSLStatement Return(DSLExpression value, Position pos) { + // Note that because Return is called before the function in which it resides exists, at + // this point we do not know the function's return type. We therefore do not check for + // errors, or coerce the value to the correct type, until the return statement is actually + // added to a function. (This is done in FunctionDefinition::Convert.) + return SkSL::ReturnStatement::Make(pos, value.releaseIfPossible()); + } + + static DSLExpression Swizzle(DSLExpression base, SkSL::SwizzleComponent::Type a, + Position pos, Position maskPos) { + return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos, + base.release(), ComponentArray{a}), + pos); + } + + static DSLExpression Swizzle(DSLExpression base, + SkSL::SwizzleComponent::Type a, + SkSL::SwizzleComponent::Type b, + Position pos, + Position maskPos) { + return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos, + base.release(), ComponentArray{a, b}), + pos); + } + + static DSLExpression Swizzle(DSLExpression base, + SkSL::SwizzleComponent::Type a, + SkSL::SwizzleComponent::Type b, + SkSL::SwizzleComponent::Type c, + Position pos, + Position maskPos) { + return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos, + base.release(), ComponentArray{a, b, c}), + pos); + } + + static DSLExpression Swizzle(DSLExpression base, + SkSL::SwizzleComponent::Type a, + SkSL::SwizzleComponent::Type b, + SkSL::SwizzleComponent::Type c, + SkSL::SwizzleComponent::Type d, + Position pos, + Position maskPos) { + return DSLExpression(Swizzle::Convert(ThreadContext::Context(), pos, maskPos, + base.release(), ComponentArray{a, b, c, d}), + pos); + } + + static DSLExpression Select(DSLExpression test, DSLExpression ifTrue, DSLExpression ifFalse, + Position pos) { + auto result = TernaryExpression::Convert(ThreadContext::Context(), pos, test.release(), + ifTrue.release(), ifFalse.release()); + SkASSERT(!result || result->fPosition == pos); + return DSLExpression(std::move(result), pos); + } + + static DSLStatement Switch(DSLExpression value, SkTArray<DSLCase> cases, Position pos) { + ExpressionArray values; + values.reserve_back(cases.size()); + StatementArray caseBlocks; + caseBlocks.reserve_back(cases.size()); + for (DSLCase& c : cases) { + values.push_back(c.fValue.releaseIfPossible()); + caseBlocks.push_back(SkSL::Block::Make(Position(), std::move(c.fStatements), + Block::Kind::kUnbracedBlock)); + } + return DSLStatement(SwitchStatement::Convert(ThreadContext::Context(), pos, + value.release(), + std::move(values), + std::move(caseBlocks), + ThreadContext::SymbolTable()), pos); + } + + static DSLStatement While(DSLExpression test, DSLStatement stmt, Position pos) { + return DSLStatement(ForStatement::ConvertWhile(ThreadContext::Context(), pos, + test.release(), + stmt.release(), + ThreadContext::SymbolTable()), pos); + } +}; + +std::unique_ptr<SkSL::Program> ReleaseProgram(std::unique_ptr<std::string> source) { + return DSLCore::ReleaseProgram(std::move(source)); +} + +void AddExtension(std::string_view name, Position pos) { + ThreadContext::ProgramElements().push_back(std::make_unique<SkSL::Extension>(pos, name)); +} + +DSLStatement Break(Position pos) { + return DSLCore::Break(pos); +} + +DSLStatement Continue(Position pos) { + return DSLCore::Continue(pos); +} + +void Declare(const DSLModifiers& modifiers, Position pos) { + SkSL::ProgramKind kind = ThreadContext::GetProgramConfig()->fKind; + if (!ProgramConfig::IsFragment(kind) && + !ProgramConfig::IsVertex(kind)) { + ThreadContext::ReportError("layout qualifiers are not allowed in this kind of program", + pos); + return; + } + DSLCore::Declare(modifiers); +} + +// Logically, we'd want the variable's initial value to appear on here in Declare, since that +// matches how we actually write code (and in fact that was what our first attempt looked like). +// Unfortunately, C++ doesn't guarantee execution order between arguments, and Declare() can appear +// as a function argument in constructs like Block(Declare(x, 0), foo(x)). If these are executed out +// of order, we will evaluate the reference to x before we evaluate Declare(x, 0), and thus the +// variable's initial value is unknown at the point of reference. There are probably some other +// issues with this as well, but it is particularly dangerous when x is const, since SkSL will +// expect its value to be known when it is referenced and will end up asserting, dereferencing a +// null pointer, or possibly doing something else awful. +// +// So, we put the initial value onto the Var itself instead of the Declare to guarantee that it is +// always executed in the correct order. +DSLStatement Declare(DSLVar& var, Position pos) { + return DSLCore::Declare(var, pos); +} + +DSLStatement Declare(SkTArray<DSLVar>& vars, Position pos) { + return DSLCore::Declare(vars, pos); +} + +void Declare(DSLGlobalVar& var, Position pos) { + DSLCore::Declare(var, pos); +} + +void Declare(SkTArray<DSLGlobalVar>& vars, Position pos) { + DSLCore::Declare(vars, pos); +} + +DSLStatement Discard(Position pos) { + return DSLCore::Discard(pos); +} + +DSLStatement Do(DSLStatement stmt, DSLExpression test, Position pos) { + return DSLCore::Do(std::move(stmt), std::move(test), pos); +} + +DSLStatement For(DSLStatement initializer, DSLExpression test, DSLExpression next, + DSLStatement stmt, Position pos, ForLoopPositions forLoopPositions) { + return DSLCore::For(std::move(initializer), std::move(test), std::move(next), + std::move(stmt), pos, forLoopPositions); +} + +DSLStatement If(DSLExpression test, DSLStatement ifTrue, DSLStatement ifFalse, Position pos) { + return DSLCore::If(std::move(test), std::move(ifTrue), std::move(ifFalse), pos); +} + +DSLExpression InterfaceBlock(const DSLModifiers& modifiers, std::string_view typeName, + SkTArray<DSLField> fields, std::string_view varName, int arraySize, + Position pos) { + return DSLCore::InterfaceBlock(modifiers, typeName, std::move(fields), varName, arraySize, pos); +} + +DSLStatement Return(DSLExpression expr, Position pos) { + return DSLCore::Return(std::move(expr), pos); +} + +DSLExpression Select(DSLExpression test, DSLExpression ifTrue, DSLExpression ifFalse, + Position pos) { + return DSLCore::Select(std::move(test), std::move(ifTrue), std::move(ifFalse), pos); +} + +DSLStatement Switch(DSLExpression value, SkTArray<DSLCase> cases, Position pos) { + return DSLCore::Switch(std::move(value), std::move(cases), pos); +} + +DSLStatement While(DSLExpression test, DSLStatement stmt, Position pos) { + return DSLCore::While(std::move(test), std::move(stmt), pos); +} + +DSLExpression Abs(DSLExpression x, Position pos) { + return DSLCore::Call("abs", pos, std::move(x)); +} + +DSLExpression All(DSLExpression x, Position pos) { + return DSLCore::Call("all", pos, std::move(x)); +} + +DSLExpression Any(DSLExpression x, Position pos) { + return DSLCore::Call("any", pos, std::move(x)); +} + +DSLExpression Atan(DSLExpression y_over_x, Position pos) { + return DSLCore::Call("atan", pos, std::move(y_over_x)); +} + +DSLExpression Atan(DSLExpression y, DSLExpression x, Position pos) { + return DSLCore::Call("atan", pos, std::move(y), std::move(x)); +} + +DSLExpression Ceil(DSLExpression x, Position pos) { + return DSLCore::Call("ceil", pos, std::move(x)); +} + +DSLExpression Clamp(DSLExpression x, DSLExpression min, DSLExpression max, Position pos) { + return DSLCore::Call("clamp", pos, std::move(x), std::move(min), std::move(max)); +} + +DSLExpression Cos(DSLExpression x, Position pos) { + return DSLCore::Call("cos", pos, std::move(x)); +} + +DSLExpression Cross(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("cross", pos, std::move(x), std::move(y)); +} + +DSLExpression Degrees(DSLExpression x, Position pos) { + return DSLCore::Call("degrees", pos, std::move(x)); +} + +DSLExpression Distance(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("distance", pos, std::move(x), std::move(y)); +} + +DSLExpression Dot(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("dot", pos, std::move(x), std::move(y)); +} + +DSLExpression Equal(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("equal", pos, std::move(x), std::move(y)); +} + +DSLExpression Exp(DSLExpression x, Position pos) { + return DSLCore::Call("exp", pos, std::move(x)); +} + +DSLExpression Exp2(DSLExpression x, Position pos) { + return DSLCore::Call("exp2", pos, std::move(x)); +} + +DSLExpression Faceforward(DSLExpression n, DSLExpression i, DSLExpression nref, Position pos) { + return DSLCore::Call("faceforward", pos, std::move(n), std::move(i), std::move(nref)); +} + +DSLExpression Fract(DSLExpression x, Position pos) { + return DSLCore::Call("fract", pos, std::move(x)); +} + +DSLExpression Floor(DSLExpression x, Position pos) { + return DSLCore::Call("floor", pos, std::move(x)); +} + +DSLExpression GreaterThan(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("greaterThan", pos, std::move(x), std::move(y)); +} + +DSLExpression GreaterThanEqual(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("greaterThanEqual", pos, std::move(x), std::move(y)); +} + +DSLExpression Inverse(DSLExpression x, Position pos) { + return DSLCore::Call("inverse", pos, std::move(x)); +} + +DSLExpression Inversesqrt(DSLExpression x, Position pos) { + return DSLCore::Call("inversesqrt", pos, std::move(x)); +} + +DSLExpression Length(DSLExpression x, Position pos) { + return DSLCore::Call("length", pos, std::move(x)); +} + +DSLExpression LessThan(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("lessThan", pos, std::move(x), std::move(y)); +} + +DSLExpression LessThanEqual(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("lessThanEqual", pos, std::move(x), std::move(y)); +} + +DSLExpression Log(DSLExpression x, Position pos) { + return DSLCore::Call("log", pos, std::move(x)); +} + +DSLExpression Log2(DSLExpression x, Position pos) { + return DSLCore::Call("log2", pos, std::move(x)); +} + +DSLExpression Max(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("max", pos, std::move(x), std::move(y)); +} + +DSLExpression Min(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("min", pos, std::move(x), std::move(y)); +} + +DSLExpression Mix(DSLExpression x, DSLExpression y, DSLExpression a, Position pos) { + return DSLCore::Call("mix", pos, std::move(x), std::move(y), std::move(a)); +} + +DSLExpression Mod(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("mod", pos, std::move(x), std::move(y)); +} + +DSLExpression Normalize(DSLExpression x, Position pos) { + return DSLCore::Call("normalize", pos, std::move(x)); +} + +DSLExpression NotEqual(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("notEqual", pos, std::move(x), std::move(y)); +} + +DSLExpression Pow(DSLExpression x, DSLExpression y, Position pos) { + return DSLCore::Call("pow", pos, std::move(x), std::move(y)); +} + +DSLExpression Radians(DSLExpression x, Position pos) { + return DSLCore::Call("radians", pos, std::move(x)); +} + +DSLExpression Reflect(DSLExpression i, DSLExpression n, Position pos) { + return DSLCore::Call("reflect", pos, std::move(i), std::move(n)); +} + +DSLExpression Refract(DSLExpression i, DSLExpression n, DSLExpression eta, Position pos) { + return DSLCore::Call("refract", pos, std::move(i), std::move(n), std::move(eta)); +} + +DSLExpression Round(DSLExpression x, Position pos) { + return DSLCore::Call("round", pos, std::move(x)); +} + +DSLExpression Saturate(DSLExpression x, Position pos) { + return DSLCore::Call("saturate", pos, std::move(x)); +} + +DSLExpression Sign(DSLExpression x, Position pos) { + return DSLCore::Call("sign", pos, std::move(x)); +} + +DSLExpression Sin(DSLExpression x, Position pos) { + return DSLCore::Call("sin", pos, std::move(x)); +} + +DSLExpression Smoothstep(DSLExpression edge1, DSLExpression edge2, DSLExpression x, + Position pos) { + return DSLCore::Call("smoothstep", pos, std::move(edge1), std::move(edge2), std::move(x)); +} + +DSLExpression Sqrt(DSLExpression x, Position pos) { + return DSLCore::Call("sqrt", pos, std::move(x)); +} + +DSLExpression Step(DSLExpression edge, DSLExpression x, Position pos) { + return DSLCore::Call("step", pos, std::move(edge), std::move(x)); +} + +DSLExpression Swizzle(DSLExpression base, SkSL::SwizzleComponent::Type a, + Position pos, Position maskPos) { + return DSLCore::Swizzle(std::move(base), a, pos, maskPos); +} + +DSLExpression Swizzle(DSLExpression base, + SkSL::SwizzleComponent::Type a, + SkSL::SwizzleComponent::Type b, + Position pos, + Position maskPos) { + return DSLCore::Swizzle(std::move(base), a, b, pos, maskPos); +} + +DSLExpression Swizzle(DSLExpression base, + SkSL::SwizzleComponent::Type a, + SkSL::SwizzleComponent::Type b, + SkSL::SwizzleComponent::Type c, + Position pos, + Position maskPos) { + return DSLCore::Swizzle(std::move(base), a, b, c, pos, maskPos); +} + +DSLExpression Swizzle(DSLExpression base, + SkSL::SwizzleComponent::Type a, + SkSL::SwizzleComponent::Type b, + SkSL::SwizzleComponent::Type c, + SkSL::SwizzleComponent::Type d, + Position pos, + Position maskPos) { + return DSLCore::Swizzle(std::move(base), a, b, c, d, pos, maskPos); +} + +DSLExpression Tan(DSLExpression x, Position pos) { + return DSLCore::Call("tan", pos, std::move(x)); +} + +DSLExpression Unpremul(DSLExpression x, Position pos) { + return DSLCore::Call("unpremul", pos, std::move(x)); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLExpression.cpp b/gfx/skia/skia/src/sksl/dsl/DSLExpression.cpp new file mode 100644 index 0000000000..937f58432b --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLExpression.cpp @@ -0,0 +1,295 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLExpression.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/sksl/DSLCore.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/DSLVar.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/dsl/priv/DSLWriter.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLPoison.h" +#include "src/sksl/ir/SkSLPostfixExpression.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <utility> + +namespace SkSL { + +namespace dsl { + +DSLExpression::DSLExpression() {} + +DSLExpression::DSLExpression(DSLExpression&& other) + : fExpression(std::move(other.fExpression)) {} + +DSLExpression::DSLExpression(std::unique_ptr<SkSL::Expression> expression, Position pos) + : fExpression(expression ? std::move(expression) + : SkSL::Poison::Make(pos, ThreadContext::Context())) { + // If a position was passed in, it must match the expression's position. + SkASSERTF(!pos.valid() || this->position() == pos, + "expected expression position (%d-%d), but received (%d-%d)", + pos.startOffset(), pos.endOffset(), + this->position().startOffset(), this->position().endOffset()); +} + +DSLExpression::DSLExpression(float value, Position pos) + : fExpression(SkSL::Literal::MakeFloat(ThreadContext::Context(), + pos, + value)) {} + +DSLExpression::DSLExpression(int value, Position pos) + : fExpression(SkSL::Literal::MakeInt(ThreadContext::Context(), + pos, + value)) {} + +DSLExpression::DSLExpression(int64_t value, Position pos) + : fExpression(SkSL::Literal::MakeInt(ThreadContext::Context(), + pos, + value)) {} + +DSLExpression::DSLExpression(unsigned int value, Position pos) + : fExpression(SkSL::Literal::MakeInt(ThreadContext::Context(), + pos, + value)) {} + +DSLExpression::DSLExpression(bool value, Position pos) + : fExpression(SkSL::Literal::MakeBool(ThreadContext::Context(), + pos, + value)) {} + +DSLExpression::DSLExpression(DSLVarBase& var, Position pos) + : fExpression(std::make_unique<SkSL::VariableReference>( + pos, DSLWriter::Var(var), SkSL::VariableReference::RefKind::kRead)) {} + +DSLExpression::DSLExpression(DSLVarBase&& var, Position pos) + : DSLExpression(var) {} + +DSLExpression::~DSLExpression() {} + +DSLExpression DSLExpression::Poison(Position pos) { + return DSLExpression(SkSL::Poison::Make(pos, ThreadContext::Context())); +} + +bool DSLExpression::isValid() const { + return this->hasValue() && !fExpression->is<SkSL::Poison>(); +} + +void DSLExpression::swap(DSLExpression& other) { + std::swap(fExpression, other.fExpression); +} + +std::unique_ptr<SkSL::Expression> DSLExpression::release() { + SkASSERT(this->hasValue()); + return std::move(fExpression); +} + +std::unique_ptr<SkSL::Expression> DSLExpression::releaseIfPossible() { + return std::move(fExpression); +} + +DSLType DSLExpression::type() const { + if (!this->hasValue()) { + return kVoid_Type; + } + return &fExpression->type(); +} + +std::string DSLExpression::description() const { + SkASSERT(this->hasValue()); + return fExpression->description(); +} + +Position DSLExpression::position() const { + SkASSERT(this->hasValue()); + return fExpression->fPosition; +} + +void DSLExpression::setPosition(Position pos) { + SkASSERT(this->hasValue()); + fExpression->fPosition = pos; +} + +DSLExpression DSLExpression::x(Position pos) { + return Swizzle(std::move(*this), X, pos); +} + +DSLExpression DSLExpression::y(Position pos) { + return Swizzle(std::move(*this), Y, pos); +} + +DSLExpression DSLExpression::z(Position pos) { + return Swizzle(std::move(*this), Z, pos); +} + +DSLExpression DSLExpression::w(Position pos) { + return Swizzle(std::move(*this), W, pos); +} + +DSLExpression DSLExpression::r(Position pos) { + return Swizzle(std::move(*this), R, pos); +} + +DSLExpression DSLExpression::g(Position pos) { + return Swizzle(std::move(*this), G, pos); +} + +DSLExpression DSLExpression::b(Position pos) { + return Swizzle(std::move(*this), B, pos); +} + +DSLExpression DSLExpression::a(Position pos) { + return Swizzle(std::move(*this), A, pos); +} + +DSLExpression DSLExpression::field(std::string_view name, Position pos) { + return DSLExpression(FieldAccess::Convert(ThreadContext::Context(), pos, + *ThreadContext::SymbolTable(), this->release(), name), pos); +} + +DSLExpression DSLExpression::assign(DSLExpression right) { + Position pos = this->position().rangeThrough(right.position()); + return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), pos, this->release(), + SkSL::Operator::Kind::EQ, right.release())); +} + +DSLExpression DSLExpression::operator[](DSLExpression right) { + Position pos = this->position().rangeThrough(right.position()); + return DSLExpression(IndexExpression::Convert(ThreadContext::Context(), + *ThreadContext::SymbolTable(), pos, + this->release(), right.release())); +} + +DSLExpression DSLExpression::index(DSLExpression index, Position pos) { + std::unique_ptr<SkSL::Expression> result = IndexExpression::Convert(ThreadContext::Context(), + *ThreadContext::SymbolTable(), pos, this->release(), index.release()); + return DSLExpression(std::move(result), pos); +} + +DSLExpression DSLExpression::operator()(SkTArray<DSLExpression> args, Position pos) { + ExpressionArray converted; + converted.reserve_back(args.size()); + for (DSLExpression& arg : args) { + converted.push_back(arg.release()); + } + return (*this)(std::move(converted), pos); +} + +DSLExpression DSLExpression::operator()(ExpressionArray args, Position pos) { + return DSLExpression(SkSL::FunctionCall::Convert(ThreadContext::Context(), pos, this->release(), + std::move(args)), pos); +} + +DSLExpression DSLExpression::prefix(Operator::Kind op, Position pos) { + std::unique_ptr<SkSL::Expression> result = PrefixExpression::Convert(ThreadContext::Context(), + pos, op, this->release()); + return DSLExpression(std::move(result), pos); +} + +DSLExpression DSLExpression::postfix(Operator::Kind op, Position pos) { + std::unique_ptr<SkSL::Expression> result = PostfixExpression::Convert(ThreadContext::Context(), + pos, this->release(), op); + return DSLExpression(std::move(result), pos); +} + +DSLExpression DSLExpression::binary(Operator::Kind op, DSLExpression right, Position pos) { + std::unique_ptr<SkSL::Expression> result = BinaryExpression::Convert(ThreadContext::Context(), + pos, this->release(), op, right.release()); + return DSLExpression(std::move(result), pos); +} + +#define OP(op, token) \ +DSLExpression operator op(DSLExpression left, DSLExpression right) { \ + return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), \ + Position(), \ + left.release(), \ + Operator::Kind::token, \ + right.release())); \ +} + +#define PREFIXOP(op, token) \ +DSLExpression operator op(DSLExpression expr) { \ + return DSLExpression(PrefixExpression::Convert(ThreadContext::Context(), \ + Position(), \ + Operator::Kind::token, \ + expr.release())); \ +} + +#define POSTFIXOP(op, token) \ +DSLExpression operator op(DSLExpression expr, int) { \ + return DSLExpression(PostfixExpression::Convert(ThreadContext::Context(), \ + Position(), \ + expr.release(), \ + Operator::Kind::token)); \ +} + +OP(+, PLUS) +OP(+=, PLUSEQ) +OP(-, MINUS) +OP(-=, MINUSEQ) +OP(*, STAR) +OP(*=, STAREQ) +OP(/, SLASH) +OP(/=, SLASHEQ) +OP(%, PERCENT) +OP(%=, PERCENTEQ) +OP(<<, SHL) +OP(<<=, SHLEQ) +OP(>>, SHR) +OP(>>=, SHREQ) +OP(&&, LOGICALAND) +OP(||, LOGICALOR) +OP(&, BITWISEAND) +OP(&=, BITWISEANDEQ) +OP(|, BITWISEOR) +OP(|=, BITWISEOREQ) +OP(^, BITWISEXOR) +OP(^=, BITWISEXOREQ) +DSLExpression LogicalXor(DSLExpression left, DSLExpression right) { + return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), + Position(), + left.release(), + SkSL::Operator::Kind::LOGICALXOR, + right.release())); +} +OP(==, EQEQ) +OP(!=, NEQ) +OP(>, GT) +OP(<, LT) +OP(>=, GTEQ) +OP(<=, LTEQ) + +PREFIXOP(+, PLUS) +PREFIXOP(-, MINUS) +PREFIXOP(!, LOGICALNOT) +PREFIXOP(~, BITWISENOT) +PREFIXOP(++, PLUSPLUS) +POSTFIXOP(++, PLUSPLUS) +PREFIXOP(--, MINUSMINUS) +POSTFIXOP(--, MINUSMINUS) + +DSLExpression operator,(DSLExpression left, DSLExpression right) { + return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), + Position(), + left.release(), + SkSL::Operator::Kind::COMMA, + right.release())); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLFunction.cpp b/gfx/skia/skia/src/sksl/dsl/DSLFunction.cpp new file mode 100644 index 0000000000..1a3836ce5a --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLFunction.cpp @@ -0,0 +1,146 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLFunction.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLString.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/DSLVar.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/dsl/priv/DSLWriter.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLFunctionPrototype.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <cstddef> +#include <memory> +#include <string> +#include <vector> + +namespace SkSL { + +namespace dsl { + +static bool is_intrinsic_in_module(const Context& context, std::string_view name) { + return context.fConfig->fIsBuiltinCode && SkSL::FindIntrinsicKind(name) != kNotIntrinsic; +} + +void DSLFunction::init(DSLModifiers modifiers, const DSLType& returnType, std::string_view name, + SkSpan<DSLParameter*> params, Position pos) { + fPosition = pos; + + const Context& context = ThreadContext::Context(); + if (context.fConfig->fSettings.fForceNoInline) { + // Apply the `noinline` modifier to every function. This allows us to test Runtime + // Effects without any inlining, even when the code is later added to a paint. + modifiers.fModifiers.fFlags &= ~Modifiers::kInline_Flag; + modifiers.fModifiers.fFlags |= Modifiers::kNoInline_Flag; + } + + std::vector<std::unique_ptr<Variable>> paramVars; + paramVars.reserve(params.size()); + for (DSLParameter* param : params) { + SkASSERT(!param->fInitialValue.hasValue()); + SkASSERT(!param->fDeclaration); + std::unique_ptr<SkSL::Variable> paramVar = DSLWriter::CreateParameterVar(*param); + if (!paramVar) { + return; + } + paramVars.push_back(std::move(paramVar)); + } + SkASSERT(paramVars.size() == params.size()); + fDecl = SkSL::FunctionDeclaration::Convert(context, + *ThreadContext::SymbolTable(), + pos, + modifiers.fPosition, + context.fModifiersPool->add(modifiers.fModifiers), + name, + std::move(paramVars), + pos, + &returnType.skslType()); + if (fDecl) { + for (size_t i = 0; i < params.size(); ++i) { + params[i]->fVar = fDecl->parameters()[i]; + params[i]->fInitialized = true; + } + } +} + +void DSLFunction::prototype() { + if (!fDecl) { + // We failed to create the declaration; error should already have been reported. + return; + } + ThreadContext::ProgramElements().push_back(std::make_unique<SkSL::FunctionPrototype>( + fDecl->fPosition, fDecl, ThreadContext::IsModule())); +} + +void DSLFunction::define(DSLBlock block, Position pos) { + std::unique_ptr<SkSL::Block> body = block.release(); + body->fPosition = pos; + if (!fDecl) { + // We failed to create the declaration; error should already have been reported. + return; + } + // We don't allow modules to define actual functions with intrinsic names. (Those should be + // reserved for actual intrinsics.) + const Context& context = ThreadContext::Context(); + if (is_intrinsic_in_module(context, fDecl->name())) { + ThreadContext::ReportError( + SkSL::String::printf("Intrinsic function '%.*s' should not have a definition", + (int)fDecl->name().size(), + fDecl->name().data()), + fDecl->fPosition); + return; + } + + if (fDecl->definition()) { + ThreadContext::ReportError(SkSL::String::printf("function '%s' was already defined", + fDecl->description().c_str()), + fDecl->fPosition); + return; + } + std::unique_ptr<FunctionDefinition> function = FunctionDefinition::Convert( + ThreadContext::Context(), + pos, + *fDecl, + std::move(body), + /*builtin=*/false); + fDecl->setDefinition(function.get()); + ThreadContext::ProgramElements().push_back(std::move(function)); +} + +DSLExpression DSLFunction::call(SkSpan<DSLExpression> args, Position pos) { + ExpressionArray released; + released.reserve_back(args.size()); + for (DSLExpression& arg : args) { + released.push_back(arg.release()); + } + return this->call(std::move(released)); +} + +DSLExpression DSLFunction::call(ExpressionArray args, Position pos) { + std::unique_ptr<SkSL::Expression> result = + SkSL::FunctionCall::Convert(ThreadContext::Context(), pos, *fDecl, std::move(args)); + return DSLExpression(std::move(result), pos); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLLayout.cpp b/gfx/skia/skia/src/sksl/dsl/DSLLayout.cpp new file mode 100644 index 0000000000..4ef840f230 --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLLayout.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLLayout.h" + +#include "src/sksl/SkSLThreadContext.h" + +#include <string> + +namespace SkSL { + +namespace dsl { + +DSLLayout& DSLLayout::flag(SkSL::Layout::Flag mask, const char* name, Position pos) { + if (fSkSLLayout.fFlags & mask) { + ThreadContext::ReportError("layout qualifier '" + std::string(name) + + "' appears more than once", pos); + } + fSkSLLayout.fFlags |= mask; + return *this; +} + +DSLLayout& DSLLayout::intValue(int* target, int value, SkSL::Layout::Flag flag, const char* name, + Position pos) { + this->flag(flag, name, pos); + *target = value; + return *this; +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLStatement.cpp b/gfx/skia/skia/src/sksl/dsl/DSLStatement.cpp new file mode 100644 index 0000000000..ed11acac80 --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLStatement.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLStatement.h" + +#include "include/private/SkSLDefines.h" +#include "include/sksl/DSLBlock.h" +#include "include/sksl/DSLExpression.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLNop.h" + +namespace SkSL { + +namespace dsl { + +DSLStatement::DSLStatement() {} + +DSLStatement::DSLStatement(DSLBlock block) + : fStatement(block.release()) {} + +DSLStatement::DSLStatement(DSLExpression expr) { + std::unique_ptr<SkSL::Expression> skslExpr = expr.release(); + if (skslExpr) { + fStatement = SkSL::ExpressionStatement::Convert(ThreadContext::Context(), + std::move(skslExpr)); + } +} + +DSLStatement::DSLStatement(std::unique_ptr<SkSL::Expression> expr) + : fStatement(SkSL::ExpressionStatement::Convert(ThreadContext::Context(), std::move(expr))) { + SkASSERT(this->hasValue()); +} + +DSLStatement::DSLStatement(std::unique_ptr<SkSL::Statement> stmt) + : fStatement(std::move(stmt)) { + SkASSERT(this->hasValue()); +} + +DSLStatement::DSLStatement(std::unique_ptr<SkSL::Statement> stmt, Position pos) + : fStatement(stmt ? std::move(stmt) : SkSL::Nop::Make()) { + if (pos.valid() && !fStatement->fPosition.valid()) { + fStatement->fPosition = pos; + } +} + +DSLStatement::~DSLStatement() {} + +DSLStatement operator,(DSLStatement left, DSLStatement right) { + Position pos = left.fStatement->fPosition; + StatementArray stmts; + stmts.reserve_back(2); + stmts.push_back(left.release()); + stmts.push_back(right.release()); + return DSLStatement(SkSL::Block::Make(pos, std::move(stmts), Block::Kind::kCompoundStatement)); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLType.cpp b/gfx/skia/skia/src/sksl/dsl/DSLType.cpp new file mode 100644 index 0000000000..82c000e5ab --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLType.cpp @@ -0,0 +1,316 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLType.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLString.h" +#include "include/private/SkSLSymbol.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLStructDefinition.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" + +#include <memory> +#include <string> +#include <vector> + +namespace SkSL { + +struct Modifiers; + +namespace dsl { + +static const SkSL::Type* verify_type(const Context& context, + const SkSL::Type* type, + bool allowGenericTypes, + Position pos) { + if (!context.fConfig->fIsBuiltinCode && type) { + if (!allowGenericTypes && (type->isGeneric() || type->isLiteral())) { + context.fErrors->error(pos, "type '" + std::string(type->name()) + "' is generic"); + return context.fTypes.fPoison.get(); + } + if (!type->isAllowedInES2(context)) { + context.fErrors->error(pos, "type '" + std::string(type->name()) +"' is not supported"); + return context.fTypes.fPoison.get(); + } + } + return type; +} + +static const SkSL::Type* find_type(const Context& context, + Position pos, + std::string_view name) { + const Symbol* symbol = ThreadContext::SymbolTable()->find(name); + if (!symbol) { + context.fErrors->error(pos, String::printf("no symbol named '%.*s'", + (int)name.length(), name.data())); + return context.fTypes.fPoison.get(); + } + if (!symbol->is<SkSL::Type>()) { + context.fErrors->error(pos, String::printf("symbol '%.*s' is not a type", + (int)name.length(), name.data())); + return context.fTypes.fPoison.get(); + } + const SkSL::Type* type = &symbol->as<SkSL::Type>(); + return verify_type(context, type, /*allowGenericTypes=*/false, pos); +} + +static const SkSL::Type* find_type(const Context& context, + Position overallPos, + std::string_view name, + Position modifiersPos, + Modifiers* modifiers) { + const auto* type = find_type(context, overallPos, name); + return type->applyQualifiers(context, modifiers, ThreadContext::SymbolTable().get(), + modifiersPos); +} + +static const SkSL::Type* get_type_from_type_constant(TypeConstant tc) { + const Context& context = ThreadContext::Context(); + switch (tc) { + case kBool_Type: + return context.fTypes.fBool.get(); + case kBool2_Type: + return context.fTypes.fBool2.get(); + case kBool3_Type: + return context.fTypes.fBool3.get(); + case kBool4_Type: + return context.fTypes.fBool4.get(); + case kHalf_Type: + return context.fTypes.fHalf.get(); + case kHalf2_Type: + return context.fTypes.fHalf2.get(); + case kHalf3_Type: + return context.fTypes.fHalf3.get(); + case kHalf4_Type: + return context.fTypes.fHalf4.get(); + case kHalf2x2_Type: + return context.fTypes.fHalf2x2.get(); + case kHalf3x2_Type: + return context.fTypes.fHalf3x2.get(); + case kHalf4x2_Type: + return context.fTypes.fHalf4x2.get(); + case kHalf2x3_Type: + return context.fTypes.fHalf2x3.get(); + case kHalf3x3_Type: + return context.fTypes.fHalf3x3.get(); + case kHalf4x3_Type: + return context.fTypes.fHalf4x3.get(); + case kHalf2x4_Type: + return context.fTypes.fHalf2x4.get(); + case kHalf3x4_Type: + return context.fTypes.fHalf3x4.get(); + case kHalf4x4_Type: + return context.fTypes.fHalf4x4.get(); + case kFloat_Type: + return context.fTypes.fFloat.get(); + case kFloat2_Type: + return context.fTypes.fFloat2.get(); + case kFloat3_Type: + return context.fTypes.fFloat3.get(); + case kFloat4_Type: + return context.fTypes.fFloat4.get(); + case kFloat2x2_Type: + return context.fTypes.fFloat2x2.get(); + case kFloat3x2_Type: + return context.fTypes.fFloat3x2.get(); + case kFloat4x2_Type: + return context.fTypes.fFloat4x2.get(); + case kFloat2x3_Type: + return context.fTypes.fFloat2x3.get(); + case kFloat3x3_Type: + return context.fTypes.fFloat3x3.get(); + case kFloat4x3_Type: + return context.fTypes.fFloat4x3.get(); + case kFloat2x4_Type: + return context.fTypes.fFloat2x4.get(); + case kFloat3x4_Type: + return context.fTypes.fFloat3x4.get(); + case kFloat4x4_Type: + return context.fTypes.fFloat4x4.get(); + case kInt_Type: + return context.fTypes.fInt.get(); + case kInt2_Type: + return context.fTypes.fInt2.get(); + case kInt3_Type: + return context.fTypes.fInt3.get(); + case kInt4_Type: + return context.fTypes.fInt4.get(); + case kShader_Type: + return context.fTypes.fShader.get(); + case kShort_Type: + return context.fTypes.fShort.get(); + case kShort2_Type: + return context.fTypes.fShort2.get(); + case kShort3_Type: + return context.fTypes.fShort3.get(); + case kShort4_Type: + return context.fTypes.fShort4.get(); + case kUInt_Type: + return context.fTypes.fUInt.get(); + case kUInt2_Type: + return context.fTypes.fUInt2.get(); + case kUInt3_Type: + return context.fTypes.fUInt3.get(); + case kUInt4_Type: + return context.fTypes.fUInt4.get(); + case kUShort_Type: + return context.fTypes.fUShort.get(); + case kUShort2_Type: + return context.fTypes.fUShort2.get(); + case kUShort3_Type: + return context.fTypes.fUShort3.get(); + case kUShort4_Type: + return context.fTypes.fUShort4.get(); + case kVoid_Type: + return context.fTypes.fVoid.get(); + case kPoison_Type: + return context.fTypes.fPoison.get(); + default: + SkUNREACHABLE; + } +} + +DSLType::DSLType(TypeConstant tc, Position pos) + : fSkSLType(verify_type(ThreadContext::Context(), + get_type_from_type_constant(tc), + /*allowGenericTypes=*/false, + pos)) {} + +DSLType::DSLType(std::string_view name, Position pos) + : fSkSLType(find_type(ThreadContext::Context(), pos, name)) {} + +DSLType::DSLType(std::string_view name, DSLModifiers* modifiers, Position pos) + : fSkSLType(find_type(ThreadContext::Context(), + pos, + name, + modifiers->fPosition, + &modifiers->fModifiers)) {} + +DSLType::DSLType(const SkSL::Type* type, Position pos) + : fSkSLType(verify_type(ThreadContext::Context(), type, /*allowGenericTypes=*/true, pos)) {} + +DSLType DSLType::Invalid() { + return DSLType(ThreadContext::Context().fTypes.fInvalid.get(), Position()); +} + +bool DSLType::isBoolean() const { + return this->skslType().isBoolean(); +} + +bool DSLType::isNumber() const { + return this->skslType().isNumber(); +} + +bool DSLType::isFloat() const { + return this->skslType().isFloat(); +} + +bool DSLType::isSigned() const { + return this->skslType().isSigned(); +} + +bool DSLType::isUnsigned() const { + return this->skslType().isUnsigned(); +} + +bool DSLType::isInteger() const { + return this->skslType().isInteger(); +} + +bool DSLType::isScalar() const { + return this->skslType().isScalar(); +} + +bool DSLType::isVector() const { + return this->skslType().isVector(); +} + +bool DSLType::isMatrix() const { + return this->skslType().isMatrix(); +} + +bool DSLType::isArray() const { + return this->skslType().isArray(); +} + +bool DSLType::isStruct() const { + return this->skslType().isStruct(); +} + +bool DSLType::isInterfaceBlock() const { + return this->skslType().isInterfaceBlock(); +} + +bool DSLType::isEffectChild() const { + return this->skslType().isEffectChild(); +} + +DSLExpression DSLType::Construct(DSLType type, SkSpan<DSLExpression> argArray) { + SkSL::ExpressionArray skslArgs; + skslArgs.reserve_back(argArray.size()); + + for (DSLExpression& arg : argArray) { + if (!arg.hasValue()) { + return DSLExpression(); + } + skslArgs.push_back(arg.release()); + } + return DSLExpression(SkSL::Constructor::Convert(ThreadContext::Context(), Position(), + type.skslType(), std::move(skslArgs))); +} + +DSLType Array(const DSLType& base, int count, Position pos) { + count = base.skslType().convertArraySize(ThreadContext::Context(), pos, + DSLExpression(count, pos).release()); + if (!count) { + return DSLType(kPoison_Type); + } + return DSLType(ThreadContext::SymbolTable()->addArrayDimension(&base.skslType(), count), pos); +} + +DSLType UnsizedArray(const DSLType& base, Position pos) { + if (!base.skslType().checkIfUsableInArray(ThreadContext::Context(), pos)) { + return DSLType(kPoison_Type); + } + return ThreadContext::SymbolTable()->addArrayDimension(&base.skslType(), + SkSL::Type::kUnsizedArray); +} + +DSLType StructType(std::string_view name, + SkSpan<DSLField> fields, + bool interfaceBlock, + Position pos) { + std::vector<SkSL::Type::Field> skslFields; + skslFields.reserve(fields.size()); + for (const DSLField& field : fields) { + skslFields.emplace_back(field.fPosition, field.fModifiers.fModifiers, field.fName, + &field.fType.skslType()); + } + auto newType = SkSL::Type::MakeStructType(ThreadContext::Context(), pos, name, + std::move(skslFields), interfaceBlock); + return DSLType(ThreadContext::SymbolTable()->add(std::move(newType)), pos); +} + +DSLType Struct(std::string_view name, SkSpan<DSLField> fields, Position pos) { + DSLType result = StructType(name, fields, /*interfaceBlock=*/false, pos); + ThreadContext::ProgramElements().push_back( + std::make_unique<SkSL::StructDefinition>(pos, result.skslType())); + return result; +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/DSLVar.cpp b/gfx/skia/skia/src/sksl/dsl/DSLVar.cpp new file mode 100644 index 0000000000..299aa27a11 --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/DSLVar.cpp @@ -0,0 +1,177 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/sksl/DSLVar.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLSymbol.h" +#include "include/sksl/DSLModifiers.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLFunctionCall.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <utility> + +namespace SkSL { + +namespace dsl { + +/** + * DSLVarBase + */ + +DSLVarBase::DSLVarBase(VariableStorage storage, DSLType type, std::string_view name, + DSLExpression initialValue, Position pos, Position namePos) + : DSLVarBase(storage, DSLModifiers(), std::move(type), name, std::move(initialValue), + pos, namePos) {} + +DSLVarBase::DSLVarBase(VariableStorage storage, const DSLModifiers& modifiers, DSLType type, + std::string_view name, DSLExpression initialValue, Position pos, + Position namePos) + : fModifiers(std::move(modifiers)) + , fType(std::move(type)) + , fNamePosition(namePos) + , fName(name) + , fInitialValue(std::move(initialValue)) + , fPosition(pos) + , fStorage(storage) {} + +void DSLVarBase::swap(DSLVarBase& other) { + SkASSERT(this->storage() == other.storage()); + std::swap(fModifiers, other.fModifiers); + std::swap(fType, other.fType); + std::swap(fDeclaration, other.fDeclaration); + std::swap(fVar, other.fVar); + std::swap(fNamePosition, other.fNamePosition); + std::swap(fName, other.fName); + std::swap(fInitialValue.fExpression, other.fInitialValue.fExpression); + std::swap(fInitialized, other.fInitialized); + std::swap(fPosition, other.fPosition); +} + +DSLExpression DSLVarBase::operator[](DSLExpression&& index) { + return DSLExpression(*this)[std::move(index)]; +} + +DSLExpression DSLVarBase::assignExpression(DSLExpression expr) { + return DSLExpression(BinaryExpression::Convert(ThreadContext::Context(), Position(), + DSLExpression(*this, Position()).release(), SkSL::Operator::Kind::EQ, + expr.release())); +} + +/** + * DSLVar + */ + +DSLVar::DSLVar() : DSLVarBase(SkSL::VariableStorage::kLocal) {} + +DSLVar::DSLVar(DSLType type, std::string_view name, DSLExpression initialValue, + Position pos, Position namePos) + : INHERITED(SkSL::VariableStorage::kLocal, type, name, std::move(initialValue), + pos, namePos) {} + +DSLVar::DSLVar(const DSLModifiers& modifiers, DSLType type, std::string_view name, + DSLExpression initialValue, Position pos, Position namePos) + : INHERITED(SkSL::VariableStorage::kLocal, modifiers, type, name, std::move(initialValue), + pos, namePos) {} + +void DSLVar::swap(DSLVar& other) { + INHERITED::swap(other); +} + +/** + * DSLGlobalVar + */ + +DSLGlobalVar::DSLGlobalVar() : DSLVarBase(SkSL::VariableStorage::kGlobal) {} + +DSLGlobalVar::DSLGlobalVar(DSLType type, std::string_view name, DSLExpression initialValue, + Position pos, Position namePos) + : INHERITED(SkSL::VariableStorage::kGlobal, type, name, std::move(initialValue), + pos, namePos) {} + +DSLGlobalVar::DSLGlobalVar(const DSLModifiers& modifiers, DSLType type, std::string_view name, + DSLExpression initialValue, Position pos, Position namePos) + : INHERITED(SkSL::VariableStorage::kGlobal, modifiers, type, name, std::move(initialValue), + pos, namePos) {} + +DSLGlobalVar::DSLGlobalVar(const char* name) + : INHERITED(SkSL::VariableStorage::kGlobal, kVoid_Type, name, DSLExpression(), + Position(), Position()) { + fName = name; + SkSL::SymbolTable* symbolTable = ThreadContext::SymbolTable().get(); + SkSL::Symbol* result = symbolTable->findMutable(fName); + SkASSERTF(result, "could not find '%.*s' in symbol table", (int)fName.length(), fName.data()); + fVar = &result->as<SkSL::Variable>(); + fInitialized = true; +} + +void DSLGlobalVar::swap(DSLGlobalVar& other) { + INHERITED::swap(other); +} + +std::unique_ptr<SkSL::Expression> DSLGlobalVar::methodCall(std::string_view methodName, + Position pos) { + if (!this->fType.isEffectChild()) { + ThreadContext::ReportError("type does not support method calls", pos); + return nullptr; + } + return FieldAccess::Convert(ThreadContext::Context(), pos, *ThreadContext::SymbolTable(), + DSLExpression(*this, pos).release(), methodName); +} + +DSLExpression DSLGlobalVar::eval(ExpressionArray args, Position pos) { + auto method = this->methodCall("eval", pos); + return DSLExpression( + method ? SkSL::FunctionCall::Convert(ThreadContext::Context(), pos, std::move(method), + std::move(args)) + : nullptr, + pos); +} + +DSLExpression DSLGlobalVar::eval(DSLExpression x, Position pos) { + ExpressionArray converted; + converted.push_back(x.release()); + return this->eval(std::move(converted), pos); +} + +DSLExpression DSLGlobalVar::eval(DSLExpression x, DSLExpression y, Position pos) { + ExpressionArray converted; + converted.push_back(x.release()); + converted.push_back(y.release()); + return this->eval(std::move(converted), pos); +} + +/** + * DSLParameter + */ + +DSLParameter::DSLParameter() : DSLVarBase(SkSL::VariableStorage::kParameter) {} + +DSLParameter::DSLParameter(DSLType type, std::string_view name, Position pos, Position namePos) + : INHERITED(SkSL::VariableStorage::kParameter, type, name, DSLExpression(), pos, namePos) {} + +DSLParameter::DSLParameter(const DSLModifiers& modifiers, DSLType type, std::string_view name, + Position pos, Position namePos) + : INHERITED(SkSL::VariableStorage::kParameter, modifiers, type, name, DSLExpression(), + pos, namePos) {} + +void DSLParameter::swap(DSLParameter& other) { + INHERITED::swap(other); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.cpp b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.cpp new file mode 100644 index 0000000000..9885db21f8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/dsl/priv/DSLWriter.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/DSLCore.h" +#include "include/sksl/DSLExpression.h" +#include "include/sksl/DSLModifiers.h" +#include "include/sksl/DSLStatement.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/DSLVar.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <utility> +#include <vector> + +namespace SkSL { + +namespace dsl { + +SkSL::Variable* DSLWriter::Var(DSLVarBase& var) { + // fInitialized is true if we have attempted to create a var, whether or not we actually + // succeeded. If it's true, we don't want to try again, to avoid reporting the same error + // multiple times. + if (!var.fInitialized) { + // We haven't even attempted to create a var yet, so fVar ought to be null + SkASSERT(!var.fVar); + var.fInitialized = true; + if (var.storage() != SkSL::VariableStorage::kParameter) { + const SkSL::Type* baseType = &var.fType.skslType(); + if (baseType->isArray()) { + baseType = &baseType->componentType(); + } + } + std::unique_ptr<SkSL::Variable> skslvar = SkSL::Variable::Convert(ThreadContext::Context(), + var.fPosition, + var.fModifiers.fPosition, + var.fModifiers.fModifiers, + &var.fType.skslType(), + var.fNamePosition, + var.fName, + /*isArray=*/false, + /*arraySize=*/nullptr, + var.storage()); + SkSL::Variable* varPtr = skslvar.get(); + if (var.storage() != SkSL::VariableStorage::kParameter) { + var.fDeclaration = VarDeclaration::Convert(ThreadContext::Context(), + std::move(skslvar), + var.fInitialValue.releaseIfPossible(), + /*addToSymbolTable=*/false); + if (var.fDeclaration) { + var.fVar = varPtr; + var.fInitialized = true; + } + } + } + return var.fVar; +} + +std::unique_ptr<SkSL::Variable> DSLWriter::CreateParameterVar(DSLParameter& var) { + // This should only be called on undeclared parameter variables, but we allow the creation to go + // ahead regardless so we don't have to worry about null pointers potentially sneaking in and + // breaking things. DSLFunction is responsible for reporting errors for invalid parameters. + return SkSL::Variable::Convert(ThreadContext::Context(), + var.fPosition, + var.fModifiers.fPosition, + var.fModifiers.fModifiers, + &var.fType.skslType(), + var.fNamePosition, + var.fName, + /*isArray=*/false, + /*arraySize=*/nullptr, + var.storage()); +} + +std::unique_ptr<SkSL::Statement> DSLWriter::Declaration(DSLVarBase& var) { + Var(var); + if (!var.fDeclaration) { + // We should have already reported an error before ending up here, just clean up the + // initial value so it doesn't assert and return a nop. + var.fInitialValue.releaseIfPossible(); + return SkSL::Nop::Make(); + } + return std::move(var.fDeclaration); +} + +void DSLWriter::AddVarDeclaration(DSLStatement& existing, DSLVar& additional) { + if (existing.fStatement->is<Block>()) { + SkSL::Block& block = existing.fStatement->as<Block>(); + SkASSERT(!block.isScope()); + block.children().push_back(Declare(additional).release()); + } else if (existing.fStatement->is<VarDeclaration>()) { + Position pos = existing.fStatement->fPosition; + StatementArray stmts; + stmts.reserve_back(2); + stmts.push_back(std::move(existing.fStatement)); + stmts.push_back(Declare(additional).release()); + existing.fStatement = SkSL::Block::Make(pos, std::move(stmts), + Block::Kind::kCompoundStatement); + } else if (existing.fStatement->isEmpty()) { + // If the variable declaration generated an error, we can end up with a Nop statement here. + existing.fStatement = Declare(additional).release(); + } +} + +void DSLWriter::Reset() { + SymbolTable::Pop(&ThreadContext::SymbolTable()); + SymbolTable::Push(&ThreadContext::SymbolTable()); + ThreadContext::ProgramElements().clear(); + ThreadContext::GetModifiersPool()->clear(); +} + +} // namespace dsl + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.h b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.h new file mode 100644 index 0000000000..798c642eab --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/priv/DSLWriter.h @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DSLWRITER +#define SKSL_DSLWRITER + +#include "include/core/SkTypes.h" + +#include <memory> + +namespace SkSL { + +class Variable; +class Statement; + +namespace dsl { + +class DSLParameter; +class DSLStatement; +class DSLVarBase; +class DSLVar; + +/** + * Various utility methods needed by DSL code. + */ +class DSLWriter { +public: + /** + * Returns the SkSL variable corresponding to a DSL var. + */ + static SkSL::Variable* Var(DSLVarBase& var); + + /** + * Creates an SkSL variable corresponding to a DSLParameter. + */ + static std::unique_ptr<SkSL::Variable> CreateParameterVar(DSLParameter& var); + + /** + * Returns the SkSL declaration corresponding to a DSLVar. + */ + static std::unique_ptr<SkSL::Statement> Declaration(DSLVarBase& var); + + /** + * Adds a new declaration into an existing declaration statement. This either turns the original + * declaration into an unscoped block or, if it already was, appends a new statement to the end + * of it. + */ + static void AddVarDeclaration(DSLStatement& existing, DSLVar& additional); + + /** + * Clears any elements or symbols which have been output. + */ + static void Reset(); +}; + +} // namespace dsl + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/dsl/priv/DSL_priv.h b/gfx/skia/skia/src/sksl/dsl/priv/DSL_priv.h new file mode 100644 index 0000000000..4967291e7e --- /dev/null +++ b/gfx/skia/skia/src/sksl/dsl/priv/DSL_priv.h @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DSL_PRIV +#define SKSL_DSL_PRIV + +#include "include/private/SkSLProgramKind.h" + +namespace SkSL { + +class Compiler; +struct ProgramSettings; + +namespace dsl { + +/** + * Initializes the DSL for compiling modules (SkSL include files). + */ +void StartModule(SkSL::Compiler* compiler, + SkSL::ProgramKind kind, + const SkSL::ProgramSettings& settings, + const SkSL::Module* parent); + +} // namespace dsl + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/generated/sksl_compute.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_compute.minified.sksl new file mode 100644 index 0000000000..f0322f9ae2 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_compute.minified.sksl @@ -0,0 +1,7 @@ +static constexpr char SKSL_MINIFIED_sksl_compute[] = +"layout(builtin=24)in uint3 sk_NumWorkgroups;layout(builtin=26)in uint3 sk_WorkgroupID" +";layout(builtin=27)in uint3 sk_LocalInvocationID;layout(builtin=28)in uint3" +" sk_GlobalInvocationID;layout(builtin=29)in uint sk_LocalInvocationIndex;$pure" +" half4 read($readableTexture2D,uint2);void write($writableTexture2D,uint2,half4" +");$pure uint width($genTexture2D);$pure uint height($genTexture2D);void workgroupBarrier" +"();void storageBarrier();"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_compute.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_compute.unoptimized.sksl new file mode 100644 index 0000000000..7f7d211176 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_compute.unoptimized.sksl @@ -0,0 +1,7 @@ +static constexpr char SKSL_MINIFIED_sksl_compute[] = +"layout(builtin=24)in uint3 sk_NumWorkgroups;layout(builtin=26)in uint3 sk_WorkgroupID" +";layout(builtin=27)in uint3 sk_LocalInvocationID;layout(builtin=28)in uint3" +" sk_GlobalInvocationID;layout(builtin=29)in uint sk_LocalInvocationIndex;$pure" +" half4 read($readableTexture2D t,uint2 pos);void write($writableTexture2D t" +",uint2 pos,half4 color);$pure uint width($genTexture2D t);$pure uint height" +"($genTexture2D t);void workgroupBarrier();void storageBarrier();"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_frag.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_frag.minified.sksl new file mode 100644 index 0000000000..f0d4a792d0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_frag.minified.sksl @@ -0,0 +1,5 @@ +static constexpr char SKSL_MINIFIED_sksl_frag[] = +"layout(builtin=15)in float4 sk_FragCoord;layout(builtin=17)in bool sk_Clockwise" +";layout(location=0,index=0,builtin=10001)out half4 sk_FragColor;layout(builtin" +"=10008)half4 sk_LastFragColor;layout(builtin=10012)out half4 sk_SecondaryFragColor" +";"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_frag.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_frag.unoptimized.sksl new file mode 100644 index 0000000000..f0d4a792d0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_frag.unoptimized.sksl @@ -0,0 +1,5 @@ +static constexpr char SKSL_MINIFIED_sksl_frag[] = +"layout(builtin=15)in float4 sk_FragCoord;layout(builtin=17)in bool sk_Clockwise" +";layout(location=0,index=0,builtin=10001)out half4 sk_FragColor;layout(builtin" +"=10008)half4 sk_LastFragColor;layout(builtin=10012)out half4 sk_SecondaryFragColor" +";"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_gpu.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_gpu.minified.sksl new file mode 100644 index 0000000000..beb7f44c00 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_gpu.minified.sksl @@ -0,0 +1,85 @@ +static constexpr char SKSL_MINIFIED_sksl_gpu[] = +"$pure $genIType mix($genIType,$genIType,$genBType);$pure $genBType mix($genBType" +",$genBType,$genBType);$pure $genType fma($genType,$genType,$genType);$pure $genHType" +" fma($genHType,$genHType,$genHType);$genType frexp($genType,out $genIType);" +"$genHType frexp($genHType,out $genIType);$pure $genType ldexp($genType,$genIType" +");$pure $genHType ldexp($genHType,$genIType);$pure uint packSnorm2x16(float2" +");$pure uint packUnorm4x8(float4);$pure uint packSnorm4x8(float4);$pure float2" +" unpackSnorm2x16(uint);$pure float4 unpackUnorm4x8(uint);$pure float4 unpackSnorm4x8" +"(uint);$pure uint packHalf2x16(float2);$pure float2 unpackHalf2x16(uint);$pure" +" $genIType bitCount($genIType);$pure $genIType bitCount($genUType);$pure $genIType" +" findLSB($genIType);$pure $genIType findLSB($genUType);$pure $genIType findMSB" +"($genIType);$pure $genIType findMSB($genUType);$pure sampler2D makeSampler2D" +"(texture2D,sampler);$pure half4 sample(sampler2D,float2);$pure half4 sample" +"(sampler2D,float3);$pure half4 sample(sampler2D,float3,float);$pure half4 sample" +"(samplerExternalOES,float2);$pure half4 sample(samplerExternalOES,float2,float" +");$pure half4 sample(sampler2DRect,float2);$pure half4 sample(sampler2DRect" +",float3);$pure half4 sampleLod(sampler2D,float2,float);$pure half4 sampleLod" +"(sampler2D,float3,float);$pure half4 sampleGrad(sampler2D,float2,float2,float2" +");$pure half4 subpassLoad(subpassInput);$pure half4 subpassLoad(subpassInputMS" +",int);$pure uint atomicLoad(atomicUint);void atomicStore(atomicUint,uint);uint" +" atomicAdd(atomicUint,uint);$pure half4 blend_clear(half4 a,half4 b){return" +" half4(0.);}$pure half4 blend_src(half4 a,half4 b){return a;}$pure half4 blend_dst" +"(half4 a,half4 b){return b;}$pure half4 blend_src_over(half4 a,half4 b){return" +" a+(1.-a.w)*b;}$pure half4 blend_dst_over(half4 a,half4 b){return(1.-b.w)*a" +"+b;}$pure half4 blend_src_in(half4 a,half4 b){return a*b.w;}$pure half4 blend_dst_in" +"(half4 a,half4 b){return b*a.w;}$pure half4 blend_src_out(half4 a,half4 b){" +"return(1.-b.w)*a;}$pure half4 blend_dst_out(half4 a,half4 b){return(1.-a.w)" +"*b;}$pure half4 blend_src_atop(half4 a,half4 b){return b.w*a+(1.-a.w)*b;}$pure" +" half4 blend_dst_atop(half4 a,half4 b){return(1.-b.w)*a+a.w*b;}$pure half4 blend_xor" +"(half4 a,half4 b){return(1.-b.w)*a+(1.-a.w)*b;}$pure half4 blend_plus(half4" +" a,half4 b){return min(a+b,1.);}$pure half4 blend_porter_duff(half4 a,half4" +" b,half4 c){half2 d=a.xy+a.zw*(half2(c.w,b.w)+min(a.zw,0.));return min(half4" +"(1.),b*d.x+c*d.y);}$pure half4 blend_modulate(half4 a,half4 b){return a*b;}" +"$pure half4 blend_screen(half4 a,half4 b){return a+(1.-a)*b;}$pure half $b(" +"half2 a,half2 b){return 2.*b.x<=b.y?(2.*a.x)*b.x:a.y*b.y-(2.*(b.y-b.x))*(a." +"y-a.x);}$pure half4 blend_overlay(half4 a,half4 b){half4 c=half4($b(a.xw,b." +"xw),$b(a.yw,b.yw),$b(a.zw,b.zw),a.w+(1.-a.w)*b.w);c.xyz+=b.xyz*(1.-a.w)+a.xyz" +"*(1.-b.w);return c;}$pure half4 blend_overlay(half c,half4 d,half4 e){return" +" blend_overlay(bool(c)?e:d,bool(c)?d:e);}$pure half4 blend_lighten(half4 a," +"half4 b){half4 c=blend_src_over(a,b);c.xyz=max(c.xyz,(1.-b.w)*a.xyz+b.xyz);" +"return c;}$pure half4 blend_darken(half c,half4 d,half4 e){half4 f=blend_src_over" +"(d,e);half3 g=(1.-e.w)*d.xyz+e.xyz;f.xyz=c*min(f.xyz*c,g*c);return f;}$pure" +" half4 blend_darken(half4 a,half4 b){return blend_darken(1.,a,b);}const half" +" $kGuardedDivideEpsilon=half(sk_Caps.mustGuardDivisionEvenAfterExplicitZeroCheck" +"?1e-08:0.);$pure inline half $c(half a,half b){return a/(b+$kGuardedDivideEpsilon" +");}$pure inline half3 $c(half3 a,half b){return a/(b+$kGuardedDivideEpsilon" +");}$pure half $d(half2 a,half2 b){if(b.x==0.){return a.x*(1.-b.y);}else{half" +" c=a.y-a.x;if(c==0.){return(a.y*b.y+a.x*(1.-b.y))+b.x*(1.-a.y);}else{c=min(" +"b.y,$c(b.x*a.y,c));return(c*a.y+a.x*(1.-b.y))+b.x*(1.-a.y);}}}$pure half4 blend_color_dodge" +"(half4 a,half4 b){return half4($d(a.xw,b.xw),$d(a.yw,b.yw),$d(a.zw,b.zw),a." +"w+(1.-a.w)*b.w);}$pure half $e(half2 a,half2 b){if(b.y==b.x){return(a.y*b.y" +"+a.x*(1.-b.y))+b.x*(1.-a.y);}else if(a.x==0.){return b.x*(1.-a.y);}else{half" +" c=max(0.,b.y-$c((b.y-b.x)*a.y,a.x));return(c*a.y+a.x*(1.-b.y))+b.x*(1.-a.y" +");}}$pure half4 blend_color_burn(half4 a,half4 b){return half4($e(a.xw,b.xw" +"),$e(a.yw,b.yw),$e(a.zw,b.zw),a.w+(1.-a.w)*b.w);}$pure half4 blend_hard_light" +"(half4 a,half4 b){return blend_overlay(b,a);}$pure half $f(half2 a,half2 b)" +"{if(2.*a.x<=a.y){return($c((b.x*b.x)*(a.y-2.*a.x),b.y)+(1.-b.y)*a.x)+b.x*((" +"-a.y+2.*a.x)+1.);}else if(4.*b.x<=b.y){half c=b.x*b.x;half e=c*b.x;half f=b" +".y*b.y;half g=f*b.y;return $c(((f*(a.x-b.x*((3.*a.y-6.*a.x)-1.))+((12.*b.y)" +"*c)*(a.y-2.*a.x))-(16.*e)*(a.y-2.*a.x))-g*a.x,f);}else{return((b.x*((a.y-2." +"*a.x)+1.)+a.x)-sqrt(b.y*b.x)*(a.y-2.*a.x))-b.y*a.x;}}$pure half4 blend_soft_light" +"(half4 a,half4 b){return b.w==0.?a:half4($f(a.xw,b.xw),$f(a.yw,b.yw),$f(a.zw" +",b.zw),a.w+(1.-a.w)*b.w);}$pure half4 blend_difference(half4 a,half4 b){return" +" half4((a.xyz+b.xyz)-2.*min(a.xyz*b.w,b.xyz*a.w),a.w+(1.-a.w)*b.w);}$pure half4" +" blend_exclusion(half4 a,half4 b){return half4((b.xyz+a.xyz)-(2.*b.xyz)*a.xyz" +",a.w+(1.-a.w)*b.w);}$pure half4 blend_multiply(half4 a,half4 b){return half4" +"(((1.-a.w)*b.xyz+(1.-b.w)*a.xyz)+a.xyz*b.xyz,a.w+(1.-a.w)*b.w);}$pure half $g" +"(half3 a){return dot(half3(.3,.59,.11),a);}$pure half3 $h(half3 a,half b,half3" +" c){half d=$g(c);half3 e=(d-$g(a))+a;half f=min(min(e.x,e.y),e.z);half g=max" +"(max(e.x,e.y),e.z);if(f<0.&&d!=f){e=d+(e-d)*$c(d,d-f);}if(g>b&&g!=d){e=d+$c" +"((e-d)*(b-d),g-d);}return e;}$pure half $i(half3 a){return max(max(a.x,a.y)" +",a.z)-min(min(a.x,a.y),a.z);}$pure half3 $j(half3 a,half3 b){half c=min(min" +"(a.x,a.y),a.z);half d=max(max(a.x,a.y),a.z);return d>c?((a-c)*$i(b))/(d-c):" +"half3(0.);}$pure half4 blend_hslc(half2 a,half4 b,half4 c){half d=c.w*b.w;half3" +" e=b.xyz*c.w;half3 f=c.xyz*b.w;half3 g=bool(a.x)?f:e;half3 h=bool(a.x)?e:f;" +"if(bool(a.y)){g=$j(g,h);h=f;}return half4(((($h(g,d,h)+c.xyz)-f)+b.xyz)-e,(" +"b.w+c.w)-d);}$pure half4 blend_hue(half4 a,half4 b){return blend_hslc(half2" +"(0.,1.),a,b);}$pure half4 blend_saturation(half4 a,half4 b){return blend_hslc" +"(half2(1.),a,b);}$pure half4 blend_color(half4 a,half4 b){return blend_hslc" +"(half2(0.),a,b);}$pure half4 blend_luminosity(half4 a,half4 b){return blend_hslc" +"(half2(1.,0.),a,b);}$pure float2 proj(float3 a){return a.xy/a.z;}$pure float" +" cross_length_2d(float2 c,float2 d){return determinant(float2x2(c,d));}$pure" +" half cross_length_2d(half2 c,half2 d){return determinant(half2x2(c,d));}$pure" +" float2 perp(float2 a){return float2(-a.y,a.x);}$pure half2 perp(half2 a){return" +" half2(-a.y,a.x);}$pure float coverage_bias(float a){return 1.-.5*a;}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_gpu.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_gpu.unoptimized.sksl new file mode 100644 index 0000000000..0b09c7d461 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_gpu.unoptimized.sksl @@ -0,0 +1,107 @@ +static constexpr char SKSL_MINIFIED_sksl_gpu[] = +"$pure $genIType mix($genIType x,$genIType y,$genBType a);$pure $genBType mix" +"($genBType x,$genBType y,$genBType a);$pure $genType fma($genType a,$genType" +" b,$genType c);$pure $genHType fma($genHType a,$genHType b,$genHType c);$genType" +" frexp($genType x,out $genIType exp);$genHType frexp($genHType x,out $genIType" +" exp);$pure $genType ldexp($genType x,$genIType exp);$pure $genHType ldexp(" +"$genHType x,$genIType exp);$pure uint packSnorm2x16(float2 v);$pure uint packUnorm4x8" +"(float4 v);$pure uint packSnorm4x8(float4 v);$pure float2 unpackSnorm2x16(uint" +" p);$pure float4 unpackUnorm4x8(uint p);$pure float4 unpackSnorm4x8(uint p)" +";$pure uint packHalf2x16(float2 v);$pure float2 unpackHalf2x16(uint v);$pure" +" $genIType bitCount($genIType value);$pure $genIType bitCount($genUType value" +");$pure $genIType findLSB($genIType value);$pure $genIType findLSB($genUType" +" value);$pure $genIType findMSB($genIType value);$pure $genIType findMSB($genUType" +" value);$pure sampler2D makeSampler2D(texture2D texture,sampler s);$pure half4" +" sample(sampler2D s,float2 P);$pure half4 sample(sampler2D s,float3 P);$pure" +" half4 sample(sampler2D s,float3 P,float bias);$pure half4 sample(samplerExternalOES" +" s,float2 P);$pure half4 sample(samplerExternalOES s,float2 P,float bias);$pure" +" half4 sample(sampler2DRect s,float2 P);$pure half4 sample(sampler2DRect s," +"float3 P);$pure half4 sampleLod(sampler2D s,float2 P,float lod);$pure half4" +" sampleLod(sampler2D s,float3 P,float lod);$pure half4 sampleGrad(sampler2D" +" s,float2,float2 dPdx,float2 dPdy);$pure half4 subpassLoad(subpassInput subpass" +");$pure half4 subpassLoad(subpassInputMS subpass,int sample);$pure uint atomicLoad" +"(atomicUint a);void atomicStore(atomicUint a,uint value);uint atomicAdd(atomicUint" +" a,uint value);$pure half4 blend_clear(half4 src,half4 dst){return half4(0." +");}$pure half4 blend_src(half4 src,half4 dst){return src;}$pure half4 blend_dst" +"(half4 src,half4 dst){return dst;}$pure half4 blend_src_over(half4 src,half4" +" dst){return src+(1.-src.w)*dst;}$pure half4 blend_dst_over(half4 src,half4" +" dst){return(1.-dst.w)*src+dst;}$pure half4 blend_src_in(half4 src,half4 dst" +"){return src*dst.w;}$pure half4 blend_dst_in(half4 src,half4 dst){return dst" +"*src.w;}$pure half4 blend_src_out(half4 src,half4 dst){return(1.-dst.w)*src" +";}$pure half4 blend_dst_out(half4 src,half4 dst){return(1.-src.w)*dst;}$pure" +" half4 blend_src_atop(half4 src,half4 dst){return dst.w*src+(1.-src.w)*dst;" +"}$pure half4 blend_dst_atop(half4 src,half4 dst){return(1.-dst.w)*src+src.w" +"*dst;}$pure half4 blend_xor(half4 src,half4 dst){return(1.-dst.w)*src+(1.-src" +".w)*dst;}$pure half4 blend_plus(half4 src,half4 dst){return min(src+dst,1.)" +";}$pure half4 blend_porter_duff(half4 blendOp,half4 src,half4 dst){half2 coeff" +"=blendOp.xy+blendOp.zw*(half2(dst.w,src.w)+min(blendOp.zw,0.));return min(half4" +"(1.),src*coeff.x+dst*coeff.y);}$pure half4 blend_modulate(half4 src,half4 dst" +"){return src*dst;}$pure half4 blend_screen(half4 src,half4 dst){return src+" +"(1.-src)*dst;}$pure half $blend_overlay_component(half2 s,half2 d){return 2." +"*d.x<=d.y?(2.*s.x)*d.x:s.y*d.y-(2.*(d.y-d.x))*(s.y-s.x);}$pure half4 blend_overlay" +"(half4 src,half4 dst){half4 result=half4($blend_overlay_component(src.xw,dst" +".xw),$blend_overlay_component(src.yw,dst.yw),$blend_overlay_component(src.zw" +",dst.zw),src.w+(1.-src.w)*dst.w);result.xyz+=dst.xyz*(1.-src.w)+src.xyz*(1." +"-dst.w);return result;}$pure half4 blend_overlay(half flip,half4 a,half4 b)" +"{return blend_overlay(bool(flip)?b:a,bool(flip)?a:b);}$pure half4 blend_lighten" +"(half4 src,half4 dst){half4 result=blend_src_over(src,dst);result.xyz=max(result" +".xyz,(1.-dst.w)*src.xyz+dst.xyz);return result;}$pure half4 blend_darken(half" +" mode,half4 src,half4 dst){half4 a=blend_src_over(src,dst);half3 b=(1.-dst." +"w)*src.xyz+dst.xyz;a.xyz=mode*min(a.xyz*mode,b*mode);return a;}$pure half4 blend_darken" +"(half4 src,half4 dst){return blend_darken(1.,src,dst);}const half $kGuardedDivideEpsilon" +"=half(sk_Caps.mustGuardDivisionEvenAfterExplicitZeroCheck?1e-08:0.);$pure inline" +" half $guarded_divide(half n,half d){return n/(d+$kGuardedDivideEpsilon);}$pure" +" inline half3 $guarded_divide(half3 n,half d){return n/(d+$kGuardedDivideEpsilon" +");}$pure half $color_dodge_component(half2 s,half2 d){if(d.x==0.){return s." +"x*(1.-d.y);}else{half delta=s.y-s.x;if(delta==0.){return(s.y*d.y+s.x*(1.-d." +"y))+d.x*(1.-s.y);}else{delta=min(d.y,$guarded_divide(d.x*s.y,delta));return" +"(delta*s.y+s.x*(1.-d.y))+d.x*(1.-s.y);}}}$pure half4 blend_color_dodge(half4" +" src,half4 dst){return half4($color_dodge_component(src.xw,dst.xw),$color_dodge_component" +"(src.yw,dst.yw),$color_dodge_component(src.zw,dst.zw),src.w+(1.-src.w)*dst." +"w);}$pure half $color_burn_component(half2 s,half2 d){if(d.y==d.x){return(s" +".y*d.y+s.x*(1.-d.y))+d.x*(1.-s.y);}else if(s.x==0.){return d.x*(1.-s.y);}else" +"{half delta=max(0.,d.y-$guarded_divide((d.y-d.x)*s.y,s.x));return(delta*s.y" +"+s.x*(1.-d.y))+d.x*(1.-s.y);}}$pure half4 blend_color_burn(half4 src,half4 dst" +"){return half4($color_burn_component(src.xw,dst.xw),$color_burn_component(src" +".yw,dst.yw),$color_burn_component(src.zw,dst.zw),src.w+(1.-src.w)*dst.w);}$pure" +" half4 blend_hard_light(half4 src,half4 dst){return blend_overlay(dst,src);" +"}$pure half $soft_light_component(half2 s,half2 d){if(2.*s.x<=s.y){return($guarded_divide" +"((d.x*d.x)*(s.y-2.*s.x),d.y)+(1.-d.y)*s.x)+d.x*((-s.y+2.*s.x)+1.);}else if(" +"4.*d.x<=d.y){half DSqd=d.x*d.x;half DCub=DSqd*d.x;half DaSqd=d.y*d.y;half DaCub" +"=DaSqd*d.y;return $guarded_divide(((DaSqd*(s.x-d.x*((3.*s.y-6.*s.x)-1.))+((" +"12.*d.y)*DSqd)*(s.y-2.*s.x))-(16.*DCub)*(s.y-2.*s.x))-DaCub*s.x,DaSqd);}else" +"{return((d.x*((s.y-2.*s.x)+1.)+s.x)-sqrt(d.y*d.x)*(s.y-2.*s.x))-d.y*s.x;}}$pure" +" half4 blend_soft_light(half4 src,half4 dst){return dst.w==0.?src:half4($soft_light_component" +"(src.xw,dst.xw),$soft_light_component(src.yw,dst.yw),$soft_light_component(" +"src.zw,dst.zw),src.w+(1.-src.w)*dst.w);}$pure half4 blend_difference(half4 src" +",half4 dst){return half4((src.xyz+dst.xyz)-2.*min(src.xyz*dst.w,dst.xyz*src" +".w),src.w+(1.-src.w)*dst.w);}$pure half4 blend_exclusion(half4 src,half4 dst" +"){return half4((dst.xyz+src.xyz)-(2.*dst.xyz)*src.xyz,src.w+(1.-src.w)*dst." +"w);}$pure half4 blend_multiply(half4 src,half4 dst){return half4(((1.-src.w" +")*dst.xyz+(1.-dst.w)*src.xyz)+src.xyz*dst.xyz,src.w+(1.-src.w)*dst.w);}$pure" +" half $blend_color_luminance(half3 color){return dot(half3(.3,.59,.11),color" +");}$pure half3 $blend_set_color_luminance(half3 hueSatColor,half alpha,half3" +" lumColor){half lum=$blend_color_luminance(lumColor);half3 result=(lum-$blend_color_luminance" +"(hueSatColor))+hueSatColor;half minComp=min(min(result.x,result.y),result.z" +");half maxComp=max(max(result.x,result.y),result.z);if(minComp<0.&&lum!=minComp" +"){result=lum+(result-lum)*$guarded_divide(lum,lum-minComp);}if(maxComp>alpha" +"&&maxComp!=lum){result=lum+$guarded_divide((result-lum)*(alpha-lum),maxComp" +"-lum);}return result;}$pure half $blend_color_saturation(half3 color){return" +" max(max(color.x,color.y),color.z)-min(min(color.x,color.y),color.z);}$pure" +" half3 $blend_set_color_saturation(half3 color,half3 satColor){half mn=min(" +"min(color.x,color.y),color.z);half mx=max(max(color.x,color.y),color.z);return" +" mx>mn?((color-mn)*$blend_color_saturation(satColor))/(mx-mn):half3(0.);}$pure" +" half4 blend_hslc(half2 flipSat,half4 src,half4 dst){half alpha=dst.w*src.w" +";half3 sda=src.xyz*dst.w;half3 dsa=dst.xyz*src.w;half3 l=bool(flipSat.x)?dsa" +":sda;half3 r=bool(flipSat.x)?sda:dsa;if(bool(flipSat.y)){l=$blend_set_color_saturation" +"(l,r);r=dsa;}return half4(((($blend_set_color_luminance(l,alpha,r)+dst.xyz)" +"-dsa)+src.xyz)-sda,(src.w+dst.w)-alpha);}$pure half4 blend_hue(half4 src,half4" +" dst){return blend_hslc(half2(0.,1.),src,dst);}$pure half4 blend_saturation" +"(half4 src,half4 dst){return blend_hslc(half2(1.),src,dst);}$pure half4 blend_color" +"(half4 src,half4 dst){return blend_hslc(half2(0.),src,dst);}$pure half4 blend_luminosity" +"(half4 src,half4 dst){return blend_hslc(half2(1.,0.),src,dst);}$pure float2" +" proj(float3 p){return p.xy/p.z;}$pure float cross_length_2d(float2 a,float2" +" b){return determinant(float2x2(a,b));}$pure half cross_length_2d(half2 a,half2" +" b){return determinant(half2x2(a,b));}$pure float2 perp(float2 v){return float2" +"(-v.y,v.x);}$pure half2 perp(half2 v){return half2(-v.y,v.x);}$pure float coverage_bias" +"(float scale){return 1.-.5*scale;}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl new file mode 100644 index 0000000000..93d4e47b8a --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl @@ -0,0 +1,3119 @@ +static constexpr uint8_t SKSL_INCLUDE_sksl_graphite_frag[] = {14,0,27,6, +8,115,107,95,101,114,114,111,114, +5,104,97,108,102,52, +5,99,111,108,111,114, +14,115,107,95,112,97,115,115,116,104,114,111,117,103,104, +10,99,111,108,111,114,80,97,114,97,109, +6,102,108,111,97,116,52, +15,115,107,95,115,111,108,105,100,95,115,104,97,100,101,114, +2,116,109, +3,105,110,116, +1,102, +5,102,108,111,97,116, +3,108,111,119, +4,104,105,103,104, +5,36,116,105,108,101, +6,99,111,111,114,100,115, +6,102,108,111,97,116,50, +6,115,117,98,115,101,116, +3,116,109,88, +3,116,109,89, +8,105,109,103,87,105,100,116,104, +9,105,109,103,72,101,105,103,104,116, +1,115, +9,115,97,109,112,108,101,114,50,68, +15,115,107,95,105,109,97,103,101,95,115,104,97,100,101,114, +8,116,105,108,101,77,111,100,101, +1,116, +10,36,116,105,108,101,95,103,114,97,100, +11,99,111,108,111,114,115,80,97,114,97,109, +12,111,102,102,115,101,116,115,80,97,114,97,109, +16,36,99,111,108,111,114,105,122,101,95,103,114,97,100,95,52, +16,36,99,111,108,111,114,105,122,101,95,103,114,97,100,95,56, +11,112,111,105,110,116,48,80,97,114,97,109, +11,112,111,105,110,116,49,80,97,114,97,109, +3,112,111,115, +19,36,108,105,110,101,97,114,95,103,114,97,100,95,108,97,121,111,117,116, +11,99,101,110,116,101,114,80,97,114,97,109, +11,114,97,100,105,117,115,80,97,114,97,109, +19,36,114,97,100,105,97,108,95,103,114,97,100,95,108,97,121,111,117,116, +9,98,105,97,115,80,97,114,97,109, +10,115,99,97,108,101,80,97,114,97,109, +18,36,115,119,101,101,112,95,103,114,97,100,95,108,97,121,111,117,116, +2,112,48, +2,112,49, +14,36,109,97,112,95,116,111,95,117,110,105,116,95,120, +8,102,108,111,97,116,51,120,51, +12,114,97,100,105,117,115,48,80,97,114,97,109, +12,114,97,100,105,117,115,49,80,97,114,97,109, +20,36,99,111,110,105,99,97,108,95,103,114,97,100,95,108,97,121,111,117,116, +23,115,107,95,108,105,110,101,97,114,95,103,114,97,100,95,52,95,115,104,97,100,101,114, +23,115,107,95,108,105,110,101,97,114,95,103,114,97,100,95,56,95,115,104,97,100,101,114, +23,115,107,95,114,97,100,105,97,108,95,103,114,97,100,95,52,95,115,104,97,100,101,114, +23,115,107,95,114,97,100,105,97,108,95,103,114,97,100,95,56,95,115,104,97,100,101,114, +22,115,107,95,115,119,101,101,112,95,103,114,97,100,95,52,95,115,104,97,100,101,114, +22,115,107,95,115,119,101,101,112,95,103,114,97,100,95,56,95,115,104,97,100,101,114, +24,115,107,95,99,111,110,105,99,97,108,95,103,114,97,100,95,52,95,115,104,97,100,101,114, +24,115,107,95,99,111,110,105,99,97,108,95,103,114,97,100,95,56,95,115,104,97,100,101,114, +7,99,111,108,111,114,73,110, +1,109, +8,102,108,111,97,116,52,120,52, +1,118, +6,105,110,72,83,76,65, +21,115,107,95,109,97,116,114,105,120,95,99,111,108,111,114,102,105,108,116,101,114, +9,98,108,101,110,100,77,111,100,101, +3,115,114,99, +3,100,115,116, +8,115,107,95,98,108,101,110,100, +15,115,107,95,98,108,101,110,100,95,115,104,97,100,101,114, +8,100,115,116,67,111,108,111,114, +8,115,114,99,67,111,108,111,114, +20,115,107,95,98,108,101,110,100,95,99,111,108,111,114,102,105,108,116,101,114, +7,105,110,67,111,108,111,114, +20,115,107,95,116,97,98,108,101,95,99,111,108,111,114,102,105,108,116,101,114, +23,115,107,95,103,97,117,115,115,105,97,110,95,99,111,108,111,114,102,105,108,116,101,114, +4,104,97,108,102, +6,107,67,108,97,109,112, +7,107,82,101,112,101,97,116, +13,107,77,105,114,114,111,114,82,101,112,101,97,116, +5,99,108,97,109,112, +6,108,101,110,103,116,104, +3,109,111,100, +7,108,101,110,103,116,104,50, +3,116,109,112, +3,109,105,120, +4,115,116,101,112, +10,105,110,115,101,116,67,108,97,109,112, +10,99,108,97,109,112,101,100,80,111,115, +5,102,108,111,111,114, +4,99,101,105,108, +6,115,97,109,112,108,101, +5,102,114,97,99,116, +3,116,95,49, +26,109,117,115,116,68,111,79,112,66,101,116,119,101,101,110,70,108,111,111,114,65,110,100,65,98,115, +3,97,98,115, +11,36,105,110,116,76,105,116,101,114,97,108, +5,100,101,108,116,97, +3,100,111,116, +8,100,105,115,116,97,110,99,101, +5,97,110,103,108,101, +28,97,116,97,110,50,73,109,112,108,101,109,101,110,116,101,100,65,115,65,116,97,110,89,79,118,101,114,88, +4,97,116,97,110, +7,105,110,118,101,114,115,101, +19,83,75,95,83,99,97,108,97,114,78,101,97,114,108,121,90,101,114,111, +7,100,67,101,110,116,101,114, +7,100,82,97,100,105,117,115, +6,114,97,100,105,97,108, +4,98,111,111,108, +5,115,116,114,105,112, +5,115,99,97,108,101, +9,115,99,97,108,101,83,105,103,110, +4,98,105,97,115, +2,112,116, +4,115,105,103,110, +9,116,114,97,110,115,102,111,114,109, +1,114, +3,114,95,50, +6,102,108,111,97,116,51, +4,115,113,114,116, +9,105,115,83,119,97,112,112,101,100, +2,67,102, +6,115,99,97,108,101,88, +6,115,99,97,108,101,89, +2,114,49, +15,105,115,70,111,99,97,108,79,110,67,105,114,99,108,101, +5,105,110,118,82,49, +11,100,82,97,100,105,117,115,83,105,103,110, +13,105,115,87,101,108,108,66,101,104,97,118,101,100, +3,120,95,116, +5,116,109,112,80,116, +4,116,101,109,112, +8,99,111,108,111,114,79,117,116, +11,36,114,103,98,95,116,111,95,104,115,108, +8,117,110,112,114,101,109,117,108, +11,36,104,115,108,95,116,111,95,114,103,98, +8,115,97,116,117,114,97,116,101, +11,98,108,101,110,100,95,99,108,101,97,114, +9,98,108,101,110,100,95,115,114,99, +9,98,108,101,110,100,95,100,115,116, +17,98,108,101,110,100,95,112,111,114,116,101,114,95,100,117,102,102, +14,98,108,101,110,100,95,109,111,100,117,108,97,116,101, +12,98,108,101,110,100,95,115,99,114,101,101,110, +13,98,108,101,110,100,95,111,118,101,114,108,97,121, +12,98,108,101,110,100,95,100,97,114,107,101,110, +17,98,108,101,110,100,95,99,111,108,111,114,95,100,111,100,103,101, +16,98,108,101,110,100,95,99,111,108,111,114,95,98,117,114,110, +16,98,108,101,110,100,95,115,111,102,116,95,108,105,103,104,116, +16,98,108,101,110,100,95,100,105,102,102,101,114,101,110,99,101, +15,98,108,101,110,100,95,101,120,99,108,117,115,105,111,110, +14,98,108,101,110,100,95,109,117,108,116,105,112,108,121, +10,98,108,101,110,100,95,104,115,108,99, +5,104,97,108,102,50, +6,102,97,99,116,111,114, +3,101,120,112, +52,1,139,0, +28,1,0, +39, +16,0,64,0,0,2,0,0, +51,255,255,11,0, +54,2,0, +17,17,0, +51,255,255,11,0,3, +28,3,0, +39, +16,0,64,0,0,23,0,1, +51,2,0, +51,255,255,11,0, +54,4,0, +17,38,0, +51,255,255,49,0,3, +28,5,0, +39, +16,0,64,0,0,56,0,1, +51,4,0, +51,255,255,11,0, +54,6,0, +17,72,0, +51,255,255,75,0,3, +54,7,0, +17,79,0, +51,255,255,81,0,3, +54,8,0, +17,87,0, +51,255,255,81,0,3, +54,9,0, +17,91,0, +51,255,255,81,0,3, +28,10,0, +39, +16,0,64,0,0,96,0,4, +51,6,0, +51,7,0, +51,8,0, +51,9,0, +51,255,255,81,0, +54,11,0, +17,102,0, +51,255,255,109,0,3, +54,12,0, +17,116,0, +51,255,255,49,0,3, +54,13,0, +17,123,0, +51,255,255,75,0,3, +54,14,0, +17,127,0, +51,255,255,75,0,3, +54,15,0, +17,131,0, +51,255,255,75,0,3, +54,16,0, +17,140,0, +51,255,255,75,0,3, +54,17,0, +17,150,0, +51,255,255,152,0,3, +28,18,0, +39, +16,0,64,0,0,162,0,7, +51,11,0, +51,12,0, +51,13,0, +51,14,0, +51,15,0, +51,16,0, +51,17,0, +51,255,255,11,0, +54,19,0, +17,178,0, +51,255,255,75,0,3, +54,20,0, +17,187,0, +51,255,255,109,0,3, +28,21,0, +39, +16,0,64,0,0,189,0,2, +51,19,0, +51,20,0, +51,255,255,109,0, +0,22,0, +51,255,255,49,0,4, +0,23,0, +51,255,255,81,0,4, +54,24,0, +17,200,0, +51,22,0,3, +54,25,0, +17,212,0, +51,23,0,3, +54,26,0, +17,187,0, +51,255,255,109,0,3, +28,27,0, +39, +16,0,64,0,0,225,0,3, +51,24,0, +51,25,0, +51,26,0, +51,255,255,11,0, +0,28,0, +51,255,255,49,0,8, +0,29,0, +51,255,255,81,0,8, +54,30,0, +17,200,0, +51,28,0,3, +54,31,0, +17,212,0, +51,29,0,3, +54,32,0, +17,187,0, +51,255,255,109,0,3, +28,33,0, +39, +16,0,64,0,0,242,0,3, +51,30,0, +51,31,0, +51,32,0, +51,255,255,11,0, +54,34,0, +17,3,1, +51,255,255,109,0,3, +54,35,0, +17,15,1, +51,255,255,109,0,3, +54,36,0, +17,27,1, +51,255,255,109,0,3, +28,37,0, +39, +16,0,64,0,0,31,1,3, +51,34,0, +51,35,0, +51,36,0, +51,255,255,109,0, +54,38,0, +17,51,1, +51,255,255,109,0,3, +54,39,0, +17,63,1, +51,255,255,81,0,3, +54,40,0, +17,27,1, +51,255,255,109,0,3, +28,41,0, +39, +16,0,64,0,0,75,1,3, +51,38,0, +51,39,0, +51,40,0, +51,255,255,109,0, +54,42,0, +17,51,1, +51,255,255,109,0,3, +54,43,0, +17,95,1, +51,255,255,81,0,3, +54,44,0, +17,105,1, +51,255,255,81,0,3, +54,45,0, +17,27,1, +51,255,255,109,0,3, +28,46,0, +39, +16,0,64,0,0,116,1,4, +51,42,0, +51,43,0, +51,44,0, +51,45,0, +51,255,255,109,0, +54,47,0, +17,135,1, +51,255,255,109,0,3, +54,48,0, +17,138,1, +51,255,255,109,0,3, +28,49,0, +39, +16,0,64,0,0,141,1,2, +51,47,0, +51,48,0, +51,255,255,156,1, +54,50,0, +17,3,1, +51,255,255,109,0,3, +54,51,0, +17,15,1, +51,255,255,109,0,3, +54,52,0, +17,165,1, +51,255,255,81,0,3, +54,53,0, +17,178,1, +51,255,255,81,0,3, +54,54,0, +17,27,1, +51,255,255,109,0,3, +28,55,0, +39, +16,0,64,0,0,191,1,5, +51,50,0, +51,51,0, +51,52,0, +51,53,0, +51,54,0, +51,255,255,109,0, +54,56,0, +17,102,0, +51,255,255,109,0,3, +54,57,0, +17,200,0, +51,22,0,3, +54,58,0, +17,212,0, +51,23,0,3, +54,59,0, +17,3,1, +51,255,255,109,0,3, +54,60,0, +17,15,1, +51,255,255,109,0,3, +54,61,0, +17,178,0, +51,255,255,75,0,3, +28,62,0, +39, +16,0,64,0,0,212,1,6, +51,56,0, +51,57,0, +51,58,0, +51,59,0, +51,60,0, +51,61,0, +51,255,255,11,0, +54,63,0, +17,102,0, +51,255,255,109,0,3, +54,64,0, +17,200,0, +51,28,0,3, +54,65,0, +17,212,0, +51,29,0,3, +54,66,0, +17,3,1, +51,255,255,109,0,3, +54,67,0, +17,15,1, +51,255,255,109,0,3, +54,68,0, +17,178,0, +51,255,255,75,0,3, +28,69,0, +39, +16,0,64,0,0,236,1,6, +51,63,0, +51,64,0, +51,65,0, +51,66,0, +51,67,0, +51,68,0, +51,255,255,11,0, +54,70,0, +17,102,0, +51,255,255,109,0,3, +54,71,0, +17,200,0, +51,22,0,3, +54,72,0, +17,212,0, +51,23,0,3, +54,73,0, +17,51,1, +51,255,255,109,0,3, +54,74,0, +17,63,1, +51,255,255,81,0,3, +54,75,0, +17,178,0, +51,255,255,75,0,3, +28,76,0, +39, +16,0,64,0,0,4,2,6, +51,70,0, +51,71,0, +51,72,0, +51,73,0, +51,74,0, +51,75,0, +51,255,255,11,0, +54,77,0, +17,102,0, +51,255,255,109,0,3, +54,78,0, +17,200,0, +51,28,0,3, +54,79,0, +17,212,0, +51,29,0,3, +54,80,0, +17,51,1, +51,255,255,109,0,3, +54,81,0, +17,63,1, +51,255,255,81,0,3, +54,82,0, +17,178,0, +51,255,255,75,0,3, +28,83,0, +39, +16,0,64,0,0,28,2,6, +51,77,0, +51,78,0, +51,79,0, +51,80,0, +51,81,0, +51,82,0, +51,255,255,11,0, +54,84,0, +17,102,0, +51,255,255,109,0,3, +54,85,0, +17,200,0, +51,22,0,3, +54,86,0, +17,212,0, +51,23,0,3, +54,87,0, +17,51,1, +51,255,255,109,0,3, +54,88,0, +17,95,1, +51,255,255,81,0,3, +54,89,0, +17,105,1, +51,255,255,81,0,3, +54,90,0, +17,178,0, +51,255,255,75,0,3, +28,91,0, +39, +16,0,64,0,0,52,2,7, +51,84,0, +51,85,0, +51,86,0, +51,87,0, +51,88,0, +51,89,0, +51,90,0, +51,255,255,11,0, +54,92,0, +17,102,0, +51,255,255,109,0,3, +54,93,0, +17,200,0, +51,28,0,3, +54,94,0, +17,212,0, +51,29,0,3, +54,95,0, +17,51,1, +51,255,255,109,0,3, +54,96,0, +17,95,1, +51,255,255,81,0,3, +54,97,0, +17,105,1, +51,255,255,81,0,3, +54,98,0, +17,178,0, +51,255,255,75,0,3, +28,99,0, +39, +16,0,64,0,0,75,2,7, +51,92,0, +51,93,0, +51,94,0, +51,95,0, +51,96,0, +51,97,0, +51,98,0, +51,255,255,11,0, +54,100,0, +17,102,0, +51,255,255,109,0,3, +54,101,0, +17,200,0, +51,22,0,3, +54,102,0, +17,212,0, +51,23,0,3, +54,103,0, +17,3,1, +51,255,255,109,0,3, +54,104,0, +17,15,1, +51,255,255,109,0,3, +54,105,0, +17,165,1, +51,255,255,81,0,3, +54,106,0, +17,178,1, +51,255,255,81,0,3, +54,107,0, +17,178,0, +51,255,255,75,0,3, +28,108,0, +39, +16,0,64,0,0,98,2,8, +51,100,0, +51,101,0, +51,102,0, +51,103,0, +51,104,0, +51,105,0, +51,106,0, +51,107,0, +51,255,255,11,0, +54,109,0, +17,102,0, +51,255,255,109,0,3, +54,110,0, +17,200,0, +51,28,0,3, +54,111,0, +17,212,0, +51,29,0,3, +54,112,0, +17,3,1, +51,255,255,109,0,3, +54,113,0, +17,15,1, +51,255,255,109,0,3, +54,114,0, +17,165,1, +51,255,255,81,0,3, +54,115,0, +17,178,1, +51,255,255,81,0,3, +54,116,0, +17,178,0, +51,255,255,75,0,3, +28,117,0, +39, +16,0,64,0,0,123,2,8, +51,109,0, +51,110,0, +51,111,0, +51,112,0, +51,113,0, +51,114,0, +51,115,0, +51,116,0, +51,255,255,11,0, +54,118,0, +17,148,2, +51,255,255,11,0,3, +54,119,0, +17,156,2, +51,255,255,158,2,3, +54,120,0, +17,167,2, +51,255,255,49,0,3, +54,121,0, +17,169,2, +51,255,255,75,0,3, +28,122,0, +39, +16,0,64,0,0,176,2,4, +51,118,0, +51,119,0, +51,120,0, +51,121,0, +51,255,255,11,0, +54,123,0, +17,198,2, +51,255,255,75,0,3, +54,124,0, +17,208,2, +51,255,255,11,0,3, +54,125,0, +17,212,2, +51,255,255,11,0,3, +28,126,0, +39, +16,0,64,0,0,216,2,3, +51,123,0, +51,124,0, +51,125,0, +51,255,255,11,0, +54,127,0, +17,198,2, +51,255,255,75,0,3, +54,128,0, +17,208,2, +51,255,255,11,0,3, +54,129,0, +17,212,2, +51,255,255,11,0,3, +28,130,0, +39, +16,0,64,0,0,225,2,3, +51,127,0, +51,128,0, +51,129,0, +51,255,255,11,0, +54,131,0, +17,241,2, +51,255,255,11,0,3, +54,132,0, +17,198,2, +51,255,255,75,0,3, +54,133,0, +17,250,2, +51,255,255,49,0,3, +28,134,0, +39, +16,0,64,0,0,3,3,3, +51,131,0, +51,132,0, +51,133,0, +51,255,255,11,0, +54,135,0, +17,24,3, +51,255,255,11,0,3, +54,136,0, +17,150,0, +51,255,255,152,0,3, +28,137,0, +39, +16,0,64,0,0,32,3,2, +51,135,0, +51,136,0, +51,255,255,11,0, +54,138,0, +17,24,3, +51,255,255,11,0,3, +28,139,0, +39, +16,0,64,0,0,53,3,1, +51,138,0, +51,255,255,11,0,31,0, +26,0, +32,0, +54,0, +36,0, +48,0, +40,0, +45,0, +9,0, +20,0, +21,0, +27,0, +22,0, +28,0, +125,0, +133,0, +129,0, +107,0, +116,0, +0,0, +138,0, +17,0, +61,0, +68,0, +121,0, +2,0, +75,0, +82,0, +4,0, +90,0, +98,0, +136,0, +20, +29,1,0, +2, +52,1,0,0,0,0,1, +44, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,128,63,1, +29,3,0, +2, +52,1,0,0,0,0,1, +44, +56,2,0,0,1, +29,5,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +56,4,0,0,1, +29,10,0, +2, +52,1,3,0, +54,140,0, +38, +16,4,82,3, +51,255,255,75,0,2, +54,141,0, +38, +16,4,89,3, +51,255,255,75,0,2, +54,142,0, +38, +16,4,97,3, +51,255,255,75,0,2,3,0, +0,0, +2,0, +1,0,4, +55,140,0, +51,255,255,75,0,0, +36, +51,255,255,75,0,0,0,0,0, +55,141,0, +51,255,255,75,0,0, +36, +51,255,255,75,0,1,0,0,0, +55,142,0, +51,255,255,75,0,0, +36, +51,255,255,75,0,2,0,0,0, +32,0, +1, +56,6,0,0,16, +56,140,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,81,0,255,255,111,3,3, +56,7,0,0, +56,8,0,0, +56,9,0,0,1, +32,0, +1, +56,6,0,0,16, +56,141,0,0, +2, +52,1,1,0, +54,143,0, +17,117,3, +51,255,255,81,0,2,1,0, +0,0,2, +55,143,0, +51,255,255,81,0,0, +1, +56,9,0,0,1, +56,8,0,0, +44, +1, +27, +51,255,255,81,0,255,255,124,3,2, +1, +56,7,0,0,1, +56,8,0,0, +56,143,0,0,0, +56,8,0,0,1, +32,0, +1, +56,6,0,0,16, +56,142,0,0, +2, +52,1,3,0, +54,144,0, +17,117,3, +51,255,255,81,0,2, +54,145,0, +17,128,3, +51,255,255,81,0,2, +54,146,0, +17,136,3, +51,255,255,81,0,2,3,0, +0,0, +1,0, +2,0,4, +55,144,0, +51,255,255,81,0,0, +1, +56,9,0,0,1, +56,8,0,0, +55,145,0, +51,255,255,81,0,0, +1, +25, +51,255,255,81,0,0,0,0,64,2, +56,144,0,0, +55,146,0, +51,255,255,81,0,0, +27, +51,255,255,81,0,255,255,124,3,2, +1, +56,7,0,0,1, +56,8,0,0, +56,145,0,0, +44, +1, +27, +51,255,255,81,0,255,255,140,3,3, +56,146,0,0, +1, +56,145,0,0,1, +56,146,0,0, +27, +51,255,255,81,0,255,255,144,3,2, +56,144,0,0, +56,146,0,0,0, +56,8,0,0,1, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,81,0,255,255,111,3,3, +56,7,0,0, +56,8,0,0, +56,9,0,0,1,1, +29,18,0, +2, +52,1,2,0, +54,147,0, +17,149,3, +51,255,255,49,0,2, +54,148,0, +17,160,3, +51,255,255,109,0,2,2,0, +1,0, +0,0,5, +22, +1, +50, +56,11,0,1,1,0,15, +27, +51,255,255,81,0,10,0,4, +56,13,0,0, +50, +56,11,0,0,1,0, +50, +56,12,0,0,1,0, +50, +56,12,0,0,1,2, +22, +1, +50, +56,11,0,1,1,1,15, +27, +51,255,255,81,0,10,0,4, +56,14,0,0, +50, +56,11,0,0,1,1, +50, +56,12,0,0,1,1, +50, +56,12,0,0,1,3, +55,147,0, +51,255,255,49,0,0, +8, +51,255,255,49,0,2, +1, +27, +51,255,255,109,0,255,255,171,3,1, +50, +56,12,0,0,2,0,1,0, +25, +51,255,255,81,0,0,0,0,63, +1, +27, +51,255,255,109,0,255,255,177,3,1, +50, +56,12,0,0,2,2,3,1, +25, +51,255,255,81,0,0,0,0,63, +55,148,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,255,255,111,3,3, +56,11,0,0, +50, +56,147,0,0,2,0,1, +50, +56,147,0,0,2,2,3, +44, +27, +51,255,255,11,0,255,255,182,3,2, +56,17,0,0, +1, +56,148,0,0,3, +8, +51,255,255,109,0,2, +12, +51,255,255,81,0,1, +56,15,0,0, +12, +51,255,255,81,0,1, +56,16,0,0,1, +29,21,0, +2, +52,1,0,0,0,0,2, +49,0, +52,1,0,0,0,0, +56,19,0,0,4,0,0,0,0,0, +2, +57,2, +22, +1, +50, +56,20,0,1,1,0,15, +27, +51,255,255,81,0,255,255,111,3,3, +50, +56,20,0,0,1,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,63, +4,0,0,1,0,0,0, +2, +57,2, +22, +1, +50, +56,20,0,1,1,0,15, +27, +51,255,255,81,0,255,255,189,3,1, +50, +56,20,0,0,1,0, +4,0,0,2,0,0,0, +2, +52,1,1,0, +54,149,0, +17,195,3, +51,255,255,81,0,2,1,0, +0,0,5, +55,149,0, +51,255,255,81,0,0, +1, +50, +56,20,0,0,1,0,1, +25, +51,255,255,81,0,0,0,128,63, +22, +1, +50, +56,20,0,1,1,0,15, +1, +1, +56,149,0,0,1, +1, +25, +51,255,255,81,0,0,0,0,64,2, +27, +51,255,255,81,0,255,255,171,3,1, +1, +56,149,0,0,2, +25, +51,255,255,81,0,0,0,0,63,1, +25, +51,255,255,81,0,0,0,128,63, +32,0, +45,199,3, +2, +52,1,0,0,0,0,1, +22, +1, +50, +56,20,0,1,1,0,15, +27, +51,255,255,81,0,255,255,111,3,3, +50, +56,20,0,0,1,0, +25, +51,255,255,81,0,0,0,128,191, +25, +51,255,255,81,0,0,0,128,63,1, +57, +22, +1, +50, +56,20,0,1,1,0,15, +27, +51,255,255,81,0,255,255,226,3,1, +50, +56,20,0,0,1,0, +4,1,0,3,0,0,0, +2, +57,2, +32,0, +1, +1, +50, +56,20,0,0,1,0,18, +25, +51,255,255,81,0,0,0,0,0,9, +1, +50, +56,20,0,0,1,0,19, +25, +51,255,255,81,0,0,0,128,63, +2, +52,1,0,0,0,0,1, +44, +8, +51,255,255,109,0,2, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,191,1, +57, +4,0, +44, +56,20,0,0,1, +29,27,0, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,26,0,0,1,1,18, +25, +51,255,255,81,0,0,0,0,0, +2, +52,1,0,0,0,0,1, +44, +13, +51,255,255,11,0,1, +25, +51,255,255,77,3,0,0,0,0,1, +32,0, +1, +50, +56,26,0,0,1,0,20, +33, +56,25,0,0, +36, +51,255,255,230,3,0,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +33, +56,24,0,0, +36, +51,255,255,230,3,0,0,0,0,1, +32,0, +1, +50, +56,26,0,0,1,0,18, +33, +56,25,0,0, +36, +51,255,255,230,3,1,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,24,0,0, +36, +51,255,255,230,3,0,0,0,0, +33, +56,24,0,0, +36, +51,255,255,230,3,1,0,0,0, +1, +1, +50, +56,26,0,0,1,0,1, +33, +56,25,0,0, +36, +51,255,255,230,3,0,0,0,0,3, +1, +33, +56,25,0,0, +36, +51,255,255,230,3,1,0,0,0,1, +33, +56,25,0,0, +36, +51,255,255,230,3,0,0,0,0,1, +32,0, +1, +50, +56,26,0,0,1,0,18, +33, +56,25,0,0, +36, +51,255,255,230,3,2,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,24,0,0, +36, +51,255,255,230,3,1,0,0,0, +33, +56,24,0,0, +36, +51,255,255,230,3,2,0,0,0, +1, +1, +50, +56,26,0,0,1,0,1, +33, +56,25,0,0, +36, +51,255,255,230,3,1,0,0,0,3, +1, +33, +56,25,0,0, +36, +51,255,255,230,3,2,0,0,0,1, +33, +56,25,0,0, +36, +51,255,255,230,3,1,0,0,0,1, +32,0, +1, +50, +56,26,0,0,1,0,18, +33, +56,25,0,0, +36, +51,255,255,230,3,3,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,24,0,0, +36, +51,255,255,230,3,2,0,0,0, +33, +56,24,0,0, +36, +51,255,255,230,3,3,0,0,0, +1, +1, +50, +56,26,0,0,1,0,1, +33, +56,25,0,0, +36, +51,255,255,230,3,2,0,0,0,3, +1, +33, +56,25,0,0, +36, +51,255,255,230,3,3,0,0,0,1, +33, +56,25,0,0, +36, +51,255,255,230,3,2,0,0,0,1, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +33, +56,24,0,0, +36, +51,255,255,230,3,3,0,0,0,1,1, +29,33,0, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,1,18, +25, +51,255,255,81,0,0,0,0,0, +2, +52,1,0,0,0,0,1, +44, +13, +51,255,255,11,0,1, +25, +51,255,255,77,3,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,18, +33, +56,31,0,0, +36, +51,255,255,230,3,4,0,0,0, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,18, +33, +56,31,0,0, +36, +51,255,255,230,3,2,0,0,0, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,20, +33, +56,31,0,0, +36, +51,255,255,230,3,0,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +33, +56,30,0,0, +36, +51,255,255,230,3,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,18, +33, +56,31,0,0, +36, +51,255,255,230,3,1,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,30,0,0, +36, +51,255,255,230,3,0,0,0,0, +33, +56,30,0,0, +36, +51,255,255,230,3,1,0,0,0, +1, +1, +50, +56,32,0,0,1,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,0,0,0,0,3, +1, +33, +56,31,0,0, +36, +51,255,255,230,3,1,0,0,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,0,0,0,0,1, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,30,0,0, +36, +51,255,255,230,3,1,0,0,0, +33, +56,30,0,0, +36, +51,255,255,230,3,2,0,0,0, +1, +1, +50, +56,32,0,0,1,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,1,0,0,0,3, +1, +33, +56,31,0,0, +36, +51,255,255,230,3,2,0,0,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,1,0,0,0,1,1, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,18, +33, +56,31,0,0, +36, +51,255,255,230,3,3,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,30,0,0, +36, +51,255,255,230,3,2,0,0,0, +33, +56,30,0,0, +36, +51,255,255,230,3,3,0,0,0, +1, +1, +50, +56,32,0,0,1,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,2,0,0,0,3, +1, +33, +56,31,0,0, +36, +51,255,255,230,3,3,0,0,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,2,0,0,0,1, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,30,0,0, +36, +51,255,255,230,3,3,0,0,0, +33, +56,30,0,0, +36, +51,255,255,230,3,4,0,0,0, +1, +1, +50, +56,32,0,0,1,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,3,0,0,0,3, +1, +33, +56,31,0,0, +36, +51,255,255,230,3,4,0,0,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,3,0,0,0,1,1,1, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,18, +33, +56,31,0,0, +36, +51,255,255,230,3,6,0,0,0, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,18, +33, +56,31,0,0, +36, +51,255,255,230,3,5,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,30,0,0, +36, +51,255,255,230,3,4,0,0,0, +33, +56,30,0,0, +36, +51,255,255,230,3,5,0,0,0, +1, +1, +50, +56,32,0,0,1,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,4,0,0,0,3, +1, +33, +56,31,0,0, +36, +51,255,255,230,3,5,0,0,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,4,0,0,0,1, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,30,0,0, +36, +51,255,255,230,3,5,0,0,0, +33, +56,30,0,0, +36, +51,255,255,230,3,6,0,0,0, +1, +1, +50, +56,32,0,0,1,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,5,0,0,0,3, +1, +33, +56,31,0,0, +36, +51,255,255,230,3,6,0,0,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,5,0,0,0,1,1, +2, +52,1,0,0,0,0,1, +32,0, +1, +50, +56,32,0,0,1,0,18, +33, +56,31,0,0, +36, +51,255,255,230,3,7,0,0,0, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +27, +51,255,255,49,0,255,255,140,3,3, +33, +56,30,0,0, +36, +51,255,255,230,3,6,0,0,0, +33, +56,30,0,0, +36, +51,255,255,230,3,7,0,0,0, +1, +1, +50, +56,32,0,0,1,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,6,0,0,0,3, +1, +33, +56,31,0,0, +36, +51,255,255,230,3,7,0,0,0,1, +33, +56,31,0,0, +36, +51,255,255,230,3,6,0,0,0,1, +2, +52,1,0,0,0,0,1, +44, +9, +51,255,255,11,0,1, +33, +56,30,0,0, +36, +51,255,255,230,3,7,0,0,0,1,1,1,1, +29,37,0, +2, +52,1,2,0, +54,150,0, +17,242,3, +51,255,255,109,0,2, +54,151,0, +17,187,0, +51,255,255,81,0,2,2,0, +0,0, +1,0,4, +22, +1, +56,36,0,2,23, +56,34,0,0, +55,150,0, +51,255,255,109,0,0, +1, +56,35,0,0,1, +56,34,0,0, +55,151,0, +51,255,255,81,0,0, +1, +27, +51,255,255,81,0,255,255,248,3,2, +56,36,0,0, +56,150,0,0,3, +27, +51,255,255,81,0,255,255,248,3,2, +56,150,0,0, +56,150,0,0, +44, +8, +51,255,255,109,0,2, +56,151,0,0, +25, +51,255,255,81,0,0,0,128,63,1, +29,41,0, +2, +52,1,1,0, +54,152,0, +17,187,0, +51,255,255,81,0,2,1,0, +0,0,2, +55,152,0, +51,255,255,81,0,0, +1, +27, +51,255,255,81,0,255,255,252,3,2, +56,40,0,0, +56,38,0,0,3, +56,39,0,0, +44, +8, +51,255,255,109,0,2, +56,152,0,0, +25, +51,255,255,81,0,0,0,128,63,1, +29,46,0, +2, +52,1,2,0, +54,153,0, +17,5,4, +51,255,255,81,0,2, +54,154,0, +17,187,0, +51,255,255,81,0,2,2,0, +0,0, +1,0,4, +22, +1, +56,45,0,2,23, +56,42,0,0, +55,153,0, +51,255,255,81,0,0, +53, +45,11,4, +1, +25, +51,255,255,81,0,0,0,0,64,2, +27, +51,255,255,81,0,255,255,40,4,2, +42,1, +50, +56,45,0,0,1,1, +1, +27, +51,255,255,81,0,255,255,117,3,1, +56,45,0,0,1, +50, +56,45,0,0,1,0, +27, +51,255,255,81,0,255,255,40,4,2, +42,1, +50, +56,45,0,0,1,1, +42,1, +50, +56,45,0,0,1,0, +55,154,0, +51,255,255,81,0,0, +1, +1, +1, +1, +56,153,0,0,2, +25, +51,255,255,81,0,131,249,34,62,0, +25, +51,255,255,81,0,0,0,0,63,0, +56,43,0,0,2, +56,44,0,0, +44, +8, +51,255,255,109,0,2, +56,154,0,0, +25, +51,255,255,81,0,0,0,128,63,1, +29,49,0, +2, +52,1,0,0,0,0,1, +44, +1, +8, +51,255,255,156,1,9, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,191, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,63, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,63,2, +27, +51,255,255,156,1,255,255,45,4,1, +8, +51,255,255,156,1,9, +1, +50, +56,48,0,0,1,1,1, +50, +56,47,0,0,1,1, +1, +50, +56,47,0,0,1,0,1, +50, +56,48,0,0,1,0, +25, +51,255,255,81,0,0,0,0,0, +1, +50, +56,48,0,0,1,0,1, +50, +56,47,0,0,1,0, +1, +50, +56,48,0,0,1,1,1, +50, +56,47,0,0,1,1, +25, +51,255,255,81,0,0,0,0,0, +50, +56,47,0,0,1,0, +50, +56,47,0,0,1,1, +25, +51,255,255,81,0,0,0,128,63,1, +29,55,0, +2, +52,1,5,0, +54,155,0, +38, +16,4,53,4, +51,255,255,81,0,2, +54,156,0, +17,73,4, +51,255,255,81,0,2, +54,157,0, +17,81,4, +51,255,255,81,0,2, +54,158,0, +17,89,4, +51,255,255,96,4,2, +54,159,0, +17,101,4, +51,255,255,96,4,2,5,0, +0,0, +1,0, +2,0, +3,0, +4,0,6, +55,155,0, +51,255,255,81,0,0, +25, +51,255,255,81,0,0,0,128,57, +55,156,0, +51,255,255,81,0,0, +27, +51,255,255,81,0,255,255,252,3,2, +56,50,0,0, +56,51,0,0, +55,157,0, +51,255,255,81,0,0, +1, +56,53,0,0,1, +56,52,0,0, +55,158,0, +51,255,255,96,4,0, +1, +56,156,0,0,18, +56,155,0,0, +55,159,0, +51,255,255,96,4,0, +1, +27, +51,255,255,81,0,255,255,226,3,1, +56,157,0,0,18, +56,155,0,0, +32,0, +56,158,0,0, +2, +52,1,5,0, +54,160,0, +17,107,4, +51,255,255,81,0,2, +54,161,0, +17,113,4, +51,255,255,81,0,2, +54,162,0, +17,123,4, +51,255,255,81,0,2, +54,163,0, +17,128,4, +51,255,255,109,0,2, +54,164,0, +17,187,0, +51,255,255,81,0,2,5,0, +2,0, +3,0, +0,0, +1,0, +4,0,7, +32,0, +56,159,0,0, +2, +52,1,0,0,0,0,1, +44, +8, +51,255,255,109,0,2, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,191,1, +57, +55,160,0, +51,255,255,81,0,0, +1, +25, +51,255,255,81,0,0,0,128,63,3, +56,157,0,0, +55,161,0, +51,255,255,81,0,0, +27, +51,255,255,81,0,255,255,131,4,1, +56,157,0,0, +55,162,0, +51,255,255,81,0,0, +1, +56,52,0,0,3, +56,157,0,0, +55,163,0, +51,255,255,109,0,0, +1, +1, +56,54,0,0,1, +56,50,0,0,2, +56,160,0,0, +55,164,0, +51,255,255,81,0,0, +1, +1, +27, +51,255,255,81,0,255,255,117,3,1, +56,163,0,0,2, +56,161,0,0,1, +56,162,0,0, +44, +8, +51,255,255,109,0,2, +56,164,0,0, +25, +51,255,255,81,0,0,0,128,63,1, +32,0, +56,159,0,0, +2, +52,1,5,0, +54,165,0, +17,136,4, +51,255,255,156,1,2, +54,166,0, +17,146,4, +51,255,255,81,0,2, +54,167,0, +17,148,4, +51,255,255,81,0,2, +54,168,0, +17,128,4, +51,255,255,109,0,2, +54,169,0, +17,187,0, +51,255,255,81,0,2,5,0, +3,0, +1,0, +2,0, +4,0, +0,0,8, +55,165,0, +51,255,255,156,1,0, +27, +51,255,255,156,1,49,0,2, +56,50,0,0, +56,51,0,0, +55,166,0, +51,255,255,81,0,0, +1, +56,52,0,0,3, +56,156,0,0, +55,167,0, +51,255,255,81,0,0, +1, +56,166,0,0,2, +56,166,0,0, +55,168,0, +51,255,255,109,0,0, +50, +1, +56,165,0,0,2, +8, +51,255,255,152,4,2, +56,54,0,0, +25, +51,255,255,81,0,0,0,128,63,2,0,1, +55,169,0, +51,255,255,81,0,0, +1, +56,167,0,0,1, +1, +50, +56,168,0,0,1,1,2, +50, +56,168,0,0,1,1, +32,0, +1, +56,169,0,0,18, +25, +51,255,255,81,0,0,0,0,0, +2, +52,1,0,0,0,0,1, +44, +8, +51,255,255,109,0,2, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,191,1, +57, +22, +1, +56,169,0,1,15, +1, +50, +56,168,0,0,1,0,0, +27, +51,255,255,81,0,255,255,159,4,1, +56,169,0,0, +44, +8, +51,255,255,109,0,2, +56,169,0,0, +25, +51,255,255,81,0,0,0,128,63,1, +2, +52,1,14,0, +54,170,0, +17,79,0, +51,255,255,81,0,2, +54,171,0, +17,164,4, +51,255,255,96,4,2, +54,172,0, +17,174,4, +51,255,255,109,0,2, +54,173,0, +17,136,4, +51,255,255,156,1,2, +54,174,0, +17,177,4, +51,255,255,81,0,2, +54,175,0, +17,184,4, +51,255,255,81,0,2, +54,176,0, +17,191,4, +51,255,255,81,0,2, +54,177,0, +17,194,4, +51,255,255,96,4,2, +54,178,0, +17,128,4, +51,255,255,109,0,2, +54,179,0, +17,210,4, +51,255,255,81,0,2, +54,180,0, +17,216,4, +51,255,255,81,0,2, +54,181,0, +17,228,4, +51,255,255,96,4,2, +54,182,0, +17,242,4, +51,255,255,81,0,2, +54,183,0, +17,187,0, +51,255,255,81,0,2,14,0, +2,0, +10,0, +0,0, +9,0, +7,0, +1,0, +11,0, +8,0, +6,0, +4,0, +5,0, +13,0, +3,0, +12,0,21, +55,170,0, +51,255,255,81,0,0, +1, +56,52,0,0,3, +1, +56,52,0,0,1, +56,53,0,0, +55,171,0, +51,255,255,96,4,0, +1, +27, +51,255,255,81,0,255,255,226,3,1, +1, +56,170,0,0,1, +25, +51,255,255,81,0,0,0,128,63,18, +56,155,0,0, +32,0, +56,171,0,0, +2, +52,1,1,0, +54,184,0, +17,246,4, +51,255,255,109,0,2,1,0, +0,0,4, +55,184,0, +51,255,255,109,0,0, +56,50,0,0, +22, +1, +56,50,0,1,15, +56,51,0,0, +22, +1, +56,51,0,1,15, +56,184,0,0, +22, +1, +56,170,0,1,15, +25, +51,255,255,81,0,0,0,0,0,1, +57, +55,172,0, +51,255,255,109,0,0, +1, +1, +56,50,0,0,2, +1, +25, +51,255,255,81,0,0,0,128,63,1, +56,170,0,0,0, +1, +56,51,0,0,2, +56,170,0,0, +55,173,0, +51,255,255,156,1,0, +27, +51,255,255,156,1,49,0,2, +56,172,0,0, +56,51,0,0, +55,174,0, +51,255,255,81,0,0, +27, +51,255,255,81,0,255,255,226,3,1, +1, +25, +51,255,255,81,0,0,0,128,63,1, +56,170,0,0, +55,175,0, +51,255,255,81,0,0, +56,174,0,0, +55,176,0, +51,255,255,81,0,0, +1, +27, +51,255,255,81,0,255,255,226,3,1, +1, +56,53,0,0,1, +56,52,0,0,3, +56,156,0,0, +55,177,0, +51,255,255,96,4,0, +1, +27, +51,255,255,81,0,255,255,226,3,1, +1, +56,176,0,0,1, +25, +51,255,255,81,0,0,0,128,63,18, +56,155,0,0, +32,0, +56,177,0,0, +2, +52,1,0,0,0,0,2, +22, +1, +56,174,0,2,24, +25, +51,255,255,81,0,0,0,0,63, +22, +1, +56,175,0,2,24, +25, +51,255,255,81,0,0,0,0,63,1, +2, +52,1,0,0,0,0,2, +22, +1, +56,174,0,2,24, +1, +56,176,0,0,3, +1, +1, +56,176,0,0,2, +56,176,0,0,1, +25, +51,255,255,81,0,0,0,128,63, +22, +1, +56,175,0,2,25, +27, +51,255,255,81,0,255,255,159,4,1, +27, +51,255,255,81,0,255,255,226,3,1, +1, +1, +56,176,0,0,2, +56,176,0,0,1, +25, +51,255,255,81,0,0,0,128,63,1, +22, +1, +56,173,0,1,15, +1, +8, +51,255,255,156,1,9, +56,174,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,0,0, +56,175,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,63,2, +56,173,0,0, +55,178,0, +51,255,255,109,0,0, +50, +1, +56,173,0,0,2, +8, +51,255,255,152,4,2, +56,54,0,0, +25, +51,255,255,81,0,0,0,128,63,2,0,1, +55,179,0, +51,255,255,81,0,0, +1, +25, +51,255,255,81,0,0,0,128,63,3, +56,176,0,0, +55,180,0, +51,255,255,81,0,0, +27, +51,255,255,81,0,255,255,131,4,1, +1, +25, +51,255,255,81,0,0,0,128,63,1, +56,170,0,0, +55,181,0, +51,255,255,96,4,0, +1, +42,7, +56,177,0,0,8, +1, +56,176,0,0,19, +25, +51,255,255,81,0,0,0,128,63, +55,182,0, +51,255,255,81,0,0, +25, +51,255,255,81,0,0,0,128,191, +32,0, +56,177,0,0, +2, +52,1,0,0,0,0,1, +22, +1, +56,182,0,1,15, +1, +27, +51,255,255,81,0,255,255,248,3,2, +56,178,0,0, +56,178,0,0,3, +50, +56,178,0,0,1,0,1, +32,0, +56,181,0,0, +2, +52,1,0,0,0,0,1, +22, +1, +56,182,0,1,15, +1, +27, +51,255,255,81,0,255,255,117,3,1, +56,178,0,0,1, +1, +50, +56,178,0,0,1,0,2, +56,179,0,0,1, +2, +52,1,1,0, +54,185,0, +17,252,4, +51,255,255,81,0,2,1,0, +0,0,2, +55,185,0, +51,255,255,81,0,0, +1, +1, +50, +56,178,0,0,1,0,2, +50, +56,178,0,0,1,0,1, +1, +50, +56,178,0,0,1,1,2, +50, +56,178,0,0,1,1, +32,0, +1, +56,185,0,0,21, +25, +51,255,255,81,0,0,0,0,0, +2, +52,1,0,0,0,0,1, +32,0, +1, +56,171,0,0,9, +1, +56,180,0,0,18, +25, +51,255,255,81,0,0,0,0,0, +2, +52,1,0,0,0,0,1, +22, +1, +56,182,0,1,15, +1, +42,1, +27, +51,255,255,81,0,255,255,159,4,1, +56,185,0,0,1, +1, +50, +56,178,0,0,1,0,2, +56,179,0,0,1, +2, +52,1,0,0,0,0,1, +22, +1, +56,182,0,1,15, +1, +27, +51,255,255,81,0,255,255,159,4,1, +56,185,0,0,1, +1, +50, +56,178,0,0,1,0,2, +56,179,0,0,1,1, +57,1, +32,0, +1, +42,7, +56,181,0,0,8, +1, +56,182,0,0,18, +25, +51,255,255,81,0,0,0,0,0, +2, +52,1,0,0,0,0,1, +44, +8, +51,255,255,109,0,2, +25, +51,255,255,81,0,0,0,0,0, +25, +51,255,255,81,0,0,0,128,191,1, +57, +55,183,0, +51,255,255,81,0,0, +1, +56,170,0,0,0, +1, +56,180,0,0,2, +56,182,0,0, +32,0, +56,171,0,0, +2, +52,1,0,0,0,0,1, +22, +1, +56,183,0,1,15, +1, +25, +51,255,255,81,0,0,0,128,63,1, +56,183,0,0,1, +57, +44, +8, +51,255,255,109,0,2, +56,183,0,0, +25, +51,255,255,81,0,0,0,128,63,1,1, +29,62,0, +2, +52,1,1,0, +54,186,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,186,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,37,0,3, +56,59,0,0, +56,60,0,0, +56,56,0,0, +22, +1, +56,186,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,61,0,0, +56,186,0,0, +44, +27, +51,255,255,11,0,27,0,3, +56,57,0,0, +56,58,0,0, +56,186,0,0,1, +29,69,0, +2, +52,1,1,0, +54,187,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,187,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,37,0,3, +56,66,0,0, +56,67,0,0, +56,63,0,0, +22, +1, +56,187,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,68,0,0, +56,187,0,0, +44, +27, +51,255,255,11,0,33,0,3, +56,64,0,0, +56,65,0,0, +56,187,0,0,1, +29,76,0, +2, +52,1,1,0, +54,188,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,188,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,41,0,3, +56,73,0,0, +56,74,0,0, +56,70,0,0, +22, +1, +56,188,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,75,0,0, +56,188,0,0, +44, +27, +51,255,255,11,0,27,0,3, +56,71,0,0, +56,72,0,0, +56,188,0,0,1, +29,83,0, +2, +52,1,1,0, +54,189,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,189,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,41,0,3, +56,80,0,0, +56,81,0,0, +56,77,0,0, +22, +1, +56,189,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,82,0,0, +56,189,0,0, +44, +27, +51,255,255,11,0,33,0,3, +56,78,0,0, +56,79,0,0, +56,189,0,0,1, +29,91,0, +2, +52,1,1,0, +54,190,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,190,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,46,0,4, +56,87,0,0, +56,88,0,0, +56,89,0,0, +56,84,0,0, +22, +1, +56,190,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,90,0,0, +56,190,0,0, +44, +27, +51,255,255,11,0,27,0,3, +56,85,0,0, +56,86,0,0, +56,190,0,0,1, +29,99,0, +2, +52,1,1,0, +54,191,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,191,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,46,0,4, +56,95,0,0, +56,96,0,0, +56,97,0,0, +56,92,0,0, +22, +1, +56,191,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,98,0,0, +56,191,0,0, +44, +27, +51,255,255,11,0,33,0,3, +56,93,0,0, +56,94,0,0, +56,191,0,0,1, +29,108,0, +2, +52,1,1,0, +54,192,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,192,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,55,0,5, +56,103,0,0, +56,104,0,0, +56,105,0,0, +56,106,0,0, +56,100,0,0, +22, +1, +56,192,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,107,0,0, +56,192,0,0, +44, +27, +51,255,255,11,0,27,0,3, +56,101,0,0, +56,102,0,0, +56,192,0,0,1, +29,117,0, +2, +52,1,1,0, +54,193,0, +17,187,0, +51,255,255,109,0,2,1,0, +0,0,3, +55,193,0, +51,255,255,109,0,0, +27, +51,255,255,109,0,55,0,5, +56,112,0,0, +56,113,0,0, +56,114,0,0, +56,115,0,0, +56,109,0,0, +22, +1, +56,193,0,1,15, +27, +51,255,255,109,0,21,0,2, +56,116,0,0, +56,193,0,0, +44, +27, +51,255,255,11,0,33,0,3, +56,110,0,0, +56,111,0,0, +56,193,0,0,1, +29,122,0, +2, +52,1,1,0, +54,194,0, +17,1,5, +51,255,255,11,0,2,1,0, +0,0,4, +32,0, +12, +51,255,255,96,4,1, +56,121,0,0, +2, +52,1,0,0,0,0,1, +22, +1, +56,118,0,1,15, +27, +51,255,255,11,0,255,255,10,5,2, +50, +56,118,0,0,3,0,1,2, +50, +56,118,0,0,1,3,1, +2, +52,1,0,0,0,0,1, +22, +1, +56,118,0,1,15, +27, +51,255,255,11,0,255,255,22,5,1, +56,118,0,0,1, +55,194,0, +51,255,255,11,0,0, +9, +51,255,255,11,0,1, +1, +1, +56,119,0,0,2, +9, +51,255,255,49,0,1, +56,118,0,0,0, +56,120,0,0, +32,0, +12, +51,255,255,96,4,1, +56,121,0,0, +2, +52,1,0,0,0,0,1, +22, +1, +56,194,0,1,15, +27, +51,255,255,11,0,255,255,31,5,2, +50, +56,194,0,0,3,0,1,2, +50, +56,194,0,0,1,3,1, +2, +52,1,0,0,0,0,2, +22, +1, +56,194,0,1,15, +27, +51,255,255,11,0,255,255,43,5,1, +56,194,0,0, +22, +1, +50, +56,194,0,2,3,0,1,2,24, +50, +56,194,0,0,1,3,1, +44, +56,194,0,0,1, +29,126,0, +2, +52,1,0,0,0,0,1, +49,0, +52,1,0,0,0,0, +56,123,0,0,30,0,0,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,52,5,2, +56,124,0,0, +56,125,0,0,1,0,1,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,64,5,2, +56,124,0,0, +56,125,0,0,1,0,2,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,74,5,2, +56,124,0,0, +56,125,0,0,1,0,3,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,191, +56,124,0,0, +56,125,0,0,1,0,4,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,128,191, +25, +51,255,255,77,3,0,0,0,0, +56,124,0,0, +56,125,0,0,1,0,5,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,0,0, +56,124,0,0, +56,125,0,0,1,0,6,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,63, +56,124,0,0, +56,125,0,0,1,0,7,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,191, +25, +51,255,255,77,3,0,0,0,0, +56,124,0,0, +56,125,0,0,1,0,8,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,191, +56,124,0,0, +56,125,0,0,1,0,9,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,128,191, +56,124,0,0, +56,125,0,0,1,0,10,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,191, +25, +51,255,255,77,3,0,0,128,63, +56,124,0,0, +56,125,0,0,1,0,11,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,191, +25, +51,255,255,77,3,0,0,128,191, +56,124,0,0, +56,125,0,0,1,0,12,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,84,5,3, +8, +51,255,255,11,0,4, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,0,0, +56,124,0,0, +56,125,0,0,1,0,13,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,102,5,2, +56,124,0,0, +56,125,0,0,1,0,14,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,117,5,2, +56,124,0,0, +56,125,0,0,1,0,15,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,130,5,3, +25, +51,255,255,77,3,0,0,0,0, +56,124,0,0, +56,125,0,0,1,0,16,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,144,5,3, +25, +51,255,255,77,3,0,0,128,63, +56,124,0,0, +56,125,0,0,1,0,17,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,144,5,3, +25, +51,255,255,77,3,0,0,128,191, +56,124,0,0, +56,125,0,0,1,0,18,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,157,5,2, +56,124,0,0, +56,125,0,0,1,0,19,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,175,5,2, +56,124,0,0, +56,125,0,0,1,0,20,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,130,5,3, +25, +51,255,255,77,3,0,0,128,63, +56,124,0,0, +56,125,0,0,1,0,21,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,192,5,2, +56,124,0,0, +56,125,0,0,1,0,22,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,209,5,2, +56,124,0,0, +56,125,0,0,1,0,23,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,226,5,2, +56,124,0,0, +56,125,0,0,1,0,24,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,242,5,2, +56,124,0,0, +56,125,0,0,1,0,25,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,1,6,3, +8, +51,255,255,12,6,2, +25, +51,255,255,77,3,0,0,0,0, +25, +51,255,255,77,3,0,0,128,63, +56,124,0,0, +56,125,0,0,1,0,26,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,1,6,3, +13, +51,255,255,12,6,1, +25, +51,255,255,77,3,0,0,128,63, +56,124,0,0, +56,125,0,0,1,0,27,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,1,6,3, +13, +51,255,255,12,6,1, +25, +51,255,255,77,3,0,0,0,0, +56,124,0,0, +56,125,0,0,1,0,28,0,0,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,255,255,1,6,3, +8, +51,255,255,12,6,2, +25, +51,255,255,77,3,0,0,128,63, +25, +51,255,255,77,3,0,0,0,0, +56,124,0,0, +56,125,0,0,1,1, +44, +13, +51,255,255,11,0,1, +25, +51,255,255,77,3,0,0,0,0,1, +29,130,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,126,0,3, +56,127,0,0, +56,128,0,0, +56,129,0,0,1, +29,134,0, +2, +52,1,0,0,0,0,1, +44, +27, +51,255,255,11,0,126,0,3, +56,132,0,0, +9, +51,255,255,11,0,1, +56,133,0,0, +56,131,0,0,1, +29,137,0, +2, +52,1,2,0, +54,195,0, +17,102,0, +51,255,255,11,0,2, +54,196,0, +17,17,0, +51,255,255,11,0,2,2,0, +1,0, +0,0,3, +55,195,0, +51,255,255,11,0,0, +1, +1, +1, +27, +51,255,255,11,0,255,255,22,5,1, +56,135,0,0,2, +25, +51,255,255,77,3,0,0,127,67,3, +25, +51,255,255,77,3,0,0,128,67,0, +25, +51,255,255,77,3,0,0,0,59, +55,196,0, +51,255,255,11,0,0, +8, +51,255,255,11,0,4, +50, +27, +51,255,255,11,0,255,255,182,3,2, +56,136,0,0, +9, +51,255,255,109,0,1, +8, +51,255,255,12,6,2, +50, +56,195,0,0,1,0, +25, +51,255,255,77,3,0,0,192,62,1,0, +50, +27, +51,255,255,11,0,255,255,182,3,2, +56,136,0,0, +9, +51,255,255,109,0,1, +8, +51,255,255,12,6,2, +50, +56,195,0,0,1,1, +25, +51,255,255,77,3,0,0,32,63,1,0, +50, +27, +51,255,255,11,0,255,255,182,3,2, +56,136,0,0, +9, +51,255,255,109,0,1, +8, +51,255,255,12,6,2, +50, +56,195,0,0,1,2, +25, +51,255,255,77,3,0,0,96,63,1,0, +25, +51,255,255,77,3,0,0,128,63, +44, +1, +56,196,0,0,2, +50, +27, +51,255,255,11,0,255,255,182,3,2, +56,136,0,0, +9, +51,255,255,109,0,1, +8, +51,255,255,12,6,2, +50, +56,195,0,0,1,3, +25, +51,255,255,77,3,0,0,0,62,1,0,1, +29,139,0, +2, +52,1,1,0, +54,197,0, +17,18,6, +51,255,255,77,3,2,1,0, +0,0,3, +55,197,0, +51,255,255,77,3,0, +1, +25, +51,255,255,77,3,0,0,128,63,1, +50, +56,138,0,0,1,3, +22, +1, +56,197,0,1,15, +1, +27, +51,255,255,77,3,255,255,25,6,1, +1, +1, +42,1, +56,197,0,0,2, +56,197,0,0,2, +25, +51,255,255,77,3,0,0,128,64,1, +25, +51,255,255,77,3,188,116,147,60, +44, +13, +51,255,255,11,0,1, +56,197,0,0,1, +21,}; +static constexpr size_t SKSL_INCLUDE_sksl_graphite_frag_LENGTH = sizeof(SKSL_INCLUDE_sksl_graphite_frag); diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.minified.sksl new file mode 100644 index 0000000000..464e53c517 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.minified.sksl @@ -0,0 +1,179 @@ +static constexpr char SKSL_MINIFIED_sksl_graphite_frag[] = +"$pure half4 sk_error(){return half4(1.,0.,1.,1.);}$pure half4 sk_passthrough" +"(half4 a){return a;}$pure half4 sk_solid_shader(float4 a){return half4(a);}" +"$pure half4 $k(int a,half4 b){half4 c=b;switch(a){case 0:break;case 1:c=half4" +"(b.xyz,1.);break;case 2:c=b.xxxx;break;case 3:c=half4(b.xxx,1.);break;case 4" +":c=b.zyxw;break;}return c;}$pure half $l(int a,half b,half[7]c){half d=c[0]" +";half e=c[1];half f=c[2];half g=c[3];half h=c[4];half i=c[5];half j=c[6];half" +" k=sign(b);b=abs(b);switch(a){case 1:b=b<h?g*b+j:pow(e*b+f,d)+i;break;case 2" +":b=pow(max(e+f*pow(b,g),0.)/(h+i*pow(b,g)),j);break;case 3:b=b*e<=1.?pow(b*" +"e,f):exp((b-i)*g)+h;b*=j+1.;break;case 4:b/=j+1.;b=b<=1.?e*pow(b,f):g*log(b" +"-h)+i;break;}return k*b;}$pure half4 sk_color_space_transform(half4 a,int b" +",int c,half[7]d,half3x3 e,int f,half[7]g){if(bool(b&1)){a=unpremul(a);}if(bool" +"(b&2)){a.x=$l(c,a.x,d);a.y=$l(c,a.y,d);a.z=$l(c,a.z,d);}if(bool(b&4)){a.xyz" +"=e*a.xyz;}if(bool(b&8)){a.x=$l(f,a.x,g);a.y=$l(f,a.y,g);a.z=$l(f,a.z,g);}if" +"(bool(b&16)){a.xyz*=a.w;}return a;}$pure float $m(int a,float b,float c,float" +" d){switch(a){case 0:return clamp(b,c,d);case 1:{float e=d-c;return mod(b-c" +",e)+c;}case 2:{float e=d-c;float g=2.*e;float h=mod(b-c,g);return mix(h,g-h" +",step(e,h))+c;}default:return b;}}$pure half4 $n(float2 a,float2 b,float4 c" +",int d,int e,int f,int g,sampler2D h){if(d==3&&f==0){float i=floor(a.x)+.5;" +"if(i<c.x||i>c.z){return half4(0.);}}if(e==3&&f==0){float i=floor(a.y)+.5;if" +"(i<c.y||i>c.w){return half4(0.);}}a.x=$m(d,a.x,c.x,c.z);a.y=$m(e,a.y,c.y,c." +"w);float4 i;if(f==0){i=float4(floor(c.xy)+.5,ceil(c.zw)-.5);}else{i=float4(" +"c.xy+.5,c.zw-.5);}float2 j=clamp(a,i.xy,i.zw);half4 k=sample(h,j/b);k=$k(g," +"k);if(f==1){half2 l=half2(a-j);half2 m=abs(l);bool n=d==1;bool o=e==1;if(n||" +"o){float p;float q;half4 r;half4 t;if(n){p=l.x>0.?i.x:i.z;r=sample(h,float2" +"(p,j.y)/b);r=$k(g,r);}if(o){q=l.y>0.?i.y:i.w;t=sample(h,float2(j.x,q)/b);t=" +"$k(g,t);}if(n&&o){half4 u=sample(h,float2(p,q)/b);u=$k(g,u);k=mix(mix(k,r,m" +".x),mix(t,u,m.x),m.y);}else if(n){k=mix(k,r,m.x);}else if(o){k=mix(k,t,m.y)" +";}}if(d==3){k*=max(1.-m.x,0.);}if(e==3){k*=max(1.-m.y,0.);}}return k;}$pure" +" half4 $o(float2 a,float2 b,float4 c,int d,int e,float4x4 g,int h,sampler2D" +" i){float2 j=fract(a-.5);a-=1.5;a=floor(a)+.5;float4 k=g*float4(1.,j.x,j.x*" +"j.x,(j.x*j.x)*j.x);float4 l=g*float4(1.,j.y,j.y*j.y,(j.y*j.y)*j.y);float4 m" +"=float4(0.);for(int n=0;n<4;++n){float4 o=float4(0.);for(int p=0;p<4;++p){o" +"+=k[p]*float4($n(a+float2(float(p),float(n)),b,c,d,e,0,h,i));}m+=l[n]*o;}return" +" half4(m);}$pure half4 sk_image_shader(float2 a,float2 b,float4 c,int d,int" +" e,int f,int g,float4x4 h,int i,int j,int k,half[7]l,half3x3 m,int n,half[7" +"]o,sampler2D p){half4 q=g!=0?$o(a,b,c,d,e,h,i,p):$n(a,b,c,d,e,f,i,p);return" +" sk_color_space_transform(q,j,k,l,m,n,o);}$pure half4 sk_dither_shader(half4" +" a,float2 b,float c,sampler2D d){half2 f=half2(half(b.x*.125),half(b.y*.125" +"));half g=sample(d,float2(f)).x-.5;return half4(half3(clamp(float3(a.xyz)+float" +"(g)*c,0.,float(a.w))),a.w);}$pure float2 $p(int a,float2 b){switch(a){case 0" +":b.x=clamp(b.x,0.,1.);break;case 1:b.x=fract(b.x);break;case 2:{float c=b.x" +"-1.;b.x=(c-2.*floor(c*.5))-1.;if(sk_Caps.mustDoOpBetweenFloorAndAbs){b.x=clamp" +"(b.x,-1.,1.);}b.x=abs(b.x);break;}case 3:if(b.x<0.||b.x>1.){return float2(0." +",-1.);}break;}return b;}$pure half4 $q(float4[4]a,float[4]b,float2 c){if(c." +"y<0.){return half4(0.);}else if(c.x<=b[0]){return half4(a[0]);}else if(c.x<" +"b[1]){return half4(mix(a[0],a[1],(c.x-b[0])/(b[1]-b[0])));}else if(c.x<b[2]" +"){return half4(mix(a[1],a[2],(c.x-b[1])/(b[2]-b[1])));}else if(c.x<b[3]){return" +" half4(mix(a[2],a[3],(c.x-b[2])/(b[3]-b[2])));}else{return half4(a[3]);}}$pure" +" half4 $r(float4[8]a,float[8]b,float2 c){if(c.y<0.){return half4(0.);}else if" +"(c.x<b[4]){if(c.x<b[2]){if(c.x<=b[0]){return half4(a[0]);}else if(c.x<b[1])" +"{return half4(mix(a[0],a[1],(c.x-b[0])/(b[1]-b[0])));}else{return half4(mix" +"(a[1],a[2],(c.x-b[1])/(b[2]-b[1])));}}else{if(c.x<b[3]){return half4(mix(a[" +"2],a[3],(c.x-b[2])/(b[3]-b[2])));}else{return half4(mix(a[3],a[4],(c.x-b[3]" +")/(b[4]-b[3])));}}}else{if(c.x<b[6]){if(c.x<b[5]){return half4(mix(a[4],a[5" +"],(c.x-b[4])/(b[5]-b[4])));}else{return half4(mix(a[5],a[6],(c.x-b[5])/(b[6" +"]-b[5])));}}else{if(c.x<b[7]){return half4(mix(a[6],a[7],(c.x-b[6])/(b[7]-b" +"[6])));}else{return half4(a[7]);}}}}half4 $s(sampler2D a,int b,float2 c){if" +"(c.y<0.){return half4(0.);}else if(c.x==0.){return sampleLod(a,float2(0.,.25" +"),0.);}else if(c.x==1.){return sampleLod(a,float2(1.,.25),0.);}else{int f=0" +";int g=b;for(int h=1;h<b;h<<=1){int i=(f+g)/2;float j=(float(i)+.5)/float(b" +");float2 k=float2(sampleLod(a,float2(j,.75),0.).xy);float l=ldexp(k.x,int(k" +".y));if(c.x<l){g=i;}else{f=i;}}float h=(float(f)+.5)/float(b);float i=(float" +"(f+1)+.5)/float(b);half4 j=sampleLod(a,float2(h,.25),0.);half4 k=sampleLod(" +"a,float2(i,.25),0.);float2 l=float2(sampleLod(a,float2(h,.75),0.).xy);float" +" m=ldexp(l.x,int(l.y));l=float2(sampleLod(a,float2(i,.75),0.).xy);float n=ldexp" +"(l.x,int(l.y));return half4(mix(float4(j),float4(k),(c.x-m)/(n-m)));}}$pure" +" float2 $t(float2 a,float2 b,float2 c){c-=a;float2 d=b-a;float e=dot(c,d)/dot" +"(d,d);return float2(e,1.);}$pure float2 $u(float2 a,float b,float2 c){float" +" d=distance(c,a)/b;return float2(d,1.);}$pure float2 $v(float2 a,float b,float" +" c,float2 d){d-=a;float e=sk_Caps.atan2ImplementedAsAtanYOverX?2.*atan(-d.y" +",length(d)-d.x):atan(-d.y,-d.x);float f=((e*.159154937+.5)+b)*c;return float2" +"(f,1.);}$pure float3x3 $w(float2 a,float2 b){return float3x3(0.,-1.,0.,1.,0." +",0.,0.,0.,1.)*inverse(float3x3(b.y-a.y,a.x-b.x,0.,b.x-a.x,b.y-a.y,0.,a.x,a." +"y,1.));}$pure float2 $x(float2 a,float2 b,float c,float d,float2 e){const float" +" f=.000244140625;float g=distance(a,b);float h=d-c;bool i=g<f;bool j=abs(h)" +"<f;if(i){if(j){return float2(0.,-1.);}float k=1./h;float l=sign(h);float m=" +"c/h;float2 n=(e-a)*k;float o=length(n)*l-m;return float2(o,1.);}else if(j){" +"float3x3 k=$w(a,b);float l=c/g;float m=l*l;float2 n=(k*float3(e,1.)).xy;float" +" o=m-n.y*n.y;if(o<0.){return float2(0.,-1.);}o=n.x+sqrt(o);return float2(o," +"1.);}else{float k=c/(c-d);bool l=abs(k-1.)<f;if(l){float2 m=a;a=b;b=m;k=0.;" +"}float2 m=a*(1.-k)+b*k;float3x3 n=$w(m,b);float o=abs(1.-k);float p=o;float" +" q=abs(d-c)/g;bool r=abs(q-1.)<f;if(r){o*=.5;p*=.5;}else{o*=q/(q*q-1.);p/=sqrt" +"(abs(q*q-1.));}n=float3x3(o,0.,0.,0.,p,0.,0.,0.,1.)*n;float2 s=(n*float3(e," +"1.)).xy;float u=1./q;float v=sign(1.-k);bool w=!r&&q>1.;float x=-1.;if(r){x" +"=dot(s,s)/s.x;}else if(w){x=length(s)-s.x*u;}else{float y=s.x*s.x-s.y*s.y;if" +"(y>=0.){if(l||v<0.){x=-sqrt(y)-s.x*u;}else{x=sqrt(y)-s.x*u;}}}if(!w&&x<0.){" +"return float2(0.,-1.);}float y=k+v*x;if(l){y=1.-y;}return float2(y,1.);}}$pure" +" half4 sk_linear_grad_4_shader(float2 a,float4[4]b,float[4]c,float2 d,float2" +" e,int f,int g,int h){float2 i=$t(d,e,a);i=$p(f,i);half4 j=$q(b,c,i);return" +" $interpolated_to_rgb_unpremul(j,g,h);}$pure half4 sk_linear_grad_8_shader(" +"float2 a,float4[8]b,float[8]c,float2 d,float2 e,int f,int g,int h){float2 i" +"=$t(d,e,a);i=$p(f,i);half4 j=$r(b,c,i);return $interpolated_to_rgb_unpremul" +"(j,g,h);}$pure half4 sk_linear_grad_tex_shader(float2 a,float2 b,float2 c,int" +" d,int e,int f,int g,sampler2D h){float2 i=$t(b,c,a);i=$p(e,i);half4 j=$s(h" +",d,i);return $interpolated_to_rgb_unpremul(j,f,g);}$pure half4 sk_radial_grad_4_shader" +"(float2 a,float4[4]b,float[4]c,float2 d,float e,int f,int g,int h){float2 i" +"=$u(d,e,a);i=$p(f,i);half4 j=$q(b,c,i);return $interpolated_to_rgb_unpremul" +"(j,g,h);}$pure half4 sk_radial_grad_8_shader(float2 a,float4[8]b,float[8]c," +"float2 d,float e,int f,int g,int h){float2 i=$u(d,e,a);i=$p(f,i);half4 j=$r" +"(b,c,i);return $interpolated_to_rgb_unpremul(j,g,h);}$pure half4 sk_radial_grad_tex_shader" +"(float2 a,float2 b,float c,int d,int e,int f,int g,sampler2D h){float2 i=$u" +"(b,c,a);i=$p(e,i);half4 j=$s(h,d,i);return $interpolated_to_rgb_unpremul(j," +"f,g);}$pure half4 sk_sweep_grad_4_shader(float2 a,float4[4]b,float[4]c,float2" +" d,float e,float f,int g,int h,int i){float2 j=$v(d,e,f,a);j=$p(g,j);half4 k" +"=$q(b,c,j);return $interpolated_to_rgb_unpremul(k,h,i);}$pure half4 sk_sweep_grad_8_shader" +"(float2 a,float4[8]b,float[8]c,float2 d,float e,float f,int g,int h,int i){" +"float2 j=$v(d,e,f,a);j=$p(g,j);half4 k=$r(b,c,j);return $interpolated_to_rgb_unpremul" +"(k,h,i);}$pure half4 sk_sweep_grad_tex_shader(float2 a,float2 b,float c,float" +" d,int e,int f,int g,int h,sampler2D i){float2 j=$v(b,c,d,a);j=$p(f,j);half4" +" k=$s(i,e,j);return $interpolated_to_rgb_unpremul(k,g,h);}$pure half4 sk_conical_grad_4_shader" +"(float2 a,float4[4]b,float[4]c,float2 d,float2 e,float f,float g,int h,int i" +",int j){float2 k=$x(d,e,f,g,a);k=$p(h,k);half4 l=$q(b,c,k);return $interpolated_to_rgb_unpremul" +"(l,i,j);}$pure half4 sk_conical_grad_8_shader(float2 a,float4[8]b,float[8]c" +",float2 d,float2 e,float f,float g,int h,int i,int j){float2 k=$x(d,e,f,g,a" +");k=$p(h,k);half4 l=$r(b,c,k);return $interpolated_to_rgb_unpremul(l,i,j);}" +"$pure half4 sk_conical_grad_tex_shader(float2 a,float2 b,float2 c,float d,float" +" e,int f,int g,int h,int i,sampler2D j){float2 k=$x(b,c,d,e,a);k=$p(g,k);half4" +" l=$s(j,f,k);return $interpolated_to_rgb_unpremul(l,h,i);}$pure half4 sk_matrix_colorfilter" +"(half4 a,float4x4 b,float4 c,int d){if(bool(d)){a=$rgb_to_hsl(a.xyz,a.w);}else" +"{a=unpremul(a);}half4 e=half4(b*float4(a)+c);if(bool(d)){e=$hsl_to_rgb(e.xyz" +",e.w);}else{e=saturate(e);e.xyz*=e.w;}return e;}$pure half4 noise_helper(half2" +" a,half2 b,int c,sampler2D d){half4 f;f.xy=floor(a);f.zw=f.xy+half2(1.);if(" +"bool(c)){if(f.x>=b.x){f.x-=b.x;}if(f.y>=b.y){f.y-=b.y;}if(f.z>=b.x){f.z-=b." +"x;}if(f.w>=b.y){f.w-=b.y;}}half g=sample(d,float2(half2(f.x*.00390625,.5)))" +".x;half h=sample(d,float2(half2(f.z*.00390625,.5))).x;half2 i=half2(g,h);i=" +"floor(i*half2(255.)+half2(.5))*half2(.003921569);half4 k=256.*i.xyxy+f.yyww" +";k*=half4(.00390625);return k;}$pure half4 noise_function(half2 a,half4 b,sampler2D" +" c){half2 d=fract(a);half2 e=(d*d)*(half2(3.)-2.*d);const half f=.00390625;" +"half4 g;for(int h=0;h<4;h++){half i=(half(h)+.5)*.25;half4 j=sample(c,float2" +"(half2(b.x,i)));half4 k=sample(c,float2(half2(b.y,i)));half4 l=sample(c,float2" +"(half2(b.w,i)));half4 m=sample(c,float2(half2(b.z,i)));half2 n;half2 o=d;n." +"x=dot((j.yw+j.xz*f)*2.-half2(1.),o);o.x-=1.;n.y=dot((k.yw+k.xz*f)*2.-half2(" +"1.),o);half2 p;p.x=mix(n.x,n.y,e.x);o.y-=1.;n.y=dot((l.yw+l.xz*f)*2.-half2(" +"1.),o);o.x+=1.;n.x=dot((m.yw+m.xz*f)*2.-half2(1.),o);p.y=mix(n.x,n.y,e.x);g" +"[h]=mix(p.x,p.y,e.y);}return g;}$pure half4 perlin_noise_shader(float2 a,float2" +" b,float2 c,int d,int e,int f,sampler2D g,sampler2D h){half2 k=half2(floor(" +"a)*b);half4 l=half4(0.);half2 m=half2(c);half n=1.;for(int o=0;o<e;++o){half4" +" p=noise_helper(k,m,f,g);half4 q=noise_function(k,p,h);if(d!=0){q=abs(q);}q" +"*=n;l+=q;k*=half2(2.);n*=.5;m*=half2(2.);}if(d==0){l=l*half4(.5)+half4(.5);" +"}l=saturate(l);return half4(l.xyz*l.www,l.w);}$pure half4 sk_blend(int a,half4" +" b,half4 c){switch(a){case 0:{return blend_clear(b,c);}case 1:{return blend_src" +"(b,c);}case 2:{return blend_dst(b,c);}case 3:{return blend_porter_duff(half4" +"(1.,0.,0.,-1.),b,c);}case 4:{return blend_porter_duff(half4(0.,1.,-1.,0.),b" +",c);}case 5:{return blend_porter_duff(half4(0.,0.,1.,0.),b,c);}case 6:{return" +" blend_porter_duff(half4(0.,0.,0.,1.),b,c);}case 7:{return blend_porter_duff" +"(half4(0.,0.,-1.,0.),b,c);}case 8:{return blend_porter_duff(half4(0.,0.,0.," +"-1.),b,c);}case 9:{return blend_porter_duff(half4(0.,0.,1.,-1.),b,c);}case 10" +":{return blend_porter_duff(half4(0.,0.,-1.,1.),b,c);}case 11:{return blend_porter_duff" +"(half4(0.,0.,-1.,-1.),b,c);}case 12:{return blend_porter_duff(half4(1.,1.,0." +",0.),b,c);}case 13:{return blend_modulate(b,c);}case 14:{return blend_screen" +"(b,c);}case 15:{return blend_overlay(0.,b,c);}case 16:{return blend_darken(" +"1.,b,c);}case 17:{return blend_darken(-1.,b,c);}case 18:{return blend_color_dodge" +"(b,c);}case 19:{return blend_color_burn(b,c);}case 20:{return blend_overlay" +"(1.,b,c);}case 21:{return blend_soft_light(b,c);}case 22:{return blend_difference" +"(b,c);}case 23:{return blend_exclusion(b,c);}case 24:{return blend_multiply" +"(b,c);}case 25:{return blend_hslc(half2(0.,1.),b,c);}case 26:{return blend_hslc" +"(half2(1.),b,c);}case 27:{return blend_hslc(half2(0.),b,c);}case 28:{return" +" blend_hslc(half2(1.,0.),b,c);}default:return half4(0.);}}$pure half4 sk_blend_shader" +"(int a,half4 b,half4 c){return sk_blend(a,c,b);}$pure half4 porter_duff_blend_shader" +"(half4 a,half4 b,half4 c){return blend_porter_duff(a,c,b);}$pure half4 sk_blend_colorfilter" +"(half4 a,int b,float4 c){return sk_blend(b,half4(c),a);}$pure half4 sk_table_colorfilter" +"(half4 a,sampler2D b){half4 c=(unpremul(a)*255.)*.00390625+.001953125;half4" +" d=half4(sample(b,float2(half2(c.x,.375))).x,sample(b,float2(half2(c.y,.625" +"))).x,sample(b,float2(half2(c.z,.875))).x,1.);return d*sample(b,float2(half2" +"(c.w,.125))).x;}$pure half4 sk_gaussian_colorfilter(half4 a){half b=1.-a.w;" +"b=exp((-b*b)*4.)-.018;return half4(b);}$pure float inverse_grad_len(float2 a" +",float2x2 b){float2 c=a*b;return inversesqrt(dot(c,c));}$pure float2 elliptical_distance" +"(float2 a,float2 b,float c,float2x2 d){float2 e=1./(b*b+c*c);float2 g=e*a;float" +" h=inverse_grad_len(g,d);float i=(.5*h)*(dot(a,g)-1.);float j=((b.x*c)*e.x)" +"*h;return float2(j-i,j+i);}void corner_distance(inout float2 a,float2x2 b,float2" +" c,float2 d,float2 e,float2 f){float2 g=f-d;if(g.x>0.&&g.y>0.){if(f.x>0.&&f" +".y>0.||c.x>0.&&c.y<0.){float2 h=elliptical_distance(g*e,f,c.x,b);if(f.x-c.x" +"<=0.){h.y=1.;}else{h.y*=-1.;}a=min(a,h);}else if(c.y==0.){float h=((c.x-g.x" +")-g.y)*inverse_grad_len(e,b);a.x=min(a.x,h);}}}void corner_distances(inout float2" +" a,float2x2 b,float2 c,float4 e,float4 f,float4 g){corner_distance(a,b,c,e." +"xy,float2(-1.),float2(f.x,g.x));corner_distance(a,b,c,e.zy,float2(1.,-1.),float2" +"(f.y,g.y));corner_distance(a,b,c,e.zw,float2(1.),float2(f.z,g.z));corner_distance" +"(a,b,c,e.xw,float2(-1.,1.),float2(f.w,g.w));}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl new file mode 100644 index 0000000000..3be44de02c --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_frag.unoptimized.sksl @@ -0,0 +1,314 @@ +static constexpr char SKSL_MINIFIED_sksl_graphite_frag[] = +"const int $kTileModeClamp=0;const int $kTileModeRepeat=1;const int $kTileModeMirror" +"=2;const int $kTileModeDecal=3;const int $kReadSwizzleNormalRGBA=0;const int" +" $kReadSwizzleRGB1=1;const int $kReadSwizzleRRRR=2;const int $kReadSwizzleRRR1" +"=3;const int $kReadSwizzleBGRA=4;const int $kFilterModeNearest=0;const int $kFilterModeLinear" +"=1;const int $kTFTypeSRGB=1;const int $kTFTypePQ=2;const int $kTFTypeHLG=3;" +"const int $kTFTypeHLGinv=4;const int $kColorSpaceXformFlagUnpremul=1;const int" +" $kColorSpaceXformFlagLinearize=2;const int $kColorSpaceXformFlagGamutTransform" +"=4;const int $kColorSpaceXformFlagEncode=8;const int $kColorSpaceXformFlagPremul" +"=16;$pure half4 sk_error(){return half4(1.,0.,1.,1.);}$pure half4 sk_passthrough" +"(half4 color){return color;}$pure half4 sk_solid_shader(float4 colorParam){" +"return half4(colorParam);}$pure half4 $apply_swizzle(int swizzleType,half4 color" +"){half4 resultantColor=color;switch(swizzleType){case 0:break;case 1:resultantColor" +"=half4(color.xyz,1.);break;case 2:resultantColor=color.xxxx;break;case 3:resultantColor" +"=half4(color.xxx,1.);break;case 4:resultantColor=color.zyxw;break;}return resultantColor" +";}$pure half $apply_xfer_fn(int kind,half x,half[7]cs){half G=cs[0];half A=" +"cs[1];half B=cs[2];half C=cs[3];half D=cs[4];half E=cs[5];half F=cs[6];half" +" s=sign(x);x=abs(x);switch(kind){case 1:x=x<D?C*x+F:pow(A*x+B,G)+E;break;case" +" 2:x=pow(max(A+B*pow(x,C),0.)/(D+E*pow(x,C)),F);break;case 3:x=x*A<=1.?pow(" +"x*A,B):exp((x-E)*C)+D;x*=F+1.;break;case 4:x/=F+1.;x=x<=1.?A*pow(x,B):C*log" +"(x-D)+E;break;}return s*x;}$pure half4 sk_color_space_transform(half4 color" +",int flags,int srcKind,half[7]srcCoeffs,half3x3 gamutTransform,int dstKind," +"half[7]dstCoeffs){if(bool(flags&$kColorSpaceXformFlagUnpremul)){color=unpremul" +"(color);}if(bool(flags&$kColorSpaceXformFlagLinearize)){color.x=$apply_xfer_fn" +"(srcKind,color.x,srcCoeffs);color.y=$apply_xfer_fn(srcKind,color.y,srcCoeffs" +");color.z=$apply_xfer_fn(srcKind,color.z,srcCoeffs);}if(bool(flags&$kColorSpaceXformFlagGamutTransform" +")){color.xyz=gamutTransform*color.xyz;}if(bool(flags&$kColorSpaceXformFlagEncode" +")){color.x=$apply_xfer_fn(dstKind,color.x,dstCoeffs);color.y=$apply_xfer_fn" +"(dstKind,color.y,dstCoeffs);color.z=$apply_xfer_fn(dstKind,color.z,dstCoeffs" +");}if(bool(flags&$kColorSpaceXformFlagPremul)){color.xyz*=color.w;}return color" +";}$pure float $tile(int tileMode,float f,float low,float high){switch(tileMode" +"){case 0:return clamp(f,low,high);case 1:{float length=high-low;return mod(" +"f-low,length)+low;}case 2:{float length=high-low;float length2=2.*length;float" +" tmp=mod(f-low,length2);return mix(tmp,length2-tmp,step(length,tmp))+low;}default" +":return f;}}$pure half4 $sample_image(float2 pos,float2 imgSize,float4 subset" +",int tileModeX,int tileModeY,int filterMode,int readSwizzle,sampler2D s){if" +"(tileModeX==$kTileModeDecal&&filterMode==$kFilterModeNearest){float snappedX" +"=floor(pos.x)+.5;if(snappedX<subset.x||snappedX>subset.z){return half4(0.);" +"}}if(tileModeY==$kTileModeDecal&&filterMode==$kFilterModeNearest){float snappedY" +"=floor(pos.y)+.5;if(snappedY<subset.y||snappedY>subset.w){return half4(0.);" +"}}pos.x=$tile(tileModeX,pos.x,subset.x,subset.z);pos.y=$tile(tileModeY,pos." +"y,subset.y,subset.w);float4 insetClamp;if(filterMode==$kFilterModeNearest){" +"insetClamp=float4(floor(subset.xy)+.5,ceil(subset.zw)-.5);}else{insetClamp=" +"float4(subset.xy+.5,subset.zw-.5);}float2 clampedPos=clamp(pos,insetClamp.xy" +",insetClamp.zw);half4 color=sample(s,clampedPos/imgSize);color=$apply_swizzle" +"(readSwizzle,color);if(filterMode==$kFilterModeLinear){half2 error=half2(pos" +"-clampedPos);half2 absError=abs(error);bool sampleExtraX=tileModeX==$kTileModeRepeat" +";bool sampleExtraY=tileModeY==$kTileModeRepeat;if(sampleExtraX||sampleExtraY" +"){float extraCoordX;float extraCoordY;half4 extraColorX;half4 extraColorY;if" +"(sampleExtraX){extraCoordX=error.x>0.?insetClamp.x:insetClamp.z;extraColorX" +"=sample(s,float2(extraCoordX,clampedPos.y)/imgSize);extraColorX=$apply_swizzle" +"(readSwizzle,extraColorX);}if(sampleExtraY){extraCoordY=error.y>0.?insetClamp" +".y:insetClamp.w;extraColorY=sample(s,float2(clampedPos.x,extraCoordY)/imgSize" +");extraColorY=$apply_swizzle(readSwizzle,extraColorY);}if(sampleExtraX&&sampleExtraY" +"){half4 extraColorXY=sample(s,float2(extraCoordX,extraCoordY)/imgSize);extraColorXY" +"=$apply_swizzle(readSwizzle,extraColorXY);color=mix(mix(color,extraColorX,absError" +".x),mix(extraColorY,extraColorXY,absError.x),absError.y);}else if(sampleExtraX" +"){color=mix(color,extraColorX,absError.x);}else if(sampleExtraY){color=mix(" +"color,extraColorY,absError.y);}}if(tileModeX==$kTileModeDecal){color*=max(1." +"-absError.x,0.);}if(tileModeY==$kTileModeDecal){color*=max(1.-absError.y,0." +");}}return color;}$pure half4 $cubic_filter_image(float2 pos,float2 imgSize" +",float4 subset,int tileModeX,int tileModeY,float4x4 coeffs,int readSwizzle," +"sampler2D s){float2 f=fract(pos-.5);pos-=1.5;pos=floor(pos)+.5;float4 wx=coeffs" +"*float4(1.,f.x,f.x*f.x,(f.x*f.x)*f.x);float4 wy=coeffs*float4(1.,f.y,f.y*f." +"y,(f.y*f.y)*f.y);float4 color=float4(0.);for(int y=0;y<4;++y){float4 rowColor" +"=float4(0.);for(int x=0;x<4;++x){rowColor+=wx[x]*float4($sample_image(pos+float2" +"(float(x),float(y)),imgSize,subset,tileModeX,tileModeY,$kFilterModeNearest," +"readSwizzle,s));}color+=wy[y]*rowColor;}return half4(color);}$pure half4 sk_image_shader" +"(float2 coords,float2 imgSize,float4 subset,int tileModeX,int tileModeY,int" +" filterMode,int useCubic,float4x4 cubicCoeffs,int readSwizzle,int csXformFlags" +",int csXformSrcKind,half[7]csXformSrcCoeffs,half3x3 csXformGamutTransform,int" +" csXformDstKind,half[7]csXformDstCoeffs,sampler2D s){half4 sampleColor=useCubic" +"!=0?$cubic_filter_image(coords,imgSize,subset,tileModeX,tileModeY,cubicCoeffs" +",readSwizzle,s):$sample_image(coords,imgSize,subset,tileModeX,tileModeY,filterMode" +",readSwizzle,s);return sk_color_space_transform(sampleColor,csXformFlags,csXformSrcKind" +",csXformSrcCoeffs,csXformGamutTransform,csXformDstKind,csXformDstCoeffs);}$pure" +" half4 sk_dither_shader(half4 colorIn,float2 coords,float range,sampler2D lut" +"){const float kImgSize=8.;half2 lutCoords=half2(half(coords.x*.125),half(coords" +".y*.125));half value=sample(lut,float2(lutCoords)).x-.5;return half4(half3(" +"clamp(float3(colorIn.xyz)+float(value)*range,0.,float(colorIn.w))),colorIn." +"w);}$pure float2 $tile_grad(int tileMode,float2 t){switch(tileMode){case 0:" +"t.x=clamp(t.x,0.,1.);break;case 1:t.x=fract(t.x);break;case 2:{float t_1=t." +"x-1.;t.x=(t_1-2.*floor(t_1*.5))-1.;if(sk_Caps.mustDoOpBetweenFloorAndAbs){t" +".x=clamp(t.x,-1.,1.);}t.x=abs(t.x);break;}case 3:if(t.x<0.||t.x>1.){return float2" +"(0.,-1.);}break;}return t;}$pure half4 $colorize_grad_4(float4[4]colorsParam" +",float[4]offsetsParam,float2 t){if(t.y<0.){return half4(0.);}else if(t.x<=offsetsParam" +"[0]){return half4(colorsParam[0]);}else if(t.x<offsetsParam[1]){return half4" +"(mix(colorsParam[0],colorsParam[1],(t.x-offsetsParam[0])/(offsetsParam[1]-offsetsParam" +"[0])));}else if(t.x<offsetsParam[2]){return half4(mix(colorsParam[1],colorsParam" +"[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam[1])));}else if(t.x<" +"offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam[3],(t.x-offsetsParam" +"[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4(colorsParam[3])" +";}}$pure half4 $colorize_grad_8(float4[8]colorsParam,float[8]offsetsParam,float2" +" t){if(t.y<0.){return half4(0.);}else if(t.x<offsetsParam[4]){if(t.x<offsetsParam" +"[2]){if(t.x<=offsetsParam[0]){return half4(colorsParam[0]);}else if(t.x<offsetsParam" +"[1]){return half4(mix(colorsParam[0],colorsParam[1],(t.x-offsetsParam[0])/(" +"offsetsParam[1]-offsetsParam[0])));}else{return half4(mix(colorsParam[1],colorsParam" +"[2],(t.x-offsetsParam[1])/(offsetsParam[2]-offsetsParam[1])));}}else{if(t.x" +"<offsetsParam[3]){return half4(mix(colorsParam[2],colorsParam[3],(t.x-offsetsParam" +"[2])/(offsetsParam[3]-offsetsParam[2])));}else{return half4(mix(colorsParam" +"[3],colorsParam[4],(t.x-offsetsParam[3])/(offsetsParam[4]-offsetsParam[3]))" +");}}}else{if(t.x<offsetsParam[6]){if(t.x<offsetsParam[5]){return half4(mix(" +"colorsParam[4],colorsParam[5],(t.x-offsetsParam[4])/(offsetsParam[5]-offsetsParam" +"[4])));}else{return half4(mix(colorsParam[5],colorsParam[6],(t.x-offsetsParam" +"[5])/(offsetsParam[6]-offsetsParam[5])));}}else{if(t.x<offsetsParam[7]){return" +" half4(mix(colorsParam[6],colorsParam[7],(t.x-offsetsParam[6])/(offsetsParam" +"[7]-offsetsParam[6])));}else{return half4(colorsParam[7]);}}}}half4 $colorize_grad_tex" +"(sampler2D colorsAndOffsetsSampler,int numStops,float2 t){const float kColorCoord" +"=.25;const float kOffsetCoord=.75;if(t.y<0.){return half4(0.);}else if(t.x==" +"0.){return sampleLod(colorsAndOffsetsSampler,float2(0.,.25),0.);}else if(t." +"x==1.){return sampleLod(colorsAndOffsetsSampler,float2(1.,.25),0.);}else{int" +" low=0;int high=numStops;for(int loop=1;loop<numStops;loop<<=1){int mid=(low" +"+high)/2;float midFlt=(float(mid)+.5)/float(numStops);float2 tmp=float2(sampleLod" +"(colorsAndOffsetsSampler,float2(midFlt,.75),0.).xy);float offset=ldexp(tmp." +"x,int(tmp.y));if(t.x<offset){high=mid;}else{low=mid;}}float lowFlt=(float(low" +")+.5)/float(numStops);float highFlt=(float(low+1)+.5)/float(numStops);half4" +" color0=sampleLod(colorsAndOffsetsSampler,float2(lowFlt,.25),0.);half4 color1" +"=sampleLod(colorsAndOffsetsSampler,float2(highFlt,.25),0.);float2 tmp=float2" +"(sampleLod(colorsAndOffsetsSampler,float2(lowFlt,.75),0.).xy);float offset0" +"=ldexp(tmp.x,int(tmp.y));tmp=float2(sampleLod(colorsAndOffsetsSampler,float2" +"(highFlt,.75),0.).xy);float offset1=ldexp(tmp.x,int(tmp.y));return half4(mix" +"(float4(color0),float4(color1),(t.x-offset0)/(offset1-offset0)));}}$pure float2" +" $linear_grad_layout(float2 point0Param,float2 point1Param,float2 pos){pos-=" +"point0Param;float2 delta=point1Param-point0Param;float t=dot(pos,delta)/dot" +"(delta,delta);return float2(t,1.);}$pure float2 $radial_grad_layout(float2 centerParam" +",float radiusParam,float2 pos){float t=distance(pos,centerParam)/radiusParam" +";return float2(t,1.);}$pure float2 $sweep_grad_layout(float2 centerParam,float" +" biasParam,float scaleParam,float2 pos){pos-=centerParam;float angle=sk_Caps" +".atan2ImplementedAsAtanYOverX?2.*atan(-pos.y,length(pos)-pos.x):atan(-pos.y" +",-pos.x);float t=((angle*.159154937+.5)+biasParam)*scaleParam;return float2" +"(t,1.);}$pure float3x3 $map_to_unit_x(float2 p0,float2 p1){return float3x3(" +"0.,-1.,0.,1.,0.,0.,0.,0.,1.)*inverse(float3x3(p1.y-p0.y,p0.x-p1.x,0.,p1.x-p0" +".x,p1.y-p0.y,0.,p0.x,p0.y,1.));}$pure float2 $conical_grad_layout(float2 point0Param" +",float2 point1Param,float radius0Param,float radius1Param,float2 pos){const" +" float SK_ScalarNearlyZero=.000244140625;float dCenter=distance(point0Param" +",point1Param);float dRadius=radius1Param-radius0Param;bool radial=dCenter<SK_ScalarNearlyZero" +";bool strip=abs(dRadius)<SK_ScalarNearlyZero;if(radial){if(strip){return float2" +"(0.,-1.);}float scale=1./dRadius;float scaleSign=sign(dRadius);float bias=radius0Param" +"/dRadius;float2 pt=(pos-point0Param)*scale;float t=length(pt)*scaleSign-bias" +";return float2(t,1.);}else if(strip){float3x3 transform=$map_to_unit_x(point0Param" +",point1Param);float r=radius0Param/dCenter;float r_2=r*r;float2 pt=(transform" +"*float3(pos,1.)).xy;float t=r_2-pt.y*pt.y;if(t<0.){return float2(0.,-1.);}t" +"=pt.x+sqrt(t);return float2(t,1.);}else{float f=radius0Param/(radius0Param-" +"radius1Param);bool isSwapped=abs(f-1.)<SK_ScalarNearlyZero;if(isSwapped){float2" +" tmpPt=point0Param;point0Param=point1Param;point1Param=tmpPt;f=0.;}float2 Cf" +"=point0Param*(1.-f)+point1Param*f;float3x3 transform=$map_to_unit_x(Cf,point1Param" +");float scaleX=abs(1.-f);float scaleY=scaleX;float r1=abs(radius1Param-radius0Param" +")/dCenter;bool isFocalOnCircle=abs(r1-1.)<SK_ScalarNearlyZero;if(isFocalOnCircle" +"){scaleX*=.5;scaleY*=.5;}else{scaleX*=r1/(r1*r1-1.);scaleY/=sqrt(abs(r1*r1-" +"1.));}transform=float3x3(scaleX,0.,0.,0.,scaleY,0.,0.,0.,1.)*transform;float2" +" pt=(transform*float3(pos,1.)).xy;float invR1=1./r1;float dRadiusSign=sign(" +"1.-f);bool isWellBehaved=!isFocalOnCircle&&r1>1.;float x_t=-1.;if(isFocalOnCircle" +"){x_t=dot(pt,pt)/pt.x;}else if(isWellBehaved){x_t=length(pt)-pt.x*invR1;}else" +"{float temp=pt.x*pt.x-pt.y*pt.y;if(temp>=0.){if(isSwapped||dRadiusSign<0.){" +"x_t=-sqrt(temp)-pt.x*invR1;}else{x_t=sqrt(temp)-pt.x*invR1;}}}if(!isWellBehaved" +"&&x_t<0.){return float2(0.,-1.);}float t=f+dRadiusSign*x_t;if(isSwapped){t=" +"1.-t;}return float2(t,1.);}}$pure half4 sk_linear_grad_4_shader(float2 coords" +",float4[4]colorsParam,float[4]offsetsParam,float2 point0Param,float2 point1Param" +",int tileMode,int colorSpace,int doUnpremul){float2 t=$linear_grad_layout(point0Param" +",point1Param,coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_4(" +"colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul(color,colorSpace" +",doUnpremul);}$pure half4 sk_linear_grad_8_shader(float2 coords,float4[8]colorsParam" +",float[8]offsetsParam,float2 point0Param,float2 point1Param,int tileMode,int" +" colorSpace,int doUnpremul){float2 t=$linear_grad_layout(point0Param,point1Param" +",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_8(colorsParam," +"offsetsParam,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul" +");}$pure half4 sk_linear_grad_tex_shader(float2 coords,float2 point0Param,float2" +" point1Param,int numStops,int tileMode,int colorSpace,int doUnpremul,sampler2D" +" colorAndOffsetSampler){float2 t=$linear_grad_layout(point0Param,point1Param" +",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_tex(colorAndOffsetSampler" +",numStops,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul" +");}$pure half4 sk_radial_grad_4_shader(float2 coords,float4[4]colorsParam,float" +"[4]offsetsParam,float2 centerParam,float radiusParam,int tileMode,int colorSpace" +",int doUnpremul){float2 t=$radial_grad_layout(centerParam,radiusParam,coords" +");t=$tile_grad(tileMode,t);half4 color=$colorize_grad_4(colorsParam,offsetsParam" +",t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul);}$pure" +" half4 sk_radial_grad_8_shader(float2 coords,float4[8]colorsParam,float[8]offsetsParam" +",float2 centerParam,float radiusParam,int tileMode,int colorSpace,int doUnpremul" +"){float2 t=$radial_grad_layout(centerParam,radiusParam,coords);t=$tile_grad" +"(tileMode,t);half4 color=$colorize_grad_8(colorsParam,offsetsParam,t);return" +" $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul);}$pure half4 sk_radial_grad_tex_shader" +"(float2 coords,float2 centerParam,float radiusParam,int numStops,int tileMode" +",int colorSpace,int doUnpremul,sampler2D colorAndOffsetSampler){float2 t=$radial_grad_layout" +"(centerParam,radiusParam,coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_tex" +"(colorAndOffsetSampler,numStops,t);return $interpolated_to_rgb_unpremul(color" +",colorSpace,doUnpremul);}$pure half4 sk_sweep_grad_4_shader(float2 coords,float4" +"[4]colorsParam,float[4]offsetsParam,float2 centerParam,float biasParam,float" +" scaleParam,int tileMode,int colorSpace,int doUnpremul){float2 t=$sweep_grad_layout" +"(centerParam,biasParam,scaleParam,coords);t=$tile_grad(tileMode,t);half4 color" +"=$colorize_grad_4(colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul" +"(color,colorSpace,doUnpremul);}$pure half4 sk_sweep_grad_8_shader(float2 coords" +",float4[8]colorsParam,float[8]offsetsParam,float2 centerParam,float biasParam" +",float scaleParam,int tileMode,int colorSpace,int doUnpremul){float2 t=$sweep_grad_layout" +"(centerParam,biasParam,scaleParam,coords);t=$tile_grad(tileMode,t);half4 color" +"=$colorize_grad_8(colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul" +"(color,colorSpace,doUnpremul);}$pure half4 sk_sweep_grad_tex_shader(float2 coords" +",float2 centerParam,float biasParam,float scaleParam,int numStops,int tileMode" +",int colorSpace,int doUnpremul,sampler2D colorAndOffsetSampler){float2 t=$sweep_grad_layout" +"(centerParam,biasParam,scaleParam,coords);t=$tile_grad(tileMode,t);half4 color" +"=$colorize_grad_tex(colorAndOffsetSampler,numStops,t);return $interpolated_to_rgb_unpremul" +"(color,colorSpace,doUnpremul);}$pure half4 sk_conical_grad_4_shader(float2 coords" +",float4[4]colorsParam,float[4]offsetsParam,float2 point0Param,float2 point1Param" +",float radius0Param,float radius1Param,int tileMode,int colorSpace,int doUnpremul" +"){float2 t=$conical_grad_layout(point0Param,point1Param,radius0Param,radius1Param" +",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_4(colorsParam," +"offsetsParam,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul" +");}$pure half4 sk_conical_grad_8_shader(float2 coords,float4[8]colorsParam," +"float[8]offsetsParam,float2 point0Param,float2 point1Param,float radius0Param" +",float radius1Param,int tileMode,int colorSpace,int doUnpremul){float2 t=$conical_grad_layout" +"(point0Param,point1Param,radius0Param,radius1Param,coords);t=$tile_grad(tileMode" +",t);half4 color=$colorize_grad_8(colorsParam,offsetsParam,t);return $interpolated_to_rgb_unpremul" +"(color,colorSpace,doUnpremul);}$pure half4 sk_conical_grad_tex_shader(float2" +" coords,float2 point0Param,float2 point1Param,float radius0Param,float radius1Param" +",int numStops,int tileMode,int colorSpace,int doUnpremul,sampler2D colorAndOffsetSampler" +"){float2 t=$conical_grad_layout(point0Param,point1Param,radius0Param,radius1Param" +",coords);t=$tile_grad(tileMode,t);half4 color=$colorize_grad_tex(colorAndOffsetSampler" +",numStops,t);return $interpolated_to_rgb_unpremul(color,colorSpace,doUnpremul" +");}$pure half4 sk_matrix_colorfilter(half4 colorIn,float4x4 m,float4 v,int inHSLA" +"){if(bool(inHSLA)){colorIn=$rgb_to_hsl(colorIn.xyz,colorIn.w);}else{colorIn" +"=unpremul(colorIn);}half4 colorOut=half4(m*float4(colorIn)+v);if(bool(inHSLA" +")){colorOut=$hsl_to_rgb(colorOut.xyz,colorOut.w);}else{colorOut=saturate(colorOut" +");colorOut.xyz*=colorOut.w;}return colorOut;}$pure half4 noise_helper(half2" +" noiseVec,half2 stitchData,int stitching,sampler2D permutationSampler){const" +" half kBlockSize=256.;half4 floorVal;floorVal.xy=floor(noiseVec);floorVal.zw" +"=floorVal.xy+half2(1.);if(bool(stitching)){if(floorVal.x>=stitchData.x){floorVal" +".x-=stitchData.x;}{}if(floorVal.y>=stitchData.y){floorVal.y-=stitchData.y;}" +"{}if(floorVal.z>=stitchData.x){floorVal.z-=stitchData.x;}{}if(floorVal.w>=stitchData" +".y){floorVal.w-=stitchData.y;}{}}half sampleX=sample(permutationSampler,float2" +"(half2(floorVal.x*.00390625,.5))).x;half sampleY=sample(permutationSampler," +"float2(half2(floorVal.z*.00390625,.5))).x;half2 latticeIdx=half2(sampleX,sampleY" +");const half kInv255=.003921569;latticeIdx=floor(latticeIdx*half2(255.)+half2" +"(.5))*half2(.003921569);half4 noiseXCoords=kBlockSize*latticeIdx.xyxy+floorVal" +".yyww;noiseXCoords*=half4(.00390625);return noiseXCoords;}$pure half4 noise_function" +"(half2 noiseVec,half4 noiseXCoords,sampler2D noiseSampler){half2 fractVal=fract" +"(noiseVec);half2 noiseSmooth=(fractVal*fractVal)*(half2(3.)-2.*fractVal);const" +" half kInv256=.00390625;half4 result;for(int channel=0;channel<4;channel++)" +"{half chanCoord=(half(channel)+.5)*.25;half4 sampleA=sample(noiseSampler,float2" +"(half2(noiseXCoords.x,chanCoord)));half4 sampleB=sample(noiseSampler,float2" +"(half2(noiseXCoords.y,chanCoord)));half4 sampleC=sample(noiseSampler,float2" +"(half2(noiseXCoords.w,chanCoord)));half4 sampleD=sample(noiseSampler,float2" +"(half2(noiseXCoords.z,chanCoord)));half2 uv;half2 tmpFractVal=fractVal;uv.x" +"=dot((sampleA.yw+sampleA.xz*kInv256)*2.-half2(1.),tmpFractVal);tmpFractVal." +"x-=1.;uv.y=dot((sampleB.yw+sampleB.xz*kInv256)*2.-half2(1.),tmpFractVal);half2" +" ab;ab.x=mix(uv.x,uv.y,noiseSmooth.x);tmpFractVal.y-=1.;uv.y=dot((sampleC.yw" +"+sampleC.xz*kInv256)*2.-half2(1.),tmpFractVal);tmpFractVal.x+=1.;uv.x=dot((" +"sampleD.yw+sampleD.xz*kInv256)*2.-half2(1.),tmpFractVal);ab.y=mix(uv.x,uv.y" +",noiseSmooth.x);result[channel]=mix(ab.x,ab.y,noiseSmooth.y);}return result" +";}$pure half4 perlin_noise_shader(float2 coords,float2 baseFrequency,float2" +" stitchDataIn,int noiseType,int numOctaves,int stitching,sampler2D permutationSampler" +",sampler2D noiseSampler){const int kFractalNoise_Type=0;const int kTurbulence_Type" +"=1;half2 noiseVec=half2(floor(coords)*baseFrequency);half4 color=half4(0.);" +"half2 stitchData=half2(stitchDataIn);half ratio=1.;for(int octave=0;octave<" +"numOctaves;++octave){half4 noiseXCoords=noise_helper(noiseVec,stitchData,stitching" +",permutationSampler);half4 tmp=noise_function(noiseVec,noiseXCoords,noiseSampler" +");if(noiseType!=kFractalNoise_Type){tmp=abs(tmp);}tmp*=ratio;color+=tmp;noiseVec" +"*=half2(2.);ratio*=.5;stitchData*=half2(2.);}if(noiseType==kFractalNoise_Type" +"){color=color*half4(.5)+half4(.5);}color=saturate(color);return half4(color" +".xyz*color.www,color.w);}$pure half4 sk_blend(int blendMode,half4 src,half4" +" dst){const int kClear=0;const int kSrc=1;const int kDst=2;const int kSrcOver" +"=3;const int kDstOver=4;const int kSrcIn=5;const int kDstIn=6;const int kSrcOut" +"=7;const int kDstOut=8;const int kSrcATop=9;const int kDstATop=10;const int" +" kXor=11;const int kPlus=12;const int kModulate=13;const int kScreen=14;const" +" int kOverlay=15;const int kDarken=16;const int kLighten=17;const int kColorDodge" +"=18;const int kColorBurn=19;const int kHardLight=20;const int kSoftLight=21" +";const int kDifference=22;const int kExclusion=23;const int kMultiply=24;const" +" int kHue=25;const int kSaturation=26;const int kColor=27;const int kLuminosity" +"=28;switch(blendMode){case 0:{return blend_clear(src,dst);}case 1:{return blend_src" +"(src,dst);}case 2:{return blend_dst(src,dst);}case 3:{return blend_porter_duff" +"(half4(1.,0.,0.,-1.),src,dst);}case 4:{return blend_porter_duff(half4(0.,1." +",-1.,0.),src,dst);}case 5:{return blend_porter_duff(half4(0.,0.,1.,0.),src," +"dst);}case 6:{return blend_porter_duff(half4(0.,0.,0.,1.),src,dst);}case 7:" +"{return blend_porter_duff(half4(0.,0.,-1.,0.),src,dst);}case 8:{return blend_porter_duff" +"(half4(0.,0.,0.,-1.),src,dst);}case 9:{return blend_porter_duff(half4(0.,0." +",1.,-1.),src,dst);}case 10:{return blend_porter_duff(half4(0.,0.,-1.,1.),src" +",dst);}case 11:{return blend_porter_duff(half4(0.,0.,-1.,-1.),src,dst);}case" +" 12:{return blend_porter_duff(half4(1.,1.,0.,0.),src,dst);}case 13:{return blend_modulate" +"(src,dst);}case 14:{return blend_screen(src,dst);}case 15:{return blend_overlay" +"(0.,src,dst);}case 16:{return blend_darken(1.,src,dst);}case 17:{return blend_darken" +"(-1.,src,dst);}case 18:{return blend_color_dodge(src,dst);}case 19:{return blend_color_burn" +"(src,dst);}case 20:{return blend_overlay(1.,src,dst);}case 21:{return blend_soft_light" +"(src,dst);}case 22:{return blend_difference(src,dst);}case 23:{return blend_exclusion" +"(src,dst);}case 24:{return blend_multiply(src,dst);}case 25:{return blend_hslc" +"(half2(0.,1.),src,dst);}case 26:{return blend_hslc(half2(1.),src,dst);}case" +" 27:{return blend_hslc(half2(0.),src,dst);}case 28:{return blend_hslc(half2" +"(1.,0.),src,dst);}default:return half4(0.);}}$pure half4 sk_blend_shader(int" +" blendMode,half4 dst,half4 src){return sk_blend(blendMode,src,dst);}$pure half4" +" porter_duff_blend_shader(half4 blendOp,half4 dst,half4 src){return blend_porter_duff" +"(blendOp,src,dst);}$pure half4 sk_blend_colorfilter(half4 dstColor,int blendMode" +",float4 srcColor){return sk_blend(blendMode,half4(srcColor),dstColor);}$pure" +" half4 sk_table_colorfilter(half4 inColor,sampler2D s){half4 coords=(unpremul" +"(inColor)*255.)*.00390625+.001953125;half4 color=half4(sample(s,float2(half2" +"(coords.x,.375))).x,sample(s,float2(half2(coords.y,.625))).x,sample(s,float2" +"(half2(coords.z,.875))).x,1.);return color*sample(s,float2(half2(coords.w,.125" +"))).x;}$pure half4 sk_gaussian_colorfilter(half4 inColor){half factor=1.-inColor" +".w;factor=exp((-factor*factor)*4.)-.018;return half4(factor);}$pure float inverse_grad_len" +"(float2 localGrad,float2x2 jacobian){float2 devGrad=localGrad*jacobian;return" +" inversesqrt(dot(devGrad,devGrad));}$pure float2 elliptical_distance(float2" +" uv,float2 radii,float strokeRadius,float2x2 jacobian){float2 invR2=1./(radii" +"*radii+strokeRadius*strokeRadius);float2 normUV=invR2*uv;float invGradLength" +"=inverse_grad_len(normUV,jacobian);float f=(.5*invGradLength)*(dot(uv,normUV" +")-1.);float width=((radii.x*strokeRadius)*invR2.x)*invGradLength;return float2" +"(width-f,width+f);}void corner_distance(inout float2 dist,float2x2 jacobian" +",float2 strokeParams,float2 cornerEdgeDist,float2 xyFlip,float2 radii){float2" +" uv=radii-cornerEdgeDist;if(uv.x>0.&&uv.y>0.){if(radii.x>0.&&radii.y>0.||strokeParams" +".x>0.&&strokeParams.y<0.){float2 d=elliptical_distance(uv*xyFlip,radii,strokeParams" +".x,jacobian);if(radii.x-strokeParams.x<=0.){d.y=1.;}else{d.y*=-1.;}dist=min" +"(dist,d);}else if(strokeParams.y==0.){float bevelDist=((strokeParams.x-uv.x" +")-uv.y)*inverse_grad_len(xyFlip,jacobian);dist.x=min(dist.x,bevelDist);}}}void" +" corner_distances(inout float2 d,float2x2 J,float2 stroke,float4 edgeDists," +"float4 xRadii,float4 yRadii){corner_distance(d,J,stroke,edgeDists.xy,float2" +"(-1.),float2(xRadii.x,yRadii.x));corner_distance(d,J,stroke,edgeDists.zy,float2" +"(1.,-1.),float2(xRadii.y,yRadii.y));corner_distance(d,J,stroke,edgeDists.zw" +",float2(1.),float2(xRadii.z,yRadii.z));corner_distance(d,J,stroke,edgeDists" +".xw,float2(-1.,1.),float2(xRadii.w,yRadii.w));}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.minified.sksl new file mode 100644 index 0000000000..99bded4db6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.minified.sksl @@ -0,0 +1,64 @@ +static constexpr char SKSL_MINIFIED_sksl_graphite_vert[] = +"$pure float curve_type_using_inf_support(float4 a){if(isinf(a.z)){return 2." +";}if(isinf(a.w)){return 1.;}return 0.;}$pure bool $k(float a){return a!=0.;" +"}$pure bool $l(float a){return a==2.;}$pure float $m(float2 a,float2 b,float2" +" c,float2 d,float2x2 e){float2 f=e*(fma(float2(-2.),b,c)+a);float2 g=e*(fma" +"(float2(-2.),c,d)+b);return max(dot(f,f),dot(g,g));}$pure float $n(float2 a" +",float2 b,float2 c,float2 d,float2x2 e){float f=$m(a,b,c,d,e);return max(ceil" +"(sqrt(3.*sqrt(f))),1.);}$pure float $o(float2 a,float2 b,float2 c,float2 d," +"float2x2 e){float f=$m(a,b,c,d,e);return ceil(log2(max(9.*f,1.))*.25);}$pure" +" float $p(float2 a,float2 b,float2 c,float d){float2 e=(min(min(a,b),c)+max" +"(max(a,b),c))*.5;a-=e;b-=e;c-=e;float f=sqrt(max(max(dot(a,a),dot(b,b)),dot" +"(c,c)));float2 g=fma(float2(-2.*d),b,a)+c;float h=abs(fma(-2.,d,2.));float i" +"=max(0.,fma(f,4.,-1.));float j=length(g)*4.+i*h;float k=4.*min(d,1.);return" +" j/k;}$pure float $q(float2 a,float2 b,float2 c,float d){float e=$p(a,b,c,d" +");return max(ceil(sqrt(e)),1.);}$pure float $r(float2 a,float2 b,float2 c,float" +" d){float e=$p(a,b,c,d);return ceil(log2(max(e,1.))*.5);}$pure float2 $s(float2" +" c,float2 d){float2 e=c-d;if(e==float2(0.)){return float2(0.);}else{float f" +"=1./max(abs(e.x),abs(e.y));return normalize(f*e);}}$pure float $t(float2 c," +"float2 d){return clamp(dot(c,d),-1.,1.);}$pure float $u(float a,float b){float" +" c=fma(a,.5,.5);return(c*b)*b>=1.?inversesqrt(c):sqrt(c);}$pure float $v(float" +" a){return.5/acos(max(1.-.25/a,-1.));}$pure float $w(float c,float d,float e" +"){return fma(d-c,e,c);}$pure float2 $w(float2 c,float2 d,float e){return fma" +"(d-c,float2(e),c);}$pure float4 $w(float4 c,float4 d,float4 e){return fma(d" +"-c,e,c);}$pure float2 tessellate_filled_curve(float2x2 a,float b,float c,float4" +" d,float4 e,float f){float2 g;if($l(f)){g=b!=0.?d.zw:(c!=0.?e.xy:d.xy);}else" +"{float2 h=d.xy;float2 i=d.zw;float2 j=e.xy;float2 k=e.zw;float l=-1.;float m" +";if($k(f)){l=k.x;m=$r(a*h,a*i,a*j,l);i*=l;k=j;}else{m=$o(h,i,j,k,a);}if(b>m" +"){c=floor(ldexp(c,int(m-b)));b=m;}float n=floor(.5+ldexp(c,int(5.-b)));if(0." +"<n&&n<32.){float o=n*.03125;float2 p=mix(h,i,o);float2 q=mix(i,j,o);float2 r" +"=mix(j,k,o);float2 s=mix(p,q,o);float2 t=mix(q,r,o);float2 x=mix(s,t,o);float" +" y=mix(1.,l,o);float z=(l+1.)-y;float A=mix(y,z,o);g=l<0.?x:s/A;}else{g=n==" +"0.?h:k;}}return g;}$pure float4 tessellate_stroked_curve(float a,float b,float2x2" +" c,float2 d,float e,float4 f,float4 g,float2 h,float2 i,float j){float2 k=f" +".xy;float2 l=f.zw;float2 m=g.xy;float2 n=g.zw;float o=-1.;if($k(j)){o=n.x;n" +"=m;}float p;if(o<0.){if(k==l&&m==n){p=1.;}else{p=$n(k,l,m,n,c);}}else{p=$q(" +"c*k,c*l,c*m,o);}float q=i.x;float r=i.y;bool s=i.x==0.;float t;if(s){t=$v(1." +");q=.5;}else{t=$v(e*i.x);}if(s){k=c*k;l=c*l;m=c*m;n=c*n;h=c*h;}float2 u=$s(" +"k==l?(l==m?n:m):l,k);float2 v=$s(n,n==m?(m==l?k:l):m);if(u==float2(0.)){u=float2" +"(1.,0.);v=float2(-1.,0.);}float x;if(r>=0.){x=(sign(r)+1.)+2.;}else{float2 y" +"=$s(k,h);float z=acos($t(y,u));float A=max(ceil(z*t),1.);x=A+2.;x=min(x,b-2." +");}float y=cross_length_2d(m-k,n-l);float z=abs(a)-x;if(z<0.){v=u;if(h!=k){" +"u=$s(k,h);}y=cross_length_2d(u,v);}float A=$t(u,v);float B=acos(A);if(y<0.)" +"{B=-B;}float C;float D=sign(a);if(z<0.){C=x-2.;p=1.;n=(m=(l=k));z+=C+1.;float" +" E=.01;bool F=abs(y)*inversesqrt(dot(u,u)*dot(v,v))<E;if(!F||dot(u,v)<0.){if" +"(z>=0.){D=y<0.?min(D,0.):max(D,0.);}}z=max(z,0.);}else{float E=(b-x)-1.;C=max" +"(ceil(abs(B)*t),1.);C=min(C,E);p=min(p,(E-C)+1.);}float E=B/C;float F=(p+C)" +"-1.;bool G=z>=F;if(z>F){D=0.;}if(abs(a)==2.&&r>0.){D*=$u(A,r);}float2 H;float2" +" I;if(z!=0.&&!G){float2 J;float2 K;float2 L=l-k;float2 M=n-k;if(o>=0.){L*=o" +";K=.5*M-L;J=(o-1.)*M;l*=o;}else{float2 N=m-l;K=N-L;J=fma(float2(-3.),N,M);}" +"float2 N=K*(p*2.);float2 O=L*(p*p);float P=0.;float Q=min(p-1.,z);float R=-" +"abs(E);float S=(1.+z)*abs(E);for(int U=4;U>=0;--U){float V=P+exp2(float(U))" +";if(V<=Q){float2 W=fma(float2(V),J,N);W=fma(float2(V),W,O);float X=dot(normalize" +"(W),u);float Y=fma(V,R,S);Y=min(Y,3.14159274);if(X>=cos(Y)){P=V;}}}float U=" +"P/p;float V=z-P;float W=acos(clamp(u.x,-1.,1.));W=u.y>=0.?W:-W;float X=fma(" +"V,E,W);H=float2(cos(X),sin(X));float2 Y=float2(-H.y,H.x);float Z=dot(Y,J);float" +" aa=dot(Y,K);float ac=dot(Y,L);float ad=max(aa*aa-Z*ac,0.);float ae=sqrt(ad" +");if(aa>0.){ae=-ae;}ae-=aa;float af=(-.5*ae)*Z;float2 ag=abs(fma(ae,ae,af))" +"<abs(fma(Z,ac,af))?float2(ae,Z):float2(ac,ae);float ah=ag.y!=0.?ag.x/ag.y:0." +";ah=clamp(ah,0.,1.);if(V==0.){ah=0.;}float ai=max(U,ah);float2 aj=$w(k,l,ai" +");float2 ak=$w(l,m,ai);float2 al=$w(m,n,ai);float2 am=$w(aj,ak,ai);float2 an" +"=$w(ak,al,ai);float2 ao=$w(am,an,ai);float ap=$w(1.,o,ai);float aq=(o+1.)-ap" +";float ar=$w(ap,aq,ai);if(ai!=ah){H=o>=0.?$s(ak*ap,aj*aq):$s(an,am);}I=o>=0." +"?am/ar:ao;}else{H=z==0.?u:v;I=z==0.?k:n;}float2 J=float2(H.y,-H.x);I+=J*(q*" +"D);if(s){return float4(I+d,inverse(c)*I);}else{return float4(c*I+d,I);}}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.unoptimized.sksl new file mode 100644 index 0000000000..e2b4f80fc5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_graphite_vert.unoptimized.sksl @@ -0,0 +1,121 @@ +static constexpr char SKSL_MINIFIED_sksl_graphite_vert[] = +"const float $PI=3.14159274;const float $kCubicCurveType=0.;const float $kConicCurveType" +"=1.;const float $kTriangularConicCurveType=2.;$pure float curve_type_using_inf_support" +"(float4 p23){if(isinf(p23.z)){return $kTriangularConicCurveType;}if(isinf(p23" +".w)){return $kConicCurveType;}return $kCubicCurveType;}$pure bool $is_conic_curve" +"(float curveType){return curveType!=$kCubicCurveType;}$pure bool $is_triangular_conic_curve" +"(float curveType){return curveType==$kTriangularConicCurveType;}const float" +" $kDegree=3.;const float $kPrecision=4.;const float $kLengthTerm=3.;const float" +" $kLengthTermPow2=9.;$pure float $wangs_formula_max_fdiff_p2(float2 p0,float2" +" p1,float2 p2,float2 p3,float2x2 matrix){float2 d0=matrix*(fma(float2(-2.)," +"p1,p2)+p0);float2 d1=matrix*(fma(float2(-2.),p2,p3)+p1);return max(dot(d0,d0" +"),dot(d1,d1));}$pure float $wangs_formula_cubic(float2 p0,float2 p1,float2 p2" +",float2 p3,float2x2 matrix){float m=$wangs_formula_max_fdiff_p2(p0,p1,p2,p3" +",matrix);return max(ceil(sqrt($kLengthTerm*sqrt(m))),1.);}$pure float $wangs_formula_cubic_log2" +"(float2 p0,float2 p1,float2 p2,float2 p3,float2x2 matrix){float m=$wangs_formula_max_fdiff_p2" +"(p0,p1,p2,p3,matrix);return ceil(log2(max($kLengthTermPow2*m,1.))*.25);}$pure" +" float $wangs_formula_conic_p2(float2 p0,float2 p1,float2 p2,float w){float2" +" C=(min(min(p0,p1),p2)+max(max(p0,p1),p2))*.5;p0-=C;p1-=C;p2-=C;float m=sqrt" +"(max(max(dot(p0,p0),dot(p1,p1)),dot(p2,p2)));float2 dp=fma(float2(-2.*w),p1" +",p0)+p2;float dw=abs(fma(-2.,w,2.));float rp_minus_1=max(0.,fma(m,$kPrecision" +",-1.));float numer=length(dp)*$kPrecision+rp_minus_1*dw;float denom=4.*min(" +"w,1.);return numer/denom;}$pure float $wangs_formula_conic(float2 p0,float2" +" p1,float2 p2,float w){float n2=$wangs_formula_conic_p2(p0,p1,p2,w);return max" +"(ceil(sqrt(n2)),1.);}$pure float $wangs_formula_conic_log2(float2 p0,float2" +" p1,float2 p2,float w){float n2=$wangs_formula_conic_p2(p0,p1,p2,w);return ceil" +"(log2(max(n2,1.))*.5);}$pure float2 $robust_normalize_diff(float2 a,float2 b" +"){float2 diff=a-b;if(diff==float2(0.)){return float2(0.);}else{float invMag" +"=1./max(abs(diff.x),abs(diff.y));return normalize(invMag*diff);}}$pure float" +" $cosine_between_unit_vectors(float2 a,float2 b){return clamp(dot(a,b),-1.," +"1.);}$pure float $miter_extent(float cosTheta,float miterLimit){float x=fma" +"(cosTheta,.5,.5);return(x*miterLimit)*miterLimit>=1.?inversesqrt(x):sqrt(x)" +";}$pure float $num_radial_segments_per_radian(float approxDevStrokeRadius){" +"return.5/acos(max(1.-.25/approxDevStrokeRadius,-1.));}$pure float $unchecked_mix" +"(float a,float b,float T){return fma(b-a,T,a);}$pure float2 $unchecked_mix(" +"float2 a,float2 b,float T){return fma(b-a,float2(T),a);}$pure float4 $unchecked_mix" +"(float4 a,float4 b,float4 T){return fma(b-a,T,a);}$pure float2 tessellate_filled_curve" +"(float2x2 vectorXform,float resolveLevel,float idxInResolveLevel,float4 p01" +",float4 p23,float curveType){float2 localcoord;if($is_triangular_conic_curve" +"(curveType)){localcoord=resolveLevel!=0.?p01.zw:(idxInResolveLevel!=0.?p23." +"xy:p01.xy);}else{float2 p0=p01.xy;float2 p1=p01.zw;float2 p2=p23.xy;float2 p3" +"=p23.zw;float w=-1.;float maxResolveLevel;if($is_conic_curve(curveType)){w=" +"p3.x;maxResolveLevel=$wangs_formula_conic_log2(vectorXform*p0,vectorXform*p1" +",vectorXform*p2,w);p1*=w;p3=p2;}else{maxResolveLevel=$wangs_formula_cubic_log2" +"(p0,p1,p2,p3,vectorXform);}if(resolveLevel>maxResolveLevel){idxInResolveLevel" +"=floor(ldexp(idxInResolveLevel,int(maxResolveLevel-resolveLevel)));resolveLevel" +"=maxResolveLevel;}float fixedVertexID=floor(.5+ldexp(idxInResolveLevel,int(" +"5.-resolveLevel)));if(0.<fixedVertexID&&fixedVertexID<32.){float T=fixedVertexID" +"*.03125;float2 ab=mix(p0,p1,T);float2 bc=mix(p1,p2,T);float2 cd=mix(p2,p3,T" +");float2 abc=mix(ab,bc,T);float2 bcd=mix(bc,cd,T);float2 abcd=mix(abc,bcd,T" +");float u=mix(1.,w,T);float v=(w+1.)-u;float uv=mix(u,v,T);localcoord=w<0.?" +"abcd:abc/uv;}else{localcoord=fixedVertexID==0.?p0:p3;}}return localcoord;}$pure" +" float4 tessellate_stroked_curve(float edgeID,float maxEdges,float2x2 affineMatrix" +",float2 translate,float maxScale,float4 p01,float4 p23,float2 lastControlPoint" +",float2 strokeParams,float curveType){float2 p0=p01.xy;float2 p1=p01.zw;float2" +" p2=p23.xy;float2 p3=p23.zw;float w=-1.;if($is_conic_curve(curveType)){w=p3" +".x;p3=p2;}float numParametricSegments;if(w<0.){if(p0==p1&&p2==p3){numParametricSegments" +"=1.;}else{numParametricSegments=$wangs_formula_cubic(p0,p1,p2,p3,affineMatrix" +");}}else{numParametricSegments=$wangs_formula_conic(affineMatrix*p0,affineMatrix" +"*p1,affineMatrix*p2,w);}float strokeRadius=strokeParams.x;float joinType=strokeParams" +".y;bool isHairline=strokeParams.x==0.;float numRadialSegmentsPerRadian;if(isHairline" +"){numRadialSegmentsPerRadian=$num_radial_segments_per_radian(1.);strokeRadius" +"=.5;}else{numRadialSegmentsPerRadian=$num_radial_segments_per_radian(maxScale" +"*strokeParams.x);}if(isHairline){p0=affineMatrix*p0;p1=affineMatrix*p1;p2=affineMatrix" +"*p2;p3=affineMatrix*p3;lastControlPoint=affineMatrix*lastControlPoint;}float2" +" tan0=$robust_normalize_diff(p0==p1?(p1==p2?p3:p2):p1,p0);float2 tan1=$robust_normalize_diff" +"(p3,p3==p2?(p2==p1?p0:p1):p2);if(tan0==float2(0.)){tan0=float2(1.,0.);tan1=" +"float2(-1.,0.);}float numEdgesInJoin;if(joinType>=0.){numEdgesInJoin=(sign(" +"joinType)+1.)+2.;}else{float2 prevTan=$robust_normalize_diff(p0,lastControlPoint" +");float joinRads=acos($cosine_between_unit_vectors(prevTan,tan0));float numRadialSegmentsInJoin" +"=max(ceil(joinRads*numRadialSegmentsPerRadian),1.);numEdgesInJoin=numRadialSegmentsInJoin" +"+2.;numEdgesInJoin=min(numEdgesInJoin,maxEdges-2.);}float turn=cross_length_2d" +"(p2-p0,p3-p1);float combinedEdgeID=abs(edgeID)-numEdgesInJoin;if(combinedEdgeID" +"<0.){tan1=tan0;if(lastControlPoint!=p0){tan0=$robust_normalize_diff(p0,lastControlPoint" +");}turn=cross_length_2d(tan0,tan1);}float cosTheta=$cosine_between_unit_vectors" +"(tan0,tan1);float rotation=acos(cosTheta);if(turn<0.){rotation=-rotation;}float" +" numRadialSegments;float strokeOutset=sign(edgeID);if(combinedEdgeID<0.){numRadialSegments" +"=numEdgesInJoin-2.;numParametricSegments=1.;p3=(p2=(p1=p0));combinedEdgeID+=" +"numRadialSegments+1.;float sinEpsilon=.01;bool tangentsNearlyParallel=abs(turn" +")*inversesqrt(dot(tan0,tan0)*dot(tan1,tan1))<sinEpsilon;if(!tangentsNearlyParallel" +"||dot(tan0,tan1)<0.){if(combinedEdgeID>=0.){strokeOutset=turn<0.?min(strokeOutset" +",0.):max(strokeOutset,0.);}}combinedEdgeID=max(combinedEdgeID,0.);}else{float" +" maxCombinedSegments=(maxEdges-numEdgesInJoin)-1.;numRadialSegments=max(ceil" +"(abs(rotation)*numRadialSegmentsPerRadian),1.);numRadialSegments=min(numRadialSegments" +",maxCombinedSegments);numParametricSegments=min(numParametricSegments,(maxCombinedSegments" +"-numRadialSegments)+1.);}float radsPerSegment=rotation/numRadialSegments;float" +" numCombinedSegments=(numParametricSegments+numRadialSegments)-1.;bool isFinalEdge" +"=combinedEdgeID>=numCombinedSegments;if(combinedEdgeID>numCombinedSegments)" +"{strokeOutset=0.;}if(abs(edgeID)==2.&&joinType>0.){strokeOutset*=$miter_extent" +"(cosTheta,joinType);}float2 tangent;float2 strokeCoord;if(combinedEdgeID!=0." +"&&!isFinalEdge){float2 A;float2 B;float2 C=p1-p0;float2 D=p3-p0;if(w>=0.){C" +"*=w;B=.5*D-C;A=(w-1.)*D;p1*=w;}else{float2 E=p2-p1;B=E-C;A=fma(float2(-3.)," +"E,D);}float2 B_=B*(numParametricSegments*2.);float2 C_=C*(numParametricSegments" +"*numParametricSegments);float lastParametricEdgeID=0.;float maxParametricEdgeID" +"=min(numParametricSegments-1.,combinedEdgeID);float negAbsRadsPerSegment=-abs" +"(radsPerSegment);float maxRotation0=(1.+combinedEdgeID)*abs(radsPerSegment)" +";for(int exp=4;exp>=0;--exp){float testParametricID=lastParametricEdgeID+exp2" +"(float(exp));if(testParametricID<=maxParametricEdgeID){float2 testTan=fma(float2" +"(testParametricID),A,B_);testTan=fma(float2(testParametricID),testTan,C_);float" +" cosRotation=dot(normalize(testTan),tan0);float maxRotation=fma(testParametricID" +",negAbsRadsPerSegment,maxRotation0);maxRotation=min(maxRotation,$PI);if(cosRotation" +">=cos(maxRotation)){lastParametricEdgeID=testParametricID;}}}float parametricT" +"=lastParametricEdgeID/numParametricSegments;float lastRadialEdgeID=combinedEdgeID" +"-lastParametricEdgeID;float angle0=acos(clamp(tan0.x,-1.,1.));angle0=tan0.y" +">=0.?angle0:-angle0;float radialAngle=fma(lastRadialEdgeID,radsPerSegment,angle0" +");tangent=float2(cos(radialAngle),sin(radialAngle));float2 norm=float2(-tangent" +".y,tangent.x);float a=dot(norm,A);float b_over_2=dot(norm,B);float c=dot(norm" +",C);float discr_over_4=max(b_over_2*b_over_2-a*c,0.);float q=sqrt(discr_over_4" +");if(b_over_2>0.){q=-q;}q-=b_over_2;float _5qa=(-.5*q)*a;float2 root=abs(fma" +"(q,q,_5qa))<abs(fma(a,c,_5qa))?float2(q,a):float2(c,q);float radialT=root.y" +"!=0.?root.x/root.y:0.;radialT=clamp(radialT,0.,1.);if(lastRadialEdgeID==0.)" +"{radialT=0.;}float T=max(parametricT,radialT);float2 ab=$unchecked_mix(p0,p1" +",T);float2 bc=$unchecked_mix(p1,p2,T);float2 cd=$unchecked_mix(p2,p3,T);float2" +" abc=$unchecked_mix(ab,bc,T);float2 bcd=$unchecked_mix(bc,cd,T);float2 abcd" +"=$unchecked_mix(abc,bcd,T);float u=$unchecked_mix(1.,w,T);float v=(w+1.)-u;" +"float uv=$unchecked_mix(u,v,T);if(T!=radialT){tangent=w>=0.?$robust_normalize_diff" +"(bc*u,ab*v):$robust_normalize_diff(bcd,abc);}strokeCoord=w>=0.?abc/uv:abcd;" +"}else{tangent=combinedEdgeID==0.?tan0:tan1;strokeCoord=combinedEdgeID==0.?p0" +":p3;}float2 ortho=float2(tangent.y,-tangent.x);strokeCoord+=ortho*(strokeRadius" +"*strokeOutset);if(isHairline){return float4(strokeCoord+translate,inverse(affineMatrix" +")*strokeCoord);}else{return float4(affineMatrix*strokeCoord+translate,strokeCoord" +");}}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_public.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_public.minified.sksl new file mode 100644 index 0000000000..9e07ced8b6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_public.minified.sksl @@ -0,0 +1,4 @@ +static constexpr char SKSL_MINIFIED_sksl_public[] = +"$pure half3 toLinearSrgb(half3);$pure half3 fromLinearSrgb(half3);half4 $eval" +"(float2,shader);half4 $eval(half4,colorFilter);half4 $eval(half4,half4,blender" +");"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_public.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_public.unoptimized.sksl new file mode 100644 index 0000000000..d71770d441 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_public.unoptimized.sksl @@ -0,0 +1,4 @@ +static constexpr char SKSL_MINIFIED_sksl_public[] = +"$pure half3 toLinearSrgb(half3 color);$pure half3 fromLinearSrgb(half3 color" +");half4 $eval(float2 coords,shader s);half4 $eval(half4 color,colorFilter f" +");half4 $eval(half4 src,half4 dst,blender b);"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.minified.sksl new file mode 100644 index 0000000000..70ca8dacc6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.minified.sksl @@ -0,0 +1,2 @@ +static constexpr char SKSL_MINIFIED_sksl_rt_shader[] = +"layout(builtin=15)float4 sk_FragCoord;"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.unoptimized.sksl new file mode 100644 index 0000000000..70ca8dacc6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_rt_shader.unoptimized.sksl @@ -0,0 +1,2 @@ +static constexpr char SKSL_MINIFIED_sksl_rt_shader[] = +"layout(builtin=15)float4 sk_FragCoord;"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_shared.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_shared.minified.sksl new file mode 100644 index 0000000000..7f2b17c64c --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_shared.minified.sksl @@ -0,0 +1,143 @@ +static constexpr char SKSL_MINIFIED_sksl_shared[] = +"$pure $genType radians($genType);$pure $genHType radians($genHType);$pure $genType" +" degrees($genType);$pure $genHType degrees($genHType);$pure $genType sin($genType" +");$pure $genHType sin($genHType);$pure $genType cos($genType);$pure $genHType" +" cos($genHType);$pure $genType tan($genType);$pure $genHType tan($genHType)" +";$pure $genType asin($genType);$pure $genHType asin($genHType);$pure $genType" +" acos($genType);$pure $genHType acos($genHType);$pure $genType atan($genType" +",$genType);$pure $genHType atan($genHType,$genHType);$pure $genType atan($genType" +");$pure $genHType atan($genHType);$es3 $pure $genType sinh($genType);$es3 $pure" +" $genHType sinh($genHType);$es3 $pure $genType cosh($genType);$es3 $pure $genHType" +" cosh($genHType);$es3 $pure $genType tanh($genType);$es3 $pure $genHType tanh" +"($genHType);$es3 $pure $genType asinh($genType);$es3 $pure $genHType asinh(" +"$genHType);$es3 $pure $genType acosh($genType);$es3 $pure $genHType acosh($genHType" +");$es3 $pure $genType atanh($genType);$es3 $pure $genHType atanh($genHType)" +";$pure $genType pow($genType,$genType);$pure $genHType pow($genHType,$genHType" +");$pure $genType exp($genType);$pure $genHType exp($genHType);$pure $genType" +" log($genType);$pure $genHType log($genHType);$pure $genType exp2($genType)" +";$pure $genHType exp2($genHType);$pure $genType log2($genType);$pure $genHType" +" log2($genHType);$pure $genType sqrt($genType);$pure $genHType sqrt($genHType" +");$pure $genType inversesqrt($genType);$pure $genHType inversesqrt($genHType" +");$pure $genType abs($genType);$pure $genHType abs($genHType);$pure $genType" +" sign($genType);$pure $genHType sign($genHType);$pure $genType floor($genType" +");$pure $genHType floor($genHType);$pure $genType ceil($genType);$pure $genHType" +" ceil($genHType);$pure $genType fract($genType);$pure $genHType fract($genHType" +");$pure $genType mod($genType,float);$pure $genType mod($genType,$genType);" +"$pure $genHType mod($genHType,half);$pure $genHType mod($genHType,$genHType" +");$pure $genType min($genType,$genType);$pure $genType min($genType,float);" +"$pure $genHType min($genHType,$genHType);$pure $genHType min($genHType,half" +");$pure $genType max($genType,$genType);$pure $genType max($genType,float);" +"$pure $genHType max($genHType,$genHType);$pure $genHType max($genHType,half" +");$pure $genType clamp($genType,$genType,$genType);$pure $genType clamp($genType" +",float,float);$pure $genHType clamp($genHType,$genHType,$genHType);$pure $genHType" +" clamp($genHType,half,half);$pure $genType saturate($genType);$pure $genHType" +" saturate($genHType);$pure $genType mix($genType,$genType,$genType);$pure $genType" +" mix($genType,$genType,float);$pure $genHType mix($genHType,$genHType,$genHType" +");$pure $genHType mix($genHType,$genHType,half);$pure $genType step($genType" +",$genType);$pure $genType step(float,$genType);$pure $genHType step($genHType" +",$genHType);$pure $genHType step(half,$genHType);$pure $genType smoothstep(" +"$genType,$genType,$genType);$pure $genType smoothstep(float,float,$genType)" +";$pure $genHType smoothstep($genHType,$genHType,$genHType);$pure $genHType smoothstep" +"(half,half,$genHType);$es3 $pure $genIType abs($genIType);$es3 $pure $genIType" +" sign($genIType);$es3 $pure $genIType floatBitsToInt($genType);$es3 $pure $genUType" +" floatBitsToUint($genType);$es3 $pure $genType intBitsToFloat($genIType);$es3" +" $pure $genType uintBitsToFloat($genUType);$es3 $pure $genType trunc($genType" +");$es3 $pure $genHType trunc($genHType);$es3 $pure $genType round($genType)" +";$es3 $pure $genHType round($genHType);$es3 $pure $genType roundEven($genType" +");$es3 $pure $genHType roundEven($genHType);$es3 $pure $genIType min($genIType" +",$genIType);$es3 $pure $genIType min($genIType,int);$es3 $pure $genUType min" +"($genUType,$genUType);$es3 $pure $genUType min($genUType,uint);$es3 $pure $genIType" +" max($genIType,$genIType);$es3 $pure $genIType max($genIType,int);$es3 $pure" +" $genUType max($genUType,$genUType);$es3 $pure $genUType max($genUType,uint" +");$es3 $pure $genIType clamp($genIType,$genIType,$genIType);$es3 $pure $genIType" +" clamp($genIType,int,int);$es3 $pure $genUType clamp($genUType,$genUType,$genUType" +");$es3 $pure $genUType clamp($genUType,uint,uint);$es3 $pure $genType mix($genType" +",$genType,$genBType);$es3 $pure $genHType mix($genHType,$genHType,$genBType" +");$es3 $pure $genBType isnan($genType);$es3 $pure $genBType isnan($genHType" +");$es3 $pure $genBType isinf($genType);$es3 $pure $genBType isinf($genHType" +");$es3 $pure $genType modf($genType,out $genType);$es3 $pure $genHType modf" +"($genHType,out $genHType);$es3 $pure uint packUnorm2x16(float2);$es3 $pure float2" +" unpackUnorm2x16(uint);$pure float length($genType);$pure half length($genHType" +");$pure float distance($genType,$genType);$pure half distance($genHType,$genHType" +");$pure float dot($genType,$genType);$pure half dot($genHType,$genHType);$pure" +" float3 cross(float3,float3);$pure half3 cross(half3,half3);$pure $genType normalize" +"($genType);$pure $genHType normalize($genHType);$pure $genType faceforward(" +"$genType,$genType,$genType);$pure $genHType faceforward($genHType,$genHType" +",$genHType);$pure $genType reflect($genType,$genType);$pure $genHType reflect" +"($genHType,$genHType);$pure $genType refract($genType,$genType,float);$pure" +" $genHType refract($genHType,$genHType,half);$pure $squareMat matrixCompMult" +"($squareMat,$squareMat);$pure $squareHMat matrixCompMult($squareHMat,$squareHMat" +");$es3 $pure $mat matrixCompMult($mat,$mat);$es3 $pure $hmat matrixCompMult" +"($hmat,$hmat);$pure $squareMat inverse($squareMat);$pure $squareHMat inverse" +"($squareHMat);$es3 $pure float determinant($squareMat);$es3 $pure half determinant" +"($squareHMat);$es3 $pure $squareMat transpose($squareMat);$es3 $pure $squareHMat" +" transpose($squareHMat);$es3 $pure float2x3 transpose(float3x2);$es3 $pure half2x3" +" transpose(half3x2);$es3 $pure float2x4 transpose(float4x2);$es3 $pure half2x4" +" transpose(half4x2);$es3 $pure float3x2 transpose(float2x3);$es3 $pure half3x2" +" transpose(half2x3);$es3 $pure float3x4 transpose(float4x3);$es3 $pure half3x4" +" transpose(half4x3);$es3 $pure float4x2 transpose(float2x4);$es3 $pure half4x2" +" transpose(half2x4);$es3 $pure float4x3 transpose(float3x4);$es3 $pure half4x3" +" transpose(half3x4);$es3 $pure $squareMat outerProduct($vec,$vec);$es3 $pure" +" $squareHMat outerProduct($hvec,$hvec);$es3 $pure float2x3 outerProduct(float3" +",float2);$es3 $pure half2x3 outerProduct(half3,half2);$es3 $pure float3x2 outerProduct" +"(float2,float3);$es3 $pure half3x2 outerProduct(half2,half3);$es3 $pure float2x4" +" outerProduct(float4,float2);$es3 $pure half2x4 outerProduct(half4,half2);$es3" +" $pure float4x2 outerProduct(float2,float4);$es3 $pure half4x2 outerProduct" +"(half2,half4);$es3 $pure float3x4 outerProduct(float4,float3);$es3 $pure half3x4" +" outerProduct(half4,half3);$es3 $pure float4x3 outerProduct(float3,float4);" +"$es3 $pure half4x3 outerProduct(half3,half4);$pure $bvec lessThan($vec,$vec" +");$pure $bvec lessThan($hvec,$hvec);$pure $bvec lessThan($ivec,$ivec);$pure" +" $bvec lessThan($svec,$svec);$pure $bvec lessThanEqual($vec,$vec);$pure $bvec" +" lessThanEqual($hvec,$hvec);$pure $bvec lessThanEqual($ivec,$ivec);$pure $bvec" +" lessThanEqual($svec,$svec);$pure $bvec greaterThan($vec,$vec);$pure $bvec greaterThan" +"($hvec,$hvec);$pure $bvec greaterThan($ivec,$ivec);$pure $bvec greaterThan(" +"$svec,$svec);$pure $bvec greaterThanEqual($vec,$vec);$pure $bvec greaterThanEqual" +"($hvec,$hvec);$pure $bvec greaterThanEqual($ivec,$ivec);$pure $bvec greaterThanEqual" +"($svec,$svec);$pure $bvec equal($vec,$vec);$pure $bvec equal($hvec,$hvec);$pure" +" $bvec equal($ivec,$ivec);$pure $bvec equal($svec,$svec);$pure $bvec equal(" +"$bvec,$bvec);$pure $bvec notEqual($vec,$vec);$pure $bvec notEqual($hvec,$hvec" +");$pure $bvec notEqual($ivec,$ivec);$pure $bvec notEqual($svec,$svec);$pure" +" $bvec notEqual($bvec,$bvec);$es3 $pure $bvec lessThan($usvec,$usvec);$es3 $pure" +" $bvec lessThan($uvec,$uvec);$es3 $pure $bvec lessThanEqual($uvec,$uvec);$es3" +" $pure $bvec lessThanEqual($usvec,$usvec);$es3 $pure $bvec greaterThan($uvec" +",$uvec);$es3 $pure $bvec greaterThan($usvec,$usvec);$es3 $pure $bvec greaterThanEqual" +"($uvec,$uvec);$es3 $pure $bvec greaterThanEqual($usvec,$usvec);$es3 $pure $bvec" +" equal($uvec,$uvec);$es3 $pure $bvec equal($usvec,$usvec);$es3 $pure $bvec notEqual" +"($uvec,$uvec);$es3 $pure $bvec notEqual($usvec,$usvec);$pure bool any($bvec" +");$pure bool all($bvec);$pure $bvec not($bvec);$es3 $pure $genType dFdx($genType" +");$es3 $pure $genType dFdy($genType);$es3 $pure $genHType dFdx($genHType);$es3" +" $pure $genHType dFdy($genHType);$es3 $pure $genType fwidth($genType);$es3 $pure" +" $genHType fwidth($genHType);$pure half4 unpremul(half4 a){return half4(a.xyz" +"/max(a.w,.0001),a.w);}$pure float4 unpremul(float4 a){return float4(a.xyz/max" +"(a.w,.0001),a.w);}$pure half4 $unpremul_polar(half4 a){return half4(a.x,a.yz" +"/max(a.w,.0001),a.w);}$pure half4 $rgb_to_hsl(half3 b,half d){half4 e=b.y<b" +".z?half4(b.zy,-1.,.6666667):half4(b.yz,0.,-.333333343);half4 f=b.x<e.x?half4" +"(e.x,b.x,e.yw):half4(b.x,e.x,e.yz);half h=f.x;half i=h-min(f.y,f.z);half j=" +"h-i*.5;half k=abs(f.w+(f.y-f.z)/(i*6.+.0001));half l=i/((d+.0001)-abs(j*2.-" +"d));half m=j/(d+.0001);return half4(k,l,m,d);}$pure half3 $hsl_to_rgb(half3" +" a){half b=(1.-abs(2.*a.z-1.))*a.y;half3 c=a.xxx+half3(0.,.6666667,.333333343" +");half3 d=saturate(abs(fract(c)*6.-3.)-1.);return(d-.5)*b+a.z;}$pure half4 $hsl_to_rgb" +"(half3 b,half c){return saturate(half4($hsl_to_rgb(b)*c,c));}$pure half3 $css_lab_to_xyz" +"(half3 a){half3 d;d.y=(a.x+16.)*.00862069;d.x=a.y*.002+d.y;d.z=d.y-a.z*.005" +";half3 g=pow(d,half3(3.));half3 h=half3(g.x>.008856452?g.x:(116.*d.x-16.)*.00110705639" +",a.x>8.000001?g.y:a.x*.00110705639,g.z>.008856452?g.z:(116.*d.z-16.)*.00110705639" +");return h*half3(.9642956,1.,.825104535);}$pure half3 $a(half3 a){return half3" +"(a.z,a.y*cos(radians(a.x)),a.y*sin(radians(a.x)));}$pure half3 $css_hcl_to_xyz" +"(half3 a){return $css_lab_to_xyz($a(a));}$pure half3 $css_oklab_to_linear_srgb" +"(half3 a){half b=(a.x+.396337777*a.y)+.215803757*a.z;half c=(a.x-.105561346" +"*a.y)-.06385417*a.z;half d=(a.x-.08948418*a.y)-1.29148555*a.z;half e=(b*b)*" +"b;half f=(c*c)*c;half g=(d*d)*d;return half3((4.0767417*e-3.3077116*f)+.230969936" +"*g,(-1.268438*e+2.60975742*f)-.341319382*g,(-.00419608643*e-.7034186*f)+1.70761466" +"*g);}$pure half3 $css_okhcl_to_linear_srgb(half3 a){return $css_oklab_to_linear_srgb" +"($a(a));}$pure half3 $css_hsl_to_srgb(half3 b){b.x=mod(b.x,360.);if(b.x<0.)" +"{b.x+=360.;}b.yz*=.01;half3 c=mod(half3(0.,8.,4.)+b.x*.0333333351,12.);half" +" d=b.y*min(b.z,1.-b.z);return b.z-d*clamp(min(c-3.,9.-c),-1.,1.);}$pure half3" +" $css_hwb_to_srgb(half3 a){a.yz*=.01;if(a.y+a.z>=1.){half b=a.y/(a.y+a.z);return" +" half3(b);}half3 b=$css_hsl_to_srgb(half3(a.x,100.,50.));b*=(1.-a.y)-a.z;b+=" +"a.y;return b;}$pure half4 $interpolated_to_rgb_unpremul(half4 a,int b,int c" +"){if(bool(c)){switch(b){case 2:;case 3:a=unpremul(a);break;case 4:;case 5:;" +"case 7:;case 8:a=$unpremul_polar(a);break;}}switch(b){case 2:{a.xyz=$css_lab_to_xyz" +"(a.xyz);break;}case 3:{a.xyz=$css_oklab_to_linear_srgb(a.xyz);break;}case 4" +":{a.xyz=$css_hcl_to_xyz(a.xyz);break;}case 5:{a.xyz=$css_okhcl_to_linear_srgb" +"(a.xyz);break;}case 7:{a.xyz=$css_hsl_to_srgb(a.xyz);break;}case 8:{a.xyz=$css_hwb_to_srgb" +"(a.xyz);break;}}return a;}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_shared.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_shared.unoptimized.sksl new file mode 100644 index 0000000000..050060ed4b --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_shared.unoptimized.sksl @@ -0,0 +1,163 @@ +static constexpr char SKSL_MINIFIED_sksl_shared[] = +"$pure $genType radians($genType degrees);$pure $genHType radians($genHType degrees" +");$pure $genType degrees($genType radians);$pure $genHType degrees($genHType" +" radians);$pure $genType sin($genType angle);$pure $genHType sin($genHType angle" +");$pure $genType cos($genType angle);$pure $genHType cos($genHType angle);$pure" +" $genType tan($genType angle);$pure $genHType tan($genHType angle);$pure $genType" +" asin($genType x);$pure $genHType asin($genHType x);$pure $genType acos($genType" +" x);$pure $genHType acos($genHType x);$pure $genType atan($genType y,$genType" +" x);$pure $genHType atan($genHType y,$genHType x);$pure $genType atan($genType" +" y_over_x);$pure $genHType atan($genHType y_over_x);$es3 $pure $genType sinh" +"($genType x);$es3 $pure $genHType sinh($genHType x);$es3 $pure $genType cosh" +"($genType x);$es3 $pure $genHType cosh($genHType x);$es3 $pure $genType tanh" +"($genType x);$es3 $pure $genHType tanh($genHType x);$es3 $pure $genType asinh" +"($genType x);$es3 $pure $genHType asinh($genHType x);$es3 $pure $genType acosh" +"($genType x);$es3 $pure $genHType acosh($genHType x);$es3 $pure $genType atanh" +"($genType x);$es3 $pure $genHType atanh($genHType x);$pure $genType pow($genType" +" x,$genType y);$pure $genHType pow($genHType x,$genHType y);$pure $genType exp" +"($genType x);$pure $genHType exp($genHType x);$pure $genType log($genType x" +");$pure $genHType log($genHType x);$pure $genType exp2($genType x);$pure $genHType" +" exp2($genHType x);$pure $genType log2($genType x);$pure $genHType log2($genHType" +" x);$pure $genType sqrt($genType x);$pure $genHType sqrt($genHType x);$pure" +" $genType inversesqrt($genType x);$pure $genHType inversesqrt($genHType x);" +"$pure $genType abs($genType x);$pure $genHType abs($genHType x);$pure $genType" +" sign($genType x);$pure $genHType sign($genHType x);$pure $genType floor($genType" +" x);$pure $genHType floor($genHType x);$pure $genType ceil($genType x);$pure" +" $genHType ceil($genHType x);$pure $genType fract($genType x);$pure $genHType" +" fract($genHType x);$pure $genType mod($genType x,float y);$pure $genType mod" +"($genType x,$genType y);$pure $genHType mod($genHType x,half y);$pure $genHType" +" mod($genHType x,$genHType y);$pure $genType min($genType x,$genType y);$pure" +" $genType min($genType x,float y);$pure $genHType min($genHType x,$genHType" +" y);$pure $genHType min($genHType x,half y);$pure $genType max($genType x,$genType" +" y);$pure $genType max($genType x,float y);$pure $genHType max($genHType x," +"$genHType y);$pure $genHType max($genHType x,half y);$pure $genType clamp($genType" +" x,$genType minVal,$genType maxVal);$pure $genType clamp($genType x,float minVal" +",float maxVal);$pure $genHType clamp($genHType x,$genHType minVal,$genHType" +" maxVal);$pure $genHType clamp($genHType x,half minVal,half maxVal);$pure $genType" +" saturate($genType x);$pure $genHType saturate($genHType x);$pure $genType mix" +"($genType x,$genType y,$genType a);$pure $genType mix($genType x,$genType y" +",float a);$pure $genHType mix($genHType x,$genHType y,$genHType a);$pure $genHType" +" mix($genHType x,$genHType y,half a);$pure $genType step($genType edge,$genType" +" x);$pure $genType step(float edge,$genType x);$pure $genHType step($genHType" +" edge,$genHType x);$pure $genHType step(half edge,$genHType x);$pure $genType" +" smoothstep($genType edge0,$genType edge1,$genType x);$pure $genType smoothstep" +"(float edge0,float edge1,$genType x);$pure $genHType smoothstep($genHType edge0" +",$genHType edge1,$genHType x);$pure $genHType smoothstep(half edge0,half edge1" +",$genHType x);$es3 $pure $genIType abs($genIType x);$es3 $pure $genIType sign" +"($genIType x);$es3 $pure $genIType floatBitsToInt($genType value);$es3 $pure" +" $genUType floatBitsToUint($genType value);$es3 $pure $genType intBitsToFloat" +"($genIType value);$es3 $pure $genType uintBitsToFloat($genUType value);$es3" +" $pure $genType trunc($genType x);$es3 $pure $genHType trunc($genHType x);$es3" +" $pure $genType round($genType x);$es3 $pure $genHType round($genHType x);$es3" +" $pure $genType roundEven($genType x);$es3 $pure $genHType roundEven($genHType" +" x);$es3 $pure $genIType min($genIType x,$genIType y);$es3 $pure $genIType min" +"($genIType x,int y);$es3 $pure $genUType min($genUType x,$genUType y);$es3 $pure" +" $genUType min($genUType x,uint y);$es3 $pure $genIType max($genIType x,$genIType" +" y);$es3 $pure $genIType max($genIType x,int y);$es3 $pure $genUType max($genUType" +" x,$genUType y);$es3 $pure $genUType max($genUType x,uint y);$es3 $pure $genIType" +" clamp($genIType x,$genIType minVal,$genIType maxVal);$es3 $pure $genIType clamp" +"($genIType x,int minVal,int maxVal);$es3 $pure $genUType clamp($genUType x," +"$genUType minVal,$genUType maxVal);$es3 $pure $genUType clamp($genUType x,uint" +" minVal,uint maxVal);$es3 $pure $genType mix($genType x,$genType y,$genBType" +" a);$es3 $pure $genHType mix($genHType x,$genHType y,$genBType a);$es3 $pure" +" $genBType isnan($genType x);$es3 $pure $genBType isnan($genHType x);$es3 $pure" +" $genBType isinf($genType x);$es3 $pure $genBType isinf($genHType x);$es3 $pure" +" $genType modf($genType x,out $genType i);$es3 $pure $genHType modf($genHType" +" x,out $genHType i);$es3 $pure uint packUnorm2x16(float2 v);$es3 $pure float2" +" unpackUnorm2x16(uint p);$pure float length($genType x);$pure half length($genHType" +" x);$pure float distance($genType p0,$genType p1);$pure half distance($genHType" +" p0,$genHType p1);$pure float dot($genType x,$genType y);$pure half dot($genHType" +" x,$genHType y);$pure float3 cross(float3 x,float3 y);$pure half3 cross(half3" +" x,half3 y);$pure $genType normalize($genType x);$pure $genHType normalize(" +"$genHType x);$pure $genType faceforward($genType N,$genType I,$genType Nref" +");$pure $genHType faceforward($genHType N,$genHType I,$genHType Nref);$pure" +" $genType reflect($genType I,$genType N);$pure $genHType reflect($genHType I" +",$genHType N);$pure $genType refract($genType I,$genType N,float eta);$pure" +" $genHType refract($genHType I,$genHType N,half eta);$pure $squareMat matrixCompMult" +"($squareMat x,$squareMat y);$pure $squareHMat matrixCompMult($squareHMat x," +"$squareHMat y);$es3 $pure $mat matrixCompMult($mat x,$mat y);$es3 $pure $hmat" +" matrixCompMult($hmat x,$hmat y);$pure $squareMat inverse($squareMat m);$pure" +" $squareHMat inverse($squareHMat m);$es3 $pure float determinant($squareMat" +" m);$es3 $pure half determinant($squareHMat m);$es3 $pure $squareMat transpose" +"($squareMat m);$es3 $pure $squareHMat transpose($squareHMat m);$es3 $pure float2x3" +" transpose(float3x2 m);$es3 $pure half2x3 transpose(half3x2 m);$es3 $pure float2x4" +" transpose(float4x2 m);$es3 $pure half2x4 transpose(half4x2 m);$es3 $pure float3x2" +" transpose(float2x3 m);$es3 $pure half3x2 transpose(half2x3 m);$es3 $pure float3x4" +" transpose(float4x3 m);$es3 $pure half3x4 transpose(half4x3 m);$es3 $pure float4x2" +" transpose(float2x4 m);$es3 $pure half4x2 transpose(half2x4 m);$es3 $pure float4x3" +" transpose(float3x4 m);$es3 $pure half4x3 transpose(half3x4 m);$es3 $pure $squareMat" +" outerProduct($vec c,$vec r);$es3 $pure $squareHMat outerProduct($hvec c,$hvec" +" r);$es3 $pure float2x3 outerProduct(float3 c,float2 r);$es3 $pure half2x3 outerProduct" +"(half3 c,half2 r);$es3 $pure float3x2 outerProduct(float2 c,float3 r);$es3 $pure" +" half3x2 outerProduct(half2 c,half3 r);$es3 $pure float2x4 outerProduct(float4" +" c,float2 r);$es3 $pure half2x4 outerProduct(half4 c,half2 r);$es3 $pure float4x2" +" outerProduct(float2 c,float4 r);$es3 $pure half4x2 outerProduct(half2 c,half4" +" r);$es3 $pure float3x4 outerProduct(float4 c,float3 r);$es3 $pure half3x4 outerProduct" +"(half4 c,half3 r);$es3 $pure float4x3 outerProduct(float3 c,float4 r);$es3 $pure" +" half4x3 outerProduct(half3 c,half4 r);$pure $bvec lessThan($vec x,$vec y);" +"$pure $bvec lessThan($hvec x,$hvec y);$pure $bvec lessThan($ivec x,$ivec y)" +";$pure $bvec lessThan($svec x,$svec y);$pure $bvec lessThanEqual($vec x,$vec" +" y);$pure $bvec lessThanEqual($hvec x,$hvec y);$pure $bvec lessThanEqual($ivec" +" x,$ivec y);$pure $bvec lessThanEqual($svec x,$svec y);$pure $bvec greaterThan" +"($vec x,$vec y);$pure $bvec greaterThan($hvec x,$hvec y);$pure $bvec greaterThan" +"($ivec x,$ivec y);$pure $bvec greaterThan($svec x,$svec y);$pure $bvec greaterThanEqual" +"($vec x,$vec y);$pure $bvec greaterThanEqual($hvec x,$hvec y);$pure $bvec greaterThanEqual" +"($ivec x,$ivec y);$pure $bvec greaterThanEqual($svec x,$svec y);$pure $bvec" +" equal($vec x,$vec y);$pure $bvec equal($hvec x,$hvec y);$pure $bvec equal(" +"$ivec x,$ivec y);$pure $bvec equal($svec x,$svec y);$pure $bvec equal($bvec" +" x,$bvec y);$pure $bvec notEqual($vec x,$vec y);$pure $bvec notEqual($hvec x" +",$hvec y);$pure $bvec notEqual($ivec x,$ivec y);$pure $bvec notEqual($svec x" +",$svec y);$pure $bvec notEqual($bvec x,$bvec y);$es3 $pure $bvec lessThan($usvec" +" x,$usvec y);$es3 $pure $bvec lessThan($uvec x,$uvec y);$es3 $pure $bvec lessThanEqual" +"($uvec x,$uvec y);$es3 $pure $bvec lessThanEqual($usvec x,$usvec y);$es3 $pure" +" $bvec greaterThan($uvec x,$uvec y);$es3 $pure $bvec greaterThan($usvec x,$usvec" +" y);$es3 $pure $bvec greaterThanEqual($uvec x,$uvec y);$es3 $pure $bvec greaterThanEqual" +"($usvec x,$usvec y);$es3 $pure $bvec equal($uvec x,$uvec y);$es3 $pure $bvec" +" equal($usvec x,$usvec y);$es3 $pure $bvec notEqual($uvec x,$uvec y);$es3 $pure" +" $bvec notEqual($usvec x,$usvec y);$pure bool any($bvec x);$pure bool all($bvec" +" x);$pure $bvec not($bvec x);$es3 $pure $genType dFdx($genType p);$es3 $pure" +" $genType dFdy($genType p);$es3 $pure $genHType dFdx($genHType p);$es3 $pure" +" $genHType dFdy($genHType p);$es3 $pure $genType fwidth($genType p);$es3 $pure" +" $genHType fwidth($genHType p);$pure half4 unpremul(half4 color){return half4" +"(color.xyz/max(color.w,.0001),color.w);}$pure float4 unpremul(float4 color)" +"{return float4(color.xyz/max(color.w,.0001),color.w);}$export $pure half4 $unpremul_polar" +"(half4 color){return half4(color.x,color.yz/max(color.w,.0001),color.w);}$export" +" $pure half4 $rgb_to_hsl(half3 c,half a){half4 p=c.y<c.z?half4(c.zy,-1.,.6666667" +"):half4(c.yz,0.,-.333333343);half4 q=c.x<p.x?half4(p.x,c.x,p.yw):half4(c.x," +"p.x,p.yz);const half kEps=.0001;half pmV=q.x;half pmC=pmV-min(q.y,q.z);half" +" pmL=pmV-pmC*.5;half H=abs(q.w+(q.y-q.z)/(pmC*6.+kEps));half S=pmC/((a+kEps" +")-abs(pmL*2.-a));half L=pmL/(a+kEps);return half4(H,S,L,a);}$export $pure half3" +" $hsl_to_rgb(half3 hsl){half C=(1.-abs(2.*hsl.z-1.))*hsl.y;half3 p=hsl.xxx+" +"half3(0.,.6666667,.333333343);half3 q=saturate(abs(fract(p)*6.-3.)-1.);return" +"(q-.5)*C+hsl.z;}$export $pure half4 $hsl_to_rgb(half3 hsl,half a){return saturate" +"(half4($hsl_to_rgb(hsl)*a,a));}$export $pure half3 $css_lab_to_xyz(half3 lab" +"){const half k=903.2963;const half e=.008856452;half3 f;f.y=(lab.x+16.)*.00862069" +";f.x=lab.y*.002+f.y;f.z=f.y-lab.z*.005;half3 f_cubed=pow(f,half3(3.));half3" +" xyz=half3(f_cubed.x>e?f_cubed.x:(116.*f.x-16.)*.00110705639,lab.x>8.000001" +"?f_cubed.y:lab.x*.00110705639,f_cubed.z>e?f_cubed.z:(116.*f.z-16.)*.00110705639" +");const half3 D50=half3(.9642956,1.,.825104535);return xyz*D50;}$pure half3" +" $css_hcl_to_lab(half3 hcl){return half3(hcl.z,hcl.y*cos(radians(hcl.x)),hcl" +".y*sin(radians(hcl.x)));}$export $pure half3 $css_hcl_to_xyz(half3 hcl){return" +" $css_lab_to_xyz($css_hcl_to_lab(hcl));}$export $pure half3 $css_oklab_to_linear_srgb" +"(half3 oklab){half l_=(oklab.x+.396337777*oklab.y)+.215803757*oklab.z;half m_" +"=(oklab.x-.105561346*oklab.y)-.06385417*oklab.z;half s_=(oklab.x-.08948418*" +"oklab.y)-1.29148555*oklab.z;half l=(l_*l_)*l_;half m=(m_*m_)*m_;half s=(s_*" +"s_)*s_;return half3((4.0767417*l-3.3077116*m)+.230969936*s,(-1.268438*l+2.60975742" +"*m)-.341319382*s,(-.00419608643*l-.7034186*m)+1.70761466*s);}$export $pure half3" +" $css_okhcl_to_linear_srgb(half3 okhcl){return $css_oklab_to_linear_srgb($css_hcl_to_lab" +"(okhcl));}$export $pure half3 $css_hsl_to_srgb(half3 hsl){hsl.x=mod(hsl.x,360." +");if(hsl.x<0.){hsl.x+=360.;}hsl.yz*=.01;half3 k=mod(half3(0.,8.,4.)+hsl.x*.0333333351" +",12.);half a=hsl.y*min(hsl.z,1.-hsl.z);return hsl.z-a*clamp(min(k-3.,9.-k)," +"-1.,1.);}$export $pure half3 $css_hwb_to_srgb(half3 hwb){hwb.yz*=.01;if(hwb" +".y+hwb.z>=1.){half gray=hwb.y/(hwb.y+hwb.z);return half3(gray);}half3 rgb=$css_hsl_to_srgb" +"(half3(hwb.x,100.,50.));rgb*=(1.-hwb.y)-hwb.z;rgb+=hwb.y;return rgb;}$export" +" $pure half4 $interpolated_to_rgb_unpremul(half4 color,int colorSpace,int doUnpremul" +"){const int kDestination=0;const int kSRGBLinear=1;const int kLab=2;const int" +" kOKLab=3;const int kLCH=4;const int kOKLCH=5;const int kSRGB=6;const int kHSL" +"=7;const int kHWB=8;if(bool(doUnpremul)){switch(colorSpace){case 2:;case 3:" +"color=unpremul(color);break;case 4:;case 5:;case 7:;case 8:color=$unpremul_polar" +"(color);break;}}switch(colorSpace){case 2:{color.xyz=$css_lab_to_xyz(color." +"xyz);break;}case 3:{color.xyz=$css_oklab_to_linear_srgb(color.xyz);break;}case" +" 4:{color.xyz=$css_hcl_to_xyz(color.xyz);break;}case 5:{color.xyz=$css_okhcl_to_linear_srgb" +"(color.xyz);break;}case 7:{color.xyz=$css_hsl_to_srgb(color.xyz);break;}case" +" 8:{color.xyz=$css_hwb_to_srgb(color.xyz);break;}}return color;}"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_vert.minified.sksl b/gfx/skia/skia/src/sksl/generated/sksl_vert.minified.sksl new file mode 100644 index 0000000000..ce8fd5f9a0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_vert.minified.sksl @@ -0,0 +1,4 @@ +static constexpr char SKSL_MINIFIED_sksl_vert[] = +"out sk_PerVertex{layout(builtin=0)float4 sk_Position;layout(builtin=1)float" +" sk_PointSize;};layout(builtin=42)in int sk_VertexID;layout(builtin=43)in int" +" sk_InstanceID;"; diff --git a/gfx/skia/skia/src/sksl/generated/sksl_vert.unoptimized.sksl b/gfx/skia/skia/src/sksl/generated/sksl_vert.unoptimized.sksl new file mode 100644 index 0000000000..ce8fd5f9a0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/generated/sksl_vert.unoptimized.sksl @@ -0,0 +1,4 @@ +static constexpr char SKSL_MINIFIED_sksl_vert[] = +"out sk_PerVertex{layout(builtin=0)float4 sk_Position;layout(builtin=1)float" +" sk_PointSize;};layout(builtin=42)in int sk_VertexID;layout(builtin=43)in int" +" sk_InstanceID;"; diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.cpp new file mode 100644 index 0000000000..fb9d544a40 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.cpp @@ -0,0 +1,284 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLBinaryExpression.h" + +#include "include/private/SkSLDefines.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLTernaryExpression.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +namespace SkSL { + +static bool is_low_precision_matrix_vector_multiply(const Expression& left, + const Operator& op, + const Expression& right, + const Type& resultType) { + return !resultType.highPrecision() && + op.kind() == Operator::Kind::STAR && + left.type().isMatrix() && + right.type().isVector() && + left.type().rows() == right.type().columns() && + Analysis::IsTrivialExpression(left) && + Analysis::IsTrivialExpression(right); +} + +static std::unique_ptr<Expression> rewrite_matrix_vector_multiply(const Context& context, + Position pos, + const Expression& left, + const Operator& op, + const Expression& right, + const Type& resultType) { + // Rewrite m33 * v3 as (m[0] * v[0] + m[1] * v[1] + m[2] * v[2]) + std::unique_ptr<Expression> sum; + for (int n = 0; n < left.type().rows(); ++n) { + // Get mat[N] with an index expression. + std::unique_ptr<Expression> matN = IndexExpression::Make( + context, pos, left.clone(), Literal::MakeInt(context, left.fPosition, n)); + // Get vec[N] with a swizzle expression. + std::unique_ptr<Expression> vecN = Swizzle::Make(context, + left.fPosition.rangeThrough(right.fPosition), right.clone(), + ComponentArray{(SkSL::SwizzleComponent::Type)n}); + // Multiply them together. + const Type* matNType = &matN->type(); + std::unique_ptr<Expression> product = + BinaryExpression::Make(context, pos, std::move(matN), op, std::move(vecN), + matNType); + // Sum all the components together. + if (!sum) { + sum = std::move(product); + } else { + sum = BinaryExpression::Make(context, + pos, + std::move(sum), + Operator(Operator::Kind::PLUS), + std::move(product), + matNType); + } + } + + return sum; +} + +std::unique_ptr<Expression> BinaryExpression::Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> left, + Operator op, + std::unique_ptr<Expression> right) { + if (!left || !right) { + return nullptr; + } + const Type* rawLeftType = (left->isIntLiteral() && right->type().isInteger()) + ? &right->type() + : &left->type(); + const Type* rawRightType = (right->isIntLiteral() && left->type().isInteger()) + ? &left->type() + : &right->type(); + + bool isAssignment = op.isAssignment(); + if (isAssignment && + !Analysis::UpdateVariableRefKind(left.get(), + op.kind() != Operator::Kind::EQ + ? VariableReference::RefKind::kReadWrite + : VariableReference::RefKind::kWrite, + context.fErrors)) { + return nullptr; + } + + const Type* leftType; + const Type* rightType; + const Type* resultType; + if (!op.determineBinaryType(context, *rawLeftType, *rawRightType, + &leftType, &rightType, &resultType)) { + context.fErrors->error(pos, "type mismatch: '" + std::string(op.tightOperatorName()) + + "' cannot operate on '" + left->type().displayName() + "', '" + + right->type().displayName() + "'"); + return nullptr; + } + + if (isAssignment && (leftType->componentType().isOpaque() || leftType->isOrContainsAtomic())) { + context.fErrors->error(pos, "assignments to opaque type '" + left->type().displayName() + + "' are not permitted"); + return nullptr; + } + if (context.fConfig->strictES2Mode()) { + if (!op.isAllowedInStrictES2Mode()) { + context.fErrors->error(pos, "operator '" + std::string(op.tightOperatorName()) + + "' is not allowed"); + return nullptr; + } + if (leftType->isOrContainsArray()) { + // Most operators are already rejected on arrays, but GLSL ES 1.0 is very explicit that + // the *only* operator allowed on arrays is subscripting (and the rules against + // assignment, comparison, and even sequence apply to structs containing arrays as well) + context.fErrors->error(pos, "operator '" + std::string(op.tightOperatorName()) + + "' can not operate on arrays (or structs containing arrays)"); + return nullptr; + } + } + + left = leftType->coerceExpression(std::move(left), context); + right = rightType->coerceExpression(std::move(right), context); + if (!left || !right) { + return nullptr; + } + + return BinaryExpression::Make(context, pos, std::move(left), op, std::move(right), resultType); +} + +std::unique_ptr<Expression> BinaryExpression::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> left, + Operator op, + std::unique_ptr<Expression> right) { + // Determine the result type of the binary expression. + const Type* leftType; + const Type* rightType; + const Type* resultType; + SkAssertResult(op.determineBinaryType(context, left->type(), right->type(), + &leftType, &rightType, &resultType)); + + return BinaryExpression::Make(context, pos, std::move(left), op, std::move(right), resultType); +} + +std::unique_ptr<Expression> BinaryExpression::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> left, + Operator op, + std::unique_ptr<Expression> right, + const Type* resultType) { + // We should have detected non-ES2 compliant behavior in Convert. + SkASSERT(!context.fConfig->strictES2Mode() || op.isAllowedInStrictES2Mode()); + SkASSERT(!context.fConfig->strictES2Mode() || !left->type().isOrContainsArray()); + + // We should have detected non-assignable assignment expressions in Convert. + SkASSERT(!op.isAssignment() || Analysis::IsAssignable(*left)); + SkASSERT(!op.isAssignment() || !left->type().componentType().isOpaque()); + + // For simple assignments, detect and report out-of-range literal values. + if (op.kind() == Operator::Kind::EQ) { + left->type().checkForOutOfRangeLiteral(context, *right); + } + + // Perform constant-folding on the expression. + if (std::unique_ptr<Expression> result = ConstantFolder::Simplify(context, pos, *left, + op, *right, *resultType)) { + return result; + } + + if (context.fConfig->fSettings.fOptimize && !context.fConfig->fIsBuiltinCode) { + // When sk_Caps.rewriteMatrixVectorMultiply is set, we rewrite medium-precision + // matrix * vector multiplication as: + // (sk_Caps.rewriteMatrixVectorMultiply ? (mat[0]*vec[0] + ... + mat[N]*vec[N]) + // : mat * vec) + if (is_low_precision_matrix_vector_multiply(*left, op, *right, *resultType)) { + // Look up `sk_Caps.rewriteMatrixVectorMultiply`. + auto caps = Setting::Make(context, pos, &ShaderCaps::fRewriteMatrixVectorMultiply); + + // There are three possible outcomes from Setting::Convert: + // - If the ShaderCaps aren't known (fCaps in the Context is null), we will get back a + // Setting IRNode. In practice, this should happen when compiling a module. + // In this case, we generate a ternary expression which will be optimized away when + // the module code is actually incorporated into a program. + // - If `rewriteMatrixVectorMultiply` is true in our shader caps, we will get back a + // Literal set to true. When this happens, we always return the rewritten expression. + // - If `rewriteMatrixVectorMultiply` is false in our shader caps, we will get back a + // Literal set to false. When this happens, we return the expression as-is. + bool capsBitIsTrue = caps->isBoolLiteral() && caps->as<Literal>().boolValue(); + if (capsBitIsTrue || !caps->isBoolLiteral()) { + // Rewrite the multiplication as a sum of vector-scalar products. + std::unique_ptr<Expression> rewrite = + rewrite_matrix_vector_multiply(context, pos, *left, op, *right, + *resultType); + + // If we know the caps bit is true, return the rewritten expression directly. + if (capsBitIsTrue) { + return rewrite; + } + + // Return a ternary expression: + // sk_Caps.rewriteMatrixVectorMultiply ? (rewrite) : (mat * vec) + return TernaryExpression::Make( + context, + pos, + std::move(caps), + std::move(rewrite), + std::make_unique<BinaryExpression>(pos, std::move(left), op, + std::move(right), resultType)); + } + } + } + + return std::make_unique<BinaryExpression>(pos, std::move(left), op, + std::move(right), resultType); +} + +bool BinaryExpression::CheckRef(const Expression& expr) { + switch (expr.kind()) { + case Expression::Kind::kFieldAccess: + return CheckRef(*expr.as<FieldAccess>().base()); + + case Expression::Kind::kIndex: + return CheckRef(*expr.as<IndexExpression>().base()); + + case Expression::Kind::kSwizzle: + return CheckRef(*expr.as<Swizzle>().base()); + + case Expression::Kind::kTernary: { + const TernaryExpression& t = expr.as<TernaryExpression>(); + return CheckRef(*t.ifTrue()) && CheckRef(*t.ifFalse()); + } + case Expression::Kind::kVariableReference: { + const VariableReference& ref = expr.as<VariableReference>(); + return ref.refKind() == VariableRefKind::kWrite || + ref.refKind() == VariableRefKind::kReadWrite; + } + default: + return false; + } +} + +std::unique_ptr<Expression> BinaryExpression::clone(Position pos) const { + return std::make_unique<BinaryExpression>(pos, + this->left()->clone(), + this->getOperator(), + this->right()->clone(), + &this->type()); +} + +std::string BinaryExpression::description(OperatorPrecedence parentPrecedence) const { + OperatorPrecedence operatorPrecedence = this->getOperator().getBinaryPrecedence(); + bool needsParens = (operatorPrecedence >= parentPrecedence); + return std::string(needsParens ? "(" : "") + + this->left()->description(operatorPrecedence) + + this->getOperator().operatorName() + + this->right()->description(operatorPrecedence) + + std::string(needsParens ? ")" : ""); +} + +VariableReference* BinaryExpression::isAssignmentIntoVariable() { + if (this->getOperator().isAssignment()) { + Analysis::AssignmentInfo assignmentInfo; + if (Analysis::IsAssignable(*this->left(), &assignmentInfo, /*errors=*/nullptr)) { + return assignmentInfo.fAssignedVar; + } + } + return nullptr; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h new file mode 100644 index 0000000000..094af55ef2 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBinaryExpression.h @@ -0,0 +1,112 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BINARYEXPRESSION +#define SKSL_BINARYEXPRESSION + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +class Type; +class VariableReference; + +/** + * A binary operation. + */ +class BinaryExpression final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kBinary; + + BinaryExpression(Position pos, std::unique_ptr<Expression> left, Operator op, + std::unique_ptr<Expression> right, const Type* type) + : INHERITED(pos, kIRNodeKind, type) + , fLeft(std::move(left)) + , fOperator(op) + , fRight(std::move(right)) { + // If we are assigning to a VariableReference, ensure that it is set to Write or ReadWrite. + SkASSERT(!op.isAssignment() || CheckRef(*this->left())); + } + + // Creates a potentially-simplified form of the expression. Determines the result type + // programmatically. Typechecks and coerces input expressions; reports errors via ErrorReporter. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> left, + Operator op, + std::unique_ptr<Expression> right); + + // Creates a potentially-simplified form of the expression. Determines the result type + // programmatically. Asserts if the expressions do not typecheck or are otherwise invalid. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> left, + Operator op, + std::unique_ptr<Expression> right); + + // Creates a potentially-simplified form of the expression. Result type is passed in. + // Asserts if the expressions do not typecheck or are otherwise invalid. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> left, + Operator op, + std::unique_ptr<Expression> right, + const Type* resultType); + + std::unique_ptr<Expression>& left() { + return fLeft; + } + + const std::unique_ptr<Expression>& left() const { + return fLeft; + } + + std::unique_ptr<Expression>& right() { + return fRight; + } + + const std::unique_ptr<Expression>& right() const { + return fRight; + } + + Operator getOperator() const { + return fOperator; + } + + std::unique_ptr<Expression> clone(Position pos) const override; + + std::string description(OperatorPrecedence parentPrecedence) const override; + + /** + * If the expression is an assignment like `a = 1` or `a += sin(b)`, returns the + * VariableReference that will be written to. For other types of expressions, returns null. + * Complex expressions that contain inner assignments, like `(a = b) * 2`, will return null. + */ + VariableReference* isAssignmentIntoVariable(); + +private: + static bool CheckRef(const Expression& expr); + + std::unique_ptr<Expression> fLeft; + Operator fOperator; + std::unique_ptr<Expression> fRight; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBlock.cpp b/gfx/skia/skia/src/sksl/ir/SkSLBlock.cpp new file mode 100644 index 0000000000..24531e2cf3 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBlock.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLBlock.h" + +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#include <type_traits> + +namespace SkSL { + +std::unique_ptr<Statement> Block::Make(Position pos, + StatementArray statements, + Kind kind, + std::shared_ptr<SymbolTable> symbols) { + // We can't simplify away braces or populated symbol tables. + if (kind == Kind::kBracedScope || (symbols && symbols->count())) { + return std::make_unique<Block>(pos, std::move(statements), kind, std::move(symbols)); + } + + // If the Block is completely empty, synthesize a Nop. + if (statements.empty()) { + return Nop::Make(); + } + + if (statements.size() > 1) { + // The statement array contains multiple statements, but some of those might be no-ops. + // If the statement array only contains one real statement, we can return that directly and + // avoid creating an additional Block node. + std::unique_ptr<Statement>* foundStatement = nullptr; + for (std::unique_ptr<Statement>& stmt : statements) { + if (!stmt->isEmpty()) { + if (!foundStatement) { + // We found a single non-empty statement. Remember it and keep looking. + foundStatement = &stmt; + continue; + } + // We found more than one non-empty statement. We actually do need a Block. + return std::make_unique<Block>(pos, std::move(statements), kind, + /*symbols=*/nullptr); + } + } + + // The array wrapped one valid Statement. Avoid allocating a Block by returning it directly. + if (foundStatement) { + return std::move(*foundStatement); + } + + // The statement array contained nothing but empty statements! + // In this case, we don't actually need to allocate a Block. + // We can just return one of those empty statements. Fall through to... + } + + return std::move(statements.front()); +} + +std::unique_ptr<Block> Block::MakeBlock(Position pos, + StatementArray statements, + Kind kind, + std::shared_ptr<SymbolTable> symbols) { + // Nothing to optimize here--eliminating empty statements doesn't actually improve the generated + // code, and we promise to return a Block. + return std::make_unique<Block>(pos, std::move(statements), kind, std::move(symbols)); +} + +std::unique_ptr<Statement> Block::clone() const { + StatementArray cloned; + cloned.reserve_back(this->children().size()); + for (const std::unique_ptr<Statement>& stmt : this->children()) { + cloned.push_back(stmt->clone()); + } + return std::make_unique<Block>(fPosition, + std::move(cloned), + fBlockKind, + SymbolTable::WrapIfBuiltin(this->symbolTable())); +} + +std::string Block::description() const { + std::string result; + + // Write scope markers if this block is a scope, or if the block is empty (since we need to emit + // something here to make the code valid). + bool isScope = this->isScope() || this->isEmpty(); + if (isScope) { + result += "{"; + } + for (const std::unique_ptr<Statement>& stmt : this->children()) { + result += "\n"; + result += stmt->description(); + } + result += isScope ? "\n}\n" : "\n"; + return result; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBlock.h b/gfx/skia/skia/src/sksl/ir/SkSLBlock.h new file mode 100644 index 0000000000..7bdfdf8bf7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBlock.h @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BLOCK +#define SKSL_BLOCK + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLPosition.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class SymbolTable; + +/** + * A block of multiple statements functioning as a single statement. + */ +class Block final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kBlock; + + // "kBracedScope" represents an actual language-level block. Other kinds of block are used to + // pass around multiple statements as if they were a single unit, with no semantic impact. + enum class Kind { + kUnbracedBlock, // Represents a group of statements without curly braces. + kBracedScope, // Represents a language-level Block, with curly braces. + kCompoundStatement, // A block which conceptually represents a single statement, such as + // `int a, b;`. (SkSL represents this internally as two statements: + // `int a; int b;`) Allowed to optimize away to its interior Statement. + // Treated as a single statement by the debugger. + }; + + Block(Position pos, StatementArray statements, + Kind kind = Kind::kBracedScope, const std::shared_ptr<SymbolTable> symbols = nullptr) + : INHERITED(pos, kIRNodeKind) + , fChildren(std::move(statements)) + , fBlockKind(kind) + , fSymbolTable(std::move(symbols)) {} + + // Make is allowed to simplify compound statements. For a single-statement unscoped Block, + // Make can return the Statement as-is. For an empty unscoped Block, Make can return Nop. + static std::unique_ptr<Statement> Make(Position pos, + StatementArray statements, + Kind kind = Kind::kBracedScope, + std::shared_ptr<SymbolTable> symbols = nullptr); + + // MakeBlock always makes a real Block object. This is important because many callers rely on + // Blocks specifically; e.g. a function body must be a scoped Block, nothing else will do. + static std::unique_ptr<Block> MakeBlock(Position pos, + StatementArray statements, + Kind kind = Kind::kBracedScope, + std::shared_ptr<SymbolTable> symbols = nullptr); + + const StatementArray& children() const { + return fChildren; + } + + StatementArray& children() { + return fChildren; + } + + bool isScope() const { + return fBlockKind == Kind::kBracedScope; + } + + Kind blockKind() const { + return fBlockKind; + } + + void setBlockKind(Kind kind) { + fBlockKind = kind; + } + + std::shared_ptr<SymbolTable> symbolTable() const { + return fSymbolTable; + } + + bool isEmpty() const override { + for (const std::unique_ptr<Statement>& stmt : this->children()) { + if (!stmt->isEmpty()) { + return false; + } + } + return true; + } + + std::unique_ptr<Statement> clone() const override; + + std::string description() const override; + +private: + StatementArray fChildren; + Kind fBlockKind; + std::shared_ptr<SymbolTable> fSymbolTable; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h new file mode 100644 index 0000000000..96cb700b14 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLBreakStatement.h @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_BREAKSTATEMENT +#define SKSL_BREAKSTATEMENT + +#include "include/private/SkSLStatement.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * A 'break' statement. + */ +class BreakStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kBreak; + + BreakStatement(Position pos) + : INHERITED(pos, kIRNodeKind) {} + + static std::unique_ptr<Statement> Make(Position pos) { + return std::make_unique<BreakStatement>(pos); + } + + std::unique_ptr<Statement> clone() const override { + return std::make_unique<BreakStatement>(fPosition); + } + + std::string description() const override { + return "break;"; + } + +private: + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLChildCall.cpp b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.cpp new file mode 100644 index 0000000000..e20a577051 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLChildCall.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +namespace SkSL { + +std::unique_ptr<Expression> ChildCall::clone(Position pos) const { + return std::make_unique<ChildCall>(pos, &this->type(), &this->child(), + this->arguments().clone()); +} + +std::string ChildCall::description(OperatorPrecedence) const { + std::string result = std::string(this->child().name()) + ".eval("; + auto separator = SkSL::String::Separator(); + for (const std::unique_ptr<Expression>& arg : this->arguments()) { + result += separator(); + result += arg->description(OperatorPrecedence::kSequence); + } + result += ")"; + return result; +} + +[[maybe_unused]] static bool call_signature_is_valid(const Context& context, + const Variable& child, + const ExpressionArray& arguments) { + const Type* half4 = context.fTypes.fHalf4.get(); + const Type* float2 = context.fTypes.fFloat2.get(); + + auto params = [&]() -> SkSTArray<2, const Type*> { + switch (child.type().typeKind()) { + case Type::TypeKind::kBlender: return { half4, half4 }; + case Type::TypeKind::kColorFilter: return { half4 }; + case Type::TypeKind::kShader: return { float2 }; + default: + SkUNREACHABLE; + } + }(); + + if (params.size() != arguments.size()) { + return false; + } + for (int i = 0; i < arguments.size(); i++) { + if (!arguments[i]->type().matches(*params[i])) { + return false; + } + } + return true; +} + +std::unique_ptr<Expression> ChildCall::Make(const Context& context, + Position pos, + const Type* returnType, + const Variable& child, + ExpressionArray arguments) { + SkASSERT(call_signature_is_valid(context, child, arguments)); + return std::make_unique<ChildCall>(pos, returnType, &child, std::move(arguments)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLChildCall.h b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.h new file mode 100644 index 0000000000..7d48a84d58 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLChildCall.h @@ -0,0 +1,72 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CHILDCALL +#define SKSL_CHILDCALL + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +class Type; +class Variable; +enum class OperatorPrecedence : uint8_t; + +/** + * A call to a child effect object (shader, color filter, or blender). + */ +class ChildCall final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kChildCall; + + ChildCall(Position pos, const Type* type, const Variable* child, ExpressionArray arguments) + : INHERITED(pos, kIRNodeKind, type) + , fChild(*child) + , fArguments(std::move(arguments)) {} + + // Creates the child call; reports errors via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type* returnType, + const Variable& child, + ExpressionArray arguments); + + const Variable& child() const { + return fChild; + } + + ExpressionArray& arguments() { + return fArguments; + } + + const ExpressionArray& arguments() const { + return fArguments; + } + + std::unique_ptr<Expression> clone(Position pos) const override; + + std::string description(OperatorPrecedence) const override; + +private: + const Variable& fChild; + ExpressionArray fArguments; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructor.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.cpp new file mode 100644 index 0000000000..5b505a6584 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 2020 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructor.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLConstructorArray.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorCompoundCast.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" +#include "src/sksl/ir/SkSLConstructorScalarCast.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLConstructorStruct.h" +#include "src/sksl/ir/SkSLType.h" + +#include <vector> + +namespace SkSL { + +static std::unique_ptr<Expression> convert_compound_constructor(const Context& context, + Position pos, + const Type& type, + ExpressionArray args) { + SkASSERT(type.isVector() || type.isMatrix()); + + // The meaning of a compound constructor containing a single argument varies significantly in + // GLSL/SkSL, depending on the argument type. + if (args.size() == 1) { + std::unique_ptr<Expression>& argument = args.front(); + if (type.isVector() && argument->type().isVector() && + argument->type().componentType().matches(type.componentType()) && + argument->type().slotCount() > type.slotCount()) { + // Casting a vector-type into a smaller matching vector-type is a slice in GLSL. + // We don't allow those casts in SkSL; recommend a swizzle instead. + // Only `.xy` and `.xyz` are valid recommendations here, because `.x` would imply a + // scalar(vector) cast, and nothing has more slots than `.xyzw`. + const char* swizzleHint; + switch (type.slotCount()) { + case 2: swizzleHint = "; use '.xy' instead"; break; + case 3: swizzleHint = "; use '.xyz' instead"; break; + default: swizzleHint = ""; SkDEBUGFAIL("unexpected slicing cast"); break; + } + + context.fErrors->error(pos, "'" + argument->type().displayName() + + "' is not a valid parameter to '" + type.displayName() + "' constructor" + + swizzleHint); + return nullptr; + } + + if (argument->type().isScalar()) { + // A constructor containing a single scalar is a splat (for vectors) or diagonal matrix + // (for matrices). It's legal regardless of the scalar's type, so synthesize an explicit + // conversion to the proper type. (This cast is a no-op if it's unnecessary; it can fail + // if we're casting a literal that exceeds the limits of the type.) + std::unique_ptr<Expression> typecast = ConstructorScalarCast::Convert( + context, pos, type.componentType(), std::move(args)); + if (!typecast) { + return nullptr; + } + + // Matrix-from-scalar creates a diagonal matrix; vector-from-scalar creates a splat. + return type.isMatrix() + ? ConstructorDiagonalMatrix::Make(context, pos, type, std::move(typecast)) + : ConstructorSplat::Make(context, pos, type, std::move(typecast)); + } else if (argument->type().isVector()) { + // A vector constructor containing a single vector with the same number of columns is a + // cast (e.g. float3 -> int3). + if (type.isVector() && argument->type().columns() == type.columns()) { + return ConstructorCompoundCast::Make(context, pos, type, std::move(argument)); + } + } else if (argument->type().isMatrix()) { + // A matrix constructor containing a single matrix can be a resize, typecast, or both. + // GLSL lumps these into one category, but internally SkSL keeps them distinct. + if (type.isMatrix()) { + // First, handle type conversion. If the component types differ, synthesize the + // destination type with the argument's rows/columns. (This will be a no-op if it's + // already the right type.) + const Type& typecastType = type.componentType().toCompound( + context, + argument->type().columns(), + argument->type().rows()); + argument = ConstructorCompoundCast::Make(context, pos, typecastType, + std::move(argument)); + + // Casting a matrix type into another matrix type is a resize. + return ConstructorMatrixResize::Make(context, pos, type, + std::move(argument)); + } + + // A vector constructor containing a single matrix can be compound construction if the + // matrix is 2x2 and the vector is 4-slot. + if (type.isVector() && type.columns() == 4 && argument->type().slotCount() == 4) { + // Casting a 2x2 matrix to a vector is a form of compound construction. + // First, reshape the matrix into a 4-slot vector of the same type. + const Type& vectorType = argument->type().componentType().toCompound(context, + /*columns=*/4, + /*rows=*/1); + std::unique_ptr<Expression> vecCtor = + ConstructorCompound::Make(context, pos, vectorType, std::move(args)); + + // Then, add a typecast to the result expression to ensure the types match. + // This will be a no-op if no typecasting is needed. + return ConstructorCompoundCast::Make(context, pos, type, std::move(vecCtor)); + } + } + } + + // For more complex cases, we walk the argument list and fix up the arguments as needed. + int expected = type.rows() * type.columns(); + int actual = 0; + for (std::unique_ptr<Expression>& arg : args) { + if (!arg->type().isScalar() && !arg->type().isVector()) { + context.fErrors->error(pos, "'" + arg->type().displayName() + + "' is not a valid parameter to '" + type.displayName() + "' constructor"); + return nullptr; + } + + // Rely on Constructor::Convert to force this subexpression to the proper type. If it's a + // literal, this will make sure it's the right type of literal. If an expression of matching + // type, the expression will be returned as-is. If it's an expression of mismatched type, + // this adds a cast. + const Type& ctorType = type.componentType().toCompound(context, arg->type().columns(), + /*rows=*/1); + ExpressionArray ctorArg; + ctorArg.push_back(std::move(arg)); + arg = Constructor::Convert(context, pos, ctorType, std::move(ctorArg)); + if (!arg) { + return nullptr; + } + actual += ctorType.columns(); + } + + if (actual != expected) { + context.fErrors->error(pos, "invalid arguments to '" + type.displayName() + + "' constructor (expected " + std::to_string(expected) + + " scalars, but found " + std::to_string(actual) + ")"); + return nullptr; + } + + return ConstructorCompound::Make(context, pos, type, std::move(args)); +} + +std::unique_ptr<Expression> Constructor::Convert(const Context& context, + Position pos, + const Type& type, + ExpressionArray args) { + if (args.size() == 1 && args[0]->type().matches(type) && !type.componentType().isOpaque()) { + // Don't generate redundant casts; if the expression is already of the correct type, just + // return it as-is. + args[0]->fPosition = pos; + return std::move(args[0]); + } + if (type.isScalar()) { + return ConstructorScalarCast::Convert(context, pos, type, std::move(args)); + } + if (type.isVector() || type.isMatrix()) { + return convert_compound_constructor(context, pos, type, std::move(args)); + } + if (type.isArray() && type.columns() > 0) { + return ConstructorArray::Convert(context, pos, type, std::move(args)); + } + if (type.isStruct() && type.fields().size() > 0) { + return ConstructorStruct::Convert(context, pos, type, std::move(args)); + } + + context.fErrors->error(pos, "cannot construct '" + type.displayName() + "'"); + return nullptr; +} + +std::optional<double> AnyConstructor::getConstantValue(int n) const { + SkASSERT(n >= 0 && n < (int)this->type().slotCount()); + for (const std::unique_ptr<Expression>& arg : this->argumentSpan()) { + int argSlots = arg->type().slotCount(); + if (n < argSlots) { + return arg->getConstantValue(n); + } + n -= argSlots; + } + + SkDEBUGFAIL("argument-list slot count doesn't match constructor-type slot count"); + return std::nullopt; +} + +Expression::ComparisonResult AnyConstructor::compareConstant(const Expression& other) const { + SkASSERT(this->type().slotCount() == other.type().slotCount()); + + if (!other.supportsConstantValues()) { + return ComparisonResult::kUnknown; + } + + int exprs = this->type().slotCount(); + for (int n = 0; n < exprs; ++n) { + // Get the n'th subexpression from each side. If either one is null, return "unknown." + std::optional<double> left = this->getConstantValue(n); + if (!left.has_value()) { + return ComparisonResult::kUnknown; + } + std::optional<double> right = other.getConstantValue(n); + if (!right.has_value()) { + return ComparisonResult::kUnknown; + } + // Both sides are known and can be compared for equality directly. + if (*left != *right) { + return ComparisonResult::kNotEqual; + } + } + return ComparisonResult::kEqual; +} + +AnyConstructor& Expression::asAnyConstructor() { + SkASSERT(this->isAnyConstructor()); + return static_cast<AnyConstructor&>(*this); +} + +const AnyConstructor& Expression::asAnyConstructor() const { + SkASSERT(this->isAnyConstructor()); + return static_cast<const AnyConstructor&>(*this); +} + +std::string AnyConstructor::description(OperatorPrecedence) const { + std::string result = this->type().description() + "("; + auto separator = SkSL::String::Separator(); + for (const std::unique_ptr<Expression>& arg : this->argumentSpan()) { + result += separator(); + result += arg->description(OperatorPrecedence::kSequence); + } + result.push_back(')'); + return result; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h new file mode 100644 index 0000000000..7b3616e7bf --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructor.h @@ -0,0 +1,143 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR +#define SKSL_CONSTRUCTOR + +#include "include/core/SkSpan.h" +#include "include/private/SkSLDefines.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +enum class OperatorPrecedence : uint8_t; + +/** + * Base class representing a constructor with unknown arguments. + */ +class AnyConstructor : public Expression { +public: + AnyConstructor(Position pos, Kind kind, const Type* type) + : INHERITED(pos, kind, type) {} + + virtual SkSpan<std::unique_ptr<Expression>> argumentSpan() = 0; + virtual SkSpan<const std::unique_ptr<Expression>> argumentSpan() const = 0; + + std::string description(OperatorPrecedence) const override; + + const Type& componentType() const { + return this->type().componentType(); + } + + bool supportsConstantValues() const override { return true; } + std::optional<double> getConstantValue(int n) const override; + + ComparisonResult compareConstant(const Expression& other) const override; + +private: + using INHERITED = Expression; +}; + +/** + * Base class representing a constructor that takes a single argument. + */ +class SingleArgumentConstructor : public AnyConstructor { +public: + SingleArgumentConstructor(Position pos, Kind kind, const Type* type, + std::unique_ptr<Expression> argument) + : INHERITED(pos, kind, type) + , fArgument(std::move(argument)) {} + + std::unique_ptr<Expression>& argument() { + return fArgument; + } + + const std::unique_ptr<Expression>& argument() const { + return fArgument; + } + + SkSpan<std::unique_ptr<Expression>> argumentSpan() final { + return {&fArgument, 1}; + } + + SkSpan<const std::unique_ptr<Expression>> argumentSpan() const final { + return {&fArgument, 1}; + } + +private: + std::unique_ptr<Expression> fArgument; + + using INHERITED = AnyConstructor; +}; + +/** + * Base class representing a constructor that takes an array of arguments. + */ +class MultiArgumentConstructor : public AnyConstructor { +public: + MultiArgumentConstructor(Position pos, Kind kind, const Type* type, + ExpressionArray arguments) + : INHERITED(pos, kind, type) + , fArguments(std::move(arguments)) {} + + ExpressionArray& arguments() { + return fArguments; + } + + const ExpressionArray& arguments() const { + return fArguments; + } + + SkSpan<std::unique_ptr<Expression>> argumentSpan() final { + return {&fArguments.front(), fArguments.size()}; + } + + SkSpan<const std::unique_ptr<Expression>> argumentSpan() const final { + return {&fArguments.front(), fArguments.size()}; + } + +private: + ExpressionArray fArguments; + + using INHERITED = AnyConstructor; +}; + +/** + * Converts any GLSL constructor, such as `float2(x, y)` or `mat3x3(otherMat)` or `int[2](0, i)`, to + * an SkSL expression. + * + * Vector constructors must always consist of either exactly 1 scalar, or a collection of vectors + * and scalars totaling exactly the right number of scalar components. + * + * Matrix constructors must always consist of either exactly 1 scalar, exactly 1 matrix, or a + * collection of vectors and scalars totaling exactly the right number of scalar components. + * + * Array constructors must always contain the proper number of array elements (matching the Type). + */ +namespace Constructor { + // Creates, typechecks and simplifies constructor expressions. Reports errors via the + // ErrorReporter. This can return null on error, so be careful. There are several different + // Constructor expression types; this class chooses the proper one based on context, e.g. + // `ConstructorCompound`, `ConstructorScalarCast`, or `ConstructorMatrixResize`. + std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + const Type& type, + ExpressionArray args); +} + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.cpp new file mode 100644 index 0000000000..f88098cf3a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorArray.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLString.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLConstructorArrayCast.h" +#include "src/sksl/ir/SkSLType.h" + +#include <algorithm> +#include <string> + +namespace SkSL { + +std::unique_ptr<Expression> ConstructorArray::Convert(const Context& context, + Position pos, + const Type& type, + ExpressionArray args) { + SkASSERTF(type.isArray() && type.columns() > 0, "%s", type.description().c_str()); + + // ES2 doesn't support first-class array types. + if (context.fConfig->strictES2Mode()) { + context.fErrors->error(pos, "construction of array type '" + type.displayName() + + "' is not supported"); + return nullptr; + } + + // An array of atomics cannot be constructed. + if (type.isOrContainsAtomic()) { + context.fErrors->error( + pos, + String::printf("construction of array type '%s' with atomic member is not allowed", + type.displayName().c_str())); + return nullptr; + } + + // If there is a single argument containing an array of matching size and the types are + // coercible, this is actually a cast. i.e., `half[10](myFloat10Array)`. This isn't a GLSL + // feature, but the Pipeline stage code generator needs this functionality so that code which + // was originally compiled with "allow narrowing conversions" enabled can be later recompiled + // without narrowing conversions (we patch over these conversions with an explicit cast). + if (args.size() == 1) { + const Expression& expr = *args.front(); + const Type& exprType = expr.type(); + + if (exprType.isArray() && exprType.canCoerceTo(type, /*allowNarrowing=*/true)) { + return ConstructorArrayCast::Make(context, pos, type, std::move(args.front())); + } + } + + // Check that the number of constructor arguments matches the array size. + if (type.columns() != args.size()) { + context.fErrors->error(pos, String::printf("invalid arguments to '%s' constructor " + "(expected %d elements, but found %d)", type.displayName().c_str(), type.columns(), + args.size())); + return nullptr; + } + + // Convert each constructor argument to the array's component type. + const Type& baseType = type.componentType(); + for (std::unique_ptr<Expression>& argument : args) { + argument = baseType.coerceExpression(std::move(argument), context); + if (!argument) { + return nullptr; + } + } + + return ConstructorArray::Make(context, pos, type, std::move(args)); +} + +std::unique_ptr<Expression> ConstructorArray::Make(const Context& context, + Position pos, + const Type& type, + ExpressionArray args) { + SkASSERT(!context.fConfig->strictES2Mode()); + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(type.columns() == args.size()); + SkASSERT(!type.isOrContainsAtomic()); + SkASSERT(std::all_of(args.begin(), args.end(), [&](const std::unique_ptr<Expression>& arg) { + return type.componentType().matches(arg->type()); + })); + + return std::make_unique<ConstructorArray>(pos, type, std::move(args)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.h new file mode 100644 index 0000000000..8dfdb34db1 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArray.h @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_ARRAY +#define SKSL_CONSTRUCTOR_ARRAY + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <utility> + +namespace SkSL { + +class Context; +class Type; + +/** + * Represents the construction of an array type, such as "float[5](x, y, z, w, 1)". + */ +class ConstructorArray final : public MultiArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorArray; + + ConstructorArray(Position pos, const Type& type, ExpressionArray arguments) + : INHERITED(pos, kIRNodeKind, &type, std::move(arguments)) {} + + // ConstructorArray::Convert will typecheck and create array-constructor expressions. + // Reports errors via the ErrorReporter; returns null on error. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + const Type& type, + ExpressionArray args); + + // ConstructorArray::Make creates array-constructor expressions; errors reported via SkASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + ExpressionArray args); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorArray>(pos, this->type(), this->arguments().clone()); + } + +private: + using INHERITED = MultiArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.cpp new file mode 100644 index 0000000000..a5f9039eb0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorArrayCast.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/ir/SkSLConstructorArray.h" +#include "src/sksl/ir/SkSLConstructorCompoundCast.h" +#include "src/sksl/ir/SkSLConstructorScalarCast.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +static std::unique_ptr<Expression> cast_constant_array(const Context& context, + Position pos, + const Type& destType, + std::unique_ptr<Expression> constCtor) { + const Type& scalarType = destType.componentType(); + + // Create a ConstructorArray(...) which typecasts each argument inside. + auto inputArgs = constCtor->as<ConstructorArray>().argumentSpan(); + ExpressionArray typecastArgs; + typecastArgs.reserve_back(inputArgs.size()); + for (std::unique_ptr<Expression>& arg : inputArgs) { + Position argPos = arg->fPosition; + if (arg->type().isScalar()) { + typecastArgs.push_back(ConstructorScalarCast::Make(context, argPos, scalarType, + std::move(arg))); + } else { + typecastArgs.push_back(ConstructorCompoundCast::Make(context, argPos, scalarType, + std::move(arg))); + } + } + + return ConstructorArray::Make(context, pos, destType, std::move(typecastArgs)); +} + +std::unique_ptr<Expression> ConstructorArrayCast::Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg) { + // Only arrays of the same size are allowed. + SkASSERT(type.isArray()); + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(arg->type().isArray()); + SkASSERT(type.columns() == arg->type().columns()); + + // If this is a no-op cast, return the expression as-is. + if (type.matches(arg->type())) { + arg->fPosition = pos; + return arg; + } + + // Look up the value of constant variables. This allows constant-expressions like `myArray` to + // be replaced with the compile-time constant `int[2](0, 1)`. + arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg)); + + // We can cast a vector of compile-time constants at compile-time. + if (Analysis::IsCompileTimeConstant(*arg)) { + return cast_constant_array(context, pos, type, std::move(arg)); + } + return std::make_unique<ConstructorArrayCast>(pos, type, std::move(arg)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.h new file mode 100644 index 0000000000..7db825142b --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorArrayCast.h @@ -0,0 +1,54 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_ARRAY_CAST +#define SKSL_CONSTRUCTOR_ARRAY_CAST + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <utility> + +namespace SkSL { + +class Context; +class Type; + +/** + * Represents the typecasting of an array. Arrays cannot be directly casted in SkSL (or GLSL), but + * type narrowing can cause an array to be implicitly casted. For instance, the expression + * `myHalf2Array == float[2](a, b)` should be allowed when narrowing conversions are enabled; this + * constructor allows the necessary array-type conversion to be represented in IR. + * + * These always contain exactly 1 array of matching size, and are never constant. + */ +class ConstructorArrayCast final : public SingleArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorArrayCast; + + ConstructorArrayCast(Position pos, const Type& type, std::unique_ptr<Expression> arg) + : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {} + + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorArrayCast>(pos, this->type(), argument()->clone()); + } + +private: + using INHERITED = SingleArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.cpp new file mode 100644 index 0000000000..06bbd8a6d8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.cpp @@ -0,0 +1,158 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorCompound.h" + +#include "include/core/SkTypes.h" +#include "include/private/base/SkTArray.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <algorithm> +#include <cstddef> +#include <numeric> +#include <string> + +namespace SkSL { + +static bool is_safe_to_eliminate(const Type& type, const Expression& arg) { + if (type.isScalar()) { + // A scalar "compound type" with a single scalar argument is a no-op and can be eliminated. + // (Pedantically, this isn't a compound at all, but it's harmless to allow and simplifies + // call sites which need to narrow a vector and may sometimes end up with a scalar.) + SkASSERTF(arg.type().matches(type), "Creating type '%s' from '%s'", + type.description().c_str(), arg.type().description().c_str()); + return true; + } + if (type.isVector() && arg.type().matches(type)) { + // A vector compound constructor containing a single argument of matching type can trivially + // be eliminated. + return true; + } + // This is a meaningful single-argument compound constructor (e.g. vector-from-matrix, + // matrix-from-vector). + return false; +} + +static const Expression* make_splat_from_arguments(const Type& type, const ExpressionArray& args) { + // Splats cannot represent a matrix. + if (type.isMatrix()) { + return nullptr; + } + const Expression* splatExpression = nullptr; + for (int index = 0; index < args.size(); ++index) { + // Arguments must only be scalars or a splat constructors (which can only contain scalars). + const Expression* expr; + if (args[index]->type().isScalar()) { + expr = args[index].get(); + } else if (args[index]->is<ConstructorSplat>()) { + expr = args[index]->as<ConstructorSplat>().argument().get(); + } else { + return nullptr; + } + // On the first iteration, just remember the expression we encountered. + if (index == 0) { + splatExpression = expr; + continue; + } + // On subsequent iterations, ensure that the expression we found matches the first one. + // (Note that IsSameExpressionTree will always reject an Expression with side effects.) + if (!Analysis::IsSameExpressionTree(*expr, *splatExpression)) { + return nullptr; + } + } + + return splatExpression; +} + +std::unique_ptr<Expression> ConstructorCompound::Make(const Context& context, + Position pos, + const Type& type, + ExpressionArray args) { + SkASSERT(type.isAllowedInES2(context)); + + // All the arguments must have matching component type. + SkASSERT(std::all_of(args.begin(), args.end(), [&](const std::unique_ptr<Expression>& arg) { + const Type& argType = arg->type(); + return (argType.isScalar() || argType.isVector() || argType.isMatrix()) && + (argType.componentType().matches(type.componentType())); + })); + + // The slot count of the combined argument list must match the composite type's slot count. + SkASSERT(type.slotCount() == + std::accumulate(args.begin(), args.end(), /*initial value*/ (size_t)0, + [](size_t n, const std::unique_ptr<Expression>& arg) { + return n + arg->type().slotCount(); + })); + // No-op compound constructors (containing a single argument of the same type) are eliminated. + // (Even though this is a "compound constructor," we let scalars pass through here; it's + // harmless to allow and simplifies call sites which need to narrow a vector and may sometimes + // end up with a scalar.) + if (args.size() == 1 && is_safe_to_eliminate(type, *args.front())) { + args.front()->fPosition = pos; + return std::move(args.front()); + } + // Beyond this point, the type must be a vector or matrix. + SkASSERT(type.isVector() || type.isMatrix()); + + if (context.fConfig->fSettings.fOptimize) { + // Find ConstructorCompounds embedded inside other ConstructorCompounds and flatten them. + // - float4(float2(1, 2), 3, 4) --> float4(1, 2, 3, 4) + // - float4(w, float3(sin(x), cos(y), tan(z))) --> float4(w, sin(x), cos(y), tan(z)) + // - mat2(float2(a, b), float2(c, d)) --> mat2(a, b, c, d) + + // See how many fields we would have if composite constructors were flattened out. + int fields = 0; + for (const std::unique_ptr<Expression>& arg : args) { + fields += arg->is<ConstructorCompound>() + ? arg->as<ConstructorCompound>().arguments().size() + : 1; + } + + // If we added up more fields than we're starting with, we found at least one input that can + // be flattened out. + if (fields > args.size()) { + ExpressionArray flattened; + flattened.reserve_back(fields); + for (std::unique_ptr<Expression>& arg : args) { + // For non-ConstructorCompound fields, move them over as-is. + if (!arg->is<ConstructorCompound>()) { + flattened.push_back(std::move(arg)); + continue; + } + // For ConstructorCompound fields, move over their inner arguments individually. + ConstructorCompound& compositeCtor = arg->as<ConstructorCompound>(); + for (std::unique_ptr<Expression>& innerArg : compositeCtor.arguments()) { + flattened.push_back(std::move(innerArg)); + } + } + args = std::move(flattened); + } + } + + // Replace constant variables with their corresponding values, so `float2(one, two)` can + // compile down to `float2(1.0, 2.0)` (the latter is a compile-time constant). + for (std::unique_ptr<Expression>& arg : args) { + arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg)); + } + + if (context.fConfig->fSettings.fOptimize) { + // Reduce compound constructors to splats where possible. + if (const Expression* splat = make_splat_from_arguments(type, args)) { + return ConstructorSplat::Make(context, pos, type, splat->clone()); + } + } + + return std::make_unique<ConstructorCompound>(pos, type, std::move(args)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.h new file mode 100644 index 0000000000..5dfd93f63c --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompound.h @@ -0,0 +1,55 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_COMPOUND +#define SKSL_CONSTRUCTOR_COMPOUND + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <utility> + +namespace SkSL { + +class Context; +class Type; + +/** + * Represents a vector or matrix that is composed from other expressions, such as + * `half3(pos.xy, 1)` or `mat3(a.xyz, b.xyz, 0, 0, 1)` + * + * These can contain a mix of scalars and aggregates. The total number of scalar values inside the + * constructor must always match the type's slot count. (e.g. `pos.xy` consumes two slots.) + * The inner values must have the same component type as the vector/matrix. + */ +class ConstructorCompound final : public MultiArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorCompound; + + ConstructorCompound(Position pos, const Type& type, ExpressionArray args) + : INHERITED(pos, kIRNodeKind, &type, std::move(args)) {} + + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + ExpressionArray args); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorCompound>(pos, this->type(), this->arguments().clone()); + } + +private: + using INHERITED = MultiArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.cpp new file mode 100644 index 0000000000..2e9d9fca4c --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorCompoundCast.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorScalarCast.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstddef> +#include <optional> + +namespace SkSL { + +static std::unique_ptr<Expression> cast_constant_composite(const Context& context, + Position pos, + const Type& destType, + std::unique_ptr<Expression> constCtor) { + const Type& scalarType = destType.componentType(); + + // We generate nicer code for splats and diagonal matrices by handling them separately instead + // of relying on the constant-subexpression code below. This is not truly necessary but it makes + // our output look a little better; human beings prefer `half4(0)` to `half4(0, 0, 0, 0)`. + if (constCtor->is<ConstructorSplat>()) { + // This is a typecast of a splat containing a constant value, e.g. `half4(7)`. We can + // replace it with a splat of a different type, e.g. `int4(7)`. + ConstructorSplat& splat = constCtor->as<ConstructorSplat>(); + return ConstructorSplat::Make( + context, pos, destType, + ConstructorScalarCast::Make(context, pos, scalarType, std::move(splat.argument()))); + } + + if (constCtor->is<ConstructorDiagonalMatrix>() && destType.isMatrix()) { + // This is a typecast of a constant diagonal matrix, e.g. `float3x3(2)`. We can replace it + // with a diagonal matrix of a different type, e.g. `half3x3(2)`. + ConstructorDiagonalMatrix& matrixCtor = constCtor->as<ConstructorDiagonalMatrix>(); + return ConstructorDiagonalMatrix::Make( + context, pos, destType, + ConstructorScalarCast::Make(context, pos, scalarType, + std::move(matrixCtor.argument()))); + } + + // Create a compound Constructor(literal, ...) which typecasts each scalar value inside. + size_t numSlots = destType.slotCount(); + SkASSERT(numSlots == constCtor->type().slotCount()); + + ExpressionArray typecastArgs; + typecastArgs.reserve_back(numSlots); + for (size_t index = 0; index < numSlots; ++index) { + std::optional<double> slotVal = constCtor->getConstantValue(index); + if (scalarType.checkForOutOfRangeLiteral(context, *slotVal, constCtor->fPosition)) { + // We've reported an error because the literal is out of range for this type. Zero out + // the value to avoid a cascade of errors. + *slotVal = 0.0; + } + typecastArgs.push_back(Literal::Make(pos, *slotVal, &scalarType)); + } + + return ConstructorCompound::Make(context, pos, destType, std::move(typecastArgs)); +} + +std::unique_ptr<Expression> ConstructorCompoundCast::Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg) { + // Only vectors or matrices of the same dimensions are allowed. + SkASSERT(type.isVector() || type.isMatrix()); + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(arg->type().isVector() == type.isVector()); + SkASSERT(arg->type().isMatrix() == type.isMatrix()); + SkASSERT(type.columns() == arg->type().columns()); + SkASSERT(type.rows() == arg->type().rows()); + + // If this is a no-op cast, return the expression as-is. + if (type.matches(arg->type())) { + return arg; + } + // Look up the value of constant variables. This allows constant-expressions like + // `int4(colorGreen)` to be replaced with the compile-time constant `int4(0, 1, 0, 1)`. + arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg)); + + // We can cast a vector of compile-time constants at compile-time. + if (Analysis::IsCompileTimeConstant(*arg)) { + return cast_constant_composite(context, pos, type, std::move(arg)); + } + return std::make_unique<ConstructorCompoundCast>(pos, type, std::move(arg)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.h new file mode 100644 index 0000000000..9dc08271d7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorCompoundCast.h @@ -0,0 +1,52 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_COMPOUND_CAST +#define SKSL_CONSTRUCTOR_COMPOUND_CAST + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <utility> + +namespace SkSL { + +class Context; +class Type; + +/** + * Represents the construction of a vector/matrix typecast, such as `half3(myInt3)` or + * `float4x4(myHalf4x4)`. Matrix resizes are done in ConstructorMatrixResize, not here. + * + * These always contain exactly 1 vector or matrix of matching size, and are never constant. + */ +class ConstructorCompoundCast final : public SingleArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorCompoundCast; + + ConstructorCompoundCast(Position pos, const Type& type, std::unique_ptr<Expression> arg) + : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {} + + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorCompoundCast>(pos, this->type(), argument()->clone()); + } + +private: + using INHERITED = SingleArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.cpp new file mode 100644 index 0000000000..e863babfa9 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.cpp @@ -0,0 +1,45 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" + +#include "include/core/SkTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +std::unique_ptr<Expression> ConstructorDiagonalMatrix::Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg) { + SkASSERT(type.isMatrix()); + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(arg->type().isScalar()); + SkASSERT(arg->type().matches(type.componentType())); + + // Look up the value of constant variables. This allows constant-expressions like `mat4(five)` + // to be replaced with `mat4(5.0)`. + arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg)); + + return std::make_unique<ConstructorDiagonalMatrix>(pos, type, std::move(arg)); +} + +std::optional<double> ConstructorDiagonalMatrix::getConstantValue(int n) const { + int rows = this->type().rows(); + int row = n % rows; + int col = n / rows; + + SkASSERT(col >= 0); + SkASSERT(row >= 0); + SkASSERT(col < this->type().columns()); + SkASSERT(row < this->type().rows()); + + return (col == row) ? this->argument()->getConstantValue(0) : 0.0; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.h new file mode 100644 index 0000000000..65342d750c --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorDiagonalMatrix.h @@ -0,0 +1,55 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_DIAGONAL_MATRIX +#define SKSL_CONSTRUCTOR_DIAGONAL_MATRIX + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <optional> +#include <utility> + +namespace SkSL { + +class Context; +class Type; + +/** + * Represents the construction of a diagonal matrix, such as `half3x3(n)`. + * + * These always contain exactly 1 scalar. + */ +class ConstructorDiagonalMatrix final : public SingleArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorDiagonalMatrix; + + ConstructorDiagonalMatrix(Position pos, const Type& type, std::unique_ptr<Expression> arg) + : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {} + + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorDiagonalMatrix>(pos, this->type(), argument()->clone()); + } + + bool supportsConstantValues() const override { return true; } + std::optional<double> getConstantValue(int n) const override; + +private: + using INHERITED = SingleArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.cpp new file mode 100644 index 0000000000..a015666e4b --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorMatrixResize.h" + +#include "include/core/SkTypes.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +std::unique_ptr<Expression> ConstructorMatrixResize::Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg) { + SkASSERT(type.isMatrix()); + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(arg->type().componentType().matches(type.componentType())); + + // If the matrix isn't actually changing size, return it as-is. + if (type.rows() == arg->type().rows() && type.columns() == arg->type().columns()) { + return arg; + } + + return std::make_unique<ConstructorMatrixResize>(pos, type, std::move(arg)); +} + +std::optional<double> ConstructorMatrixResize::getConstantValue(int n) const { + int rows = this->type().rows(); + int row = n % rows; + int col = n / rows; + + SkASSERT(col >= 0); + SkASSERT(row >= 0); + SkASSERT(col < this->type().columns()); + SkASSERT(row < this->type().rows()); + + // GLSL resize matrices are of the form: + // |m m 0| + // |m m 0| + // |0 0 1| + // Where `m` is the matrix being wrapped, and other cells contain the identity matrix. + + // Forward `getConstantValue` to the wrapped matrix if the position is in its bounds. + if (col < this->argument()->type().columns() && row < this->argument()->type().rows()) { + // Recalculate `n` in terms of the inner matrix's dimensions. + n = row + (col * this->argument()->type().rows()); + return this->argument()->getConstantValue(n); + } + + // Synthesize an identity matrix for out-of-bounds positions. + return (col == row) ? 1.0 : 0.0; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.h new file mode 100644 index 0000000000..bf1d3f5897 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorMatrixResize.h @@ -0,0 +1,56 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_MATRIX_RESIZE +#define SKSL_CONSTRUCTOR_MATRIX_RESIZE + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <optional> +#include <utility> + +namespace SkSL { + +class Context; +class Type; + +/** + * Represents the construction of a matrix resize operation, such as `mat4x4(myMat2x2)`. + * + * These always contain exactly 1 matrix of non-matching size. Cells that aren't present in the + * input matrix are populated with the identity matrix. + */ +class ConstructorMatrixResize final : public SingleArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorMatrixResize; + + ConstructorMatrixResize(Position pos, const Type& type, std::unique_ptr<Expression> arg) + : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {} + + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorMatrixResize>(pos, this->type(), argument()->clone()); + } + + bool supportsConstantValues() const override { return true; } + std::optional<double> getConstantValue(int n) const override; + +private: + using INHERITED = SingleArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp new file mode 100644 index 0000000000..7b3074f31c --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.cpp @@ -0,0 +1,93 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorScalarCast.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLType.h" + +#include <string> + +namespace SkSL { + +std::unique_ptr<Expression> ConstructorScalarCast::Convert(const Context& context, + Position pos, + const Type& rawType, + ExpressionArray args) { + // As you might expect, scalar-cast constructors should only be created with scalar types. + const Type& type = rawType.scalarTypeForLiteral(); + SkASSERT(type.isScalar()); + + if (args.size() != 1) { + context.fErrors->error(pos, "invalid arguments to '" + type.displayName() + + "' constructor, (expected exactly 1 argument, but found " + + std::to_string(args.size()) + ")"); + return nullptr; + } + + const Type& argType = args[0]->type(); + if (!argType.isScalar()) { + // Casting a vector-type into its scalar component type is treated as a slice in GLSL. + // We don't allow those casts in SkSL; recommend a .x swizzle instead. + const char* swizzleHint = ""; + if (argType.componentType().matches(type)) { + if (argType.isVector()) { + swizzleHint = "; use '.x' instead"; + } else if (argType.isMatrix()) { + swizzleHint = "; use '[0][0]' instead"; + } + } + + context.fErrors->error(pos, + "'" + argType.displayName() + "' is not a valid parameter to '" + + type.displayName() + "' constructor" + swizzleHint); + return nullptr; + } + if (type.checkForOutOfRangeLiteral(context, *args[0])) { + return nullptr; + } + + return ConstructorScalarCast::Make(context, pos, type, std::move(args[0])); +} + +std::unique_ptr<Expression> ConstructorScalarCast::Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg) { + SkASSERT(type.isScalar()); + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(arg->type().isScalar()); + + // No cast required when the types match. + if (arg->type().matches(type)) { + return arg; + } + // Look up the value of constant variables. This allows constant-expressions like `int(zero)` to + // be replaced with a literal zero. + arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg)); + + // We can cast scalar literals at compile-time when possible. (If the resulting literal would be + // out of range for its type, we report an error and return zero to minimize error cascading. + // This can occur when code is inlined, so we can't necessarily catch it during Convert. As + // such, it's not safe to return null or assert.) + if (arg->is<Literal>()) { + double value = arg->as<Literal>().value(); + if (type.checkForOutOfRangeLiteral(context, value, arg->fPosition)) { + value = 0.0; + } + return Literal::Make(pos, value, &type); + } + return std::make_unique<ConstructorScalarCast>(pos, type, std::move(arg)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.h new file mode 100644 index 0000000000..295d19d959 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorScalarCast.h @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_SCALAR_CAST +#define SKSL_CONSTRUCTOR_SCALAR_CAST + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <utility> + +namespace SkSL { + +class Context; +class ExpressionArray; +class Type; + +/** + * Represents the construction of a scalar cast, such as `float(intVariable)`. + * + * These always contain exactly 1 scalar of a differing type, and are never constant. + */ +class ConstructorScalarCast final : public SingleArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorScalarCast; + + ConstructorScalarCast(Position pos, const Type& type, std::unique_ptr<Expression> arg) + : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {} + + // ConstructorScalarCast::Convert will typecheck and create scalar-constructor expressions. + // Reports errors via the ErrorReporter; returns null on error. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + const Type& rawType, + ExpressionArray args); + + // ConstructorScalarCast::Make casts a scalar expression. Casts that can be evaluated at + // compile-time will do so (e.g. `int(4.1)` --> `Literal(int 4)`). Errors reported via SkASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorScalarCast>(pos, this->type(), argument()->clone()); + } + +private: + using INHERITED = SingleArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.cpp new file mode 100644 index 0000000000..0d5110c279 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorSplat.h" + +#include "src/sksl/SkSLConstantFolder.h" + +namespace SkSL { + +std::unique_ptr<Expression> ConstructorSplat::Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg) { + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(type.isScalar() || type.isVector()); + SkASSERT(arg->type().scalarTypeForLiteral().matches( + type.componentType().scalarTypeForLiteral())); + SkASSERT(arg->type().isScalar()); + + // A "splat" to a scalar type is a no-op and can be eliminated. + if (type.isScalar()) { + arg->fPosition = pos; + return arg; + } + + // Replace constant variables with their corresponding values, so `float3(five)` can compile + // down to `float3(5.0)` (the latter is a compile-time constant). + arg = ConstantFolder::MakeConstantValueForVariable(pos, std::move(arg)); + + SkASSERT(type.isVector()); + return std::make_unique<ConstructorSplat>(pos, type, std::move(arg)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.h new file mode 100644 index 0000000000..4b342d8d7a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorSplat.h @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_SPLAT +#define SKSL_CONSTRUCTOR_SPLAT + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <memory> +#include <optional> +#include <utility> + +namespace SkSL { + +class Context; + +/** + * Represents the construction of a vector splat, such as `half3(n)`. + * + * These always contain exactly 1 scalar. + */ +class ConstructorSplat final : public SingleArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorSplat; + + ConstructorSplat(Position pos, const Type& type, std::unique_ptr<Expression> arg) + : INHERITED(pos, kIRNodeKind, &type, std::move(arg)) {} + + // The input argument must be scalar. A "splat" to a scalar type will be optimized into a no-op. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + std::unique_ptr<Expression> arg); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorSplat>(pos, this->type(), argument()->clone()); + } + + bool supportsConstantValues() const override { + return true; + } + + std::optional<double> getConstantValue(int n) const override { + SkASSERT(n >= 0 && n < this->type().columns()); + return this->argument()->getConstantValue(0); + } + +private: + using INHERITED = SingleArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.cpp b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.cpp new file mode 100644 index 0000000000..d8c42b4abc --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLConstructorStruct.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTArray.h" +#include "include/private/base/SkTo.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLType.h" + +#include <string> +#include <vector> + +namespace SkSL { + +std::unique_ptr<Expression> ConstructorStruct::Convert(const Context& context, + Position pos, + const Type& type, + ExpressionArray args) { + SkASSERTF(type.isStruct() && type.fields().size() > 0, "%s", type.description().c_str()); + + // Check that the number of constructor arguments matches the array size. + if (type.fields().size() != SkToSizeT(args.size())) { + context.fErrors->error(pos, + String::printf("invalid arguments to '%s' constructor " + "(expected %zu elements, but found %d)", + type.displayName().c_str(), type.fields().size(), + args.size())); + return nullptr; + } + + // A struct with atomic members cannot be constructed. + if (type.isOrContainsAtomic()) { + context.fErrors->error( + pos, + String::printf("construction of struct type '%s' with atomic member is not allowed", + type.displayName().c_str())); + return nullptr; + } + + // Convert each constructor argument to the struct's field type. + for (int index=0; index<args.size(); ++index) { + std::unique_ptr<Expression>& argument = args[index]; + const Type::Field& field = type.fields()[index]; + + argument = field.fType->coerceExpression(std::move(argument), context); + if (!argument) { + return nullptr; + } + } + + return ConstructorStruct::Make(context, pos, type, std::move(args)); +} + +[[maybe_unused]] static bool arguments_match_field_types(const ExpressionArray& args, + const Type& type) { + SkASSERT(type.fields().size() == SkToSizeT(args.size())); + + for (int index = 0; index < args.size(); ++index) { + const std::unique_ptr<Expression>& argument = args[index]; + const Type::Field& field = type.fields()[index]; + if (!argument->type().matches(*field.fType)) { + return false; + } + } + + return true; +} + +std::unique_ptr<Expression> ConstructorStruct::Make(const Context& context, + Position pos, + const Type& type, + ExpressionArray args) { + SkASSERT(type.isAllowedInES2(context)); + SkASSERT(arguments_match_field_types(args, type)); + SkASSERT(!type.isOrContainsAtomic()); + return std::make_unique<ConstructorStruct>(pos, type, std::move(args)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.h b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.h new file mode 100644 index 0000000000..dab7c6c67d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLConstructorStruct.h @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONSTRUCTOR_STRUCT +#define SKSL_CONSTRUCTOR_STRUCT + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <utility> + +namespace SkSL { + +class Context; +class Type; + +/** + * Represents the construction of an struct object, such as "Color(red, green, blue, 1)". + */ +class ConstructorStruct final : public MultiArgumentConstructor { +public: + inline static constexpr Kind kIRNodeKind = Kind::kConstructorStruct; + + ConstructorStruct(Position pos, const Type& type, ExpressionArray arguments) + : INHERITED(pos, kIRNodeKind, &type, std::move(arguments)) {} + + // ConstructorStruct::Convert will typecheck and create struct-constructor expressions. + // Reports errors via the ErrorReporter; returns null on error. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + const Type& type, + ExpressionArray args); + + // ConstructorStruct::Make creates struct-constructor expressions; errors reported via SkASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type& type, + ExpressionArray args); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<ConstructorStruct>(pos, this->type(), this->arguments().clone()); + } + +private: + using INHERITED = MultiArgumentConstructor; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h new file mode 100644 index 0000000000..64eadbe53d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLContinueStatement.h @@ -0,0 +1,44 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_CONTINUESTATEMENT +#define SKSL_CONTINUESTATEMENT + +#include "include/private/SkSLStatement.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * A 'continue' statement. + */ +class ContinueStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kContinue; + + ContinueStatement(Position pos) + : INHERITED(pos, kIRNodeKind) {} + + static std::unique_ptr<Statement> Make(Position pos) { + return std::make_unique<ContinueStatement>(pos); + } + + std::unique_ptr<Statement> clone() const override { + return std::make_unique<ContinueStatement>(fPosition); + } + + std::string description() const override { + return "continue;"; + } + +private: + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.cpp new file mode 100644 index 0000000000..2f090219f2 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.cpp @@ -0,0 +1,33 @@ +/* + * 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/SkTypes.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLDiscardStatement.h" + +namespace SkSL { + +std::unique_ptr<Statement> DiscardStatement::Convert(const Context& context, Position pos) { + if (!ProgramConfig::IsFragment(context.fConfig->fKind)) { + context.fErrors->error(pos, "discard statement is only permitted in fragment shaders"); + return nullptr; + } + return DiscardStatement::Make(context, pos); +} + +std::unique_ptr<Statement> DiscardStatement::Make(const Context& context, Position pos) { + SkASSERT(ProgramConfig::IsFragment(context.fConfig->fKind)); + return std::make_unique<DiscardStatement>(pos); +} + +std::unique_ptr<Statement> DiscardStatement::clone() const { + return std::make_unique<DiscardStatement>(fPosition); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h new file mode 100644 index 0000000000..1e947d7d0f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLDiscardStatement.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DISCARDSTATEMENT +#define SKSL_DISCARDSTATEMENT + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLPosition.h" + +#include <memory> +#include <string> + +namespace SkSL { + +class Context; + +/** + * A 'discard' statement. + */ +class DiscardStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kDiscard; + + DiscardStatement(Position pos) : INHERITED(pos, kIRNodeKind) {} + + // Creates a discard-statement; reports errors via ErrorReporter. + static std::unique_ptr<Statement> Convert(const Context& context, Position pos); + + // Creates a discard-statement; reports errors via SkASSERT. + static std::unique_ptr<Statement> Make(const Context& context, Position pos); + + std::unique_ptr<Statement> clone() const override; + + std::string description() const override { + return "discard;"; + } + +private: + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.cpp new file mode 100644 index 0000000000..1a0a62e0aa --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLDoStatement.h" + +#include "include/core/SkTypes.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +std::unique_ptr<Statement> DoStatement::Convert(const Context& context, + Position pos, + std::unique_ptr<Statement> stmt, + std::unique_ptr<Expression> test) { + if (context.fConfig->strictES2Mode()) { + context.fErrors->error(pos, "do-while loops are not supported"); + return nullptr; + } + test = context.fTypes.fBool->coerceExpression(std::move(test), context); + if (!test) { + return nullptr; + } + if (Analysis::DetectVarDeclarationWithoutScope(*stmt, context.fErrors)) { + return nullptr; + } + return DoStatement::Make(context, pos, std::move(stmt), std::move(test)); +} + +std::unique_ptr<Statement> DoStatement::Make(const Context& context, + Position pos, + std::unique_ptr<Statement> stmt, + std::unique_ptr<Expression> test) { + SkASSERT(!context.fConfig->strictES2Mode()); + SkASSERT(test->type().matches(*context.fTypes.fBool)); + SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*stmt)); + return std::make_unique<DoStatement>(pos, std::move(stmt), std::move(test)); +} + +std::unique_ptr<Statement> DoStatement::clone() const { + return std::make_unique<DoStatement>(fPosition, this->statement()->clone(), + this->test()->clone()); +} + +std::string DoStatement::description() const { + return "do " + this->statement()->description() + + " while (" + this->test()->description() + ");"; +} + +} // namespace SkSL + diff --git a/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h new file mode 100644 index 0000000000..461252b38d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLDoStatement.h @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DOSTATEMENT +#define SKSL_DOSTATEMENT + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; + +/** + * A 'do' statement. + */ +class DoStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kDo; + + DoStatement(Position pos, std::unique_ptr<Statement> statement, + std::unique_ptr<Expression> test) + : INHERITED(pos, kIRNodeKind) + , fStatement(std::move(statement)) + , fTest(std::move(test)) {} + + // Creates an SkSL do-while loop; uses the ErrorReporter to report errors. + static std::unique_ptr<Statement> Convert(const Context& context, + Position pos, + std::unique_ptr<Statement> stmt, + std::unique_ptr<Expression> test); + + // Creates an SkSL do-while loop; reports errors via ASSERT. + static std::unique_ptr<Statement> Make(const Context& context, + Position pos, + std::unique_ptr<Statement> stmt, + std::unique_ptr<Expression> test); + + std::unique_ptr<Statement>& statement() { + return fStatement; + } + + const std::unique_ptr<Statement>& statement() const { + return fStatement; + } + + std::unique_ptr<Expression>& test() { + return fTest; + } + + const std::unique_ptr<Expression>& test() const { + return fTest; + } + + std::unique_ptr<Statement> clone() const override; + + std::string description() const override; + +private: + std::unique_ptr<Statement> fStatement; + std::unique_ptr<Expression> fTest; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLExpression.cpp new file mode 100644 index 0000000000..b6ebb7b0ec --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExpression.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2021 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLExpression.h" + +#include "include/private/SkSLDefines.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLContext.h" + +namespace SkSL { + +std::string Expression::description() const { + return this->description(OperatorPrecedence::kTopLevel); +} + +bool Expression::isIncomplete(const Context& context) const { + switch (this->kind()) { + case Kind::kFunctionReference: + context.fErrors->error(fPosition.after(), "expected '(' to begin function call"); + return true; + + case Kind::kMethodReference: + context.fErrors->error(fPosition.after(), "expected '(' to begin method call"); + return true; + + case Kind::kTypeReference: + context.fErrors->error(fPosition.after(), + "expected '(' to begin constructor invocation"); + return true; + + default: + return false; + } +} + +ExpressionArray ExpressionArray::clone() const { + ExpressionArray cloned; + cloned.reserve_back(this->size()); + for (const std::unique_ptr<Expression>& expr : *this) { + cloned.push_back(expr ? expr->clone() : nullptr); + } + return cloned; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLExpression.h new file mode 100644 index 0000000000..6336b4a5ef --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExpression.h @@ -0,0 +1,143 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXPRESSION +#define SKSL_EXPRESSION + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <memory> +#include <optional> +#include <string> + +namespace SkSL { + +class AnyConstructor; +class Context; +enum class OperatorPrecedence : uint8_t; + +/** + * Abstract supertype of all expressions. + */ +class Expression : public IRNode { +public: + using Kind = ExpressionKind; + + Expression(Position pos, Kind kind, const Type* type) + : INHERITED(pos, (int) kind) + , fType(type) { + SkASSERT(kind >= Kind::kFirst && kind <= Kind::kLast); + } + + Kind kind() const { + return (Kind) fKind; + } + + virtual const Type& type() const { + return *fType; + } + + bool isAnyConstructor() const { + static_assert((int)Kind::kConstructorArray - 1 == (int)Kind::kChildCall); + static_assert((int)Kind::kConstructorStruct + 1 == (int)Kind::kFieldAccess); + return this->kind() >= Kind::kConstructorArray && this->kind() <= Kind::kConstructorStruct; + } + + bool isIntLiteral() const { + return this->kind() == Kind::kLiteral && this->type().isInteger(); + } + + bool isFloatLiteral() const { + return this->kind() == Kind::kLiteral && this->type().isFloat(); + } + + bool isBoolLiteral() const { + return this->kind() == Kind::kLiteral && this->type().isBoolean(); + } + + AnyConstructor& asAnyConstructor(); + const AnyConstructor& asAnyConstructor() const; + + /** + * Returns true if this expression is incomplete. Specifically, dangling function/method-call + * references that were never invoked, or type references that were never constructed, are + * considered incomplete expressions and should result in an error. + */ + bool isIncomplete(const Context& context) const; + + /** + * Compares this constant expression against another constant expression. Returns kUnknown if + * we aren't able to deduce a result (an expression isn't actually constant, the types are + * mismatched, etc). + */ + enum class ComparisonResult { + kUnknown = -1, + kNotEqual, + kEqual + }; + virtual ComparisonResult compareConstant(const Expression& other) const { + return ComparisonResult::kUnknown; + } + + CoercionCost coercionCost(const Type& target) const { + return this->type().coercionCost(target); + } + + /** + * Returns true if this expression type supports `getConstantValue`. (This particular expression + * may or may not actually contain a constant value.) It's harmless to call `getConstantValue` + * on expressions which don't support constant values or don't contain any constant values, but + * if `supportsConstantValues` returns false, you can assume that `getConstantValue` will return + * nullopt for every slot of this expression. This allows for early-out opportunities in some + * cases. (Some expressions have tons of slots but never hold a constant value; e.g. a variable + * holding a very large array.) + */ + virtual bool supportsConstantValues() const { + return false; + } + + /** + * Returns the n'th compile-time constant value within a literal or constructor. + * Use Type::slotCount to determine the number of slots within an expression. + * Slots which do not contain compile-time constant values will return nullopt. + * `vec4(1, vec2(2), 3)` contains four compile-time constants: (1, 2, 2, 3) + * `mat2(f)` contains four slots, and two are constant: (nullopt, 0, + * 0, nullopt) + * All classes which override this function must also implement `supportsConstantValues`. + */ + virtual std::optional<double> getConstantValue(int n) const { + SkASSERT(!this->supportsConstantValues()); + return std::nullopt; + } + + virtual std::unique_ptr<Expression> clone(Position pos) const = 0; + + /** + * Returns a clone at the same position. + */ + std::unique_ptr<Expression> clone() const { return this->clone(fPosition); } + + /** + * Returns a description of the expression. + */ + std::string description() const final; + virtual std::string description(OperatorPrecedence parentPrecedence) const = 0; + + +private: + const Type* fType; + + using INHERITED = IRNode; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.cpp new file mode 100644 index 0000000000..7f2831644f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLExpressionStatement.h" + +#include "include/core/SkTypes.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +namespace SkSL { + +std::unique_ptr<Statement> ExpressionStatement::Convert(const Context& context, + std::unique_ptr<Expression> expr) { + // Expression-statements need to represent a complete expression. + // Report an error on intermediate expressions, like FunctionReference or TypeReference. + if (expr->isIncomplete(context)) { + return nullptr; + } + return ExpressionStatement::Make(context, std::move(expr)); +} + +std::unique_ptr<Statement> ExpressionStatement::Make(const Context& context, + std::unique_ptr<Expression> expr) { + SkASSERT(!expr->isIncomplete(context)); + + if (context.fConfig->fSettings.fOptimize) { + // Expression-statements without any side effect can be replaced with a Nop. + if (!Analysis::HasSideEffects(*expr)) { + return Nop::Make(); + } + + // If this is an assignment statement like `a += b;`, the ref-kind of `a` will be set as + // read-write; `a` is written-to by the +=, and read-from by the consumer of the expression. + // We can demote the ref-kind to "write" safely, because the result of the expression is + // discarded; that is, `a` is never actually read-from. + if (expr->is<BinaryExpression>()) { + BinaryExpression& binary = expr->as<BinaryExpression>(); + if (VariableReference* assignedVar = binary.isAssignmentIntoVariable()) { + if (assignedVar->refKind() == VariableRefKind::kReadWrite) { + assignedVar->setRefKind(VariableRefKind::kWrite); + } + } + } + } + + return std::make_unique<ExpressionStatement>(std::move(expr)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h new file mode 100644 index 0000000000..d213ad230e --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExpressionStatement.h @@ -0,0 +1,66 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXPRESSIONSTATEMENT +#define SKSL_EXPRESSIONSTATEMENT + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; + +/** + * A lone expression being used as a statement. + */ +class ExpressionStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kExpression; + + ExpressionStatement(std::unique_ptr<Expression> expression) + : INHERITED(expression->fPosition, kIRNodeKind) + , fExpression(std::move(expression)) {} + + // Creates an SkSL expression-statement; reports errors via ErrorReporter. + static std::unique_ptr<Statement> Convert(const Context& context, + std::unique_ptr<Expression> expr); + + // Creates an SkSL expression-statement; reports errors via assertion. + static std::unique_ptr<Statement> Make(const Context& context, + std::unique_ptr<Expression> expr); + + const std::unique_ptr<Expression>& expression() const { + return fExpression; + } + + std::unique_ptr<Expression>& expression() { + return fExpression; + } + + std::unique_ptr<Statement> clone() const override { + return std::make_unique<ExpressionStatement>(this->expression()->clone()); + } + + std::string description() const override { + return this->expression()->description() + ";"; + } + +private: + std::unique_ptr<Expression> fExpression; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLExtension.h b/gfx/skia/skia/src/sksl/ir/SkSLExtension.h new file mode 100644 index 0000000000..94c2dd933e --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLExtension.h @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_EXTENSION +#define SKSL_EXTENSION + +#include "include/private/SkSLProgramElement.h" + +namespace SkSL { + +/** + * An extension declaration. + */ +class Extension final : public ProgramElement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kExtension; + + Extension(Position pos, std::string_view name) + : INHERITED(pos, kIRNodeKind) + , fName(name) {} + + std::string_view name() const { + return fName; + } + + std::unique_ptr<ProgramElement> clone() const override { + return std::unique_ptr<ProgramElement>(new Extension(fPosition, this->name())); + } + + std::string description() const override { + return "#extension " + std::string(this->name()) + " : enable"; + } + +private: + std::string_view fName; + + using INHERITED = ProgramElement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLField.h b/gfx/skia/skia/src/sksl/ir/SkSLField.h new file mode 100644 index 0000000000..a7fa6575cf --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLField.h @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FIELD +#define SKSL_FIELD + +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLSymbol.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +namespace SkSL { + +/** + * A symbol which should be interpreted as a field access. Fields are added to the symboltable + * whenever a bare reference to an identifier should refer to a struct field; in GLSL, this is the + * result of declaring anonymous interface blocks. + */ +class Field final : public Symbol { +public: + inline static constexpr Kind kIRNodeKind = Kind::kField; + + Field(Position pos, const Variable* owner, int fieldIndex) + : INHERITED(pos, kIRNodeKind, owner->type().fields()[fieldIndex].fName, + owner->type().fields()[fieldIndex].fType) + , fOwner(owner) + , fFieldIndex(fieldIndex) {} + + int fieldIndex() const { + return fFieldIndex; + } + + const Variable& owner() const { + return *fOwner; + } + + std::string description() const override { + return this->owner().name().empty() + ? std::string(this->name()) + : (this->owner().description() + "." + std::string(this->name())); + } + +private: + const Variable* fOwner; + int fFieldIndex; + + using INHERITED = Symbol; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.cpp new file mode 100644 index 0000000000..5758280b7a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.cpp @@ -0,0 +1,125 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLFieldAccess.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLSymbol.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLConstructorStruct.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLMethodReference.h" +#include "src/sksl/ir/SkSLSetting.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#include <cstddef> + +namespace SkSL { + +std::unique_ptr<Expression> FieldAccess::Convert(const Context& context, + Position pos, + SymbolTable& symbolTable, + std::unique_ptr<Expression> base, + std::string_view field) { + const Type& baseType = base->type(); + if (baseType.isEffectChild()) { + // Turn the field name into a free function name, prefixed with '$': + std::string methodName = "$" + std::string(field); + const Symbol* result = symbolTable.find(methodName); + if (result && result->is<FunctionDeclaration>()) { + return std::make_unique<MethodReference>(context, pos, std::move(base), + &result->as<FunctionDeclaration>()); + } + context.fErrors->error(pos, "type '" + baseType.displayName() + "' has no method named '" + + std::string(field) + "'"); + return nullptr; + } + if (baseType.isStruct()) { + const std::vector<Type::Field>& fields = baseType.fields(); + for (size_t i = 0; i < fields.size(); i++) { + if (fields[i].fName == field) { + return FieldAccess::Make(context, pos, std::move(base), (int) i); + } + } + } + if (baseType.matches(*context.fTypes.fSkCaps)) { + return Setting::Convert(context, pos, field); + } + + context.fErrors->error(pos, "type '" + baseType.displayName() + + "' does not have a field named '" + std::string(field) + "'"); + return nullptr; +} + +static std::unique_ptr<Expression> extract_field(Position pos, + const ConstructorStruct& ctor, + int fieldIndex) { + // Confirm that the fields that are being removed are side-effect free. + const ExpressionArray& args = ctor.arguments(); + int numFields = args.size(); + for (int index = 0; index < numFields; ++index) { + if (fieldIndex == index) { + continue; + } + if (Analysis::HasSideEffects(*args[index])) { + return nullptr; + } + } + + // Return the desired field. + return args[fieldIndex]->clone(pos); +} + +std::unique_ptr<Expression> FieldAccess::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> base, + int fieldIndex, + OwnerKind ownerKind) { + SkASSERT(base->type().isStruct()); + SkASSERT(fieldIndex >= 0); + SkASSERT(fieldIndex < (int)base->type().fields().size()); + + // Replace `knownStruct.field` with the field's value if there are no side-effects involved. + const Expression* expr = ConstantFolder::GetConstantValueForVariable(*base); + if (expr->is<ConstructorStruct>()) { + if (std::unique_ptr<Expression> field = extract_field(pos, expr->as<ConstructorStruct>(), + fieldIndex)) { + return field; + } + } + + return std::make_unique<FieldAccess>(pos, std::move(base), fieldIndex, ownerKind); +} + +size_t FieldAccess::initialSlot() const { + SkSpan<const Type::Field> fields = this->base()->type().fields(); + const int fieldIndex = this->fieldIndex(); + + size_t slot = 0; + for (int index = 0; index < fieldIndex; ++index) { + slot += fields[index].fType->slotCount(); + } + return slot; +} + +std::string FieldAccess::description(OperatorPrecedence) const { + std::string f = this->base()->description(OperatorPrecedence::kPostfix); + if (!f.empty()) { + f.push_back('.'); + } + return f + std::string(this->base()->type().fields()[this->fieldIndex()].fName); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h new file mode 100644 index 0000000000..8eca68fb38 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFieldAccess.h @@ -0,0 +1,104 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FIELDACCESS +#define SKSL_FIELDACCESS + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace SkSL { + +class Context; +class SymbolTable; +enum class OperatorPrecedence : uint8_t; + +enum class FieldAccessOwnerKind : int8_t { + kDefault, + // this field access is to a field of an anonymous interface block (and thus, the field name + // is actually in global scope, so only the field name needs to be written in GLSL) + kAnonymousInterfaceBlock +}; + +/** + * An expression which extracts a field from a struct, as in 'foo.bar'. + */ +class FieldAccess final : public Expression { +public: + using OwnerKind = FieldAccessOwnerKind; + + inline static constexpr Kind kIRNodeKind = Kind::kFieldAccess; + + FieldAccess(Position pos, std::unique_ptr<Expression> base, int fieldIndex, + OwnerKind ownerKind = OwnerKind::kDefault) + : INHERITED(pos, kIRNodeKind, base->type().fields()[fieldIndex].fType) + , fFieldIndex(fieldIndex) + , fOwnerKind(ownerKind) + , fBase(std::move(base)) {} + + // Returns a field-access expression; reports errors via the ErrorReporter. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + SymbolTable& symbolTable, + std::unique_ptr<Expression> base, + std::string_view field); + + // Returns a field-access expression; reports errors via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> base, + int fieldIndex, + OwnerKind ownerKind = OwnerKind::kDefault); + + std::unique_ptr<Expression>& base() { + return fBase; + } + + const std::unique_ptr<Expression>& base() const { + return fBase; + } + + int fieldIndex() const { + return fFieldIndex; + } + + OwnerKind ownerKind() const { + return fOwnerKind; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<FieldAccess>(pos, + this->base()->clone(), + this->fieldIndex(), + this->ownerKind()); + } + + size_t initialSlot() const; + + std::string description(OperatorPrecedence) const override; + +private: + int fFieldIndex; + FieldAccessOwnerKind fOwnerKind; + std::unique_ptr<Expression> fBase; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLForStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.cpp new file mode 100644 index 0000000000..8777f6638c --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLForStatement.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +namespace SkSL { + +static bool is_vardecl_block_initializer(const Statement* stmt) { + if (!stmt) { + return false; + } + if (!stmt->is<SkSL::Block>()) { + return false; + } + const SkSL::Block& b = stmt->as<SkSL::Block>(); + if (b.isScope()) { + return false; + } + for (const auto& child : b.children()) { + if (!child->is<SkSL::VarDeclaration>()) { + return false; + } + } + return true; +} + +static bool is_simple_initializer(const Statement* stmt) { + return !stmt || stmt->isEmpty() || stmt->is<SkSL::VarDeclaration>() || + stmt->is<SkSL::ExpressionStatement>(); +} + +std::unique_ptr<Statement> ForStatement::clone() const { + std::unique_ptr<LoopUnrollInfo> unrollInfo; + if (fUnrollInfo) { + unrollInfo = std::make_unique<LoopUnrollInfo>(*fUnrollInfo); + } + + return std::make_unique<ForStatement>( + fPosition, + fForLoopPositions, + this->initializer() ? this->initializer()->clone() : nullptr, + this->test() ? this->test()->clone() : nullptr, + this->next() ? this->next()->clone() : nullptr, + this->statement()->clone(), + std::move(unrollInfo), + SymbolTable::WrapIfBuiltin(this->symbols())); +} + +std::string ForStatement::description() const { + std::string result("for ("); + if (this->initializer()) { + result += this->initializer()->description(); + } else { + result += ";"; + } + result += " "; + if (this->test()) { + result += this->test()->description(); + } + result += "; "; + if (this->next()) { + result += this->next()->description(); + } + result += ") " + this->statement()->description(); + return result; +} + +std::unique_ptr<Statement> ForStatement::Convert(const Context& context, + Position pos, + ForLoopPositions positions, + std::unique_ptr<Statement> initializer, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> next, + std::unique_ptr<Statement> statement, + std::shared_ptr<SymbolTable> symbolTable) { + bool isSimpleInitializer = is_simple_initializer(initializer.get()); + bool isVardeclBlockInitializer = + !isSimpleInitializer && is_vardecl_block_initializer(initializer.get()); + + if (!isSimpleInitializer && !isVardeclBlockInitializer) { + context.fErrors->error(initializer->fPosition, "invalid for loop initializer"); + return nullptr; + } + + if (test) { + test = context.fTypes.fBool->coerceExpression(std::move(test), context); + if (!test) { + return nullptr; + } + } + + // The type of the next-expression doesn't matter, but it needs to be a complete expression. + // Report an error on intermediate expressions like FunctionReference or TypeReference. + if (next && next->isIncomplete(context)) { + return nullptr; + } + + std::unique_ptr<LoopUnrollInfo> unrollInfo; + if (context.fConfig->strictES2Mode()) { + // In strict-ES2, loops must be unrollable or it's an error. + unrollInfo = Analysis::GetLoopUnrollInfo(pos, positions, initializer.get(), test.get(), + next.get(), statement.get(), context.fErrors); + if (!unrollInfo) { + return nullptr; + } + } else { + // In ES3, loops don't have to be unrollable, but we can use the unroll information for + // optimization purposes. + unrollInfo = Analysis::GetLoopUnrollInfo(pos, positions, initializer.get(), test.get(), + next.get(), statement.get(), /*errors=*/nullptr); + } + + if (Analysis::DetectVarDeclarationWithoutScope(*statement, context.fErrors)) { + return nullptr; + } + + if (isVardeclBlockInitializer) { + // If the initializer statement of a for loop contains multiple variables, this causes + // difficulties for several of our backends; e.g. Metal doesn't have a way to express arrays + // of different size in the same decl-stmt, because the array-size is part of the type. It's + // conceptually equivalent to synthesize a scope, declare the variables, and then emit a for + // statement with an empty init-stmt. (Note that we can't just do this transformation + // unilaterally for all for-statements, because the resulting for loop isn't ES2-compliant.) + StatementArray scope; + scope.push_back(std::move(initializer)); + scope.push_back(ForStatement::Make(context, pos, positions, /*initializer=*/nullptr, + std::move(test), std::move(next), std::move(statement), + std::move(unrollInfo), /*symbolTable=*/nullptr)); + return Block::Make(pos, std::move(scope), Block::Kind::kBracedScope, + std::move(symbolTable)); + } + + return ForStatement::Make(context, pos, positions, std::move(initializer), std::move(test), + std::move(next), std::move(statement), std::move(unrollInfo), + std::move(symbolTable)); +} + +std::unique_ptr<Statement> ForStatement::ConvertWhile(const Context& context, Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Statement> statement, + std::shared_ptr<SymbolTable> symbolTable) { + if (context.fConfig->strictES2Mode()) { + context.fErrors->error(pos, "while loops are not supported"); + return nullptr; + } + return ForStatement::Convert(context, pos, ForLoopPositions(), /*initializer=*/nullptr, + std::move(test), /*next=*/nullptr, std::move(statement), std::move(symbolTable)); +} + +std::unique_ptr<Statement> ForStatement::Make(const Context& context, + Position pos, + ForLoopPositions positions, + std::unique_ptr<Statement> initializer, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> next, + std::unique_ptr<Statement> statement, + std::unique_ptr<LoopUnrollInfo> unrollInfo, + std::shared_ptr<SymbolTable> symbolTable) { + SkASSERT(is_simple_initializer(initializer.get()) || + is_vardecl_block_initializer(initializer.get())); + SkASSERT(!test || test->type().matches(*context.fTypes.fBool)); + SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*statement)); + SkASSERT(unrollInfo || !context.fConfig->strictES2Mode()); + + // Unrollable loops are easy to optimize because we know initializer, test and next don't have + // interesting side effects. + if (unrollInfo) { + // A zero-iteration unrollable loop can be replaced with Nop. + // An unrollable loop with an empty body can be replaced with Nop. + if (unrollInfo->fCount <= 0 || statement->isEmpty()) { + return Nop::Make(); + } + } + + return std::make_unique<ForStatement>(pos, positions, std::move(initializer), std::move(test), + std::move(next), std::move(statement), std::move(unrollInfo), std::move(symbolTable)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h new file mode 100644 index 0000000000..468df41cbc --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLForStatement.h @@ -0,0 +1,150 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FORSTATEMENT +#define SKSL_FORSTATEMENT + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +class SymbolTable; +class Variable; + +/** + * The unrollability information for an ES2-compatible loop. + */ +struct LoopUnrollInfo { + const Variable* fIndex; + double fStart; + double fDelta; + int fCount; +}; + +/** + * A 'for' statement. + */ +class ForStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kFor; + + ForStatement(Position pos, + ForLoopPositions forLoopPositions, + std::unique_ptr<Statement> initializer, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> next, + std::unique_ptr<Statement> statement, + std::unique_ptr<LoopUnrollInfo> unrollInfo, + std::shared_ptr<SymbolTable> symbols) + : INHERITED(pos, kIRNodeKind) + , fForLoopPositions(forLoopPositions) + , fSymbolTable(std::move(symbols)) + , fInitializer(std::move(initializer)) + , fTest(std::move(test)) + , fNext(std::move(next)) + , fStatement(std::move(statement)) + , fUnrollInfo(std::move(unrollInfo)) {} + + // Creates an SkSL for loop; handles type-coercion and uses the ErrorReporter to report errors. + static std::unique_ptr<Statement> Convert(const Context& context, + Position pos, + ForLoopPositions forLoopPositions, + std::unique_ptr<Statement> initializer, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> next, + std::unique_ptr<Statement> statement, + std::shared_ptr<SymbolTable> symbolTable); + + // Creates an SkSL while loop; handles type-coercion and uses the ErrorReporter for errors. + static std::unique_ptr<Statement> ConvertWhile(const Context& context, Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Statement> statement, + std::shared_ptr<SymbolTable> symbolTable); + + // Creates an SkSL for/while loop. Assumes properly coerced types and reports errors via assert. + static std::unique_ptr<Statement> Make(const Context& context, + Position pos, + ForLoopPositions forLoopPositions, + std::unique_ptr<Statement> initializer, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> next, + std::unique_ptr<Statement> statement, + std::unique_ptr<LoopUnrollInfo> unrollInfo, + std::shared_ptr<SymbolTable> symbolTable); + + ForLoopPositions forLoopPositions() const { + return fForLoopPositions; + } + + std::unique_ptr<Statement>& initializer() { + return fInitializer; + } + + const std::unique_ptr<Statement>& initializer() const { + return fInitializer; + } + + std::unique_ptr<Expression>& test() { + return fTest; + } + + const std::unique_ptr<Expression>& test() const { + return fTest; + } + + std::unique_ptr<Expression>& next() { + return fNext; + } + + const std::unique_ptr<Expression>& next() const { + return fNext; + } + + std::unique_ptr<Statement>& statement() { + return fStatement; + } + + const std::unique_ptr<Statement>& statement() const { + return fStatement; + } + + const std::shared_ptr<SymbolTable>& symbols() const { + return fSymbolTable; + } + + /** Loop-unroll information is only supported in strict-ES2 code. Null is returned in ES3+. */ + const LoopUnrollInfo* unrollInfo() const { + return fUnrollInfo.get(); + } + + std::unique_ptr<Statement> clone() const override; + + std::string description() const override; + +private: + ForLoopPositions fForLoopPositions; + std::shared_ptr<SymbolTable> fSymbolTable; + std::unique_ptr<Statement> fInitializer; + std::unique_ptr<Expression> fTest; + std::unique_ptr<Expression> fNext; + std::unique_ptr<Statement> fStatement; + std::unique_ptr<LoopUnrollInfo> fUnrollInfo; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.cpp new file mode 100644 index 0000000000..69df07db9d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.cpp @@ -0,0 +1,1056 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLFunctionCall.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkFloatingPoint.h" +#include "include/private/base/SkTArray.h" +#include "include/private/base/SkTo.h" +#include "include/sksl/DSLCore.h" +#include "include/sksl/DSLExpression.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/base/SkHalf.h" +#include "src/core/SkMatrixInvert.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLChildCall.h" +#include "src/sksl/ir/SkSLConstructor.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionReference.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLMethodReference.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLTypeReference.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +#include <algorithm> +#include <array> +#include <cmath> +#include <cstdint> +#include <cstring> +#include <optional> +#include <string_view> +#include <vector> + +namespace SkSL { + +using IntrinsicArguments = std::array<const Expression*, 3>; + +static bool has_compile_time_constant_arguments(const ExpressionArray& arguments) { + for (const std::unique_ptr<Expression>& arg : arguments) { + const Expression* expr = ConstantFolder::GetConstantValueForVariable(*arg); + if (!Analysis::IsCompileTimeConstant(*expr)) { + return false; + } + } + return true; +} + +template <typename T> +static void type_check_expression(const Expression& expr); + +template <> +void type_check_expression<float>(const Expression& expr) { + SkASSERT(expr.type().componentType().isFloat()); +} + +template <> +void type_check_expression<SKSL_INT>(const Expression& expr) { + SkASSERT(expr.type().componentType().isInteger()); +} + +template <> +void type_check_expression<bool>(const Expression& expr) { + SkASSERT(expr.type().componentType().isBoolean()); +} + +static std::unique_ptr<Expression> assemble_compound(const Context& context, + Position pos, + const Type& returnType, + double value[]) { + int numSlots = returnType.slotCount(); + ExpressionArray array; + array.reserve_back(numSlots); + for (int index = 0; index < numSlots; ++index) { + array.push_back(Literal::Make(pos, value[index], &returnType.componentType())); + } + return ConstructorCompound::Make(context, pos, returnType, std::move(array)); +} + +using CoalesceFn = double (*)(double, double, double); +using FinalizeFn = double (*)(double); + +static std::unique_ptr<Expression> coalesce_n_way_vector(const Expression* arg0, + const Expression* arg1, + double startingState, + const Type& returnType, + CoalesceFn coalesce, + FinalizeFn finalize) { + // Takes up to two vector or scalar arguments and coalesces them in sequence: + // scalar = startingState; + // scalar = coalesce(scalar, arg0.x, arg1.x); + // scalar = coalesce(scalar, arg0.y, arg1.y); + // scalar = coalesce(scalar, arg0.z, arg1.z); + // scalar = coalesce(scalar, arg0.w, arg1.w); + // scalar = finalize(scalar); + // + // If an argument is null, zero is passed to the coalesce function. If the arguments are a mix + // of scalars and vectors, the scalars is interpreted as a vector containing the same value for + // every component. + + Position pos = arg0->fPosition; + double minimumValue = returnType.componentType().minimumValue(); + double maximumValue = returnType.componentType().maximumValue(); + + const Type& vecType = arg0->type().isVector() ? arg0->type() : + (arg1 && arg1->type().isVector()) ? arg1->type() : + arg0->type(); + SkASSERT( arg0->type().componentType().matches(vecType.componentType())); + SkASSERT(!arg1 || arg1->type().componentType().matches(vecType.componentType())); + + double value = startingState; + int arg0Index = 0; + int arg1Index = 0; + for (int index = 0; index < vecType.columns(); ++index) { + std::optional<double> arg0Value = arg0->getConstantValue(arg0Index); + arg0Index += arg0->type().isVector() ? 1 : 0; + SkASSERT(arg0Value.has_value()); + + std::optional<double> arg1Value = 0.0; + if (arg1) { + arg1Value = arg1->getConstantValue(arg1Index); + arg1Index += arg1->type().isVector() ? 1 : 0; + SkASSERT(arg1Value.has_value()); + } + + value = coalesce(value, *arg0Value, *arg1Value); + + if (value >= minimumValue && value <= maximumValue) { + // This result will fit inside the return type. + } else { + // The value is outside the float range or is NaN (all if-checks fail); do not optimize. + return nullptr; + } + } + + if (finalize) { + value = finalize(value); + } + + return Literal::Make(pos, value, &returnType); +} + +template <typename T> +static std::unique_ptr<Expression> coalesce_vector(const IntrinsicArguments& arguments, + double startingState, + const Type& returnType, + CoalesceFn coalesce, + FinalizeFn finalize) { + SkASSERT(arguments[0]); + SkASSERT(!arguments[1]); + type_check_expression<T>(*arguments[0]); + + return coalesce_n_way_vector(arguments[0], /*arg1=*/nullptr, + startingState, returnType, coalesce, finalize); +} + +template <typename T> +static std::unique_ptr<Expression> coalesce_pairwise_vectors(const IntrinsicArguments& arguments, + double startingState, + const Type& returnType, + CoalesceFn coalesce, + FinalizeFn finalize) { + SkASSERT(arguments[0]); + SkASSERT(arguments[1]); + SkASSERT(!arguments[2]); + type_check_expression<T>(*arguments[0]); + type_check_expression<T>(*arguments[1]); + + return coalesce_n_way_vector(arguments[0], arguments[1], + startingState, returnType, coalesce, finalize); +} + +using CompareFn = bool (*)(double, double); + +static std::unique_ptr<Expression> optimize_comparison(const Context& context, + const IntrinsicArguments& arguments, + CompareFn compare) { + const Expression* left = arguments[0]; + const Expression* right = arguments[1]; + SkASSERT(left); + SkASSERT(right); + SkASSERT(!arguments[2]); + + const Type& type = left->type(); + SkASSERT(type.isVector()); + SkASSERT(type.componentType().isScalar()); + SkASSERT(type.matches(right->type())); + + double array[4]; + + for (int index = 0; index < type.columns(); ++index) { + std::optional<double> leftValue = left->getConstantValue(index); + std::optional<double> rightValue = right->getConstantValue(index); + SkASSERT(leftValue.has_value()); + SkASSERT(rightValue.has_value()); + array[index] = compare(*leftValue, *rightValue) ? 1.0 : 0.0; + } + + const Type& bvecType = context.fTypes.fBool->toCompound(context, type.columns(), /*rows=*/1); + return assemble_compound(context, left->fPosition, bvecType, array); +} + +using EvaluateFn = double (*)(double, double, double); + +static std::unique_ptr<Expression> evaluate_n_way_intrinsic(const Context& context, + const Expression* arg0, + const Expression* arg1, + const Expression* arg2, + const Type& returnType, + EvaluateFn eval) { + // Takes up to three arguments and evaluates all of them, left-to-right, in tandem. + // Equivalent to constructing a new compound value containing the results from: + // eval(arg0.x, arg1.x, arg2.x), + // eval(arg0.y, arg1.y, arg2.y), + // eval(arg0.z, arg1.z, arg2.z), + // eval(arg0.w, arg1.w, arg2.w) + // + // If an argument is null, zero is passed to the evaluation function. If the arguments are a mix + // of scalars and compounds, scalars are interpreted as a compound containing the same value for + // every component. + + double minimumValue = returnType.componentType().minimumValue(); + double maximumValue = returnType.componentType().maximumValue(); + int slots = returnType.slotCount(); + double array[16]; + + int arg0Index = 0; + int arg1Index = 0; + int arg2Index = 0; + for (int index = 0; index < slots; ++index) { + std::optional<double> arg0Value = arg0->getConstantValue(arg0Index); + arg0Index += arg0->type().isScalar() ? 0 : 1; + SkASSERT(arg0Value.has_value()); + + std::optional<double> arg1Value = 0.0; + if (arg1) { + arg1Value = arg1->getConstantValue(arg1Index); + arg1Index += arg1->type().isScalar() ? 0 : 1; + SkASSERT(arg1Value.has_value()); + } + + std::optional<double> arg2Value = 0.0; + if (arg2) { + arg2Value = arg2->getConstantValue(arg2Index); + arg2Index += arg2->type().isScalar() ? 0 : 1; + SkASSERT(arg2Value.has_value()); + } + + array[index] = eval(*arg0Value, *arg1Value, *arg2Value); + + if (array[index] >= minimumValue && array[index] <= maximumValue) { + // This result will fit inside the return type. + } else { + // The value is outside the float range or is NaN (all if-checks fail); do not optimize. + return nullptr; + } + } + + return assemble_compound(context, arg0->fPosition, returnType, array); +} + +template <typename T> +static std::unique_ptr<Expression> evaluate_intrinsic(const Context& context, + const IntrinsicArguments& arguments, + const Type& returnType, + EvaluateFn eval) { + SkASSERT(arguments[0]); + SkASSERT(!arguments[1]); + type_check_expression<T>(*arguments[0]); + + return evaluate_n_way_intrinsic(context, arguments[0], /*arg1=*/nullptr, /*arg2=*/nullptr, + returnType, eval); +} + +static std::unique_ptr<Expression> evaluate_intrinsic_numeric(const Context& context, + const IntrinsicArguments& arguments, + const Type& returnType, + EvaluateFn eval) { + SkASSERT(arguments[0]); + SkASSERT(!arguments[1]); + const Type& type = arguments[0]->type().componentType(); + + if (type.isFloat()) { + return evaluate_intrinsic<float>(context, arguments, returnType, eval); + } + if (type.isInteger()) { + return evaluate_intrinsic<SKSL_INT>(context, arguments, returnType, eval); + } + + SkDEBUGFAILF("unsupported type %s", type.description().c_str()); + return nullptr; +} + +static std::unique_ptr<Expression> evaluate_pairwise_intrinsic(const Context& context, + const IntrinsicArguments& arguments, + const Type& returnType, + EvaluateFn eval) { + SkASSERT(arguments[0]); + SkASSERT(arguments[1]); + SkASSERT(!arguments[2]); + const Type& type = arguments[0]->type().componentType(); + + if (type.isFloat()) { + type_check_expression<float>(*arguments[0]); + type_check_expression<float>(*arguments[1]); + } else if (type.isInteger()) { + type_check_expression<SKSL_INT>(*arguments[0]); + type_check_expression<SKSL_INT>(*arguments[1]); + } else { + SkDEBUGFAILF("unsupported type %s", type.description().c_str()); + return nullptr; + } + + return evaluate_n_way_intrinsic(context, arguments[0], arguments[1], /*arg2=*/nullptr, + returnType, eval); +} + +static std::unique_ptr<Expression> evaluate_3_way_intrinsic(const Context& context, + const IntrinsicArguments& arguments, + const Type& returnType, + EvaluateFn eval) { + SkASSERT(arguments[0]); + SkASSERT(arguments[1]); + SkASSERT(arguments[2]); + const Type& type = arguments[0]->type().componentType(); + + if (type.isFloat()) { + type_check_expression<float>(*arguments[0]); + type_check_expression<float>(*arguments[1]); + type_check_expression<float>(*arguments[2]); + } else if (type.isInteger()) { + type_check_expression<SKSL_INT>(*arguments[0]); + type_check_expression<SKSL_INT>(*arguments[1]); + type_check_expression<SKSL_INT>(*arguments[2]); + } else { + SkDEBUGFAILF("unsupported type %s", type.description().c_str()); + return nullptr; + } + + return evaluate_n_way_intrinsic(context, arguments[0], arguments[1], arguments[2], + returnType, eval); +} + +template <typename T1, typename T2> +static double pun_value(double val) { + // Interpret `val` as a value of type T1. + static_assert(sizeof(T1) == sizeof(T2)); + T1 inputValue = (T1)val; + // Reinterpret those bits as a value of type T2. + T2 outputValue; + memcpy(&outputValue, &inputValue, sizeof(T2)); + // Return the value-of-type-T2 as a double. (Non-finite values will prohibit optimization.) + return (double)outputValue; +} + +// Helper functions for optimizing all of our intrinsics. +namespace Intrinsics { +namespace { + +double coalesce_length(double a, double b, double) { return a + (b * b); } +double finalize_length(double a) { return std::sqrt(a); } + +double coalesce_distance(double a, double b, double c) { b -= c; return a + (b * b); } +double finalize_distance(double a) { return std::sqrt(a); } + +double coalesce_dot(double a, double b, double c) { return a + (b * c); } +double coalesce_any(double a, double b, double) { return a || b; } +double coalesce_all(double a, double b, double) { return a && b; } + +bool compare_lessThan(double a, double b) { return a < b; } +bool compare_lessThanEqual(double a, double b) { return a <= b; } +bool compare_greaterThan(double a, double b) { return a > b; } +bool compare_greaterThanEqual(double a, double b) { return a >= b; } +bool compare_equal(double a, double b) { return a == b; } +bool compare_notEqual(double a, double b) { return a != b; } + +double evaluate_radians(double a, double, double) { return a * 0.0174532925; } +double evaluate_degrees(double a, double, double) { return a * 57.2957795; } +double evaluate_sin(double a, double, double) { return std::sin(a); } +double evaluate_cos(double a, double, double) { return std::cos(a); } +double evaluate_tan(double a, double, double) { return std::tan(a); } +double evaluate_asin(double a, double, double) { return std::asin(a); } +double evaluate_acos(double a, double, double) { return std::acos(a); } +double evaluate_atan(double a, double, double) { return std::atan(a); } +double evaluate_atan2(double a, double b, double) { return std::atan2(a, b); } +double evaluate_asinh(double a, double, double) { return std::asinh(a); } +double evaluate_acosh(double a, double, double) { return std::acosh(a); } +double evaluate_atanh(double a, double, double) { return std::atanh(a); } + +double evaluate_pow(double a, double b, double) { return std::pow(a, b); } +double evaluate_exp(double a, double, double) { return std::exp(a); } +double evaluate_log(double a, double, double) { return std::log(a); } +double evaluate_exp2(double a, double, double) { return std::exp2(a); } +double evaluate_log2(double a, double, double) { return std::log2(a); } +double evaluate_sqrt(double a, double, double) { return std::sqrt(a); } +double evaluate_inversesqrt(double a, double, double) { + return sk_ieee_double_divide(1.0, std::sqrt(a)); +} + +double evaluate_abs(double a, double, double) { return std::abs(a); } +double evaluate_sign(double a, double, double) { return (a > 0) - (a < 0); } +double evaluate_floor(double a, double, double) { return std::floor(a); } +double evaluate_ceil(double a, double, double) { return std::ceil(a); } +double evaluate_fract(double a, double, double) { return a - std::floor(a); } +double evaluate_min(double a, double b, double) { return (a < b) ? a : b; } +double evaluate_max(double a, double b, double) { return (a > b) ? a : b; } +double evaluate_clamp(double x, double l, double h) { return (x < l) ? l : (x > h) ? h : x; } +double evaluate_fma(double a, double b, double c) { return a * b + c; } +double evaluate_saturate(double a, double, double) { return (a < 0) ? 0 : (a > 1) ? 1 : a; } +double evaluate_mix(double x, double y, double a) { return x * (1 - a) + y * a; } +double evaluate_step(double e, double x, double) { return (x < e) ? 0 : 1; } +double evaluate_mod(double a, double b, double) { + return a - b * std::floor(sk_ieee_double_divide(a, b)); +} +double evaluate_smoothstep(double edge0, double edge1, double x) { + double t = sk_ieee_double_divide(x - edge0, edge1 - edge0); + t = (t < 0) ? 0 : (t > 1) ? 1 : t; + return t * t * (3.0 - 2.0 * t); +} + +double evaluate_matrixCompMult(double x, double y, double) { return x * y; } + +double evaluate_not(double a, double, double) { return !a; } +double evaluate_sinh(double a, double, double) { return std::sinh(a); } +double evaluate_cosh(double a, double, double) { return std::cosh(a); } +double evaluate_tanh(double a, double, double) { return std::tanh(a); } +double evaluate_trunc(double a, double, double) { return std::trunc(a); } +double evaluate_round(double a, double, double) { + // The semantics of std::remainder guarantee a rounded-to-even result here, regardless of the + // current float-rounding mode. + return a - std::remainder(a, 1.0); +} +double evaluate_floatBitsToInt(double a, double, double) { return pun_value<float, int32_t> (a); } +double evaluate_floatBitsToUint(double a, double, double) { return pun_value<float, uint32_t>(a); } +double evaluate_intBitsToFloat(double a, double, double) { return pun_value<int32_t, float>(a); } +double evaluate_uintBitsToFloat(double a, double, double) { return pun_value<uint32_t, float>(a); } + +} // namespace +} // namespace Intrinsics + +static void extract_matrix(const Expression* expr, float mat[16]) { + size_t numSlots = expr->type().slotCount(); + for (size_t index = 0; index < numSlots; ++index) { + mat[index] = *expr->getConstantValue(index); + } +} + +static std::unique_ptr<Expression> optimize_intrinsic_call(const Context& context, + Position pos, + IntrinsicKind intrinsic, + const ExpressionArray& argArray, + const Type& returnType) { + // Replace constant variables with their literal values. + IntrinsicArguments arguments = {}; + SkASSERT(SkToSizeT(argArray.size()) <= arguments.size()); + for (int index = 0; index < argArray.size(); ++index) { + arguments[index] = ConstantFolder::GetConstantValueForVariable(*argArray[index]); + } + + auto Get = [&](int idx, int col) -> float { + return *arguments[idx]->getConstantValue(col); + }; + + using namespace SkSL::dsl; + switch (intrinsic) { + // 8.1 : Angle and Trigonometry Functions + case k_radians_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_radians); + case k_degrees_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_degrees); + case k_sin_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_sin); + case k_cos_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_cos); + case k_tan_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_tan); + case k_sinh_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_sinh); + case k_cosh_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_cosh); + case k_tanh_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_tanh); + case k_asin_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_asin); + case k_acos_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_acos); + case k_atan_IntrinsicKind: + if (argArray.size() == 1) { + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_atan); + } else { + return evaluate_pairwise_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_atan2); + } + case k_asinh_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_asinh); + + case k_acosh_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_acosh); + case k_atanh_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_atanh); + // 8.2 : Exponential Functions + case k_pow_IntrinsicKind: + return evaluate_pairwise_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_pow); + case k_exp_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_exp); + case k_log_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_log); + case k_exp2_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_exp2); + case k_log2_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_log2); + case k_sqrt_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_sqrt); + case k_inversesqrt_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_inversesqrt); + // 8.3 : Common Functions + case k_abs_IntrinsicKind: + return evaluate_intrinsic_numeric(context, arguments, returnType, + Intrinsics::evaluate_abs); + case k_sign_IntrinsicKind: + return evaluate_intrinsic_numeric(context, arguments, returnType, + Intrinsics::evaluate_sign); + case k_floor_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_floor); + case k_ceil_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_ceil); + case k_fract_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_fract); + case k_mod_IntrinsicKind: + return evaluate_pairwise_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_mod); + case k_min_IntrinsicKind: + return evaluate_pairwise_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_min); + case k_max_IntrinsicKind: + return evaluate_pairwise_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_max); + case k_clamp_IntrinsicKind: + return evaluate_3_way_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_clamp); + case k_fma_IntrinsicKind: + return evaluate_3_way_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_fma); + case k_saturate_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_saturate); + case k_mix_IntrinsicKind: + if (arguments[2]->type().componentType().isBoolean()) { + const SkSL::Type& numericType = arguments[0]->type().componentType(); + + if (numericType.isFloat()) { + type_check_expression<float>(*arguments[0]); + type_check_expression<float>(*arguments[1]); + } else if (numericType.isInteger()) { + type_check_expression<SKSL_INT>(*arguments[0]); + type_check_expression<SKSL_INT>(*arguments[1]); + } else if (numericType.isBoolean()) { + type_check_expression<bool>(*arguments[0]); + type_check_expression<bool>(*arguments[1]); + } else { + SkDEBUGFAILF("unsupported type %s", numericType.description().c_str()); + return nullptr; + } + return evaluate_n_way_intrinsic(context, arguments[0], arguments[1], arguments[2], + returnType, Intrinsics::evaluate_mix); + } else { + return evaluate_3_way_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_mix); + } + case k_step_IntrinsicKind: + return evaluate_pairwise_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_step); + case k_smoothstep_IntrinsicKind: + return evaluate_3_way_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_smoothstep); + case k_trunc_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_trunc); + case k_round_IntrinsicKind: // GLSL `round` documents its rounding mode as unspecified + case k_roundEven_IntrinsicKind: // and is allowed to behave identically to `roundEven`. + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_round); + case k_floatBitsToInt_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_floatBitsToInt); + case k_floatBitsToUint_IntrinsicKind: + return evaluate_intrinsic<float>(context, arguments, returnType, + Intrinsics::evaluate_floatBitsToUint); + case k_intBitsToFloat_IntrinsicKind: + return evaluate_intrinsic<SKSL_INT>(context, arguments, returnType, + Intrinsics::evaluate_intBitsToFloat); + case k_uintBitsToFloat_IntrinsicKind: + return evaluate_intrinsic<SKSL_INT>(context, arguments, returnType, + Intrinsics::evaluate_uintBitsToFloat); + // 8.4 : Floating-Point Pack and Unpack Functions + case k_packUnorm2x16_IntrinsicKind: { + auto Pack = [&](int n) -> unsigned int { + float x = Get(0, n); + return (int)std::round(Intrinsics::evaluate_clamp(x, 0.0, 1.0) * 65535.0); + }; + return UInt(((Pack(0) << 0) & 0x0000FFFF) | + ((Pack(1) << 16) & 0xFFFF0000)).release(); + } + case k_packSnorm2x16_IntrinsicKind: { + auto Pack = [&](int n) -> unsigned int { + float x = Get(0, n); + return (int)std::round(Intrinsics::evaluate_clamp(x, -1.0, 1.0) * 32767.0); + }; + return UInt(((Pack(0) << 0) & 0x0000FFFF) | + ((Pack(1) << 16) & 0xFFFF0000)).release(); + } + case k_packHalf2x16_IntrinsicKind: { + auto Pack = [&](int n) -> unsigned int { + return SkFloatToHalf(Get(0, n)); + }; + return UInt(((Pack(0) << 0) & 0x0000FFFF) | + ((Pack(1) << 16) & 0xFFFF0000)).release(); + } + case k_unpackUnorm2x16_IntrinsicKind: { + SKSL_INT x = *arguments[0]->getConstantValue(0); + uint16_t a = ((x >> 0) & 0x0000FFFF); + uint16_t b = ((x >> 16) & 0x0000FFFF); + return Float2(double(a) / 65535.0, + double(b) / 65535.0).release(); + } + case k_unpackSnorm2x16_IntrinsicKind: { + SKSL_INT x = *arguments[0]->getConstantValue(0); + int16_t a = ((x >> 0) & 0x0000FFFF); + int16_t b = ((x >> 16) & 0x0000FFFF); + return Float2(Intrinsics::evaluate_clamp(double(a) / 32767.0, -1.0, 1.0), + Intrinsics::evaluate_clamp(double(b) / 32767.0, -1.0, 1.0)).release(); + } + case k_unpackHalf2x16_IntrinsicKind: { + SKSL_INT x = *arguments[0]->getConstantValue(0); + uint16_t a = ((x >> 0) & 0x0000FFFF); + uint16_t b = ((x >> 16) & 0x0000FFFF); + return Float2(SkHalfToFloat(a), + SkHalfToFloat(b)).release(); + } + // 8.5 : Geometric Functions + case k_length_IntrinsicKind: + return coalesce_vector<float>(arguments, /*startingState=*/0, returnType, + Intrinsics::coalesce_length, + Intrinsics::finalize_length); + case k_distance_IntrinsicKind: + return coalesce_pairwise_vectors<float>(arguments, /*startingState=*/0, returnType, + Intrinsics::coalesce_distance, + Intrinsics::finalize_distance); + case k_dot_IntrinsicKind: + return coalesce_pairwise_vectors<float>(arguments, /*startingState=*/0, returnType, + Intrinsics::coalesce_dot, + /*finalize=*/nullptr); + case k_cross_IntrinsicKind: { + auto X = [&](int n) -> float { return Get(0, n); }; + auto Y = [&](int n) -> float { return Get(1, n); }; + SkASSERT(arguments[0]->type().columns() == 3); // the vec2 form is not a real intrinsic + + double vec[3] = {X(1) * Y(2) - Y(1) * X(2), + X(2) * Y(0) - Y(2) * X(0), + X(0) * Y(1) - Y(0) * X(1)}; + return assemble_compound(context, arguments[0]->fPosition, returnType, vec); + } + case k_normalize_IntrinsicKind: { + auto Vec = [&] { return DSLExpression{arguments[0]->clone()}; }; + return (Vec() / Length(Vec())).release(); + } + case k_faceforward_IntrinsicKind: { + auto N = [&] { return DSLExpression{arguments[0]->clone()}; }; + auto I = [&] { return DSLExpression{arguments[1]->clone()}; }; + auto NRef = [&] { return DSLExpression{arguments[2]->clone()}; }; + return (N() * Select(Dot(NRef(), I()) < 0, 1, -1)).release(); + } + case k_reflect_IntrinsicKind: { + auto I = [&] { return DSLExpression{arguments[0]->clone()}; }; + auto N = [&] { return DSLExpression{arguments[1]->clone()}; }; + return (I() - 2.0 * Dot(N(), I()) * N()).release(); + } + case k_refract_IntrinsicKind: { + // Refract uses its arguments out-of-order in such a way that we end up trying to create + // an invalid Position range, so we rewrite the arguments' positions to avoid that here. + auto clone = [&](const Expression* expr) { + return DSLExpression(expr->clone(pos)); + }; + auto I = [&] { return clone(arguments[0]); }; + auto N = [&] { return clone(arguments[1]); }; + auto Eta = [&] { return clone(arguments[2]); }; + + std::unique_ptr<Expression> k = + (1 - Pow(Eta(), 2) * (1 - Pow(Dot(N(), I()), 2))).release(); + if (!k->is<Literal>()) { + return nullptr; + } + double kValue = k->as<Literal>().value(); + return ((kValue < 0) ? + (0 * I()) : + (Eta() * I() - (Eta() * Dot(N(), I()) + std::sqrt(kValue)) * N())).release(); + } + + // 8.6 : Matrix Functions + case k_matrixCompMult_IntrinsicKind: + return evaluate_pairwise_intrinsic(context, arguments, returnType, + Intrinsics::evaluate_matrixCompMult); + case k_transpose_IntrinsicKind: { + double mat[16]; + int index = 0; + for (int c = 0; c < returnType.columns(); ++c) { + for (int r = 0; r < returnType.rows(); ++r) { + mat[index++] = Get(0, (returnType.columns() * r) + c); + } + } + return assemble_compound(context, arguments[0]->fPosition, returnType, mat); + } + case k_outerProduct_IntrinsicKind: { + double mat[16]; + int index = 0; + for (int c = 0; c < returnType.columns(); ++c) { + for (int r = 0; r < returnType.rows(); ++r) { + mat[index++] = Get(0, r) * Get(1, c); + } + } + return assemble_compound(context, arguments[0]->fPosition, returnType, mat); + } + case k_determinant_IntrinsicKind: { + float mat[16]; + extract_matrix(arguments[0], mat); + float determinant; + switch (arguments[0]->type().slotCount()) { + case 4: + determinant = SkInvert2x2Matrix(mat, /*outMatrix=*/nullptr); + break; + case 9: + determinant = SkInvert3x3Matrix(mat, /*outMatrix=*/nullptr); + break; + case 16: + determinant = SkInvert4x4Matrix(mat, /*outMatrix=*/nullptr); + break; + default: + SkDEBUGFAILF("unsupported type %s", arguments[0]->type().description().c_str()); + return nullptr; + } + return Literal::MakeFloat(arguments[0]->fPosition, determinant, &returnType); + } + case k_inverse_IntrinsicKind: { + float mat[16] = {}; + extract_matrix(arguments[0], mat); + switch (arguments[0]->type().slotCount()) { + case 4: + if (SkInvert2x2Matrix(mat, mat) == 0.0f) { + return nullptr; + } + break; + case 9: + if (SkInvert3x3Matrix(mat, mat) == 0.0f) { + return nullptr; + } + break; + case 16: + if (SkInvert4x4Matrix(mat, mat) == 0.0f) { + return nullptr; + } + break; + default: + SkDEBUGFAILF("unsupported type %s", arguments[0]->type().description().c_str()); + return nullptr; + } + + double dmat[16]; + std::copy(mat, mat + std::size(mat), dmat); + return assemble_compound(context, arguments[0]->fPosition, returnType, dmat); + } + // 8.7 : Vector Relational Functions + case k_lessThan_IntrinsicKind: + return optimize_comparison(context, arguments, Intrinsics::compare_lessThan); + + case k_lessThanEqual_IntrinsicKind: + return optimize_comparison(context, arguments, Intrinsics::compare_lessThanEqual); + + case k_greaterThan_IntrinsicKind: + return optimize_comparison(context, arguments, Intrinsics::compare_greaterThan); + + case k_greaterThanEqual_IntrinsicKind: + return optimize_comparison(context, arguments, Intrinsics::compare_greaterThanEqual); + + case k_equal_IntrinsicKind: + return optimize_comparison(context, arguments, Intrinsics::compare_equal); + + case k_notEqual_IntrinsicKind: + return optimize_comparison(context, arguments, Intrinsics::compare_notEqual); + + case k_any_IntrinsicKind: + return coalesce_vector<bool>(arguments, /*startingState=*/false, returnType, + Intrinsics::coalesce_any, + /*finalize=*/nullptr); + case k_all_IntrinsicKind: + return coalesce_vector<bool>(arguments, /*startingState=*/true, returnType, + Intrinsics::coalesce_all, + /*finalize=*/nullptr); + case k_not_IntrinsicKind: + return evaluate_intrinsic<bool>(context, arguments, returnType, + Intrinsics::evaluate_not); + default: + return nullptr; + } +} + +std::unique_ptr<Expression> FunctionCall::clone(Position pos) const { + return std::make_unique<FunctionCall>(pos, &this->type(), &this->function(), + this->arguments().clone()); +} + +std::string FunctionCall::description(OperatorPrecedence) const { + std::string result = std::string(this->function().name()) + "("; + auto separator = SkSL::String::Separator(); + for (const std::unique_ptr<Expression>& arg : this->arguments()) { + result += separator(); + result += arg->description(OperatorPrecedence::kSequence); + } + result += ")"; + return result; +} + +/** + * Determines the cost of coercing the arguments of a function to the required types. Cost has no + * particular meaning other than "lower costs are preferred". Returns CoercionCost::Impossible() if + * the call is not valid. + */ +static CoercionCost call_cost(const Context& context, + const FunctionDeclaration& function, + const ExpressionArray& arguments) { + if (context.fConfig->strictES2Mode() && + (function.modifiers().fFlags & Modifiers::kES3_Flag)) { + return CoercionCost::Impossible(); + } + if (function.parameters().size() != SkToSizeT(arguments.size())) { + return CoercionCost::Impossible(); + } + FunctionDeclaration::ParamTypes types; + const Type* ignored; + if (!function.determineFinalTypes(arguments, &types, &ignored)) { + return CoercionCost::Impossible(); + } + CoercionCost total = CoercionCost::Free(); + for (int i = 0; i < arguments.size(); i++) { + total = total + arguments[i]->coercionCost(*types[i]); + } + return total; +} + +const FunctionDeclaration* FunctionCall::FindBestFunctionForCall( + const Context& context, + const FunctionDeclaration* overloadChain, + const ExpressionArray& arguments) { + if (!overloadChain->nextOverload()) { + return overloadChain; + } + CoercionCost bestCost = CoercionCost::Impossible(); + const FunctionDeclaration* best = nullptr; + for (const FunctionDeclaration* f = overloadChain; f != nullptr; f = f->nextOverload()) { + CoercionCost cost = call_cost(context, *f, arguments); + if (cost <= bestCost) { + bestCost = cost; + best = f; + } + } + return bestCost.fImpossible ? nullptr : best; +} + +static std::string build_argument_type_list(SkSpan<const std::unique_ptr<Expression>> arguments) { + std::string result = "("; + auto separator = SkSL::String::Separator(); + for (const std::unique_ptr<Expression>& arg : arguments) { + result += separator(); + result += arg->type().displayName(); + } + return result + ")"; +} + +std::unique_ptr<Expression> FunctionCall::Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> functionValue, + ExpressionArray arguments) { + switch (functionValue->kind()) { + case Expression::Kind::kTypeReference: + return Constructor::Convert(context, + pos, + functionValue->as<TypeReference>().value(), + std::move(arguments)); + case Expression::Kind::kFunctionReference: { + const FunctionReference& ref = functionValue->as<FunctionReference>(); + const FunctionDeclaration* best = FindBestFunctionForCall(context, ref.overloadChain(), + arguments); + if (best) { + return FunctionCall::Convert(context, pos, *best, std::move(arguments)); + } + std::string msg = "no match for " + std::string(ref.overloadChain()->name()) + + build_argument_type_list(arguments); + context.fErrors->error(pos, msg); + return nullptr; + } + case Expression::Kind::kMethodReference: { + MethodReference& ref = functionValue->as<MethodReference>(); + arguments.push_back(std::move(ref.self())); + + const FunctionDeclaration* best = FindBestFunctionForCall(context, ref.overloadChain(), + arguments); + if (best) { + return FunctionCall::Convert(context, pos, *best, std::move(arguments)); + } + std::string msg = + "no match for " + arguments.back()->type().displayName() + + "::" + std::string(ref.overloadChain()->name().substr(1)) + + build_argument_type_list(SkSpan(arguments).first(arguments.size() - 1)); + context.fErrors->error(pos, msg); + return nullptr; + } + case Expression::Kind::kPoison: + functionValue->fPosition = pos; + return functionValue; + default: + context.fErrors->error(pos, "not a function"); + return nullptr; + } +} + +std::unique_ptr<Expression> FunctionCall::Convert(const Context& context, + Position pos, + const FunctionDeclaration& function, + ExpressionArray arguments) { + // Reject ES3 function calls in strict ES2 mode. + if (context.fConfig->strictES2Mode() && (function.modifiers().fFlags & Modifiers::kES3_Flag)) { + context.fErrors->error(pos, "call to '" + function.description() + "' is not supported"); + return nullptr; + } + + // Reject function calls with the wrong number of arguments. + if (function.parameters().size() != SkToSizeT(arguments.size())) { + std::string msg = "call to '" + std::string(function.name()) + "' expected " + + std::to_string(function.parameters().size()) + " argument"; + if (function.parameters().size() != 1) { + msg += "s"; + } + msg += ", but found " + std::to_string(arguments.size()); + context.fErrors->error(pos, msg); + return nullptr; + } + + // Resolve generic types. + FunctionDeclaration::ParamTypes types; + const Type* returnType; + if (!function.determineFinalTypes(arguments, &types, &returnType)) { + std::string msg = "no match for " + std::string(function.name()) + + build_argument_type_list(arguments); + context.fErrors->error(pos, msg); + return nullptr; + } + + for (int i = 0; i < arguments.size(); i++) { + // Coerce each argument to the proper type. + arguments[i] = types[i]->coerceExpression(std::move(arguments[i]), context); + if (!arguments[i]) { + return nullptr; + } + // Update the refKind on out-parameters, and ensure that they are actually assignable. + const Modifiers& paramModifiers = function.parameters()[i]->modifiers(); + if (paramModifiers.fFlags & Modifiers::kOut_Flag) { + const VariableRefKind refKind = paramModifiers.fFlags & Modifiers::kIn_Flag + ? VariableReference::RefKind::kReadWrite + : VariableReference::RefKind::kPointer; + if (!Analysis::UpdateVariableRefKind(arguments[i].get(), refKind, context.fErrors)) { + return nullptr; + } + } + // TODO(skia:13609): Make sure that we don't pass writeonly objects to readonly parameters, + // or vice-versa. + } + + if (function.isMain()) { + context.fErrors->error(pos, "call to 'main' is not allowed"); + return nullptr; + } + + if (function.intrinsicKind() == k_eval_IntrinsicKind) { + // This is a method call on an effect child. Translate it into a ChildCall, which simplifies + // handling in the generators and analysis code. + const Variable& child = *arguments.back()->as<VariableReference>().variable(); + arguments.pop_back(); + return ChildCall::Make(context, pos, returnType, child, std::move(arguments)); + } + + return Make(context, pos, returnType, function, std::move(arguments)); +} + +std::unique_ptr<Expression> FunctionCall::Make(const Context& context, + Position pos, + const Type* returnType, + const FunctionDeclaration& function, + ExpressionArray arguments) { + SkASSERT(function.parameters().size() == SkToSizeT(arguments.size())); + + // We might be able to optimize built-in intrinsics. + if (function.isIntrinsic() && has_compile_time_constant_arguments(arguments)) { + // The function is an intrinsic and all inputs are compile-time constants. Optimize it. + if (std::unique_ptr<Expression> expr = optimize_intrinsic_call(context, + pos, + function.intrinsicKind(), + arguments, + *returnType)) { + expr->fPosition = pos; + return expr; + } + } + + return std::make_unique<FunctionCall>(pos, returnType, &function, std::move(arguments)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h new file mode 100644 index 0000000000..9f31a52772 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionCall.h @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONCALL +#define SKSL_FUNCTIONCALL + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +class FunctionDeclaration; +class Type; +enum class OperatorPrecedence : uint8_t; + +/** + * A function invocation. + */ +class FunctionCall final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kFunctionCall; + + FunctionCall(Position pos, const Type* type, const FunctionDeclaration* function, + ExpressionArray arguments) + : INHERITED(pos, kIRNodeKind, type) + , fFunction(*function) + , fArguments(std::move(arguments)) {} + + // Resolves generic types, performs type conversion on arguments, determines return type, and + // reports errors via the ErrorReporter. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + const FunctionDeclaration& function, + ExpressionArray arguments); + + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> functionValue, + ExpressionArray arguments); + + // Creates the function call; reports errors via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + const Type* returnType, + const FunctionDeclaration& function, + ExpressionArray arguments); + + static const FunctionDeclaration* FindBestFunctionForCall(const Context& context, + const FunctionDeclaration* overloads, + const ExpressionArray& arguments); + + const FunctionDeclaration& function() const { + return fFunction; + } + + ExpressionArray& arguments() { + return fArguments; + } + + const ExpressionArray& arguments() const { + return fArguments; + } + + std::unique_ptr<Expression> clone(Position pos) const override; + + std::string description(OperatorPrecedence) const override; + +private: + const FunctionDeclaration& fFunction; + ExpressionArray fArguments; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp new file mode 100644 index 0000000000..036bfac02e --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp @@ -0,0 +1,598 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramKind.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTo.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <algorithm> +#include <cstddef> +#include <utility> + +namespace SkSL { + +static bool check_modifiers(const Context& context, + Position pos, + const Modifiers& modifiers) { + const int permitted = Modifiers::kInline_Flag | + Modifiers::kNoInline_Flag | + (context.fConfig->fIsBuiltinCode ? (Modifiers::kES3_Flag | + Modifiers::kPure_Flag | + Modifiers::kExport_Flag) : 0); + modifiers.checkPermitted(context, pos, permitted, /*permittedLayoutFlags=*/0); + if ((modifiers.fFlags & Modifiers::kInline_Flag) && + (modifiers.fFlags & Modifiers::kNoInline_Flag)) { + context.fErrors->error(pos, "functions cannot be both 'inline' and 'noinline'"); + return false; + } + return true; +} + +static bool check_return_type(const Context& context, Position pos, const Type& returnType) { + ErrorReporter& errors = *context.fErrors; + if (returnType.isArray()) { + errors.error(pos, "functions may not return type '" + returnType.displayName() + "'"); + return false; + } + if (context.fConfig->strictES2Mode() && returnType.isOrContainsArray()) { + errors.error(pos, "functions may not return structs containing arrays"); + return false; + } + if (!context.fConfig->fIsBuiltinCode && returnType.componentType().isOpaque()) { + errors.error(pos, "functions may not return opaque type '" + returnType.displayName() + + "'"); + return false; + } + return true; +} + +static bool check_parameters(const Context& context, + std::vector<std::unique_ptr<Variable>>& parameters, + bool isMain) { + auto typeIsValidForColor = [&](const Type& type) { + return type.matches(*context.fTypes.fHalf4) || type.matches(*context.fTypes.fFloat4); + }; + + // The first color parameter passed to main() is the input color; the second is the dest color. + static constexpr int kBuiltinColorIDs[] = {SK_INPUT_COLOR_BUILTIN, SK_DEST_COLOR_BUILTIN}; + unsigned int builtinColorIndex = 0; + + // Check modifiers on each function parameter. + for (auto& param : parameters) { + const Type& type = param->type(); + int permittedFlags = Modifiers::kConst_Flag | Modifiers::kIn_Flag; + if (!type.isOpaque()) { + permittedFlags |= Modifiers::kOut_Flag; + } + if (type.typeKind() == Type::TypeKind::kTexture) { + permittedFlags |= Modifiers::kReadOnly_Flag | Modifiers::kWriteOnly_Flag; + } + param->modifiers().checkPermitted(context, + param->modifiersPosition(), + permittedFlags, + /*permittedLayoutFlags=*/0); + // Only the (builtin) declarations of 'sample' are allowed to have shader/colorFilter or FP + // parameters. You can pass other opaque types to functions safely; this restriction is + // specific to "child" objects. + if (type.isEffectChild() && !context.fConfig->fIsBuiltinCode) { + context.fErrors->error(param->fPosition, "parameters of type '" + type.displayName() + + "' not allowed"); + return false; + } + + Modifiers m = param->modifiers(); + bool modifiersChanged = false; + + // The `in` modifier on function parameters is implicit, so we can replace `in float x` with + // `float x`. This prevents any ambiguity when matching a function by its param types. + if (Modifiers::kIn_Flag == (m.fFlags & (Modifiers::kOut_Flag | Modifiers::kIn_Flag))) { + m.fFlags &= ~(Modifiers::kOut_Flag | Modifiers::kIn_Flag); + modifiersChanged = true; + } + + if (isMain) { + if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind) && + context.fConfig->fKind != ProgramKind::kMeshFragment && + context.fConfig->fKind != ProgramKind::kMeshVertex) { + // We verify that the signature is fully correct later. For now, if this is a + // runtime effect of any flavor, a float2 param is supposed to be the coords, and a + // half4/float parameter is supposed to be the input or destination color: + if (type.matches(*context.fTypes.fFloat2)) { + m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN; + modifiersChanged = true; + } else if (typeIsValidForColor(type) && + builtinColorIndex < std::size(kBuiltinColorIDs)) { + m.fLayout.fBuiltin = kBuiltinColorIDs[builtinColorIndex++]; + modifiersChanged = true; + } + } else if (ProgramConfig::IsFragment(context.fConfig->fKind)) { + // For testing purposes, we have .sksl inputs that are treated as both runtime + // effects and fragment shaders. To make that work, fragment shaders are allowed to + // have a coords parameter. + if (type.matches(*context.fTypes.fFloat2)) { + m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN; + modifiersChanged = true; + } + } + } + + if (modifiersChanged) { + param->setModifiers(context.fModifiersPool->add(m)); + } + } + return true; +} + +static bool check_main_signature(const Context& context, Position pos, const Type& returnType, + std::vector<std::unique_ptr<Variable>>& parameters) { + ErrorReporter& errors = *context.fErrors; + ProgramKind kind = context.fConfig->fKind; + + auto typeIsValidForColor = [&](const Type& type) { + return type.matches(*context.fTypes.fHalf4) || type.matches(*context.fTypes.fFloat4); + }; + + auto typeIsValidForAttributes = [&](const Type& type) { + return type.isStruct() && type.name() == "Attributes"; + }; + + auto typeIsValidForVaryings = [&](const Type& type) { + return type.isStruct() && type.name() == "Varyings"; + }; + + auto paramIsCoords = [&](int idx) { + const Variable& p = *parameters[idx]; + return p.type().matches(*context.fTypes.fFloat2) && + p.modifiers().fFlags == 0 && + p.modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN; + }; + + auto paramIsBuiltinColor = [&](int idx, int builtinID) { + const Variable& p = *parameters[idx]; + return typeIsValidForColor(p.type()) && + p.modifiers().fFlags == 0 && + p.modifiers().fLayout.fBuiltin == builtinID; + }; + + auto paramIsConstInAttributes = [&](int idx) { + const Variable& p = *parameters[idx]; + return typeIsValidForAttributes(p.type()) && p.modifiers().fFlags == Modifiers::kConst_Flag; + }; + + auto paramIsConstInVaryings = [&](int idx) { + const Variable& p = *parameters[idx]; + return typeIsValidForVaryings(p.type()) && p.modifiers().fFlags == Modifiers::kConst_Flag; + }; + + auto paramIsOutColor = [&](int idx) { + const Variable& p = *parameters[idx]; + return typeIsValidForColor(p.type()) && p.modifiers().fFlags == Modifiers::kOut_Flag; + }; + + auto paramIsInputColor = [&](int n) { return paramIsBuiltinColor(n, SK_INPUT_COLOR_BUILTIN); }; + auto paramIsDestColor = [&](int n) { return paramIsBuiltinColor(n, SK_DEST_COLOR_BUILTIN); }; + + switch (kind) { + case ProgramKind::kRuntimeColorFilter: + case ProgramKind::kPrivateRuntimeColorFilter: { + // (half4|float4) main(half4|float4) + if (!typeIsValidForColor(returnType)) { + errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); + return false; + } + bool validParams = (parameters.size() == 1 && paramIsInputColor(0)); + if (!validParams) { + errors.error(pos, "'main' parameter must be 'vec4', 'float4', or 'half4'"); + return false; + } + break; + } + case ProgramKind::kRuntimeShader: + case ProgramKind::kPrivateRuntimeShader: { + // (half4|float4) main(float2) + if (!typeIsValidForColor(returnType)) { + errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); + return false; + } + if (!(parameters.size() == 1 && paramIsCoords(0))) { + errors.error(pos, "'main' parameter must be 'float2' or 'vec2'"); + return false; + } + break; + } + case ProgramKind::kRuntimeBlender: + case ProgramKind::kPrivateRuntimeBlender: { + // (half4|float4) main(half4|float4, half4|float4) + if (!typeIsValidForColor(returnType)) { + errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); + return false; + } + if (!(parameters.size() == 2 && + paramIsInputColor(0) && + paramIsDestColor(1))) { + errors.error(pos, "'main' parameters must be (vec4|float4|half4, " + "vec4|float4|half4)"); + return false; + } + break; + } + case ProgramKind::kMeshVertex: { + // Varyings main(const Attributes) + if (!typeIsValidForVaryings(returnType)) { + errors.error(pos, "'main' must return 'Varyings'."); + return false; + } + if (!(parameters.size() == 1 && paramIsConstInAttributes(0))) { + errors.error(pos, "'main' parameter must be 'const Attributes'."); + return false; + } + break; + } + case ProgramKind::kMeshFragment: { + // float2 main(const Varyings) -or- float2 main(const Varyings, out half4|float4) + if (!returnType.matches(*context.fTypes.fFloat2)) { + errors.error(pos, "'main' must return: 'vec2' or 'float2'"); + return false; + } + if (!((parameters.size() == 1 && paramIsConstInVaryings(0)) || + (parameters.size() == 2 && paramIsConstInVaryings(0) && paramIsOutColor(1)))) { + errors.error(pos, + "'main' parameters must be (const Varyings, (out (half4|float4))?)"); + return false; + } + break; + } + case ProgramKind::kFragment: + case ProgramKind::kGraphiteFragment: { + bool validParams = (parameters.size() == 0) || + (parameters.size() == 1 && paramIsCoords(0)); + if (!validParams) { + errors.error(pos, "shader 'main' must be main() or main(float2)"); + return false; + } + break; + } + case ProgramKind::kVertex: + case ProgramKind::kGraphiteVertex: + case ProgramKind::kCompute: + if (!returnType.matches(*context.fTypes.fVoid)) { + errors.error(pos, "'main' must return 'void'"); + return false; + } + if (parameters.size()) { + errors.error(pos, "shader 'main' must have zero parameters"); + return false; + } + break; + } + return true; +} + +/** + * Given a concrete type (`float3`) and a generic type (`$genType`), returns the index of the + * concrete type within the generic type's typelist. Returns -1 if there is no match. + */ +static int find_generic_index(const Type& concreteType, + const Type& genericType, + bool allowNarrowing) { + SkSpan<const Type* const> genericTypes = genericType.coercibleTypes(); + for (size_t index = 0; index < genericTypes.size(); ++index) { + if (concreteType.canCoerceTo(*genericTypes[index], allowNarrowing)) { + return index; + } + } + return -1; +} + +/** Returns true if the types match, or if `concreteType` can be found in `maybeGenericType`. */ +static bool type_generically_matches(const Type& concreteType, const Type& maybeGenericType) { + return maybeGenericType.isGeneric() + ? find_generic_index(concreteType, maybeGenericType, /*allowNarrowing=*/false) != -1 + : concreteType.matches(maybeGenericType); +} + +/** + * Checks a parameter list (params) against the parameters of a function that was declared earlier + * (otherParams). Returns true if they match, even if the parameters in `otherParams` contain + * generic types. + */ +static bool parameters_match(const std::vector<std::unique_ptr<Variable>>& params, + const std::vector<Variable*>& otherParams) { + // If the param lists are different lengths, they're definitely not a match. + if (params.size() != otherParams.size()) { + return false; + } + + // Figure out a consistent generic index (or bail if we find a contradiction). + int genericIndex = -1; + for (size_t i = 0; i < params.size(); ++i) { + const Type* paramType = ¶ms[i]->type(); + const Type* otherParamType = &otherParams[i]->type(); + + if (otherParamType->isGeneric()) { + int genericIndexForThisParam = find_generic_index(*paramType, *otherParamType, + /*allowNarrowing=*/false); + if (genericIndexForThisParam == -1) { + // The type wasn't a match for this generic at all; these params can't be a match. + return false; + } + if (genericIndex != -1 && genericIndex != genericIndexForThisParam) { + // The generic index mismatches from what we determined on a previous parameter. + return false; + } + genericIndex = genericIndexForThisParam; + } + } + + // Now that we've determined a generic index (if we needed one), do a parameter check. + for (size_t i = 0; i < params.size(); i++) { + const Type* paramType = ¶ms[i]->type(); + const Type* otherParamType = &otherParams[i]->type(); + + // Make generic types concrete. + if (otherParamType->isGeneric()) { + SkASSERT(genericIndex != -1); + SkASSERT(genericIndex < (int)otherParamType->coercibleTypes().size()); + otherParamType = otherParamType->coercibleTypes()[genericIndex]; + } + // Detect type mismatches. + if (!paramType->matches(*otherParamType)) { + return false; + } + } + return true; +} + +/** + * Checks for a previously existing declaration of this function, reporting errors if there is an + * incompatible symbol. Returns true and sets outExistingDecl to point to the existing declaration + * (or null if none) on success, returns false on error. + */ +static bool find_existing_declaration(const Context& context, + SymbolTable& symbols, + Position pos, + const Modifiers* modifiers, + std::string_view name, + std::vector<std::unique_ptr<Variable>>& parameters, + Position returnTypePos, + const Type* returnType, + FunctionDeclaration** outExistingDecl) { + auto invalidDeclDescription = [&]() -> std::string { + std::vector<Variable*> paramPtrs; + paramPtrs.reserve(parameters.size()); + for (std::unique_ptr<Variable>& param : parameters) { + paramPtrs.push_back(param.get()); + } + return FunctionDeclaration(pos, + modifiers, + name, + std::move(paramPtrs), + returnType, + context.fConfig->fIsBuiltinCode) + .description(); + }; + + ErrorReporter& errors = *context.fErrors; + Symbol* entry = symbols.findMutable(name); + *outExistingDecl = nullptr; + if (entry) { + if (!entry->is<FunctionDeclaration>()) { + errors.error(pos, "symbol '" + std::string(name) + "' was already defined"); + return false; + } + for (FunctionDeclaration* other = &entry->as<FunctionDeclaration>(); other; + other = other->mutableNextOverload()) { + SkASSERT(name == other->name()); + if (!parameters_match(parameters, other->parameters())) { + continue; + } + if (!type_generically_matches(*returnType, other->returnType())) { + errors.error(returnTypePos, + "functions '" + invalidDeclDescription() + "' and '" + + other->description() + "' differ only in return type"); + return false; + } + for (size_t i = 0; i < parameters.size(); i++) { + if (parameters[i]->modifiers() != other->parameters()[i]->modifiers()) { + errors.error(parameters[i]->fPosition, + "modifiers on parameter " + std::to_string(i + 1) + + " differ between declaration and definition"); + return false; + } + } + if (*modifiers != other->modifiers() || other->definition() || other->isIntrinsic()) { + errors.error(pos, "duplicate definition of '" + invalidDeclDescription() + "'"); + return false; + } + *outExistingDecl = other; + break; + } + if (!*outExistingDecl && entry->as<FunctionDeclaration>().isMain()) { + errors.error(pos, "duplicate definition of 'main'"); + return false; + } + } + return true; +} + +FunctionDeclaration::FunctionDeclaration(Position pos, + const Modifiers* modifiers, + std::string_view name, + std::vector<Variable*> parameters, + const Type* returnType, + bool builtin) + : INHERITED(pos, kIRNodeKind, name, /*type=*/nullptr) + , fDefinition(nullptr) + , fModifiers(modifiers) + , fParameters(std::move(parameters)) + , fReturnType(returnType) + , fBuiltin(builtin) + , fIsMain(name == "main") + , fIntrinsicKind(builtin ? FindIntrinsicKind(name) : kNotIntrinsic) { + // None of the parameters are allowed to be be null. + SkASSERT(std::count(fParameters.begin(), fParameters.end(), nullptr) == 0); +} + +FunctionDeclaration* FunctionDeclaration::Convert(const Context& context, + SymbolTable& symbols, + Position pos, + Position modifiersPosition, + const Modifiers* modifiers, + std::string_view name, + std::vector<std::unique_ptr<Variable>> parameters, + Position returnTypePos, + const Type* returnType) { + bool isMain = (name == "main"); + + FunctionDeclaration* decl = nullptr; + if (!check_modifiers(context, modifiersPosition, *modifiers) || + !check_return_type(context, returnTypePos, *returnType) || + !check_parameters(context, parameters, isMain) || + (isMain && !check_main_signature(context, pos, *returnType, parameters)) || + !find_existing_declaration(context, symbols, pos, modifiers, name, parameters, + returnTypePos, returnType, &decl)) { + return nullptr; + } + std::vector<Variable*> finalParameters; + finalParameters.reserve(parameters.size()); + for (std::unique_ptr<Variable>& param : parameters) { + finalParameters.push_back(symbols.takeOwnershipOfSymbol(std::move(param))); + } + if (decl) { + return decl; + } + auto result = std::make_unique<FunctionDeclaration>(pos, + modifiers, + name, + std::move(finalParameters), + returnType, + context.fConfig->fIsBuiltinCode); + return symbols.add(std::move(result)); +} + +std::string FunctionDeclaration::mangledName() const { + if ((this->isBuiltin() && !this->definition()) || this->isMain()) { + // Builtins without a definition (like `sin` or `sqrt`) must use their real names. + return std::string(this->name()); + } + // Built-in functions can have a $ prefix, which will fail to compile in GLSL. Remove the + // $ and add a unique mangling specifier, so user code can't conflict with the name. + std::string_view name = this->name(); + const char* builtinMarker = ""; + if (skstd::starts_with(name, '$')) { + name.remove_prefix(1); + builtinMarker = "Q"; // a unique, otherwise-unused mangle character + } + // Rename function to `funcname_returntypeparamtypes`. + std::string result = std::string(name) + "_" + builtinMarker + + this->returnType().abbreviatedName(); + for (const Variable* p : this->parameters()) { + result += p->type().abbreviatedName(); + } + return result; +} + +std::string FunctionDeclaration::description() const { + int modifierFlags = this->modifiers().fFlags; + std::string result = + (modifierFlags ? Modifiers::DescribeFlags(modifierFlags) + " " : std::string()) + + this->returnType().displayName() + " " + std::string(this->name()) + "("; + auto separator = SkSL::String::Separator(); + for (const Variable* p : this->parameters()) { + result += separator(); + // We can't just say `p->description()` here, because occasionally might have added layout + // flags onto parameters (like `layout(builtin=10009)`) and don't want to reproduce that. + if (p->modifiers().fFlags) { + result += Modifiers::DescribeFlags(p->modifiers().fFlags) + " "; + } + result += p->type().displayName(); + result += " "; + result += p->name(); + } + result += ")"; + return result; +} + +bool FunctionDeclaration::matches(const FunctionDeclaration& f) const { + if (this->name() != f.name()) { + return false; + } + const std::vector<Variable*>& parameters = this->parameters(); + const std::vector<Variable*>& otherParameters = f.parameters(); + if (parameters.size() != otherParameters.size()) { + return false; + } + for (size_t i = 0; i < parameters.size(); i++) { + if (!parameters[i]->type().matches(otherParameters[i]->type())) { + return false; + } + } + return true; +} + +bool FunctionDeclaration::determineFinalTypes(const ExpressionArray& arguments, + ParamTypes* outParameterTypes, + const Type** outReturnType) const { + const std::vector<Variable*>& parameters = this->parameters(); + SkASSERT(SkToSizeT(arguments.size()) == parameters.size()); + + outParameterTypes->reserve_back(arguments.size()); + int genericIndex = -1; + for (int i = 0; i < arguments.size(); i++) { + // Non-generic parameters are final as-is. + const Type& parameterType = parameters[i]->type(); + if (!parameterType.isGeneric()) { + outParameterTypes->push_back(¶meterType); + continue; + } + // We use the first generic parameter we find to lock in the generic index; + // e.g. if we find `float3` here, all `$genType`s will be assumed to be `float3`. + if (genericIndex == -1) { + genericIndex = find_generic_index(arguments[i]->type(), parameterType, + /*allowNarrowing=*/true); + if (genericIndex == -1) { + // The passed-in type wasn't a match for ANY of the generic possibilities. + // This function isn't a match at all. + return false; + } + } + outParameterTypes->push_back(parameterType.coercibleTypes()[genericIndex]); + } + // Apply the generic index to our return type. + const Type& returnType = this->returnType(); + if (returnType.isGeneric()) { + if (genericIndex == -1) { + // We don't support functions with a generic return type and no other generics. + return false; + } + *outReturnType = returnType.coercibleTypes()[genericIndex]; + } else { + *outReturnType = &returnType; + } + return true; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h new file mode 100644 index 0000000000..462456c1ea --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.h @@ -0,0 +1,153 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONDECLARATION +#define SKSL_FUNCTIONDECLARATION + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLSymbol.h" +#include "include/private/base/SkTArray.h" +#include "src/sksl/SkSLIntrinsicList.h" + +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +namespace SkSL { + +class Context; +class ExpressionArray; +class FunctionDefinition; +class Position; +class SymbolTable; +class Type; +class Variable; + +struct Modifiers; + +/** + * A function declaration (not a definition -- does not contain a body). + */ +class FunctionDeclaration final : public Symbol { +public: + inline static constexpr Kind kIRNodeKind = Kind::kFunctionDeclaration; + + FunctionDeclaration(Position pos, + const Modifiers* modifiers, + std::string_view name, + std::vector<Variable*> parameters, + const Type* returnType, + bool builtin); + + static FunctionDeclaration* Convert(const Context& context, + SymbolTable& symbols, + Position pos, + Position modifiersPos, + const Modifiers* modifiers, + std::string_view name, + std::vector<std::unique_ptr<Variable>> parameters, + Position returnTypePos, + const Type* returnType); + + const Modifiers& modifiers() const { + return *fModifiers; + } + + void setModifiers(const Modifiers* m) { + fModifiers = m; + } + + const FunctionDefinition* definition() const { + return fDefinition; + } + + void setDefinition(const FunctionDefinition* definition) { + fDefinition = definition; + fIntrinsicKind = kNotIntrinsic; + } + + void setNextOverload(FunctionDeclaration* overload) { + SkASSERT(!overload || overload->name() == this->name()); + fNextOverload = overload; + } + + const std::vector<Variable*>& parameters() const { + return fParameters; + } + + const Type& returnType() const { + return *fReturnType; + } + + bool isBuiltin() const { + return fBuiltin; + } + + bool isMain() const { + return fIsMain; + } + + IntrinsicKind intrinsicKind() const { + return fIntrinsicKind; + } + + bool isIntrinsic() const { + return this->intrinsicKind() != kNotIntrinsic; + } + + const FunctionDeclaration* nextOverload() const { + return fNextOverload; + } + + FunctionDeclaration* mutableNextOverload() const { + return fNextOverload; + } + + std::string mangledName() const; + + std::string description() const override; + + bool matches(const FunctionDeclaration& f) const; + + /** + * Determine the effective types of this function's parameters and return value when called with + * the given arguments. This is relevant for functions with generic parameter types, where this + * will collapse the generic types down into specific concrete types. + * + * Returns true if it was able to select a concrete set of types for the generic function, false + * if there is no possible way this can match the argument types. Note that even a true return + * does not guarantee that the function can be successfully called with those arguments, merely + * indicates that an attempt should be made. If false is returned, the state of + * outParameterTypes and outReturnType are undefined. + * + * This always assumes narrowing conversions are *allowed*. The calling code needs to verify + * that each argument can actually be coerced to the final parameter type, respecting the + * narrowing-conversions flag. This is handled in callCost(), or in convertCall() (via coerce). + */ + using ParamTypes = SkSTArray<8, const Type*>; + bool determineFinalTypes(const ExpressionArray& arguments, + ParamTypes* outParameterTypes, + const Type** outReturnType) const; + +private: + const FunctionDefinition* fDefinition; + FunctionDeclaration* fNextOverload = nullptr; + const Modifiers* fModifiers; + std::vector<Variable*> fParameters; + const Type* fReturnType; + bool fBuiltin; + bool fIsMain; + mutable IntrinsicKind fIntrinsicKind = kNotIntrinsic; + + using INHERITED = Symbol; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.cpp new file mode 100644 index 0000000000..b33a4352a6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.cpp @@ -0,0 +1,246 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLFunctionDefinition.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLSymbol.h" +#include "include/sksl/DSLCore.h" +#include "include/sksl/DSLExpression.h" +#include "include/sksl/DSLStatement.h" +#include "include/sksl/DSLType.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/base/SkSafeMath.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLField.h" +#include "src/sksl/ir/SkSLFieldAccess.h" +#include "src/sksl/ir/SkSLReturnStatement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/transform/SkSLProgramWriter.h" + +#include <algorithm> +#include <cstddef> +#include <forward_list> +#include <string_view> +#include <vector> + +namespace SkSL { + +static void append_rtadjust_fixup_to_vertex_main(const Context& context, + const FunctionDeclaration& decl, + Block& body) { + using namespace SkSL::dsl; + using SkSL::dsl::Swizzle; // disambiguate from SkSL::Swizzle + using OwnerKind = SkSL::FieldAccess::OwnerKind; + + // If this program uses RTAdjust... + ThreadContext::RTAdjustData& rtAdjust = ThreadContext::RTAdjustState(); + if (rtAdjust.fVar || rtAdjust.fInterfaceBlock) { + // ...append a line to the end of the function body which fixes up sk_Position. + const SymbolTable* symbolTable = ThreadContext::SymbolTable().get(); + const Field& skPositionField = symbolTable->find(Compiler::POSITION_NAME)->as<Field>(); + + auto Ref = [](const Variable* var) -> std::unique_ptr<Expression> { + return VariableReference::Make(Position(), var); + }; + auto Field = [&](const Variable* var, int idx) -> std::unique_ptr<Expression> { + return FieldAccess::Make(context, Position(), Ref(var), idx, + OwnerKind::kAnonymousInterfaceBlock); + }; + auto Pos = [&]() -> DSLExpression { + return DSLExpression(Field(&skPositionField.owner(), skPositionField.fieldIndex())); + }; + auto Adjust = [&]() -> DSLExpression { + return DSLExpression(rtAdjust.fInterfaceBlock + ? Field(rtAdjust.fInterfaceBlock, rtAdjust.fFieldIndex) + : Ref(rtAdjust.fVar)); + }; + + auto fixupStmt = DSLStatement( + Pos().assign(Float4(Swizzle(Pos(), X, Y) * Swizzle(Adjust(), X, Z) + + Swizzle(Pos(), W, W) * Swizzle(Adjust(), Y, W), + 0, + Pos().w())) + ); + + body.children().push_back(fixupStmt.release()); + } +} + +std::unique_ptr<FunctionDefinition> FunctionDefinition::Convert(const Context& context, + Position pos, + const FunctionDeclaration& function, + std::unique_ptr<Statement> body, + bool builtin) { + class Finalizer : public ProgramWriter { + public: + Finalizer(const Context& context, const FunctionDeclaration& function, Position pos) + : fContext(context) + , fFunction(function) { + // Function parameters count as local variables. + for (const Variable* var : function.parameters()) { + this->addLocalVariable(var, pos); + } + } + + void addLocalVariable(const Variable* var, Position pos) { + // We count the number of slots used, but don't consider the precision of the base type. + // In practice, this reflects what GPUs actually do pretty well. (i.e., RelaxedPrecision + // math doesn't mean your variable takes less space.) We also don't attempt to reclaim + // slots at the end of a Block. + size_t prevSlotsUsed = fSlotsUsed; + fSlotsUsed = SkSafeMath::Add(fSlotsUsed, var->type().slotCount()); + // To avoid overzealous error reporting, only trigger the error at the first + // place where the stack limit is exceeded. + if (prevSlotsUsed < kVariableSlotLimit && fSlotsUsed >= kVariableSlotLimit) { + fContext.fErrors->error(pos, "variable '" + std::string(var->name()) + + "' exceeds the stack size limit"); + } + } + + ~Finalizer() override { + SkASSERT(fBreakableLevel == 0); + SkASSERT(fContinuableLevel == std::forward_list<int>{0}); + } + + bool functionReturnsValue() const { + return !fFunction.returnType().isVoid(); + } + + bool visitExpression(Expression& expr) override { + // We don't need to scan expressions. + return false; + } + + bool visitStatement(Statement& stmt) override { + switch (stmt.kind()) { + case Statement::Kind::kVarDeclaration: { + const Variable* var = stmt.as<VarDeclaration>().var(); + if (var->type().isOrContainsUnsizedArray()) { + fContext.fErrors->error(stmt.fPosition, + "unsized arrays are not permitted here"); + } else { + this->addLocalVariable(var, stmt.fPosition); + } + break; + } + case Statement::Kind::kReturn: { + // Early returns from a vertex main() function will bypass sk_Position + // normalization, so SkASSERT that we aren't doing that. If this becomes an + // issue, we can add normalization before each return statement. + if (ProgramConfig::IsVertex(fContext.fConfig->fKind) && fFunction.isMain()) { + fContext.fErrors->error( + stmt.fPosition, + "early returns from vertex programs are not supported"); + } + + // Verify that the return statement matches the function's return type. + ReturnStatement& returnStmt = stmt.as<ReturnStatement>(); + if (returnStmt.expression()) { + if (this->functionReturnsValue()) { + // Coerce return expression to the function's return type. + returnStmt.setExpression(fFunction.returnType().coerceExpression( + std::move(returnStmt.expression()), fContext)); + } else { + // Returning something from a function with a void return type. + fContext.fErrors->error(returnStmt.expression()->fPosition, + "may not return a value from a void function"); + returnStmt.setExpression(nullptr); + } + } else { + if (this->functionReturnsValue()) { + // Returning nothing from a function with a non-void return type. + fContext.fErrors->error(returnStmt.fPosition, + "expected function to return '" + + fFunction.returnType().displayName() + "'"); + } + } + break; + } + case Statement::Kind::kDo: + case Statement::Kind::kFor: { + ++fBreakableLevel; + ++fContinuableLevel.front(); + bool result = INHERITED::visitStatement(stmt); + --fContinuableLevel.front(); + --fBreakableLevel; + return result; + } + case Statement::Kind::kSwitch: { + ++fBreakableLevel; + fContinuableLevel.push_front(0); + bool result = INHERITED::visitStatement(stmt); + fContinuableLevel.pop_front(); + --fBreakableLevel; + return result; + } + case Statement::Kind::kBreak: + if (fBreakableLevel == 0) { + fContext.fErrors->error(stmt.fPosition, + "break statement must be inside a loop or switch"); + } + break; + case Statement::Kind::kContinue: + if (fContinuableLevel.front() == 0) { + if (std::any_of(fContinuableLevel.begin(), + fContinuableLevel.end(), + [](int level) { return level > 0; })) { + fContext.fErrors->error(stmt.fPosition, + "continue statement cannot be used in a switch"); + } else { + fContext.fErrors->error(stmt.fPosition, + "continue statement must be inside a loop"); + } + } + break; + default: + break; + } + return INHERITED::visitStatement(stmt); + } + + private: + const Context& fContext; + const FunctionDeclaration& fFunction; + // how deeply nested we are in breakable constructs (for, do, switch). + int fBreakableLevel = 0; + // number of slots consumed by all variables declared in the function + size_t fSlotsUsed = 0; + // how deeply nested we are in continuable constructs (for, do). + // We keep a stack (via a forward_list) in order to disallow continue inside of switch. + std::forward_list<int> fContinuableLevel{0}; + + using INHERITED = ProgramWriter; + }; + + Finalizer(context, function, pos).visitStatement(*body); + if (function.isMain() && ProgramConfig::IsVertex(context.fConfig->fKind)) { + append_rtadjust_fixup_to_vertex_main(context, function, body->as<Block>()); + } + + if (Analysis::CanExitWithoutReturningValue(function, *body)) { + context.fErrors->error(body->fPosition, "function '" + std::string(function.name()) + + "' can exit without returning a value"); + } + + SkASSERTF(!function.isIntrinsic(), "Intrinsic function '%.*s' should not have a definition", + (int)function.name().size(), function.name().data()); + return std::make_unique<FunctionDefinition>(pos, &function, builtin, std::move(body)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h new file mode 100644 index 0000000000..7b77b68a2b --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDefinition.h @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONDEFINITION +#define SKSL_FUNCTIONDEFINITION + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; + +/** + * A function definition (a declaration plus an associated block of code). + */ +class FunctionDefinition final : public ProgramElement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kFunction; + + FunctionDefinition(Position pos, const FunctionDeclaration* declaration, bool builtin, + std::unique_ptr<Statement> body) + : INHERITED(pos, kIRNodeKind) + , fDeclaration(declaration) + , fBuiltin(builtin) + , fBody(std::move(body)) {} + + /** + * Coerces `return` statements to the return type of the function, and reports errors in the + * function that can't be detected at the individual statement level: + * - `break` and `continue` statements must be in reasonable places. + * - non-void functions are required to return a value on all paths. + * - vertex main() functions don't allow early returns. + * + * This will return a FunctionDefinition even if an error is detected; this leads to better + * diagnostics overall. (Returning null here leads to spurious "function 'f()' was not defined" + * errors when trying to call a function with an error in it.) + */ + static std::unique_ptr<FunctionDefinition> Convert(const Context& context, + Position pos, + const FunctionDeclaration& function, + std::unique_ptr<Statement> body, + bool builtin); + + const FunctionDeclaration& declaration() const { + return *fDeclaration; + } + + bool isBuiltin() const { + return fBuiltin; + } + + std::unique_ptr<Statement>& body() { + return fBody; + } + + const std::unique_ptr<Statement>& body() const { + return fBody; + } + + std::unique_ptr<ProgramElement> clone() const override { + return std::make_unique<FunctionDefinition>(fPosition, &this->declaration(), + /*builtin=*/false, this->body()->clone()); + } + + std::string description() const override { + return this->declaration().description() + " " + this->body()->description(); + } + +private: + const FunctionDeclaration* fDeclaration; + bool fBuiltin; + std::unique_ptr<Statement> fBody; + + using INHERITED = ProgramElement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionPrototype.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionPrototype.h new file mode 100644 index 0000000000..934cf85c27 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionPrototype.h @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONPROTOTYPE +#define SKSL_FUNCTIONPROTOTYPE + +#include "include/private/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +namespace SkSL { + +/** + * A function prototype (a function declaration as a top-level program element) + */ +class FunctionPrototype final : public ProgramElement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kFunctionPrototype; + + FunctionPrototype(Position pos, const FunctionDeclaration* declaration, bool builtin) + : INHERITED(pos, kIRNodeKind) + , fDeclaration(declaration) + , fBuiltin(builtin) {} + + const FunctionDeclaration& declaration() const { + return *fDeclaration; + } + + bool isBuiltin() const { + return fBuiltin; + } + + std::unique_ptr<ProgramElement> clone() const override { + return std::make_unique<FunctionPrototype>(fPosition, &this->declaration(), + /*builtin=*/false); + } + + std::string description() const override { + return this->declaration().description() + ";"; + } + +private: + const FunctionDeclaration* fDeclaration; + bool fBuiltin; + + using INHERITED = ProgramElement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h b/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h new file mode 100644 index 0000000000..4788dbb418 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionReference.h @@ -0,0 +1,55 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FUNCTIONREFERENCE +#define SKSL_FUNCTIONREFERENCE + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" + +namespace SkSL { + +/** + * An identifier referring to a function name. This is an intermediate value: FunctionReferences are + * always eventually replaced by FunctionCalls in valid programs. + */ +class FunctionReference final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kFunctionReference; + + FunctionReference(const Context& context, Position pos, + const FunctionDeclaration* overloadChain) + : INHERITED(pos, kIRNodeKind, context.fTypes.fInvalid.get()) + , fOverloadChain(overloadChain) {} + + const FunctionDeclaration* overloadChain() const { + return fOverloadChain; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::unique_ptr<Expression>(new FunctionReference(pos, this->overloadChain(), + &this->type())); + } + + std::string description(OperatorPrecedence) const override { + return "<function>"; + } + +private: + FunctionReference(Position pos, const FunctionDeclaration* overloadChain, const Type* type) + : INHERITED(pos, kIRNodeKind, type) + , fOverloadChain(overloadChain) {} + + const FunctionDeclaration* fOverloadChain; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.cpp new file mode 100644 index 0000000000..7d6918629c --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.cpp @@ -0,0 +1,108 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLIfStatement.h" + +#include "include/core/SkTypes.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +std::unique_ptr<Statement> IfStatement::clone() const { + return std::make_unique<IfStatement>(fPosition, this->test()->clone(), this->ifTrue()->clone(), + this->ifFalse() ? this->ifFalse()->clone() : nullptr); +} + +std::string IfStatement::description() const { + std::string result; + result += "if (" + this->test()->description() + ") " + this->ifTrue()->description(); + if (this->ifFalse()) { + result += " else " + this->ifFalse()->description(); + } + return result; +} + +std::unique_ptr<Statement> IfStatement::Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Statement> ifTrue, + std::unique_ptr<Statement> ifFalse) { + test = context.fTypes.fBool->coerceExpression(std::move(test), context); + if (!test) { + return nullptr; + } + SkASSERT(ifTrue); + if (Analysis::DetectVarDeclarationWithoutScope(*ifTrue, context.fErrors)) { + return nullptr; + } + if (ifFalse && Analysis::DetectVarDeclarationWithoutScope(*ifFalse, context.fErrors)) { + return nullptr; + } + return IfStatement::Make(context, pos, std::move(test), std::move(ifTrue), std::move(ifFalse)); +} + +static std::unique_ptr<Statement> replace_empty_with_nop(std::unique_ptr<Statement> stmt, + bool isEmpty) { + return (stmt && (!isEmpty || stmt->is<Nop>())) ? std::move(stmt) + : Nop::Make(); +} + +std::unique_ptr<Statement> IfStatement::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Statement> ifTrue, + std::unique_ptr<Statement> ifFalse) { + SkASSERT(test->type().matches(*context.fTypes.fBool)); + SkASSERT(!Analysis::DetectVarDeclarationWithoutScope(*ifTrue)); + SkASSERT(!ifFalse || !Analysis::DetectVarDeclarationWithoutScope(*ifFalse)); + + const bool optimize = context.fConfig->fSettings.fOptimize; + bool trueIsEmpty = false; + bool falseIsEmpty = false; + + if (optimize) { + // If both sides are empty, the if statement can be reduced to its test expression. + trueIsEmpty = ifTrue->isEmpty(); + falseIsEmpty = !ifFalse || ifFalse->isEmpty(); + if (trueIsEmpty && falseIsEmpty) { + return ExpressionStatement::Make(context, std::move(test)); + } + } + + if (optimize) { + // Static Boolean values can fold down to a single branch. + const Expression* testValue = ConstantFolder::GetConstantValueForVariable(*test); + if (testValue->isBoolLiteral()) { + if (testValue->as<Literal>().boolValue()) { + return replace_empty_with_nop(std::move(ifTrue), trueIsEmpty); + } else { + return replace_empty_with_nop(std::move(ifFalse), falseIsEmpty); + } + } + } + + if (optimize) { + // Replace an empty if-true branches with Nop; eliminate empty if-false branches entirely. + ifTrue = replace_empty_with_nop(std::move(ifTrue), trueIsEmpty); + if (falseIsEmpty) { + ifFalse = nullptr; + } + } + + return std::make_unique<IfStatement>( + pos, std::move(test), std::move(ifTrue), std::move(ifFalse)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h new file mode 100644 index 0000000000..379b8ad50d --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIfStatement.h @@ -0,0 +1,91 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_IFSTATEMENT +#define SKSL_IFSTATEMENT + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; + +/** + * An 'if' statement. + */ +class IfStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kIf; + + IfStatement(Position pos, std::unique_ptr<Expression> test, + std::unique_ptr<Statement> ifTrue, std::unique_ptr<Statement> ifFalse) + : INHERITED(pos, kIRNodeKind) + , fTest(std::move(test)) + , fIfTrue(std::move(ifTrue)) + , fIfFalse(std::move(ifFalse)) {} + + // Creates a potentially-simplified form of the if-statement. Typechecks and coerces the test + // expression; reports errors via ErrorReporter. + static std::unique_ptr<Statement> Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Statement> ifTrue, + std::unique_ptr<Statement> ifFalse); + + // Creates a potentially-simplified form of the if-statement; reports errors via ASSERT. + static std::unique_ptr<Statement> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Statement> ifTrue, + std::unique_ptr<Statement> ifFalse); + + std::unique_ptr<Expression>& test() { + return fTest; + } + + const std::unique_ptr<Expression>& test() const { + return fTest; + } + + std::unique_ptr<Statement>& ifTrue() { + return fIfTrue; + } + + const std::unique_ptr<Statement>& ifTrue() const { + return fIfTrue; + } + + std::unique_ptr<Statement>& ifFalse() { + return fIfFalse; + } + + const std::unique_ptr<Statement>& ifFalse() const { + return fIfFalse; + } + + std::unique_ptr<Statement> clone() const override; + + std::string description() const override; + +private: + std::unique_ptr<Expression> fTest; + std::unique_ptr<Statement> fIfTrue; + std::unique_ptr<Statement> fIfFalse; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.cpp new file mode 100644 index 0000000000..b12f1b3726 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLIndexExpression.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLConstructorArray.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLTypeReference.h" + +#include <cstdint> +#include <optional> + +namespace SkSL { + +static bool index_out_of_range(const Context& context, Position pos, SKSL_INT index, + const Expression& base) { + if (index >= 0) { + if (base.type().columns() == Type::kUnsizedArray) { + return false; + } else if (index < base.type().columns()) { + return false; + } + } + context.fErrors->error(pos, "index " + std::to_string(index) + " out of range for '" + + base.type().displayName() + "'"); + return true; +} + +const Type& IndexExpression::IndexType(const Context& context, const Type& type) { + if (type.isMatrix()) { + if (type.componentType().matches(*context.fTypes.fFloat)) { + switch (type.rows()) { + case 2: return *context.fTypes.fFloat2; + case 3: return *context.fTypes.fFloat3; + case 4: return *context.fTypes.fFloat4; + default: SkASSERT(false); + } + } else if (type.componentType().matches(*context.fTypes.fHalf)) { + switch (type.rows()) { + case 2: return *context.fTypes.fHalf2; + case 3: return *context.fTypes.fHalf3; + case 4: return *context.fTypes.fHalf4; + default: SkASSERT(false); + } + } + } + return type.componentType(); +} + +std::unique_ptr<Expression> IndexExpression::Convert(const Context& context, + SymbolTable& symbolTable, + Position pos, + std::unique_ptr<Expression> base, + std::unique_ptr<Expression> index) { + // Convert an array type reference: `int[10]`. + if (base->is<TypeReference>()) { + const Type& baseType = base->as<TypeReference>().value(); + SKSL_INT arraySize = baseType.convertArraySize(context, pos, std::move(index)); + if (!arraySize) { + return nullptr; + } + return TypeReference::Convert(context, pos, + symbolTable.addArrayDimension(&baseType, arraySize)); + } + // Convert an index expression with an expression inside of it: `arr[a * 3]`. + const Type& baseType = base->type(); + if (!baseType.isArray() && !baseType.isMatrix() && !baseType.isVector()) { + context.fErrors->error(base->fPosition, + "expected array, but found '" + baseType.displayName() + "'"); + return nullptr; + } + if (!index->type().isInteger()) { + index = context.fTypes.fInt->coerceExpression(std::move(index), context); + if (!index) { + return nullptr; + } + } + // Perform compile-time bounds checking on constant-expression indices. + const Expression* indexExpr = ConstantFolder::GetConstantValueForVariable(*index); + if (indexExpr->isIntLiteral()) { + SKSL_INT indexValue = indexExpr->as<Literal>().intValue(); + if (index_out_of_range(context, index->fPosition, indexValue, *base)) { + return nullptr; + } + } + return IndexExpression::Make(context, pos, std::move(base), std::move(index)); +} + +std::unique_ptr<Expression> IndexExpression::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> base, + std::unique_ptr<Expression> index) { + const Type& baseType = base->type(); + SkASSERT(baseType.isArray() || baseType.isMatrix() || baseType.isVector()); + SkASSERT(index->type().isInteger()); + + const Expression* indexExpr = ConstantFolder::GetConstantValueForVariable(*index); + if (indexExpr->isIntLiteral()) { + SKSL_INT indexValue = indexExpr->as<Literal>().intValue(); + if (!index_out_of_range(context, index->fPosition, indexValue, *base)) { + if (baseType.isVector()) { + // Constant array indexes on vectors can be converted to swizzles: `v[2]` --> `v.z`. + // Swizzling is harmless and can unlock further simplifications for some base types. + return Swizzle::Make(context, pos, std::move(base), + ComponentArray{(int8_t)indexValue}); + } + + if (baseType.isArray() && !Analysis::HasSideEffects(*base)) { + // Indexing an constant array constructor with a constant index can just pluck out + // the requested value from the array. + const Expression* baseExpr = ConstantFolder::GetConstantValueForVariable(*base); + if (baseExpr->is<ConstructorArray>()) { + const ConstructorArray& arrayCtor = baseExpr->as<ConstructorArray>(); + const ExpressionArray& arguments = arrayCtor.arguments(); + SkASSERT(arguments.size() == baseType.columns()); + + return arguments[indexValue]->clone(pos); + } + } + + if (baseType.isMatrix() && !Analysis::HasSideEffects(*base)) { + // Matrices can be constructed with vectors that don't line up on column boundaries, + // so extracting out the values from the constructor can be tricky. Fortunately, we + // can reconstruct an equivalent vector using `getConstantValue`. If we + // can't extract the data using `getConstantValue`, it wasn't constant and + // we're not obligated to simplify anything. + const Expression* baseExpr = ConstantFolder::GetConstantValueForVariable(*base); + int vecWidth = baseType.rows(); + const Type& scalarType = baseType.componentType(); + const Type& vecType = scalarType.toCompound(context, vecWidth, /*rows=*/1); + indexValue *= vecWidth; + + ExpressionArray ctorArgs; + ctorArgs.reserve_back(vecWidth); + for (int slot = 0; slot < vecWidth; ++slot) { + std::optional<double> slotVal = baseExpr->getConstantValue(indexValue + slot); + if (slotVal.has_value()) { + ctorArgs.push_back(Literal::Make(baseExpr->fPosition, *slotVal, + &scalarType)); + } else { + ctorArgs.clear(); + break; + } + } + + if (!ctorArgs.empty()) { + return ConstructorCompound::Make(context, pos, vecType, std::move(ctorArgs)); + } + } + } + } + + return std::make_unique<IndexExpression>(context, pos, std::move(base), std::move(index)); +} + +std::string IndexExpression::description(OperatorPrecedence) const { + return this->base()->description(OperatorPrecedence::kPostfix) + "[" + + this->index()->description(OperatorPrecedence::kTopLevel) + "]"; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h new file mode 100644 index 0000000000..222728e9eb --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLIndexExpression.h @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_INDEX +#define SKSL_INDEX + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +class SymbolTable; +class Type; +enum class OperatorPrecedence : uint8_t; + +/** + * An expression which extracts a value from an array, vector or matrix, as in 'm[2]'. + */ +class IndexExpression final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kIndex; + + IndexExpression(const Context& context, Position pos, std::unique_ptr<Expression> base, + std::unique_ptr<Expression> index) + : INHERITED(pos, kIRNodeKind, &IndexType(context, base->type())) + , fBase(std::move(base)) + , fIndex(std::move(index)) {} + + // Returns a simplified index-expression; reports errors via the ErrorReporter. + static std::unique_ptr<Expression> Convert(const Context& context, + SymbolTable& symbolTable, + Position pos, + std::unique_ptr<Expression> base, + std::unique_ptr<Expression> index); + + // Returns a simplified index-expression; reports errors via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> base, + std::unique_ptr<Expression> index); + + /** + * Given a type, returns the type that will result from extracting an array value from it. + */ + static const Type& IndexType(const Context& context, const Type& type); + + std::unique_ptr<Expression>& base() { + return fBase; + } + + const std::unique_ptr<Expression>& base() const { + return fBase; + } + + std::unique_ptr<Expression>& index() { + return fIndex; + } + + const std::unique_ptr<Expression>& index() const { + return fIndex; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::unique_ptr<Expression>(new IndexExpression(pos, this->base()->clone(), + this->index()->clone(), + &this->type())); + } + + std::string description(OperatorPrecedence) const override; + + using INHERITED = Expression; + +private: + IndexExpression(Position pos, std::unique_ptr<Expression> base, + std::unique_ptr<Expression> index, const Type* type) + : INHERITED(pos, Kind::kIndex, type) + , fBase(std::move(base)) + , fIndex(std::move(index)) {} + + std::unique_ptr<Expression> fBase; + std::unique_ptr<Expression> fIndex; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.cpp b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.cpp new file mode 100644 index 0000000000..f093624eff --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.cpp @@ -0,0 +1,132 @@ +/* + * 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/SkSLModifiers.h" +#include "include/private/SkSLString.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLField.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#include <cstddef> +#include <cstdint> +#include <vector> + +namespace SkSL { + +enum class ProgramKind : int8_t; + +InterfaceBlock::~InterfaceBlock() { + // Unhook this InterfaceBlock from its associated Variable, since we're being deleted. + if (fVariable) { + fVariable->detachDeadInterfaceBlock(); + } +} + +static std::optional<int> find_rt_adjust_index(SkSpan<const Type::Field> fields) { + for (size_t index = 0; index < fields.size(); ++index) { + const SkSL::Type::Field& f = fields[index]; + if (f.fName == SkSL::Compiler::RTADJUST_NAME) { + return index; + } + } + + return std::nullopt; +} + +std::unique_ptr<InterfaceBlock> InterfaceBlock::Convert(const Context& context, + Position pos, + Variable* variable, + std::shared_ptr<SymbolTable> symbols) { + if (SkSL::ProgramKind kind = context.fConfig->fKind; !ProgramConfig::IsFragment(kind) && + !ProgramConfig::IsVertex(kind) && + !ProgramConfig::IsCompute(kind)) { + context.fErrors->error(pos, "interface blocks are not allowed in this kind of program"); + return nullptr; + } + + // Find sk_RTAdjust and error out if it's not of type `float4`. + SkSpan<const Type::Field> fields = variable->type().componentType().fields(); + std::optional<int> rtAdjustIndex = find_rt_adjust_index(fields); + if (rtAdjustIndex.has_value()) { + const Type::Field& rtAdjustField = fields[*rtAdjustIndex]; + if (!rtAdjustField.fType->matches(*context.fTypes.fFloat4)) { + context.fErrors->error(rtAdjustField.fPosition, "sk_RTAdjust must have type 'float4'"); + return nullptr; + } + } + return InterfaceBlock::Make(context, pos, variable, rtAdjustIndex, symbols); +} + +std::unique_ptr<InterfaceBlock> InterfaceBlock::Make(const Context& context, + Position pos, + Variable* variable, + std::optional<int> rtAdjustIndex, + std::shared_ptr<SymbolTable> symbols) { + SkASSERT(ProgramConfig::IsFragment(context.fConfig->fKind) || + ProgramConfig::IsVertex(context.fConfig->fKind) || + ProgramConfig::IsCompute(context.fConfig->fKind)); + + SkASSERT(variable->type().componentType().isInterfaceBlock()); + SkSpan<const Type::Field> fields = variable->type().componentType().fields(); + + if (rtAdjustIndex.has_value()) { + [[maybe_unused]] const Type::Field& rtAdjustField = fields[*rtAdjustIndex]; + SkASSERT(rtAdjustField.fName == SkSL::Compiler::RTADJUST_NAME); + SkASSERT(rtAdjustField.fType->matches(*context.fTypes.fFloat4)); + + ThreadContext::RTAdjustData& rtAdjustData = ThreadContext::RTAdjustState(); + rtAdjustData.fInterfaceBlock = variable; + rtAdjustData.fFieldIndex = *rtAdjustIndex; + } + + if (variable->name().empty()) { + // This interface block is anonymous. Add each field to the top-level symbol table. + for (size_t i = 0; i < fields.size(); ++i) { + symbols->add(std::make_unique<SkSL::Field>(fields[i].fPosition, variable, i)); + } + } else { + // Add the global variable to the top-level symbol table. + symbols->addWithoutOwnership(variable); + } + + return std::make_unique<SkSL::InterfaceBlock>(pos, variable, symbols); +} + +std::unique_ptr<ProgramElement> InterfaceBlock::clone() const { + return std::make_unique<InterfaceBlock>(fPosition, + this->var(), + SymbolTable::WrapIfBuiltin(this->typeOwner())); +} + +std::string InterfaceBlock::description() const { + std::string result = this->var()->modifiers().description() + + std::string(this->typeName()) + " {\n"; + const Type* structType = &this->var()->type(); + if (structType->isArray()) { + structType = &structType->componentType(); + } + for (const auto& f : structType->fields()) { + result += f.description() + "\n"; + } + result += "}"; + if (!this->instanceName().empty()) { + result += " " + std::string(this->instanceName()); + if (this->arraySize() > 0) { + String::appendf(&result, "[%d]", this->arraySize()); + } + } + return result + ";"; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h new file mode 100644 index 0000000000..a9447dd9cb --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLInterfaceBlock.h @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_INTERFACEBLOCK +#define SKSL_INTERFACEBLOCK + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLProgramElement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <utility> + +namespace SkSL { + +class Context; +class SymbolTable; + +/** + * An interface block, as in: + * + * out sk_PerVertex { + * layout(builtin=0) float4 sk_Position; + * layout(builtin=1) float sk_PointSize; + * }; + * + * At the IR level, this is represented by a single variable of struct type. + */ +class InterfaceBlock final : public ProgramElement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kInterfaceBlock; + + InterfaceBlock(Position pos, + Variable* var, + std::shared_ptr<SymbolTable> typeOwner) + : INHERITED(pos, kIRNodeKind) + , fVariable(var) + , fTypeOwner(std::move(typeOwner)) { + SkASSERT(fVariable->type().componentType().isInterfaceBlock()); + fVariable->setInterfaceBlock(this); + } + + ~InterfaceBlock() override; + + // Returns an InterfaceBlock; errors are reported to the ErrorReporter. + // The caller is responsible for adding the InterfaceBlock to the program elements. + // The program's RTAdjustData will be updated if the InterfaceBlock contains sk_RTAdjust. + // The passed-in symbol table will be updated with a reference to the interface block variable + // (if it is named) or each of the interface block fields (if it is anonymous). + static std::unique_ptr<InterfaceBlock> Convert(const Context& context, + Position pos, + Variable* variable, + std::shared_ptr<SymbolTable> symbols); + + // Returns an InterfaceBlock; errors are reported via SkASSERT. + // The caller is responsible for adding the InterfaceBlock to the program elements. + // If the InterfaceBlock contains sk_RTAdjust, the caller is responsible for passing its field + // index in `rtAdjustIndex`. + // The passed-in symbol table will be updated with a reference to the interface block variable + // (if it is named) or each of the interface block fields (if it is anonymous). + static std::unique_ptr<InterfaceBlock> Make(const Context& context, + Position pos, + Variable* variable, + std::optional<int> rtAdjustIndex, + std::shared_ptr<SymbolTable> symbols); + + Variable* var() const { + return fVariable; + } + + void detachDeadVariable() { + fVariable = nullptr; + } + + std::string_view typeName() const { + return fVariable->type().componentType().name(); + } + + std::string_view instanceName() const { + return fVariable->name(); + } + + const std::shared_ptr<SymbolTable>& typeOwner() const { + return fTypeOwner; + } + + int arraySize() const { + return fVariable->type().isArray() ? fVariable->type().columns() : 0; + } + + std::unique_ptr<ProgramElement> clone() const override; + + std::string description() const override; + +private: + Variable* fVariable; + std::shared_ptr<SymbolTable> fTypeOwner; + + using INHERITED = ProgramElement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLLayout.cpp b/gfx/skia/skia/src/sksl/ir/SkSLLayout.cpp new file mode 100644 index 0000000000..3274ab1185 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLLayout.cpp @@ -0,0 +1,75 @@ +/* + * 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/private/SkSLLayout.h" +#include "include/private/SkSLString.h" + +namespace SkSL { + +std::string Layout::description() const { + std::string result; + auto separator = SkSL::String::Separator(); + if (fLocation >= 0) { + result += separator() + "location = " + std::to_string(fLocation); + } + if (fOffset >= 0) { + result += separator() + "offset = " + std::to_string(fOffset); + } + if (fBinding >= 0) { + result += separator() + "binding = " + std::to_string(fBinding); + } + if (fTexture >= 0) { + result += separator() + "texture = " + std::to_string(fTexture); + } + if (fSampler >= 0) { + result += separator() + "sampler = " + std::to_string(fSampler); + } + if (fIndex >= 0) { + result += separator() + "index = " + std::to_string(fIndex); + } + if (fSet >= 0) { + result += separator() + "set = " + std::to_string(fSet); + } + if (fBuiltin >= 0) { + result += separator() + "builtin = " + std::to_string(fBuiltin); + } + if (fInputAttachmentIndex >= 0) { + result += separator() + "input_attachment_index = " + + std::to_string(fInputAttachmentIndex); + } + if (fFlags & kOriginUpperLeft_Flag) { + result += separator() + "origin_upper_left"; + } + if (fFlags & kBlendSupportAllEquations_Flag) { + result += separator() + "blend_support_all_equations"; + } + if (fFlags & kPushConstant_Flag) { + result += separator() + "push_constant"; + } + if (fFlags & kColor_Flag) { + result += separator() + "color"; + } + if (result.size() > 0) { + result = "layout (" + result + ")"; + } + return result; +} + +bool Layout::operator==(const Layout& other) const { + return fFlags == other.fFlags && + fLocation == other.fLocation && + fOffset == other.fOffset && + fBinding == other.fBinding && + fTexture == other.fTexture && + fSampler == other.fSampler && + fIndex == other.fIndex && + fSet == other.fSet && + fBuiltin == other.fBuiltin && + fInputAttachmentIndex == other.fInputAttachmentIndex; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLLiteral.cpp b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.cpp new file mode 100644 index 0000000000..aa8c5c4440 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.cpp @@ -0,0 +1,23 @@ +/* + * 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/private/SkSLString.h" +#include "src/sksl/ir/SkSLLiteral.h" + +namespace SkSL { + +std::string Literal::description(OperatorPrecedence) const { + if (this->type().isBoolean()) { + return fValue ? "true" : "false"; + } + if (this->type().isInteger()) { + return std::to_string(this->intValue()); + } + return skstd::to_string(this->floatValue()); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLLiteral.h b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.h new file mode 100644 index 0000000000..d4b0bd1be6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLLiteral.h @@ -0,0 +1,145 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_FLOATLITERAL +#define SKSL_FLOATLITERAL + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <cinttypes> +#include <memory> +#include <optional> +#include <string> + +namespace SkSL { + +enum class OperatorPrecedence : uint8_t; + +/** + * A literal value. These can contain ints, floats, or booleans. + */ + +class Literal : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kLiteral; + + Literal(Position pos, double value, const Type* type) + : INHERITED(pos, kIRNodeKind, type) + , fValue(value) {} + + // Makes a literal of $floatLiteral type. + static std::unique_ptr<Literal> MakeFloat(const Context& context, Position pos, + float value) { + return std::make_unique<Literal>(pos, value, context.fTypes.fFloatLiteral.get()); + } + + // Makes a float literal of the specified type. + static std::unique_ptr<Literal> MakeFloat(Position pos, float value, const Type* type) { + SkASSERT(type->isFloat()); + return std::make_unique<Literal>(pos, value, type); + } + + // Makes a literal of $intLiteral type. + static std::unique_ptr<Literal> MakeInt(const Context& context, Position pos, + SKSL_INT value) { + return std::make_unique<Literal>(pos, value, context.fTypes.fIntLiteral.get()); + } + + // Makes an int literal of the specified type. + static std::unique_ptr<Literal> MakeInt(Position pos, SKSL_INT value, const Type* type) { + SkASSERT(type->isInteger()); + SkASSERTF(value >= type->minimumValue(), "Value %" PRId64 " does not fit in type %s", + value, type->description().c_str()); + SkASSERTF(value <= type->maximumValue(), "Value %" PRId64 " does not fit in type %s", + value, type->description().c_str()); + return std::make_unique<Literal>(pos, value, type); + } + + // Makes a literal of boolean type. + static std::unique_ptr<Literal> MakeBool(const Context& context, Position pos, bool value) { + return std::make_unique<Literal>(pos, value, context.fTypes.fBool.get()); + } + + // Makes a literal of boolean type. (Functionally identical to the above, but useful if you + // don't have access to the Context.) + static std::unique_ptr<Literal> MakeBool(Position pos, bool value, const Type* type) { + SkASSERT(type->isBoolean()); + return std::make_unique<Literal>(pos, value, type); + } + + // Makes a literal of the specified type, rounding as needed. + static std::unique_ptr<Literal> Make(Position pos, double value, const Type* type) { + if (type->isFloat()) { + return MakeFloat(pos, value, type); + } + if (type->isInteger()) { + return MakeInt(pos, value, type); + } + SkASSERT(type->isBoolean()); + return MakeBool(pos, value, type); + } + + float floatValue() const { + SkASSERT(this->type().isFloat()); + return (SKSL_FLOAT)fValue; + } + + SKSL_INT intValue() const { + SkASSERT(this->type().isInteger()); + return (SKSL_INT)fValue; + } + + SKSL_INT boolValue() const { + SkASSERT(this->type().isBoolean()); + return (bool)fValue; + } + + double value() const { + return fValue; + } + + std::string description(OperatorPrecedence) const override; + + ComparisonResult compareConstant(const Expression& other) const override { + if (!other.is<Literal>() || this->type().numberKind() != other.type().numberKind()) { + return ComparisonResult::kUnknown; + } + return this->value() == other.as<Literal>().value() + ? ComparisonResult::kEqual + : ComparisonResult::kNotEqual; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<Literal>(pos, this->value(), &this->type()); + } + + bool supportsConstantValues() const override { + return true; + } + + std::optional<double> getConstantValue(int n) const override { + SkASSERT(n == 0); + return fValue; + } + +private: + double fValue; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLMethodReference.h b/gfx/skia/skia/src/sksl/ir/SkSLMethodReference.h new file mode 100644 index 0000000000..c077ec3f9f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLMethodReference.h @@ -0,0 +1,73 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_METHODREFERENCE +#define SKSL_METHODREFERENCE + +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +class FunctionDeclaration; + +/** + * An identifier referring to a method name, along with an instance for the call. + * This is an intermediate value: MethodReferences are always eventually replaced by FunctionCalls + * in valid programs. + * + * Method calls are only supported on effect-child types, and they all resolve to intrinsics + * prefixed with '$', and taking the 'self' object as the last parameter. For example: + * + * uniform shader child; + * ... + * child.eval(xy) --> $eval(xy, child) + */ +class MethodReference final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kMethodReference; + + MethodReference(const Context& context, + Position pos, + std::unique_ptr<Expression> self, + const FunctionDeclaration* overloadChain) + : INHERITED(pos, kIRNodeKind, context.fTypes.fInvalid.get()) + , fSelf(std::move(self)) + , fOverloadChain(overloadChain) {} + + std::unique_ptr<Expression>& self() { return fSelf; } + const std::unique_ptr<Expression>& self() const { return fSelf; } + + const FunctionDeclaration* overloadChain() const { return fOverloadChain; } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::unique_ptr<Expression>(new MethodReference( + pos, this->self()->clone(), this->overloadChain(), &this->type())); + } + + std::string description(OperatorPrecedence) const override { + return "<method>"; + } + +private: + MethodReference(Position pos, + std::unique_ptr<Expression> self, + const FunctionDeclaration* overloadChain, + const Type* type) + : INHERITED(pos, kIRNodeKind, type) + , fSelf(std::move(self)) + , fOverloadChain(overloadChain) {} + + std::unique_ptr<Expression> fSelf; + const FunctionDeclaration* fOverloadChain; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLModifiers.cpp b/gfx/skia/skia/src/sksl/ir/SkSLModifiers.cpp new file mode 100644 index 0000000000..548d38d2cd --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLModifiers.cpp @@ -0,0 +1,115 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLModifiers.h" + +#include "include/core/SkTypes.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkMathPriv.h" +#include "src/sksl/SkSLContext.h" + +namespace SkSL { + +bool Modifiers::checkPermitted(const Context& context, + Position pos, + int permittedModifierFlags, + int permittedLayoutFlags) const { + static constexpr struct { Modifiers::Flag flag; const char* name; } kModifierFlags[] = { + { Modifiers::kConst_Flag, "const" }, + { Modifiers::kIn_Flag, "in" }, + { Modifiers::kOut_Flag, "out" }, + { Modifiers::kUniform_Flag, "uniform" }, + { Modifiers::kFlat_Flag, "flat" }, + { Modifiers::kNoPerspective_Flag, "noperspective" }, + { Modifiers::kPure_Flag, "$pure" }, + { Modifiers::kInline_Flag, "inline" }, + { Modifiers::kNoInline_Flag, "noinline" }, + { Modifiers::kHighp_Flag, "highp" }, + { Modifiers::kMediump_Flag, "mediump" }, + { Modifiers::kLowp_Flag, "lowp" }, + { Modifiers::kExport_Flag, "$export" }, + { Modifiers::kES3_Flag, "$es3" }, + { Modifiers::kWorkgroup_Flag, "workgroup" }, + { Modifiers::kReadOnly_Flag, "readonly" }, + { Modifiers::kWriteOnly_Flag, "writeonly" }, + { Modifiers::kBuffer_Flag, "buffer" }, + }; + + bool success = true; + int modifierFlags = fFlags; + for (const auto& f : kModifierFlags) { + if (modifierFlags & f.flag) { + if (!(permittedModifierFlags & f.flag)) { + context.fErrors->error(pos, "'" + std::string(f.name) + "' is not permitted here"); + success = false; + } + modifierFlags &= ~f.flag; + } + } + SkASSERT(modifierFlags == 0); + + int backendFlags = fLayout.fFlags & Layout::kAllBackendFlagsMask; + if (SkPopCount(backendFlags) > 1) { + context.fErrors->error(pos, "only one backend qualifier can be used"); + success = false; + } + + static constexpr struct { Layout::Flag flag; const char* name; } kLayoutFlags[] = { + { Layout::kOriginUpperLeft_Flag, "origin_upper_left"}, + { Layout::kPushConstant_Flag, "push_constant"}, + { Layout::kBlendSupportAllEquations_Flag, "blend_support_all_equations"}, + { Layout::kColor_Flag, "color"}, + { Layout::kLocation_Flag, "location"}, + { Layout::kOffset_Flag, "offset"}, + { Layout::kBinding_Flag, "binding"}, + { Layout::kTexture_Flag, "texture"}, + { Layout::kSampler_Flag, "sampler"}, + { Layout::kIndex_Flag, "index"}, + { Layout::kSet_Flag, "set"}, + { Layout::kBuiltin_Flag, "builtin"}, + { Layout::kInputAttachmentIndex_Flag, "input_attachment_index"}, + { Layout::kSPIRV_Flag, "spirv"}, + { Layout::kMetal_Flag, "metal"}, + { Layout::kGL_Flag, "gl"}, + { Layout::kWGSL_Flag, "wgsl"}, + }; + + int layoutFlags = fLayout.fFlags; + if ((layoutFlags & (Layout::kTexture_Flag | Layout::kSampler_Flag)) && + layoutFlags & Layout::kBinding_Flag) { + context.fErrors->error(pos, "'binding' modifier cannot coexist with 'texture'/'sampler'"); + success = false; + } + // The `texture` and `sampler` flags are only allowed when explicitly targeting Metal and WGSL + if (!(layoutFlags & (Layout::kMetal_Flag | Layout::kWGSL_Flag))) { + permittedLayoutFlags &= ~Layout::kTexture_Flag; + permittedLayoutFlags &= ~Layout::kSampler_Flag; + } + // The `set` flag is not allowed when explicitly targeting Metal and GLSL. It is currently + // allowed when no backend flag is present. + // TODO(skia:14023): Further restrict the `set` flag to SPIR-V and WGSL + if (layoutFlags & (Layout::kMetal_Flag | Layout::kGL_Flag)) { + permittedLayoutFlags &= ~Layout::kSet_Flag; + } + // TODO(skia:14023): Restrict the `push_constant` flag to SPIR-V and WGSL + + for (const auto& lf : kLayoutFlags) { + if (layoutFlags & lf.flag) { + if (!(permittedLayoutFlags & lf.flag)) { + context.fErrors->error(pos, "layout qualifier '" + std::string(lf.name) + + "' is not permitted here"); + success = false; + } + layoutFlags &= ~lf.flag; + } + } + SkASSERT(layoutFlags == 0); + return success; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h b/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h new file mode 100644 index 0000000000..8d9179ce89 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLModifiersDeclaration.h @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_MODIFIERDECLARATION +#define SKSL_MODIFIERDECLARATION + +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" + +namespace SkSL { + +/** + * A declaration that consists only of modifiers, e.g.: + * + * layout(blend_support_all_equations) out; + */ +class ModifiersDeclaration final : public ProgramElement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kModifiers; + + ModifiersDeclaration(const Modifiers* modifiers) + : INHERITED(Position(), kIRNodeKind) + , fModifiers(modifiers) {} + + const Modifiers& modifiers() const { + return *fModifiers; + } + + std::unique_ptr<ProgramElement> clone() const override { + return std::make_unique<ModifiersDeclaration>(&this->modifiers()); + } + + std::string description() const override { + return this->modifiers().description() + ";"; + } + +private: + const Modifiers* fModifiers; + + using INHERITED = ProgramElement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLNop.h b/gfx/skia/skia/src/sksl/ir/SkSLNop.h new file mode 100644 index 0000000000..f2810ef15f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLNop.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_NOP +#define SKSL_NOP + +#include "include/private/SkSLStatement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +namespace SkSL { + +/** + * A no-op statement that does nothing. + */ +class Nop final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kNop; + + Nop() + : INHERITED(Position(), kIRNodeKind) {} + + static std::unique_ptr<Statement> Make() { + return std::make_unique<Nop>(); + } + + bool isEmpty() const override { + return true; + } + + std::string description() const override { + return ";"; + } + + std::unique_ptr<Statement> clone() const override { + return std::make_unique<Nop>(); + } + +private: + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPoison.h b/gfx/skia/skia/src/sksl/ir/SkSLPoison.h new file mode 100644 index 0000000000..31bd850308 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLPoison.h @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" + +namespace SkSL { + +class Poison : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kPoison; + + static std::unique_ptr<Expression> Make(Position pos, const Context& context) { + return std::make_unique<Poison>(pos, context.fTypes.fPoison.get()); + } + + Poison(Position pos, const Type* type) + : INHERITED(pos, kIRNodeKind, type) {} + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<Poison>(pos, &this->type()); + } + + std::string description(OperatorPrecedence) const override { + return Compiler::POISON_TAG; + } + +private: + using INHERITED = Expression; +}; + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.cpp new file mode 100644 index 0000000000..83d5856faf --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLPostfixExpression.h" + +#include "include/core/SkTypes.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +namespace SkSL { + +std::unique_ptr<Expression> PostfixExpression::Convert(const Context& context, Position pos, + std::unique_ptr<Expression> base, Operator op) { + const Type& baseType = base->type(); + if (!baseType.isNumber()) { + context.fErrors->error(pos, "'" + std::string(op.tightOperatorName()) + + "' cannot operate on '" + baseType.displayName() + "'"); + return nullptr; + } + if (!Analysis::UpdateVariableRefKind(base.get(), VariableRefKind::kReadWrite, + context.fErrors)) { + return nullptr; + } + return PostfixExpression::Make(context, pos, std::move(base), op); +} + +std::unique_ptr<Expression> PostfixExpression::Make(const Context& context, Position pos, + std::unique_ptr<Expression> base, Operator op) { + SkASSERT(base->type().isNumber()); + SkASSERT(Analysis::IsAssignable(*base)); + return std::make_unique<PostfixExpression>(pos, std::move(base), op); +} + +std::string PostfixExpression::description(OperatorPrecedence parentPrecedence) const { + bool needsParens = (OperatorPrecedence::kPostfix >= parentPrecedence); + return std::string(needsParens ? "(" : "") + + this->operand()->description(OperatorPrecedence::kPostfix) + + std::string(this->getOperator().tightOperatorName()) + + std::string(needsParens ? ")" : ""); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h new file mode 100644 index 0000000000..a8d457a527 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLPostfixExpression.h @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_POSTFIXEXPRESSION +#define SKSL_POSTFIXEXPRESSION + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; + +/** + * An expression modified by a unary operator appearing after it, such as 'i++'. + */ +class PostfixExpression final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kPostfix; + + PostfixExpression(Position pos, std::unique_ptr<Expression> operand, Operator op) + : INHERITED(pos, kIRNodeKind, &operand->type()) + , fOperand(std::move(operand)) + , fOperator(op) {} + + // Creates an SkSL postfix expression; uses the ErrorReporter to report errors. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> base, + Operator op); + + // Creates an SkSL postfix expression; reports errors via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> base, + Operator op); + + Operator getOperator() const { + return fOperator; + } + + std::unique_ptr<Expression>& operand() { + return fOperand; + } + + const std::unique_ptr<Expression>& operand() const { + return fOperand; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<PostfixExpression>(pos, this->operand()->clone(), + this->getOperator()); + } + + std::string description(OperatorPrecedence parentPrecedence) const override; + +private: + std::unique_ptr<Expression> fOperand; + Operator fOperator; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.cpp new file mode 100644 index 0000000000..a5ea728de2 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.cpp @@ -0,0 +1,282 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLPrefixExpression.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLConstructorArray.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorDiagonalMatrix.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariableReference.h" + +namespace SkSL { + +static ExpressionArray negate_operands(const Context& context, + Position pos, + const ExpressionArray& operands); + +static std::unique_ptr<Expression> simplify_negation(const Context& context, + Position pos, + const Expression& originalExpr) { + const Expression* value = ConstantFolder::GetConstantValueForVariable(originalExpr); + switch (value->kind()) { + case Expression::Kind::kLiteral: { + // Convert -literal(1) to literal(-1). + double negated = -value->as<Literal>().value(); + // Don't simplify the expression if the type can't hold the negated value. + const Type& type = value->type(); + if (type.checkForOutOfRangeLiteral(context, negated, pos)) { + return nullptr; + } + return Literal::Make(pos, negated, &type); + } + case Expression::Kind::kPrefix: { + // Convert `-(-expression)` into `expression`. + const PrefixExpression& prefix = value->as<PrefixExpression>(); + if (prefix.getOperator().kind() == Operator::Kind::MINUS) { + return prefix.operand()->clone(pos); + } + break; + } + case Expression::Kind::kConstructorArray: + // Convert `-array[N](literal, ...)` into `array[N](-literal, ...)`. + if (Analysis::IsCompileTimeConstant(*value)) { + const ConstructorArray& ctor = value->as<ConstructorArray>(); + return ConstructorArray::Make(context, pos, ctor.type(), + negate_operands(context, pos, ctor.arguments())); + } + break; + + case Expression::Kind::kConstructorDiagonalMatrix: + // Convert `-matrix(literal)` into `matrix(-literal)`. + if (Analysis::IsCompileTimeConstant(*value)) { + const ConstructorDiagonalMatrix& ctor = value->as<ConstructorDiagonalMatrix>(); + if (std::unique_ptr<Expression> simplified = simplify_negation(context, + pos, + *ctor.argument())) { + return ConstructorDiagonalMatrix::Make(context, pos, ctor.type(), + std::move(simplified)); + } + } + break; + + case Expression::Kind::kConstructorSplat: + // Convert `-vector(literal)` into `vector(-literal)`. + if (Analysis::IsCompileTimeConstant(*value)) { + const ConstructorSplat& ctor = value->as<ConstructorSplat>(); + if (std::unique_ptr<Expression> simplified = simplify_negation(context, + pos, + *ctor.argument())) { + return ConstructorSplat::Make(context, pos, ctor.type(), std::move(simplified)); + } + } + break; + + case Expression::Kind::kConstructorCompound: + // Convert `-vecN(literal, ...)` into `vecN(-literal, ...)`. + if (Analysis::IsCompileTimeConstant(*value)) { + const ConstructorCompound& ctor = value->as<ConstructorCompound>(); + return ConstructorCompound::Make(context, pos, ctor.type(), + negate_operands(context, pos, ctor.arguments())); + } + break; + + default: + break; + } + return nullptr; +} + +static ExpressionArray negate_operands(const Context& context, + Position pos, + const ExpressionArray& array) { + ExpressionArray replacement; + replacement.reserve_back(array.size()); + for (const std::unique_ptr<Expression>& expr : array) { + // The logic below is very similar to `negate_operand`, but with different ownership rules. + if (std::unique_ptr<Expression> simplified = simplify_negation(context, pos, *expr)) { + replacement.push_back(std::move(simplified)); + } else { + replacement.push_back(std::make_unique<PrefixExpression>(pos, Operator::Kind::MINUS, + expr->clone())); + } + } + return replacement; +} + +static std::unique_ptr<Expression> negate_operand(const Context& context, + Position pos, + std::unique_ptr<Expression> value) { + // Attempt to simplify this negation (e.g. eliminate double negation, literal values) + if (std::unique_ptr<Expression> simplified = simplify_negation(context, pos, *value)) { + return simplified; + } + + // No simplified form; convert expression to Prefix(TK_MINUS, expression). + return std::make_unique<PrefixExpression>(pos, Operator::Kind::MINUS, std::move(value)); +} + +static std::unique_ptr<Expression> logical_not_operand(const Context& context, + Position pos, + std::unique_ptr<Expression> operand) { + const Expression* value = ConstantFolder::GetConstantValueForVariable(*operand); + switch (value->kind()) { + case Expression::Kind::kLiteral: { + // Convert !boolLiteral(true) to boolLiteral(false). + SkASSERT(value->type().isBoolean()); + const Literal& b = value->as<Literal>(); + return Literal::MakeBool(pos, !b.boolValue(), &operand->type()); + } + case Expression::Kind::kPrefix: { + // Convert `!(!expression)` into `expression`. + PrefixExpression& prefix = operand->as<PrefixExpression>(); + if (prefix.getOperator().kind() == Operator::Kind::LOGICALNOT) { + prefix.operand()->fPosition = pos; + return std::move(prefix.operand()); + } + break; + } + default: + break; + } + + // No simplified form; convert expression to Prefix(TK_LOGICALNOT, expression). + return std::make_unique<PrefixExpression>(pos, Operator::Kind::LOGICALNOT, std::move(operand)); +} + +std::unique_ptr<Expression> PrefixExpression::Convert(const Context& context, Position pos, + Operator op, std::unique_ptr<Expression> base) { + const Type& baseType = base->type(); + switch (op.kind()) { + case Operator::Kind::PLUS: + if (baseType.isArray() || !baseType.componentType().isNumber()) { + context.fErrors->error(pos, + "'+' cannot operate on '" + baseType.displayName() + "'"); + return nullptr; + } + break; + + case Operator::Kind::MINUS: + if (baseType.isArray() || !baseType.componentType().isNumber()) { + context.fErrors->error(pos, + "'-' cannot operate on '" + baseType.displayName() + "'"); + return nullptr; + } + break; + + case Operator::Kind::PLUSPLUS: + case Operator::Kind::MINUSMINUS: + if (!baseType.isNumber()) { + context.fErrors->error(pos, + "'" + std::string(op.tightOperatorName()) + + "' cannot operate on '" + baseType.displayName() + "'"); + return nullptr; + } + if (!Analysis::UpdateVariableRefKind(base.get(), VariableReference::RefKind::kReadWrite, + context.fErrors)) { + return nullptr; + } + break; + + case Operator::Kind::LOGICALNOT: + if (!baseType.isBoolean()) { + context.fErrors->error(pos, + "'" + std::string(op.tightOperatorName()) + + "' cannot operate on '" + baseType.displayName() + "'"); + return nullptr; + } + break; + + case Operator::Kind::BITWISENOT: + if (context.fConfig->strictES2Mode()) { + // GLSL ES 1.00, Section 5.1 + context.fErrors->error( + pos, + "operator '" + std::string(op.tightOperatorName()) + "' is not allowed"); + return nullptr; + } + if (baseType.isArray() || !baseType.componentType().isInteger()) { + context.fErrors->error(pos, + "'" + std::string(op.tightOperatorName()) + + "' cannot operate on '" + baseType.displayName() + "'"); + return nullptr; + } + if (baseType.isLiteral()) { + // The expression `~123` is no longer a literal; coerce to the actual type. + base = baseType.scalarTypeForLiteral().coerceExpression(std::move(base), context); + if (!base) { + return nullptr; + } + } + break; + + default: + SK_ABORT("unsupported prefix operator"); + } + + std::unique_ptr<Expression> result = PrefixExpression::Make(context, pos, op, std::move(base)); + SkASSERT(result->fPosition == pos); + return result; +} + +std::unique_ptr<Expression> PrefixExpression::Make(const Context& context, Position pos, + Operator op, std::unique_ptr<Expression> base) { + switch (op.kind()) { + case Operator::Kind::PLUS: + SkASSERT(!base->type().isArray()); + SkASSERT(base->type().componentType().isNumber()); + base->fPosition = pos; + return base; + + case Operator::Kind::MINUS: + SkASSERT(!base->type().isArray()); + SkASSERT(base->type().componentType().isNumber()); + return negate_operand(context, pos, std::move(base)); + + case Operator::Kind::LOGICALNOT: + SkASSERT(base->type().isBoolean()); + return logical_not_operand(context, pos, std::move(base)); + + case Operator::Kind::PLUSPLUS: + case Operator::Kind::MINUSMINUS: + SkASSERT(base->type().isNumber()); + SkASSERT(Analysis::IsAssignable(*base)); + break; + + case Operator::Kind::BITWISENOT: + SkASSERT(!context.fConfig->strictES2Mode()); + SkASSERT(!base->type().isArray()); + SkASSERT(base->type().componentType().isInteger()); + SkASSERT(!base->type().isLiteral()); + break; + + default: + SkDEBUGFAILF("unsupported prefix operator: %s", op.operatorName()); + } + + return std::make_unique<PrefixExpression>(pos, op, std::move(base)); +} + +std::string PrefixExpression::description(OperatorPrecedence parentPrecedence) const { + bool needsParens = (OperatorPrecedence::kPrefix >= parentPrecedence); + return std::string(needsParens ? "(" : "") + + std::string(this->getOperator().tightOperatorName()) + + this->operand()->description(OperatorPrecedence::kPrefix) + + std::string(needsParens ? ")" : ""); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h new file mode 100644 index 0000000000..58c41d404a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLPrefixExpression.h @@ -0,0 +1,73 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PREFIXEXPRESSION +#define SKSL_PREFIXEXPRESSION + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLOperator.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; + +/** + * An expression modified by a unary operator appearing before it, such as '!flag'. + */ +class PrefixExpression final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kPrefix; + + // Use PrefixExpression::Make to automatically simplify various prefix expression types. + PrefixExpression(Position pos, Operator op, std::unique_ptr<Expression> operand) + : INHERITED(pos, kIRNodeKind, &operand->type()) + , fOperator(op) + , fOperand(std::move(operand)) {} + + // Creates an SkSL prefix expression; uses the ErrorReporter to report errors. + static std::unique_ptr<Expression> Convert(const Context& context, Position pos, Operator op, + std::unique_ptr<Expression> base); + + // Creates an SkSL prefix expression; reports errors via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, Position pos, Operator op, + std::unique_ptr<Expression> base); + + Operator getOperator() const { + return fOperator; + } + + std::unique_ptr<Expression>& operand() { + return fOperand; + } + + const std::unique_ptr<Expression>& operand() const { + return fOperand; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<PrefixExpression>(pos, this->getOperator(), + this->operand()->clone()); + } + + std::string description(OperatorPrecedence parentPrecedence) const override; + +private: + Operator fOperator; + std::unique_ptr<Expression> fOperand; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLProgram.cpp b/gfx/skia/skia/src/sksl/ir/SkSLProgram.cpp new file mode 100644 index 0000000000..332e4a2ac4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLProgram.cpp @@ -0,0 +1,116 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLString.h" +#include "include/private/SkSLSymbol.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSymbolTable.h" // IWYU pragma: keep +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <type_traits> +#include <utility> + +namespace SkSL { + +Program::Program(std::unique_ptr<std::string> source, + std::unique_ptr<ProgramConfig> config, + std::shared_ptr<Context> context, + std::vector<std::unique_ptr<ProgramElement>> elements, + std::vector<const ProgramElement*> sharedElements, + std::unique_ptr<ModifiersPool> modifiers, + std::shared_ptr<SymbolTable> symbols, + std::unique_ptr<Pool> pool, + Inputs inputs) + : fSource(std::move(source)) + , fConfig(std::move(config)) + , fContext(context) + , fModifiers(std::move(modifiers)) + , fSymbols(symbols) + , fPool(std::move(pool)) + , fOwnedElements(std::move(elements)) + , fSharedElements(std::move(sharedElements)) + , fInputs(inputs) { + fUsage = Analysis::GetUsage(*this); +} + +Program::~Program() { + // Some or all of the program elements are in the pool. To free them safely, we must attach + // the pool before destroying any program elements. (Otherwise, we may accidentally call + // delete on a pooled node.) + AutoAttachPoolToThread attach(fPool.get()); + + fOwnedElements.clear(); + fContext.reset(); + fSymbols.reset(); + fModifiers.reset(); +} + +std::string Program::description() const { + std::string result = fConfig->versionDescription(); + for (const ProgramElement* e : this->elements()) { + result += e->description(); + } + return result; +} + +const FunctionDeclaration* Program::getFunction(const char* functionName) const { + const Symbol* symbol = fSymbols->find(functionName); + bool valid = symbol && symbol->is<FunctionDeclaration>() && + symbol->as<FunctionDeclaration>().definition(); + return valid ? &symbol->as<FunctionDeclaration>() : nullptr; +} + +static void gather_uniforms(UniformInfo* info, const Type& type, const std::string& name) { + switch (type.typeKind()) { + case Type::TypeKind::kStruct: + for (const auto& f : type.fields()) { + gather_uniforms(info, *f.fType, name + "." + std::string(f.fName)); + } + break; + case Type::TypeKind::kArray: + for (int i = 0; i < type.columns(); ++i) { + gather_uniforms(info, type.componentType(), + String::printf("%s[%d]", name.c_str(), i)); + } + break; + case Type::TypeKind::kScalar: + case Type::TypeKind::kVector: + case Type::TypeKind::kMatrix: + info->fUniforms.push_back({name, type.componentType().numberKind(), + type.rows(), type.columns(), info->fUniformSlotCount}); + info->fUniformSlotCount += type.slotCount(); + break; + default: + break; + } +} + +std::unique_ptr<UniformInfo> Program::getUniformInfo() { + auto info = std::make_unique<UniformInfo>(); + for (const ProgramElement* e : this->elements()) { + if (!e->is<GlobalVarDeclaration>()) { + continue; + } + const GlobalVarDeclaration& decl = e->as<GlobalVarDeclaration>(); + const Variable& var = *decl.varDeclaration().var(); + if (var.modifiers().fFlags & Modifiers::kUniform_Flag) { + gather_uniforms(info.get(), var.type(), std::string(var.name())); + } + } + return info; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLProgram.h b/gfx/skia/skia/src/sksl/ir/SkSLProgram.h new file mode 100644 index 0000000000..fbdb936e49 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLProgram.h @@ -0,0 +1,171 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_PROGRAM +#define SKSL_PROGRAM + +#include "src/sksl/ir/SkSLType.h" + +#include <memory> +#include <string> +#include <vector> + +// name of the uniform used to handle features that are sensitive to whether Y is flipped. +// TODO: find a better home for this constant +#define SKSL_RTFLIP_NAME "u_skRTFlip" + +namespace SkSL { + +class Context; +class FunctionDeclaration; +class ModifiersPool; +class Pool; +class ProgramElement; +class ProgramUsage; +class SymbolTable; +struct ProgramConfig; + +/** Represents a list the Uniforms contained within a Program. */ +struct UniformInfo { + struct Uniform { + std::string fName; + SkSL::Type::NumberKind fKind; + int fColumns; + int fRows; + int fSlot; + }; + std::vector<Uniform> fUniforms; + int fUniformSlotCount = 0; +}; + +/** + * Represents a fully-digested program, ready for code generation. + */ +struct Program { + struct Inputs { + bool fUseFlipRTUniform = false; + bool operator==(const Inputs& that) const { + return fUseFlipRTUniform == that.fUseFlipRTUniform; + } + bool operator!=(const Inputs& that) const { return !(*this == that); } + }; + + Program(std::unique_ptr<std::string> source, + std::unique_ptr<ProgramConfig> config, + std::shared_ptr<Context> context, + std::vector<std::unique_ptr<ProgramElement>> elements, + std::vector<const ProgramElement*> sharedElements, + std::unique_ptr<ModifiersPool> modifiers, + std::shared_ptr<SymbolTable> symbols, + std::unique_ptr<Pool> pool, + Inputs inputs); + + ~Program(); + + class ElementsCollection { + public: + class iterator { + public: + const ProgramElement* operator*() { + if (fShared != fSharedEnd) { + return *fShared; + } else { + return fOwned->get(); + } + } + + iterator& operator++() { + if (fShared != fSharedEnd) { + ++fShared; + } else { + ++fOwned; + } + return *this; + } + + bool operator==(const iterator& other) const { + return fOwned == other.fOwned && fShared == other.fShared; + } + + bool operator!=(const iterator& other) const { + return !(*this == other); + } + + private: + using Owned = std::vector<std::unique_ptr<ProgramElement>>::const_iterator; + using Shared = std::vector<const ProgramElement*>::const_iterator; + friend class ElementsCollection; + + iterator(Owned owned, Owned ownedEnd, Shared shared, Shared sharedEnd) + : fOwned(owned), fOwnedEnd(ownedEnd), fShared(shared), fSharedEnd(sharedEnd) {} + + Owned fOwned; + Owned fOwnedEnd; + Shared fShared; + Shared fSharedEnd; + }; + + iterator begin() const { + return iterator(fProgram.fOwnedElements.begin(), fProgram.fOwnedElements.end(), + fProgram.fSharedElements.begin(), fProgram.fSharedElements.end()); + } + + iterator end() const { + return iterator(fProgram.fOwnedElements.end(), fProgram.fOwnedElements.end(), + fProgram.fSharedElements.end(), fProgram.fSharedElements.end()); + } + + private: + friend struct Program; + + ElementsCollection(const Program& program) : fProgram(program) {} + const Program& fProgram; + }; + + /** + * Iterates over *all* elements in this Program, both owned and shared (builtin). The iterator's + * value type is `const ProgramElement*`, so it's clear that you *must not* modify anything (as + * you might be mutating shared data). + */ + ElementsCollection elements() const { return ElementsCollection(*this); } + + /** + * Returns a function declaration with the given name; null is returned if the function doesn't + * exist or has no definition. If the function might have overloads, you can use nextOverload() + * to search for the function with the expected parameter list. + */ + const FunctionDeclaration* getFunction(const char* functionName) const; + + /** + * Returns a list of uniforms used by this Program. The uniform list will exclude opaque types + * like textures, samplers, or child effects. + */ + std::unique_ptr<UniformInfo> getUniformInfo(); + + std::string description() const; + const ProgramUsage* usage() const { return fUsage.get(); } + + std::unique_ptr<std::string> fSource; + std::unique_ptr<ProgramConfig> fConfig; + std::shared_ptr<Context> fContext; + std::unique_ptr<ProgramUsage> fUsage; + std::unique_ptr<ModifiersPool> fModifiers; + // it's important to keep fOwnedElements defined after (and thus destroyed before) fSymbols, + // because destroying elements can modify reference counts in symbols + std::shared_ptr<SymbolTable> fSymbols; + std::unique_ptr<Pool> fPool; + // Contains *only* elements owned exclusively by this program. + std::vector<std::unique_ptr<ProgramElement>> fOwnedElements; + // Contains *only* elements owned by a built-in module that are included in this program. + // Use elements() to iterate over the combined set of owned + shared elements. + std::vector<const ProgramElement*> fSharedElements; + Inputs fInputs; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h new file mode 100644 index 0000000000..3ee739fb79 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLReturnStatement.h @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_RETURNSTATEMENT +#define SKSL_RETURNSTATEMENT + +#include "include/private/SkSLStatement.h" +#include "src/sksl/ir/SkSLExpression.h" + +namespace SkSL { + +/** + * A 'return' statement. + */ +class ReturnStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kReturn; + + ReturnStatement(Position pos, std::unique_ptr<Expression> expression) + : INHERITED(pos, kIRNodeKind) + , fExpression(std::move(expression)) {} + + static std::unique_ptr<Statement> Make(Position pos, + std::unique_ptr<Expression> expression) { + return std::make_unique<ReturnStatement>(pos, std::move(expression)); + } + + std::unique_ptr<Expression>& expression() { + return fExpression; + } + + const std::unique_ptr<Expression>& expression() const { + return fExpression; + } + + void setExpression(std::unique_ptr<Expression> expr) { + fExpression = std::move(expr); + } + + std::unique_ptr<Statement> clone() const override { + return std::make_unique<ReturnStatement>(fPosition, + this->expression() ? this->expression()->clone() : nullptr); + } + + std::string description() const override { + if (this->expression()) { + return "return " + this->expression()->description() + ";"; + } else { + return "return;"; + } + } + +private: + std::unique_ptr<Expression> fExpression; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp new file mode 100644 index 0000000000..303549bcf7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSetting.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLSetting.h" + +#include "include/core/SkTypes.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLLiteral.h" + +#include <initializer_list> + +namespace SkSL { + +namespace { + +using CapsLookupTable = SkTHashMap<std::string_view, Setting::CapsPtr>; + +static const CapsLookupTable& caps_lookup_table() { + // Create a lookup table that converts strings into the equivalent ShaderCaps member-pointers. + static CapsLookupTable* sCapsLookupTable = new CapsLookupTable({ + CapsLookupTable::Pair("mustDoOpBetweenFloorAndAbs", + &ShaderCaps::fMustDoOpBetweenFloorAndAbs), + CapsLookupTable::Pair("mustGuardDivisionEvenAfterExplicitZeroCheck", + &ShaderCaps::fMustGuardDivisionEvenAfterExplicitZeroCheck), + CapsLookupTable::Pair("atan2ImplementedAsAtanYOverX", + &ShaderCaps::fAtan2ImplementedAsAtanYOverX), + CapsLookupTable::Pair("floatIs32Bits", + &ShaderCaps::fFloatIs32Bits), + CapsLookupTable::Pair("integerSupport", + &ShaderCaps::fIntegerSupport), + CapsLookupTable::Pair("builtinDeterminantSupport", + &ShaderCaps::fBuiltinDeterminantSupport), + CapsLookupTable::Pair("rewriteMatrixVectorMultiply", + &ShaderCaps::fRewriteMatrixVectorMultiply), + }); + return *sCapsLookupTable; +} + +} // namespace + +std::string_view Setting::name() const { + for (const auto& [name, capsPtr] : caps_lookup_table()) { + if (capsPtr == fCapsPtr) { + return name; + } + } + SkUNREACHABLE; +} + +std::unique_ptr<Expression> Setting::Convert(const Context& context, + Position pos, + const std::string_view& name) { + SkASSERT(context.fConfig); + + if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { + context.fErrors->error(pos, "name 'sk_Caps' is reserved"); + return nullptr; + } + + const CapsPtr* capsPtr = caps_lookup_table().find(name); + if (!capsPtr) { + context.fErrors->error(pos, "unknown capability flag '" + std::string(name) + "'"); + return nullptr; + } + + return Setting::Make(context, pos, *capsPtr); +} + +std::unique_ptr<Expression> Setting::Make(const Context& context, Position pos, CapsPtr capsPtr) { + if (context.fCaps) { + // We know the caps values--return a boolean literal. + return Literal::MakeBool(context, pos, context.fCaps->*capsPtr); + } + + // We don't know the caps values yet--generate a Setting IRNode. + return std::make_unique<Setting>(pos, capsPtr, context.fTypes.fBool.get()); +} + +std::unique_ptr<Expression> Setting::toLiteral(const Context& context) const { + SkASSERT(context.fCaps); + return Literal::MakeBool(fPosition, context.fCaps->*fCapsPtr, &this->type()); +} + + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSetting.h b/gfx/skia/skia/src/sksl/ir/SkSLSetting.h new file mode 100644 index 0000000000..6085744ad0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSetting.h @@ -0,0 +1,75 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SETTING +#define SKSL_SETTING + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> + + +namespace SkSL { + +class Context; +class Type; +enum class OperatorPrecedence : uint8_t; + +/** + * Represents a compile-time constant setting, such as sk_Caps.integerSupport. These IRNodes are + * used when assembling a module. These nodes are replaced with the value of the setting during + * compilation when ShaderCaps are available. + */ +class Setting final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kSetting; + + using CapsPtr = const bool ShaderCaps::*; + + Setting(Position pos, CapsPtr capsPtr, const Type* type) + : INHERITED(pos, kIRNodeKind, type) + , fCapsPtr(capsPtr) {} + + // Creates the current value of the associated caps bit as a Literal if ShaderCaps are + // available, or a Setting IRNode when ShaderCaps are not known. Reports errors via the + // ErrorReporter. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + const std::string_view& name); + + // Creates the current value of the passed-in caps bit as a Literal if ShaderCaps are + // available, or a Setting IRNode when ShaderCaps are not known. + static std::unique_ptr<Expression> Make(const Context& context, Position pos, CapsPtr capsPtr); + + // Converts a Setting expression to its actual ShaderCaps value (boolean true/false). + std::unique_ptr<Expression> toLiteral(const Context& context) const; + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<Setting>(pos, fCapsPtr, &this->type()); + } + + std::string_view name() const; + + std::string description(OperatorPrecedence) const override { + return "sk_Caps." + std::string(this->name()); + } + +private: + CapsPtr fCapsPtr; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLStructDefinition.h b/gfx/skia/skia/src/sksl/ir/SkSLStructDefinition.h new file mode 100644 index 0000000000..4b6b5b81d7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLStructDefinition.h @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_STRUCTDEFINITION +#define SKSL_STRUCTDEFINITION + +#include <memory> + +#include "include/private/SkSLProgramElement.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +/** + * A struct at global scope, as in: + * + * struct RenderData { + * float3 color; + * bool highQuality; + * }; + */ +class StructDefinition final : public ProgramElement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kStructDefinition; + + StructDefinition(Position pos, const Type& type) + : INHERITED(pos, kIRNodeKind) + , fType(&type) {} + + const Type& type() const { + return *fType; + } + + std::unique_ptr<ProgramElement> clone() const override { + return std::make_unique<StructDefinition>(fPosition, this->type()); + } + + std::string description() const override { + std::string s = "struct "; + s += this->type().name(); + s += " { "; + for (const auto& f : this->type().fields()) { + s += f.fModifiers.description(); + s += f.fType->description(); + s += " "; + s += f.fName; + s += "; "; + } + s += "};"; + return s; + } + +private: + const Type* fType = nullptr; + + using INHERITED = ProgramElement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h b/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h new file mode 100644 index 0000000000..206693ceaf --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchCase.h @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SWITCHCASE +#define SKSL_SWITCHCASE + +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLString.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <cinttypes> + +namespace SkSL { + +/** + * A single case of a 'switch' statement. + */ +class SwitchCase final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kSwitchCase; + + static std::unique_ptr<SwitchCase> Make(Position pos, SKSL_INT value, + std::unique_ptr<Statement> statement) { + return std::unique_ptr<SwitchCase>(new SwitchCase(pos, /*isDefault=*/false, value, + std::move(statement))); + } + + static std::unique_ptr<SwitchCase> MakeDefault(Position pos, + std::unique_ptr<Statement> statement) { + return std::unique_ptr<SwitchCase>(new SwitchCase(pos, /*isDefault=*/true, -1, + std::move(statement))); + } + + bool isDefault() const { + return fDefault; + } + + SKSL_INT value() const { + SkASSERT(!this->isDefault()); + return fValue; + } + + std::unique_ptr<Statement>& statement() { + return fStatement; + } + + const std::unique_ptr<Statement>& statement() const { + return fStatement; + } + + std::unique_ptr<Statement> clone() const override { + return fDefault ? SwitchCase::MakeDefault(fPosition, this->statement()->clone()) + : SwitchCase::Make(fPosition, this->value(), this->statement()->clone()); + } + + std::string description() const override { + if (this->isDefault()) { + return String::printf("default:\n%s", fStatement->description().c_str()); + } else { + return String::printf("case %" PRId64 ":\n%s", + (int64_t) this->value(), + fStatement->description().c_str()); + } + } + +private: + SwitchCase(Position pos, bool isDefault, SKSL_INT value, std::unique_ptr<Statement> statement) + : INHERITED(pos, kIRNodeKind) + , fDefault(isDefault) + , fValue(std::move(value)) + , fStatement(std::move(statement)) {} + + bool fDefault; + SKSL_INT fValue; + std::unique_ptr<Statement> fStatement; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.cpp new file mode 100644 index 0000000000..f0bb7d05ce --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.cpp @@ -0,0 +1,275 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLSwitchStatement.h" + +#include "include/core/SkTypes.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" + +#include <algorithm> +#include <forward_list> +#include <iterator> + +namespace SkSL { + +std::unique_ptr<Statement> SwitchStatement::clone() const { + StatementArray cases; + cases.reserve_back(this->cases().size()); + for (const std::unique_ptr<Statement>& stmt : this->cases()) { + cases.push_back(stmt->clone()); + } + return std::make_unique<SwitchStatement>(fPosition, + this->value()->clone(), + std::move(cases), + SymbolTable::WrapIfBuiltin(this->symbols())); +} + +std::string SwitchStatement::description() const { + std::string result; + result += String::printf("switch (%s) {\n", this->value()->description().c_str()); + for (const auto& c : this->cases()) { + result += c->description(); + } + result += "}"; + return result; +} + +static std::forward_list<const SwitchCase*> find_duplicate_case_values( + const StatementArray& cases) { + std::forward_list<const SwitchCase*> duplicateCases; + SkTHashSet<SKSL_INT> intValues; + bool foundDefault = false; + + for (const std::unique_ptr<Statement>& stmt : cases) { + const SwitchCase* sc = &stmt->as<SwitchCase>(); + if (sc->isDefault()) { + if (foundDefault) { + duplicateCases.push_front(sc); + continue; + } + foundDefault = true; + } else { + SKSL_INT value = sc->value(); + if (intValues.contains(value)) { + duplicateCases.push_front(sc); + continue; + } + intValues.add(value); + } + } + + return duplicateCases; +} + +static void move_all_but_break(std::unique_ptr<Statement>& stmt, StatementArray* target) { + switch (stmt->kind()) { + case Statement::Kind::kBlock: { + // Recurse into the block. + Block& block = stmt->as<Block>(); + + StatementArray blockStmts; + blockStmts.reserve_back(block.children().size()); + for (std::unique_ptr<Statement>& blockStmt : block.children()) { + move_all_but_break(blockStmt, &blockStmts); + } + + target->push_back(Block::Make(block.fPosition, std::move(blockStmts), block.blockKind(), + block.symbolTable())); + break; + } + + case Statement::Kind::kBreak: + // Do not append a break to the target. + break; + + default: + // Append normal statements to the target. + target->push_back(std::move(stmt)); + break; + } +} + +std::unique_ptr<Statement> SwitchStatement::BlockForCase(StatementArray* cases, + SwitchCase* caseToCapture, + std::shared_ptr<SymbolTable> symbolTable) { + // We have to be careful to not move any of the pointers until after we're sure we're going to + // succeed, so before we make any changes at all, we check the switch-cases to decide on a plan + // of action. First, find the switch-case we are interested in. + auto iter = cases->begin(); + for (; iter != cases->end(); ++iter) { + const SwitchCase& sc = (*iter)->as<SwitchCase>(); + if (&sc == caseToCapture) { + break; + } + } + + // Next, walk forward through the rest of the switch. If we find a conditional break, we're + // stuck and can't simplify at all. If we find an unconditional break, we have a range of + // statements that we can use for simplification. + auto startIter = iter; + Statement* stripBreakStmt = nullptr; + for (; iter != cases->end(); ++iter) { + std::unique_ptr<Statement>& stmt = (*iter)->as<SwitchCase>().statement(); + if (Analysis::SwitchCaseContainsConditionalExit(*stmt)) { + // We can't reduce switch-cases to a block when they have conditional exits. + return nullptr; + } + if (Analysis::SwitchCaseContainsUnconditionalExit(*stmt)) { + // We found an unconditional exit. We can use this block, but we'll need to strip + // out the break statement if there is one. + stripBreakStmt = stmt.get(); + break; + } + } + + // We fell off the bottom of the switch or encountered a break. We know the range of statements + // that we need to move over, and we know it's safe to do so. + StatementArray caseStmts; + caseStmts.reserve_back(std::distance(startIter, iter) + 1); + + // We can move over most of the statements as-is. + while (startIter != iter) { + caseStmts.push_back(std::move((*startIter)->as<SwitchCase>().statement())); + ++startIter; + } + + // If we found an unconditional break at the end, we need to move what we can while avoiding + // that break. + if (stripBreakStmt != nullptr) { + SkASSERT((*startIter)->as<SwitchCase>().statement().get() == stripBreakStmt); + move_all_but_break((*startIter)->as<SwitchCase>().statement(), &caseStmts); + } + + // Return our newly-synthesized block. + return Block::Make(caseToCapture->fPosition, std::move(caseStmts), Block::Kind::kBracedScope, + std::move(symbolTable)); +} + +std::unique_ptr<Statement> SwitchStatement::Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> value, + ExpressionArray caseValues, + StatementArray caseStatements, + std::shared_ptr<SymbolTable> symbolTable) { + SkASSERT(caseValues.size() == caseStatements.size()); + + value = context.fTypes.fInt->coerceExpression(std::move(value), context); + if (!value) { + return nullptr; + } + + StatementArray cases; + for (int i = 0; i < caseValues.size(); ++i) { + if (caseValues[i]) { + Position casePos = caseValues[i]->fPosition; + // Case values must be constant integers of the same type as the switch value + std::unique_ptr<Expression> caseValue = value->type().coerceExpression( + std::move(caseValues[i]), context); + if (!caseValue) { + return nullptr; + } + SKSL_INT intValue; + if (!ConstantFolder::GetConstantInt(*caseValue, &intValue)) { + context.fErrors->error(casePos, "case value must be a constant integer"); + return nullptr; + } + cases.push_back(SwitchCase::Make(casePos, intValue, std::move(caseStatements[i]))); + } else { + cases.push_back(SwitchCase::MakeDefault(pos, std::move(caseStatements[i]))); + } + } + + // Detect duplicate `case` labels and report an error. + // (Using forward_list here to optimize for the common case of no results.) + std::forward_list<const SwitchCase*> duplicateCases = find_duplicate_case_values(cases); + if (!duplicateCases.empty()) { + duplicateCases.reverse(); + for (const SwitchCase* sc : duplicateCases) { + if (sc->isDefault()) { + context.fErrors->error(sc->fPosition, "duplicate default case"); + } else { + context.fErrors->error(sc->fPosition, "duplicate case value '" + + std::to_string(sc->value()) + "'"); + } + } + return nullptr; + } + + return SwitchStatement::Make( + context, pos, std::move(value), std::move(cases), std::move(symbolTable)); +} + +std::unique_ptr<Statement> SwitchStatement::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> value, + StatementArray cases, + std::shared_ptr<SymbolTable> symbolTable) { + // Confirm that every statement in `cases` is a SwitchCase. + SkASSERT(std::all_of(cases.begin(), cases.end(), [&](const std::unique_ptr<Statement>& stmt) { + return stmt->is<SwitchCase>(); + })); + + // Confirm that every switch-case value is unique. + SkASSERT(find_duplicate_case_values(cases).empty()); + + // Flatten switch statements if we're optimizing, and the value is known + if (context.fConfig->fSettings.fOptimize) { + SKSL_INT switchValue; + if (ConstantFolder::GetConstantInt(*value, &switchValue)) { + SwitchCase* defaultCase = nullptr; + SwitchCase* matchingCase = nullptr; + for (const std::unique_ptr<Statement>& stmt : cases) { + SwitchCase& sc = stmt->as<SwitchCase>(); + if (sc.isDefault()) { + defaultCase = ≻ + continue; + } + + if (sc.value() == switchValue) { + matchingCase = ≻ + break; + } + } + + if (!matchingCase) { + // No case value matches the switch value. + if (!defaultCase) { + // No default switch-case exists; the switch had no effect. + // We can eliminate the entire switch! + return Nop::Make(); + } + // We had a default case; that's what we matched with. + matchingCase = defaultCase; + } + + // Convert the switch-case that we matched with into a block. + std::unique_ptr<Statement> newBlock = BlockForCase(&cases, matchingCase, symbolTable); + if (newBlock) { + return newBlock; + } + } + } + + // The switch couldn't be optimized away; emit it normally. + return std::make_unique<SwitchStatement>( + pos, std::move(value), std::move(cases), std::move(symbolTable)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h new file mode 100644 index 0000000000..71b96aa229 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwitchStatement.h @@ -0,0 +1,102 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SWITCHSTATEMENT +#define SKSL_SWITCHSTATEMENT + +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLStatement.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +class SwitchCase; +class SymbolTable; + +/** + * A 'switch' statement. + */ +class SwitchStatement final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kSwitch; + + SwitchStatement(Position pos, std::unique_ptr<Expression> value, + StatementArray cases, std::shared_ptr<SymbolTable> symbols) + : INHERITED(pos, kIRNodeKind) + , fValue(std::move(value)) + , fCases(std::move(cases)) + , fSymbols(std::move(symbols)) {} + + // Create a `switch` statement with an array of case-values and case-statements. + // Coerces case values to the proper type and reports an error if cases are duplicated. + // Reports errors via the ErrorReporter. + static std::unique_ptr<Statement> Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> value, + ExpressionArray caseValues, + StatementArray caseStatements, + std::shared_ptr<SymbolTable> symbolTable); + + // Create a `switch` statement with an array of SwitchCases. The array of SwitchCases must + // already contain non-overlapping, correctly-typed case values. Reports errors via ASSERT. + static std::unique_ptr<Statement> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> value, + StatementArray cases, + std::shared_ptr<SymbolTable> symbolTable); + + // Returns a block containing all of the statements that will be run if the given case matches + // (which, owing to the statements being owned by unique_ptrs, means the switch itself will be + // disassembled by this call and must then be discarded). + // Returns null (and leaves the switch unmodified) if no such simple reduction is possible, such + // as when break statements appear inside conditionals. + static std::unique_ptr<Statement> BlockForCase(StatementArray* cases, + SwitchCase* caseToCapture, + std::shared_ptr<SymbolTable> symbolTable); + + std::unique_ptr<Expression>& value() { + return fValue; + } + + const std::unique_ptr<Expression>& value() const { + return fValue; + } + + StatementArray& cases() { + return fCases; + } + + const StatementArray& cases() const { + return fCases; + } + + const std::shared_ptr<SymbolTable>& symbols() const { + return fSymbols; + } + + std::unique_ptr<Statement> clone() const override; + + std::string description() const override; + +private: + std::unique_ptr<Expression> fValue; + StatementArray fCases; // every Statement inside fCases must be a SwitchCase + std::shared_ptr<SymbolTable> fSymbols; + + using INHERITED = Statement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.cpp new file mode 100644 index 0000000000..a19ff0274e --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.cpp @@ -0,0 +1,548 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLSwizzle.h" + +#include "include/core/SkSpan.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTArray.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLConstructorCompoundCast.h" +#include "src/sksl/ir/SkSLConstructorScalarCast.h" +#include "src/sksl/ir/SkSLConstructorSplat.h" +#include "src/sksl/ir/SkSLLiteral.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <optional> + +namespace SkSL { + +static bool validate_swizzle_domain(const ComponentArray& fields) { + enum SwizzleDomain { + kCoordinate, + kColor, + kUV, + kRectangle, + }; + + std::optional<SwizzleDomain> domain; + + for (int8_t field : fields) { + SwizzleDomain fieldDomain; + switch (field) { + case SwizzleComponent::X: + case SwizzleComponent::Y: + case SwizzleComponent::Z: + case SwizzleComponent::W: + fieldDomain = kCoordinate; + break; + case SwizzleComponent::R: + case SwizzleComponent::G: + case SwizzleComponent::B: + case SwizzleComponent::A: + fieldDomain = kColor; + break; + case SwizzleComponent::S: + case SwizzleComponent::T: + case SwizzleComponent::P: + case SwizzleComponent::Q: + fieldDomain = kUV; + break; + case SwizzleComponent::UL: + case SwizzleComponent::UT: + case SwizzleComponent::UR: + case SwizzleComponent::UB: + fieldDomain = kRectangle; + break; + case SwizzleComponent::ZERO: + case SwizzleComponent::ONE: + continue; + default: + return false; + } + + if (!domain.has_value()) { + domain = fieldDomain; + } else if (domain != fieldDomain) { + return false; + } + } + + return true; +} + +static char mask_char(int8_t component) { + switch (component) { + case SwizzleComponent::X: return 'x'; + case SwizzleComponent::Y: return 'y'; + case SwizzleComponent::Z: return 'z'; + case SwizzleComponent::W: return 'w'; + case SwizzleComponent::R: return 'r'; + case SwizzleComponent::G: return 'g'; + case SwizzleComponent::B: return 'b'; + case SwizzleComponent::A: return 'a'; + case SwizzleComponent::S: return 's'; + case SwizzleComponent::T: return 't'; + case SwizzleComponent::P: return 'p'; + case SwizzleComponent::Q: return 'q'; + case SwizzleComponent::UL: return 'L'; + case SwizzleComponent::UT: return 'T'; + case SwizzleComponent::UR: return 'R'; + case SwizzleComponent::UB: return 'B'; + case SwizzleComponent::ZERO: return '0'; + case SwizzleComponent::ONE: return '1'; + default: SkUNREACHABLE; + } +} + +static std::string mask_string(const ComponentArray& components) { + std::string result; + for (int8_t component : components) { + result += mask_char(component); + } + return result; +} + +static std::unique_ptr<Expression> optimize_constructor_swizzle(const Context& context, + Position pos, + const ConstructorCompound& base, + ComponentArray components) { + auto baseArguments = base.argumentSpan(); + std::unique_ptr<Expression> replacement; + const Type& exprType = base.type(); + const Type& componentType = exprType.componentType(); + int swizzleSize = components.size(); + + // Swizzles can duplicate some elements and discard others, e.g. + // `half4(1, 2, 3, 4).xxz` --> `half3(1, 1, 3)`. However, there are constraints: + // - Expressions with side effects need to occur exactly once, even if they would otherwise be + // swizzle-eliminated + // - Non-trivial expressions should not be repeated, but elimination is OK. + // + // Look up the argument for the constructor at each index. This is typically simple but for + // weird cases like `half4(bar.yz, half2(foo))`, it can be harder than it seems. This example + // would result in: + // argMap[0] = {.fArgIndex = 0, .fComponent = 0} (bar.yz .x) + // argMap[1] = {.fArgIndex = 0, .fComponent = 1} (bar.yz .y) + // argMap[2] = {.fArgIndex = 1, .fComponent = 0} (half2(foo) .x) + // argMap[3] = {.fArgIndex = 1, .fComponent = 1} (half2(foo) .y) + struct ConstructorArgMap { + int8_t fArgIndex; + int8_t fComponent; + }; + + int numConstructorArgs = base.type().columns(); + ConstructorArgMap argMap[4] = {}; + int writeIdx = 0; + for (int argIdx = 0; argIdx < (int)baseArguments.size(); ++argIdx) { + const Expression& arg = *baseArguments[argIdx]; + const Type& argType = arg.type(); + + if (!argType.isScalar() && !argType.isVector()) { + return nullptr; + } + + int argSlots = argType.slotCount(); + for (int componentIdx = 0; componentIdx < argSlots; ++componentIdx) { + argMap[writeIdx].fArgIndex = argIdx; + argMap[writeIdx].fComponent = componentIdx; + ++writeIdx; + } + } + SkASSERT(writeIdx == numConstructorArgs); + + // Count up the number of times each constructor argument is used by the swizzle. + // `half4(bar.yz, half2(foo)).xwxy` -> { 3, 1 } + // - bar.yz is referenced 3 times, by `.x_xy` + // - half(foo) is referenced 1 time, by `._w__` + int8_t exprUsed[4] = {}; + for (int8_t c : components) { + exprUsed[argMap[c].fArgIndex]++; + } + + for (int index = 0; index < numConstructorArgs; ++index) { + int8_t constructorArgIndex = argMap[index].fArgIndex; + const Expression& baseArg = *baseArguments[constructorArgIndex]; + + // Check that non-trivial expressions are not swizzled in more than once. + if (exprUsed[constructorArgIndex] > 1 && !Analysis::IsTrivialExpression(baseArg)) { + return nullptr; + } + // Check that side-effect-bearing expressions are swizzled in exactly once. + if (exprUsed[constructorArgIndex] != 1 && Analysis::HasSideEffects(baseArg)) { + return nullptr; + } + } + + struct ReorderedArgument { + int8_t fArgIndex; + ComponentArray fComponents; + }; + SkSTArray<4, ReorderedArgument> reorderedArgs; + for (int8_t c : components) { + const ConstructorArgMap& argument = argMap[c]; + const Expression& baseArg = *baseArguments[argument.fArgIndex]; + + if (baseArg.type().isScalar()) { + // This argument is a scalar; add it to the list as-is. + SkASSERT(argument.fComponent == 0); + reorderedArgs.push_back({argument.fArgIndex, + ComponentArray{}}); + } else { + // This argument is a component from a vector. + SkASSERT(baseArg.type().isVector()); + SkASSERT(argument.fComponent < baseArg.type().columns()); + if (reorderedArgs.empty() || + reorderedArgs.back().fArgIndex != argument.fArgIndex) { + // This can't be combined with the previous argument. Add a new one. + reorderedArgs.push_back({argument.fArgIndex, + ComponentArray{argument.fComponent}}); + } else { + // Since we know this argument uses components, it should already have at least one + // component set. + SkASSERT(!reorderedArgs.back().fComponents.empty()); + // Build up the current argument with one more component. + reorderedArgs.back().fComponents.push_back(argument.fComponent); + } + } + } + + // Convert our reordered argument list to an actual array of expressions, with the new order and + // any new inner swizzles that need to be applied. + ExpressionArray newArgs; + newArgs.reserve_back(swizzleSize); + for (const ReorderedArgument& reorderedArg : reorderedArgs) { + std::unique_ptr<Expression> newArg = + baseArguments[reorderedArg.fArgIndex]->clone(); + + if (reorderedArg.fComponents.empty()) { + newArgs.push_back(std::move(newArg)); + } else { + newArgs.push_back(Swizzle::Make(context, pos, std::move(newArg), + reorderedArg.fComponents)); + } + } + + // Wrap the new argument list in a compound constructor. + return ConstructorCompound::Make(context, + pos, + componentType.toCompound(context, swizzleSize, /*rows=*/1), + std::move(newArgs)); +} + +std::unique_ptr<Expression> Swizzle::Convert(const Context& context, + Position pos, + Position maskPos, + std::unique_ptr<Expression> base, + std::string_view maskString) { + ComponentArray components; + for (size_t i = 0; i < maskString.length(); ++i) { + char field = maskString[i]; + switch (field) { + case '0': components.push_back(SwizzleComponent::ZERO); break; + case '1': components.push_back(SwizzleComponent::ONE); break; + case 'x': components.push_back(SwizzleComponent::X); break; + case 'r': components.push_back(SwizzleComponent::R); break; + case 's': components.push_back(SwizzleComponent::S); break; + case 'L': components.push_back(SwizzleComponent::UL); break; + case 'y': components.push_back(SwizzleComponent::Y); break; + case 'g': components.push_back(SwizzleComponent::G); break; + case 't': components.push_back(SwizzleComponent::T); break; + case 'T': components.push_back(SwizzleComponent::UT); break; + case 'z': components.push_back(SwizzleComponent::Z); break; + case 'b': components.push_back(SwizzleComponent::B); break; + case 'p': components.push_back(SwizzleComponent::P); break; + case 'R': components.push_back(SwizzleComponent::UR); break; + case 'w': components.push_back(SwizzleComponent::W); break; + case 'a': components.push_back(SwizzleComponent::A); break; + case 'q': components.push_back(SwizzleComponent::Q); break; + case 'B': components.push_back(SwizzleComponent::UB); break; + default: + context.fErrors->error(Position::Range(maskPos.startOffset() + i, + maskPos.startOffset() + i + 1), + String::printf("invalid swizzle component '%c'", field)); + return nullptr; + } + } + return Convert(context, pos, maskPos, std::move(base), std::move(components)); +} + +// Swizzles are complicated due to constant components. The most difficult case is a mask like +// '.x1w0'. A naive approach might turn that into 'float4(base.x, 1, base.w, 0)', but that evaluates +// 'base' twice. We instead group the swizzle mask ('xw') and constants ('1, 0') together and use a +// secondary swizzle to put them back into the right order, so in this case we end up with +// 'float4(base.xw, 1, 0).xzyw'. +std::unique_ptr<Expression> Swizzle::Convert(const Context& context, + Position pos, + Position rawMaskPos, + std::unique_ptr<Expression> base, + ComponentArray inComponents) { + Position maskPos = rawMaskPos.valid() ? rawMaskPos : pos; + if (!validate_swizzle_domain(inComponents)) { + context.fErrors->error(maskPos, "invalid swizzle mask '" + mask_string(inComponents) + "'"); + return nullptr; + } + + const Type& baseType = base->type().scalarTypeForLiteral(); + + if (!baseType.isVector() && !baseType.isScalar()) { + context.fErrors->error( + pos, "cannot swizzle value of type '" + baseType.displayName() + "'"); + return nullptr; + } + + if (inComponents.size() > 4) { + Position errorPos = rawMaskPos.valid() ? Position::Range(maskPos.startOffset() + 4, + maskPos.endOffset()) + : pos; + context.fErrors->error(errorPos, + "too many components in swizzle mask '" + mask_string(inComponents) + "'"); + return nullptr; + } + + ComponentArray maskComponents; + bool foundXYZW = false; + for (int i = 0; i < inComponents.size(); ++i) { + switch (inComponents[i]) { + case SwizzleComponent::ZERO: + case SwizzleComponent::ONE: + // Skip over constant fields for now. + break; + case SwizzleComponent::X: + case SwizzleComponent::R: + case SwizzleComponent::S: + case SwizzleComponent::UL: + foundXYZW = true; + maskComponents.push_back(SwizzleComponent::X); + break; + case SwizzleComponent::Y: + case SwizzleComponent::G: + case SwizzleComponent::T: + case SwizzleComponent::UT: + foundXYZW = true; + if (baseType.columns() >= 2) { + maskComponents.push_back(SwizzleComponent::Y); + break; + } + [[fallthrough]]; + case SwizzleComponent::Z: + case SwizzleComponent::B: + case SwizzleComponent::P: + case SwizzleComponent::UR: + foundXYZW = true; + if (baseType.columns() >= 3) { + maskComponents.push_back(SwizzleComponent::Z); + break; + } + [[fallthrough]]; + case SwizzleComponent::W: + case SwizzleComponent::A: + case SwizzleComponent::Q: + case SwizzleComponent::UB: + foundXYZW = true; + if (baseType.columns() >= 4) { + maskComponents.push_back(SwizzleComponent::W); + break; + } + [[fallthrough]]; + default: + // The swizzle component references a field that doesn't exist in the base type. + context.fErrors->error( + Position::Range(maskPos.startOffset() + i, + maskPos.startOffset() + i + 1), + String::printf("invalid swizzle component '%c'", + mask_char(inComponents[i]))); + return nullptr; + } + } + + if (!foundXYZW) { + context.fErrors->error(maskPos, "swizzle must refer to base expression"); + return nullptr; + } + + // Coerce literals in expressions such as `(12345).xxx` to their actual type. + base = baseType.coerceExpression(std::move(base), context); + if (!base) { + return nullptr; + } + + // First, we need a vector expression that is the non-constant portion of the swizzle, packed: + // scalar.xxx -> type3(scalar) + // scalar.x0x0 -> type2(scalar) + // vector.zyx -> vector.zyx + // vector.x0y0 -> vector.xy + std::unique_ptr<Expression> expr = Swizzle::Make(context, pos, std::move(base), maskComponents); + + // If we have processed the entire swizzle, we're done. + if (maskComponents.size() == inComponents.size()) { + return expr; + } + + // Now we create a constructor that has the correct number of elements for the final swizzle, + // with all fields at the start. It's not finished yet; constants we need will be added below. + // scalar.x0x0 -> type4(type2(x), ...) + // vector.y111 -> type4(vector.y, ...) + // vector.z10x -> type4(vector.zx, ...) + // + // The constructor will have at most three arguments: { base expr, constant 0, constant 1 } + ExpressionArray constructorArgs; + constructorArgs.reserve_back(3); + constructorArgs.push_back(std::move(expr)); + + // Apply another swizzle to shuffle the constants into the correct place. Any constant values we + // need are also tacked on to the end of the constructor. + // scalar.x0x0 -> type4(type2(x), 0).xyxy + // vector.y111 -> type2(vector.y, 1).xyyy + // vector.z10x -> type4(vector.zx, 1, 0).xzwy + const Type* scalarType = &baseType.componentType(); + ComponentArray swizzleComponents; + int maskFieldIdx = 0; + int constantFieldIdx = maskComponents.size(); + int constantZeroIdx = -1, constantOneIdx = -1; + + for (int i = 0; i < inComponents.size(); i++) { + switch (inComponents[i]) { + case SwizzleComponent::ZERO: + if (constantZeroIdx == -1) { + // Synthesize a '0' argument at the end of the constructor. + constructorArgs.push_back(Literal::Make(pos, /*value=*/0, scalarType)); + constantZeroIdx = constantFieldIdx++; + } + swizzleComponents.push_back(constantZeroIdx); + break; + case SwizzleComponent::ONE: + if (constantOneIdx == -1) { + // Synthesize a '1' argument at the end of the constructor. + constructorArgs.push_back(Literal::Make(pos, /*value=*/1, scalarType)); + constantOneIdx = constantFieldIdx++; + } + swizzleComponents.push_back(constantOneIdx); + break; + default: + // The non-constant fields are already in the expected order. + swizzleComponents.push_back(maskFieldIdx++); + break; + } + } + + expr = ConstructorCompound::Make(context, pos, + scalarType->toCompound(context, constantFieldIdx, /*rows=*/1), + std::move(constructorArgs)); + + // Create (and potentially optimize-away) the resulting swizzle-expression. + return Swizzle::Make(context, pos, std::move(expr), swizzleComponents); +} + +std::unique_ptr<Expression> Swizzle::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> expr, + ComponentArray components) { + const Type& exprType = expr->type(); + SkASSERTF(exprType.isVector() || exprType.isScalar(), + "cannot swizzle type '%s'", exprType.description().c_str()); + SkASSERT(components.size() >= 1 && components.size() <= 4); + + // Confirm that the component array only contains X/Y/Z/W. (Call MakeWith01 if you want support + // for ZERO and ONE. Once initial IR generation is complete, no swizzles should have zeros or + // ones in them.) + SkASSERT(std::all_of(components.begin(), components.end(), [](int8_t component) { + return component >= SwizzleComponent::X && + component <= SwizzleComponent::W; + })); + + // SkSL supports splatting a scalar via `scalar.xxxx`, but not all versions of GLSL allow this. + // Replace swizzles with equivalent splat constructors (`scalar.xxx` --> `half3(value)`). + if (exprType.isScalar()) { + return ConstructorSplat::Make(context, pos, + exprType.toCompound(context, components.size(), /*rows=*/1), + std::move(expr)); + } + + // Detect identity swizzles like `color.rgba` and optimize it away. + if (components.size() == exprType.columns()) { + bool identity = true; + for (int i = 0; i < components.size(); ++i) { + if (components[i] != i) { + identity = false; + break; + } + } + if (identity) { + expr->fPosition = pos; + return expr; + } + } + + // Optimize swizzles of swizzles, e.g. replace `foo.argb.rggg` with `foo.arrr`. + if (expr->is<Swizzle>()) { + Swizzle& base = expr->as<Swizzle>(); + ComponentArray combined; + for (int8_t c : components) { + combined.push_back(base.components()[c]); + } + + // It may actually be possible to further simplify this swizzle. Go again. + // (e.g. `color.abgr.abgr` --> `color.rgba` --> `color`.) + return Swizzle::Make(context, pos, std::move(base.base()), combined); + } + + // If we are swizzling a constant expression, we can use its value instead here (so that + // swizzles like `colorWhite.x` can be simplified to `1`). + const Expression* value = ConstantFolder::GetConstantValueForVariable(*expr); + + // `half4(scalar).zyy` can be optimized to `half3(scalar)`, and `half3(scalar).y` can be + // optimized to just `scalar`. The swizzle components don't actually matter, as every field + // in a splat constructor holds the same value. + if (value->is<ConstructorSplat>()) { + const ConstructorSplat& splat = value->as<ConstructorSplat>(); + return ConstructorSplat::Make( + context, pos, + splat.type().componentType().toCompound(context, components.size(), /*rows=*/1), + splat.argument()->clone()); + } + + // Swizzles on casts, like `half4(myFloat4).zyy`, can optimize to `half3(myFloat4.zyy)`. + if (value->is<ConstructorCompoundCast>()) { + const ConstructorCompoundCast& cast = value->as<ConstructorCompoundCast>(); + const Type& castType = cast.type().componentType().toCompound(context, components.size(), + /*rows=*/1); + std::unique_ptr<Expression> swizzled = Swizzle::Make(context, pos, cast.argument()->clone(), + std::move(components)); + return (castType.columns() > 1) + ? ConstructorCompoundCast::Make(context, pos, castType, std::move(swizzled)) + : ConstructorScalarCast::Make(context, pos, castType, std::move(swizzled)); + } + + // Swizzles on compound constructors, like `half4(1, 2, 3, 4).yw`, can become `half2(2, 4)`. + if (value->is<ConstructorCompound>()) { + const ConstructorCompound& ctor = value->as<ConstructorCompound>(); + if (auto replacement = optimize_constructor_swizzle(context, pos, ctor, components)) { + return replacement; + } + } + + // The swizzle could not be simplified, so apply the requested swizzle to the base expression. + return std::make_unique<Swizzle>(context, pos, std::move(expr), components); +} + +std::string Swizzle::description(OperatorPrecedence) const { + std::string result = this->base()->description(OperatorPrecedence::kPostfix) + "."; + for (int x : this->components()) { + result += "xyzw"[x]; + } + return result; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h new file mode 100644 index 0000000000..9911546103 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSwizzle.h @@ -0,0 +1,103 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SWIZZLE +#define SKSL_SWIZZLE + +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <utility> + +namespace SkSL { + +class Context; +enum class OperatorPrecedence : uint8_t; + +/** + * Represents a vector swizzle operation such as 'float3(1, 2, 3).zyx'. + */ +class Swizzle final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kSwizzle; + + Swizzle(const Context& context, Position pos, std::unique_ptr<Expression> base, + const ComponentArray& components) + : INHERITED(pos, kIRNodeKind, + &base->type().componentType().toCompound(context, components.size(), 1)) + , fBase(std::move(base)) + , fComponents(components) { + SkASSERT(this->components().size() >= 1 && this->components().size() <= 4); + } + + // Swizzle::Convert permits component arrays containing ZERO or ONE, does typechecking, reports + // errors via ErrorReporter, and returns an expression that combines constructors and native + // swizzles (comprised solely of X/Y/W/Z). + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + Position maskPos, + std::unique_ptr<Expression> base, + ComponentArray inComponents); + + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + Position maskPos, + std::unique_ptr<Expression> base, + std::string_view maskString); + + // Swizzle::Make does not permit ZERO or ONE in the component array, just X/Y/Z/W; errors are + // reported via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> expr, + ComponentArray inComponents); + + std::unique_ptr<Expression>& base() { + return fBase; + } + + const std::unique_ptr<Expression>& base() const { + return fBase; + } + + const ComponentArray& components() const { + return fComponents; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::unique_ptr<Expression>(new Swizzle(pos, &this->type(), this->base()->clone(), + this->components())); + } + + std::string description(OperatorPrecedence) const override; + +private: + Swizzle(Position pos, const Type* type, std::unique_ptr<Expression> base, + const ComponentArray& components) + : INHERITED(pos, kIRNodeKind, type) + , fBase(std::move(base)) + , fComponents(components) { + SkASSERT(this->components().size() >= 1 && this->components().size() <= 4); + } + + std::unique_ptr<Expression> fBase; + ComponentArray fComponents; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp new file mode 100644 index 0000000000..e8771c9560 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.cpp @@ -0,0 +1,122 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLSymbolTable.h" + +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLType.h" + +namespace SkSL { + +bool SymbolTable::isType(std::string_view name) const { + const Symbol* symbol = this->find(name); + return symbol && symbol->is<Type>(); +} + +bool SymbolTable::isBuiltinType(std::string_view name) const { + if (!this->isBuiltin()) { + return fParent && fParent->isBuiltinType(name); + } + return this->isType(name); +} + +const Symbol* SymbolTable::findBuiltinSymbol(std::string_view name) const { + if (!this->isBuiltin()) { + return fParent ? fParent->findBuiltinSymbol(name) : nullptr; + } + return this->find(name); +} + +Symbol* SymbolTable::lookup(const SymbolKey& key) const { + Symbol** symbolPPtr = fSymbols.find(key); + if (symbolPPtr) { + return *symbolPPtr; + } + + // The symbol wasn't found; recurse into the parent symbol table. + return fParent ? fParent->lookup(key) : nullptr; +} + +void SymbolTable::renameSymbol(Symbol* symbol, std::string_view newName) { + if (symbol->is<FunctionDeclaration>()) { + // This is a function declaration, so we need to rename the entire overload set. + for (FunctionDeclaration* fn = &symbol->as<FunctionDeclaration>(); fn != nullptr; + fn = fn->mutableNextOverload()) { + fn->setName(newName); + } + } else { + // Other types of symbols don't allow multiple symbols with the same name. + symbol->setName(newName); + } + + this->addWithoutOwnership(symbol); +} + +const std::string* SymbolTable::takeOwnershipOfString(std::string str) { + fOwnedStrings.push_front(std::move(str)); + // Because fOwnedStrings is a linked list, pointers to elements are stable. + return &fOwnedStrings.front(); +} + +void SymbolTable::addWithoutOwnership(Symbol* symbol) { + auto key = MakeSymbolKey(symbol->name()); + + // If this is a function declaration, we need to keep the overload chain in sync. + if (symbol->is<FunctionDeclaration>()) { + // If we have a function with the same name... + Symbol* existingSymbol = this->lookup(key); + if (existingSymbol && existingSymbol->is<FunctionDeclaration>()) { + // ... add the existing function as the next overload in the chain. + FunctionDeclaration* existingDecl = &existingSymbol->as<FunctionDeclaration>(); + symbol->as<FunctionDeclaration>().setNextOverload(existingDecl); + fSymbols[key] = symbol; + return; + } + } + + if (fAtModuleBoundary && fParent && fParent->lookup(key)) { + // We are attempting to declare a symbol at global scope that already exists in a parent + // module. This is a duplicate symbol and should be rejected. + } else { + Symbol*& refInSymbolTable = fSymbols[key]; + + if (refInSymbolTable == nullptr) { + refInSymbolTable = symbol; + return; + } + } + + ThreadContext::ReportError("symbol '" + std::string(symbol->name()) + "' was already defined", + symbol->fPosition); +} + +void SymbolTable::injectWithoutOwnership(Symbol* symbol) { + auto key = MakeSymbolKey(symbol->name()); + fSymbols[key] = symbol; +} + +const Type* SymbolTable::addArrayDimension(const Type* type, int arraySize) { + if (arraySize == 0) { + return type; + } + // If this is a builtin type, we add it as high as possible in the symbol table tree (at the + // module boundary), to enable additional reuse of the array-type. + if (type->isInBuiltinTypes() && fParent && !fAtModuleBoundary) { + return fParent->addArrayDimension(type, arraySize); + } + // Reuse an existing array type with this name if one already exists in our symbol table. + std::string arrayName = type->getArrayName(arraySize); + if (const Symbol* existingType = this->find(arrayName)) { + return &existingType->as<Type>(); + } + // Add a new array type to the symbol table. + const std::string* arrayNamePtr = this->takeOwnershipOfString(std::move(arrayName)); + return this->add(Type::MakeArrayType(*arrayNamePtr, *type, arraySize)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h new file mode 100644 index 0000000000..47f619c82a --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLSymbolTable.h @@ -0,0 +1,214 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_SYMBOLTABLE +#define SKSL_SYMBOLTABLE + +#include "include/core/SkTypes.h" +#include "include/private/SkOpts_spi.h" +#include "include/private/SkSLSymbol.h" +#include "src/core/SkTHash.h" + +#include <cstddef> +#include <cstdint> +#include <forward_list> +#include <memory> +#include <string> +#include <string_view> +#include <type_traits> +#include <utility> +#include <vector> + +namespace SkSL { + +class Type; + +/** + * Maps identifiers to symbols. + */ +class SymbolTable { +public: + explicit SymbolTable(bool builtin) + : fBuiltin(builtin) {} + + explicit SymbolTable(std::shared_ptr<SymbolTable> parent, bool builtin) + : fParent(parent) + , fBuiltin(builtin) {} + + /** Replaces the passed-in SymbolTable with a newly-created child symbol table. */ + static void Push(std::shared_ptr<SymbolTable>* table) { + Push(table, (*table)->isBuiltin()); + } + static void Push(std::shared_ptr<SymbolTable>* table, bool isBuiltin) { + *table = std::make_shared<SymbolTable>(*table, isBuiltin); + } + + /** + * Replaces the passed-in SymbolTable with its parent. If the child symbol table is otherwise + * unreferenced, it will be deleted. + */ + static void Pop(std::shared_ptr<SymbolTable>* table) { + *table = (*table)->fParent; + } + + /** + * If the input is a built-in symbol table, returns a new empty symbol table as a child of the + * input table. If the input is not a built-in symbol table, returns it as-is. Built-in symbol + * tables must not be mutated after creation, so they must be wrapped if mutation is necessary. + */ + static std::shared_ptr<SymbolTable> WrapIfBuiltin(std::shared_ptr<SymbolTable> symbolTable) { + if (!symbolTable) { + return nullptr; + } + if (!symbolTable->isBuiltin()) { + return symbolTable; + } + return std::make_shared<SymbolTable>(std::move(symbolTable), /*builtin=*/false); + } + + /** + * Looks up the requested symbol and returns a const pointer. + */ + const Symbol* find(std::string_view name) const { + return this->lookup(MakeSymbolKey(name)); + } + + /** + * Looks up the requested symbol, only searching the built-in symbol tables. Always const. + */ + const Symbol* findBuiltinSymbol(std::string_view name) const; + + /** + * Looks up the requested symbol and returns a mutable pointer. Use caution--mutating a symbol + * will have program-wide impact, and built-in symbol tables must never be mutated. + */ + Symbol* findMutable(std::string_view name) const { + return this->lookup(MakeSymbolKey(name)); + } + + /** + * Assigns a new name to the passed-in symbol. The old name will continue to exist in the symbol + * table and point to the symbol. + */ + void renameSymbol(Symbol* symbol, std::string_view newName); + + /** + * Returns true if the name refers to a type (user or built-in) in the current symbol table. + */ + bool isType(std::string_view name) const; + + /** + * Returns true if the name refers to a builtin type. + */ + bool isBuiltinType(std::string_view name) const; + + /** + * Adds a symbol to this symbol table, without conferring ownership. The caller is responsible + * for keeping the Symbol alive throughout the lifetime of the program/module. + */ + void addWithoutOwnership(Symbol* symbol); + + /** + * Adds a symbol to this symbol table, conferring ownership. + */ + template <typename T> + T* add(std::unique_ptr<T> symbol) { + T* ptr = symbol.get(); + this->addWithoutOwnership(this->takeOwnershipOfSymbol(std::move(symbol))); + return ptr; + } + + /** + * Forces a symbol into this symbol table, without conferring ownership. Replaces any existing + * symbol with the same name, if one exists. + */ + void injectWithoutOwnership(Symbol* symbol); + + /** + * Forces a symbol into this symbol table, conferring ownership. Replaces any existing symbol + * with the same name, if one exists. + */ + template <typename T> + T* inject(std::unique_ptr<T> symbol) { + T* ptr = symbol.get(); + this->injectWithoutOwnership(this->takeOwnershipOfSymbol(std::move(symbol))); + return ptr; + } + + /** + * Confers ownership of a symbol without adding its name to the lookup table. + */ + template <typename T> + T* takeOwnershipOfSymbol(std::unique_ptr<T> symbol) { + T* ptr = symbol.get(); + fOwnedSymbols.push_back(std::move(symbol)); + return ptr; + } + + /** + * Given type = `float` and arraySize = 5, creates the array type `float[5]` in the symbol + * table. The created array type is returned. If zero is passed, the base type is returned + * unchanged. + */ + const Type* addArrayDimension(const Type* type, int arraySize); + + // Call fn for every symbol in the table. You may not mutate anything. + template <typename Fn> + void foreach(Fn&& fn) const { + fSymbols.foreach( + [&fn](const SymbolKey& key, const Symbol* symbol) { fn(key.fName, symbol); }); + } + + size_t count() { + return fSymbols.count(); + } + + /** Returns true if this is a built-in SymbolTable. */ + bool isBuiltin() const { + return fBuiltin; + } + + const std::string* takeOwnershipOfString(std::string n); + + /** + * Indicates that this symbol table's parent is in a different module than this one. + */ + void markModuleBoundary() { + fAtModuleBoundary = true; + } + + std::shared_ptr<SymbolTable> fParent; + + std::vector<std::unique_ptr<const Symbol>> fOwnedSymbols; + +private: + struct SymbolKey { + std::string_view fName; + uint32_t fHash; + + bool operator==(const SymbolKey& that) const { return fName == that.fName; } + bool operator!=(const SymbolKey& that) const { return fName != that.fName; } + struct Hash { + uint32_t operator()(const SymbolKey& key) const { return key.fHash; } + }; + }; + + static SymbolKey MakeSymbolKey(std::string_view name) { + return SymbolKey{name, SkOpts::hash_fn(name.data(), name.size(), 0)}; + } + + Symbol* lookup(const SymbolKey& key) const; + + bool fBuiltin = false; + bool fAtModuleBoundary = false; + std::forward_list<std::string> fOwnedStrings; + SkTHashMap<SymbolKey, Symbol*, SymbolKey::Hash> fSymbols; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.cpp b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.cpp new file mode 100644 index 0000000000..2447bc043f --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.cpp @@ -0,0 +1,136 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLTernaryExpression.h" + +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLOperator.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLPrefixExpression.h" + +namespace SkSL { + +std::unique_ptr<Expression> TernaryExpression::Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> ifTrue, + std::unique_ptr<Expression> ifFalse) { + test = context.fTypes.fBool->coerceExpression(std::move(test), context); + if (!test || !ifTrue || !ifFalse) { + return nullptr; + } + if (ifTrue->type().componentType().isOpaque()) { + context.fErrors->error(pos, "ternary expression of opaque type '" + + ifTrue->type().displayName() + "' not allowed"); + return nullptr; + } + const Type* trueType; + const Type* falseType; + const Type* resultType; + Operator equalityOp(Operator::Kind::EQEQ); + if (!equalityOp.determineBinaryType(context, ifTrue->type(), ifFalse->type(), + &trueType, &falseType, &resultType) || + !trueType->matches(*falseType)) { + context.fErrors->error(ifTrue->fPosition.rangeThrough(ifFalse->fPosition), + "ternary operator result mismatch: '" + ifTrue->type().displayName() + "', '" + + ifFalse->type().displayName() + "'"); + return nullptr; + } + if (context.fConfig->strictES2Mode() && trueType->isOrContainsArray()) { + context.fErrors->error(pos, "ternary operator result may not be an array (or struct " + "containing an array)"); + return nullptr; + } + ifTrue = trueType->coerceExpression(std::move(ifTrue), context); + if (!ifTrue) { + return nullptr; + } + ifFalse = falseType->coerceExpression(std::move(ifFalse), context); + if (!ifFalse) { + return nullptr; + } + return TernaryExpression::Make(context, pos, std::move(test), std::move(ifTrue), + std::move(ifFalse)); +} + +std::unique_ptr<Expression> TernaryExpression::Make(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> ifTrue, + std::unique_ptr<Expression> ifFalse) { + SkASSERT(ifTrue->type().matches(ifFalse->type())); + SkASSERT(!ifTrue->type().componentType().isOpaque()); + SkASSERT(!context.fConfig->strictES2Mode() || !ifTrue->type().isOrContainsArray()); + + const Expression* testExpr = ConstantFolder::GetConstantValueForVariable(*test); + if (testExpr->isBoolLiteral()) { + // static boolean test, just return one of the branches + if (testExpr->as<Literal>().boolValue()) { + ifTrue->fPosition = pos; + return ifTrue; + } else { + ifFalse->fPosition = pos; + return ifFalse; + } + } + + if (context.fConfig->fSettings.fOptimize) { + const Expression* ifTrueExpr = ConstantFolder::GetConstantValueForVariable(*ifTrue); + const Expression* ifFalseExpr = ConstantFolder::GetConstantValueForVariable(*ifFalse); + + // A ternary with matching true- and false-cases does not need to branch. + if (Analysis::IsSameExpressionTree(*ifTrueExpr, *ifFalseExpr)) { + // If `test` has no side-effects, we can eliminate it too, and just return `ifTrue`. + if (!Analysis::HasSideEffects(*test)) { + ifTrue->fPosition = pos; + return ifTrue; + } + // Return a comma-expression containing `(test, ifTrue)`. + return BinaryExpression::Make(context, pos, std::move(test), + Operator::Kind::COMMA, std::move(ifTrue)); + } + + // A ternary of the form `test ? expr : false` can be simplified to `test && expr`. + if (ifFalseExpr->isBoolLiteral() && !ifFalseExpr->as<Literal>().boolValue()) { + return BinaryExpression::Make(context, pos, std::move(test), + Operator::Kind::LOGICALAND, std::move(ifTrue)); + } + + // A ternary of the form `test ? true : expr` can be simplified to `test || expr`. + if (ifTrueExpr->isBoolLiteral() && ifTrueExpr->as<Literal>().boolValue()) { + return BinaryExpression::Make(context, pos, std::move(test), + Operator::Kind::LOGICALOR, std::move(ifFalse)); + } + + // A ternary of the form `test ? false : true` can be simplified to `!test`. + if (ifTrueExpr->isBoolLiteral() && !ifTrueExpr->as<Literal>().boolValue() && + ifFalseExpr->isBoolLiteral() && ifFalseExpr->as<Literal>().boolValue()) { + return PrefixExpression::Make(context, pos, Operator::Kind::LOGICALNOT, + std::move(test)); + } + } + + return std::make_unique<TernaryExpression>(pos, std::move(test), std::move(ifTrue), + std::move(ifFalse)); +} + +std::string TernaryExpression::description(OperatorPrecedence parentPrecedence) const { + bool needsParens = (OperatorPrecedence::kTernary >= parentPrecedence); + return std::string(needsParens ? "(" : "") + + this->test()->description(OperatorPrecedence::kTernary) + " ? " + + this->ifTrue()->description(OperatorPrecedence::kTernary) + " : " + + this->ifFalse()->description(OperatorPrecedence::kTernary) + + std::string(needsParens ? ")" : ""); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h new file mode 100644 index 0000000000..41f864e2c7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLTernaryExpression.h @@ -0,0 +1,100 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_TERNARYEXPRESSION +#define SKSL_TERNARYEXPRESSION + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +enum class OperatorPrecedence : uint8_t; + +/** + * A ternary expression (test ? ifTrue : ifFalse). + */ +class TernaryExpression final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kTernary; + + TernaryExpression(Position pos, std::unique_ptr<Expression> test, + std::unique_ptr<Expression> ifTrue, std::unique_ptr<Expression> ifFalse) + : INHERITED(pos, kIRNodeKind, &ifTrue->type()) + , fTest(std::move(test)) + , fIfTrue(std::move(ifTrue)) + , fIfFalse(std::move(ifFalse)) { + SkASSERT(this->ifTrue()->type().matches(this->ifFalse()->type())); + } + + // Creates a potentially-simplified form of the ternary. Typechecks and coerces input + // expressions; reports errors via ErrorReporter. + static std::unique_ptr<Expression> Convert(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> ifTrue, + std::unique_ptr<Expression> ifFalse); + + // Creates a potentially-simplified form of the ternary; reports errors via ASSERT. + static std::unique_ptr<Expression> Make(const Context& context, + Position pos, + std::unique_ptr<Expression> test, + std::unique_ptr<Expression> ifTrue, + std::unique_ptr<Expression> ifFalse); + + std::unique_ptr<Expression>& test() { + return fTest; + } + + const std::unique_ptr<Expression>& test() const { + return fTest; + } + + std::unique_ptr<Expression>& ifTrue() { + return fIfTrue; + } + + const std::unique_ptr<Expression>& ifTrue() const { + return fIfTrue; + } + + std::unique_ptr<Expression>& ifFalse() { + return fIfFalse; + } + + const std::unique_ptr<Expression>& ifFalse() const { + return fIfFalse; + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<TernaryExpression>(pos, this->test()->clone(), + this->ifTrue()->clone(), + this->ifFalse()->clone()); + } + + std::string description(OperatorPrecedence parentPrecedence) const override; + +private: + std::unique_ptr<Expression> fTest; + std::unique_ptr<Expression> fIfTrue; + std::unique_ptr<Expression> fIfFalse; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLType.cpp b/gfx/skia/skia/src/sksl/ir/SkSLType.cpp new file mode 100644 index 0000000000..265d49dcc8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLType.cpp @@ -0,0 +1,1208 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLType.h" + +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLString.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/base/SkMathPriv.h" +#include "src/base/SkSafeMath.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLConstructorArrayCast.h" +#include "src/sksl/ir/SkSLConstructorCompoundCast.h" +#include "src/sksl/ir/SkSLConstructorScalarCast.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLSymbolTable.h" + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <optional> +#include <string_view> +#include <utility> + +namespace SkSL { + +static constexpr int kMaxStructDepth = 8; + +class AliasType final : public Type { +public: + AliasType(std::string_view name, const Type& targetType) + : INHERITED(name, targetType.abbreviatedName(), targetType.typeKind()) + , fTargetType(targetType) {} + + const Type& resolve() const override { + return fTargetType; + } + + const Type& componentType() const override { + return fTargetType.componentType(); + } + + NumberKind numberKind() const override { + return fTargetType.numberKind(); + } + + int priority() const override { + return fTargetType.priority(); + } + + int columns() const override { + return fTargetType.columns(); + } + + int rows() const override { + return fTargetType.rows(); + } + + int bitWidth() const override { + return fTargetType.bitWidth(); + } + + bool isAllowedInES2() const override { + return fTargetType.isAllowedInES2(); + } + + size_t slotCount() const override { + return fTargetType.slotCount(); + } + + SpvDim_ dimensions() const override { + return fTargetType.dimensions(); + } + + bool isDepth() const override { + return fTargetType.isDepth(); + } + + bool isArrayedTexture() const override { + return fTargetType.isArrayedTexture(); + } + + bool isScalar() const override { + return fTargetType.isScalar(); + } + + bool isLiteral() const override { + return fTargetType.isLiteral(); + } + + bool isVector() const override { + return fTargetType.isVector(); + } + + bool isMatrix() const override { + return fTargetType.isMatrix(); + } + + bool isArray() const override { + return fTargetType.isArray(); + } + + bool isUnsizedArray() const override { + return fTargetType.isUnsizedArray(); + } + + bool isStruct() const override { + return fTargetType.isStruct(); + } + + bool isInterfaceBlock() const override { + return fTargetType.isInterfaceBlock(); + } + + bool isMultisampled() const override { + return fTargetType.isMultisampled(); + } + + TextureAccess textureAccess() const override { + return fTargetType.textureAccess(); + } + + SkSpan<const Type* const> coercibleTypes() const override { + return fTargetType.coercibleTypes(); + } + +private: + using INHERITED = Type; + + const Type& fTargetType; +}; + +class ArrayType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kArray; + + ArrayType(std::string_view name, const char* abbrev, const Type& componentType, int count) + : INHERITED(name, abbrev, kTypeKind) + , fComponentType(componentType) + , fCount(count) { + SkASSERT(count > 0 || count == kUnsizedArray); + // Disallow multi-dimensional arrays. + SkASSERT(!componentType.is<ArrayType>()); + } + + bool isArray() const override { + return true; + } + + bool isUnsizedArray() const override { + return fCount == kUnsizedArray; + } + + const Type& componentType() const override { + return fComponentType; + } + + int columns() const override { + return fCount; + } + + int bitWidth() const override { + return this->componentType().bitWidth(); + } + + bool isAllowedInES2() const override { + return fComponentType.isAllowedInES2(); + } + + size_t slotCount() const override { + SkASSERT(fCount != kUnsizedArray); + SkASSERT(fCount > 0); + return fCount * fComponentType.slotCount(); + } + +private: + using INHERITED = Type; + + const Type& fComponentType; + int fCount; +}; + +class GenericType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kGeneric; + + GenericType(const char* name, SkSpan<const Type* const> coercibleTypes) + : INHERITED(name, "G", kTypeKind) { + fNumTypes = coercibleTypes.size(); + SkASSERT(fNumTypes <= std::size(fCoercibleTypes)); + std::copy(coercibleTypes.begin(), coercibleTypes.end(), fCoercibleTypes); + } + + SkSpan<const Type* const> coercibleTypes() const override { + return SkSpan(fCoercibleTypes, fNumTypes); + } + +private: + using INHERITED = Type; + + const Type* fCoercibleTypes[9]; + size_t fNumTypes; +}; + +class LiteralType : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kLiteral; + + LiteralType(const char* name, const Type& scalarType, int8_t priority) + : INHERITED(name, "L", kTypeKind) + , fScalarType(scalarType) + , fPriority(priority) {} + + const Type& scalarTypeForLiteral() const override { + return fScalarType; + } + + int priority() const override { + return fPriority; + } + + int columns() const override { + return 1; + } + + int rows() const override { + return 1; + } + + NumberKind numberKind() const override { + return fScalarType.numberKind(); + } + + int bitWidth() const override { + return fScalarType.bitWidth(); + } + + double minimumValue() const override { + return fScalarType.minimumValue(); + } + + double maximumValue() const override { + return fScalarType.maximumValue(); + } + + bool isScalar() const override { + return true; + } + + bool isLiteral() const override { + return true; + } + + size_t slotCount() const override { + return 1; + } + +private: + using INHERITED = Type; + + const Type& fScalarType; + int8_t fPriority; +}; + + +class ScalarType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kScalar; + + ScalarType(std::string_view name, const char* abbrev, NumberKind numberKind, int8_t priority, + int8_t bitWidth) + : INHERITED(name, abbrev, kTypeKind) + , fNumberKind(numberKind) + , fPriority(priority) + , fBitWidth(bitWidth) {} + + NumberKind numberKind() const override { + return fNumberKind; + } + + int priority() const override { + return fPriority; + } + + int bitWidth() const override { + return fBitWidth; + } + + int columns() const override { + return 1; + } + + int rows() const override { + return 1; + } + + bool isScalar() const override { + return true; + } + + bool isAllowedInES2() const override { + return fNumberKind != NumberKind::kUnsigned; + } + + size_t slotCount() const override { + return 1; + } + + using int_limits = std::numeric_limits<int32_t>; + using short_limits = std::numeric_limits<int16_t>; + using uint_limits = std::numeric_limits<uint32_t>; + using ushort_limits = std::numeric_limits<uint16_t>; + using float_limits = std::numeric_limits<float>; + + /** Returns the maximum value that can fit in the type. */ + double minimumValue() const override { + switch (this->numberKind()) { + case NumberKind::kSigned: + return this->highPrecision() ? int_limits::lowest() + : short_limits::lowest(); + + case NumberKind::kUnsigned: + return 0; + + case NumberKind::kFloat: + default: + return float_limits::lowest(); + } + } + + /** Returns the maximum value that can fit in the type. */ + double maximumValue() const override { + switch (this->numberKind()) { + case NumberKind::kSigned: + return this->highPrecision() ? int_limits::max() + : short_limits::max(); + + case NumberKind::kUnsigned: + return this->highPrecision() ? uint_limits::max() + : ushort_limits::max(); + + case NumberKind::kFloat: + default: + return float_limits::max(); + } + } + +private: + using INHERITED = Type; + + NumberKind fNumberKind; + int8_t fPriority; + int8_t fBitWidth; +}; + +class AtomicType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kAtomic; + + AtomicType(std::string_view name, const char* abbrev) : INHERITED(name, abbrev, kTypeKind) {} + + bool isAllowedInES2() const override { return false; } + +private: + using INHERITED = Type; +}; + +class MatrixType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kMatrix; + + MatrixType(std::string_view name, const char* abbrev, const Type& componentType, + int8_t columns, int8_t rows) + : INHERITED(name, abbrev, kTypeKind) + , fComponentType(componentType.as<ScalarType>()) + , fColumns(columns) + , fRows(rows) { + SkASSERT(columns >= 2 && columns <= 4); + SkASSERT(rows >= 2 && rows <= 4); + } + + const ScalarType& componentType() const override { + return fComponentType; + } + + int columns() const override { + return fColumns; + } + + int rows() const override { + return fRows; + } + + int bitWidth() const override { + return this->componentType().bitWidth(); + } + + bool isMatrix() const override { + return true; + } + + bool isAllowedInES2() const override { + return fColumns == fRows; + } + + size_t slotCount() const override { + return fColumns * fRows; + } + +private: + using INHERITED = Type; + + const ScalarType& fComponentType; + int8_t fColumns; + int8_t fRows; +}; + +class TextureType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kTexture; + + TextureType(const char* name, SpvDim_ dimensions, bool isDepth, bool isArrayed, + bool isMultisampled, TextureAccess textureAccess) + : INHERITED(name, "T", kTypeKind) + , fDimensions(dimensions) + , fIsDepth(isDepth) + , fIsArrayed(isArrayed) + , fIsMultisampled(isMultisampled) + , fTextureAccess(textureAccess) {} + + SpvDim_ dimensions() const override { + return fDimensions; + } + + bool isDepth() const override { + return fIsDepth; + } + + bool isArrayedTexture() const override { + return fIsArrayed; + } + + bool isMultisampled() const override { + return fIsMultisampled; + } + + TextureAccess textureAccess() const override { + return fTextureAccess; + } + +private: + using INHERITED = Type; + + SpvDim_ fDimensions; + bool fIsDepth; + bool fIsArrayed; + bool fIsMultisampled; + TextureAccess fTextureAccess; +}; + +class SamplerType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kSampler; + + SamplerType(const char* name, const Type& textureType) + : INHERITED(name, "Z", kTypeKind) + , fTextureType(textureType.as<TextureType>()) {} + + const TextureType& textureType() const override { + return fTextureType; + } + + SpvDim_ dimensions() const override { + return fTextureType.dimensions(); + } + + bool isDepth() const override { + return fTextureType.isDepth(); + } + + bool isArrayedTexture() const override { + return fTextureType.isArrayedTexture(); + } + + bool isMultisampled() const override { + return fTextureType.isMultisampled(); + } + + TextureAccess textureAccess() const override { + return fTextureType.textureAccess(); + } + +private: + using INHERITED = Type; + + const TextureType& fTextureType; +}; + +class StructType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kStruct; + + StructType(Position pos, std::string_view name, std::vector<Field> fields, bool interfaceBlock) + : INHERITED(std::move(name), "S", kTypeKind, pos) + , fFields(std::move(fields)) + , fInterfaceBlock(interfaceBlock) {} + + const std::vector<Field>& fields() const override { + return fFields; + } + + bool isStruct() const override { + return true; + } + + bool isInterfaceBlock() const override { + return fInterfaceBlock; + } + + bool isAllowedInES2() const override { + return std::all_of(fFields.begin(), fFields.end(), [](const Field& f) { + return f.fType->isAllowedInES2(); + }); + } + + size_t slotCount() const override { + size_t slots = 0; + for (const Field& field : fFields) { + slots += field.fType->slotCount(); + } + return slots; + } + +private: + using INHERITED = Type; + + std::vector<Field> fFields; + bool fInterfaceBlock; +}; + +class VectorType final : public Type { +public: + inline static constexpr TypeKind kTypeKind = TypeKind::kVector; + + VectorType(std::string_view name, const char* abbrev, const Type& componentType, + int8_t columns) + : INHERITED(name, abbrev, kTypeKind) + , fComponentType(componentType.as<ScalarType>()) + , fColumns(columns) { + SkASSERT(columns >= 2 && columns <= 4); + } + + const ScalarType& componentType() const override { + return fComponentType; + } + + int columns() const override { + return fColumns; + } + + int rows() const override { + return 1; + } + + int bitWidth() const override { + return this->componentType().bitWidth(); + } + + bool isVector() const override { + return true; + } + + bool isAllowedInES2() const override { + return fComponentType.isAllowedInES2(); + } + + size_t slotCount() const override { + return fColumns; + } + +private: + using INHERITED = Type; + + const ScalarType& fComponentType; + int8_t fColumns; +}; + +std::string Type::getArrayName(int arraySize) const { + std::string_view name = this->name(); + if (arraySize == kUnsizedArray) { + return String::printf("%.*s[]", (int)name.size(), name.data()); + } + return String::printf("%.*s[%d]", (int)name.size(), name.data(), arraySize); +} + +std::unique_ptr<Type> Type::MakeAliasType(std::string_view name, const Type& targetType) { + return std::make_unique<AliasType>(std::move(name), targetType); +} + +std::unique_ptr<Type> Type::MakeArrayType(std::string_view name, const Type& componentType, + int columns) { + return std::make_unique<ArrayType>(std::move(name), componentType.abbreviatedName(), + componentType, columns); +} + +std::unique_ptr<Type> Type::MakeGenericType(const char* name, SkSpan<const Type* const> types) { + return std::make_unique<GenericType>(name, types); +} + +std::unique_ptr<Type> Type::MakeLiteralType(const char* name, const Type& scalarType, + int8_t priority) { + return std::make_unique<LiteralType>(name, scalarType, priority); +} + +std::unique_ptr<Type> Type::MakeMatrixType(std::string_view name, const char* abbrev, + const Type& componentType, int columns, int8_t rows) { + return std::make_unique<MatrixType>(name, abbrev, componentType, columns, rows); +} + +std::unique_ptr<Type> Type::MakeSamplerType(const char* name, const Type& textureType) { + return std::make_unique<SamplerType>(name, textureType); +} + +std::unique_ptr<Type> Type::MakeSpecialType(const char* name, const char* abbrev, + Type::TypeKind typeKind) { + return std::unique_ptr<Type>(new Type(name, abbrev, typeKind)); +} + +std::unique_ptr<Type> Type::MakeScalarType(std::string_view name, const char* abbrev, + Type::NumberKind numberKind, int8_t priority, + int8_t bitWidth) { + return std::make_unique<ScalarType>(name, abbrev, numberKind, priority, bitWidth); +} + +std::unique_ptr<Type> Type::MakeAtomicType(std::string_view name, const char* abbrev) { + return std::make_unique<AtomicType>(name, abbrev); +} + +static bool is_too_deeply_nested(const Type* t, int limit) { + if (limit <= 0) { + return true; + } + + if (t->isStruct()) { + for (const Type::Field& f : t->fields()) { + if (is_too_deeply_nested(f.fType, limit - 1)) { + return true; + } + } + } + + return false; +} + +std::unique_ptr<Type> Type::MakeStructType(const Context& context, + Position pos, + std::string_view name, + std::vector<Field> fields, + bool interfaceBlock) { + for (const Field& field : fields) { + if (field.fModifiers.fFlags != Modifiers::kNo_Flag) { + std::string desc = field.fModifiers.description(); + desc.pop_back(); // remove trailing space + context.fErrors->error(field.fPosition, + "modifier '" + desc + "' is not permitted on a struct field"); + } + if (field.fModifiers.fLayout.fFlags & Layout::kBinding_Flag) { + context.fErrors->error(field.fPosition, + "layout qualifier 'binding' is not permitted on a struct field"); + } + if (field.fModifiers.fLayout.fFlags & Layout::kSet_Flag) { + context.fErrors->error(field.fPosition, + "layout qualifier 'set' is not permitted on a struct field"); + } + + if (field.fType->isVoid()) { + context.fErrors->error(field.fPosition, "type 'void' is not permitted in a struct"); + } + if (field.fType->isOpaque() && !field.fType->isAtomic()) { + context.fErrors->error(field.fPosition, "opaque type '" + field.fType->displayName() + + "' is not permitted in a struct"); + } + } + for (const Field& field : fields) { + if (is_too_deeply_nested(field.fType, kMaxStructDepth)) { + context.fErrors->error(pos, "struct '" + std::string(name) + "' is too deeply nested"); + break; + } + } + size_t slots = 0; + for (const Field& field : fields) { + if (field.fType->isUnsizedArray()) { + continue; + } + slots = SkSafeMath::Add(slots, field.fType->slotCount()); + if (slots >= kVariableSlotLimit) { + context.fErrors->error(pos, "struct is too large"); + break; + } + } + return std::make_unique<StructType>(pos, name, std::move(fields), interfaceBlock); +} + +std::unique_ptr<Type> Type::MakeTextureType(const char* name, SpvDim_ dimensions, bool isDepth, + bool isArrayedTexture, bool isMultisampled, + TextureAccess textureAccess) { + return std::make_unique<TextureType>(name, dimensions, isDepth, isArrayedTexture, + isMultisampled, textureAccess); +} + +std::unique_ptr<Type> Type::MakeVectorType(std::string_view name, const char* abbrev, + const Type& componentType, int columns) { + return std::make_unique<VectorType>(name, abbrev, componentType, columns); +} + +CoercionCost Type::coercionCost(const Type& other) const { + if (this->matches(other)) { + return CoercionCost::Free(); + } + if (this->typeKind() == other.typeKind() && + (this->isVector() || this->isMatrix() || this->isArray())) { + // Vectors/matrices/arrays of the same size can be coerced if their component type can be. + if (this->isMatrix() && (this->rows() != other.rows())) { + return CoercionCost::Impossible(); + } + if (this->columns() != other.columns()) { + return CoercionCost::Impossible(); + } + return this->componentType().coercionCost(other.componentType()); + } + if (this->isNumber() && other.isNumber()) { + if (this->isLiteral() && this->isInteger()) { + return CoercionCost::Free(); + } else if (this->numberKind() != other.numberKind()) { + return CoercionCost::Impossible(); + } else if (other.priority() >= this->priority()) { + return CoercionCost::Normal(other.priority() - this->priority()); + } else { + return CoercionCost::Narrowing(this->priority() - other.priority()); + } + } + if (fTypeKind == TypeKind::kGeneric) { + SkSpan<const Type* const> types = this->coercibleTypes(); + for (size_t i = 0; i < types.size(); i++) { + if (types[i]->matches(other)) { + return CoercionCost::Normal((int) i + 1); + } + } + } + return CoercionCost::Impossible(); +} + +const Type* Type::applyQualifiers(const Context& context, + Modifiers* modifiers, + SymbolTable* symbols, + Position pos) const { + const Type* type; + type = this->applyPrecisionQualifiers(context, modifiers, symbols, pos); + type = type->applyAccessQualifiers(context, modifiers, symbols, pos); + return type; +} + +const Type* Type::applyPrecisionQualifiers(const Context& context, + Modifiers* modifiers, + SymbolTable* symbols, + Position pos) const { + int precisionQualifiers = modifiers->fFlags & (Modifiers::kHighp_Flag | + Modifiers::kMediump_Flag | + Modifiers::kLowp_Flag); + if (!precisionQualifiers) { + // No precision qualifiers here. Return the type as-is. + return this; + } + + if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { + // We want to discourage precision modifiers internally. Instead, use the type that + // corresponds to the precision you need. (e.g. half vs float, short vs int) + context.fErrors->error(pos, "precision qualifiers are not allowed"); + return context.fTypes.fPoison.get(); + } + + if (SkPopCount(precisionQualifiers) > 1) { + context.fErrors->error(pos, "only one precision qualifier can be used"); + return context.fTypes.fPoison.get(); + } + + // We're going to return a whole new type, so the modifier bits can be cleared out. + modifiers->fFlags &= ~(Modifiers::kHighp_Flag | + Modifiers::kMediump_Flag | + Modifiers::kLowp_Flag); + + const Type& component = this->componentType(); + if (component.highPrecision()) { + if (precisionQualifiers & Modifiers::kHighp_Flag) { + // Type is already high precision, and we are requesting high precision. Return as-is. + return this; + } + + // SkSL doesn't support low precision, so `lowp` is interpreted as medium precision. + // Ascertain the mediump equivalent type for this type, if any. + const Type* mediumpType; + switch (component.numberKind()) { + case Type::NumberKind::kFloat: + mediumpType = context.fTypes.fHalf.get(); + break; + + case Type::NumberKind::kSigned: + mediumpType = context.fTypes.fShort.get(); + break; + + case Type::NumberKind::kUnsigned: + mediumpType = context.fTypes.fUShort.get(); + break; + + default: + mediumpType = context.fTypes.fPoison.get(); + break; + } + + if (mediumpType) { + // Convert the mediump component type into the final vector/matrix/array type as needed. + return this->isArray() + ? symbols->addArrayDimension(mediumpType, this->columns()) + : &mediumpType->toCompound(context, this->columns(), this->rows()); + } + } + + context.fErrors->error(pos, "type '" + this->displayName() + + "' does not support precision qualifiers"); + return context.fTypes.fPoison.get(); +} + +const Type* Type::applyAccessQualifiers(const Context& context, + Modifiers* modifiers, + SymbolTable* symbols, + Position pos) const { + int accessQualifiers = modifiers->fFlags & (Modifiers::kReadOnly_Flag | + Modifiers::kWriteOnly_Flag); + if (!accessQualifiers) { + // No access qualifiers here. Return the type as-is. + return this; + } + + // We're going to return a whole new type, so the modifier bits can be cleared out. + modifiers->fFlags &= ~(Modifiers::kReadOnly_Flag | + Modifiers::kWriteOnly_Flag); + + if (this->matches(*context.fTypes.fReadWriteTexture2D)) { + switch (accessQualifiers) { + case Modifiers::kReadOnly_Flag: + return context.fTypes.fReadOnlyTexture2D.get(); + + case Modifiers::kWriteOnly_Flag: + return context.fTypes.fWriteOnlyTexture2D.get(); + + default: + context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers " + "cannot be combined"); + return this; + } + } + + context.fErrors->error(pos, "type '" + this->displayName() + "' does not support qualifier '" + + Modifiers::DescribeFlags(accessQualifiers) + "'"); + return this; +} + +const Type& Type::toCompound(const Context& context, int columns, int rows) const { + SkASSERT(this->isScalar()); + if (columns == 1 && rows == 1) { + return *this; + } + if (this->matches(*context.fTypes.fFloat) || this->matches(*context.fTypes.fFloatLiteral)) { + switch (rows) { + case 1: + switch (columns) { + case 1: return *context.fTypes.fFloat; + case 2: return *context.fTypes.fFloat2; + case 3: return *context.fTypes.fFloat3; + case 4: return *context.fTypes.fFloat4; + default: SK_ABORT("unsupported vector column count (%d)", columns); + } + case 2: + switch (columns) { + case 2: return *context.fTypes.fFloat2x2; + case 3: return *context.fTypes.fFloat3x2; + case 4: return *context.fTypes.fFloat4x2; + default: SK_ABORT("unsupported matrix column count (%d)", columns); + } + case 3: + switch (columns) { + case 2: return *context.fTypes.fFloat2x3; + case 3: return *context.fTypes.fFloat3x3; + case 4: return *context.fTypes.fFloat4x3; + default: SK_ABORT("unsupported matrix column count (%d)", columns); + } + case 4: + switch (columns) { + case 2: return *context.fTypes.fFloat2x4; + case 3: return *context.fTypes.fFloat3x4; + case 4: return *context.fTypes.fFloat4x4; + default: SK_ABORT("unsupported matrix column count (%d)", columns); + } + default: SK_ABORT("unsupported row count (%d)", rows); + } + } else if (this->matches(*context.fTypes.fHalf)) { + switch (rows) { + case 1: + switch (columns) { + case 1: return *context.fTypes.fHalf; + case 2: return *context.fTypes.fHalf2; + case 3: return *context.fTypes.fHalf3; + case 4: return *context.fTypes.fHalf4; + default: SK_ABORT("unsupported vector column count (%d)", columns); + } + case 2: + switch (columns) { + case 2: return *context.fTypes.fHalf2x2; + case 3: return *context.fTypes.fHalf3x2; + case 4: return *context.fTypes.fHalf4x2; + default: SK_ABORT("unsupported matrix column count (%d)", columns); + } + case 3: + switch (columns) { + case 2: return *context.fTypes.fHalf2x3; + case 3: return *context.fTypes.fHalf3x3; + case 4: return *context.fTypes.fHalf4x3; + default: SK_ABORT("unsupported matrix column count (%d)", columns); + } + case 4: + switch (columns) { + case 2: return *context.fTypes.fHalf2x4; + case 3: return *context.fTypes.fHalf3x4; + case 4: return *context.fTypes.fHalf4x4; + default: SK_ABORT("unsupported matrix column count (%d)", columns); + } + default: SK_ABORT("unsupported row count (%d)", rows); + } + } else if (this->matches(*context.fTypes.fInt) || this->matches(*context.fTypes.fIntLiteral)) { + switch (rows) { + case 1: + switch (columns) { + case 1: return *context.fTypes.fInt; + case 2: return *context.fTypes.fInt2; + case 3: return *context.fTypes.fInt3; + case 4: return *context.fTypes.fInt4; + default: SK_ABORT("unsupported vector column count (%d)", columns); + } + default: SK_ABORT("unsupported row count (%d)", rows); + } + } else if (this->matches(*context.fTypes.fShort)) { + switch (rows) { + case 1: + switch (columns) { + case 1: return *context.fTypes.fShort; + case 2: return *context.fTypes.fShort2; + case 3: return *context.fTypes.fShort3; + case 4: return *context.fTypes.fShort4; + default: SK_ABORT("unsupported vector column count (%d)", columns); + } + default: SK_ABORT("unsupported row count (%d)", rows); + } + } else if (this->matches(*context.fTypes.fUInt)) { + switch (rows) { + case 1: + switch (columns) { + case 1: return *context.fTypes.fUInt; + case 2: return *context.fTypes.fUInt2; + case 3: return *context.fTypes.fUInt3; + case 4: return *context.fTypes.fUInt4; + default: SK_ABORT("unsupported vector column count (%d)", columns); + } + default: SK_ABORT("unsupported row count (%d)", rows); + } + } else if (this->matches(*context.fTypes.fUShort)) { + switch (rows) { + case 1: + switch (columns) { + case 1: return *context.fTypes.fUShort; + case 2: return *context.fTypes.fUShort2; + case 3: return *context.fTypes.fUShort3; + case 4: return *context.fTypes.fUShort4; + default: SK_ABORT("unsupported vector column count (%d)", columns); + } + default: SK_ABORT("unsupported row count (%d)", rows); + } + } else if (this->matches(*context.fTypes.fBool)) { + switch (rows) { + case 1: + switch (columns) { + case 1: return *context.fTypes.fBool; + case 2: return *context.fTypes.fBool2; + case 3: return *context.fTypes.fBool3; + case 4: return *context.fTypes.fBool4; + default: SK_ABORT("unsupported vector column count (%d)", columns); + } + default: SK_ABORT("unsupported row count (%d)", rows); + } + } + SkDEBUGFAILF("unsupported toCompound type %s", this->description().c_str()); + return *context.fTypes.fVoid; +} + +const Type* Type::clone(SymbolTable* symbolTable) const { + // Many types are built-ins, and exist in every SymbolTable by default. + if (this->isInBuiltinTypes()) { + return this; + } + // Even if the type isn't a built-in, it might already exist in the SymbolTable. + const Symbol* clonedSymbol = symbolTable->find(this->name()); + if (clonedSymbol != nullptr) { + const Type& clonedType = clonedSymbol->as<Type>(); + SkASSERT(clonedType.typeKind() == this->typeKind()); + return &clonedType; + } + // This type actually needs to be cloned into the destination SymbolTable. + switch (this->typeKind()) { + case TypeKind::kArray: { + return symbolTable->addArrayDimension(&this->componentType(), this->columns()); + } + case TypeKind::kStruct: { + // We are cloning an existing struct, so there's no need to call MakeStructType and + // fully error-check it again. + const std::string* name = symbolTable->takeOwnershipOfString(std::string(this->name())); + return symbolTable->add(std::make_unique<StructType>( + this->fPosition, *name, this->fields(), this->isInterfaceBlock())); + } + default: + SkDEBUGFAILF("don't know how to clone type '%s'", this->description().c_str()); + return nullptr; + } +} + +std::unique_ptr<Expression> Type::coerceExpression(std::unique_ptr<Expression> expr, + const Context& context) const { + if (!expr || expr->isIncomplete(context)) { + return nullptr; + } + if (expr->type().matches(*this)) { + return expr; + } + + const Position pos = expr->fPosition; + const ProgramSettings& settings = context.fConfig->fSettings; + if (!expr->coercionCost(*this).isPossible(settings.fAllowNarrowingConversions)) { + context.fErrors->error(pos, "expected '" + this->displayName() + "', but found '" + + expr->type().displayName() + "'"); + return nullptr; + } + + if (this->isScalar()) { + return ConstructorScalarCast::Make(context, pos, *this, std::move(expr)); + } + if (this->isVector() || this->isMatrix()) { + return ConstructorCompoundCast::Make(context, pos, *this, std::move(expr)); + } + if (this->isArray()) { + return ConstructorArrayCast::Make(context, pos, *this, std::move(expr)); + } + context.fErrors->error(pos, "cannot construct '" + this->displayName() + "'"); + return nullptr; +} + +static bool is_or_contains_array(const Type* type, bool onlyMatchUnsizedArrays) { + if (type->isStruct()) { + for (const Type::Field& f : type->fields()) { + if (is_or_contains_array(f.fType, onlyMatchUnsizedArrays)) { + return true; + } + } + return false; + } + + if (type->isArray()) { + return onlyMatchUnsizedArrays + ? (type->isUnsizedArray() || is_or_contains_array(&type->componentType(), true)) + : true; + } + + return false; +} + +bool Type::isOrContainsArray() const { + return is_or_contains_array(this, /*onlyMatchUnsizedArrays=*/false); +} + +bool Type::isOrContainsUnsizedArray() const { + return is_or_contains_array(this, /*onlyMatchUnsizedArrays=*/true); +} + +bool Type::isOrContainsAtomic() const { + if (this->isAtomic()) { + return true; + } + + if (this->isArray() && this->componentType().isOrContainsAtomic()) { + return true; + } + + if (this->isStruct()) { + for (const Field& f : this->fields()) { + if (f.fType->isOrContainsAtomic()) { + return true; + } + } + } + + return false; +} + +bool Type::isAllowedInES2(const Context& context) const { + return !context.fConfig->strictES2Mode() || this->isAllowedInES2(); +} + +bool Type::checkForOutOfRangeLiteral(const Context& context, const Expression& expr) const { + bool foundError = false; + const Type& baseType = this->componentType(); + if (baseType.isNumber()) { + // Replace constant expressions with their corresponding values. + const Expression* valueExpr = ConstantFolder::GetConstantValueForVariable(expr); + if (valueExpr->supportsConstantValues()) { + // Iterate over every constant subexpression in the value. + int numSlots = valueExpr->type().slotCount(); + for (int slot = 0; slot < numSlots; ++slot) { + std::optional<double> slotVal = valueExpr->getConstantValue(slot); + // Check for Literal values that are out of range for the base type. + if (slotVal.has_value() && + baseType.checkForOutOfRangeLiteral(context, *slotVal, valueExpr->fPosition)) { + foundError = true; + } + } + } + } + + // We don't need range checks for floats or booleans; any matched-type value is acceptable. + return foundError; +} + +bool Type::checkForOutOfRangeLiteral(const Context& context, double value, Position pos) const { + SkASSERT(this->isScalar()); + if (!this->isNumber()) { + return false; + } + if (value >= this->minimumValue() && value <= this->maximumValue()) { + return false; + } + // We found a value that can't fit in our type. Flag it as an error. + context.fErrors->error(pos, SkSL::String::printf("value is out of range for type '%s': %.0f", + this->displayName().c_str(), + value)); + return true; +} + +bool Type::checkIfUsableInArray(const Context& context, Position arrayPos) const { + if (this->isArray()) { + context.fErrors->error(arrayPos, "multi-dimensional arrays are not supported"); + return false; + } + if (this->isVoid()) { + context.fErrors->error(arrayPos, "type 'void' may not be used in an array"); + return false; + } + if (this->isOpaque() && !this->isAtomic()) { + context.fErrors->error(arrayPos, "opaque type '" + std::string(this->name()) + + "' may not be used in an array"); + return false; + } + return true; +} + +SKSL_INT Type::convertArraySize(const Context& context, + Position arrayPos, + std::unique_ptr<Expression> size) const { + size = context.fTypes.fInt->coerceExpression(std::move(size), context); + if (!size) { + return 0; + } + if (!this->checkIfUsableInArray(context, arrayPos)) { + return 0; + } + SKSL_INT count; + if (!ConstantFolder::GetConstantInt(*size, &count)) { + context.fErrors->error(size->fPosition, "array size must be an integer"); + return 0; + } + if (count <= 0) { + context.fErrors->error(size->fPosition, "array size must be positive"); + return 0; + } + if (SkSafeMath::Mul(this->slotCount(), count) > kVariableSlotLimit) { + context.fErrors->error(size->fPosition, "array size is too large"); + return 0; + } + return static_cast<int>(count); +} + +std::string Type::Field::description() const { + return fModifiers.description() + fType->displayName() + " " + std::string(fName) + ";"; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLType.h b/gfx/skia/skia/src/sksl/ir/SkSLType.h new file mode 100644 index 0000000000..955381f8c9 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLType.h @@ -0,0 +1,600 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_TYPE +#define SKSL_TYPE + +#include "include/core/SkSpan.h" +#include "include/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLSymbol.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/spirv.h" + +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <tuple> +#include <vector> + +namespace SkSL { + +class Context; +class Expression; +class SymbolTable; + +struct CoercionCost { + static CoercionCost Free() { return { 0, 0, false }; } + static CoercionCost Normal(int cost) { return { cost, 0, false }; } + static CoercionCost Narrowing(int cost) { return { 0, cost, false }; } + static CoercionCost Impossible() { return { 0, 0, true }; } + + bool isPossible(bool allowNarrowing) const { + return !fImpossible && (fNarrowingCost == 0 || allowNarrowing); + } + + // Addition of two costs. Saturates at Impossible(). + CoercionCost operator+(CoercionCost rhs) const { + if (fImpossible || rhs.fImpossible) { + return Impossible(); + } + return { fNormalCost + rhs.fNormalCost, fNarrowingCost + rhs.fNarrowingCost, false }; + } + + bool operator<(CoercionCost rhs) const { + return std::tie( fImpossible, fNarrowingCost, fNormalCost) < + std::tie(rhs.fImpossible, rhs.fNarrowingCost, rhs.fNormalCost); + } + + bool operator<=(CoercionCost rhs) const { + return std::tie( fImpossible, fNarrowingCost, fNormalCost) <= + std::tie(rhs.fImpossible, rhs.fNarrowingCost, rhs.fNormalCost); + } + + int fNormalCost; + int fNarrowingCost; + bool fImpossible; +}; + +/** + * Represents a type, such as int or float4. + */ +class Type : public Symbol { +public: + inline static constexpr Kind kIRNodeKind = Kind::kType; + inline static constexpr int kMaxAbbrevLength = 3; + // Represents unspecified array dimensions, as in `int[]`. + inline static constexpr int kUnsizedArray = -1; + struct Field { + Field(Position pos, Modifiers modifiers, std::string_view name, const Type* type) + : fPosition(pos) + , fModifiers(modifiers) + , fName(name) + , fType(type) {} + + std::string description() const; + + Position fPosition; + Modifiers fModifiers; + std::string_view fName; + const Type* fType; + }; + + enum class TypeKind : int8_t { + kArray, + kAtomic, + kGeneric, + kLiteral, + kMatrix, + kOther, + kSampler, + kSeparateSampler, + kScalar, + kStruct, + kTexture, + kVector, + kVoid, + + // Types that represent stages in the Skia pipeline + kColorFilter, + kShader, + kBlender, + }; + + enum class NumberKind : int8_t { + kFloat, + kSigned, + kUnsigned, + kBoolean, + kNonnumeric + }; + + enum class TextureAccess : int8_t { + kSample, // `kSample` access level allows both sampling and reading + kRead, + kWrite, + kReadWrite, + }; + + Type(const Type& other) = delete; + + /** Creates an array type. `columns` may be kUnsizedArray. */ + static std::unique_ptr<Type> MakeArrayType(std::string_view name, const Type& componentType, + int columns); + + /** Converts a component type and a size (float, 10) into an array name ("float[10]"). */ + std::string getArrayName(int arraySize) const; + + /** + * Creates an alias which maps to another type. + */ + static std::unique_ptr<Type> MakeAliasType(std::string_view name, const Type& targetType); + + /** + * Create a generic type which maps to the listed types--e.g. $genType is a generic type which + * can match float, float2, float3 or float4. + */ + static std::unique_ptr<Type> MakeGenericType(const char* name, SkSpan<const Type* const> types); + + /** Create a type for literal scalars. */ + static std::unique_ptr<Type> MakeLiteralType(const char* name, const Type& scalarType, + int8_t priority); + + /** Create a matrix type. */ + static std::unique_ptr<Type> MakeMatrixType(std::string_view name, const char* abbrev, + const Type& componentType, int columns, + int8_t rows); + + /** Create a sampler type. */ + static std::unique_ptr<Type> MakeSamplerType(const char* name, const Type& textureType); + + /** Create a scalar type. */ + static std::unique_ptr<Type> MakeScalarType(std::string_view name, const char* abbrev, + Type::NumberKind numberKind, int8_t priority, + int8_t bitWidth); + + /** + * Create a "special" type with the given name, abbreviation, and TypeKind. + */ + static std::unique_ptr<Type> MakeSpecialType(const char* name, const char* abbrev, + Type::TypeKind typeKind); + + /** + * Creates a struct type with the given fields. Reports an error if the struct is not + * well-formed. + */ + static std::unique_ptr<Type> MakeStructType(const Context& context, + Position pos, + std::string_view name, + std::vector<Field> fields, + bool interfaceBlock = false); + + /** Create a texture type. */ + static std::unique_ptr<Type> MakeTextureType(const char* name, SpvDim_ dimensions, bool isDepth, + bool isArrayedTexture, bool isMultisampled, + TextureAccess textureAccess); + + /** Create a vector type. */ + static std::unique_ptr<Type> MakeVectorType(std::string_view name, const char* abbrev, + const Type& componentType, int columns); + + /** Create an atomic type. */ + static std::unique_ptr<Type> MakeAtomicType(std::string_view name, const char* abbrev); + + template <typename T> + bool is() const { + return this->typeKind() == T::kTypeKind; + } + + template <typename T> + const T& as() const { + SkASSERT(this->is<T>()); + return static_cast<const T&>(*this); + } + + template <typename T> + T& as() { + SkASSERT(this->is<T>()); + return static_cast<T&>(*this); + } + + /** Creates a clone of this Type, if needed, and inserts it into a different symbol table. */ + const Type* clone(SymbolTable* symbolTable) const; + + /** + * Returns true if this type is known to come from BuiltinTypes. If this returns true, the Type + * will always be available in the root SymbolTable and never needs to be copied to migrate an + * Expression from one location to another. If it returns false, the Type might not exist in a + * separate SymbolTable and you'll need to consider copying it. + */ + bool isInBuiltinTypes() const { + return !(this->isArray() || this->isStruct()); + } + + std::string displayName() const { + return std::string(this->scalarTypeForLiteral().name()); + } + + std::string description() const override { + return this->displayName(); + } + + /** Returns true if the program supports this type. Strict ES2 programs can't use ES3 types. */ + bool isAllowedInES2(const Context& context) const; + + /** Returns true if this type is legal to use in a strict-ES2 program. */ + virtual bool isAllowedInES2() const { + return true; + } + + /** If this is an alias, returns the underlying type, otherwise returns this. */ + virtual const Type& resolve() const { + return *this; + } + + /** Returns true if these types are equal after alias resolution. */ + bool matches(const Type& other) const { + return this->resolve().name() == other.resolve().name(); + } + + /** + * Returns an abbreviated name of the type, meant for name-mangling. (e.g. float4x4 -> f44) + */ + const char* abbreviatedName() const { + return fAbbreviatedName; + } + + /** + * Returns the category (scalar, vector, matrix, etc.) of this type. + */ + TypeKind typeKind() const { + return fTypeKind; + } + + /** + * Returns the NumberKind of this type (always kNonnumeric for non-scalar values). + */ + virtual NumberKind numberKind() const { + return NumberKind::kNonnumeric; + } + + /** + * Returns true if this type is a bool. + */ + bool isBoolean() const { + return this->numberKind() == NumberKind::kBoolean; + } + + /** + * Returns true if this is a numeric scalar type. + */ + bool isNumber() const { + switch (this->numberKind()) { + case NumberKind::kFloat: + case NumberKind::kSigned: + case NumberKind::kUnsigned: + return true; + default: + return false; + } + } + + /** + * Returns true if this is a floating-point scalar type (float or half). + */ + bool isFloat() const { + return this->numberKind() == NumberKind::kFloat; + } + + /** + * Returns true if this is a signed scalar type (int or short). + */ + bool isSigned() const { + return this->numberKind() == NumberKind::kSigned; + } + + /** + * Returns true if this is an unsigned scalar type (uint or ushort). + */ + bool isUnsigned() const { + return this->numberKind() == NumberKind::kUnsigned; + } + + /** + * Returns true if this is a signed or unsigned integer. + */ + bool isInteger() const { + switch (this->numberKind()) { + case NumberKind::kSigned: + case NumberKind::kUnsigned: + return true; + default: + return false; + } + } + + /** + * Returns true if this is an "opaque type" (an external object which the shader references in + * some fashion). https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Opaque_types + */ + bool isOpaque() const { + switch (fTypeKind) { + case TypeKind::kAtomic: + case TypeKind::kBlender: + case TypeKind::kColorFilter: + case TypeKind::kSampler: + case TypeKind::kSeparateSampler: + case TypeKind::kShader: + case TypeKind::kTexture: + return true; + default: + return false; + } + } + + /** + * Returns the "priority" of a number type, in order of float > half > int > short. + * When operating on two number types, the result is the higher-priority type. + */ + virtual int priority() const { + SkDEBUGFAIL("not a number type"); + return -1; + } + + /** + * Returns true if an instance of this type can be freely coerced (implicitly converted) to + * another type. + */ + bool canCoerceTo(const Type& other, bool allowNarrowing) const { + return this->coercionCost(other).isPossible(allowNarrowing); + } + + /** + * Determines the "cost" of coercing (implicitly converting) this type to another type. The cost + * is a number with no particular meaning other than that lower costs are preferable to higher + * costs. Returns INT_MAX if the coercion is not possible. + */ + CoercionCost coercionCost(const Type& other) const; + + /** + * For matrices and vectors, returns the type of individual cells (e.g. mat2 has a component + * type of Float). For arrays, returns the base type. For all other types, returns the type + * itself. + */ + virtual const Type& componentType() const { + return *this; + } + + /** + * For texture samplers, returns the type of texture it samples (e.g., sampler2D has + * a texture type of texture2D). + */ + virtual const Type& textureType() const { + SkDEBUGFAIL("not a sampler type"); + return *this; + } + + /** + * For matrices and vectors, returns the number of columns (e.g. both mat3 and float3 return 3). + * For scalars, returns 1. For arrays, returns either the size of the array (if known) or -1. + * For all other types, causes an assertion failure. + */ + virtual int columns() const { + SkDEBUGFAIL("type does not have columns"); + return -1; + } + + /** + * For matrices, returns the number of rows (e.g. mat2x4 returns 4). For vectors and scalars, + * returns 1. For all other types, causes an assertion failure. + */ + virtual int rows() const { + SkDEBUGFAIL("type does not have rows"); + return -1; + } + + /** Returns the minimum value that can fit in the type. */ + virtual double minimumValue() const { + SkDEBUGFAIL("type does not have a minimum value"); + return -INFINITY; + } + + virtual double maximumValue() const { + SkDEBUGFAIL("type does not have a maximum value"); + return +INFINITY; + } + + /** + * Returns the number of scalars needed to hold this type. + */ + virtual size_t slotCount() const { + return 0; + } + + virtual const std::vector<Field>& fields() const { + SK_ABORT("Internal error: not a struct"); + } + + /** + * For generic types, returns the types that this generic type can substitute for. + */ + virtual SkSpan<const Type* const> coercibleTypes() const { + SkDEBUGFAIL("Internal error: not a generic type"); + return {}; + } + + virtual SpvDim_ dimensions() const { + SkASSERT(false); + return SpvDim1D; + } + + virtual bool isDepth() const { + SkASSERT(false); + return false; + } + + virtual bool isArrayedTexture() const { + SkASSERT(false); + return false; + } + + bool isVoid() const { + return fTypeKind == TypeKind::kVoid; + } + + bool isGeneric() const { + return fTypeKind == TypeKind::kGeneric; + } + + bool isAtomic() const { return this->typeKind() == TypeKind::kAtomic; } + + virtual bool isScalar() const { + return false; + } + + virtual bool isLiteral() const { + return false; + } + + virtual const Type& scalarTypeForLiteral() const { + return *this; + } + + virtual bool isVector() const { + return false; + } + + virtual bool isMatrix() const { + return false; + } + + virtual bool isArray() const { + return false; + } + + virtual bool isUnsizedArray() const { + return false; + } + + virtual bool isStruct() const { + return false; + } + + virtual bool isInterfaceBlock() const { + return false; + } + + // Is this type something that can be bound & sampled from an SkRuntimeEffect? + // Includes types that represent stages of the Skia pipeline (colorFilter, shader, blender). + bool isEffectChild() const { + return fTypeKind == TypeKind::kColorFilter || + fTypeKind == TypeKind::kShader || + fTypeKind == TypeKind::kBlender; + } + + virtual bool isMultisampled() const { + SkDEBUGFAIL("not a texture type"); + return false; + } + + virtual TextureAccess textureAccess() const { + SkDEBUGFAIL("not a texture type"); + return TextureAccess::kSample; + } + + bool hasPrecision() const { + return this->componentType().isNumber() || fTypeKind == TypeKind::kSampler; + } + + bool highPrecision() const { + return this->bitWidth() >= 32; + } + + virtual int bitWidth() const { + return 0; + } + + bool isOrContainsArray() const; + bool isOrContainsUnsizedArray() const; + bool isOrContainsAtomic() const; + + /** + * Returns the corresponding vector or matrix type with the specified number of columns and + * rows. + */ + const Type& toCompound(const Context& context, int columns, int rows) const; + + /** + * Returns a type which honors the precision and access-level qualifiers set in Modifiers. e.g.: + * - Modifier `mediump` + Type `float2`: Type `half2` + * - Modifier `readonly` + Type `texture2D`: Type `readonlyTexture2D` + * Generates an error if the qualifiers don't make sense (`highp bool`, `writeonly MyStruct`) + */ + const Type* applyQualifiers(const Context& context, + Modifiers* modifiers, + SymbolTable* symbols, + Position pos) const; + + /** + * Coerces the passed-in expression to this type. If the types are incompatible, reports an + * error and returns null. + */ + std::unique_ptr<Expression> coerceExpression(std::unique_ptr<Expression> expr, + const Context& context) const; + + /** Detects any IntLiterals in the expression which can't fit in this type. */ + bool checkForOutOfRangeLiteral(const Context& context, const Expression& expr) const; + + /** Checks if `value` can fit in this type. The type must be scalar. */ + bool checkForOutOfRangeLiteral(const Context& context, double value, Position pos) const; + + /** + * Reports errors and returns false if this type cannot be used as the base type for an array. + */ + bool checkIfUsableInArray(const Context& context, Position arrayPos) const; + + /** + * Verifies that the expression is a valid constant array size for this type. Returns the array + * size, or reports errors and returns zero if the expression isn't a valid literal value. + */ + SKSL_INT convertArraySize(const Context& context, Position arrayPos, + std::unique_ptr<Expression> size) const; + +protected: + Type(std::string_view name, const char* abbrev, TypeKind kind, + Position pos = Position()) + : INHERITED(pos, kIRNodeKind, name) + , fTypeKind(kind) { + SkASSERT(strlen(abbrev) <= kMaxAbbrevLength); + strcpy(fAbbreviatedName, abbrev); + } + + const Type* applyPrecisionQualifiers(const Context& context, + Modifiers* modifiers, + SymbolTable* symbols, + Position pos) const; + + const Type* applyAccessQualifiers(const Context& context, + Modifiers* modifiers, + SymbolTable* symbols, + Position pos) const; + +private: + using INHERITED = Symbol; + + char fAbbreviatedName[kMaxAbbrevLength + 1] = {}; + TypeKind fTypeKind; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.cpp b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.cpp new file mode 100644 index 0000000000..c725b38bb6 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLTypeReference.h" + +#include "include/core/SkTypes.h" +#include "include/sksl/SkSLErrorReporter.h" + +namespace SkSL { + +std::unique_ptr<TypeReference> TypeReference::Convert(const Context& context, + Position pos, + const Type* type) { + if (!type->isAllowedInES2(context)) { + context.fErrors->error(pos, "type '" + type->displayName() + "' is not supported"); + return nullptr; + } + return TypeReference::Make(context, pos, type); +} + +std::unique_ptr<TypeReference> TypeReference::Make(const Context& context, + Position pos, + const Type* type) { + SkASSERT(type->isAllowedInES2(context)); + return std::make_unique<TypeReference>(context, pos, type); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h new file mode 100644 index 0000000000..aaca8a8f64 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLTypeReference.h @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_TYPEREFERENCE +#define SKSL_TYPEREFERENCE + +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <memory> +#include <string> + +namespace SkSL { + +enum class OperatorPrecedence : uint8_t; + +/** + * Represents an identifier referring to a type. This is an intermediate value: TypeReferences are + * always eventually replaced by Constructors in valid programs. + */ +class TypeReference final : public Expression { +public: + inline static constexpr Kind kIRNodeKind = Kind::kTypeReference; + + TypeReference(const Context& context, Position pos, const Type* value) + : TypeReference(pos, value, context.fTypes.fInvalid.get()) {} + + // Creates a reference to an SkSL type; uses the ErrorReporter to report errors. + static std::unique_ptr<TypeReference> Convert(const Context& context, + Position pos, + const Type* type); + + // Creates a reference to an SkSL type; reports errors via ASSERT. + static std::unique_ptr<TypeReference> Make(const Context& context, Position pos, + const Type* type); + + const Type& value() const { + return fValue; + } + + std::string description(OperatorPrecedence) const override { + return std::string(this->value().name()); + } + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::unique_ptr<Expression>(new TypeReference(pos, &this->value(), &this->type())); + } + +private: + TypeReference(Position pos, const Type* value, const Type* type) + : INHERITED(pos, kIRNodeKind, type) + , fValue(*value) {} + + const Type& fValue; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.cpp b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.cpp new file mode 100644 index 0000000000..8d698687bf --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.cpp @@ -0,0 +1,468 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLVarDeclarations.h" + +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramKind.h" +#include "include/private/SkSLString.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstddef> +#include <string_view> +#include <vector> + +namespace SkSL { +namespace { + +static bool check_valid_uniform_type(Position pos, + const Type* t, + const Context& context, + bool topLevel = true) { + const Type& ct = t->componentType(); + + // In RuntimeEffects we only allow a restricted set of types, namely shader/blender/colorFilter, + // 32-bit signed integers, 16-bit and 32-bit floats, and their composites. + { + bool error = false; + if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { + // `shader`, `blender`, `colorFilter` + if (t->isEffectChild()) { + return true; + } + + // `int`, `int2`, `int3`, `int4` + if (ct.isSigned() && ct.bitWidth() == 32 && (t->isScalar() || t->isVector())) { + return true; + } + + // `float`, `float2`, `float3`, `float4`, `float2x2`, `float3x3`, `float4x4` + // `half`, `half2`, `half3`, `half4`, `half2x2`, `half3x3`, `half4x4` + if (ct.isFloat() && + (t->isScalar() || t->isVector() || (t->isMatrix() && t->rows() == t->columns()))) { + return true; + } + + // Everything else is an error. + error = true; + } + + // We disallow boolean uniforms in SkSL since they are not well supported by backend + // platforms and drivers. We disallow atomic variables in uniforms as that doesn't map + // cleanly to all backends. + if (error || (ct.isBoolean() && (t->isScalar() || t->isVector())) || ct.isAtomic()) { + context.fErrors->error( + pos, "variables of type '" + t->displayName() + "' may not be uniform"); + return false; + } + } + + // In non-RTE SkSL we allow structs and interface blocks to be uniforms but we must make sure + // their fields are allowed. + if (t->isStruct()) { + for (const Type::Field& field : t->fields()) { + if (!check_valid_uniform_type( + field.fPosition, field.fType, context, /*topLevel=*/false)) { + // Emit a "caused by" line only for the top-level uniform type and not for any + // nested structs. + if (topLevel) { + context.fErrors->error(pos, "caused by:"); + } + return false; + } + } + } + return true; +} + +} // namespace + +std::unique_ptr<Statement> VarDeclaration::clone() const { + // Cloning a VarDeclaration is inherently problematic, as we normally expect a one-to-one + // mapping between Variables and VarDeclarations and a straightforward clone would violate this + // assumption. We could of course theoretically clone the Variable as well, but that would + // require additional context and tracking, since for the whole process to work we would also + // have to fixup any subsequent VariableReference clones to point to the newly cloned Variables + // instead of the originals. + // + // Since the only reason we ever clone VarDeclarations is to support tests of clone() and we do + // not expect to ever need to do so otherwise, a full solution to this issue is unnecessary at + // the moment. We instead just keep track of whether a VarDeclaration is a clone so we can + // handle its cleanup properly. This allows clone() to work in the simple case that a + // VarDeclaration's clone does not outlive the original, which is adequate for testing. Since + // this leaves a sharp edge in place - destroying the original could cause a use-after-free in + // some circumstances - we also disable cloning altogether unless the + // fAllowVarDeclarationCloneForTesting ProgramSetting is enabled. + if (ThreadContext::Settings().fAllowVarDeclarationCloneForTesting) { + return std::make_unique<VarDeclaration>(this->var(), + &this->baseType(), + fArraySize, + this->value() ? this->value()->clone() : nullptr, + /*isClone=*/true); + } else { + SkDEBUGFAIL("VarDeclaration::clone() is unsupported"); + return nullptr; + } +} + +std::string VarDeclaration::description() const { + std::string result = this->var()->modifiers().description() + this->baseType().description() + + " " + std::string(this->var()->name()); + if (this->arraySize() > 0) { + String::appendf(&result, "[%d]", this->arraySize()); + } + if (this->value()) { + result += " = " + this->value()->description(); + } + result += ";"; + return result; +} + +void VarDeclaration::ErrorCheck(const Context& context, + Position pos, + Position modifiersPosition, + const Modifiers& modifiers, + const Type* type, + Variable::Storage storage) { + const Type* baseType = type; + if (baseType->isArray()) { + baseType = &baseType->componentType(); + } + SkASSERT(!baseType->isArray()); + + if (baseType->matches(*context.fTypes.fInvalid)) { + context.fErrors->error(pos, "invalid type"); + return; + } + if (baseType->isVoid()) { + context.fErrors->error(pos, "variables of type 'void' are not allowed"); + return; + } + + if (baseType->componentType().isOpaque() && !baseType->componentType().isAtomic() && + storage != Variable::Storage::kGlobal) { + context.fErrors->error(pos, + "variables of type '" + baseType->displayName() + "' must be global"); + } + if ((modifiers.fFlags & Modifiers::kIn_Flag) && baseType->isMatrix()) { + context.fErrors->error(pos, "'in' variables may not have matrix type"); + } + if ((modifiers.fFlags & Modifiers::kIn_Flag) && type->isUnsizedArray()) { + context.fErrors->error(pos, "'in' variables may not have unsized array type"); + } + if ((modifiers.fFlags & Modifiers::kOut_Flag) && type->isUnsizedArray()) { + context.fErrors->error(pos, "'out' variables may not have unsized array type"); + } + if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kUniform_Flag)) { + context.fErrors->error(pos, "'in uniform' variables not permitted"); + } + if ((modifiers.fFlags & Modifiers::kReadOnly_Flag) && + (modifiers.fFlags & Modifiers::kWriteOnly_Flag)) { + context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers cannot be combined"); + } + if ((modifiers.fFlags & Modifiers::kUniform_Flag) && + (modifiers.fFlags & Modifiers::kBuffer_Flag)) { + context.fErrors->error(pos, "'uniform buffer' variables not permitted"); + } + if ((modifiers.fFlags & Modifiers::kWorkgroup_Flag) && + (modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag))) { + context.fErrors->error(pos, "in / out variables may not be declared workgroup"); + } + if ((modifiers.fFlags & Modifiers::kUniform_Flag)) { + check_valid_uniform_type(pos, baseType, context); + } + if (baseType->isEffectChild() && !(modifiers.fFlags & Modifiers::kUniform_Flag)) { + context.fErrors->error(pos, + "variables of type '" + baseType->displayName() + "' must be uniform"); + } + if (baseType->isEffectChild() && (context.fConfig->fKind == ProgramKind::kMeshVertex || + context.fConfig->fKind == ProgramKind::kMeshFragment)) { + context.fErrors->error(pos, "effects are not permitted in custom mesh shaders"); + } + if (baseType->isOrContainsAtomic()) { + // An atomic variable (or a struct or an array that contains an atomic member) must be + // either: + // a. Declared as a workgroup-shared variable, OR + // b. Declared as the member of writable storage buffer block (i.e. has no readonly + // restriction). + // + // The checks below will enforce these two rules on all declarations. If the variable is not + // declared with the workgroup modifier, then it must be declared in the interface block + // storage. If this is the declaration for an interface block that contains an atomic + // member, then it must have the `buffer` modifier and no `readonly` modifier. + bool isWorkgroup = modifiers.fFlags & Modifiers::kWorkgroup_Flag; + bool isBlockMember = (storage == Variable::Storage::kInterfaceBlock); + bool isWritableStorageBuffer = modifiers.fFlags & Modifiers::kBuffer_Flag && + !(modifiers.fFlags & Modifiers::kReadOnly_Flag); + + if (!isWorkgroup && + !(baseType->isInterfaceBlock() ? isWritableStorageBuffer : isBlockMember)) { + context.fErrors->error(pos, + "atomics are only permitted in workgroup variables and writable " + "storage blocks"); + } + } + if (modifiers.fLayout.fFlags & Layout::kColor_Flag) { + if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { + context.fErrors->error(pos, "'layout(color)' is only permitted in runtime effects"); + } + if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) { + context.fErrors->error(pos, + "'layout(color)' is only permitted on 'uniform' variables"); + } + auto validColorXformType = [](const Type& t) { + return t.isVector() && t.componentType().isFloat() && + (t.columns() == 3 || t.columns() == 4); + }; + if (!validColorXformType(*baseType)) { + context.fErrors->error(pos, + "'layout(color)' is not permitted on variables of type '" + + baseType->displayName() + "'"); + } + } + + int permitted = Modifiers::kConst_Flag | Modifiers::kHighp_Flag | Modifiers::kMediump_Flag | + Modifiers::kLowp_Flag; + if (storage == Variable::Storage::kGlobal) { + // Uniforms are allowed in all programs + permitted |= Modifiers::kUniform_Flag; + + // No other modifiers are allowed in runtime effects. + if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { + if (baseType->isInterfaceBlock()) { + // Interface blocks allow `buffer`. + permitted |= Modifiers::kBuffer_Flag; + + if (modifiers.fFlags & Modifiers::kBuffer_Flag) { + // Only storage blocks allow `readonly` and `writeonly`. + // (`readonly` and `writeonly` textures are converted to separate types via + // applyAccessQualifiers.) + permitted |= Modifiers::kReadOnly_Flag | Modifiers::kWriteOnly_Flag; + } + + // It is an error for an unsized array to appear anywhere but the last member of a + // "buffer" block. + const auto& fields = baseType->fields(); + const size_t illegalRangeEnd = + fields.size() - ((modifiers.fFlags & Modifiers::kBuffer_Flag) ? 1 : 0); + for (size_t i = 0; i < illegalRangeEnd; ++i) { + if (fields[i].fType->isUnsizedArray()) { + context.fErrors->error( + fields[i].fPosition, + "unsized array must be the last member of a storage block"); + } + } + } + + if (!baseType->isOpaque()) { + // Only non-opaque types allow `in` and `out`. + permitted |= Modifiers::kIn_Flag | Modifiers::kOut_Flag; + } + if (ProgramConfig::IsCompute(context.fConfig->fKind)) { + // Only compute shaders allow `workgroup`. + if (!baseType->isOpaque() || baseType->isAtomic()) { + permitted |= Modifiers::kWorkgroup_Flag; + } + } else { + // Only vertex/fragment shaders allow `flat` and `noperspective`. + permitted |= Modifiers::kFlat_Flag | Modifiers::kNoPerspective_Flag; + } + } + } + + int permittedLayoutFlags = ~0; + + // The `texture` and `sampler` modifiers can be present respectively on a texture and sampler or + // simultaneously on a combined image-sampler but they are not permitted on any other type. + switch (baseType->typeKind()) { + case Type::TypeKind::kSampler: + // Both texture and sampler flags are permitted + break; + case Type::TypeKind::kTexture: + permittedLayoutFlags &= ~Layout::kSampler_Flag; + break; + case Type::TypeKind::kSeparateSampler: + permittedLayoutFlags &= ~Layout::kTexture_Flag; + break; + default: + permittedLayoutFlags &= ~(Layout::kTexture_Flag | Layout::kSampler_Flag); + break; + } + + // We don't allow 'binding' or 'set' on normal uniform variables, only on textures, samplers, + // and interface blocks (holding uniform variables). They're also only allowed at global scope, + // not on interface block fields (or locals/parameters). + bool permitBindingAndSet = baseType->typeKind() == Type::TypeKind::kSampler || + baseType->typeKind() == Type::TypeKind::kSeparateSampler || + baseType->typeKind() == Type::TypeKind::kTexture || + baseType->isInterfaceBlock(); + if (storage != Variable::Storage::kGlobal || + ((modifiers.fFlags & Modifiers::kUniform_Flag) && !permitBindingAndSet)) { + permittedLayoutFlags &= ~Layout::kBinding_Flag; + permittedLayoutFlags &= ~Layout::kSet_Flag; + permittedLayoutFlags &= ~Layout::kSPIRV_Flag; + permittedLayoutFlags &= ~Layout::kMetal_Flag; + permittedLayoutFlags &= ~Layout::kWGSL_Flag; + permittedLayoutFlags &= ~Layout::kGL_Flag; + } + if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { + // Disallow all layout flags except 'color' in runtime effects + permittedLayoutFlags &= Layout::kColor_Flag; + } + modifiers.checkPermitted(context, modifiersPosition, permitted, permittedLayoutFlags); +} + +bool VarDeclaration::ErrorCheckAndCoerce(const Context& context, const Variable& var, + std::unique_ptr<Expression>& value) { + ErrorCheck(context, var.fPosition, var.modifiersPosition(), var.modifiers(), &var.type(), + var.storage()); + if (value) { + if (var.type().isOpaque()) { + context.fErrors->error(value->fPosition, "opaque type '" + var.type().displayName() + + "' cannot use initializer expressions"); + return false; + } + if (var.modifiers().fFlags & Modifiers::kIn_Flag) { + context.fErrors->error(value->fPosition, + "'in' variables cannot use initializer expressions"); + return false; + } + if (var.modifiers().fFlags & Modifiers::kUniform_Flag) { + context.fErrors->error(value->fPosition, + "'uniform' variables cannot use initializer expressions"); + return false; + } + if (var.storage() == Variable::Storage::kInterfaceBlock) { + context.fErrors->error(value->fPosition, + "initializers are not permitted on interface block fields"); + return false; + } + value = var.type().coerceExpression(std::move(value), context); + if (!value) { + return false; + } + } + if (var.modifiers().fFlags & Modifiers::kConst_Flag) { + if (!value) { + context.fErrors->error(var.fPosition, "'const' variables must be initialized"); + return false; + } + if (!Analysis::IsConstantExpression(*value)) { + context.fErrors->error(value->fPosition, + "'const' variable initializer must be a constant expression"); + return false; + } + } + if (var.storage() == Variable::Storage::kInterfaceBlock) { + if (var.type().isOpaque()) { + context.fErrors->error(var.fPosition, "opaque type '" + var.type().displayName() + + "' is not permitted in an interface block"); + return false; + } + } + if (var.storage() == Variable::Storage::kGlobal) { + if (value && !Analysis::IsConstantExpression(*value)) { + context.fErrors->error(value->fPosition, + "global variable initializer must be a constant expression"); + return false; + } + } + return true; +} + +std::unique_ptr<Statement> VarDeclaration::Convert(const Context& context, + std::unique_ptr<Variable> var, + std::unique_ptr<Expression> value, + bool addToSymbolTable) { + if (!ErrorCheckAndCoerce(context, *var, value)) { + return nullptr; + } + const Type* baseType = &var->type(); + int arraySize = 0; + if (baseType->isArray()) { + arraySize = baseType->columns(); + baseType = &baseType->componentType(); + } + std::unique_ptr<Statement> varDecl = VarDeclaration::Make(context, var.get(), baseType, + arraySize, std::move(value)); + if (!varDecl) { + return nullptr; + } + + SymbolTable* symbols = ThreadContext::SymbolTable().get(); + if (var->storage() == Variable::Storage::kGlobal || + var->storage() == Variable::Storage::kInterfaceBlock) { + // Check if this globally-scoped variable name overlaps an existing symbol name. + if (symbols->find(var->name())) { + context.fErrors->error(var->fPosition, + "symbol '" + std::string(var->name()) + "' was already defined"); + return nullptr; + } + + // `sk_RTAdjust` is special, and makes the IR generator emit position-fixup expressions. + if (var->name() == Compiler::RTADJUST_NAME) { + if (ThreadContext::RTAdjustState().fVar || + ThreadContext::RTAdjustState().fInterfaceBlock) { + context.fErrors->error(var->fPosition, "duplicate definition of 'sk_RTAdjust'"); + return nullptr; + } + if (!var->type().matches(*context.fTypes.fFloat4)) { + context.fErrors->error(var->fPosition, "sk_RTAdjust must have type 'float4'"); + return nullptr; + } + ThreadContext::RTAdjustState().fVar = var.get(); + } + } + + if (addToSymbolTable) { + symbols->add(std::move(var)); + } else { + symbols->takeOwnershipOfSymbol(std::move(var)); + } + return varDecl; +} + +std::unique_ptr<Statement> VarDeclaration::Make(const Context& context, Variable* var, + const Type* baseType, int arraySize, std::unique_ptr<Expression> value) { + SkASSERT(!baseType->isArray()); + // function parameters cannot have variable declarations + SkASSERT(var->storage() != Variable::Storage::kParameter); + // 'const' variables must be initialized + SkASSERT(!(var->modifiers().fFlags & Modifiers::kConst_Flag) || value); + // 'const' variable initializer must be a constant expression + SkASSERT(!(var->modifiers().fFlags & Modifiers::kConst_Flag) || + Analysis::IsConstantExpression(*value)); + // global variable initializer must be a constant expression + SkASSERT(!(value && var->storage() == Variable::Storage::kGlobal && + !Analysis::IsConstantExpression(*value))); + // opaque type not permitted on an interface block + SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && var->type().isOpaque())); + // initializers are not permitted on interface block fields + SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && value)); + // opaque type cannot use initializer expressions + SkASSERT(!(value && var->type().isOpaque())); + // 'in' variables cannot use initializer expressions + SkASSERT(!(value && (var->modifiers().fFlags & Modifiers::kIn_Flag))); + // 'uniform' variables cannot use initializer expressions + SkASSERT(!(value && (var->modifiers().fFlags & Modifiers::kUniform_Flag))); + + auto result = std::make_unique<VarDeclaration>(var, baseType, arraySize, std::move(value)); + var->setVarDeclaration(result.get()); + return std::move(result); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h new file mode 100644 index 0000000000..b90528732e --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVarDeclarations.h @@ -0,0 +1,164 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VARDECLARATIONS +#define SKSL_VARDECLARATIONS + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <memory> +#include <string> +#include <utility> + +namespace SkSL { + +class Context; +class Position; +class Type; + +struct Modifiers; + +/** + * A single variable declaration statement. Multiple variables declared together are expanded to + * separate (sequential) statements. For instance, the SkSL 'int x = 2, y[3];' produces two + * VarDeclaration instances (wrapped in an unscoped Block). + */ +class VarDeclaration final : public Statement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kVarDeclaration; + + VarDeclaration(Variable* var, + const Type* baseType, + int arraySize, + std::unique_ptr<Expression> value, + bool isClone = false) + : INHERITED(var->fPosition, kIRNodeKind) + , fVar(var) + , fBaseType(*baseType) + , fArraySize(arraySize) + , fValue(std::move(value)) + , fIsClone(isClone) {} + + ~VarDeclaration() override { + // Unhook this VarDeclaration from its associated Variable, since we're being deleted. + if (fVar && !fIsClone) { + fVar->detachDeadVarDeclaration(); + } + } + + // Checks the modifiers, baseType, and storage for compatibility with one another and reports + // errors if needed. This method is implicitly called during Convert(), but is also explicitly + // called while processing interface block fields. + static void ErrorCheck(const Context& context, Position pos, Position modifiersPosition, + const Modifiers& modifiers, const Type* type, Variable::Storage storage); + + // Does proper error checking and type coercion; reports errors via ErrorReporter. + static std::unique_ptr<Statement> Convert(const Context& context, std::unique_ptr<Variable> var, + std::unique_ptr<Expression> value, bool addToSymbolTable = true); + + // Reports errors via ASSERT. + static std::unique_ptr<Statement> Make(const Context& context, + Variable* var, + const Type* baseType, + int arraySize, + std::unique_ptr<Expression> value); + const Type& baseType() const { + return fBaseType; + } + + Variable* var() const { + return fVar; + } + + void detachDeadVariable() { + fVar = nullptr; + } + + int arraySize() const { + return fArraySize; + } + + std::unique_ptr<Expression>& value() { + return fValue; + } + + const std::unique_ptr<Expression>& value() const { + return fValue; + } + + std::unique_ptr<Statement> clone() const override; + + std::string description() const override; + +private: + static bool ErrorCheckAndCoerce(const Context& context, + const Variable& var, + std::unique_ptr<Expression>& value); + + Variable* fVar; + const Type& fBaseType; + int fArraySize; // zero means "not an array" + std::unique_ptr<Expression> fValue; + // if this VarDeclaration is a clone, it doesn't actually own the associated variable + bool fIsClone; + + using INHERITED = Statement; +}; + +/** + * A variable declaration appearing at global scope. A global declaration like 'int x, y;' produces + * two GlobalVarDeclaration elements, each containing the declaration of one variable. + */ +class GlobalVarDeclaration final : public ProgramElement { +public: + inline static constexpr Kind kIRNodeKind = Kind::kGlobalVar; + + GlobalVarDeclaration(std::unique_ptr<Statement> decl) + : INHERITED(decl->fPosition, kIRNodeKind) + , fDeclaration(std::move(decl)) { + SkASSERT(this->declaration()->is<VarDeclaration>()); + this->varDeclaration().var()->setGlobalVarDeclaration(this); + } + + std::unique_ptr<Statement>& declaration() { + return fDeclaration; + } + + const std::unique_ptr<Statement>& declaration() const { + return fDeclaration; + } + + VarDeclaration& varDeclaration() { + return fDeclaration->as<VarDeclaration>(); + } + + const VarDeclaration& varDeclaration() const { + return fDeclaration->as<VarDeclaration>(); + } + + std::unique_ptr<ProgramElement> clone() const override { + return std::make_unique<GlobalVarDeclaration>(this->declaration()->clone()); + } + + std::string description() const override { + return this->declaration()->description(); + } + +private: + std::unique_ptr<Statement> fDeclaration; + + using INHERITED = ProgramElement; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariable.cpp b/gfx/skia/skia/src/sksl/ir/SkSLVariable.cpp new file mode 100644 index 0000000000..95c292a8ad --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVariable.cpp @@ -0,0 +1,212 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLVariable.h" + +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLMangler.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLThreadContext.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" + +#include <type_traits> +#include <utility> + +namespace SkSL { + +Variable::~Variable() { + // Unhook this Variable from its associated VarDeclaration, since we're being deleted. + if (VarDeclaration* declaration = this->varDeclaration()) { + declaration->detachDeadVariable(); + } +} + +InterfaceBlockVariable::~InterfaceBlockVariable() { + // Unhook this Variable from its associated InterfaceBlock, since we're being deleted. + if (fInterfaceBlockElement) { + fInterfaceBlockElement->detachDeadVariable(); + } +} + +const Expression* Variable::initialValue() const { + VarDeclaration* declaration = this->varDeclaration(); + return declaration ? declaration->value().get() : nullptr; +} + +VarDeclaration* Variable::varDeclaration() const { + if (!fDeclaringElement) { + return nullptr; + } + SkASSERT(fDeclaringElement->is<VarDeclaration>() || + fDeclaringElement->is<GlobalVarDeclaration>()); + return fDeclaringElement->is<GlobalVarDeclaration>() + ? &fDeclaringElement->as<GlobalVarDeclaration>().varDeclaration() + : &fDeclaringElement->as<VarDeclaration>(); +} + +GlobalVarDeclaration* Variable::globalVarDeclaration() const { + if (!fDeclaringElement) { + return nullptr; + } + SkASSERT(fDeclaringElement->is<VarDeclaration>() || + fDeclaringElement->is<GlobalVarDeclaration>()); + return fDeclaringElement->is<GlobalVarDeclaration>() + ? &fDeclaringElement->as<GlobalVarDeclaration>() + : nullptr; +} + +void Variable::setVarDeclaration(VarDeclaration* declaration) { + SkASSERT(!fDeclaringElement || this == declaration->var()); + if (!fDeclaringElement) { + fDeclaringElement = declaration; + } +} + +void Variable::setGlobalVarDeclaration(GlobalVarDeclaration* global) { + SkASSERT(!fDeclaringElement || this == global->varDeclaration().var()); + fDeclaringElement = global; +} + +std::string Variable::mangledName() const { + // Only private variables need to use name mangling. + std::string_view name = this->name(); + if (!skstd::starts_with(name, '$')) { + return std::string(name); + } + + // The $ prefix will fail to compile in GLSL, so replace it with `sk_Priv`. + name.remove_prefix(1); + return "sk_Priv" + std::string(name); +} + +std::unique_ptr<Variable> Variable::Convert(const Context& context, + Position pos, + Position modifiersPos, + const Modifiers& modifiers, + const Type* baseType, + Position namePos, + std::string_view name, + bool isArray, + std::unique_ptr<Expression> arraySize, + Variable::Storage storage) { + if (modifiers.fLayout.fLocation == 0 && modifiers.fLayout.fIndex == 0 && + (modifiers.fFlags & Modifiers::kOut_Flag) && + ProgramConfig::IsFragment(context.fConfig->fKind) && name != Compiler::FRAGCOLOR_NAME) { + context.fErrors->error(modifiersPos, + "out location=0, index=0 is reserved for sk_FragColor"); + } + if (baseType->isUnsizedArray() && storage != Variable::Storage::kInterfaceBlock) { + context.fErrors->error(pos, "unsized arrays are not permitted here"); + } + if (ProgramConfig::IsCompute(ThreadContext::Context().fConfig->fKind) && + modifiers.fLayout.fBuiltin == -1) { + if (storage == Variable::Storage::kGlobal) { + if (modifiers.fFlags & Modifiers::kIn_Flag) { + context.fErrors->error(pos, "pipeline inputs not permitted in compute shaders"); + } else if (modifiers.fFlags & Modifiers::kOut_Flag) { + context.fErrors->error(pos, "pipeline outputs not permitted in compute shaders"); + } + } + } + + return Make(context, pos, modifiersPos, modifiers, baseType, name, isArray, + std::move(arraySize), storage); +} + +std::unique_ptr<Variable> Variable::Make(const Context& context, + Position pos, + Position modifiersPos, + const Modifiers& modifiers, + const Type* baseType, + std::string_view name, + bool isArray, + std::unique_ptr<Expression> arraySize, + Variable::Storage storage) { + const Type* type = baseType; + int arraySizeValue = 0; + if (isArray) { + SkASSERT(arraySize); + arraySizeValue = type->convertArraySize(context, pos, std::move(arraySize)); + if (!arraySizeValue) { + return nullptr; + } + type = ThreadContext::SymbolTable()->addArrayDimension(type, arraySizeValue); + } + if (type->componentType().isInterfaceBlock()) { + return std::make_unique<InterfaceBlockVariable>(pos, + modifiersPos, + context.fModifiersPool->add(modifiers), + name, + type, + context.fConfig->fIsBuiltinCode, + storage); + } else { + return std::make_unique<Variable>(pos, + modifiersPos, + context.fModifiersPool->add(modifiers), + name, + type, + context.fConfig->fIsBuiltinCode, + storage); + } +} + +Variable::ScratchVariable Variable::MakeScratchVariable(const Context& context, + Mangler& mangler, + std::string_view baseName, + const Type* type, + const Modifiers& modifiers, + SymbolTable* symbolTable, + std::unique_ptr<Expression> initialValue) { + // $floatLiteral or $intLiteral aren't real types that we can use for scratch variables, so + // replace them if they ever appear here. If this happens, we likely forgot to coerce a type + // somewhere during compilation. + if (type->isLiteral()) { + SkDEBUGFAIL("found a $literal type in MakeScratchVariable"); + type = &type->scalarTypeForLiteral(); + } + + // Out-parameters aren't supported. + SkASSERT(!(modifiers.fFlags & Modifiers::kOut_Flag)); + + // Provide our new variable with a unique name, and add it to our symbol table. + const std::string* name = + symbolTable->takeOwnershipOfString(mangler.uniqueName(baseName, symbolTable)); + + // Create our new variable and add it to the symbol table. + ScratchVariable result; + auto var = std::make_unique<Variable>(initialValue ? initialValue->fPosition : Position(), + /*modifiersPosition=*/Position(), + context.fModifiersPool->add(Modifiers{}), + name->c_str(), + type, + symbolTable->isBuiltin(), + Variable::Storage::kLocal); + + // If we are creating an array type, reduce it to base type plus array-size. + int arraySize = 0; + if (type->isArray()) { + arraySize = type->columns(); + type = &type->componentType(); + } + // Create our variable declaration. + result.fVarDecl = VarDeclaration::Make(context, var.get(), type, arraySize, + std::move(initialValue)); + result.fVarSymbol = symbolTable->add(std::move(var)); + return result; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariable.h b/gfx/skia/skia/src/sksl/ir/SkSLVariable.h new file mode 100644 index 0000000000..a94292873b --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVariable.h @@ -0,0 +1,179 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VARIABLE +#define SKSL_VARIABLE + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLSymbol.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> + +namespace SkSL { + +class Context; +class Expression; +class GlobalVarDeclaration; +class InterfaceBlock; +class Mangler; +class SymbolTable; +class VarDeclaration; + +enum class VariableStorage : int8_t { + kGlobal, + kInterfaceBlock, + kLocal, + kParameter, +}; + +/** + * Represents a variable, whether local, global, or a function parameter. This represents the + * variable itself (the storage location), which is shared between all VariableReferences which + * read or write that storage location. + */ +class Variable : public Symbol { +public: + using Storage = VariableStorage; + + inline static constexpr Kind kIRNodeKind = Kind::kVariable; + + Variable(Position pos, Position modifiersPosition, const Modifiers* modifiers, + std::string_view name, const Type* type, bool builtin, Storage storage) + : INHERITED(pos, kIRNodeKind, name, type) + , fModifiersPosition(modifiersPosition) + , fModifiers(modifiers) + , fStorage(storage) + , fBuiltin(builtin) {} + + ~Variable() override; + + static std::unique_ptr<Variable> Convert(const Context& context, Position pos, + Position modifiersPos, const Modifiers& modifiers, const Type* baseType, + Position namePos, std::string_view name, bool isArray, + std::unique_ptr<Expression> arraySize, Variable::Storage storage); + + static std::unique_ptr<Variable> Make(const Context& context, Position pos, + Position modifiersPos, const Modifiers& modifiers, const Type* baseType, + std::string_view name, bool isArray, std::unique_ptr<Expression> arraySize, + Variable::Storage storage); + + /** + * Creates a local scratch variable and the associated VarDeclaration statement. + * Useful when doing IR rewrites, e.g. inlining a function call. + */ + struct ScratchVariable { + const Variable* fVarSymbol; + std::unique_ptr<Statement> fVarDecl; + }; + static ScratchVariable MakeScratchVariable(const Context& context, + Mangler& mangler, + std::string_view baseName, + const Type* type, + const Modifiers& modifiers, + SymbolTable* symbolTable, + std::unique_ptr<Expression> initialValue); + const Modifiers& modifiers() const { + return *fModifiers; + } + + void setModifiers(const Modifiers* modifiers) { + fModifiers = modifiers; + } + + Position modifiersPosition() const { + return fModifiersPosition; + } + + bool isBuiltin() const { + return fBuiltin; + } + + Storage storage() const { + return fStorage; + } + + const Expression* initialValue() const; + + VarDeclaration* varDeclaration() const; + + void setVarDeclaration(VarDeclaration* declaration); + + GlobalVarDeclaration* globalVarDeclaration() const; + + void setGlobalVarDeclaration(GlobalVarDeclaration* global); + + void detachDeadVarDeclaration() { + // The VarDeclaration is being deleted, so our reference to it has become stale. + fDeclaringElement = nullptr; + } + + // The interfaceBlock methods are no-op stubs here. They have proper implementations in + // InterfaceBlockVariable, declared below this class, which dedicates extra space to store the + // pointer back to the InterfaceBlock. + virtual InterfaceBlock* interfaceBlock() const { return nullptr; } + + virtual void setInterfaceBlock(InterfaceBlock*) { SkUNREACHABLE; } + + virtual void detachDeadInterfaceBlock() {} + + std::string description() const override { + return this->modifiers().description() + this->type().displayName() + " " + + std::string(this->name()); + } + + std::string mangledName() const; + +private: + IRNode* fDeclaringElement = nullptr; + // We don't store the position in the Modifiers object itself because they are pooled + Position fModifiersPosition; + const Modifiers* fModifiers; + VariableStorage fStorage; + bool fBuiltin; + + using INHERITED = Symbol; +}; + +/** + * This represents a Variable associated with an InterfaceBlock. Mostly a normal variable, but also + * has an extra pointer back to the InterfaceBlock element that owns it. + */ +class InterfaceBlockVariable final : public Variable { +public: + using Variable::Variable; + + ~InterfaceBlockVariable() override; + + InterfaceBlock* interfaceBlock() const override { return fInterfaceBlockElement; } + + void setInterfaceBlock(InterfaceBlock* elem) override { + SkASSERT(!fInterfaceBlockElement); + fInterfaceBlockElement = elem; + } + + void detachDeadInterfaceBlock() override { + // The InterfaceBlock is being deleted, so our reference to it has become stale. + fInterfaceBlockElement = nullptr; + } + +private: + InterfaceBlock* fInterfaceBlockElement = nullptr; + + using INHERITED = Variable; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp new file mode 100644 index 0000000000..6eca3ddb89 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/ir/SkSLVariableReference.h" + +#include "src/sksl/ir/SkSLVariable.h" + +namespace SkSL { + +VariableReference::VariableReference(Position pos, const Variable* variable, RefKind refKind) + : INHERITED(pos, kIRNodeKind, &variable->type()) + , fVariable(variable) + , fRefKind(refKind) { + SkASSERT(this->variable()); +} + +std::string VariableReference::description(OperatorPrecedence) const { + return std::string(this->variable()->name()); +} + +void VariableReference::setRefKind(RefKind refKind) { + fRefKind = refKind; +} + +void VariableReference::setVariable(const Variable* variable) { + fVariable = variable; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h new file mode 100644 index 0000000000..d33c569314 --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLVariableReference.h @@ -0,0 +1,87 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_VARIABLEREFERENCE +#define SKSL_VARIABLEREFERENCE + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/sksl/SkSLPosition.h" +#include "src/sksl/ir/SkSLExpression.h" + +#include <cstdint> +#include <memory> +#include <string> + +namespace SkSL { + +class Variable; +enum class OperatorPrecedence : uint8_t; + +enum class VariableRefKind : int8_t { + kRead, + kWrite, + kReadWrite, + // taking the address of a variable - we consider this a read & write but don't complain if + // the variable was not previously assigned + kPointer +}; + +/** + * A reference to a variable, through which it can be read or written. In the statement: + * + * x = x + 1; + * + * there is only one Variable 'x', but two VariableReferences to it. + */ +class VariableReference final : public Expression { +public: + using RefKind = VariableRefKind; + + inline static constexpr Kind kIRNodeKind = Kind::kVariableReference; + + VariableReference(Position pos, const Variable* variable, RefKind refKind); + + // Creates a VariableReference. There isn't much in the way of error-checking or optimization + // opportunities here. + static std::unique_ptr<Expression> Make(Position pos, + const Variable* variable, + RefKind refKind = RefKind::kRead) { + SkASSERT(variable); + return std::make_unique<VariableReference>(pos, variable, refKind); + } + + VariableReference(const VariableReference&) = delete; + VariableReference& operator=(const VariableReference&) = delete; + + const Variable* variable() const { + return fVariable; + } + + RefKind refKind() const { + return fRefKind; + } + + void setRefKind(RefKind refKind); + void setVariable(const Variable* variable); + + std::unique_ptr<Expression> clone(Position pos) const override { + return std::make_unique<VariableReference>(pos, this->variable(), this->refKind()); + } + + std::string description(OperatorPrecedence) const override; + +private: + const Variable* fVariable; + VariableRefKind fRefKind; + + using INHERITED = Expression; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/DFA.h b/gfx/skia/skia/src/sksl/lex/DFA.h new file mode 100644 index 0000000000..1fab51f921 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/DFA.h @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DFA +#define SKSL_DFA + +#include <string> +#include <vector> + +/** + * Tables representing a deterministic finite automaton for matching regular expressions. + */ +struct DFA { + DFA(std::vector<int> charMappings, std::vector<std::vector<int>> transitions, + std::vector<int> accepts) + : fCharMappings(charMappings) + , fTransitions(transitions) + , fAccepts(accepts) {} + + // maps chars to the row index of fTransitions, as multiple characters may map to the same row. + // starting from state s and looking at char c, the new state is + // fTransitions[fCharMappings[c]][s]. + std::vector<int> fCharMappings; + + // one row per character mapping, one column per state + std::vector<std::vector<int>> fTransitions; + + // contains, for each state, the token id we should report when matching ends in that state (-1 + // for no match) + std::vector<int> fAccepts; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/DFAState.h b/gfx/skia/skia/src/sksl/lex/DFAState.h new file mode 100644 index 0000000000..a09d7ba673 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/DFAState.h @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_DFASTATE +#define SKSL_DFASTATE + +#include "src/sksl/lex/LexUtil.h" + +#include <vector> +#include <string> + +struct DFAState { + struct Label { + std::vector<int> fStates; + + Label(std::vector<int> states) + : fStates(std::move(states)) {} + + bool operator==(const Label& other) const { + return fStates == other.fStates; + } + + bool operator!=(const Label& other) const { + return !(*this == other); + } + +#ifdef SK_DEBUG + std::string description() const { + std::string result = "<"; + const char* separator = ""; + for (int s : fStates) { + result += separator; + result += std::to_string(s); + separator = ", "; + } + result += ">"; + return result; + } +#endif + }; + + DFAState() + : fId(INVALID) + , fLabel({}) {} + + DFAState(int id, Label label) + : fId(id) + , fLabel(std::move(label)) {} + + DFAState(const DFAState& other) = delete; + + int fId; + + Label fLabel; + + bool fIsScanned = false; +}; + +namespace std { + template<> struct hash<DFAState::Label> { + size_t operator()(const DFAState::Label& s) const { + size_t result = 0; + for (int i : s.fStates) { + result = result * 101 + i; + } + return result; + } + }; +} // namespace + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/LexUtil.h b/gfx/skia/skia/src/sksl/lex/LexUtil.h new file mode 100644 index 0000000000..65692cb21b --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/LexUtil.h @@ -0,0 +1,20 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_LEXUTIL +#define SKSL_LEXUTIL + +#include <cstdlib> + +#define INVALID -1 + +#define SK_ABORT(...) (fprintf(stderr, __VA_ARGS__), abort()) +#define SkASSERT(x) \ + (void)((x) || (SK_ABORT("failed SkASSERT(%s): %s:%d\n", #x, __FILE__, __LINE__), 0)) +#define SkUNREACHABLE (SK_ABORT("unreachable")) + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/Main.cpp b/gfx/skia/skia/src/sksl/lex/Main.cpp new file mode 100644 index 0000000000..ab4e3a618b --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/Main.cpp @@ -0,0 +1,238 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/DFA.h" +#include "src/sksl/lex/LexUtil.h" +#include "src/sksl/lex/NFA.h" +#include "src/sksl/lex/NFAtoDFA.h" +#include "src/sksl/lex/RegexNode.h" +#include "src/sksl/lex/RegexParser.h" +#include "src/sksl/lex/TransitionTable.h" + +#include <stdio.h> +#include <stdlib.h> +#include <algorithm> +#include <sstream> +#include <string> +#include <vector> + +/** + * Processes a .lex file and produces .h and .cpp files which implement a lexical analyzer. The .lex + * file is a text file with one token definition per line. Each line is of the form: + * <TOKEN_NAME> = <pattern> + * where <pattern> is either a regular expression (e.g [0-9]) or a double-quoted literal string. + */ + +static constexpr const char HEADER[] = + "/*\n" + " * Copyright 2017 Google Inc.\n" + " *\n" + " * Use of this source code is governed by a BSD-style license that can be\n" + " * found in the LICENSE file.\n" + " */\n" + "/*****************************************************************************************\n" + " ******************** This file was generated by sksllex. Do not edit. *******************\n" + " *****************************************************************************************/\n"; + +static void writeH(const DFA& dfa, const char* lexer, const char* token, + const std::vector<std::string>& tokens, const char* hPath) { + std::ofstream out(hPath); + SkASSERT(out.good()); + out << HEADER; + out << "#ifndef SKSL_" << lexer << "\n"; + out << "#define SKSL_" << lexer << "\n"; + out << "#include <cstdint>\n"; + out << "#include <string_view>\n"; + out << "namespace SkSL {\n"; + out << "\n"; + out << "struct " << token << " {\n"; + out << " enum class Kind {\n"; + for (const std::string& t : tokens) { + out << " TK_" << t << ",\n"; + } + out << " TK_NONE,"; + out << R"( + }; + + )" << token << "() {}"; + + out << token << R"((Kind kind, int32_t offset, int32_t length) + : fKind(kind) + , fOffset(offset) + , fLength(length) {} + + Kind fKind = Kind::TK_NONE; + int32_t fOffset = -1; + int32_t fLength = -1; +}; + +class )" << lexer << R"( { +public: + void start(std::string_view text) { + fText = text; + fOffset = 0; + } + + )" << token << R"( next(); + + struct Checkpoint { + int32_t fOffset; + }; + + Checkpoint getCheckpoint() const { + return {fOffset}; + } + + void rewindToCheckpoint(Checkpoint checkpoint) { + fOffset = checkpoint.fOffset; + } + +private: + std::string_view fText; + int32_t fOffset; +}; + +} // namespace +#endif +)"; +} + +static void writeCPP(const DFA& dfa, const char* lexer, const char* token, const char* include, + const char* cppPath) { + std::ofstream out(cppPath); + SkASSERT(out.good()); + out << HEADER; + out << "#include \"" << include << "\"\n"; + out << "\n"; + out << "namespace SkSL {\n"; + out << "\n"; + + size_t states = 0; + for (const auto& row : dfa.fTransitions) { + states = std::max(states, row.size()); + } + out << "using State = " << (states <= 256 ? "uint8_t" : "uint16_t") << ";\n"; + + // Find the first character mapped in our DFA. + size_t startChar = 0; + for (; startChar < dfa.fCharMappings.size(); ++startChar) { + if (dfa.fCharMappings[startChar] != 0) { + break; + } + } + + // Arbitrarily-chosen character which is greater than startChar, and should not appear in actual + // input. + SkASSERT(startChar < 18); + out << "static constexpr uint8_t kInvalidChar = 18;"; + out << "static constexpr int8_t kMappings[" << dfa.fCharMappings.size() - startChar << "] = {\n" + " "; + const char* separator = ""; + for (size_t index = startChar; index < dfa.fCharMappings.size(); ++index) { + out << separator << std::to_string(dfa.fCharMappings[index]); + separator = ", "; + } + out << "\n};\n"; + + WriteTransitionTable(out, dfa, states); + + out << "static const int8_t kAccepts[" << states << "] = {"; + for (size_t i = 0; i < states; ++i) { + if (i < dfa.fAccepts.size()) { + out << " " << dfa.fAccepts[i] << ","; + } else { + out << " " << INVALID << ","; + } + } + out << " };\n"; + out << "\n"; + + out << token << " " << lexer << "::next() {"; + out << R"( + // note that we cheat here: normally a lexer needs to worry about the case + // where a token has a prefix which is not itself a valid token - for instance, + // maybe we have a valid token 'while', but 'w', 'wh', etc. are not valid + // tokens. Our grammar doesn't have this property, so we can simplify the logic + // a bit. + int32_t startOffset = fOffset; + State state = 1; + for (;;) { + if (fOffset >= (int32_t)fText.length()) { + if (startOffset == (int32_t)fText.length() || kAccepts[state] == -1) { + return )" << token << "(" << token << R"(::Kind::TK_END_OF_FILE, startOffset, 0); + } + break; + } + uint8_t c = (uint8_t)(fText[fOffset] - )" << startChar << R"(); + if (c >= )" << dfa.fCharMappings.size() - startChar << R"() { + c = kInvalidChar; + } + State newState = get_transition(kMappings[c], state); + if (!newState) { + break; + } + state = newState; + ++fOffset; + } + Token::Kind kind = ()" << token << R"(::Kind) kAccepts[state]; + return )" << token << R"((kind, startOffset, fOffset - startOffset); +} + +} // namespace +)"; +} + +static void process(const char* inPath, const char* lexer, const char* token, const char* hPath, + const char* cppPath) { + NFA nfa; + std::vector<std::string> tokens; + tokens.push_back("END_OF_FILE"); + std::string line; + std::ifstream in(inPath); + while (std::getline(in, line)) { + if (line.length() == 0) { + continue; + } + if (line.length() >= 2 && line[0] == '/' && line[1] == '/') { + continue; + } + std::istringstream split(line); + std::string name, delimiter, pattern; + if (split >> name >> delimiter >> pattern) { + SkASSERT(split.eof()); + SkASSERT(name != ""); + SkASSERT(delimiter == "="); + SkASSERT(pattern != ""); + tokens.push_back(name); + if (pattern[0] == '"') { + SkASSERT(pattern.size() > 2 && pattern[pattern.size() - 1] == '"'); + RegexNode node = RegexNode(RegexNode::kChar_Kind, pattern[1]); + for (size_t i = 2; i < pattern.size() - 1; ++i) { + node = RegexNode(RegexNode::kConcat_Kind, node, + RegexNode(RegexNode::kChar_Kind, pattern[i])); + } + nfa.addRegex(node); + } + else { + nfa.addRegex(RegexParser().parse(pattern)); + } + } + } + NFAtoDFA converter(&nfa); + DFA dfa = converter.convert(); + writeH(dfa, lexer, token, tokens, hPath); + writeCPP(dfa, lexer, token, (std::string("src/sksl/SkSL") + lexer + ".h").c_str(), cppPath); +} + +int main(int argc, const char** argv) { + if (argc != 6) { + printf("usage: sksllex <input.lex> <lexername> <tokenname> <output.h> <output.cpp>\n"); + exit(1); + } + process(argv[1], argv[2], argv[3], argv[4], argv[5]); + return 0; +} diff --git a/gfx/skia/skia/src/sksl/lex/NFA.cpp b/gfx/skia/skia/src/sksl/lex/NFA.cpp new file mode 100644 index 0000000000..e73fc154d7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFA.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/NFA.h" + +#include "src/sksl/lex/LexUtil.h" +#include <string> + +int NFA::match(std::string s) const { + std::vector<int> states = fStartStates; + for (size_t i = 0; i < s.size(); ++i) { + std::vector<int> next; + for (int id : states) { + if (fStates[id].accept(s[i])) { + for (int nextId : fStates[id].fNext) { + if (fStates[nextId].fKind != NFAState::kRemapped_Kind) { + next.push_back(nextId); + } else { + next.insert(next.end(), fStates[nextId].fData.begin(), + fStates[nextId].fData.end()); + } + } + } + } + if (!next.size()) { + return INVALID; + } + states = next; + } + int accept = INVALID; + for (int id : states) { + if (fStates[id].fKind == NFAState::kAccept_Kind) { + int result = fStates[id].fData[0]; + if (accept == INVALID || result < accept) { + accept = result; + } + } + } + return accept; +} diff --git a/gfx/skia/skia/src/sksl/lex/NFA.h b/gfx/skia/skia/src/sksl/lex/NFA.h new file mode 100644 index 0000000000..368fb3ec19 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFA.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_NFA +#define SKSL_NFA + +#include "src/sksl/lex/NFAState.h" +#include "src/sksl/lex/RegexNode.h" + +#include <string> +#include <utility> +#include <vector> + +/** + * A nondeterministic finite automaton for matching regular expressions. The NFA is initialized with + * a number of regular expressions, and then matches a string against all of them simultaneously. + */ +struct NFA { + /** + * Adds a new regular expression to the set of expressions matched by this automaton, returning + * its index. + */ + int addRegex(const RegexNode& regex) { + std::vector<int> accept; + // we reserve token 0 for END_OF_FILE, so this starts at 1 + accept.push_back(this->addState(NFAState(++fRegexCount))); + std::vector<int> startStates = regex.createStates(this, accept); + fStartStates.insert(fStartStates.end(), startStates.begin(), startStates.end()); + return fStartStates.size() - 1; + } + + /** + * Adds a new state to the NFA, returning its index. + */ + int addState(NFAState s) { + fStates.push_back(std::move(s)); + return fStates.size() - 1; + } + + /** + * Matches a string against all of the regexes added to this NFA. Returns the index of the first + * (in addRegex order) matching expression, or -1 if no match. This is relatively slow and used + * only for debugging purposes; the NFA should be converted to a DFA before actual use. + */ + int match(std::string s) const; + + int fRegexCount = 0; + + std::vector<NFAState> fStates; + + std::vector<int> fStartStates; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/NFAState.h b/gfx/skia/skia/src/sksl/lex/NFAState.h new file mode 100644 index 0000000000..848a6f11ee --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFAState.h @@ -0,0 +1,152 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_NFASTATE +#define SKSL_NFASTATE + +#include <string> +#include <vector> + +#include "src/sksl/lex/LexUtil.h" + +struct NFAState { + enum Kind { + // represents an accept state - if the NFA ends up in this state, we have successfully + // matched the token indicated by fData[0] + kAccept_Kind, + // matches the single character fChar + kChar_Kind, + // the regex '.'; matches any char but '\n' + kDot_Kind, + // a state which serves as a placeholder for the states indicated in fData. When we + // transition to this state, we instead transition to all of the fData states. + kRemapped_Kind, + // contains a list of true/false values in fData. fData[c] tells us whether we accept the + // character c. + kTable_Kind + }; + + NFAState(Kind kind, std::vector<int> next) + : fKind(kind) + , fNext(std::move(next)) {} + + NFAState(char c, std::vector<int> next) + : fKind(kChar_Kind) + , fChar(c) + , fNext(std::move(next)) {} + + NFAState(std::vector<int> states) + : fKind(kRemapped_Kind) + , fData(std::move(states)) {} + + NFAState(bool inverse, std::vector<bool> accepts, std::vector<int> next) + : fKind(kTable_Kind) + , fInverse(inverse) + , fNext(std::move(next)) { + for (bool b : accepts) { + fData.push_back(b); + } + } + + NFAState(int token) + : fKind(kAccept_Kind) { + fData.push_back(token); + } + + bool accept(char c) const { + switch (fKind) { + case kAccept_Kind: + return false; + case kChar_Kind: + return c == fChar; + case kDot_Kind: + return c != '\n'; + case kTable_Kind: { + bool value; + if ((size_t) c < fData.size()) { + value = fData[c]; + } else { + value = false; + } + return value != fInverse; + } + default: + SkUNREACHABLE; + } + } + +#ifdef SK_DEBUG + std::string description() const { + switch (fKind) { + case kAccept_Kind: + return "Accept(" + std::to_string(fData[0]) + ")"; + case kChar_Kind: { + std::string result = "Char('" + std::string(1, fChar) + "'"; + for (int v : fNext) { + result += ", "; + result += std::to_string(v); + } + result += ")"; + return result; + } + case kDot_Kind: { + std::string result = "Dot("; + const char* separator = ""; + for (int v : fNext) { + result += separator; + result += std::to_string(v); + separator = ", "; + } + result += ")"; + return result; + } + case kRemapped_Kind: { + std::string result = "Remapped("; + const char* separator = ""; + for (int v : fData) { + result += separator; + result += std::to_string(v); + separator = ", "; + } + result += ")"; + return result; + } + case kTable_Kind: { + std::string result = std::string("Table(") + (fInverse ? "true" : "false") + ", ["; + const char* separator = ""; + for (int v : fData) { + result += separator; + result += v ? "true" : "false"; + separator = ", "; + } + result += "]"; + for (int n : fNext) { + result += ", "; + result += std::to_string(n); + } + result += ")"; + return result; + } + default: + SkUNREACHABLE; + } + } +#endif + + Kind fKind; + + char fChar = 0; + + bool fInverse = false; + + std::vector<int> fData; + + // states we transition to upon a succesful match from this state + std::vector<int> fNext; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h b/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h new file mode 100644 index 0000000000..5331042985 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/NFAtoDFA.h @@ -0,0 +1,168 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef NFAtoDFA_DEFINED +#define NFAtoDFA_DEFINED + +#include "src/sksl/lex/DFA.h" +#include "src/sksl/lex/DFAState.h" +#include "src/sksl/lex/NFA.h" +#include "src/sksl/lex/NFAState.h" + +#include <algorithm> +#include <climits> +#include <memory> +#include <unordered_map> +#include <set> +#include <vector> + +/** + * Converts a nondeterministic finite automaton to a deterministic finite automaton. Since NFAs and + * DFAs differ only in that an NFA allows multiple states at the same time, we can find each + * possible combination of simultaneous NFA states and give this combination a label. These labelled + * nodes are our DFA nodes, since we can only be in one such unique set of NFA states at a time. + * + * As an NFA can end up in multiple accept states at the same time (for instance, the token "while" + * is valid for both WHILE and IDENTIFIER), we disambiguate by preferring the first matching regex + * (in terms of the order in which they were added to the NFA). + */ +class NFAtoDFA { +public: + inline static constexpr char START_CHAR = 9; + inline static constexpr char END_CHAR = 126; + + NFAtoDFA(NFA* nfa) + : fNFA(*nfa) {} + + /** + * Returns a DFA created from the NFA. + */ + DFA convert() { + // create state 0, the "reject" state + getState(DFAState::Label({})); + // create a state representing being in all of the NFA's start states at once + std::vector<int> startStates = fNFA.fStartStates; + std::sort(startStates.begin(), startStates.end()); + // this becomes state 1, our start state + DFAState* start = getState(DFAState::Label(startStates)); + this->scanState(start); + + this->computeMappings(); + + int stateCount = 0; + for (const auto& row : fTransitions) { + stateCount = std::max(stateCount, (int) row.size()); + } + return DFA(fCharMappings, fTransitions, fAccepts); + } + +private: + /** + * Returns an existing state with the given label, or creates a new one and returns it. + */ + DFAState* getState(DFAState::Label label) { + auto found = fStates.find(label); + if (found == fStates.end()) { + int id = fStates.size(); + fStates[label] = std::unique_ptr<DFAState>(new DFAState(id, label)); + return fStates[label].get(); + } + return found->second.get(); + } + + void add(int nfaState, std::vector<int>* states) { + NFAState state = fNFA.fStates[nfaState]; + if (state.fKind == NFAState::kRemapped_Kind) { + for (int next : state.fData) { + this->add(next, states); + } + } else { + for (int entry : *states) { + if (nfaState == entry) { + return; + } + } + states->push_back(nfaState); + } + } + + void addTransition(char c, int start, int next) { + while (fTransitions.size() <= (size_t) c) { + fTransitions.push_back(std::vector<int>()); + } + std::vector<int>& row = fTransitions[c]; + while (row.size() <= (size_t) start) { + row.push_back(INVALID); + } + row[start] = next; + } + + void scanState(DFAState* state) { + state->fIsScanned = true; + for (char c = START_CHAR; c <= END_CHAR; ++c) { + std::vector<int> next; + int bestAccept = INT_MAX; + for (int idx : state->fLabel.fStates) { + const NFAState& nfaState = fNFA.fStates[idx]; + if (nfaState.accept(c)) { + for (int nextState : nfaState.fNext) { + if (fNFA.fStates[nextState].fKind == NFAState::kAccept_Kind) { + bestAccept = std::min(bestAccept, fNFA.fStates[nextState].fData[0]); + } + this->add(nextState, &next); + } + } + } + std::sort(next.begin(), next.end()); + DFAState* nextState = this->getState(DFAState::Label(next)); + this->addTransition(c, state->fId, nextState->fId); + if (bestAccept != INT_MAX) { + while (fAccepts.size() <= (size_t) nextState->fId) { + fAccepts.push_back(INVALID); + } + fAccepts[nextState->fId] = bestAccept; + } + if (!nextState->fIsScanned) { + this->scanState(nextState); + } + } + } + + // collapse rows with the same transitions to a single row. This is common, as each row + // represents a character and often there are many characters for which all transitions are + // identical (e.g. [0-9] are treated the same way by all lexer rules) + void computeMappings() { + // mappings[<input row>] = <output row> + std::vector<std::vector<int>*> uniques; + // this could be done more efficiently, but O(n^2) is plenty fast for our purposes + for (size_t i = 0; i < fTransitions.size(); ++i) { + int found = -1; + for (size_t j = 0; j < uniques.size(); ++j) { + if (*uniques[j] == fTransitions[i]) { + found = j; + break; + } + } + if (found == -1) { + found = (int) uniques.size(); + uniques.push_back(&fTransitions[i]); + } + fCharMappings.push_back(found); + } + std::vector<std::vector<int>> newTransitions; + for (std::vector<int>* row : uniques) { + newTransitions.push_back(*row); + } + fTransitions = newTransitions; + } + + const NFA& fNFA; + std::unordered_map<DFAState::Label, std::unique_ptr<DFAState>> fStates; + std::vector<std::vector<int>> fTransitions; + std::vector<int> fCharMappings; + std::vector<int> fAccepts; +}; +#endif // NFAtoDFA_DEFINED diff --git a/gfx/skia/skia/src/sksl/lex/RegexNode.cpp b/gfx/skia/skia/src/sksl/lex/RegexNode.cpp new file mode 100644 index 0000000000..aa4e23a8b9 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexNode.cpp @@ -0,0 +1,123 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/RegexNode.h" + +#include "src/sksl/lex/LexUtil.h" +#include "src/sksl/lex/NFA.h" +#include "src/sksl/lex/NFAState.h" + +#include <string> + +std::vector<int> RegexNode::createStates(NFA* nfa, const std::vector<int>& accept) const { + std::vector<int> result; + switch (fKind) { + case kChar_Kind: + result.push_back(nfa->addState(NFAState(fPayload.fChar, accept))); + break; + case kCharset_Kind: { + std::vector<bool> chars; + for (const RegexNode& child : fChildren) { + if (child.fKind == kChar_Kind) { + while (chars.size() <= (size_t) child.fPayload.fChar) { + chars.push_back(false); + } + chars[child.fPayload.fChar] = true; + } else { + SkASSERT(child.fKind == kRange_Kind); + while (chars.size() <= (size_t) child.fChildren[1].fPayload.fChar) { + chars.push_back(false); + } + for (char c = child.fChildren[0].fPayload.fChar; + c <= child.fChildren[1].fPayload.fChar; + ++c) { + chars[c] = true; + } + } + } + result.push_back(nfa->addState(NFAState(fPayload.fBool, chars, accept))); + break; + } + case kConcat_Kind: { + std::vector<int> right = fChildren[1].createStates(nfa, accept); + result = fChildren[0].createStates(nfa, right); + break; + } + case kDot_Kind: + result.push_back(nfa->addState(NFAState(NFAState::kDot_Kind, accept))); + break; + case kOr_Kind: { + std::vector<int> states = fChildren[0].createStates(nfa, accept); + result.insert(result.end(), states.begin(), states.end()); + states = fChildren[1].createStates(nfa, accept); + result.insert(result.end(), states.begin(), states.end()); + break; + } + case kPlus_Kind: { + std::vector<int> next = accept; + std::vector<int> placeholder; + int id = nfa->addState(NFAState(placeholder)); + next.push_back(id); + result = fChildren[0].createStates(nfa, next); + nfa->fStates[id] = NFAState(result); + break; + } + case kQuestion_Kind: + result = fChildren[0].createStates(nfa, accept); + result.insert(result.end(), accept.begin(), accept.end()); + break; + case kRange_Kind: + SkUNREACHABLE; + case kStar_Kind: { + std::vector<int> next = accept; + std::vector<int> placeholder; + int id = nfa->addState(NFAState(placeholder)); + next.push_back(id); + result = fChildren[0].createStates(nfa, next); + result.insert(result.end(), accept.begin(), accept.end()); + nfa->fStates[id] = NFAState(result); + break; + } + } + return result; +} + +#ifdef SK_DEBUG +std::string RegexNode::description() const { + switch (fKind) { + case kChar_Kind: + return std::string(1, fPayload.fChar); + case kCharset_Kind: { + std::string result("["); + if (fPayload.fBool) { + result += "^"; + } + for (const RegexNode& c : fChildren) { + result += c.description(); + } + result += "]"; + return result; + } + case kConcat_Kind: + return fChildren[0].description() + fChildren[1].description(); + case kDot_Kind: + return "."; + case kOr_Kind: + return "(" + fChildren[0].description() + "|" + fChildren[1].description() + ")"; + case kPlus_Kind: + return fChildren[0].description() + "+"; + case kQuestion_Kind: + return fChildren[0].description() + "?"; + case kRange_Kind: + return fChildren[0].description() + "-" + fChildren[1].description(); + case kStar_Kind: + return fChildren[0].description() + "*"; + default: + return "<" + std::to_string(fKind) + ">"; + } +} +#endif diff --git a/gfx/skia/skia/src/sksl/lex/RegexNode.h b/gfx/skia/skia/src/sksl/lex/RegexNode.h new file mode 100644 index 0000000000..20c294744e --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexNode.h @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_REGEXNODE +#define SKSL_REGEXNODE + +#include <string> +#include <utility> +#include <vector> + +struct NFA; + +/** + * Represents a node in the parse tree of a regular expression. + */ +struct RegexNode { + enum Kind { + kChar_Kind, + kCharset_Kind, + kConcat_Kind, + kDot_Kind, + kOr_Kind, + kPlus_Kind, + kRange_Kind, + kQuestion_Kind, + kStar_Kind + }; + + RegexNode(Kind kind) + : fKind(kind) {} + + RegexNode(Kind kind, char payload) + : fKind(kind) { + fPayload.fChar = payload; + } + + RegexNode(Kind kind, const char* children) + : fKind(kind) { + fPayload.fBool = false; + while (*children != '\0') { + fChildren.emplace_back(kChar_Kind, *children); + ++children; + } + } + + RegexNode(Kind kind, RegexNode child) + : fKind(kind) { + fChildren.push_back(std::move(child)); + } + + RegexNode(Kind kind, RegexNode child1, RegexNode child2) + : fKind(kind) { + fChildren.push_back(std::move(child1)); + fChildren.push_back(std::move(child2)); + } + + /** + * Creates NFA states for this node, with a successful match against this node resulting in a + * transition to all of the states in the accept vector. + */ + std::vector<int> createStates(NFA* nfa, const std::vector<int>& accept) const; + + std::string description() const; + + Kind fKind; + + union Payload { + char fChar; + bool fBool; + } fPayload; + + std::vector<RegexNode> fChildren; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/RegexParser.cpp b/gfx/skia/skia/src/sksl/lex/RegexParser.cpp new file mode 100644 index 0000000000..27c499e66b --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexParser.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/RegexParser.h" + +#include "src/sksl/lex/LexUtil.h" + +#include <stdio.h> +#include <stdlib.h> +#include <utility> +#include <vector> + +RegexNode RegexParser::parse(std::string source) { + fSource = source; + fIndex = 0; + SkASSERT(fStack.size() == 0); + this->regex(); + SkASSERT(fStack.size() == 1); + SkASSERT(fIndex == source.size()); + return this->pop(); +} + +char RegexParser::peek() { + if (fIndex >= fSource.size()) { + return END; + } + return fSource[fIndex]; +} + +void RegexParser::expect(char c) { + if (this->peek() != c) { + printf("expected '%c' at index %d, but found '%c'", c, (int) fIndex, this->peek()); + exit(1); + } + ++fIndex; +} + +RegexNode RegexParser::pop() { + RegexNode result = fStack.top(); + fStack.pop(); + return result; +} + +void RegexParser::term() { + switch (this->peek()) { + case '(': this->group(); break; + case '[': this->set(); break; + case '.': this->dot(); break; + default: this->literal(); break; + } +} + +void RegexParser::quantifiedTerm() { + this->term(); + switch (this->peek()) { + case '*': fStack.push(RegexNode(RegexNode::kStar_Kind, this->pop())); ++fIndex; break; + case '+': fStack.push(RegexNode(RegexNode::kPlus_Kind, this->pop())); ++fIndex; break; + case '?': fStack.push(RegexNode(RegexNode::kQuestion_Kind, this->pop())); ++fIndex; break; + default: break; + } +} + +void RegexParser::sequence() { + this->quantifiedTerm(); + for (;;) { + switch (this->peek()) { + case END: [[fallthrough]]; + case '|': [[fallthrough]]; + case ')': return; + default: + this->sequence(); + RegexNode right = this->pop(); + RegexNode left = this->pop(); + fStack.emplace(RegexNode::kConcat_Kind, std::move(left), std::move(right)); + break; + } + } +} + +RegexNode RegexParser::escapeSequence(char c) { + switch (c) { + case 'n': return RegexNode(RegexNode::kChar_Kind, '\n'); + case 'r': return RegexNode(RegexNode::kChar_Kind, '\r'); + case 't': return RegexNode(RegexNode::kChar_Kind, '\t'); + case 's': return RegexNode(RegexNode::kCharset_Kind, " \t\n\r"); + default: return RegexNode(RegexNode::kChar_Kind, c); + } +} + +void RegexParser::literal() { + char c = this->peek(); + if (c == '\\') { + ++fIndex; + fStack.push(this->escapeSequence(peek())); + ++fIndex; + } + else { + fStack.push(RegexNode(RegexNode::kChar_Kind, c)); + ++fIndex; + } +} + +void RegexParser::dot() { + this->expect('.'); + fStack.push(RegexNode(RegexNode::kDot_Kind)); +} + +void RegexParser::group() { + this->expect('('); + this->regex(); + this->expect(')'); +} + +void RegexParser::setItem() { + this->literal(); + if (this->peek() == '-') { + ++fIndex; + if (peek() == ']') { + fStack.push(RegexNode(RegexNode::kChar_Kind, '-')); + } + else { + literal(); + RegexNode end = this->pop(); + SkASSERT(end.fKind == RegexNode::kChar_Kind); + RegexNode start = this->pop(); + SkASSERT(start.fKind == RegexNode::kChar_Kind); + fStack.push(RegexNode(RegexNode::kRange_Kind, std::move(start), std::move(end))); + } + } +} + +void RegexParser::set() { + expect('['); + size_t depth = fStack.size(); + RegexNode set(RegexNode::kCharset_Kind); + if (this->peek() == '^') { + ++fIndex; + set.fPayload.fBool = true; + } + else { + set.fPayload.fBool = false; + } + for (;;) { + switch (this->peek()) { + case ']': + ++fIndex; + while (fStack.size() > depth) { + set.fChildren.push_back(this->pop()); + } + fStack.push(std::move(set)); + return; + case END: + printf("unterminated character set\n"); + exit(1); + default: + this->setItem(); + break; + } + } +} + +void RegexParser::regex() { + this->sequence(); + switch (this->peek()) { + case '|': { + ++fIndex; + this->regex(); + RegexNode right = this->pop(); + RegexNode left = this->pop(); + fStack.push(RegexNode(RegexNode::kOr_Kind, left, right)); + break; + } + case END: // fall through + case ')': + return; + default: + SkASSERT(false); + } +} diff --git a/gfx/skia/skia/src/sksl/lex/RegexParser.h b/gfx/skia/skia/src/sksl/lex/RegexParser.h new file mode 100644 index 0000000000..b8f4f1ffb4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/RegexParser.h @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_REGEXPARSER +#define SKSL_REGEXPARSER + +#include "src/sksl/lex/RegexNode.h" + +#include <stack> +#include <string> + +/** + * Turns a simple regular expression into a parse tree. The regular expression syntax supports only + * the basic quantifiers ('*', '+', and '?'), alternation ('|'), character sets ('[a-z]'), and + * groups ('()'). + */ +class RegexParser { +public: + RegexNode parse(std::string source); + +private: + inline static constexpr char END = '\0'; + + char peek(); + + void expect(char c); + + RegexNode pop(); + + /** + * Matches a char literal, parenthesized group, character set, or dot ('.'). + */ + void term(); + + /** + * Matches a term followed by an optional quantifier ('*', '+', or '?'). + */ + void quantifiedTerm(); + + /** + * Matches a sequence of quantifiedTerms. + */ + void sequence(); + + /** + * Returns a node representing the given escape character (e.g. escapeSequence('n') returns a + * node which matches a newline character). + */ + RegexNode escapeSequence(char c); + + /** + * Matches a literal character or escape sequence. + */ + void literal(); + + /** + * Matches a dot ('.'). + */ + void dot(); + + /** + * Matches a parenthesized group. + */ + void group(); + + /** + * Matches a literal character, escape sequence, or character range from a character set. + */ + void setItem(); + + /** + * Matches a character set. + */ + void set(); + + void regex(); + + std::string fSource; + + size_t fIndex; + + std::stack<RegexNode> fStack; +}; + +#endif diff --git a/gfx/skia/skia/src/sksl/lex/TransitionTable.cpp b/gfx/skia/skia/src/sksl/lex/TransitionTable.cpp new file mode 100644 index 0000000000..83720fca1c --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/TransitionTable.cpp @@ -0,0 +1,241 @@ +/* + * Copyright 2021 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/lex/DFA.h" +#include "src/sksl/lex/TransitionTable.h" + +#include <array> +#include <algorithm> +#include <cassert> +#include <cmath> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace { + +// The number of bits to use per entry in our compact transition table. This is customizable: +// - 1-bit: reasonable in theory. Doesn't actually pack many slices. +// - 2-bit: best fit for our data. Packs extremely well. +// - 4-bit: packs all but one slice, but doesn't save as much space overall. +// - 8-bit: way too large (an 8-bit LUT plus an 8-bit data table is as big as a 16-bit table) +// Other values don't divide cleanly into a byte and do not work. +constexpr int kNumBits = 2; + +// These values are derived from kNumBits and shouldn't need to change. +constexpr int kNumValues = (1 << kNumBits) - 1; +constexpr int kDataPerByte = 8 / kNumBits; + +enum IndexType { + kCompactEntry = 0, + kFullEntry, +}; +struct IndexEntry { + IndexType type; + int pos; +}; +struct CompactEntry { + std::array<int, kNumValues> v = {}; + std::vector<int> data; +}; +struct FullEntry { + std::vector<int> data; +}; + +using TransitionSet = std::unordered_set<int>; + +static int add_compact_entry(const TransitionSet& transitionSet, + const std::vector<int>& data, + std::vector<CompactEntry>* entries) { + // Create a compact entry with the unique values from the transition set, padded out with zeros + // and sorted. + CompactEntry result{}; + assert(transitionSet.size() <= result.v.size()); + std::copy(transitionSet.begin(), transitionSet.end(), result.v.begin()); + std::sort(result.v.rbegin(), result.v.rend()); + + // Create a mapping from real values to small values. + std::unordered_map<int, int> translationTable; + for (size_t index = 0; index < result.v.size(); ++index) { + translationTable[result.v[index]] = index; + } + translationTable[0] = result.v.size(); + + // Convert the real values into small values. + for (size_t index = 0; index < data.size(); ++index) { + int value = data[index]; + assert(translationTable.find(value) != translationTable.end()); + result.data.push_back(translationTable[value]); + } + + // Look for an existing entry that exactly matches this one. + for (size_t index = 0; index < entries->size(); ++index) { + if (entries->at(index).v == result.v && entries->at(index).data == result.data) { + return index; + } + } + + // Add this as a new entry. + entries->push_back(std::move(result)); + return (int)(entries->size() - 1); +} + +static int add_full_entry(const TransitionSet& transitionMap, + const std::vector<int>& data, + std::vector<FullEntry>* entries) { + // Create a full entry with this data. + FullEntry result{}; + result.data = std::vector<int>(data.begin(), data.end()); + + // Look for an existing entry that exactly matches this one. + for (size_t index = 0; index < entries->size(); ++index) { + if (entries->at(index).data == result.data) { + return index; + } + } + + // Add this as a new entry. + entries->push_back(std::move(result)); + return (int)(entries->size() - 1); +} + +} // namespace + +void WriteTransitionTable(std::ofstream& out, const DFA& dfa, size_t states) { + int numTransitions = dfa.fTransitions.size(); + + // Assemble our compact and full data tables, and an index into them. + std::vector<CompactEntry> compactEntries; + std::vector<FullEntry> fullEntries; + std::vector<IndexEntry> indices; + for (size_t s = 0; s < states; ++s) { + // Copy all the transitions for this state into a flat array, and into a histogram (counting + // the number of unique state-transition values). Most states only transition to a few + // possible new states. + TransitionSet transitionSet; + std::vector<int> data(numTransitions); + for (int t = 0; t < numTransitions; ++t) { + if ((size_t) t < dfa.fTransitions.size() && s < dfa.fTransitions[t].size()) { + int value = dfa.fTransitions[t][s]; + assert(value >= 0 && value < (int)states); + data[t] = value; + transitionSet.insert(value); + } + } + + transitionSet.erase(0); + if (transitionSet.size() <= kNumValues) { + // This table only contained a small number of unique nonzero values. + // Use a compact representation that squishes each value down to a few bits. + int index = add_compact_entry(transitionSet, data, &compactEntries); + indices.push_back(IndexEntry{kCompactEntry, index}); + } else { + // This table contained a large number of values. We can't compact it. + int index = add_full_entry(transitionSet, data, &fullEntries); + indices.push_back(IndexEntry{kFullEntry, index}); + } + } + + // Find the largest value for each compact-entry slot. + int maxValue = 0; + for (const CompactEntry& entry : compactEntries) { + for (int index=0; index < kNumValues; ++index) { + maxValue = std::max(maxValue, entry.v[index]); + } + } + + // Figure out how many bits we need to store our max value. + int bitsPerValue = std::ceil(std::log2(maxValue)); + maxValue = (1 << bitsPerValue) - 1; + + // If we exceed 10 bits per value, three values would overflow 32 bits. If this happens, we'll + // need to pack our values another way. + assert(bitsPerValue <= 10); + + // Emit all the structs our transition table will use. + out << "using IndexEntry = int16_t;\n" + << "struct FullEntry {\n" + << " State data[" << numTransitions << "];\n" + << "};\n"; + + // Emit the compact-entry structure. We store all three values in `v`. If kNumBits were to + // change, we would need to adjust the packing algorithm. + static_assert(kNumBits == 2); + out << "struct CompactEntry {\n" + << " uint32_t values;\n" + << " uint8_t data[" << std::ceil(float(numTransitions) / float(kDataPerByte)) << "];\n" + << "};\n"; + + // Emit the full-table data. + out << "static constexpr FullEntry kFull[] = {\n"; + for (const FullEntry& entry : fullEntries) { + out << " {"; + for (int value : entry.data) { + out << value << ", "; + } + out << "},\n"; + } + out << "};\n"; + + // Emit the compact-table data. + out << "static constexpr CompactEntry kCompact[] = {\n"; + for (const CompactEntry& entry : compactEntries) { + out << " {"; + + // We pack all three values into `v`. If kNumBits were to change, we would need to adjust + // this packing algorithm. + static_assert(kNumBits == 2); + out << entry.v[0]; + if (entry.v[1]) { + out << " | (" << entry.v[1] << " << " << bitsPerValue << ")"; + } + if (entry.v[2]) { + out << " | (" << entry.v[2] << " << " << (2 * bitsPerValue) << ")"; + } + out << ", {"; + + unsigned int shiftBits = 0, combinedBits = 0; + for (int index = 0; index < numTransitions; index++) { + combinedBits |= entry.data[index] << shiftBits; + shiftBits += kNumBits; + assert(shiftBits <= 8); + if (shiftBits == 8) { + out << combinedBits << ", "; + shiftBits = 0; + combinedBits = 0; + } + } + if (shiftBits > 0) { + // Flush any partial values. + out << combinedBits; + } + out << "}},\n"; + } + out << "};\n" + << "static constexpr IndexEntry kIndices[] = {\n"; + for (const IndexEntry& entry : indices) { + if (entry.type == kFullEntry) { + // Bit-not is used so that full entries start at -1 and go down from there. + out << ~entry.pos << ", "; + } else { + // Compact entries start at 0 and go up from there. + out << entry.pos << ", "; + } + } + out << "};\n" + << "State get_transition(int transition, int state) {\n" + << " IndexEntry index = kIndices[state];\n" + << " if (index < 0) { return kFull[~index].data[transition]; }\n" + << " const CompactEntry& entry = kCompact[index];\n" + << " int v = entry.data[transition >> " << std::log2(kDataPerByte) << "];\n" + << " v >>= " << kNumBits << " * (transition & " << kDataPerByte - 1 << ");\n" + << " v &= " << kNumValues << ";\n" + << " v *= " << bitsPerValue << ";\n" + << " return (entry.values >> v) & " << maxValue << ";\n" + << "}\n"; +} diff --git a/gfx/skia/skia/src/sksl/lex/TransitionTable.h b/gfx/skia/skia/src/sksl/lex/TransitionTable.h new file mode 100644 index 0000000000..6ac6986fae --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/TransitionTable.h @@ -0,0 +1,18 @@ +/* + * Copyright 2021 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_TRANSITIONTABLE +#define SKSL_TRANSITIONTABLE + +#include <stddef.h> +#include <fstream> + +struct DFA; + +void WriteTransitionTable(std::ofstream& out, const DFA& dfa, size_t states); + +#endif // SKSL_TRANSITIONTABLE diff --git a/gfx/skia/skia/src/sksl/lex/sksl.lex b/gfx/skia/skia/src/sksl/lex/sksl.lex new file mode 100644 index 0000000000..02a946a350 --- /dev/null +++ b/gfx/skia/skia/src/sksl/lex/sksl.lex @@ -0,0 +1,102 @@ +// ***************** +// *** IMPORTANT *** +// ***************** +// +// 1. This file is only used when gn arg skia_lex is set to true. It is used to regenerate the +// SkSLLexer.h and SkSLLexer.cpp files. +// 2. Since token IDs are used to identify operators and baked into the .dehydrated.sksl files, +// after modifying this file it is likely everything will break until you update the dehydrated +// binaries. If things break after updating the lexer, set REHYDRATE in SkSLCompiler.cpp to 0, +// rebuild, and then set it back to 1. + +FLOAT_LITERAL = [0-9]*\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?|[0-9]+([eE][+-]?[0-9]+) +INT_LITERAL = ([1-9][0-9]*|0[0-7]*|0[xX][0-9a-fA-F]+)[uU]? +BAD_OCTAL = (0[0-9]+)[uU]? +TRUE_LITERAL = "true" +FALSE_LITERAL = "false" +IF = "if" +ELSE = "else" +FOR = "for" +WHILE = "while" +DO = "do" +SWITCH = "switch" +CASE = "case" +DEFAULT = "default" +BREAK = "break" +CONTINUE = "continue" +DISCARD = "discard" +RETURN = "return" +IN = "in" +OUT = "out" +INOUT = "inout" +UNIFORM = "uniform" +CONST = "const" +FLAT = "flat" +NOPERSPECTIVE = "noperspective" +INLINE = "inline" +NOINLINE = "noinline" +PURE = "$pure" +READONLY = "readonly" +WRITEONLY = "writeonly" +BUFFER = "buffer" +STRUCT = "struct" +LAYOUT = "layout" +HIGHP = "highp" +MEDIUMP = "mediump" +LOWP = "lowp" +ES3 = "$es3" +EXPORT = "$export" +WORKGROUP = "workgroup" +RESERVED = atomic|attribute|varying|precision|invariant|asm|class|union|enum|typedef|template|this|packed|goto|volatile|public|static|extern|external|interface|long|double|fixed|unsigned|superp|input|output|hvec[234]|dvec[234]|fvec[234]|sampler[13]D|sampler[12]DShadow|sampler3DRect|sampler2DRectShadow|samplerCube|sizeof|cast|namespace|using|gl_[0-9a-zA-Z_]* +PRIVATE_IDENTIFIER = $[0-9a-zA-Z_]* +IDENTIFIER = [a-zA-Z_][0-9a-zA-Z_]* +DIRECTIVE = #[a-zA-Z_][0-9a-zA-Z_]* +LPAREN = "(" +RPAREN = ")" +LBRACE = "{" +RBRACE = "}" +LBRACKET = "[" +RBRACKET = "]" +DOT = "." +COMMA = "," +PLUSPLUS = "++" +MINUSMINUS = "--" +PLUS = "+" +MINUS = "-" +STAR = "*" +SLASH = "/" +PERCENT = "%" +SHL = "<<" +SHR = ">>" +BITWISEOR = "|" +BITWISEXOR = "^" +BITWISEAND = "&" +BITWISENOT = "~" +LOGICALOR = "||" +LOGICALXOR = "^^" +LOGICALAND = "&&" +LOGICALNOT = "!" +QUESTION = "?" +COLON = ":" +EQ = "=" +EQEQ = "==" +NEQ = "!=" +GT = ">" +LT = "<" +GTEQ = ">=" +LTEQ = "<=" +PLUSEQ = "+=" +MINUSEQ = "-=" +STAREQ = "*=" +SLASHEQ = "/=" +PERCENTEQ = "%=" +SHLEQ = "<<=" +SHREQ = ">>=" +BITWISEOREQ = "|=" +BITWISEXOREQ = "^=" +BITWISEANDEQ = "&=" +SEMICOLON = ";" +WHITESPACE = \s+ +LINE_COMMENT = //.* +BLOCK_COMMENT = /\*([^*]|\*[^/])*\*/ +INVALID = . diff --git a/gfx/skia/skia/src/sksl/sksl_compute.sksl b/gfx/skia/skia/src/sksl/sksl_compute.sksl new file mode 100644 index 0000000000..b06cb7b38b --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_compute.sksl @@ -0,0 +1,21 @@ +// defines built-in interfaces supported by SkSL compute shaders + +layout(builtin=24) in uint3 sk_NumWorkgroups; +layout(builtin=26) in uint3 sk_WorkgroupID; +layout(builtin=27) in uint3 sk_LocalInvocationID; +layout(builtin=28) in uint3 sk_GlobalInvocationID; +layout(builtin=29) in uint sk_LocalInvocationIndex; + +$pure half4 read($readableTexture2D t, uint2 pos); +void write($writableTexture2D t, uint2 pos, half4 color); + +$pure uint width($genTexture2D t); +$pure uint height($genTexture2D t); + +// Control-barrier with memory-ordering constraints applied to +// workgroup shared memory only. +void workgroupBarrier(); + +// Control-barrier with memory-ordering constraints applied to +// uniform and storage-buffer memory. +void storageBarrier(); diff --git a/gfx/skia/skia/src/sksl/sksl_frag.sksl b/gfx/skia/skia/src/sksl/sksl_frag.sksl new file mode 100644 index 0000000000..aa1e09d645 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_frag.sksl @@ -0,0 +1,9 @@ +// defines built-in interfaces supported by SkSL fragment shaders + +// See "enum SpvBuiltIn_" in ./spirv.h +layout(builtin=15) in float4 sk_FragCoord; +layout(builtin=17) in bool sk_Clockwise; // Similar to gl_FrontFacing, but defined in device space. + +layout(location=0,index=0,builtin=10001) out half4 sk_FragColor; +layout(builtin=10008) half4 sk_LastFragColor; +layout(builtin=10012) out half4 sk_SecondaryFragColor; diff --git a/gfx/skia/skia/src/sksl/sksl_gpu.sksl b/gfx/skia/skia/src/sksl/sksl_gpu.sksl new file mode 100644 index 0000000000..80ac013a55 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_gpu.sksl @@ -0,0 +1,324 @@ +// Not exposed in shared module + +$pure $genIType mix($genIType x, $genIType y, $genBType a); +$pure $genBType mix($genBType x, $genBType y, $genBType a); +$pure $genType fma($genType a, $genType b, $genType c); +$pure $genHType fma($genHType a, $genHType b, $genHType c); + $genType frexp($genType x, out $genIType exp); + $genHType frexp($genHType x, out $genIType exp); +$pure $genType ldexp($genType x, in $genIType exp); +$pure $genHType ldexp($genHType x, in $genIType exp); + +$pure uint packSnorm2x16(float2 v); +$pure uint packUnorm4x8(float4 v); +$pure uint packSnorm4x8(float4 v); +$pure float2 unpackSnorm2x16(uint p); +$pure float4 unpackUnorm4x8(uint p); +$pure float4 unpackSnorm4x8(uint p); +$pure uint packHalf2x16(float2 v); +$pure float2 unpackHalf2x16(uint v); + +$pure $genIType bitCount($genIType value); +$pure $genIType bitCount($genUType value); +$pure $genIType findLSB($genIType value); +$pure $genIType findLSB($genUType value); +$pure $genIType findMSB($genIType value); +$pure $genIType findMSB($genUType value); + +$pure sampler2D makeSampler2D(texture2D texture, sampler s); + +$pure half4 sample(sampler2D s, float2 P); +$pure half4 sample(sampler2D s, float3 P); +$pure half4 sample(sampler2D s, float3 P, float bias); + +$pure half4 sample(samplerExternalOES s, float2 P); +$pure half4 sample(samplerExternalOES s, float2 P, float bias); + +$pure half4 sample(sampler2DRect s, float2 P); +$pure half4 sample(sampler2DRect s, float3 P); + +$pure half4 sampleLod(sampler2D s, float2 P, float lod); +$pure half4 sampleLod(sampler2D s, float3 P, float lod); + +$pure half4 sampleGrad(sampler2D s, float2, float2 dPdx, float2 dPdy); + +// Currently we do not support the generic types of loading subpassInput so we have some explicit +// versions that we currently use +$pure half4 subpassLoad(subpassInput subpass); +$pure half4 subpassLoad(subpassInputMS subpass, int sample); + +/** Atomically loads the value from `a` and returns it. */ +$pure uint atomicLoad(atomicUint a); + +/** Atomically stores the value of `value` to `a` */ +void atomicStore(atomicUint a, uint value); + +/** + * Performs an atomic addition of `value` to the contents of `a` and returns the original contents + * of `a` from before the addition occurred. + */ +uint atomicAdd(atomicUint a, uint value); + +// Definitions of functions implementing all of the SkBlendMode blends. + +$pure half4 blend_clear(half4 src, half4 dst) { return half4(0); } + +$pure half4 blend_src(half4 src, half4 dst) { return src; } + +$pure half4 blend_dst(half4 src, half4 dst) { return dst; } + +$pure half4 blend_src_over(half4 src, half4 dst) { return src + (1 - src.a)*dst; } + +$pure half4 blend_dst_over(half4 src, half4 dst) { return (1 - dst.a)*src + dst; } + +$pure half4 blend_src_in(half4 src, half4 dst) { return src*dst.a; } + +$pure half4 blend_dst_in(half4 src, half4 dst) { return dst*src.a; } + +$pure half4 blend_src_out(half4 src, half4 dst) { return (1 - dst.a)*src; } + +$pure half4 blend_dst_out(half4 src, half4 dst) { return (1 - src.a)*dst; } + +$pure half4 blend_src_atop(half4 src, half4 dst) { return dst.a*src + (1 - src.a)*dst; } + +$pure half4 blend_dst_atop(half4 src, half4 dst) { return (1 - dst.a) * src + src.a*dst; } + +$pure half4 blend_xor(half4 src, half4 dst) { return (1 - dst.a)*src + (1 - src.a)*dst; } + +$pure half4 blend_plus(half4 src, half4 dst) { return min(src + dst, 1); } + +// This multi-purpose Porter-Duff blend function can perform any of the thirteen blends above, +// when passed one of the following values for BlendOp: +// - Clear: half4(0, 0, 0, 0) +// - Src: half4(1, 0, 0, 0) +// - Dst: half4(0, 1, 0, 0) +// - SrcOver: half4(1, 0, 0, -1) +// - DstOver: half4(0, 1, -1, 0) +// - SrcIn: half4(0, 0, 1, 0) +// - DstIn: half4(0, 0, 0, 1) +// - SrcOut: half4(0, 0, -1, 0) +// - DstOut: half4(0, 0, 0, -1) +// - SrcATop: half4(0, 0, 1, -1) +// - DstATop: half4(0, 0, -1, 1) +// - Xor: half4(0, 0, -1, -1) +// - Plus: half4(1, 1, 0, 0) +$pure half4 blend_porter_duff(half4 blendOp, half4 src, half4 dst) { + half2 coeff = blendOp.xy + (blendOp.zw * (half2(dst.a, src.a) + min(blendOp.zw, 0))); + return min(half4(1), src * coeff.x + dst * coeff.y); +} + +$pure half4 blend_modulate(half4 src, half4 dst) { return src*dst; } + +$pure half4 blend_screen(half4 src, half4 dst) { return src + (1 - src)*dst; } + +$pure half $blend_overlay_component(half2 s, half2 d) { + return (2*d.x <= d.y) ? 2*s.x*d.x + : s.y*d.y - 2*(d.y - d.x)*(s.y - s.x); +} + +$pure half4 blend_overlay(half4 src, half4 dst) { + half4 result = half4($blend_overlay_component(src.ra, dst.ra), + $blend_overlay_component(src.ga, dst.ga), + $blend_overlay_component(src.ba, dst.ba), + src.a + (1 - src.a)*dst.a); + result.rgb += dst.rgb*(1 - src.a) + src.rgb*(1 - dst.a); + return result; +} + +$pure half4 blend_overlay(half flip, half4 a, half4 b) { + return blend_overlay(bool(flip) ? b : a, bool(flip) ? a : b); +} + +$pure half4 blend_lighten(half4 src, half4 dst) { + half4 result = blend_src_over(src, dst); + result.rgb = max(result.rgb, (1 - dst.a)*src.rgb + dst.rgb); + return result; +} + +$pure half4 blend_darken(half mode /* darken: 1, lighten: -1 */, half4 src, half4 dst) { + half4 a = blend_src_over(src, dst); + half3 b = (1 - dst.a) * src.rgb + dst.rgb; // DstOver.rgb + a.rgb = mode * min(a.rgb * mode, b.rgb * mode); + return a; +} + +$pure half4 blend_darken(half4 src, half4 dst) { + return blend_darken(1, src, dst); +} + +const half $kGuardedDivideEpsilon = sk_Caps.mustGuardDivisionEvenAfterExplicitZeroCheck + ? 0.00000001 + : 0.0; + +$pure inline half $guarded_divide(half n, half d) { + return n / (d + $kGuardedDivideEpsilon); +} + +$pure inline half3 $guarded_divide(half3 n, half d) { + return n / (d + $kGuardedDivideEpsilon); +} + +$pure half $color_dodge_component(half2 s, half2 d) { + if (d.x == 0) { + return s.x*(1 - d.y); + } else { + half delta = s.y - s.x; + if (delta == 0) { + return s.y*d.y + s.x*(1 - d.y) + d.x*(1 - s.y); + } else { + delta = min(d.y, $guarded_divide(d.x*s.y, delta)); + return delta*s.y + s.x*(1 - d.y) + d.x*(1 - s.y); + } + } +} + +$pure half4 blend_color_dodge(half4 src, half4 dst) { + return half4($color_dodge_component(src.ra, dst.ra), + $color_dodge_component(src.ga, dst.ga), + $color_dodge_component(src.ba, dst.ba), + src.a + (1 - src.a)*dst.a); +} + +$pure half $color_burn_component(half2 s, half2 d) { + if (d.y == d.x) { + return s.y*d.y + s.x*(1 - d.y) + d.x*(1 - s.y); + } else if (s.x == 0) { + return d.x*(1 - s.y); + } else { + half delta = max(0, d.y - $guarded_divide((d.y - d.x)*s.y, s.x)); + return delta*s.y + s.x*(1 - d.y) + d.x*(1 - s.y); + } +} + +$pure half4 blend_color_burn(half4 src, half4 dst) { + return half4($color_burn_component(src.ra, dst.ra), + $color_burn_component(src.ga, dst.ga), + $color_burn_component(src.ba, dst.ba), + src.a + (1 - src.a)*dst.a); +} + +$pure half4 blend_hard_light(half4 src, half4 dst) { + return blend_overlay(dst, src); +} + +$pure half $soft_light_component(half2 s, half2 d) { + if (2*s.x <= s.y) { + return $guarded_divide(d.x*d.x*(s.y - 2*s.x), d.y) + (1 - d.y)*s.x + d.x*(-s.y + 2*s.x + 1); + } else if (4.0 * d.x <= d.y) { + half DSqd = d.x*d.x; + half DCub = DSqd*d.x; + half DaSqd = d.y*d.y; + half DaCub = DaSqd*d.y; + return $guarded_divide(DaSqd*(s.x - d.x*(3*s.y - 6*s.x - 1)) + 12*d.y*DSqd*(s.y - 2*s.x) + - 16*DCub * (s.y - 2*s.x) - DaCub*s.x, DaSqd); + } else { + return d.x*(s.y - 2*s.x + 1) + s.x - sqrt(d.y*d.x)*(s.y - 2*s.x) - d.y*s.x; + } +} + +$pure half4 blend_soft_light(half4 src, half4 dst) { + return (dst.a == 0) ? src : half4($soft_light_component(src.ra, dst.ra), + $soft_light_component(src.ga, dst.ga), + $soft_light_component(src.ba, dst.ba), + src.a + (1 - src.a)*dst.a); +} + +$pure half4 blend_difference(half4 src, half4 dst) { + return half4(src.rgb + dst.rgb - 2*min(src.rgb*dst.a, dst.rgb*src.a), + src.a + (1 - src.a)*dst.a); +} + +$pure half4 blend_exclusion(half4 src, half4 dst) { + return half4(dst.rgb + src.rgb - 2*dst.rgb*src.rgb, src.a + (1 - src.a)*dst.a); +} + +$pure half4 blend_multiply(half4 src, half4 dst) { + return half4((1 - src.a)*dst.rgb + (1 - dst.a)*src.rgb + src.rgb*dst.rgb, + src.a + (1 - src.a)*dst.a); +} + +$pure half $blend_color_luminance(half3 color) { return dot(half3(0.3, 0.59, 0.11), color); } + +$pure half3 $blend_set_color_luminance(half3 hueSatColor, half alpha, half3 lumColor) { + half lum = $blend_color_luminance(lumColor); + half3 result = lum - $blend_color_luminance(hueSatColor) + hueSatColor; + half minComp = min(min(result.r, result.g), result.b); + half maxComp = max(max(result.r, result.g), result.b); + if (minComp < 0 && lum != minComp) { + result = lum + (result - lum) * $guarded_divide(lum, (lum - minComp)); + } + if (maxComp > alpha && maxComp != lum) { + result = lum + $guarded_divide((result - lum) * (alpha - lum), (maxComp - lum)); + } + return result; +} + +$pure half $blend_color_saturation(half3 color) { + return max(max(color.r, color.g), color.b) - min(min(color.r, color.g), color.b); +} + +$pure half3 $blend_set_color_saturation(half3 color, half3 satColor) { + half mn = min(min(color.r, color.g), color.b); + half mx = max(max(color.r, color.g), color.b); + + return (mx > mn) ? ((color - mn) * $blend_color_saturation(satColor)) / (mx - mn) + : half3(0); +} + +$pure half4 blend_hslc(half2 flipSat, half4 src, half4 dst) { + half alpha = dst.a * src.a; + half3 sda = src.rgb * dst.a; + half3 dsa = dst.rgb * src.a; + half3 l = bool(flipSat.x) ? dsa : sda; + half3 r = bool(flipSat.x) ? sda : dsa; + if (bool(flipSat.y)) { + l = $blend_set_color_saturation(l, r); + r = dsa; + } + return half4($blend_set_color_luminance(l, alpha, r) + dst.rgb - dsa + src.rgb - sda, + src.a + dst.a - alpha); +} + +$pure half4 blend_hue(half4 src, half4 dst) { + return blend_hslc(half2(0, 1), src, dst); +} + +$pure half4 blend_saturation(half4 src, half4 dst) { + return blend_hslc(half2(1), src, dst); +} + +$pure half4 blend_color(half4 src, half4 dst) { + return blend_hslc(half2(0), src, dst); +} + +$pure half4 blend_luminosity(half4 src, half4 dst) { + return blend_hslc(half2(1, 0), src, dst); +} + +$pure float2 proj(float3 p) { return p.xy / p.z; } + +// Implement cross() as a determinant to communicate our intent more clearly to the compiler. +// NOTE: Due to precision issues, it might be the case that cross(a, a) != 0. +$pure float cross_length_2d(float2 a, float2 b) { + return determinant(float2x2(a, b)); +} + +$pure half cross_length_2d(half2 a, half2 b) { + return determinant(half2x2(a, b)); +} + +$pure float2 perp(float2 v) { + return float2(-v.y, v.x); +} + +$pure half2 perp(half2 v) { + return half2(-v.y, v.x); +} + +// Returns a bias given a scale factor, such that 'scale * (dist + bias)' converts the distance to +// a per-pixel coverage value, automatically widening the visible coverage ramp for subpixel +// dimensions. The 'scale' must already be equal to the narrowest dimension of the shape and clamped +// to [0, 1.0]. +$pure float coverage_bias(float scale) { + return 1.0 - 0.5 * scale; +} diff --git a/gfx/skia/skia/src/sksl/sksl_graphite_frag.sksl b/gfx/skia/skia/src/sksl/sksl_graphite_frag.sksl new file mode 100644 index 0000000000..7cd6080959 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_graphite_frag.sksl @@ -0,0 +1,1135 @@ +// Graphite-specific fragment shader code + +const int $kTileModeClamp = 0; +const int $kTileModeRepeat = 1; +const int $kTileModeMirror = 2; +const int $kTileModeDecal = 3; + +const int $kReadSwizzleNormalRGBA = 0; +const int $kReadSwizzleRGB1 = 1; +const int $kReadSwizzleRRRR = 2; +const int $kReadSwizzleRRR1 = 3; +const int $kReadSwizzleBGRA = 4; + +const int $kFilterModeNearest = 0; +const int $kFilterModeLinear = 1; + +const int $kTFTypeSRGB = 1; +const int $kTFTypePQ = 2; +const int $kTFTypeHLG = 3; +const int $kTFTypeHLGinv = 4; + +const int $kColorSpaceXformFlagUnpremul = 0x1; +const int $kColorSpaceXformFlagLinearize = 0x2; +const int $kColorSpaceXformFlagGamutTransform = 0x4; +const int $kColorSpaceXformFlagEncode = 0x8; +const int $kColorSpaceXformFlagPremul = 0x10; + +$pure half4 sk_error() { + return half4(1.0, 0.0, 1.0, 1.0); +} + +$pure half4 sk_passthrough(half4 color) { + return color; +} + +$pure half4 sk_solid_shader(float4 colorParam) { + return half4(colorParam); +} + +$pure half4 $apply_swizzle(int swizzleType, half4 color) { + half4 resultantColor = color; + switch (swizzleType) { + case $kReadSwizzleNormalRGBA: + break; + case $kReadSwizzleRGB1: + resultantColor = color.rgb1; + break; + case $kReadSwizzleRRRR: + resultantColor = color.rrrr; + break; + case $kReadSwizzleRRR1: + resultantColor = color.rrr1; + break; + case $kReadSwizzleBGRA: + resultantColor = color.bgra; + break; + } + return resultantColor; +} + +$pure half $apply_xfer_fn(int kind, half x, half cs[7]) { + half G = cs[0], A = cs[1], B = cs[2], C = cs[3], D = cs[4], E = cs[5], F = cs[6]; + half s = sign(x); + x = abs(x); + switch (kind) { + case $kTFTypeSRGB: + x = (x < D) ? (C * x) + F + : pow(A * x + B, G) + E; + break; + case $kTFTypePQ: + x = pow(max(A + B * pow(x, C), 0) / (D + E * pow(x, C)), F); + break; + case $kTFTypeHLG: + x = (x * A <= 1) ? pow(x * A, B) + : exp((x - E) * C) + D; + x *= (F + 1); + break; + case $kTFTypeHLGinv: + x /= (F + 1); + x = (x <= 1) ? A * pow(x, B) + : C * log(x - D) + E; + break; + } + return s * x; +} + +// TODO(b/239548614) need to plumb Graphite equivalent of fColorSpaceMathNeedsFloat. +// This would change 'color' from half4 to float4 +$pure half4 sk_color_space_transform(half4 color, + int flags, + int srcKind, + half srcCoeffs[7], + half3x3 gamutTransform, + int dstKind, + half dstCoeffs[7]) { + if (bool(flags & $kColorSpaceXformFlagUnpremul)) { + color = unpremul(color); + } + + if (bool(flags & $kColorSpaceXformFlagLinearize)) { + color.r = $apply_xfer_fn(srcKind, color.r, srcCoeffs); + color.g = $apply_xfer_fn(srcKind, color.g, srcCoeffs); + color.b = $apply_xfer_fn(srcKind, color.b, srcCoeffs); + } + if (bool(flags & $kColorSpaceXformFlagGamutTransform)) { + color.rgb = gamutTransform * color.rgb; + } + if (bool(flags & $kColorSpaceXformFlagEncode)) { + color.r = $apply_xfer_fn(dstKind, color.r, dstCoeffs); + color.g = $apply_xfer_fn(dstKind, color.g, dstCoeffs); + color.b = $apply_xfer_fn(dstKind, color.b, dstCoeffs); + } + + if (bool(flags & $kColorSpaceXformFlagPremul)) { + color.rgb *= color.a; + } + return color; +} + +$pure float $tile(int tileMode, float f, float low, float high) { + switch (tileMode) { + case $kTileModeClamp: + return clamp(f, low, high); + + case $kTileModeRepeat: { + float length = high - low; + return (mod(f - low, length) + low); + } + case $kTileModeMirror: { + float length = high - low; + float length2 = 2 * length; + float tmp = mod(f - low, length2); + return (mix(tmp, length2 - tmp, step(length, tmp)) + low); + } + default: // $kTileModeDecal + // Decal is handled later. + return f; + } +} + +$pure half4 $sample_image(float2 pos, + float2 imgSize, + float4 subset, + int tileModeX, + int tileModeY, + int filterMode, + int readSwizzle, + sampler2D s) { + // Do hard-edge shader transitions to the border color for nearest-neighbor decal tiling at the + // subset boundaries. Snap the input coordinates to nearest neighbor before comparing to the + // subset rect, to avoid GPU interpolation errors. See https://crbug.com/skia/10403. + if (tileModeX == $kTileModeDecal && filterMode == $kFilterModeNearest) { + float snappedX = floor(pos.x) + 0.5; + if (snappedX < subset.x || snappedX > subset.z) { + return half4(0); + } + } + if (tileModeY == $kTileModeDecal && filterMode == $kFilterModeNearest) { + float snappedY = floor(pos.y) + 0.5; + if (snappedY < subset.y || snappedY > subset.w) { + return half4(0); + } + } + + pos.x = $tile(tileModeX, pos.x, subset.x, subset.z); + pos.y = $tile(tileModeY, pos.y, subset.y, subset.w); + + // Clamp to an inset subset to prevent sampling neighboring texels when coords fall exactly at + // texel boundaries. + float4 insetClamp; + if (filterMode == $kFilterModeNearest) { + insetClamp = float4(floor(subset.xy) + 0.5, ceil(subset.zw) - 0.5); + } else { + insetClamp = float4(subset.xy + 0.5, subset.zw - 0.5); + } + float2 clampedPos = clamp(pos, insetClamp.xy, insetClamp.zw); + half4 color = sample(s, clampedPos / imgSize); + color = $apply_swizzle(readSwizzle, color); + + if (filterMode == $kFilterModeLinear) { + // Remember the amount the coord moved for clamping. This is used to implement shader-based + // filtering for repeat and decal tiling. + half2 error = half2(pos - clampedPos); + half2 absError = abs(error); + + // Do 1 or 3 more texture reads depending on whether both x and y tiling modes are repeat + // and whether we're near a single subset edge or a corner. Then blend the multiple reads + // using the error values calculated above. + bool sampleExtraX = tileModeX == $kTileModeRepeat; + bool sampleExtraY = tileModeY == $kTileModeRepeat; + if (sampleExtraX || sampleExtraY) { + float extraCoordX; + float extraCoordY; + half4 extraColorX; + half4 extraColorY; + if (sampleExtraX) { + extraCoordX = error.x > 0 ? insetClamp.x : insetClamp.z; + extraColorX = sample(s, float2(extraCoordX, clampedPos.y) / imgSize); + extraColorX = $apply_swizzle(readSwizzle, extraColorX); + } + if (sampleExtraY) { + extraCoordY = error.y > 0 ? insetClamp.y : insetClamp.w; + extraColorY = sample(s, float2(clampedPos.x, extraCoordY) / imgSize); + extraColorY = $apply_swizzle(readSwizzle, extraColorY); + } + if (sampleExtraX && sampleExtraY) { + half4 extraColorXY = sample(s, float2(extraCoordX, extraCoordY) / imgSize); + extraColorXY = $apply_swizzle(readSwizzle, extraColorXY); + color = mix(mix(color, extraColorX, absError.x), + mix(extraColorY, extraColorXY, absError.x), + absError.y); + } else if (sampleExtraX) { + color = mix(color, extraColorX, absError.x); + } else if (sampleExtraY) { + color = mix(color, extraColorY, absError.y); + } + } + + // Do soft edge shader filtering for decal tiling and linear filtering using the error + // values calculated above. + if (tileModeX == $kTileModeDecal) { + color *= max(1 - absError.x, 0); + } + if (tileModeY == $kTileModeDecal) { + color *= max(1 - absError.y, 0); + } + } + + return color; +} + +$pure half4 $cubic_filter_image(float2 pos, + float2 imgSize, + float4 subset, + int tileModeX, + int tileModeY, + float4x4 coeffs, + int readSwizzle, + sampler2D s) { + // Determine pos's fractional offset f between texel centers. + float2 f = fract(pos - 0.5); + // Sample 16 points at 1-pixel intervals from [p - 1.5 ... p + 1.5]. + pos -= 1.5; + // Snap to texel centers to prevent sampling neighboring texels. + pos = floor(pos) + 0.5; + + float4 wx = coeffs * float4(1.0, f.x, f.x * f.x, f.x * f.x * f.x); + float4 wy = coeffs * float4(1.0, f.y, f.y * f.y, f.y * f.y * f.y); + float4 color = float4(0); + for (int y = 0; y < 4; ++y) { + float4 rowColor = float4(0); + for (int x = 0; x < 4; ++x) { + rowColor += wx[x] * $sample_image(pos + float2(x, y), imgSize, subset, + tileModeX, tileModeY, $kFilterModeNearest, + readSwizzle, s); + } + color += wy[y] * rowColor; + } + return half4(color); +} + +$pure half4 sk_image_shader(float2 coords, + float2 imgSize, + float4 subset, + int tileModeX, + int tileModeY, + int filterMode, + int useCubic, + float4x4 cubicCoeffs, + int readSwizzle, + int csXformFlags, + int csXformSrcKind, + half csXformSrcCoeffs[7], + half3x3 csXformGamutTransform, + int csXformDstKind, + half csXformDstCoeffs[7], + sampler2D s) { + half4 sampleColor = (useCubic != 0) + ? $cubic_filter_image(coords, imgSize, subset, tileModeX, tileModeY, cubicCoeffs, + readSwizzle, s) + : $sample_image(coords, imgSize, subset, tileModeX, tileModeY, filterMode, readSwizzle, s); + return sk_color_space_transform(sampleColor, csXformFlags, csXformSrcKind, csXformSrcCoeffs, + csXformGamutTransform, csXformDstKind, csXformDstCoeffs); +} + +$pure half4 sk_dither_shader(half4 colorIn, + float2 coords, + float range, + sampler2D lut) { + const float kImgSize = 8; + + half2 lutCoords = half2(coords.x/kImgSize, coords.y/kImgSize); + half value = sample(lut, lutCoords).r - 0.5; // undo the bias in the table + // For each color channel, add the random offset to the channel value and then clamp + // between 0 and alpha to keep the color premultiplied. + return half4(clamp(colorIn.rgb + value * range, 0.0, colorIn.a), colorIn.a); +} + +$pure float2 $tile_grad(int tileMode, float2 t) { + switch (tileMode) { + case $kTileModeClamp: + t.x = clamp(t.x, 0, 1); + break; + + case $kTileModeRepeat: + t.x = fract(t.x); + break; + + case $kTileModeMirror: { + float t_1 = t.x - 1; + t.x = t_1 - 2 * floor(t_1 * 0.5) - 1; + if (sk_Caps.mustDoOpBetweenFloorAndAbs) { + // At this point the expected value of tiled_t should between -1 and 1, so this + // clamp has no effect other than to break up the floor and abs calls and make sure + // the compiler doesn't merge them back together. + t.x = clamp(t.x, -1, 1); + } + t.x = abs(t.x); + break; + } + + case $kTileModeDecal: + if (t.x < 0 || t.x > 1) { + return float2(0, -1); + } + break; + } + + return t; +} + +$pure half4 $colorize_grad_4(float4 colorsParam[4], float offsetsParam[4], float2 t) { + if (t.y < 0) { + return half4(0); + + } else if (t.x <= offsetsParam[0]) { + return half4(colorsParam[0]); + } else if (t.x < offsetsParam[1]) { + return half4(mix(colorsParam[0], colorsParam[1], (t.x - offsetsParam[0]) / + (offsetsParam[1] - offsetsParam[0]))); + } else if (t.x < offsetsParam[2]) { + return half4(mix(colorsParam[1], colorsParam[2], (t.x - offsetsParam[1]) / + (offsetsParam[2] - offsetsParam[1]))); + } else if (t.x < offsetsParam[3]) { + return half4(mix(colorsParam[2], colorsParam[3], (t.x - offsetsParam[2]) / + (offsetsParam[3] - offsetsParam[2]))); + } else { + return half4(colorsParam[3]); + } +} + +$pure half4 $colorize_grad_8(float4 colorsParam[8], float offsetsParam[8], float2 t) { + if (t.y < 0) { + return half4(0); + + // Unrolled binary search through intervals + // ( .. 0), (0 .. 1), (1 .. 2), (2 .. 3), (3 .. 4), (4 .. 5), (5 .. 6), (6 .. 7), (7 .. ). + } else if (t.x < offsetsParam[4]) { + if (t.x < offsetsParam[2]) { + if (t.x <= offsetsParam[0]) { + return half4(colorsParam[0]); + } else if (t.x < offsetsParam[1]) { + return half4(mix(colorsParam[0], colorsParam[1], + (t.x - offsetsParam[0]) / + (offsetsParam[1] - offsetsParam[0]))); + } else { + return half4(mix(colorsParam[1], colorsParam[2], + (t.x - offsetsParam[1]) / + (offsetsParam[2] - offsetsParam[1]))); + } + } else { + if (t.x < offsetsParam[3]) { + return half4(mix(colorsParam[2], colorsParam[3], + (t.x - offsetsParam[2]) / + (offsetsParam[3] - offsetsParam[2]))); + } else { + return half4(mix(colorsParam[3], colorsParam[4], + (t.x - offsetsParam[3]) / + (offsetsParam[4] - offsetsParam[3]))); + } + } + } else { + if (t.x < offsetsParam[6]) { + if (t.x < offsetsParam[5]) { + return half4(mix(colorsParam[4], colorsParam[5], + (t.x - offsetsParam[4]) / + (offsetsParam[5] - offsetsParam[4]))); + } else { + return half4(mix(colorsParam[5], colorsParam[6], + (t.x - offsetsParam[5]) / + (offsetsParam[6] - offsetsParam[5]))); + } + } else { + if (t.x < offsetsParam[7]) { + return half4(mix(colorsParam[6], colorsParam[7], + (t.x - offsetsParam[6]) / + (offsetsParam[7] - offsetsParam[6]))); + } else { + return half4(colorsParam[7]); + } + } + } +} + +half4 $colorize_grad_tex(sampler2D colorsAndOffsetsSampler, int numStops, float2 t) { + const float kColorCoord = 0.25; + const float kOffsetCoord = 0.75; + + if (t.y < 0) { + return half4(0); + } else if (t.x == 0) { + return sampleLod(colorsAndOffsetsSampler, float2(0, kColorCoord), 0); + } else if (t.x == 1) { + return sampleLod(colorsAndOffsetsSampler, float2(1, kColorCoord), 0); + } else { + int low = 0; + int high = numStops; + for (int loop = 1; loop < numStops; loop <<= 1) { + int mid = (low + high) / 2; + float midFlt = (float(mid) + 0.5) / float(numStops); + + float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(midFlt, kOffsetCoord), 0).xy; + float offset = ldexp(tmp.x, int(tmp.y)); + + if (t.x < offset) { + high = mid; + } else { + low = mid; + } + } + + float lowFlt = (float(low) + 0.5) / float(numStops); + float highFlt = (float(low + 1) + 0.5) / float(numStops); + half4 color0 = sampleLod(colorsAndOffsetsSampler, float2(lowFlt, kColorCoord), 0); + half4 color1 = sampleLod(colorsAndOffsetsSampler, float2(highFlt, kColorCoord), 0); + + float2 tmp = sampleLod(colorsAndOffsetsSampler, float2(lowFlt, kOffsetCoord), 0).xy; + float offset0 = ldexp(tmp.x, int(tmp.y)); + + tmp = sampleLod(colorsAndOffsetsSampler, float2(highFlt, kOffsetCoord), 0).xy; + float offset1 = ldexp(tmp.x, int(tmp.y)); + + return half4(mix(color0, color1, + (t.x - offset0) / + (offset1 - offset0))); + } +} + +$pure float2 $linear_grad_layout(float2 point0Param, float2 point1Param, float2 pos) { + pos -= point0Param; + float2 delta = point1Param - point0Param; + float t = dot(pos, delta) / dot(delta, delta); + return float2(t, 1); +} + +$pure float2 $radial_grad_layout(float2 centerParam, float radiusParam, float2 pos) { + float t = distance(pos, centerParam) / radiusParam; + return float2(t, 1); +} + +$pure float2 $sweep_grad_layout(float2 centerParam, float biasParam, float scaleParam, float2 pos) { + pos -= centerParam; + + // Some devices incorrectly implement atan2(y,x) as atan(y/x). In actuality it is + // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). To work around this we pass in + // (sqrt(x^2 + y^2) + x) as the second parameter to atan2 in these cases. We let the device + // handle the undefined behavior if the second parameter is 0, instead of doing the divide + // ourselves and calling atan with the quotient. + float angle = sk_Caps.atan2ImplementedAsAtanYOverX ? 2 * atan(-pos.y, length(pos) - pos.x) + : atan(-pos.y, -pos.x); + + // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi] + float t = (angle * 0.1591549430918 + 0.5 + biasParam) * scaleParam; + return float2(t, 1); +} + +$pure float3x3 $map_to_unit_x(float2 p0, float2 p1) { + // Returns a matrix that maps [p0, p1] to [(0, 0), (1, 0)]. Results are undefined if p0 = p1. + // From skia/src/core/SkMatrix.cpp, SkMatrix::setPolyToPoly. + return float3x3( + 0, -1, 0, + 1, 0, 0, + 0, 0, 1 + ) * inverse(float3x3( + p1.y - p0.y, p0.x - p1.x, 0, + p1.x - p0.x, p1.y - p0.y, 0, + p0.x, p0.y, 1 + )); +} + +$pure float2 $conical_grad_layout(float2 point0Param, + float2 point1Param, + float radius0Param, + float radius1Param, + float2 pos) { + const float SK_ScalarNearlyZero = 1.0 / (1 << 12); + float dCenter = distance(point0Param, point1Param); + float dRadius = radius1Param - radius0Param; + + // Degenerate case: a radial gradient (p0 = p1). + bool radial = dCenter < SK_ScalarNearlyZero; + + // Degenerate case: a strip with bandwidth 2r (r0 = r1). + bool strip = abs(dRadius) < SK_ScalarNearlyZero; + + if (radial) { + if (strip) { + // The start and end inputs are the same in both position and radius. + // We don't expect to see this input, but just in case we avoid dividing by zero. + return float2(0, -1); + } + + float scale = 1 / dRadius; + float scaleSign = sign(dRadius); + float bias = radius0Param / dRadius; + + float2 pt = (pos - point0Param) * scale; + float t = length(pt) * scaleSign - bias; + return float2(t, 1); + + } else if (strip) { + float3x3 transform = $map_to_unit_x(point0Param, point1Param); + float r = radius0Param / dCenter; + float r_2 = r * r; + + float2 pt = (transform * pos.xy1).xy; + float t = r_2 - pt.y * pt.y; + if (t < 0) { + return float2(0, -1); + } + t = pt.x + sqrt(t); + return float2(t, 1); + + } else { + // See https://skia.org/docs/dev/design/conical/ for details on how this algorithm works. + // Calculate f and swap inputs if necessary (steps 1 and 2). + float f = radius0Param / (radius0Param - radius1Param); + + bool isSwapped = abs(f - 1) < SK_ScalarNearlyZero; + if (isSwapped) { + float2 tmpPt = point0Param; + point0Param = point1Param; + point1Param = tmpPt; + f = 0; + } + + // Apply mapping from [Cf, C1] to unit x, and apply the precalculations from steps 3 and 4, + // all in the same transformation. + float2 Cf = point0Param * (1 - f) + point1Param * f; + float3x3 transform = $map_to_unit_x(Cf, point1Param); + + float scaleX = abs(1 - f); + float scaleY = scaleX; + float r1 = abs(radius1Param - radius0Param) / dCenter; + bool isFocalOnCircle = abs(r1 - 1) < SK_ScalarNearlyZero; + if (isFocalOnCircle) { + scaleX *= 0.5; + scaleY *= 0.5; + } else { + scaleX *= r1 / (r1 * r1 - 1); + scaleY /= sqrt(abs(r1 * r1 - 1)); + } + transform = float3x3( + scaleX, 0, 0, + 0, scaleY, 0, + 0, 0, 1 + ) * transform; + + float2 pt = (transform * pos.xy1).xy; + + // Continue with step 5 onward. + float invR1 = 1 / r1; + float dRadiusSign = sign(1 - f); + bool isWellBehaved = !isFocalOnCircle && r1 > 1; + + float x_t = -1; + if (isFocalOnCircle) { + x_t = dot(pt, pt) / pt.x; + } else if (isWellBehaved) { + x_t = length(pt) - pt.x * invR1; + } else { + float temp = pt.x * pt.x - pt.y * pt.y; + if (temp >= 0) { + if (isSwapped || dRadiusSign < 0) { + x_t = -sqrt(temp) - pt.x * invR1; + } else { + x_t = sqrt(temp) - pt.x * invR1; + } + } + } + + if (!isWellBehaved && x_t < 0) { + return float2(0, -1); + } + + float t = f + dRadiusSign * x_t; + if (isSwapped) { + t = 1 - t; + } + return float2(t, 1); + } +} + +$pure half4 sk_linear_grad_4_shader(float2 coords, + float4 colorsParam[4], + float offsetsParam[4], + float2 point0Param, + float2 point1Param, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $linear_grad_layout(point0Param, point1Param, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_linear_grad_8_shader(float2 coords, + float4 colorsParam[8], + float offsetsParam[8], + float2 point0Param, + float2 point1Param, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $linear_grad_layout(point0Param, point1Param, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_linear_grad_tex_shader(float2 coords, + float2 point0Param, + float2 point1Param, + int numStops, + int tileMode, + int colorSpace, + int doUnpremul, + sampler2D colorAndOffsetSampler) { + float2 t = $linear_grad_layout(point0Param, point1Param, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_radial_grad_4_shader(float2 coords, + float4 colorsParam[4], + float offsetsParam[4], + float2 centerParam, + float radiusParam, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $radial_grad_layout(centerParam, radiusParam, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_radial_grad_8_shader(float2 coords, + float4 colorsParam[8], + float offsetsParam[8], + float2 centerParam, + float radiusParam, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $radial_grad_layout(centerParam, radiusParam, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_radial_grad_tex_shader(float2 coords, + float2 centerParam, + float radiusParam, + int numStops, + int tileMode, + int colorSpace, + int doUnpremul, + sampler2D colorAndOffsetSampler) { + float2 t = $radial_grad_layout(centerParam, radiusParam, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_sweep_grad_4_shader(float2 coords, + float4 colorsParam[4], + float offsetsParam[4], + float2 centerParam, + float biasParam, + float scaleParam, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_sweep_grad_8_shader(float2 coords, + float4 colorsParam[8], + float offsetsParam[8], + float2 centerParam, + float biasParam, + float scaleParam, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_sweep_grad_tex_shader(float2 coords, + float2 centerParam, + float biasParam, + float scaleParam, + int numStops, + int tileMode, + int colorSpace, + int doUnpremul, + sampler2D colorAndOffsetSampler) { + float2 t = $sweep_grad_layout(centerParam, biasParam, scaleParam, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_conical_grad_4_shader(float2 coords, + float4 colorsParam[4], + float offsetsParam[4], + float2 point0Param, + float2 point1Param, + float radius0Param, + float radius1Param, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_4(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_conical_grad_8_shader(float2 coords, + float4 colorsParam[8], + float offsetsParam[8], + float2 point0Param, + float2 point1Param, + float radius0Param, + float radius1Param, + int tileMode, + int colorSpace, + int doUnpremul) { + float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_8(colorsParam, offsetsParam, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_conical_grad_tex_shader(float2 coords, + float2 point0Param, + float2 point1Param, + float radius0Param, + float radius1Param, + int numStops, + int tileMode, + int colorSpace, + int doUnpremul, + sampler2D colorAndOffsetSampler) { + float2 t = $conical_grad_layout(point0Param, point1Param, radius0Param, radius1Param, coords); + t = $tile_grad(tileMode, t); + half4 color = $colorize_grad_tex(colorAndOffsetSampler, numStops, t); + return $interpolated_to_rgb_unpremul(color, colorSpace, doUnpremul); +} + +$pure half4 sk_matrix_colorfilter(half4 colorIn, float4x4 m, float4 v, int inHSLA) { + if (bool(inHSLA)) { + colorIn = $rgb_to_hsl(colorIn.rgb, colorIn.a); // includes unpremul + } else { + colorIn = unpremul(colorIn); + } + + half4 colorOut = half4((m * colorIn) + v); + + if (bool(inHSLA)) { + colorOut = $hsl_to_rgb(colorOut.rgb, colorOut.a); // includes clamp and premul + } else { + colorOut = saturate(colorOut); + colorOut.rgb *= colorOut.a; + } + + return colorOut; +} + +// This method computes the 4 x-coodinates ([0..1]) that should be used to look +// up in the Perlin noise shader's noise table. +$pure half4 noise_helper(half2 noiseVec, + half2 stitchData, + int stitching, + sampler2D permutationSampler) { + const half kBlockSize = 256.0; + + half4 floorVal; + floorVal.xy = floor(noiseVec); + floorVal.zw = floorVal.xy + half2(1); + + // Adjust frequencies if we're stitching tiles + if (bool(stitching)) { + if (floorVal.x >= stitchData.x) { floorVal.x -= stitchData.x; }; + if (floorVal.y >= stitchData.y) { floorVal.y -= stitchData.y; }; + if (floorVal.z >= stitchData.x) { floorVal.z -= stitchData.x; }; + if (floorVal.w >= stitchData.y) { floorVal.w -= stitchData.y; }; + } + + half sampleX = sample(permutationSampler, half2(floorVal.x/kBlockSize, 0.5)).r; + half sampleY = sample(permutationSampler, half2(floorVal.z/kBlockSize, 0.5)).r; + + half2 latticeIdx = half2(sampleX, sampleY); + + const half kInv255 = 0.003921569; // 1.0 / 255.0 + + // Aggressively round to the nearest exact (N / 255) floating point values. + // This prevents rounding errors on some platforms (e.g., Tegras) + latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(kInv255); + + // Get (x,y) coordinates with the permuted x + half4 noiseXCoords = kBlockSize*latticeIdx.xyxy + floorVal.yyww; + + noiseXCoords /= half4(kBlockSize); + return noiseXCoords; +} + +// TODO: Move this to sksl_shared.sksl and try to share with Ganesh +$pure half4 noise_function(half2 noiseVec, + half4 noiseXCoords, + sampler2D noiseSampler) { + + half2 fractVal = fract(noiseVec); + + // smooth curve : t^2*(3 - 2*t) + half2 noiseSmooth = fractVal*fractVal*(half2(3) - 2*fractVal); + + // This is used to convert the two 16bit integers packed into rgba 8 bit input into + // a [-1,1] vector + const half kInv256 = 0.00390625; // 1.0 / 256.0 + + half4 result; + + for (int channel = 0; channel < 4; channel++) { + + // There are 4 lines in the noise texture, put y coords at center of each. + half chanCoord = (half(channel) + 0.5) / 4.0; + + half4 sampleA = sample(noiseSampler, half2(noiseXCoords.x, chanCoord)); + half4 sampleB = sample(noiseSampler, half2(noiseXCoords.y, chanCoord)); + half4 sampleC = sample(noiseSampler, half2(noiseXCoords.w, chanCoord)); + half4 sampleD = sample(noiseSampler, half2(noiseXCoords.z, chanCoord)); + + half2 uv; + half2 tmpFractVal = fractVal; + + // Compute u, at offset (0,0) + uv.x = dot((sampleA.ga + sampleA.rb*kInv256)*2 - half2(1), tmpFractVal); + + // Compute v, at offset (-1,0) + tmpFractVal.x -= 1.0; + uv.y = dot((sampleB.ga + sampleB.rb*kInv256)*2 - half2(1), tmpFractVal); + + // Compute 'a' as a linear interpolation of 'u' and 'v' + half2 ab; + ab.x = mix(uv.x, uv.y, noiseSmooth.x); + + // Compute v, at offset (-1,-1) + tmpFractVal.y -= 1.0; + uv.y = dot((sampleC.ga + sampleC.rb*kInv256)*2 - half2(1), tmpFractVal); + + // Compute u, at offset (0,-1) + tmpFractVal.x += 1.0; + uv.x = dot((sampleD.ga + sampleD.rb*kInv256)*2 - half2(1), tmpFractVal); + + // Compute 'b' as a linear interpolation of 'u' and 'v' + ab.y = mix(uv.x, uv.y, noiseSmooth.x); + + // Compute the noise as a linear interpolation of 'a' and 'b' + result[channel] = mix(ab.x, ab.y, noiseSmooth.y); + } + + return result; +} + +// permutationSampler is [kBlockSize x 1] A8 +// noiseSampler is [kBlockSize x 4] RGBA8 premul +$pure half4 perlin_noise_shader(float2 coords, + float2 baseFrequency, + float2 stitchDataIn, + int noiseType, + int numOctaves, + int stitching, + sampler2D permutationSampler, + sampler2D noiseSampler) { + const int kFractalNoise_Type = 0; + const int kTurbulence_Type = 1; + + // There are rounding errors if the floor operation is not performed here + half2 noiseVec = half2(floor(coords.xy) * baseFrequency); + + // Clear the color accumulator + half4 color = half4(0); + + half2 stitchData = half2(stitchDataIn); + + half ratio = 1.0; + + // Loop over all octaves + for (int octave = 0; octave < numOctaves; ++octave) { + half4 noiseXCoords = noise_helper(noiseVec, stitchData, stitching, permutationSampler); + + half4 tmp = noise_function(noiseVec, noiseXCoords, noiseSampler); + + if (noiseType != kFractalNoise_Type) { + // For kTurbulence_Type the result is: abs(noise[-1,1]) + tmp = abs(tmp); + } + + tmp *= ratio; + color += tmp; + + noiseVec *= half2(2.0); + ratio *= 0.5; + stitchData *= half2(2.0); + } + + if (noiseType == kFractalNoise_Type) { + // For kFractalNoise_Type the result is: noise[-1,1] * 0.5 + 0.5 + color = color * half4(0.5) + half4(0.5); + } + + // Clamp values + color = saturate(color); + + // Pre-multiply the result + return half4(color.rgb * color.aaa, color.a); +} + +$pure half4 sk_blend(int blendMode, half4 src, half4 dst) { + const int kClear = 0; + const int kSrc = 1; + const int kDst = 2; + const int kSrcOver = 3; + const int kDstOver = 4; + const int kSrcIn = 5; + const int kDstIn = 6; + const int kSrcOut = 7; + const int kDstOut = 8; + const int kSrcATop = 9; + const int kDstATop = 10; + const int kXor = 11; + const int kPlus = 12; + const int kModulate = 13; + const int kScreen = 14; + const int kOverlay = 15; + const int kDarken = 16; + const int kLighten = 17; + const int kColorDodge = 18; + const int kColorBurn = 19; + const int kHardLight = 20; + const int kSoftLight = 21; + const int kDifference = 22; + const int kExclusion = 23; + const int kMultiply = 24; + const int kHue = 25; + const int kSaturation = 26; + const int kColor = 27; + const int kLuminosity = 28; + + switch (blendMode) { + case kClear: { return blend_clear(src, dst); } + case kSrc: { return blend_src(src, dst); } + case kDst: { return blend_dst(src, dst); } + case kSrcOver: { return blend_porter_duff(half4(1, 0, 0, -1), src, dst); } + case kDstOver: { return blend_porter_duff(half4(0, 1, -1, 0), src, dst); } + case kSrcIn: { return blend_porter_duff(half4(0, 0, 1, 0), src, dst); } + case kDstIn: { return blend_porter_duff(half4(0, 0, 0, 1), src, dst); } + case kSrcOut: { return blend_porter_duff(half4(0, 0, -1, 0), src, dst); } + case kDstOut: { return blend_porter_duff(half4(0, 0, 0, -1), src, dst); } + case kSrcATop: { return blend_porter_duff(half4(0, 0, 1, -1), src, dst); } + case kDstATop: { return blend_porter_duff(half4(0, 0, -1, 1), src, dst); } + case kXor: { return blend_porter_duff(half4(0, 0, -1, -1), src, dst); } + case kPlus: { return blend_porter_duff(half4(1, 1, 0, 0), src, dst); } + case kModulate: { return blend_modulate(src, dst); } + case kScreen: { return blend_screen(src, dst); } + case kOverlay: { return blend_overlay(/*flip=*/0, src, dst); } + case kDarken: { return blend_darken(/*mode=*/1, src, dst); } + case kLighten: { return blend_darken(/*mode=*/-1, src, dst); } + case kColorDodge: { return blend_color_dodge(src, dst); } + case kColorBurn: { return blend_color_burn(src, dst); } + case kHardLight: { return blend_overlay(/*flip=*/1, src, dst); } + case kSoftLight: { return blend_soft_light(src, dst); } + case kDifference: { return blend_difference(src, dst); } + case kExclusion: { return blend_exclusion(src, dst); } + case kMultiply: { return blend_multiply(src, dst); } + case kHue: { return blend_hslc(/*flipSat=*/half2(0, 1), src, dst); } + case kSaturation: { return blend_hslc(/*flipSat=*/half2(1), src, dst); } + case kColor: { return blend_hslc(/*flipSat=*/half2(0), src, dst); } + case kLuminosity: { return blend_hslc(/*flipSat=*/half2(1, 0), src, dst); } + default: return half4(0); // Avoids 'blend can exit without returning a value' error + } +} + +$pure half4 sk_blend_shader(int blendMode, half4 dst, half4 src) { + return sk_blend(blendMode, src, dst); +} + +$pure half4 porter_duff_blend_shader(half4 blendOp, half4 dst, half4 src) { + return blend_porter_duff(blendOp, src, dst); +} + +$pure half4 sk_blend_colorfilter(half4 dstColor, int blendMode, float4 srcColor) { + return sk_blend(blendMode, half4(srcColor), dstColor); +} + +$pure half4 sk_table_colorfilter(half4 inColor, sampler2D s) { + half4 coords = unpremul(inColor) * 255.0/256.0 + 0.5/256.0; + half4 color = half4(sample(s, half2(coords.r, 3.0/8.0)).r, + sample(s, half2(coords.g, 5.0/8.0)).r, + sample(s, half2(coords.b, 7.0/8.0)).r, + 1); + return color * sample(s, half2(coords.a, 1.0/8.0)).r; +} + +$pure half4 sk_gaussian_colorfilter(half4 inColor) { + half factor = 1 - inColor.a; + factor = exp(-factor * factor * 4) - 0.018; + return half4(factor); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Support functions for analytic round rectangles + +// Calculates 1/|∇| in device space by applying the chain rule to a local gradient vector and the +// 2x2 Jacobian describing the transform from local-to-device space. For non-perspective, this is +// equivalent to the "normal matrix", or the inverse transpose. For perspective, J should be +// W(u,v) [m00' - m20'u m01' - m21'u] derived from the first two columns of the 3x3 inverse. +// [m10' - m20'v m11' - m21'v] +$pure float inverse_grad_len(float2 localGrad, float2x2 jacobian) { + // NOTE: By chain rule, the local gradient is on the left side of the Jacobian matrix + float2 devGrad = localGrad * jacobian; + // NOTE: This uses the L2 norm, which is more accurate than the L1 norm used by fwidth(). + // TODO: Switch to L1 since it is a 2x perf improvement according to Xcode with little visual + // impact, but start with L2 to measure the change separately from the algorithmic update. + // return 1.0 / (abs(devGrad.x) + abs(devGrad.y)); + return inversesqrt(dot(devGrad, devGrad)); +} + +// Returns distance from both sides of a stroked circle or ellipse. Elliptical coverage is +// only accurate if strokeRadius = 0. A positive value represents the interior of the stroke. +$pure float2 elliptical_distance(float2 uv, float2 radii, float strokeRadius, float2x2 jacobian) { + // We do need to evaluate up to two circle equations: one with + // R = cornerRadius(r)+strokeRadius(s), and another with R = r-s. + // This can be consolidated into a common evaluation against a circle of radius sqrt(r^2+s^2): + // (x/(r+/-s))^2 + (y/(r+/-s))^2 = 1 + // x^2 + y^2 = (r+/-s)^2 + // x^2 + y^2 = r^2 + s^2 +/- 2rs + // (x/sqrt(r^2+s^2))^2 + (y/sqrt(r^2+s^2)) = 1 +/- 2rs/(r^2+s^2) + // The 2rs/(r^2+s^2) is the "width" that adjusts the implicit function to the outer or inner + // edge of the stroke. For fills and hairlines, s = 0, which means these operations remain valid + // for elliptical corners where radii holds the different X and Y corner radii. + float2 invR2 = 1.0 / (radii * radii + strokeRadius*strokeRadius); + float2 normUV = invR2 * uv; + float invGradLength = inverse_grad_len(normUV, jacobian); + + // Since normUV already includes 1/r^2 in the denominator, dot with just 'uv' instead. + float f = 0.5 * invGradLength * (dot(uv, normUV) - 1.0); + + // This is 0 for fills/hairlines, which are the only types that allow + // elliptical corners (strokeRadius == 0). For regular strokes just use X. + float width = radii.x * strokeRadius * invR2.x * invGradLength; + return float2(width - f, width + f); +} + +// Accumulates the minimum (and negative maximum) of the outer and inner corner distances in 'dist' +// for a possibly elliptical corner with 'radii' and relative pixel location specified by +// 'cornerEdgeDist'. The corner's basis relative to the jacobian is defined in 'xyFlip'. +void corner_distance(inout float2 dist, + float2x2 jacobian, + float2 strokeParams, + float2 cornerEdgeDist, + float2 xyFlip, + float2 radii) { + float2 uv = radii - cornerEdgeDist; + // NOTE: For mitered corners uv > 0 only if it's stroked, and in that case the + // subsequent conditions skip calculating anything. + if (uv.x > 0.0 && uv.y > 0.0) { + if ((radii.x > 0.0 && radii.y > 0.0) || + (strokeParams.x > 0.0 && strokeParams.y < 0.0 /* round-join */)) { + // A rounded corner so incorporate outer elliptical distance if we're within the + // quarter circle. + float2 d = elliptical_distance(uv * xyFlip, radii, strokeParams.x, jacobian); + if (radii.x - strokeParams.x <= 0.0) { + d.y = 1.0; // disregard inner curve since it's collapsed into an inner miter. + } else { + d.y *= -1.0; // Negate so that "min" accumulates the maximum value instead + } + dist = min(dist, d); + } else if (strokeParams.y == 0.0 /* bevel-join */) { + // Bevels are--by construction--interior mitered, so inner distance is based + // purely on the edge distance calculations, but the outer distance is to a 45-degree + // line and not the vertical/horizontal lines of the other edges. + float bevelDist = (strokeParams.x - uv.x - uv.y) * inverse_grad_len(xyFlip, jacobian); + dist.x = min(dist.x, bevelDist); + } // Else it's a miter so both inner and outer distances are unmodified + } // Else we're not affected by the corner so leave distances unmodified +} + +// Accumulates the minimum (and negative maximum) of the outer and inner corner distances into 'd', +// for all four corners of a [round] rectangle. 'edgeDists' should be ordered LTRB with positive +// distance representing the interior of the edge. 'xRadii' and 'yRadii' should hold the per-corner +// elliptical radii, ordered TL, TR, BR, BL. +void corner_distances(inout float2 d, + float2x2 J, + float2 stroke, // {radii, joinStyle}, see StrokeStyle struct definition + float4 edgeDists, + float4 xRadii, + float4 yRadii) { + corner_distance(d, J, stroke, edgeDists.xy, float2(-1.0, -1.0), float2(xRadii[0], yRadii[0])); + corner_distance(d, J, stroke, edgeDists.zy, float2( 1.0, -1.0), float2(xRadii[1], yRadii[1])); + corner_distance(d, J, stroke, edgeDists.zw, float2( 1.0, 1.0), float2(xRadii[2], yRadii[2])); + corner_distance(d, J, stroke, edgeDists.xw, float2(-1.0, 1.0), float2(xRadii[3], yRadii[3])); +} diff --git a/gfx/skia/skia/src/sksl/sksl_graphite_vert.sksl b/gfx/skia/skia/src/sksl/sksl_graphite_vert.sksl new file mode 100644 index 0000000000..4f20c0bca5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_graphite_vert.sksl @@ -0,0 +1,535 @@ +// Graphite-specific vertex shader code + +const float $PI = 3.141592653589793238; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Support functions for tessellating path renderers + +const float $kCubicCurveType = 0; // skgpu::tess::kCubicCurveType +const float $kConicCurveType = 1; // skgpu::tess::kConicCurveType +const float $kTriangularConicCurveType = 2; // skgpu::tess::kTriangularConicCurveType + +// This function can be used on GPUs with infinity support to infer the curve type from the specific +// path control-point encoding used by tessellating path renderers. Calling this function on a +// platform that lacks infinity support may result in a shader compilation error. +$pure float curve_type_using_inf_support(float4 p23) { + if (isinf(p23.z)) { + return $kTriangularConicCurveType; + } + if (isinf(p23.w)) { + return $kConicCurveType; + } + return $kCubicCurveType; +} + +$pure bool $is_conic_curve(float curveType) { + return curveType != $kCubicCurveType; +} + +$pure bool $is_triangular_conic_curve(float curveType) { + return curveType == $kTriangularConicCurveType; +} + +// Wang's formula gives the minimum number of evenly spaced (in the parametric sense) line segments +// that a bezier curve must be chopped into in order to guarantee all lines stay within a distance +// of "1/precision" pixels from the true curve. Its definition for a bezier curve of degree "n" is +// as follows: +// +// maxLength = max([length(p[i+2] - 2p[i+1] + p[i]) for (0 <= i <= n-2)]) +// numParametricSegments = sqrt(maxLength * precision * n*(n - 1)/8) +// +// (Goldman, Ron. (2003). 5.6.3 Wang's Formula. "Pyramid Algorithms: A Dynamic Programming Approach +// to Curves and Surfaces for Geometric Modeling". Morgan Kaufmann Publishers.) + +const float $kDegree = 3; +const float $kPrecision = 4; // Must match skgpu::tess::kPrecision +const float $kLengthTerm = ($kDegree * ($kDegree - 1) / 8.0) * $kPrecision; +const float $kLengthTermPow2 = (($kDegree * $kDegree) * (($kDegree - 1) * ($kDegree - 1)) / 64.0) * + ($kPrecision * $kPrecision); + +// Returns the length squared of the largest forward difference from Wang's cubic formula. +$pure float $wangs_formula_max_fdiff_p2(float2 p0, float2 p1, float2 p2, float2 p3, + float2x2 matrix) { + float2 d0 = matrix * (fma(float2(-2), p1, p2) + p0); + float2 d1 = matrix * (fma(float2(-2), p2, p3) + p1); + return max(dot(d0,d0), dot(d1,d1)); +} + +$pure float $wangs_formula_cubic(float2 p0, float2 p1, float2 p2, float2 p3, + float2x2 matrix) { + float m = $wangs_formula_max_fdiff_p2(p0, p1, p2, p3, matrix); + return max(ceil(sqrt($kLengthTerm * sqrt(m))), 1.0); +} + +$pure float $wangs_formula_cubic_log2(float2 p0, float2 p1, float2 p2, float2 p3, + float2x2 matrix) { + float m = $wangs_formula_max_fdiff_p2(p0, p1, p2, p3, matrix); + return ceil(log2(max($kLengthTermPow2 * m, 1.0)) * .25); +} + +$pure float $wangs_formula_conic_p2(float2 p0, float2 p1, float2 p2, float w) { + // Translate the bounding box center to the origin. + float2 C = (min(min(p0, p1), p2) + max(max(p0, p1), p2)) * 0.5; + p0 -= C; + p1 -= C; + p2 -= C; + + // Compute max length. + float m = sqrt(max(max(dot(p0,p0), dot(p1,p1)), dot(p2,p2))); + + // Compute forward differences. + float2 dp = fma(float2(-2.0 * w), p1, p0) + p2; + float dw = abs(fma(-2.0, w, 2.0)); + + // Compute numerator and denominator for parametric step size of linearization. Here, the + // epsilon referenced from the cited paper is 1/precision. + float rp_minus_1 = max(0.0, fma(m, $kPrecision, -1.0)); + float numer = length(dp) * $kPrecision + rp_minus_1 * dw; + float denom = 4 * min(w, 1.0); + + return numer/denom; +} + +$pure float $wangs_formula_conic(float2 p0, float2 p1, float2 p2, float w) { + float n2 = $wangs_formula_conic_p2(p0, p1, p2, w); + return max(ceil(sqrt(n2)), 1.0); +} + +$pure float $wangs_formula_conic_log2(float2 p0, float2 p1, float2 p2, float w) { + float n2 = $wangs_formula_conic_p2(p0, p1, p2, w); + return ceil(log2(max(n2, 1.0)) * .5); +} + +// Returns the normalized difference between a and b, i.e. normalize(a - b), with care taken for +// if 'a' and/or 'b' have large coordinates. +$pure float2 $robust_normalize_diff(float2 a, float2 b) { + float2 diff = a - b; + if (diff == float2(0.0)) { + return float2(0.0); + } else { + float invMag = 1.0 / max(abs(diff.x), abs(diff.y)); + return normalize(invMag * diff); + } +} + +// Returns the cosine of the angle between a and b, assuming a and b are unit vectors already. +// Guaranteed to be between [-1, 1]. +$pure float $cosine_between_unit_vectors(float2 a, float2 b) { + // Since a and b are assumed to be normalized, the cosine is equal to the dot product, although + // we clamp that to ensure it falls within the expected range of [-1, 1]. + return clamp(dot(a, b), -1.0, 1.0); +} + +// Extends the middle radius to either the miter point, or the bevel edge if we surpassed the +// miter limit and need to revert to a bevel join. +$pure float $miter_extent(float cosTheta, float miterLimit) { + float x = fma(cosTheta, .5, .5); + return (x * miterLimit * miterLimit >= 1.0) ? inversesqrt(x) : sqrt(x); +} + +// Returns the number of radial segments required for each radian of rotation, in order for the +// curve to appear "smooth" as defined by the approximate device-space stroke radius. +$pure float $num_radial_segments_per_radian(float approxDevStrokeRadius) { + return .5 / acos(max(1.0 - (1.0 / $kPrecision) / approxDevStrokeRadius, -1.0)); +} + +// Unlike mix(), this does not return b when t==1. But it otherwise seems to get better +// precision than "a*(1 - t) + b*t" for things like chopping cubics on exact cusp points. +// We override this result anyway when t==1 so it shouldn't be a problem. +$pure float $unchecked_mix(float a, float b, float T) { + return fma(b - a, T, a); +} +$pure float2 $unchecked_mix(float2 a, float2 b, float T) { + return fma(b - a, float2(T), a); +} +$pure float4 $unchecked_mix(float4 a, float4 b, float4 T) { + return fma(b - a, T, a); +} + +// Compute a vertex position for the curve described by p01 and p23 packed control points, +// tessellated to the given resolve level, and assuming it will be drawn as a filled curve. +$pure float2 tessellate_filled_curve(float2x2 vectorXform, + float resolveLevel, float idxInResolveLevel, + float4 p01, float4 p23, + float curveType) { + float2 localcoord; + if ($is_triangular_conic_curve(curveType)) { + // This patch is an exact triangle. + localcoord = (resolveLevel != 0) ? p01.zw + : (idxInResolveLevel != 0) ? p23.xy + : p01.xy; + } else { + float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw; + float w = -1; // w < 0 tells us to treat the instance as an integral cubic. + float maxResolveLevel; + if ($is_conic_curve(curveType)) { + // Conics are 3 points, with the weight in p3. + w = p3.x; + maxResolveLevel = $wangs_formula_conic_log2(vectorXform*p0, + vectorXform*p1, + vectorXform*p2, w); + p1 *= w; // Unproject p1. + p3 = p2; // Duplicate the endpoint for shared code that also runs on cubics. + } else { + // The patch is an integral cubic. + maxResolveLevel = $wangs_formula_cubic_log2(p0, p1, p2, p3, vectorXform); + } + if (resolveLevel > maxResolveLevel) { + // This vertex is at a higher resolve level than we need. Demote to a lower + // resolveLevel, which will produce a degenerate triangle. + idxInResolveLevel = floor(ldexp(idxInResolveLevel, + int(maxResolveLevel - resolveLevel))); + resolveLevel = maxResolveLevel; + } + // Promote our location to a discrete position in the maximum fixed resolve level. + // This is extra paranoia to ensure we get the exact same fp32 coordinates for + // colocated points from different resolve levels (e.g., the vertices T=3/4 and + // T=6/8 should be exactly colocated). + float fixedVertexID = floor(.5 + ldexp(idxInResolveLevel, int(5 - resolveLevel))); + if (0 < fixedVertexID && fixedVertexID < 32) { + float T = fixedVertexID * (1 / 32.0); + + // Evaluate at T. Use De Casteljau's for its accuracy and stability. + float2 ab = mix(p0, p1, T); + float2 bc = mix(p1, p2, T); + float2 cd = mix(p2, p3, T); + float2 abc = mix(ab, bc, T); + float2 bcd = mix(bc, cd, T); + float2 abcd = mix(abc, bcd, T); + + // Evaluate the conic weight at T. + float u = mix(1.0, w, T); + float v = w + 1 - u; // == mix(w, 1, T) + float uv = mix(u, v, T); + + localcoord = (w < 0) ? /*cubic*/ abcd : /*conic*/ abc/uv; + } else { + localcoord = (fixedVertexID == 0) ? p0.xy : p3.xy; + } + } + return localcoord; +} + +// Device coords are in xy, local coords are in zw, since for now perspective isn't supported. +$pure float4 tessellate_stroked_curve(float edgeID, float maxEdges, + float2x2 affineMatrix, + float2 translate, + float maxScale /* derived from affineMatrix */, + float4 p01, float4 p23, + float2 lastControlPoint, + float2 strokeParams, + float curveType) { + float2 p0=p01.xy, p1=p01.zw, p2=p23.xy, p3=p23.zw; + float w = -1; // w<0 means the curve is an integral cubic. + if ($is_conic_curve(curveType)) { + // Conics are 3 points, with the weight in p3. + w = p3.x; + p3 = p2; // Setting p3 equal to p2 works for the remaining rotational logic. + } + + // Call Wang's formula to determine parametric segments before transform points for hairlines + // so that it is consistent with how the CPU tested the control points for chopping. + float numParametricSegments; + if (w < 0) { + if (p0 == p1 && p2 == p3) { + numParametricSegments = 1; // a line + } else { + numParametricSegments = $wangs_formula_cubic(p0, p1, p2, p3, affineMatrix); + } + } else { + numParametricSegments = $wangs_formula_conic(affineMatrix * p0, + affineMatrix * p1, + affineMatrix * p2, w); + } + + // Matches skgpu::tess::StrokeParams + float strokeRadius = strokeParams.x; + float joinType = strokeParams.y; // <0 = round join, ==0 = bevel join, >0 encodes miter limit + bool isHairline = strokeParams.x == 0.0; + float numRadialSegmentsPerRadian; + if (isHairline) { + numRadialSegmentsPerRadian = $num_radial_segments_per_radian(1.0); + strokeRadius = 0.5; + } else { + numRadialSegmentsPerRadian = $num_radial_segments_per_radian(maxScale * strokeParams.x); + } + + if (isHairline) { + // Hairline case. Transform the points before tessellation. We can still hold off on the + // translate until the end; we just need to perform the scale and skew right now. + p0 = affineMatrix * p0; + p1 = affineMatrix * p1; + p2 = affineMatrix * p2; + p3 = affineMatrix * p3; + lastControlPoint = affineMatrix * lastControlPoint; + } + + // Find the starting and ending tangents. + float2 tan0 = $robust_normalize_diff((p0 == p1) ? ((p1 == p2) ? p3 : p2) : p1, p0); + float2 tan1 = $robust_normalize_diff(p3, (p3 == p2) ? ((p2 == p1) ? p0 : p1) : p2); + if (tan0 == float2(0)) { + // The stroke is a point. This special case tells us to draw a stroke-width circle as a + // 180 degree point stroke instead. + tan0 = float2(1,0); + tan1 = float2(-1,0); + } + + // Determine how many edges to give to the join. We emit the first and final edges + // of the join twice: once full width and once restricted to half width. This guarantees + // perfect seaming by matching the vertices from the join as well as from the strokes on + // either side. + float numEdgesInJoin; + if (joinType >= 0 /*Is the join not a round type?*/) { + // Bevel(0) and miter(+) joins get 1 and 2 segments respectively. + // +2 because we emit the beginning and ending edges twice (see above comments). + numEdgesInJoin = sign(joinType) + 1 + 2; + } else { + float2 prevTan = $robust_normalize_diff(p0, lastControlPoint); + float joinRads = acos($cosine_between_unit_vectors(prevTan, tan0)); + float numRadialSegmentsInJoin = max(ceil(joinRads * numRadialSegmentsPerRadian), 1); + // +2 because we emit the beginning and ending edges twice (see above comment). + numEdgesInJoin = numRadialSegmentsInJoin + 2; + // The stroke section needs at least two edges. Don't assign more to the join than + // "maxEdges - 2". (This is only relevant when the ideal max edge count calculated + // on the CPU had to be limited to maxEdges in the draw call). + numEdgesInJoin = min(numEdgesInJoin, maxEdges - 2); + } + + // Find which direction the curve turns. + // NOTE: Since the curve is not allowed to inflect, we can just check F'(.5) x F''(.5). + // NOTE: F'(.5) x F''(.5) has the same sign as (P2 - P0) x (P3 - P1) + float turn = cross_length_2d(p2 - p0, p3 - p1); + float combinedEdgeID = abs(edgeID) - numEdgesInJoin; + if (combinedEdgeID < 0) { + tan1 = tan0; + // Don't let tan0 become zero. The code as-is isn't built to handle that case. tan0=0 + // means the join is disabled, and to disable it with the existing code we can leave + // tan0 equal to tan1. + if (lastControlPoint != p0) { + tan0 = $robust_normalize_diff(p0, lastControlPoint); + } + turn = cross_length_2d(tan0, tan1); + } + + // Calculate the curve's starting angle and rotation. + float cosTheta = $cosine_between_unit_vectors(tan0, tan1); + float rotation = acos(cosTheta); + if (turn < 0) { + // Adjust sign of rotation to match the direction the curve turns. + rotation = -rotation; + } + + float numRadialSegments; + float strokeOutset = sign(edgeID); + if (combinedEdgeID < 0) { + // We belong to the preceding join. The first and final edges get duplicated, so we only + // have "numEdgesInJoin - 2" segments. + numRadialSegments = numEdgesInJoin - 2; + numParametricSegments = 1; // Joins don't have parametric segments. + p3 = p2 = p1 = p0; // Colocate all points on the junction point. + // Shift combinedEdgeID to the range [-1, numRadialSegments]. This duplicates the first + // edge and lands one edge at the very end of the join. (The duplicated final edge will + // actually come from the section of our strip that belongs to the stroke.) + combinedEdgeID += numRadialSegments + 1; + // We normally restrict the join on one side of the junction, but if the tangents are + // nearly equivalent this could theoretically result in bad seaming and/or cracks on the + // side we don't put it on. If the tangents are nearly equivalent then we leave the join + // double-sided. + float sinEpsilon = 1e-2; // ~= sin(180deg / 3000) + bool tangentsNearlyParallel = + (abs(turn) * inversesqrt(dot(tan0, tan0) * dot(tan1, tan1))) < sinEpsilon; + if (!tangentsNearlyParallel || dot(tan0, tan1) < 0) { + // There are two edges colocated at the beginning. Leave the first one double sided + // for seaming with the previous stroke. (The double sided edge at the end will + // actually come from the section of our strip that belongs to the stroke.) + if (combinedEdgeID >= 0) { + strokeOutset = (turn < 0) ? min(strokeOutset, 0) : max(strokeOutset, 0); + } + } + combinedEdgeID = max(combinedEdgeID, 0); + } else { + // We belong to the stroke. Unless numRadialSegmentsPerRadian is incredibly high, + // clamping to maxCombinedSegments will be a no-op because the draw call was invoked with + // sufficient vertices to cover the worst case scenario of 180 degree rotation. + float maxCombinedSegments = maxEdges - numEdgesInJoin - 1; + numRadialSegments = max(ceil(abs(rotation) * numRadialSegmentsPerRadian), 1); + numRadialSegments = min(numRadialSegments, maxCombinedSegments); + numParametricSegments = min(numParametricSegments, + maxCombinedSegments - numRadialSegments + 1); + } + + // Additional parameters for final tessellation evaluation. + float radsPerSegment = rotation / numRadialSegments; + float numCombinedSegments = numParametricSegments + numRadialSegments - 1; + bool isFinalEdge = (combinedEdgeID >= numCombinedSegments); + if (combinedEdgeID > numCombinedSegments) { + strokeOutset = 0; // The strip has more edges than we need. Drop this one. + } + // Edge #2 extends to the miter point. + if (abs(edgeID) == 2 && joinType > 0/*Is the join a miter type?*/) { + strokeOutset *= $miter_extent(cosTheta, joinType/*miterLimit*/); + } + + float2 tangent, strokeCoord; + if (combinedEdgeID != 0 && !isFinalEdge) { + // Compute the location and tangent direction of the stroke edge with the integral id + // "combinedEdgeID", where combinedEdgeID is the sorted-order index of parametric and radial + // edges. Start by finding the tangent function's power basis coefficients. These define a + // tangent direction (scaled by some uniform value) as: + // |T^2| + // Tangent_Direction(T) = dx,dy = |A 2B C| * |T | + // |. . .| |1 | + float2 A, B, C = p1 - p0; + float2 D = p3 - p0; + if (w >= 0.0) { + // P0..P2 represent a conic and P3==P2. The derivative of a conic has a cumbersome + // order-4 denominator. However, this isn't necessary if we are only interested in a + // vector in the same *direction* as a given tangent line. Since the denominator scales + // dx and dy uniformly, we can throw it out completely after evaluating the derivative + // with the standard quotient rule. This leaves us with a simpler quadratic function + // that we use to find a tangent. + C *= w; + B = .5*D - C; + A = (w - 1.0) * D; + p1 *= w; + } else { + float2 E = p2 - p1; + B = E - C; + A = fma(float2(-3), E, D); + } + // FIXME(crbug.com/800804,skbug.com/11268): Consider normalizing the exponents in A,B,C at + // this point in order to prevent fp32 overflow. + + // Now find the coefficients that give a tangent direction from a parametric edge ID: + // + // |parametricEdgeID^2| + // Tangent_Direction(parametricEdgeID) = dx,dy = |A B_ C_| * |parametricEdgeID | + // |. . .| |1 | + // + float2 B_ = B * (numParametricSegments * 2.0); + float2 C_ = C * (numParametricSegments * numParametricSegments); + + // Run a binary search to determine the highest parametric edge that is located on or before + // the combinedEdgeID. A combined ID is determined by the sum of complete parametric and + // radial segments behind it. i.e., find the highest parametric edge where: + // + // parametricEdgeID + floor(numRadialSegmentsAtParametricT) <= combinedEdgeID + // + float lastParametricEdgeID = 0.0; + float maxParametricEdgeID = min(numParametricSegments - 1.0, combinedEdgeID); + float negAbsRadsPerSegment = -abs(radsPerSegment); + float maxRotation0 = (1.0 + combinedEdgeID) * abs(radsPerSegment); + for (int exp = 5 /*max resolve level*/ - 1; exp >= 0; --exp) { + // Test the parametric edge at lastParametricEdgeID + 2^exp. + float testParametricID = lastParametricEdgeID + exp2(float(exp)); + if (testParametricID <= maxParametricEdgeID) { + float2 testTan = fma(float2(testParametricID), A, B_); + testTan = fma(float2(testParametricID), testTan, C_); + float cosRotation = dot(normalize(testTan), tan0); + float maxRotation = fma(testParametricID, negAbsRadsPerSegment, maxRotation0); + maxRotation = min(maxRotation, $PI); + // Is rotation <= maxRotation? (i.e., is the number of complete radial segments + // behind testT, + testParametricID <= combinedEdgeID?) + if (cosRotation >= cos(maxRotation)) { + // testParametricID is on or before the combinedEdgeID. Keep it! + lastParametricEdgeID = testParametricID; + } + } + } + + // Find the T value of the parametric edge at lastParametricEdgeID. + float parametricT = lastParametricEdgeID / numParametricSegments; + + // Now that we've identified the highest parametric edge on or before the + // combinedEdgeID, the highest radial edge is easy: + float lastRadialEdgeID = combinedEdgeID - lastParametricEdgeID; + + // Find the angle of tan0, i.e. the angle between tan0 and the positive x axis. + float angle0 = acos(clamp(tan0.x, -1.0, 1.0)); + angle0 = tan0.y >= 0.0 ? angle0 : -angle0; + + // Find the tangent vector on the edge at lastRadialEdgeID. By construction it is already + // normalized. + float radialAngle = fma(lastRadialEdgeID, radsPerSegment, angle0); + tangent = float2(cos(radialAngle), sin(radialAngle)); + float2 norm = float2(-tangent.y, tangent.x); + + // Find the T value where the tangent is orthogonal to norm. This is a quadratic: + // + // dot(norm, Tangent_Direction(T)) == 0 + // + // |T^2| + // norm * |A 2B C| * |T | == 0 + // |. . .| |1 | + // + float a=dot(norm,A), b_over_2=dot(norm,B), c=dot(norm,C); + float discr_over_4 = max(b_over_2*b_over_2 - a*c, 0.0); + float q = sqrt(discr_over_4); + if (b_over_2 > 0.0) { + q = -q; + } + q -= b_over_2; + + // Roots are q/a and c/q. Since each curve section does not inflect or rotate more than 180 + // degrees, there can only be one tangent orthogonal to "norm" inside 0..1. Pick the root + // nearest .5. + float _5qa = -.5*q*a; + float2 root = (abs(fma(q,q,_5qa)) < abs(fma(a,c,_5qa))) ? float2(q,a) : float2(c,q); + float radialT = (root.t != 0.0) ? root.s / root.t : 0.0; + radialT = clamp(radialT, 0.0, 1.0); + + if (lastRadialEdgeID == 0.0) { + // The root finder above can become unstable when lastRadialEdgeID == 0 (e.g., if + // there are roots at exatly 0 and 1 both). radialT should always == 0 in this case. + radialT = 0.0; + } + + // Now that we've identified the T values of the last parametric and radial edges, our final + // T value for combinedEdgeID is whichever is larger. + float T = max(parametricT, radialT); + + // Evaluate the cubic at T. Use De Casteljau's for its accuracy and stability. + float2 ab = $unchecked_mix(p0, p1, T); + float2 bc = $unchecked_mix(p1, p2, T); + float2 cd = $unchecked_mix(p2, p3, T); + float2 abc = $unchecked_mix(ab, bc, T); + float2 bcd = $unchecked_mix(bc, cd, T); + float2 abcd = $unchecked_mix(abc, bcd, T); + + // Evaluate the conic weight at T. + float u = $unchecked_mix(1.0, w, T); + float v = w + 1 - u; // == mix(w, 1, T) + float uv = $unchecked_mix(u, v, T); + + // If we went with T=parametricT, then update the tangent. Otherwise leave it at the radial + // tangent found previously. (In the event that parametricT == radialT, we keep the radial + // tangent.) + if (T != radialT) { + // We must re-normalize here because the tangent is determined by the curve coefficients + tangent = w >= 0.0 ? $robust_normalize_diff(bc*u, ab*v) + : $robust_normalize_diff(bcd, abc); + } + + strokeCoord = (w >= 0.0) ? abc/uv : abcd; + } else { + // Edges at the beginning and end of the strip use exact endpoints and tangents. This + // ensures crack-free seaming between instances. + tangent = (combinedEdgeID == 0) ? tan0 : tan1; + strokeCoord = (combinedEdgeID == 0) ? p0 : p3; + } + + // At this point 'tangent' is normalized, so the orthogonal vector is also normalized. + float2 ortho = float2(tangent.y, -tangent.x); + strokeCoord += ortho * (strokeRadius * strokeOutset); + + if (isHairline) { + // Hairline case. The scale and skew already happened before tessellation. + // TODO: There's probably a more efficient way to tessellate the hairline that lets us + // avoid inverting the affine matrix to get back to local coords, but it's just a 2x2 so + // this works for now. + return float4(strokeCoord + translate, inverse(affineMatrix) * strokeCoord); + } else { + // Normal case. Do the transform after tessellation. + return float4(affineMatrix * strokeCoord + translate, strokeCoord); + } +} diff --git a/gfx/skia/skia/src/sksl/sksl_public.sksl b/gfx/skia/skia/src/sksl/sksl_public.sksl new file mode 100644 index 0000000000..1168612c70 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_public.sksl @@ -0,0 +1,10 @@ +// SkSL intrinsics that are not part of GLSL + +// Color space transformation, between the working (destination) space and fixed (known) spaces: +$pure half3 toLinearSrgb(half3 color); +$pure half3 fromLinearSrgb(half3 color); + +// SkSL intrinsics that reflect Skia's C++ object model: + half4 $eval(float2 coords, shader s); + half4 $eval(half4 color, colorFilter f); + half4 $eval(half4 src, half4 dst, blender b); diff --git a/gfx/skia/skia/src/sksl/sksl_rt_shader.sksl b/gfx/skia/skia/src/sksl/sksl_rt_shader.sksl new file mode 100644 index 0000000000..abae14745b --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_rt_shader.sksl @@ -0,0 +1 @@ +layout(builtin=15) float4 sk_FragCoord; diff --git a/gfx/skia/skia/src/sksl/sksl_shared.sksl b/gfx/skia/skia/src/sksl/sksl_shared.sksl new file mode 100644 index 0000000000..3720e4c872 --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_shared.sksl @@ -0,0 +1,449 @@ +// Intrinsics that are available to public SkSL (SkRuntimeEffect) + +// See "The OpenGL ES Shading Language, Section 8" + +// 8.1 : Angle and Trigonometry Functions +$pure $genType radians($genType degrees); +$pure $genHType radians($genHType degrees); +$pure $genType degrees($genType radians); +$pure $genHType degrees($genHType radians); + +$pure $genType sin($genType angle); +$pure $genHType sin($genHType angle); +$pure $genType cos($genType angle); +$pure $genHType cos($genHType angle); +$pure $genType tan($genType angle); +$pure $genHType tan($genHType angle); + +$pure $genType asin($genType x); +$pure $genHType asin($genHType x); +$pure $genType acos($genType x); +$pure $genHType acos($genHType x); +$pure $genType atan($genType y, $genType x); +$pure $genHType atan($genHType y, $genHType x); +$pure $genType atan($genType y_over_x); +$pure $genHType atan($genHType y_over_x); + +// 8.1 : Angle and Trigonometry Functions (GLSL ES 3.0) +$pure $es3 $genType sinh($genType x); +$pure $es3 $genHType sinh($genHType x); +$pure $es3 $genType cosh($genType x); +$pure $es3 $genHType cosh($genHType x); +$pure $es3 $genType tanh($genType x); +$pure $es3 $genHType tanh($genHType x); +$pure $es3 $genType asinh($genType x); +$pure $es3 $genHType asinh($genHType x); +$pure $es3 $genType acosh($genType x); +$pure $es3 $genHType acosh($genHType x); +$pure $es3 $genType atanh($genType x); +$pure $es3 $genHType atanh($genHType x); + +// 8.2 : Exponential Functions +$pure $genType pow($genType x, $genType y); +$pure $genHType pow($genHType x, $genHType y); +$pure $genType exp($genType x); +$pure $genHType exp($genHType x); +$pure $genType log($genType x); +$pure $genHType log($genHType x); +$pure $genType exp2($genType x); +$pure $genHType exp2($genHType x); +$pure $genType log2($genType x); +$pure $genHType log2($genHType x); + +$pure $genType sqrt($genType x); +$pure $genHType sqrt($genHType x); +$pure $genType inversesqrt($genType x); +$pure $genHType inversesqrt($genHType x); + +// 8.3 : Common Functions +$pure $genType abs($genType x); +$pure $genHType abs($genHType x); +$pure $genType sign($genType x); +$pure $genHType sign($genHType x); +$pure $genType floor($genType x); +$pure $genHType floor($genHType x); +$pure $genType ceil($genType x); +$pure $genHType ceil($genHType x); +$pure $genType fract($genType x); +$pure $genHType fract($genHType x); +$pure $genType mod($genType x, float y); +$pure $genType mod($genType x, $genType y); +$pure $genHType mod($genHType x, half y); +$pure $genHType mod($genHType x, $genHType y); + +$pure $genType min($genType x, $genType y); +$pure $genType min($genType x, float y); +$pure $genHType min($genHType x, $genHType y); +$pure $genHType min($genHType x, half y); +$pure $genType max($genType x, $genType y); +$pure $genType max($genType x, float y); +$pure $genHType max($genHType x, $genHType y); +$pure $genHType max($genHType x, half y); +$pure $genType clamp($genType x, $genType minVal, $genType maxVal); +$pure $genType clamp($genType x, float minVal, float maxVal); +$pure $genHType clamp($genHType x, $genHType minVal, $genHType maxVal); +$pure $genHType clamp($genHType x, half minVal, half maxVal); +$pure $genType saturate($genType x); // SkSL extension +$pure $genHType saturate($genHType x); // SkSL extension +$pure $genType mix($genType x, $genType y, $genType a); +$pure $genType mix($genType x, $genType y, float a); +$pure $genHType mix($genHType x, $genHType y, $genHType a); +$pure $genHType mix($genHType x, $genHType y, half a); +$pure $genType step($genType edge, $genType x); +$pure $genType step(float edge, $genType x); +$pure $genHType step($genHType edge, $genHType x); +$pure $genHType step(half edge, $genHType x); +$pure $genType smoothstep($genType edge0, $genType edge1, $genType x); +$pure $genType smoothstep(float edge0, float edge1, $genType x); +$pure $genHType smoothstep($genHType edge0, $genHType edge1, $genHType x); +$pure $genHType smoothstep(half edge0, half edge1, $genHType x); + +// 8.3 : Common Functions (GLSL ES 3.0) +$pure $es3 $genIType abs($genIType x); +$pure $es3 $genIType sign($genIType x); +$pure $es3 $genIType floatBitsToInt ($genType value); +$pure $es3 $genUType floatBitsToUint($genType value); +$pure $es3 $genType intBitsToFloat ($genIType value); +$pure $es3 $genType uintBitsToFloat($genUType value); +$pure $es3 $genType trunc($genType x); +$pure $es3 $genHType trunc($genHType x); +$pure $es3 $genType round($genType x); +$pure $es3 $genHType round($genHType x); +$pure $es3 $genType roundEven($genType x); +$pure $es3 $genHType roundEven($genHType x); +$pure $es3 $genIType min($genIType x, $genIType y); +$pure $es3 $genIType min($genIType x, int y); +$pure $es3 $genUType min($genUType x, $genUType y); +$pure $es3 $genUType min($genUType x, uint y); +$pure $es3 $genIType max($genIType x, $genIType y); +$pure $es3 $genIType max($genIType x, int y); +$pure $es3 $genUType max($genUType x, $genUType y); +$pure $es3 $genUType max($genUType x, uint y); +$pure $es3 $genIType clamp($genIType x, $genIType minVal, $genIType maxVal); +$pure $es3 $genIType clamp($genIType x, int minVal, int maxVal); +$pure $es3 $genUType clamp($genUType x, $genUType minVal, $genUType maxVal); +$pure $es3 $genUType clamp($genUType x, uint minVal, uint maxVal); +$pure $es3 $genType mix($genType x, $genType y, $genBType a); +$pure $es3 $genHType mix($genHType x, $genHType y, $genBType a); + +// 8.3 : Common Functions (GLSL ES 3.0) -- cannot be used in constant-expressions +$pure $es3 $genBType isnan($genType x); +$pure $es3 $genBType isnan($genHType x); +$pure $es3 $genBType isinf($genType x); +$pure $es3 $genBType isinf($genHType x); +$pure $es3 $genType modf($genType x, out $genType i); +$pure $es3 $genHType modf($genHType x, out $genHType i); + +// 8.4 : Floating-Point Pack and Unpack Functions (GLSL ES 3.0) +$pure $es3 uint packUnorm2x16(float2 v); +$pure $es3 float2 unpackUnorm2x16(uint p); + +// 8.5 : Geometric Functions +$pure float length($genType x); +$pure half length($genHType x); +$pure float distance($genType p0, $genType p1); +$pure half distance($genHType p0, $genHType p1); +$pure float dot($genType x, $genType y); +$pure half dot($genHType x, $genHType y); +$pure float3 cross(float3 x, float3 y); +$pure half3 cross(half3 x, half3 y); +$pure $genType normalize($genType x); +$pure $genHType normalize($genHType x); +$pure $genType faceforward($genType N, $genType I, $genType Nref); +$pure $genHType faceforward($genHType N, $genHType I, $genHType Nref); +$pure $genType reflect($genType I, $genType N); +$pure $genHType reflect($genHType I, $genHType N); +$pure $genType refract($genType I, $genType N, float eta); +$pure $genHType refract($genHType I, $genHType N, half eta); + +// 8.6 : Matrix Functions +$pure $squareMat matrixCompMult($squareMat x, $squareMat y); +$pure $squareHMat matrixCompMult($squareHMat x, $squareHMat y); +$pure $es3 $mat matrixCompMult($mat x, $mat y); +$pure $es3 $hmat matrixCompMult($hmat x, $hmat y); + +// 8.6 : Matrix Functions (GLSL 1.4, poly-filled by SkSL as needed) +$pure $squareMat inverse($squareMat m); +$pure $squareHMat inverse($squareHMat m); + +// 8.6 : Matrix Functions (GLSL ES 3.0) +$pure $es3 float determinant($squareMat m); +$pure $es3 half determinant($squareHMat m); +$pure $es3 $squareMat transpose($squareMat m); +$pure $es3 $squareHMat transpose($squareHMat m); +$pure $es3 float2x3 transpose(float3x2 m); +$pure $es3 half2x3 transpose(half3x2 m); +$pure $es3 float2x4 transpose(float4x2 m); +$pure $es3 half2x4 transpose(half4x2 m); +$pure $es3 float3x2 transpose(float2x3 m); +$pure $es3 half3x2 transpose(half2x3 m); +$pure $es3 float3x4 transpose(float4x3 m); +$pure $es3 half3x4 transpose(half4x3 m); +$pure $es3 float4x2 transpose(float2x4 m); +$pure $es3 half4x2 transpose(half2x4 m); +$pure $es3 float4x3 transpose(float3x4 m); +$pure $es3 half4x3 transpose(half3x4 m); +$pure $es3 $squareMat outerProduct($vec c, $vec r); +$pure $es3 $squareHMat outerProduct($hvec c, $hvec r); +$pure $es3 float2x3 outerProduct(float3 c, float2 r); +$pure $es3 half2x3 outerProduct(half3 c, half2 r); +$pure $es3 float3x2 outerProduct(float2 c, float3 r); +$pure $es3 half3x2 outerProduct(half2 c, half3 r); +$pure $es3 float2x4 outerProduct(float4 c, float2 r); +$pure $es3 half2x4 outerProduct(half4 c, half2 r); +$pure $es3 float4x2 outerProduct(float2 c, float4 r); +$pure $es3 half4x2 outerProduct(half2 c, half4 r); +$pure $es3 float3x4 outerProduct(float4 c, float3 r); +$pure $es3 half3x4 outerProduct(half4 c, half3 r); +$pure $es3 float4x3 outerProduct(float3 c, float4 r); +$pure $es3 half4x3 outerProduct(half3 c, half4 r); + +// 8.7 : Vector Relational Functions +$pure $bvec lessThan($vec x, $vec y); +$pure $bvec lessThan($hvec x, $hvec y); +$pure $bvec lessThan($ivec x, $ivec y); +$pure $bvec lessThan($svec x, $svec y); +$pure $bvec lessThanEqual($vec x, $vec y); +$pure $bvec lessThanEqual($hvec x, $hvec y); +$pure $bvec lessThanEqual($ivec x, $ivec y); +$pure $bvec lessThanEqual($svec x, $svec y); +$pure $bvec greaterThan($vec x, $vec y); +$pure $bvec greaterThan($hvec x, $hvec y); +$pure $bvec greaterThan($ivec x, $ivec y); +$pure $bvec greaterThan($svec x, $svec y); +$pure $bvec greaterThanEqual($vec x, $vec y); +$pure $bvec greaterThanEqual($hvec x, $hvec y); +$pure $bvec greaterThanEqual($ivec x, $ivec y); +$pure $bvec greaterThanEqual($svec x, $svec y); +$pure $bvec equal($vec x, $vec y); +$pure $bvec equal($hvec x, $hvec y); +$pure $bvec equal($ivec x, $ivec y); +$pure $bvec equal($svec x, $svec y); +$pure $bvec equal($bvec x, $bvec y); +$pure $bvec notEqual($vec x, $vec y); +$pure $bvec notEqual($hvec x, $hvec y); +$pure $bvec notEqual($ivec x, $ivec y); +$pure $bvec notEqual($svec x, $svec y); +$pure $bvec notEqual($bvec x, $bvec y); + +$pure $es3 $bvec lessThan($usvec x, $usvec y); +$pure $es3 $bvec lessThan($uvec x, $uvec y); +$pure $es3 $bvec lessThanEqual($uvec x, $uvec y); +$pure $es3 $bvec lessThanEqual($usvec x, $usvec y); +$pure $es3 $bvec greaterThan($uvec x, $uvec y); +$pure $es3 $bvec greaterThan($usvec x, $usvec y); +$pure $es3 $bvec greaterThanEqual($uvec x, $uvec y); +$pure $es3 $bvec greaterThanEqual($usvec x, $usvec y); +$pure $es3 $bvec equal($uvec x, $uvec y); +$pure $es3 $bvec equal($usvec x, $usvec y); +$pure $es3 $bvec notEqual($uvec x, $uvec y); +$pure $es3 $bvec notEqual($usvec x, $usvec y); + +$pure bool any($bvec x); +$pure bool all($bvec x); +$pure $bvec not($bvec x); + +// 8.9 : Fragment Processing Functions (GLSL ES 3.0) +$pure $es3 $genType dFdx($genType p); +$pure $es3 $genType dFdy($genType p); +$pure $es3 $genHType dFdx($genHType p); +$pure $es3 $genHType dFdy($genHType p); +$pure $es3 $genType fwidth($genType p); +$pure $es3 $genHType fwidth($genHType p); + + +// SkSL utility functions + +// The max() guards against division by zero when the incoming color is transparent black +$pure half4 unpremul(half4 color) { return half4 (color.rgb / max(color.a, 0.0001), color.a); } +$pure float4 unpremul(float4 color) { return float4(color.rgb / max(color.a, 0.0001), color.a); } + +// Similar, but used for polar-space CSS colors +$export $pure half4 $unpremul_polar(half4 color) { + return half4(color.r, color.gb / max(color.a, 0.0001), color.a); +} + +// Convert RGBA -> HSLA (including unpremul). +// +// Based on work by Sam Hocevar, Emil Persson, and Ian Taylor [1][2][3]. High-level ideas: +// +// - minimize the number of branches by sorting and computing the hue phase in parallel (vec4s) +// +// - trade the third sorting branch for a potentially faster std::min and leaving 2nd/3rd +// channels unsorted (based on the observation that swapping both the channels and the bias sign +// has no effect under abs) +// +// - use epsilon offsets for denominators, to avoid explicit zero-checks +// +// An additional trick we employ is deferring premul->unpremul conversion until the very end: the +// alpha factor gets naturally simplified for H and S, and only L requires a dedicated unpremul +// division (so we trade three divs for one). +// +// [1] http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +// [2] http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl +// [3] http://www.chilliant.com/rgb2hsv.html + +$export $pure half4 $rgb_to_hsl(half3 c, half a) { + half4 p = (c.g < c.b) ? half4(c.bg, -1, 2/3.0) + : half4(c.gb, 0, -1/3.0); + half4 q = (c.r < p.x) ? half4(p.x, c.r, p.yw) + : half4(c.r, p.x, p.yz); + + // q.x -> max channel value + // q.yz -> 2nd/3rd channel values (unsorted) + // q.w -> bias value dependent on max channel selection + + const half kEps = 0.0001; + half pmV = q.x; + half pmC = pmV - min(q.y, q.z); + half pmL = pmV - pmC * 0.5; + half H = abs(q.w + (q.y - q.z) / (pmC * 6 + kEps)); + half S = pmC / (a + kEps - abs(pmL * 2 - a)); + half L = pmL / (a + kEps); + + return half4(H, S, L, a); +} + +// Convert HSLA -> RGBA (including clamp and premul). +// +// Based on work by Sam Hocevar, Emil Persson, and Ian Taylor [1][2][3]. +// +// [1] http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv +// [2] http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl +// [3] http://www.chilliant.com/rgb2hsv.html + +$export $pure half3 $hsl_to_rgb(half3 hsl) { + half C = (1 - abs(2 * hsl.z - 1)) * hsl.y; + half3 p = hsl.xxx + half3(0, 2/3.0, 1/3.0); + half3 q = saturate(abs(fract(p) * 6 - 3) - 1); + + return (q - 0.5) * C + hsl.z; +} + +$export $pure half4 $hsl_to_rgb(half3 hsl, half a) { + return saturate(half4($hsl_to_rgb(hsl) * a, a)); +} + +// Color conversion functions used in gradient interpolation, based on +// https://www.w3.org/TR/css-color-4/#color-conversion-code +// TODO(skia:13108): For all of these, we can eliminate any linear math at the beginning +// (by removing the corresponding linear math at the end of the CPU code). +$export $pure half3 $css_lab_to_xyz(half3 lab) { + const half k = 24389 / 27.0; + const half e = 216 / 24389.0; + + half3 f; + f[1] = (lab[0] + 16) / 116; + f[0] = (lab[1] / 500) + f[1]; + f[2] = f[1] - (lab[2] / 200); + + half3 f_cubed = pow(f, half3(3)); + + half3 xyz = half3( + f_cubed[0] > e ? f_cubed[0] : (116 * f[0] - 16) / k, + lab[0] > k * e ? f_cubed[1] : lab[0] / k, + f_cubed[2] > e ? f_cubed[2] : (116 * f[2] - 16) / k + ); + + const half3 D50 = half3(0.3457 / 0.3585, 1.0, (1.0 - 0.3457 - 0.3585) / 0.3585); + return xyz * D50; +} + +// Skia stores all polar colors with hue in the first component, so this "LCH -> Lab" transform +// actually takes "HCL". This is also used to do the same polar transform for OkHCL to OkLAB. +// See similar comments & logic in SkGradientShaderBase.cpp. +$pure half3 $css_hcl_to_lab(half3 hcl) { + return half3( + hcl[2], + hcl[1] * cos(radians(hcl[0])), + hcl[1] * sin(radians(hcl[0])) + ); +} + +$export $pure half3 $css_hcl_to_xyz(half3 hcl) { + return $css_lab_to_xyz($css_hcl_to_lab(hcl)); +} + +$export $pure half3 $css_oklab_to_linear_srgb(half3 oklab) { + half l_ = oklab.x + 0.3963377774 * oklab.y + 0.2158037573 * oklab.z, + m_ = oklab.x - 0.1055613458 * oklab.y - 0.0638541728 * oklab.z, + s_ = oklab.x - 0.0894841775 * oklab.y - 1.2914855480 * oklab.z; + + half l = l_*l_*l_, + m = m_*m_*m_, + s = s_*s_*s_; + + return half3( + +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s + ); +} + +$export $pure half3 $css_okhcl_to_linear_srgb(half3 okhcl) { + return $css_oklab_to_linear_srgb($css_hcl_to_lab(okhcl)); +} + +// TODO(skia:13108): Use our optimized version (though it has different range) +// Doing so might require fixing (re-deriving?) the math for the HWB version below +$export $pure half3 $css_hsl_to_srgb(half3 hsl) { + hsl.x = mod(hsl.x, 360); + if (hsl.x < 0) { + hsl.x += 360; + } + + hsl.yz /= 100; + + half3 k = mod(half3(0, 8, 4) + hsl.x/30, 12); + half a = hsl.y * min(hsl.z, 1 - hsl.z); + return hsl.z - a * clamp(min(k - 3, 9 - k), -1, 1); +} + +$export $pure half3 $css_hwb_to_srgb(half3 hwb) { + hwb.yz /= 100; + if (hwb.y + hwb.z >= 1) { + half gray = hwb.y / (hwb.y + hwb.z); + return half3(gray); + } + half3 rgb = $css_hsl_to_srgb(half3(hwb.x, 100, 50)); + rgb *= (1 - hwb.y - hwb.z); + rgb += hwb.y; + return rgb; +} + +/* + * The actual output color space of this function depends on the input color space + * (it might be sRGB, linear sRGB, or linear XYZ). The actual space is what's stored + * in the gradient/SkColor4fXformer's fIntermediateColorSpace. + */ +$export $pure half4 $interpolated_to_rgb_unpremul(half4 color, int colorSpace, int doUnpremul) { + const int kDestination = 0; + const int kSRGBLinear = 1; + const int kLab = 2; + const int kOKLab = 3; + const int kLCH = 4; + const int kOKLCH = 5; + const int kSRGB = 6; + const int kHSL = 7; + const int kHWB = 8; + + if (bool(doUnpremul)) { + switch (colorSpace) { + case kLab: + case kOKLab: color = unpremul(color); break; + case kLCH: + case kOKLCH: + case kHSL: + case kHWB: color = $unpremul_polar(color); break; + } + } + switch (colorSpace) { + case kLab: { color.rgb = $css_lab_to_xyz(color.rgb); break; } + case kOKLab: { color.rgb = $css_oklab_to_linear_srgb(color.rgb); break; } + case kLCH: { color.rgb = $css_hcl_to_xyz(color.rgb); break; } + case kOKLCH: { color.rgb = $css_okhcl_to_linear_srgb(color.rgb); break; } + case kHSL: { color.rgb = $css_hsl_to_srgb(color.rgb); break; } + case kHWB: { color.rgb = $css_hwb_to_srgb(color.rgb); break; } + } + return color; +} diff --git a/gfx/skia/skia/src/sksl/sksl_vert.sksl b/gfx/skia/skia/src/sksl/sksl_vert.sksl new file mode 100644 index 0000000000..de730b3fbf --- /dev/null +++ b/gfx/skia/skia/src/sksl/sksl_vert.sksl @@ -0,0 +1,9 @@ +// defines built-in interfaces supported by SkSL vertex shaders + +out sk_PerVertex { + layout(builtin=0) float4 sk_Position; + layout(builtin=1) float sk_PointSize; +}; + +layout(builtin=42) in int sk_VertexID; +layout(builtin=43) in int sk_InstanceID; diff --git a/gfx/skia/skia/src/sksl/spirv.h b/gfx/skia/skia/src/sksl/spirv.h new file mode 100644 index 0000000000..22821ed862 --- /dev/null +++ b/gfx/skia/skia/src/sksl/spirv.h @@ -0,0 +1,870 @@ +/* +** Copyright (c) 2014-2016 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and/or associated documentation files (the "Materials"), +** to deal in the Materials without restriction, including without limitation +** the rights to use, copy, modify, merge, publish, distribute, sublicense, +** and/or sell copies of the Materials, and to permit persons to whom the +** Materials are furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Materials. +** +** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +** IN THE MATERIALS. +*/ + +/* +** This header is automatically generated by the same tool that creates +** the Binary Section of the SPIR-V specification. +*/ + +/* +** Enumeration tokens for SPIR-V, in various styles: +** C, C++, C++11, JSON, Lua, Python +** +** - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL +** - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL +** - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL +** - Lua will use tables, e.g.: spv.SourceLanguage.GLSL +** - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL'] +** +** Some tokens act like mask values, which can be OR'd together, +** while others are mutually exclusive. The mask-like ones have +** "Mask" in their name, and a parallel enum that has the shift +** amount (1 << x) for each corresponding enumerant. +*/ + +#ifndef spirv_H +#define spirv_H + +typedef unsigned int SpvId; + +#define SPV_VERSION 0x10000 +#define SPV_REVISION 4 + +static const unsigned int SpvMagicNumber = 0x07230203; +static const unsigned int SpvVersion = 0x00010000; +static const unsigned int SpvRevision = 4; +static const unsigned int SpvOpCodeMask = 0xffff; +static const unsigned int SpvWordCountShift = 16; + +typedef enum SpvSourceLanguage_ { + SpvSourceLanguageUnknown = 0, + SpvSourceLanguageESSL = 1, + SpvSourceLanguageGLSL = 2, + SpvSourceLanguageOpenCL_C = 3, + SpvSourceLanguageOpenCL_CPP = 4, +} SpvSourceLanguage; + +typedef enum SpvExecutionModel_ { + SpvExecutionModelVertex = 0, + SpvExecutionModelTessellationControl = 1, + SpvExecutionModelTessellationEvaluation = 2, + SpvExecutionModelGeometry = 3, + SpvExecutionModelFragment = 4, + SpvExecutionModelGLCompute = 5, + SpvExecutionModelKernel = 6, +} SpvExecutionModel; + +typedef enum SpvAddressingModel_ { + SpvAddressingModelLogical = 0, + SpvAddressingModelPhysical32 = 1, + SpvAddressingModelPhysical64 = 2, +} SpvAddressingModel; + +typedef enum SpvMemoryModel_ { + SpvMemoryModelSimple = 0, + SpvMemoryModelGLSL450 = 1, + SpvMemoryModelOpenCL = 2, +} SpvMemoryModel; + +typedef enum SpvExecutionMode_ { + SpvExecutionModeInvocations = 0, + SpvExecutionModeSpacingEqual = 1, + SpvExecutionModeSpacingFractionalEven = 2, + SpvExecutionModeSpacingFractionalOdd = 3, + SpvExecutionModeVertexOrderCw = 4, + SpvExecutionModeVertexOrderCcw = 5, + SpvExecutionModePixelCenterInteger = 6, + SpvExecutionModeOriginUpperLeft = 7, + SpvExecutionModeOriginLowerLeft = 8, + SpvExecutionModeEarlyFragmentTests = 9, + SpvExecutionModePointMode = 10, + SpvExecutionModeXfb = 11, + SpvExecutionModeDepthReplacing = 12, + SpvExecutionModeDepthGreater = 14, + SpvExecutionModeDepthLess = 15, + SpvExecutionModeDepthUnchanged = 16, + SpvExecutionModeLocalSize = 17, + SpvExecutionModeLocalSizeHint = 18, + SpvExecutionModeInputPoints = 19, + SpvExecutionModeInputLines = 20, + SpvExecutionModeInputLinesAdjacency = 21, + SpvExecutionModeTriangles = 22, + SpvExecutionModeInputTrianglesAdjacency = 23, + SpvExecutionModeQuads = 24, + SpvExecutionModeIsolines = 25, + SpvExecutionModeOutputVertices = 26, + SpvExecutionModeOutputPoints = 27, + SpvExecutionModeOutputLineStrip = 28, + SpvExecutionModeOutputTriangleStrip = 29, + SpvExecutionModeVecTypeHint = 30, + SpvExecutionModeContractionOff = 31, +} SpvExecutionMode; + +typedef enum SpvStorageClass_ { + SpvStorageClassUniformConstant = 0, + SpvStorageClassInput = 1, + SpvStorageClassUniform = 2, + SpvStorageClassOutput = 3, + SpvStorageClassWorkgroup = 4, + SpvStorageClassCrossWorkgroup = 5, + SpvStorageClassPrivate = 6, + SpvStorageClassFunction = 7, + SpvStorageClassGeneric = 8, + SpvStorageClassPushConstant = 9, + SpvStorageClassAtomicCounter = 10, + SpvStorageClassImage = 11, +} SpvStorageClass; + +typedef enum SpvDim_ { + SpvDim1D = 0, + SpvDim2D = 1, + SpvDim3D = 2, + SpvDimCube = 3, + SpvDimRect = 4, + SpvDimBuffer = 5, + SpvDimSubpassData = 6, +} SpvDim; + +typedef enum SpvSamplerAddressingMode_ { + SpvSamplerAddressingModeNone = 0, + SpvSamplerAddressingModeClampToEdge = 1, + SpvSamplerAddressingModeClamp = 2, + SpvSamplerAddressingModeRepeat = 3, + SpvSamplerAddressingModeRepeatMirrored = 4, +} SpvSamplerAddressingMode; + +typedef enum SpvSamplerFilterMode_ { + SpvSamplerFilterModeNearest = 0, + SpvSamplerFilterModeLinear = 1, +} SpvSamplerFilterMode; + +typedef enum SpvImageFormat_ { + SpvImageFormatUnknown = 0, + SpvImageFormatRgba32f = 1, + SpvImageFormatRgba16f = 2, + SpvImageFormatR32f = 3, + SpvImageFormatRgba8 = 4, + SpvImageFormatRgba8Snorm = 5, + SpvImageFormatRg32f = 6, + SpvImageFormatRg16f = 7, + SpvImageFormatR11fG11fB10f = 8, + SpvImageFormatR16f = 9, + SpvImageFormatRgba16 = 10, + SpvImageFormatRgb10A2 = 11, + SpvImageFormatRg16 = 12, + SpvImageFormatRg8 = 13, + SpvImageFormatR16 = 14, + SpvImageFormatR8 = 15, + SpvImageFormatRgba16Snorm = 16, + SpvImageFormatRg16Snorm = 17, + SpvImageFormatRg8Snorm = 18, + SpvImageFormatR16Snorm = 19, + SpvImageFormatR8Snorm = 20, + SpvImageFormatRgba32i = 21, + SpvImageFormatRgba16i = 22, + SpvImageFormatRgba8i = 23, + SpvImageFormatR32i = 24, + SpvImageFormatRg32i = 25, + SpvImageFormatRg16i = 26, + SpvImageFormatRg8i = 27, + SpvImageFormatR16i = 28, + SpvImageFormatR8i = 29, + SpvImageFormatRgba32ui = 30, + SpvImageFormatRgba16ui = 31, + SpvImageFormatRgba8ui = 32, + SpvImageFormatR32ui = 33, + SpvImageFormatRgb10a2ui = 34, + SpvImageFormatRg32ui = 35, + SpvImageFormatRg16ui = 36, + SpvImageFormatRg8ui = 37, + SpvImageFormatR16ui = 38, + SpvImageFormatR8ui = 39, +} SpvImageFormat; + +typedef enum SpvImageChannelOrder_ { + SpvImageChannelOrderR = 0, + SpvImageChannelOrderA = 1, + SpvImageChannelOrderRG = 2, + SpvImageChannelOrderRA = 3, + SpvImageChannelOrderRGB = 4, + SpvImageChannelOrderRGBA = 5, + SpvImageChannelOrderBGRA = 6, + SpvImageChannelOrderARGB = 7, + SpvImageChannelOrderIntensity = 8, + SpvImageChannelOrderLuminance = 9, + SpvImageChannelOrderRx = 10, + SpvImageChannelOrderRGx = 11, + SpvImageChannelOrderRGBx = 12, + SpvImageChannelOrderDepth = 13, + SpvImageChannelOrderDepthStencil = 14, + SpvImageChannelOrdersRGB = 15, + SpvImageChannelOrdersRGBx = 16, + SpvImageChannelOrdersRGBA = 17, + SpvImageChannelOrdersBGRA = 18, +} SpvImageChannelOrder; + +typedef enum SpvImageChannelDataType_ { + SpvImageChannelDataTypeSnormInt8 = 0, + SpvImageChannelDataTypeSnormInt16 = 1, + SpvImageChannelDataTypeUnormInt8 = 2, + SpvImageChannelDataTypeUnormInt16 = 3, + SpvImageChannelDataTypeUnormShort565 = 4, + SpvImageChannelDataTypeUnormShort555 = 5, + SpvImageChannelDataTypeUnormInt101010 = 6, + SpvImageChannelDataTypeSignedInt8 = 7, + SpvImageChannelDataTypeSignedInt16 = 8, + SpvImageChannelDataTypeSignedInt32 = 9, + SpvImageChannelDataTypeUnsignedInt8 = 10, + SpvImageChannelDataTypeUnsignedInt16 = 11, + SpvImageChannelDataTypeUnsignedInt32 = 12, + SpvImageChannelDataTypeHalfFloat = 13, + SpvImageChannelDataTypeFloat = 14, + SpvImageChannelDataTypeUnormInt24 = 15, + SpvImageChannelDataTypeUnormInt101010_2 = 16, +} SpvImageChannelDataType; + +typedef enum SpvImageOperandsShift_ { + SpvImageOperandsBiasShift = 0, + SpvImageOperandsLodShift = 1, + SpvImageOperandsGradShift = 2, + SpvImageOperandsConstOffsetShift = 3, + SpvImageOperandsOffsetShift = 4, + SpvImageOperandsConstOffsetsShift = 5, + SpvImageOperandsSampleShift = 6, + SpvImageOperandsMinLodShift = 7, +} SpvImageOperandsShift; + +typedef enum SpvImageOperandsMask_ { + SpvImageOperandsMaskNone = 0, + SpvImageOperandsBiasMask = 0x00000001, + SpvImageOperandsLodMask = 0x00000002, + SpvImageOperandsGradMask = 0x00000004, + SpvImageOperandsConstOffsetMask = 0x00000008, + SpvImageOperandsOffsetMask = 0x00000010, + SpvImageOperandsConstOffsetsMask = 0x00000020, + SpvImageOperandsSampleMask = 0x00000040, + SpvImageOperandsMinLodMask = 0x00000080, +} SpvImageOperandsMask; + +typedef enum SpvFPFastMathModeShift_ { + SpvFPFastMathModeNotNaNShift = 0, + SpvFPFastMathModeNotInfShift = 1, + SpvFPFastMathModeNSZShift = 2, + SpvFPFastMathModeAllowRecipShift = 3, + SpvFPFastMathModeFastShift = 4, +} SpvFPFastMathModeShift; + +typedef enum SpvFPFastMathModeMask_ { + SpvFPFastMathModeMaskNone = 0, + SpvFPFastMathModeNotNaNMask = 0x00000001, + SpvFPFastMathModeNotInfMask = 0x00000002, + SpvFPFastMathModeNSZMask = 0x00000004, + SpvFPFastMathModeAllowRecipMask = 0x00000008, + SpvFPFastMathModeFastMask = 0x00000010, +} SpvFPFastMathModeMask; + +typedef enum SpvFPRoundingMode_ { + SpvFPRoundingModeRTE = 0, + SpvFPRoundingModeRTZ = 1, + SpvFPRoundingModeRTP = 2, + SpvFPRoundingModeRTN = 3, +} SpvFPRoundingMode; + +typedef enum SpvLinkageType_ { + SpvLinkageTypeExport = 0, + SpvLinkageTypeImport = 1, +} SpvLinkageType; + +typedef enum SpvAccessQualifier_ { + SpvAccessQualifierReadOnly = 0, + SpvAccessQualifierWriteOnly = 1, + SpvAccessQualifierReadWrite = 2, +} SpvAccessQualifier; + +typedef enum SpvFunctionParameterAttribute_ { + SpvFunctionParameterAttributeZext = 0, + SpvFunctionParameterAttributeSext = 1, + SpvFunctionParameterAttributeByVal = 2, + SpvFunctionParameterAttributeSret = 3, + SpvFunctionParameterAttributeNoAlias = 4, + SpvFunctionParameterAttributeNoCapture = 5, + SpvFunctionParameterAttributeNoWrite = 6, + SpvFunctionParameterAttributeNoReadWrite = 7, +} SpvFunctionParameterAttribute; + +typedef enum SpvDecoration_ { + SpvDecorationRelaxedPrecision = 0, + SpvDecorationSpecId = 1, + SpvDecorationBlock = 2, + SpvDecorationBufferBlock = 3, + SpvDecorationRowMajor = 4, + SpvDecorationColMajor = 5, + SpvDecorationArrayStride = 6, + SpvDecorationMatrixStride = 7, + SpvDecorationGLSLShared = 8, + SpvDecorationGLSLPacked = 9, + SpvDecorationCPacked = 10, + SpvDecorationBuiltIn = 11, + SpvDecorationNoPerspective = 13, + SpvDecorationFlat = 14, + SpvDecorationPatch = 15, + SpvDecorationCentroid = 16, + SpvDecorationSample = 17, + SpvDecorationInvariant = 18, + SpvDecorationRestrict = 19, + SpvDecorationAliased = 20, + SpvDecorationVolatile = 21, + SpvDecorationConstant = 22, + SpvDecorationCoherent = 23, + SpvDecorationNonWritable = 24, + SpvDecorationNonReadable = 25, + SpvDecorationUniform = 26, + SpvDecorationSaturatedConversion = 28, + SpvDecorationStream = 29, + SpvDecorationLocation = 30, + SpvDecorationComponent = 31, + SpvDecorationIndex = 32, + SpvDecorationBinding = 33, + SpvDecorationDescriptorSet = 34, + SpvDecorationOffset = 35, + SpvDecorationXfbBuffer = 36, + SpvDecorationXfbStride = 37, + SpvDecorationFuncParamAttr = 38, + SpvDecorationFPRoundingMode = 39, + SpvDecorationFPFastMathMode = 40, + SpvDecorationLinkageAttributes = 41, + SpvDecorationNoContraction = 42, + SpvDecorationInputAttachmentIndex = 43, + SpvDecorationAlignment = 44, +} SpvDecoration; + +typedef enum SpvBuiltIn_ { + SpvBuiltInPosition = 0, + SpvBuiltInPointSize = 1, + SpvBuiltInClipDistance = 3, + SpvBuiltInCullDistance = 4, + SpvBuiltInVertexId = 5, + SpvBuiltInInstanceId = 6, + SpvBuiltInPrimitiveId = 7, + SpvBuiltInInvocationId = 8, + SpvBuiltInLayer = 9, + SpvBuiltInViewportIndex = 10, + SpvBuiltInTessLevelOuter = 11, + SpvBuiltInTessLevelInner = 12, + SpvBuiltInTessCoord = 13, + SpvBuiltInPatchVertices = 14, + SpvBuiltInFragCoord = 15, + SpvBuiltInPointCoord = 16, + SpvBuiltInFrontFacing = 17, + SpvBuiltInSampleId = 18, + SpvBuiltInSamplePosition = 19, + SpvBuiltInSampleMask = 20, + SpvBuiltInFragDepth = 22, + SpvBuiltInHelperInvocation = 23, + SpvBuiltInNumWorkgroups = 24, + SpvBuiltInWorkgroupSize = 25, + SpvBuiltInWorkgroupId = 26, + SpvBuiltInLocalInvocationId = 27, + SpvBuiltInGlobalInvocationId = 28, + SpvBuiltInLocalInvocationIndex = 29, + SpvBuiltInWorkDim = 30, + SpvBuiltInGlobalSize = 31, + SpvBuiltInEnqueuedWorkgroupSize = 32, + SpvBuiltInGlobalOffset = 33, + SpvBuiltInGlobalLinearId = 34, + SpvBuiltInSubgroupSize = 36, + SpvBuiltInSubgroupMaxSize = 37, + SpvBuiltInNumSubgroups = 38, + SpvBuiltInNumEnqueuedSubgroups = 39, + SpvBuiltInSubgroupId = 40, + SpvBuiltInSubgroupLocalInvocationId = 41, + SpvBuiltInVertexIndex = 42, + SpvBuiltInInstanceIndex = 43, +} SpvBuiltIn; + +typedef enum SpvSelectionControlShift_ { + SpvSelectionControlFlattenShift = 0, + SpvSelectionControlDontFlattenShift = 1, +} SpvSelectionControlShift; + +typedef enum SpvSelectionControlMask_ { + SpvSelectionControlMaskNone = 0, + SpvSelectionControlFlattenMask = 0x00000001, + SpvSelectionControlDontFlattenMask = 0x00000002, +} SpvSelectionControlMask; + +typedef enum SpvLoopControlShift_ { + SpvLoopControlUnrollShift = 0, + SpvLoopControlDontUnrollShift = 1, +} SpvLoopControlShift; + +typedef enum SpvLoopControlMask_ { + SpvLoopControlMaskNone = 0, + SpvLoopControlUnrollMask = 0x00000001, + SpvLoopControlDontUnrollMask = 0x00000002, +} SpvLoopControlMask; + +typedef enum SpvFunctionControlShift_ { + SpvFunctionControlInlineShift = 0, + SpvFunctionControlDontInlineShift = 1, + SpvFunctionControlPureShift = 2, + SpvFunctionControlConstShift = 3, +} SpvFunctionControlShift; + +typedef enum SpvFunctionControlMask_ { + SpvFunctionControlMaskNone = 0, + SpvFunctionControlInlineMask = 0x00000001, + SpvFunctionControlDontInlineMask = 0x00000002, + SpvFunctionControlPureMask = 0x00000004, + SpvFunctionControlConstMask = 0x00000008, +} SpvFunctionControlMask; + +typedef enum SpvMemorySemanticsShift_ { + SpvMemorySemanticsAcquireShift = 1, + SpvMemorySemanticsReleaseShift = 2, + SpvMemorySemanticsAcquireReleaseShift = 3, + SpvMemorySemanticsSequentiallyConsistentShift = 4, + SpvMemorySemanticsUniformMemoryShift = 6, + SpvMemorySemanticsSubgroupMemoryShift = 7, + SpvMemorySemanticsWorkgroupMemoryShift = 8, + SpvMemorySemanticsCrossWorkgroupMemoryShift = 9, + SpvMemorySemanticsAtomicCounterMemoryShift = 10, + SpvMemorySemanticsImageMemoryShift = 11, +} SpvMemorySemanticsShift; + +typedef enum SpvMemorySemanticsMask_ { + SpvMemorySemanticsMaskNone = 0, + SpvMemorySemanticsAcquireMask = 0x00000002, + SpvMemorySemanticsReleaseMask = 0x00000004, + SpvMemorySemanticsAcquireReleaseMask = 0x00000008, + SpvMemorySemanticsSequentiallyConsistentMask = 0x00000010, + SpvMemorySemanticsUniformMemoryMask = 0x00000040, + SpvMemorySemanticsSubgroupMemoryMask = 0x00000080, + SpvMemorySemanticsWorkgroupMemoryMask = 0x00000100, + SpvMemorySemanticsCrossWorkgroupMemoryMask = 0x00000200, + SpvMemorySemanticsAtomicCounterMemoryMask = 0x00000400, + SpvMemorySemanticsImageMemoryMask = 0x00000800, +} SpvMemorySemanticsMask; + +typedef enum SpvMemoryAccessShift_ { + SpvMemoryAccessVolatileShift = 0, + SpvMemoryAccessAlignedShift = 1, + SpvMemoryAccessNontemporalShift = 2, +} SpvMemoryAccessShift; + +typedef enum SpvMemoryAccessMask_ { + SpvMemoryAccessMaskNone = 0, + SpvMemoryAccessVolatileMask = 0x00000001, + SpvMemoryAccessAlignedMask = 0x00000002, + SpvMemoryAccessNontemporalMask = 0x00000004, +} SpvMemoryAccessMask; + +typedef enum SpvScope_ { + SpvScopeCrossDevice = 0, + SpvScopeDevice = 1, + SpvScopeWorkgroup = 2, + SpvScopeSubgroup = 3, + SpvScopeInvocation = 4, +} SpvScope; + +typedef enum SpvGroupOperation_ { + SpvGroupOperationReduce = 0, + SpvGroupOperationInclusiveScan = 1, + SpvGroupOperationExclusiveScan = 2, +} SpvGroupOperation; + +typedef enum SpvKernelEnqueueFlags_ { + SpvKernelEnqueueFlagsNoWait = 0, + SpvKernelEnqueueFlagsWaitKernel = 1, + SpvKernelEnqueueFlagsWaitWorkGroup = 2, +} SpvKernelEnqueueFlags; + +typedef enum SpvKernelProfilingInfoShift_ { + SpvKernelProfilingInfoCmdExecTimeShift = 0, +} SpvKernelProfilingInfoShift; + +typedef enum SpvKernelProfilingInfoMask_ { + SpvKernelProfilingInfoMaskNone = 0, + SpvKernelProfilingInfoCmdExecTimeMask = 0x00000001, +} SpvKernelProfilingInfoMask; + +typedef enum SpvCapability_ { + SpvCapabilityMatrix = 0, + SpvCapabilityShader = 1, + SpvCapabilityGeometry = 2, + SpvCapabilityTessellation = 3, + SpvCapabilityAddresses = 4, + SpvCapabilityLinkage = 5, + SpvCapabilityKernel = 6, + SpvCapabilityVector16 = 7, + SpvCapabilityFloat16Buffer = 8, + SpvCapabilityFloat16 = 9, + SpvCapabilityFloat64 = 10, + SpvCapabilityInt64 = 11, + SpvCapabilityInt64Atomics = 12, + SpvCapabilityImageBasic = 13, + SpvCapabilityImageReadWrite = 14, + SpvCapabilityImageMipmap = 15, + SpvCapabilityPipes = 17, + SpvCapabilityGroups = 18, + SpvCapabilityDeviceEnqueue = 19, + SpvCapabilityLiteralSampler = 20, + SpvCapabilityAtomicStorage = 21, + SpvCapabilityInt16 = 22, + SpvCapabilityTessellationPointSize = 23, + SpvCapabilityGeometryPointSize = 24, + SpvCapabilityImageGatherExtended = 25, + SpvCapabilityStorageImageMultisample = 27, + SpvCapabilityUniformBufferArrayDynamicIndexing = 28, + SpvCapabilitySampledImageArrayDynamicIndexing = 29, + SpvCapabilityStorageBufferArrayDynamicIndexing = 30, + SpvCapabilityStorageImageArrayDynamicIndexing = 31, + SpvCapabilityClipDistance = 32, + SpvCapabilityCullDistance = 33, + SpvCapabilityImageCubeArray = 34, + SpvCapabilitySampleRateShading = 35, + SpvCapabilityImageRect = 36, + SpvCapabilitySampledRect = 37, + SpvCapabilityGenericPointer = 38, + SpvCapabilityInt8 = 39, + SpvCapabilityInputAttachment = 40, + SpvCapabilitySparseResidency = 41, + SpvCapabilityMinLod = 42, + SpvCapabilitySampled1D = 43, + SpvCapabilityImage1D = 44, + SpvCapabilitySampledCubeArray = 45, + SpvCapabilitySampledBuffer = 46, + SpvCapabilityImageBuffer = 47, + SpvCapabilityImageMSArray = 48, + SpvCapabilityStorageImageExtendedFormats = 49, + SpvCapabilityImageQuery = 50, + SpvCapabilityDerivativeControl = 51, + SpvCapabilityInterpolationFunction = 52, + SpvCapabilityTransformFeedback = 53, + SpvCapabilityGeometryStreams = 54, + SpvCapabilityStorageImageReadWithoutFormat = 55, + SpvCapabilityStorageImageWriteWithoutFormat = 56, + SpvCapabilityMultiViewport = 57, +} SpvCapability; + +typedef enum SpvOp_ { + SpvOpNop = 0, + SpvOpUndef = 1, + SpvOpSourceContinued = 2, + SpvOpSource = 3, + SpvOpSourceExtension = 4, + SpvOpName = 5, + SpvOpMemberName = 6, + SpvOpString = 7, + SpvOpLine = 8, + SpvOpExtension = 10, + SpvOpExtInstImport = 11, + SpvOpExtInst = 12, + SpvOpMemoryModel = 14, + SpvOpEntryPoint = 15, + SpvOpExecutionMode = 16, + SpvOpCapability = 17, + SpvOpTypeVoid = 19, + SpvOpTypeBool = 20, + SpvOpTypeInt = 21, + SpvOpTypeFloat = 22, + SpvOpTypeVector = 23, + SpvOpTypeMatrix = 24, + SpvOpTypeImage = 25, + SpvOpTypeSampler = 26, + SpvOpTypeSampledImage = 27, + SpvOpTypeArray = 28, + SpvOpTypeRuntimeArray = 29, + SpvOpTypeStruct = 30, + SpvOpTypeOpaque = 31, + SpvOpTypePointer = 32, + SpvOpTypeFunction = 33, + SpvOpTypeEvent = 34, + SpvOpTypeDeviceEvent = 35, + SpvOpTypeReserveId = 36, + SpvOpTypeQueue = 37, + SpvOpTypePipe = 38, + SpvOpTypeForwardPointer = 39, + SpvOpConstantTrue = 41, + SpvOpConstantFalse = 42, + SpvOpConstant = 43, + SpvOpConstantComposite = 44, + SpvOpConstantSampler = 45, + SpvOpConstantNull = 46, + SpvOpSpecConstantTrue = 48, + SpvOpSpecConstantFalse = 49, + SpvOpSpecConstant = 50, + SpvOpSpecConstantComposite = 51, + SpvOpSpecConstantOp = 52, + SpvOpFunction = 54, + SpvOpFunctionParameter = 55, + SpvOpFunctionEnd = 56, + SpvOpFunctionCall = 57, + SpvOpVariable = 59, + SpvOpImageTexelPointer = 60, + SpvOpLoad = 61, + SpvOpStore = 62, + SpvOpCopyMemory = 63, + SpvOpCopyMemorySized = 64, + SpvOpAccessChain = 65, + SpvOpInBoundsAccessChain = 66, + SpvOpPtrAccessChain = 67, + SpvOpArrayLength = 68, + SpvOpGenericPtrMemSemantics = 69, + SpvOpInBoundsPtrAccessChain = 70, + SpvOpDecorate = 71, + SpvOpMemberDecorate = 72, + SpvOpDecorationGroup = 73, + SpvOpGroupDecorate = 74, + SpvOpGroupMemberDecorate = 75, + SpvOpVectorExtractDynamic = 77, + SpvOpVectorInsertDynamic = 78, + SpvOpVectorShuffle = 79, + SpvOpCompositeConstruct = 80, + SpvOpCompositeExtract = 81, + SpvOpCompositeInsert = 82, + SpvOpCopyObject = 83, + SpvOpTranspose = 84, + SpvOpSampledImage = 86, + SpvOpImageSampleImplicitLod = 87, + SpvOpImageSampleExplicitLod = 88, + SpvOpImageSampleDrefImplicitLod = 89, + SpvOpImageSampleDrefExplicitLod = 90, + SpvOpImageSampleProjImplicitLod = 91, + SpvOpImageSampleProjExplicitLod = 92, + SpvOpImageSampleProjDrefImplicitLod = 93, + SpvOpImageSampleProjDrefExplicitLod = 94, + SpvOpImageFetch = 95, + SpvOpImageGather = 96, + SpvOpImageDrefGather = 97, + SpvOpImageRead = 98, + SpvOpImageWrite = 99, + SpvOpImage = 100, + SpvOpImageQueryFormat = 101, + SpvOpImageQueryOrder = 102, + SpvOpImageQuerySizeLod = 103, + SpvOpImageQuerySize = 104, + SpvOpImageQueryLod = 105, + SpvOpImageQueryLevels = 106, + SpvOpImageQuerySamples = 107, + SpvOpConvertFToU = 109, + SpvOpConvertFToS = 110, + SpvOpConvertSToF = 111, + SpvOpConvertUToF = 112, + SpvOpUConvert = 113, + SpvOpSConvert = 114, + SpvOpFConvert = 115, + SpvOpQuantizeToF16 = 116, + SpvOpConvertPtrToU = 117, + SpvOpSatConvertSToU = 118, + SpvOpSatConvertUToS = 119, + SpvOpConvertUToPtr = 120, + SpvOpPtrCastToGeneric = 121, + SpvOpGenericCastToPtr = 122, + SpvOpGenericCastToPtrExplicit = 123, + SpvOpBitcast = 124, + SpvOpSNegate = 126, + SpvOpFNegate = 127, + SpvOpIAdd = 128, + SpvOpFAdd = 129, + SpvOpISub = 130, + SpvOpFSub = 131, + SpvOpIMul = 132, + SpvOpFMul = 133, + SpvOpUDiv = 134, + SpvOpSDiv = 135, + SpvOpFDiv = 136, + SpvOpUMod = 137, + SpvOpSRem = 138, + SpvOpSMod = 139, + SpvOpFRem = 140, + SpvOpFMod = 141, + SpvOpVectorTimesScalar = 142, + SpvOpMatrixTimesScalar = 143, + SpvOpVectorTimesMatrix = 144, + SpvOpMatrixTimesVector = 145, + SpvOpMatrixTimesMatrix = 146, + SpvOpOuterProduct = 147, + SpvOpDot = 148, + SpvOpIAddCarry = 149, + SpvOpISubBorrow = 150, + SpvOpUMulExtended = 151, + SpvOpSMulExtended = 152, + SpvOpAny = 154, + SpvOpAll = 155, + SpvOpIsNan = 156, + SpvOpIsInf = 157, + SpvOpIsFinite = 158, + SpvOpIsNormal = 159, + SpvOpSignBitSet = 160, + SpvOpLessOrGreater = 161, + SpvOpOrdered = 162, + SpvOpUnordered = 163, + SpvOpLogicalEqual = 164, + SpvOpLogicalNotEqual = 165, + SpvOpLogicalOr = 166, + SpvOpLogicalAnd = 167, + SpvOpLogicalNot = 168, + SpvOpSelect = 169, + SpvOpIEqual = 170, + SpvOpINotEqual = 171, + SpvOpUGreaterThan = 172, + SpvOpSGreaterThan = 173, + SpvOpUGreaterThanEqual = 174, + SpvOpSGreaterThanEqual = 175, + SpvOpULessThan = 176, + SpvOpSLessThan = 177, + SpvOpULessThanEqual = 178, + SpvOpSLessThanEqual = 179, + SpvOpFOrdEqual = 180, + SpvOpFUnordEqual = 181, + SpvOpFOrdNotEqual = 182, + SpvOpFUnordNotEqual = 183, + SpvOpFOrdLessThan = 184, + SpvOpFUnordLessThan = 185, + SpvOpFOrdGreaterThan = 186, + SpvOpFUnordGreaterThan = 187, + SpvOpFOrdLessThanEqual = 188, + SpvOpFUnordLessThanEqual = 189, + SpvOpFOrdGreaterThanEqual = 190, + SpvOpFUnordGreaterThanEqual = 191, + SpvOpShiftRightLogical = 194, + SpvOpShiftRightArithmetic = 195, + SpvOpShiftLeftLogical = 196, + SpvOpBitwiseOr = 197, + SpvOpBitwiseXor = 198, + SpvOpBitwiseAnd = 199, + SpvOpNot = 200, + SpvOpBitFieldInsert = 201, + SpvOpBitFieldSExtract = 202, + SpvOpBitFieldUExtract = 203, + SpvOpBitReverse = 204, + SpvOpBitCount = 205, + SpvOpDPdx = 207, + SpvOpDPdy = 208, + SpvOpFwidth = 209, + SpvOpDPdxFine = 210, + SpvOpDPdyFine = 211, + SpvOpFwidthFine = 212, + SpvOpDPdxCoarse = 213, + SpvOpDPdyCoarse = 214, + SpvOpFwidthCoarse = 215, + SpvOpEmitVertex = 218, + SpvOpEndPrimitive = 219, + SpvOpEmitStreamVertex = 220, + SpvOpEndStreamPrimitive = 221, + SpvOpControlBarrier = 224, + SpvOpMemoryBarrier = 225, + SpvOpAtomicLoad = 227, + SpvOpAtomicStore = 228, + SpvOpAtomicExchange = 229, + SpvOpAtomicCompareExchange = 230, + SpvOpAtomicCompareExchangeWeak = 231, + SpvOpAtomicIIncrement = 232, + SpvOpAtomicIDecrement = 233, + SpvOpAtomicIAdd = 234, + SpvOpAtomicISub = 235, + SpvOpAtomicSMin = 236, + SpvOpAtomicUMin = 237, + SpvOpAtomicSMax = 238, + SpvOpAtomicUMax = 239, + SpvOpAtomicAnd = 240, + SpvOpAtomicOr = 241, + SpvOpAtomicXor = 242, + SpvOpPhi = 245, + SpvOpLoopMerge = 246, + SpvOpSelectionMerge = 247, + SpvOpLabel = 248, + SpvOpBranch = 249, + SpvOpBranchConditional = 250, + SpvOpSwitch = 251, + SpvOpKill = 252, + SpvOpReturn = 253, + SpvOpReturnValue = 254, + SpvOpUnreachable = 255, + SpvOpLifetimeStart = 256, + SpvOpLifetimeStop = 257, + SpvOpGroupAsyncCopy = 259, + SpvOpGroupWaitEvents = 260, + SpvOpGroupAll = 261, + SpvOpGroupAny = 262, + SpvOpGroupBroadcast = 263, + SpvOpGroupIAdd = 264, + SpvOpGroupFAdd = 265, + SpvOpGroupFMin = 266, + SpvOpGroupUMin = 267, + SpvOpGroupSMin = 268, + SpvOpGroupFMax = 269, + SpvOpGroupUMax = 270, + SpvOpGroupSMax = 271, + SpvOpReadPipe = 274, + SpvOpWritePipe = 275, + SpvOpReservedReadPipe = 276, + SpvOpReservedWritePipe = 277, + SpvOpReserveReadPipePackets = 278, + SpvOpReserveWritePipePackets = 279, + SpvOpCommitReadPipe = 280, + SpvOpCommitWritePipe = 281, + SpvOpIsValidReserveId = 282, + SpvOpGetNumPipePackets = 283, + SpvOpGetMaxPipePackets = 284, + SpvOpGroupReserveReadPipePackets = 285, + SpvOpGroupReserveWritePipePackets = 286, + SpvOpGroupCommitReadPipe = 287, + SpvOpGroupCommitWritePipe = 288, + SpvOpEnqueueMarker = 291, + SpvOpEnqueueKernel = 292, + SpvOpGetKernelNDrangeSubGroupCount = 293, + SpvOpGetKernelNDrangeMaxSubGroupSize = 294, + SpvOpGetKernelWorkGroupSize = 295, + SpvOpGetKernelPreferredWorkGroupSizeMultiple = 296, + SpvOpRetainEvent = 297, + SpvOpReleaseEvent = 298, + SpvOpCreateUserEvent = 299, + SpvOpIsValidEvent = 300, + SpvOpSetUserEventStatus = 301, + SpvOpCaptureEventProfilingInfo = 302, + SpvOpGetDefaultQueue = 303, + SpvOpBuildNDRange = 304, + SpvOpImageSparseSampleImplicitLod = 305, + SpvOpImageSparseSampleExplicitLod = 306, + SpvOpImageSparseSampleDrefImplicitLod = 307, + SpvOpImageSparseSampleDrefExplicitLod = 308, + SpvOpImageSparseSampleProjImplicitLod = 309, + SpvOpImageSparseSampleProjExplicitLod = 310, + SpvOpImageSparseSampleProjDrefImplicitLod = 311, + SpvOpImageSparseSampleProjDrefExplicitLod = 312, + SpvOpImageSparseFetch = 313, + SpvOpImageSparseGather = 314, + SpvOpImageSparseDrefGather = 315, + SpvOpImageSparseTexelsResident = 316, + SpvOpNoLine = 317, + SpvOpAtomicFlagTestAndSet = 318, + SpvOpAtomicFlagClear = 319, + SpvOpImageSparseRead = 320, +} SpvOp; + +#endif // #ifndef spirv_H diff --git a/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.cpp b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.cpp new file mode 100644 index 0000000000..9e3e6254f7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/tracing/SkRPDebugTrace.h" + +#include <sstream> +#include <utility> + +namespace SkSL { + +void SkRPDebugTrace::writeTrace(SkWStream* o) const { + // Not yet implemented. +} + +void SkRPDebugTrace::dump(SkWStream* o) const { + // Not yet implemented. +} + +void SkRPDebugTrace::setSource(std::string source) { + fSource.clear(); + std::stringstream stream{std::move(source)}; + while (stream.good()) { + fSource.push_back({}); + std::getline(stream, fSource.back(), '\n'); + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.h b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.h new file mode 100644 index 0000000000..1c3fa3bc54 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkRPDebugTrace.h @@ -0,0 +1,48 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKRPDEBUGTRACE +#define SKRPDEBUGTRACE + +#include "include/sksl/SkSLDebugTrace.h" +#include "src/sksl/tracing/SkSLDebugInfo.h" + +#include <string> +#include <vector> + +class SkWStream; + +namespace SkSL { + +class SkRPDebugTrace : public DebugTrace { +public: + /** Serializes a debug trace to JSON which can be parsed by our debugger. */ + void writeTrace(SkWStream* o) const override; + + /** Generates a human-readable dump of the debug trace. */ + void dump(SkWStream* o) const override; + + /** Attaches the SkSL source to be debugged. */ + void setSource(std::string source); + + /** A 1:1 mapping of slot numbers to debug information. */ + std::vector<SlotDebugInfo> fSlotInfo; + std::vector<FunctionDebugInfo> fFuncInfo; + + /** The SkSL debug trace. */ + std::vector<TraceInfo> fTraceInfo; + + /** SkVM uniforms live in fSlotInfo; SkRP has dedicated a uniform slot map in fUniformInfo. */ + std::vector<SlotDebugInfo> fUniformInfo; + + /** The SkSL code, split line-by-line. */ + std::vector<std::string> fSource; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/tracing/SkSLDebugInfo.h b/gfx/skia/skia/src/sksl/tracing/SkSLDebugInfo.h new file mode 100644 index 0000000000..74b7a430d4 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkSLDebugInfo.h @@ -0,0 +1,55 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSLDEBUGINFO +#define SKSLDEBUGINFO + +#include "src/sksl/ir/SkSLType.h" + +#include <cstdint> +#include <string> + +namespace SkSL { + +struct TraceInfo { + enum class Op { + kLine, /** data: line number, (unused) */ + kVar, /** data: slot, value */ + kEnter, /** data: function index, (unused) */ + kExit, /** data: function index, (unused) */ + kScope, /** data: scope delta, (unused) */ + }; + Op op; + int32_t data[2]; +}; + +struct SlotDebugInfo { + /** The full name of this variable (without component), e.g. `myArray[3].myStruct.myVector` */ + std::string name; + /** The dimensions of this variable: 1x1 is a scalar, Nx1 is a vector, NxM is a matrix. */ + uint8_t columns = 1, rows = 1; + /** Which component of the variable is this slot? (e.g. `vec4.z` is component 2) */ + uint8_t componentIndex = 0; + /** Complex types (arrays/structs) can be tracked as a "group" of adjacent slots. */ + int groupIndex = 0; + /** What kind of numbers belong in this slot? */ + SkSL::Type::NumberKind numberKind = SkSL::Type::NumberKind::kNonnumeric; + /** Where is this variable located in the program? */ + int line = 0; + Position pos = {}; + /** If this slot holds a function's return value, contains 1; if not, -1. */ + int fnReturnValue = -1; +}; + +struct FunctionDebugInfo { + /** Full function declaration: `float myFunction(half4 color)`) */ + std::string name; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.cpp b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.cpp new file mode 100644 index 0000000000..c394f7e0a7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/tracing/SkSLDebugInfo.h" +#include "src/sksl/tracing/SkSLTraceHook.h" + +namespace SkSL { + +std::unique_ptr<Tracer> Tracer::Make(std::vector<TraceInfo>* traceInfo) { + auto hook = std::make_unique<Tracer>(); + hook->fTraceInfo = traceInfo; + return hook; +} + +void Tracer::line(int lineNum) { + fTraceInfo->push_back({TraceInfo::Op::kLine, /*data=*/{lineNum, 0}}); +} +void Tracer::var(int slot, int32_t val) { + fTraceInfo->push_back({TraceInfo::Op::kVar, /*data=*/{slot, val}}); +} +void Tracer::enter(int fnIdx) { + fTraceInfo->push_back({TraceInfo::Op::kEnter, /*data=*/{fnIdx, 0}}); +} +void Tracer::exit(int fnIdx) { + fTraceInfo->push_back({TraceInfo::Op::kExit, /*data=*/{fnIdx, 0}}); +} +void Tracer::scope(int delta) { + fTraceInfo->push_back({TraceInfo::Op::kScope, /*data=*/{delta, 0}}); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.h b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.h new file mode 100644 index 0000000000..404e1be229 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkSLTraceHook.h @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSLTRACEHOOK +#define SKSLTRACEHOOK + +#include <cstdint> +#include <memory> +#include <vector> + +namespace SkSL { + +struct TraceInfo; + +class TraceHook { +public: + virtual ~TraceHook() = default; + virtual void line(int lineNum) = 0; + virtual void var(int slot, int32_t val) = 0; + virtual void enter(int fnIdx) = 0; + virtual void exit(int fnIdx) = 0; + virtual void scope(int delta) = 0; +}; + +class Tracer : public TraceHook { +public: + static std::unique_ptr<Tracer> Make(std::vector<TraceInfo>* traceInfo); + + void line(int lineNum) override; + void var(int slot, int32_t val) override; + void enter(int fnIdx) override; + void exit(int fnIdx) override; + void scope(int delta) override; + +private: + std::vector<TraceInfo>* fTraceInfo; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.cpp b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.cpp new file mode 100644 index 0000000000..091bd73720 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.cpp @@ -0,0 +1,417 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/tracing/SkVMDebugTrace.h" + +#ifdef SKSL_ENABLE_TRACING + +#include "include/core/SkData.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "src/core/SkStreamPriv.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/utils/SkJSON.h" +#include "src/utils/SkJSONWriter.h" + +#include <cstdio> +#include <cstring> +#include <sstream> +#include <string> +#include <string_view> +#include <utility> + +static constexpr char kTraceVersion[] = "20220209"; + +namespace SkSL { + +std::string SkVMDebugTrace::getSlotComponentSuffix(int slotIndex) const { + const SkSL::SlotDebugInfo& slot = fSlotInfo[slotIndex]; + + if (slot.rows > 1) { + return "[" + std::to_string(slot.componentIndex / slot.rows) + + "][" + std::to_string(slot.componentIndex % slot.rows) + + "]"; + } + if (slot.columns > 1) { + switch (slot.componentIndex) { + case 0: return ".x"; + case 1: return ".y"; + case 2: return ".z"; + case 3: return ".w"; + default: return "[???]"; + } + } + return {}; +} + +double SkVMDebugTrace::interpretValueBits(int slotIndex, int32_t valueBits) const { + SkASSERT(slotIndex >= 0); + SkASSERT((size_t)slotIndex < fSlotInfo.size()); + switch (fSlotInfo[slotIndex].numberKind) { + case SkSL::Type::NumberKind::kUnsigned: { + uint32_t uintValue; + static_assert(sizeof(uintValue) == sizeof(valueBits)); + memcpy(&uintValue, &valueBits, sizeof(uintValue)); + return uintValue; + } + case SkSL::Type::NumberKind::kFloat: { + float floatValue; + static_assert(sizeof(floatValue) == sizeof(valueBits)); + memcpy(&floatValue, &valueBits, sizeof(floatValue)); + return floatValue; + } + default: { + return valueBits; + } + } +} + +std::string SkVMDebugTrace::slotValueToString(int slotIndex, double value) const { + SkASSERT(slotIndex >= 0); + SkASSERT((size_t)slotIndex < fSlotInfo.size()); + switch (fSlotInfo[slotIndex].numberKind) { + case SkSL::Type::NumberKind::kBoolean: { + return value ? "true" : "false"; + } + default: { + char buffer[32]; + snprintf(buffer, std::size(buffer), "%.8g", value); + return buffer; + } + } +} + +std::string SkVMDebugTrace::getSlotValue(int slotIndex, int32_t valueBits) const { + return this->slotValueToString(slotIndex, this->interpretValueBits(slotIndex, valueBits)); +} + +void SkVMDebugTrace::setTraceCoord(const SkIPoint& coord) { + fTraceCoord = coord; +} + +void SkVMDebugTrace::setSource(std::string source) { + fSource.clear(); + std::stringstream stream{std::move(source)}; + while (stream.good()) { + fSource.push_back({}); + std::getline(stream, fSource.back(), '\n'); + } +} + +void SkVMDebugTrace::dump(SkWStream* o) const { + for (size_t index = 0; index < fSlotInfo.size(); ++index) { + const SlotDebugInfo& info = fSlotInfo[index]; + + o->writeText("$"); + o->writeDecAsText(index); + o->writeText(" = "); + o->writeText(info.name.c_str()); + o->writeText(" ("); + switch (info.numberKind) { + case Type::NumberKind::kFloat: o->writeText("float"); break; + case Type::NumberKind::kSigned: o->writeText("int"); break; + case Type::NumberKind::kUnsigned: o->writeText("uint"); break; + case Type::NumberKind::kBoolean: o->writeText("bool"); break; + case Type::NumberKind::kNonnumeric: o->writeText("???"); break; + } + if (info.rows * info.columns > 1) { + o->writeDecAsText(info.columns); + if (info.rows != 1) { + o->writeText("x"); + o->writeDecAsText(info.rows); + } + o->writeText(" : "); + o->writeText("slot "); + o->writeDecAsText(info.componentIndex + 1); + o->writeText("/"); + o->writeDecAsText(info.rows * info.columns); + } + o->writeText(", L"); + o->writeDecAsText(info.line); + o->writeText(")"); + o->newline(); + } + + for (size_t index = 0; index < fFuncInfo.size(); ++index) { + const FunctionDebugInfo& info = fFuncInfo[index]; + + o->writeText("F"); + o->writeDecAsText(index); + o->writeText(" = "); + o->writeText(info.name.c_str()); + o->newline(); + } + + o->newline(); + + if (!fTraceInfo.empty()) { + std::string indent = ""; + for (const SkSL::TraceInfo& traceInfo : fTraceInfo) { + int data0 = traceInfo.data[0]; + int data1 = traceInfo.data[1]; + switch (traceInfo.op) { + case SkSL::TraceInfo::Op::kLine: + o->writeText(indent.c_str()); + o->writeText("line "); + o->writeDecAsText(data0); + break; + + case SkSL::TraceInfo::Op::kVar: { + const SlotDebugInfo& slot = fSlotInfo[data0]; + o->writeText(indent.c_str()); + o->writeText(slot.name.c_str()); + o->writeText(this->getSlotComponentSuffix(data0).c_str()); + o->writeText(" = "); + o->writeText(this->getSlotValue(data0, data1).c_str()); + break; + } + case SkSL::TraceInfo::Op::kEnter: + o->writeText(indent.c_str()); + o->writeText("enter "); + o->writeText(fFuncInfo[data0].name.c_str()); + indent += " "; + break; + + case SkSL::TraceInfo::Op::kExit: + indent.resize(indent.size() - 2); + o->writeText(indent.c_str()); + o->writeText("exit "); + o->writeText(fFuncInfo[data0].name.c_str()); + break; + + case SkSL::TraceInfo::Op::kScope: + for (int delta = data0; delta < 0; ++delta) { + indent.pop_back(); + } + o->writeText(indent.c_str()); + o->writeText("scope "); + o->writeText((data0 >= 0) ? "+" : ""); + o->writeDecAsText(data0); + for (int delta = data0; delta > 0; --delta) { + indent.push_back(' '); + } + break; + } + o->newline(); + } + } +} + +void SkVMDebugTrace::writeTrace(SkWStream* w) const { + SkJSONWriter json(w); + + json.beginObject(); // root + json.appendNString("version", kTraceVersion); + json.beginArray("source"); + + for (const std::string& line : fSource) { + json.appendString(line); + } + + json.endArray(); // code + json.beginArray("slots"); + + for (size_t index = 0; index < fSlotInfo.size(); ++index) { + const SlotDebugInfo& info = fSlotInfo[index]; + + json.beginObject(); + json.appendString("name", info.name.data(), info.name.size()); + json.appendS32("columns", info.columns); + json.appendS32("rows", info.rows); + json.appendS32("index", info.componentIndex); + if (info.groupIndex != info.componentIndex) { + json.appendS32("groupIdx", info.groupIndex); + } + json.appendS32("kind", (int)info.numberKind); + json.appendS32("line", info.line); + if (info.fnReturnValue >= 0) { + json.appendS32("retval", info.fnReturnValue); + } + json.endObject(); + } + + json.endArray(); // slots + json.beginArray("functions"); + + for (size_t index = 0; index < fFuncInfo.size(); ++index) { + const FunctionDebugInfo& info = fFuncInfo[index]; + + json.beginObject(); + json.appendString("name", info.name); + json.endObject(); + } + + json.endArray(); // functions + json.beginArray("trace"); + + for (size_t index = 0; index < fTraceInfo.size(); ++index) { + const TraceInfo& trace = fTraceInfo[index]; + json.beginArray(); + json.appendS32((int)trace.op); + + // Skip trailing zeros in the data (since most ops only use one value). + int lastDataIdx = std::size(trace.data) - 1; + while (lastDataIdx >= 0 && !trace.data[lastDataIdx]) { + --lastDataIdx; + } + for (int dataIdx = 0; dataIdx <= lastDataIdx; ++dataIdx) { + json.appendS32(trace.data[dataIdx]); + } + json.endArray(); + } + + json.endArray(); // trace + json.endObject(); // root + json.flush(); +} + +bool SkVMDebugTrace::readTrace(SkStream* r) { + sk_sp<SkData> data = SkCopyStreamToData(r); + skjson::DOM json(reinterpret_cast<const char*>(data->bytes()), data->size()); + const skjson::ObjectValue* root = json.root(); + if (!root) { + return false; + } + + const skjson::StringValue* version = (*root)["version"]; + if (!version || version->str() != kTraceVersion) { + return false; + } + + const skjson::ArrayValue* source = (*root)["source"]; + if (!source) { + return false; + } + + fSource.clear(); + for (const skjson::StringValue* line : *source) { + if (!line) { + return false; + } + fSource.push_back(line->begin()); + } + + const skjson::ArrayValue* slots = (*root)["slots"]; + if (!slots) { + return false; + } + + fSlotInfo.clear(); + for (const skjson::ObjectValue* element : *slots) { + if (!element) { + return false; + } + + // Grow the slot array to hold this element. + fSlotInfo.push_back({}); + SlotDebugInfo& info = fSlotInfo.back(); + + // Populate the SlotInfo with our JSON data. + const skjson::StringValue* name = (*element)["name"]; + const skjson::NumberValue* columns = (*element)["columns"]; + const skjson::NumberValue* rows = (*element)["rows"]; + const skjson::NumberValue* index = (*element)["index"]; + const skjson::NumberValue* groupIdx = (*element)["groupIdx"]; + const skjson::NumberValue* kind = (*element)["kind"]; + const skjson::NumberValue* line = (*element)["line"]; + const skjson::NumberValue* retval = (*element)["retval"]; + if (!name || !columns || !rows || !index || !kind || !line) { + return false; + } + + info.name = name->begin(); + info.columns = **columns; + info.rows = **rows; + info.componentIndex = **index; + info.groupIndex = groupIdx ? **groupIdx : info.componentIndex; + info.numberKind = (SkSL::Type::NumberKind)(int)**kind; + info.line = **line; + info.fnReturnValue = retval ? **retval : -1; + } + + const skjson::ArrayValue* functions = (*root)["functions"]; + if (!functions) { + return false; + } + + fFuncInfo.clear(); + for (const skjson::ObjectValue* element : *functions) { + if (!element) { + return false; + } + + // Grow the function array to hold this element. + fFuncInfo.push_back({}); + FunctionDebugInfo& info = fFuncInfo.back(); + + // Populate the FunctionInfo with our JSON data. + const skjson::StringValue* name = (*element)["name"]; + if (!name) { + return false; + } + + info.name = name->begin(); + } + + const skjson::ArrayValue* trace = (*root)["trace"]; + if (!trace) { + return false; + } + + fTraceInfo.clear(); + fTraceInfo.reserve(trace->size()); + for (const skjson::ArrayValue* element : *trace) { + fTraceInfo.push_back(TraceInfo{}); + TraceInfo& info = fTraceInfo.back(); + + if (!element || element->size() < 1 || element->size() > (1 + std::size(info.data))) { + return false; + } + const skjson::NumberValue* opVal = (*element)[0]; + if (!opVal) { + return false; + } + info.op = (TraceInfo::Op)(int)**opVal; + for (size_t elemIdx = 1; elemIdx < element->size(); ++elemIdx) { + const skjson::NumberValue* dataVal = (*element)[elemIdx]; + if (!dataVal) { + return false; + } + info.data[elemIdx - 1] = **dataVal; + } + } + + return true; +} + +} // namespace SkSL + +#else // SKSL_ENABLE_TRACING + +#include <string> + +namespace SkSL { + void SkVMDebugTrace::setTraceCoord(const SkIPoint &coord) {} + + void SkVMDebugTrace::setSource(std::string source) {} + + bool SkVMDebugTrace::readTrace(SkStream *r) { return false; } + + void SkVMDebugTrace::writeTrace(SkWStream *w) const {} + + void SkVMDebugTrace::dump(SkWStream *o) const {} + + std::string SkVMDebugTrace::getSlotComponentSuffix(int slotIndex) const { return ""; } + + std::string SkVMDebugTrace::getSlotValue(int slotIndex, int32_t value) const { return ""; } + + double SkVMDebugTrace::interpretValueBits(int slotIndex, int32_t valueBits) const { return 0; } + + std::string SkVMDebugTrace::slotValueToString(int slotIndex, double value) const { return ""; } +} +#endif diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.h b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.h new file mode 100644 index 0000000000..c9b3525fe7 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTrace.h @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKVMDEBUGTRACE +#define SKVMDEBUGTRACE + +#include "include/sksl/SkSLDebugTrace.h" + +#include "include/core/SkPoint.h" +#include "src/sksl/tracing/SkSLDebugInfo.h" +#include "src/sksl/tracing/SkSLTraceHook.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +class SkStream; +class SkWStream; + +namespace SkSL { + +class SkVMDebugTrace : public DebugTrace { +public: + /** + * Sets the device-coordinate pixel to trace. If it's not set, the point at (0, 0) will be used. + */ + void setTraceCoord(const SkIPoint& coord); + + /** Attaches the SkSL source to be debugged. */ + void setSource(std::string source); + + /** Serializes a debug trace to JSON which can be parsed by our debugger. */ + bool readTrace(SkStream* r); + void writeTrace(SkWStream* w) const override; + + /** Generates a human-readable dump of the debug trace. */ + void dump(SkWStream* o) const override; + + /** Returns a slot's component as a variable-name suffix, e.g. ".x" or "[2][2]". */ + std::string getSlotComponentSuffix(int slotIndex) const; + + /** Bit-casts a slot's value, then converts to text, e.g. "3.14" or "true" or "12345". */ + std::string getSlotValue(int slotIndex, int32_t value) const; + + /** Bit-casts a value for a given slot into a double, honoring the slot's NumberKind. */ + double interpretValueBits(int slotIndex, int32_t valueBits) const; + + /** Converts a numeric value into text, based on the slot's NumberKind. */ + std::string slotValueToString(int slotIndex, double value) const; + + /** The device-coordinate pixel to trace (controlled by setTraceCoord) */ + SkIPoint fTraceCoord = {}; + + /** A 1:1 mapping of slot numbers to debug information. */ + std::vector<SlotDebugInfo> fSlotInfo; + std::vector<FunctionDebugInfo> fFuncInfo; + + /** The SkSL debug trace. */ + std::vector<TraceInfo> fTraceInfo; + + /** The SkSL code, split line-by-line. */ + std::vector<std::string> fSource; + + /** + * A trace hook which populates fTraceInfo during SkVM program evaluation. This will be created + * automatically by the SkSLVMCodeGenerator. + */ + std::unique_ptr<SkSL::TraceHook> fTraceHook; +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp new file mode 100644 index 0000000000..7ae9e4638b --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp @@ -0,0 +1,284 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/tracing/SkSLDebugInfo.h" +#include "src/sksl/tracing/SkVMDebugTracePlayer.h" + +#include <limits.h> +#include <algorithm> +#include <utility> + +namespace SkSL { + +void SkVMDebugTracePlayer::reset(sk_sp<SkVMDebugTrace> debugTrace) { + size_t nslots = debugTrace ? debugTrace->fSlotInfo.size() : 0; + fDebugTrace = debugTrace; + fCursor = 0; + fScope = 0; + fSlots.clear(); + fSlots.resize(nslots, {/*fValue=*/0, + /*fScope=*/INT_MAX, + /*fWriteTime=*/0}); + fStack.clear(); + fStack.push_back({/*fFunction=*/-1, + /*fLine=*/-1, + /*fDisplayMask=*/SkBitSet(nslots)}); + fDirtyMask.emplace(nslots); + fReturnValues.emplace(nslots); + + if (fDebugTrace) { + for (size_t slotIdx = 0; slotIdx < nslots; ++slotIdx) { + if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue >= 0) { + fReturnValues->set(slotIdx); + } + } + + for (const TraceInfo& trace : fDebugTrace->fTraceInfo) { + if (trace.op == TraceInfo::Op::kLine) { + fLineNumbers[trace.data[0]] += 1; + } + } + } +} + +void SkVMDebugTracePlayer::step() { + this->tidyState(); + while (!this->traceHasCompleted()) { + if (this->execute(fCursor++)) { + break; + } + } +} + +void SkVMDebugTracePlayer::stepOver() { + this->tidyState(); + size_t initialStackDepth = fStack.size(); + while (!this->traceHasCompleted()) { + bool canEscapeFromThisStackDepth = (fStack.size() <= initialStackDepth); + if (this->execute(fCursor++)) { + if (canEscapeFromThisStackDepth || this->atBreakpoint()) { + break; + } + } + } +} + +void SkVMDebugTracePlayer::stepOut() { + this->tidyState(); + size_t initialStackDepth = fStack.size(); + while (!this->traceHasCompleted()) { + if (this->execute(fCursor++)) { + bool hasEscapedFromInitialStackDepth = (fStack.size() < initialStackDepth); + if (hasEscapedFromInitialStackDepth || this->atBreakpoint()) { + break; + } + } + } +} + +void SkVMDebugTracePlayer::run() { + this->tidyState(); + while (!this->traceHasCompleted()) { + if (this->execute(fCursor++)) { + if (this->atBreakpoint()) { + break; + } + } + } +} + +void SkVMDebugTracePlayer::tidyState() { + fDirtyMask->reset(); + + // Conceptually this is `fStack.back().fDisplayMask &= ~fReturnValues`, but SkBitSet doesn't + // support masking one set of bits against another. + fReturnValues->forEachSetIndex([&](int slot) { + fStack.back().fDisplayMask.reset(slot); + }); +} + +bool SkVMDebugTracePlayer::traceHasCompleted() const { + return !fDebugTrace || fCursor >= fDebugTrace->fTraceInfo.size(); +} + +int32_t SkVMDebugTracePlayer::getCurrentLine() const { + SkASSERT(!fStack.empty()); + return fStack.back().fLine; +} + +int32_t SkVMDebugTracePlayer::getCurrentLineInStackFrame(int stackFrameIndex) const { + // The first entry on the stack is the "global" frame before we enter main, so offset our index + // by one to account for it. + ++stackFrameIndex; + SkASSERT(stackFrameIndex > 0); + SkASSERT((size_t)stackFrameIndex < fStack.size()); + return fStack[stackFrameIndex].fLine; +} + +bool SkVMDebugTracePlayer::atBreakpoint() const { + return fBreakpointLines.count(this->getCurrentLine()); +} + +void SkVMDebugTracePlayer::setBreakpoints(std::unordered_set<int> breakpointLines) { + fBreakpointLines = std::move(breakpointLines); +} + +void SkVMDebugTracePlayer::addBreakpoint(int line) { + fBreakpointLines.insert(line); +} + +void SkVMDebugTracePlayer::removeBreakpoint(int line) { + fBreakpointLines.erase(line); +} + +std::vector<int> SkVMDebugTracePlayer::getCallStack() const { + SkASSERT(!fStack.empty()); + std::vector<int> funcs; + funcs.reserve(fStack.size() - 1); + for (size_t index = 1; index < fStack.size(); ++index) { + funcs.push_back(fStack[index].fFunction); + } + return funcs; +} + +int SkVMDebugTracePlayer::getStackDepth() const { + SkASSERT(!fStack.empty()); + return fStack.size() - 1; +} + +std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getVariablesForDisplayMask( + const SkBitSet& displayMask) const { + SkASSERT(displayMask.size() == fSlots.size()); + + std::vector<VariableData> vars; + displayMask.forEachSetIndex([&](int slot) { + double typedValue = fDebugTrace->interpretValueBits(slot, fSlots[slot].fValue); + vars.push_back({slot, fDirtyMask->test(slot), typedValue}); + }); + // Order the variable list so that the most recently-written variables are shown at the top. + std::stable_sort(vars.begin(), vars.end(), [&](const VariableData& a, const VariableData& b) { + return fSlots[a.fSlotIndex].fWriteTime > fSlots[b.fSlotIndex].fWriteTime; + }); + return vars; +} + +std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getLocalVariables( + int stackFrameIndex) const { + // The first entry on the stack is the "global" frame before we enter main, so offset our index + // by one to account for it. + ++stackFrameIndex; + if (stackFrameIndex <= 0 || (size_t)stackFrameIndex >= fStack.size()) { + SkDEBUGFAILF("stack frame %d doesn't exist", stackFrameIndex - 1); + return {}; + } + return this->getVariablesForDisplayMask(fStack[stackFrameIndex].fDisplayMask); +} + +std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getGlobalVariables() const { + if (fStack.empty()) { + return {}; + } + return this->getVariablesForDisplayMask(fStack.front().fDisplayMask); +} + +void SkVMDebugTracePlayer::updateVariableWriteTime(int slotIdx, size_t cursor) { + // The slotIdx could point to any slot within a variable. + // We want to update the write time on EVERY slot associated with this variable. + // The SlotInfo's groupIndex gives us enough information to find the affected range. + const SkSL::SlotDebugInfo& changedSlot = fDebugTrace->fSlotInfo[slotIdx]; + slotIdx -= changedSlot.groupIndex; + SkASSERT(slotIdx >= 0); + SkASSERT(slotIdx < (int)fDebugTrace->fSlotInfo.size()); + + for (;;) { + fSlots[slotIdx++].fWriteTime = cursor; + + // Stop if we've reached the final slot. + if (slotIdx >= (int)fDebugTrace->fSlotInfo.size()) { + break; + } + // Each separate variable-group starts with a groupIndex of 0; stop when we detect this. + if (fDebugTrace->fSlotInfo[slotIdx].groupIndex == 0) { + break; + } + } +} + +bool SkVMDebugTracePlayer::execute(size_t position) { + if (position >= fDebugTrace->fTraceInfo.size()) { + SkDEBUGFAILF("position %zu out of range", position); + return true; + } + + const TraceInfo& trace = fDebugTrace->fTraceInfo[position]; + switch (trace.op) { + case TraceInfo::Op::kLine: { // data: line number, (unused) + SkASSERT(!fStack.empty()); + int lineNumber = trace.data[0]; + SkASSERT(lineNumber >= 0); + SkASSERT((size_t)lineNumber < fDebugTrace->fSource.size()); + SkASSERT(fLineNumbers[lineNumber] > 0); + fStack.back().fLine = lineNumber; + fLineNumbers[lineNumber] -= 1; + return true; + } + case TraceInfo::Op::kVar: { // data: slot, value + int slotIdx = trace.data[0]; + int value = trace.data[1]; + SkASSERT(slotIdx >= 0); + SkASSERT((size_t)slotIdx < fDebugTrace->fSlotInfo.size()); + fSlots[slotIdx].fValue = value; + fSlots[slotIdx].fScope = std::min<>(fSlots[slotIdx].fScope, fScope); + this->updateVariableWriteTime(slotIdx, position); + if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue < 0) { + // Normal variables are associated with the current function. + SkASSERT(fStack.size() > 0); + fStack.rbegin()[0].fDisplayMask.set(slotIdx); + } else { + // Return values are associated with the parent function (since the current function + // is exiting and we won't see them there). + SkASSERT(fStack.size() > 1); + fStack.rbegin()[1].fDisplayMask.set(slotIdx); + } + fDirtyMask->set(slotIdx); + break; + } + case TraceInfo::Op::kEnter: { // data: function index, (unused) + int fnIdx = trace.data[0]; + SkASSERT(fnIdx >= 0); + SkASSERT((size_t)fnIdx < fDebugTrace->fFuncInfo.size()); + fStack.push_back({/*fFunction=*/fnIdx, + /*fLine=*/-1, + /*fDisplayMask=*/SkBitSet(fDebugTrace->fSlotInfo.size())}); + break; + } + case TraceInfo::Op::kExit: { // data: function index, (unused) + SkASSERT(!fStack.empty()); + SkASSERT(fStack.back().fFunction == trace.data[0]); + fStack.pop_back(); + return true; + } + case TraceInfo::Op::kScope: { // data: scope delta, (unused) + SkASSERT(!fStack.empty()); + fScope += trace.data[0]; + if (trace.data[0] < 0) { + // If the scope is being reduced, discard variables that are now out of scope. + for (size_t slotIdx = 0; slotIdx < fSlots.size(); ++slotIdx) { + if (fScope < fSlots[slotIdx].fScope) { + fSlots[slotIdx].fScope = INT_MAX; + fStack.back().fDisplayMask.reset(slotIdx); + } + } + } + return false; + } + } + + return false; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.h b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.h new file mode 100644 index 0000000000..13ee7b7bd8 --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.h @@ -0,0 +1,136 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/sksl/tracing/SkVMDebugTrace.h" + +#include "include/core/SkRefCnt.h" +#include "include/core/SkTypes.h" +#include "src/utils/SkBitSet.h" + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +namespace SkSL { + +/** + * Plays back a SkVM debug trace, allowing its contents to be viewed like a traditional debugger. + */ +class SkVMDebugTracePlayer { +public: + /** Resets playback to the start of the trace. Breakpoints are not cleared. */ + void reset(sk_sp<SkVMDebugTrace> trace); + + /** Advances the simulation to the next Line op. */ + void step(); + + /** + * Advances the simulation to the next Line op, skipping past matched Enter/Exit pairs. + * Breakpoints will also stop the simulation even if we haven't reached an Exit. + */ + void stepOver(); + + /** + * Advances the simulation until we exit from the current stack frame. + * Breakpoints will also stop the simulation even if we haven't left the stack frame. + */ + void stepOut(); + + /** Advances the simulation until we hit a breakpoint, or the trace completes. */ + void run(); + + /** Breakpoints will force the simulation to stop whenever a desired line is reached. */ + void setBreakpoints(std::unordered_set<int> breakpointLines); + void addBreakpoint(int line); + void removeBreakpoint(int line); + using BreakpointSet = std::unordered_set<int>; + const BreakpointSet& getBreakpoints() { return fBreakpointLines; } + + /** Returns true if we have reached the end of the trace. */ + bool traceHasCompleted() const; + + /** Returns true if there is a breakpoint set at the current line. */ + bool atBreakpoint() const; + + /** Retrieves the cursor position. */ + size_t cursor() { return fCursor; } + + /** Retrieves the current line. */ + int32_t getCurrentLine() const; + + /** Retrieves the current line for a given stack frame. */ + int32_t getCurrentLineInStackFrame(int stackFrameIndex) const; + + /** Returns the call stack as an array of FunctionInfo indices. */ + std::vector<int> getCallStack() const; + + /** Returns the size of the call stack. */ + int getStackDepth() const; + + /** + * Returns every line number reached inside this debug trace, along with the remaining number of + * times that this trace will reach it. e.g. {100, 2} means line 100 will be reached twice. + */ + using LineNumberMap = std::unordered_map<int, int>; + const LineNumberMap& getLineNumbersReached() const { return fLineNumbers; } + + /** Returns variables from a stack frame, or from global scope. */ + struct VariableData { + int fSlotIndex; + bool fDirty; // has this slot been written-to since the last step call? + double fValue; // value in slot (with type-conversion applied) + }; + std::vector<VariableData> getLocalVariables(int stackFrameIndex) const; + std::vector<VariableData> getGlobalVariables() const; + +private: + /** + * Executes the trace op at the passed-in cursor position. Returns true if we've reached a line + * or exit trace op, which indicate a stopping point. + */ + bool execute(size_t position); + + /** + * Cleans up temporary state between steps, such as the dirty mask and function return values. + */ + void tidyState(); + + /** Updates fWriteTime for the entire variable at a given slot. */ + void updateVariableWriteTime(int slotIdx, size_t writeTime); + + /** Returns a vector of the indices and values of each slot that is enabled in `bits`. */ + std::vector<VariableData> getVariablesForDisplayMask(const SkBitSet& bits) const; + + struct StackFrame { + int32_t fFunction; // from fFuncInfo + int32_t fLine; // our current line number within the function + SkBitSet fDisplayMask; // the variable slots which have been touched in this function + }; + struct Slot { + int32_t fValue; // values in each slot + int fScope; // the scope value of each slot + size_t fWriteTime; // when was the variable in this slot most recently written? + // (by cursor position) + }; + sk_sp<SkVMDebugTrace> fDebugTrace; + size_t fCursor = 0; // position of the read head + int fScope = 0; // the current scope depth (as tracked by + // trace_scope) + std::vector<Slot> fSlots; // the array of all slots + std::vector<StackFrame> fStack; // the execution stack + std::optional<SkBitSet> fDirtyMask; // variable slots touched during the most-recently + // executed step + std::optional<SkBitSet> fReturnValues; // variable slots containing return values + LineNumberMap fLineNumbers; // holds [line number, the remaining number of + // times to reach this line during the trace] + BreakpointSet fBreakpointLines; // all breakpoints set by setBreakpointLines +}; + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLAddConstToVarModifiers.cpp b/gfx/skia/skia/src/sksl/transform/SkSLAddConstToVarModifiers.cpp new file mode 100644 index 0000000000..f024d7d681 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLAddConstToVarModifiers.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLModifiers.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/transform/SkSLTransform.h" + +namespace SkSL { + +class Expression; + +const Modifiers* Transform::AddConstToVarModifiers(const Context& context, + const Variable& var, + const Expression* initialValue, + const ProgramUsage* usage) { + // If the variable is already marked as `const`, keep our existing modifiers. + const Modifiers* modifiers = &var.modifiers(); + if (modifiers->fFlags & Modifiers::kConst_Flag) { + return modifiers; + } + // If the variable doesn't have a compile-time-constant initial value, we can't `const` it. + if (!initialValue || !Analysis::IsCompileTimeConstant(*initialValue)) { + return modifiers; + } + // This only works for variables that are written-to a single time. + ProgramUsage::VariableCounts counts = usage->get(var); + if (counts.fWrite != 1) { + return modifiers; + } + // Add `const` to our variable's modifiers, making it eligible for constant-folding. + Modifiers constModifiers = *modifiers; + constModifiers.fFlags |= Modifiers::kConst_Flag; + return context.fModifiersPool->add(constModifiers); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadFunctions.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadFunctions.cpp new file mode 100644 index 0000000000..6332a9b716 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadFunctions.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLProgramElement.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <vector> + +namespace SkSL { + +static bool dead_function_predicate(const ProgramElement* element, ProgramUsage* usage) { + if (!element->is<FunctionDefinition>()) { + return false; + } + const FunctionDefinition& fn = element->as<FunctionDefinition>(); + if (fn.declaration().isMain() || usage->get(fn.declaration()) > 0) { + return false; + } + // This function is about to be eliminated by remove_if; update ProgramUsage accordingly. + usage->remove(*element); + return true; +} + +bool Transform::EliminateDeadFunctions(Program& program) { + ProgramUsage* usage = program.fUsage.get(); + + size_t numOwnedElements = program.fOwnedElements.size(); + size_t numSharedElements = program.fSharedElements.size(); + + if (program.fConfig->fSettings.fRemoveDeadFunctions) { + program.fOwnedElements.erase(std::remove_if(program.fOwnedElements.begin(), + program.fOwnedElements.end(), + [&](const std::unique_ptr<ProgramElement>& pe) { + return dead_function_predicate(pe.get(), + usage); + }), + program.fOwnedElements.end()); + program.fSharedElements.erase(std::remove_if(program.fSharedElements.begin(), + program.fSharedElements.end(), + [&](const ProgramElement* pe) { + return dead_function_predicate(pe, usage); + }), + program.fSharedElements.end()); + } + return program.fOwnedElements.size() < numOwnedElements || + program.fSharedElements.size() < numSharedElements; +} + +bool Transform::EliminateDeadFunctions(const Context& context, + Module& module, + ProgramUsage* usage) { + size_t numElements = module.fElements.size(); + + if (context.fConfig->fSettings.fRemoveDeadFunctions) { + module.fElements.erase(std::remove_if(module.fElements.begin(), + module.fElements.end(), + [&](const std::unique_ptr<ProgramElement>& pe) { + return dead_function_predicate(pe.get(), usage); + }), + module.fElements.end()); + } + return module.fElements.size() < numElements; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp new file mode 100644 index 0000000000..700e176ca5 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLProgramElement.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <vector> + +namespace SkSL { + +static bool is_dead_variable(const ProgramElement& element, + ProgramUsage* usage, + bool onlyPrivateGlobals) { + if (!element.is<GlobalVarDeclaration>()) { + return false; + } + const GlobalVarDeclaration& global = element.as<GlobalVarDeclaration>(); + const VarDeclaration& varDecl = global.varDeclaration(); + if (onlyPrivateGlobals && !skstd::starts_with(varDecl.var()->name(), '$')) { + return false; + } + if (!usage->isDead(*varDecl.var())) { + return false; + } + // This declaration is about to be eliminated by remove_if; update ProgramUsage accordingly. + usage->remove(&varDecl); + return true; +} + +bool Transform::EliminateDeadGlobalVariables(const Context& context, + Module& module, + ProgramUsage* usage, + bool onlyPrivateGlobals) { + auto isDeadVariable = [&](const ProgramElement& element) { + return is_dead_variable(element, usage, onlyPrivateGlobals); + }; + + size_t numElements = module.fElements.size(); + if (context.fConfig->fSettings.fRemoveDeadVariables) { + module.fElements.erase(std::remove_if(module.fElements.begin(), + module.fElements.end(), + [&](const std::unique_ptr<ProgramElement>& pe) { + return isDeadVariable(*pe); + }), + module.fElements.end()); + } + return module.fElements.size() < numElements; +} + +bool Transform::EliminateDeadGlobalVariables(Program& program) { + auto isDeadVariable = [&](const ProgramElement& element) { + return is_dead_variable(element, program.fUsage.get(), /*onlyPrivateGlobals=*/false); + }; + + size_t numOwnedElements = program.fOwnedElements.size(); + size_t numSharedElements = program.fSharedElements.size(); + if (program.fConfig->fSettings.fRemoveDeadVariables) { + program.fOwnedElements.erase(std::remove_if(program.fOwnedElements.begin(), + program.fOwnedElements.end(), + [&](const std::unique_ptr<ProgramElement>& pe) { + return isDeadVariable(*pe); + }), + program.fOwnedElements.end()); + program.fSharedElements.erase(std::remove_if(program.fSharedElements.begin(), + program.fSharedElements.end(), + [&](const ProgramElement* pe) { + return isDeadVariable(*pe); + }), + program.fSharedElements.end()); + } + return program.fOwnedElements.size() < numOwnedElements || + program.fSharedElements.size() < numSharedElements; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadLocalVariables.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadLocalVariables.cpp new file mode 100644 index 0000000000..8329cc90c0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateDeadLocalVariables.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2021 Google LLC + * + * 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/core/SkTypes.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLBinaryExpression.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLExpressionStatement.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/transform/SkSLProgramWriter.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <memory> +#include <utility> +#include <vector> + +namespace SkSL { + +class Context; + +static bool eliminate_dead_local_variables(const Context& context, + SkSpan<std::unique_ptr<ProgramElement>> elements, + ProgramUsage* usage) { + class DeadLocalVariableEliminator : public ProgramWriter { + public: + DeadLocalVariableEliminator(const Context& context, ProgramUsage* usage) + : fContext(context) + , fUsage(usage) {} + + using ProgramWriter::visitProgramElement; + + bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override { + if (expr->is<BinaryExpression>()) { + // Search for expressions of the form `deadVar = anyExpression`. + BinaryExpression& binary = expr->as<BinaryExpression>(); + if (VariableReference* assignedVar = binary.isAssignmentIntoVariable()) { + if (fDeadVariables.contains(assignedVar->variable())) { + // Replace `deadVar = anyExpression` with `anyExpression`. + fUsage->remove(binary.left().get()); + expr = std::move(binary.right()); + + // If `anyExpression` is now a lone ExpressionStatement, it's highly likely + // that we can eliminate it entirely. This flag will let us know to check. + fAssignmentWasEliminated = true; + + // Re-process the newly cleaned-up expression. This lets us fully clean up + // gnarly assignments like `a = b = 123;` where both `a` and `b` are dead, + // or silly double-assignments like `a = a = 123;`. + return this->visitExpressionPtr(expr); + } + } + } + if (expr->is<VariableReference>()) { + SkASSERT(!fDeadVariables.contains(expr->as<VariableReference>().variable())); + } + return INHERITED::visitExpressionPtr(expr); + } + + bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override { + if (stmt->is<VarDeclaration>()) { + VarDeclaration& varDecl = stmt->as<VarDeclaration>(); + const Variable* var = varDecl.var(); + ProgramUsage::VariableCounts* counts = fUsage->fVariableCounts.find(var); + SkASSERT(counts); + SkASSERT(counts->fVarExists); + if (CanEliminate(var, *counts)) { + fDeadVariables.add(var); + if (var->initialValue()) { + // The variable has an initial-value expression, which might have side + // effects. ExpressionStatement::Make will preserve side effects, but + // replaces pure expressions with Nop. + fUsage->remove(stmt.get()); + stmt = ExpressionStatement::Make(fContext, std::move(varDecl.value())); + fUsage->add(stmt.get()); + } else { + // The variable has no initial-value and can be cleanly eliminated. + fUsage->remove(stmt.get()); + stmt = Nop::Make(); + } + fMadeChanges = true; + + // Re-process the newly cleaned-up statement. This lets us fully clean up + // gnarly assignments like `a = b = 123;` where both `a` and `b` are dead, + // or silly double-assignments like `a = a = 123;`. + return this->visitStatementPtr(stmt); + } + } + + bool result = INHERITED::visitStatementPtr(stmt); + + // If we eliminated an assignment above, we may have left behind an inert + // ExpressionStatement. + if (fAssignmentWasEliminated) { + fAssignmentWasEliminated = false; + if (stmt->is<ExpressionStatement>()) { + ExpressionStatement& exprStmt = stmt->as<ExpressionStatement>(); + if (!Analysis::HasSideEffects(*exprStmt.expression())) { + // The expression-statement was inert; eliminate it entirely. + fUsage->remove(&exprStmt); + stmt = Nop::Make(); + } + } + } + + return result; + } + + static bool CanEliminate(const Variable* var, const ProgramUsage::VariableCounts& counts) { + return counts.fVarExists && !counts.fRead && var->storage() == VariableStorage::kLocal; + } + + bool fMadeChanges = false; + const Context& fContext; + ProgramUsage* fUsage; + SkTHashSet<const Variable*> fDeadVariables; + bool fAssignmentWasEliminated = false; + + using INHERITED = ProgramWriter; + }; + + DeadLocalVariableEliminator visitor{context, usage}; + + for (auto& [var, counts] : usage->fVariableCounts) { + if (DeadLocalVariableEliminator::CanEliminate(var, counts)) { + // This program contains at least one dead local variable. + // Scan the program for any dead local variables and eliminate them all. + for (std::unique_ptr<ProgramElement>& pe : elements) { + if (pe->is<FunctionDefinition>()) { + visitor.visitProgramElement(*pe); + } + } + break; + } + } + + return visitor.fMadeChanges; +} + +bool Transform::EliminateDeadLocalVariables(const Context& context, + Module& module, + ProgramUsage* usage) { + return eliminate_dead_local_variables(context, SkSpan(module.fElements), usage); +} + +bool Transform::EliminateDeadLocalVariables(Program& program) { + return program.fConfig->fSettings.fRemoveDeadVariables + ? eliminate_dead_local_variables(*program.fContext, + SkSpan(program.fOwnedElements), + program.fUsage.get()) + : false; +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateEmptyStatements.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateEmptyStatements.cpp new file mode 100644 index 0000000000..e36867bd5e --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateEmptyStatements.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Google LLC + * + * 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/SkSLDefines.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/ir/SkSLBlock.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/transform/SkSLProgramWriter.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <algorithm> +#include <iterator> +#include <memory> +#include <vector> + +namespace SkSL { + +class Expression; + +static void eliminate_empty_statements(SkSpan<std::unique_ptr<ProgramElement>> elements) { + class EmptyStatementEliminator : public ProgramWriter { + public: + bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override { + // We don't need to look inside expressions at all. + return false; + } + + bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override { + // Work from the innermost blocks to the outermost. + INHERITED::visitStatementPtr(stmt); + + if (stmt->is<Block>()) { + StatementArray& children = stmt->as<Block>().children(); + auto iter = std::remove_if(children.begin(), children.end(), + [](std::unique_ptr<Statement>& stmt) { + return stmt->isEmpty(); + }); + children.resize(std::distance(children.begin(), iter)); + } + + // We always check the entire program. + return false; + } + + using INHERITED = ProgramWriter; + }; + + for (std::unique_ptr<ProgramElement>& pe : elements) { + if (pe->is<FunctionDefinition>()) { + EmptyStatementEliminator visitor; + visitor.visitStatementPtr(pe->as<FunctionDefinition>().body()); + } + } +} + +void Transform::EliminateEmptyStatements(Module& module) { + return eliminate_empty_statements(SkSpan(module.fElements)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp b/gfx/skia/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp new file mode 100644 index 0000000000..23d1b39be0 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp @@ -0,0 +1,214 @@ +/* + * Copyright 2021 Google LLC + * + * 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/core/SkTypes.h" +#include "include/private/SkSLDefines.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/base/SkTArray.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLIfStatement.h" +#include "src/sksl/ir/SkSLNop.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSwitchCase.h" +#include "src/sksl/ir/SkSLSwitchStatement.h" +#include "src/sksl/transform/SkSLProgramWriter.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <memory> +#include <vector> + +namespace SkSL { + +class Expression; + +static void eliminate_unreachable_code(SkSpan<std::unique_ptr<ProgramElement>> elements, + ProgramUsage* usage) { + class UnreachableCodeEliminator : public ProgramWriter { + public: + UnreachableCodeEliminator(ProgramUsage* usage) : fUsage(usage) { + fFoundFunctionExit.push_back(false); + fFoundBlockExit.push_back(false); + } + + bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override { + // We don't need to look inside expressions at all. + return false; + } + + bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override { + if (fFoundFunctionExit.back() || fFoundBlockExit.back()) { + // If we already found an exit in this section, anything beyond it is dead code. + if (!stmt->is<Nop>()) { + // Eliminate the dead statement by substituting a Nop. + fUsage->remove(stmt.get()); + stmt = Nop::Make(); + } + return false; + } + + switch (stmt->kind()) { + case Statement::Kind::kReturn: + case Statement::Kind::kDiscard: + // We found a function exit on this path. + fFoundFunctionExit.back() = true; + break; + + case Statement::Kind::kBreak: + // A `break` statement can either be breaking out of a loop or terminating an + // individual switch case. We treat both cases the same way: they only apply + // to the statements associated with the parent statement (i.e. enclosing loop + // block / preceding case label). + case Statement::Kind::kContinue: + fFoundBlockExit.back() = true; + break; + + case Statement::Kind::kExpression: + case Statement::Kind::kNop: + case Statement::Kind::kVarDeclaration: + // These statements don't affect control flow. + break; + + case Statement::Kind::kBlock: + // Blocks are on the straight-line path and don't affect control flow. + return INHERITED::visitStatementPtr(stmt); + + case Statement::Kind::kDo: { + // Function-exits are allowed to propagate outside of a do-loop, because it + // always executes its body at least once. + fFoundBlockExit.push_back(false); + bool result = INHERITED::visitStatementPtr(stmt); + fFoundBlockExit.pop_back(); + return result; + } + case Statement::Kind::kFor: { + // Function-exits are not allowed to propagate out, because a for-loop or while- + // loop could potentially run zero times. + fFoundFunctionExit.push_back(false); + fFoundBlockExit.push_back(false); + bool result = INHERITED::visitStatementPtr(stmt); + fFoundBlockExit.pop_back(); + fFoundFunctionExit.pop_back(); + return result; + } + case Statement::Kind::kIf: { + // This statement is conditional and encloses two inner sections of code. + // If both sides contain a function-exit or loop-exit, that exit is allowed to + // propagate out. + IfStatement& ifStmt = stmt->as<IfStatement>(); + + fFoundFunctionExit.push_back(false); + fFoundBlockExit.push_back(false); + bool result = (ifStmt.ifTrue() && this->visitStatementPtr(ifStmt.ifTrue())); + bool foundFunctionExitOnTrue = fFoundFunctionExit.back(); + bool foundLoopExitOnTrue = fFoundBlockExit.back(); + fFoundFunctionExit.pop_back(); + fFoundBlockExit.pop_back(); + + fFoundFunctionExit.push_back(false); + fFoundBlockExit.push_back(false); + result |= (ifStmt.ifFalse() && this->visitStatementPtr(ifStmt.ifFalse())); + bool foundFunctionExitOnFalse = fFoundFunctionExit.back(); + bool foundLoopExitOnFalse = fFoundBlockExit.back(); + fFoundFunctionExit.pop_back(); + fFoundBlockExit.pop_back(); + + fFoundFunctionExit.back() |= foundFunctionExitOnTrue && + foundFunctionExitOnFalse; + fFoundBlockExit.back() |= foundLoopExitOnTrue && + foundLoopExitOnFalse; + return result; + } + case Statement::Kind::kSwitch: { + // In switch statements we consider unreachable code on a per-case basis. + SwitchStatement& sw = stmt->as<SwitchStatement>(); + bool result = false; + + // Tracks whether we found at least one case that doesn't lead to a return + // statement (potentially via fallthrough). + bool foundCaseWithoutReturn = false; + bool hasDefault = false; + for (std::unique_ptr<Statement>& c : sw.cases()) { + // We eliminate unreachable code within the statements of the individual + // case. Breaks are not allowed to propagate outside the case statement + // itself. Function returns are allowed to propagate out only if all cases + // have a return AND one of the cases is default (so that we know at least + // one of the branches will be taken). This is similar to how we handle if + // statements above. + fFoundFunctionExit.push_back(false); + fFoundBlockExit.push_back(false); + + SwitchCase& sc = c->as<SwitchCase>(); + result |= this->visitStatementPtr(sc.statement()); + + // When considering whether a case has a return we can propagate, we + // assume the following: + // 1. The default case is always placed last in a switch statement and + // it is the last possible label reachable via fallthrough. Thus if + // it does not contain a return statement, then we don't propagate a + // function return. + // 2. In all other cases we prevent the return from propagating only if + // we encounter a break statement. If no return or break is found, + // we defer the decision to the fallthrough case. We won't propagate + // a return unless we eventually encounter a default label. + // + // See resources/sksl/shared/SwitchWithEarlyReturn.sksl for test cases that + // exercise this. + if (sc.isDefault()) { + foundCaseWithoutReturn |= !fFoundFunctionExit.back(); + hasDefault = true; + } else { + // We can only be sure that a case does not lead to a return if it + // doesn't fallthrough. + foundCaseWithoutReturn |= + (!fFoundFunctionExit.back() && fFoundBlockExit.back()); + } + + fFoundFunctionExit.pop_back(); + fFoundBlockExit.pop_back(); + } + + fFoundFunctionExit.back() |= !foundCaseWithoutReturn && hasDefault; + return result; + } + case Statement::Kind::kSwitchCase: + // We should never hit this case as switch cases are handled in the previous + // case. + SkUNREACHABLE; + } + + return false; + } + + ProgramUsage* fUsage; + SkSTArray<32, bool> fFoundFunctionExit; + SkSTArray<32, bool> fFoundBlockExit; + + using INHERITED = ProgramWriter; + }; + + for (std::unique_ptr<ProgramElement>& pe : elements) { + if (pe->is<FunctionDefinition>()) { + UnreachableCodeEliminator visitor{usage}; + visitor.visitStatementPtr(pe->as<FunctionDefinition>().body()); + } + } +} + +void Transform::EliminateUnreachableCode(Module& module, ProgramUsage* usage) { + return eliminate_unreachable_code(SkSpan(module.fElements), usage); +} + +void Transform::EliminateUnreachableCode(Program& program) { + return eliminate_unreachable_code(SkSpan(program.fOwnedElements), program.fUsage.get()); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinFunctions.cpp b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinFunctions.cpp new file mode 100644 index 0000000000..cb937dec49 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinFunctions.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLIntrinsicList.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +namespace SkSL { + +class ProgramElement; + +void Transform::FindAndDeclareBuiltinFunctions(Program& program) { + ProgramUsage* usage = program.fUsage.get(); + Context& context = *program.fContext; + + std::vector<const FunctionDefinition*> addedBuiltins; + for (;;) { + // Find all the built-ins referenced by the program but not yet included in the code. + size_t numBuiltinsAtStart = addedBuiltins.size(); + for (const auto& [fn, count] : usage->fCallCounts) { + if (!fn->isBuiltin() || count == 0) { + // Not a built-in; skip it. + continue; + } + if (fn->intrinsicKind() == k_dFdy_IntrinsicKind) { + // Programs that invoke the `dFdy` intrinsic will need the RTFlip input. + program.fInputs.fUseFlipRTUniform = !context.fConfig->fSettings.fForceNoRTFlip; + } + if (const FunctionDefinition* builtinDef = fn->definition()) { + // Make sure we only add a built-in function once. We rarely add more than a handful + // of builtin functions, so linear search here is good enough. + if (std::find(addedBuiltins.begin(), addedBuiltins.end(), builtinDef) == + addedBuiltins.end()) { + addedBuiltins.push_back(builtinDef); + } + } + } + + if (addedBuiltins.size() == numBuiltinsAtStart) { + // If we didn't reference any more built-in functions than before, we're done. + break; + } + + // Sort the referenced builtin functions into a consistent order; otherwise our output will + // become non-deterministic. The exact order isn't particularly important; we sort backwards + // because we add elements to the shared-elements in reverse order at the end. + std::sort(addedBuiltins.begin() + numBuiltinsAtStart, + addedBuiltins.end(), + [](const FunctionDefinition* aDefinition, const FunctionDefinition* bDefinition) { + const FunctionDeclaration& a = aDefinition->declaration(); + const FunctionDeclaration& b = bDefinition->declaration(); + if (a.name() != b.name()) { + return a.name() > b.name(); + } + return a.description() > b.description(); + }); + + // Update the ProgramUsage to track all these newly discovered functions. + int usageCallCounts = usage->fCallCounts.count(); + + for (size_t index = numBuiltinsAtStart; index < addedBuiltins.size(); ++index) { + usage->add(*addedBuiltins[index]); + } + + if (usage->fCallCounts.count() == usageCallCounts) { + // If we aren't making any more unique function calls than before, we're done. + break; + } + } + + // Insert the new functions into the program's shared elements, right at the front. + // They are added in reverse so that the deepest dependencies are added to the top. + program.fSharedElements.insert(program.fSharedElements.begin(), + addedBuiltins.rbegin(), addedBuiltins.rend()); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinVariables.cpp b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinVariables.cpp new file mode 100644 index 0000000000..bbc68fa7af --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLFindAndDeclareBuiltinVariables.cpp @@ -0,0 +1,180 @@ +/* + * Copyright 2021 Google LLC. + * + * 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/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLSymbol.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/SkSLUtil.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLInterfaceBlock.h" +#include "src/sksl/ir/SkSLProgram.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <algorithm> +#include <memory> +#include <string_view> +#include <vector> + +namespace SkSL { +namespace Transform { +namespace { + +class BuiltinVariableScanner { +public: + BuiltinVariableScanner(const Context& context, const SymbolTable& symbols) + : fContext(context) + , fSymbols(symbols) {} + + void addDeclaringElement(const ProgramElement* decl) { + // Make sure we only add a built-in variable once. We only have a small handful of built-in + // variables to declare, so linear search here is good enough. + if (std::find(fNewElements.begin(), fNewElements.end(), decl) == fNewElements.end()) { + fNewElements.push_back(decl); + } + } + + void addDeclaringElement(const Symbol* symbol) { + if (!symbol || !symbol->is<Variable>()) { + return; + } + const Variable& var = symbol->as<Variable>(); + if (const GlobalVarDeclaration* decl = var.globalVarDeclaration()) { + this->addDeclaringElement(decl); + } else if (const InterfaceBlock* block = var.interfaceBlock()) { + this->addDeclaringElement(block); + } else { + // Double-check that this variable isn't associated with a global or an interface block. + // (Locals and parameters will come along naturally as part of the associated function.) + SkASSERTF(var.storage() != VariableStorage::kGlobal && + var.storage() != VariableStorage::kInterfaceBlock, + "%.*s", (int)var.name().size(), var.name().data()); + } + } + + void addImplicitFragColorWrite(SkSpan<const std::unique_ptr<ProgramElement>> elements) { + for (const std::unique_ptr<ProgramElement>& pe : elements) { + if (!pe->is<FunctionDefinition>()) { + continue; + } + const FunctionDefinition& funcDef = pe->as<FunctionDefinition>(); + if (funcDef.declaration().isMain()) { + if (funcDef.declaration().returnType().matches(*fContext.fTypes.fHalf4)) { + // We synthesize writes to sk_FragColor if main() returns a color, even if it's + // otherwise unreferenced. + this->addDeclaringElement(fSymbols.findBuiltinSymbol(Compiler::FRAGCOLOR_NAME)); + } + // Now that main() has been found, we can stop scanning. + break; + } + } + } + + static std::string_view GlobalVarBuiltinName(const ProgramElement& elem) { + return elem.as<GlobalVarDeclaration>().varDeclaration().var()->name(); + } + + static std::string_view InterfaceBlockName(const ProgramElement& elem) { + return elem.as<InterfaceBlock>().instanceName(); + } + + void sortNewElements() { + std::sort(fNewElements.begin(), + fNewElements.end(), + [](const ProgramElement* a, const ProgramElement* b) { + if (a->kind() != b->kind()) { + return a->kind() < b->kind(); + } + switch (a->kind()) { + case ProgramElement::Kind::kGlobalVar: + SkASSERT(GlobalVarBuiltinName(*a) != GlobalVarBuiltinName(*b)); + return GlobalVarBuiltinName(*a) < GlobalVarBuiltinName(*b); + + case ProgramElement::Kind::kInterfaceBlock: + SkASSERT(InterfaceBlockName(*a) != InterfaceBlockName(*b)); + return InterfaceBlockName(*a) < InterfaceBlockName(*b); + + default: + SkUNREACHABLE; + } + }); + } + + const Context& fContext; + const SymbolTable& fSymbols; + std::vector<const ProgramElement*> fNewElements; +}; + +} // namespace + +void FindAndDeclareBuiltinVariables(Program& program) { + const Context& context = *program.fContext; + const SymbolTable& symbols = *program.fSymbols; + BuiltinVariableScanner scanner(context, symbols); + + if (ProgramConfig::IsFragment(program.fConfig->fKind)) { + // Find main() in the program and check its return type. + // If it's half4, we treat that as an implicit write to sk_FragColor and add a reference. + scanner.addImplicitFragColorWrite(program.fOwnedElements); + + // Vulkan requires certain builtin variables be present, even if they're unused. At one + // time, validation errors would result if sk_Clockwise was missing. Now, it's just (Adreno) + // driver bugs that drop or corrupt draws if they're missing. + scanner.addDeclaringElement(symbols.findBuiltinSymbol("sk_Clockwise")); + } + + // Scan all the variables used by the program and declare any built-ins. + for (const auto& [var, counts] : program.fUsage->fVariableCounts) { + if (var->isBuiltin()) { + scanner.addDeclaringElement(var); + + // Set the FlipRT program input if we find sk_FragCoord or sk_Clockwise. + switch (var->modifiers().fLayout.fBuiltin) { + case SK_FRAGCOORD_BUILTIN: + if (context.fCaps->fCanUseFragCoord) { + program.fInputs.fUseFlipRTUniform = + !context.fConfig->fSettings.fForceNoRTFlip; + } + break; + + case SK_CLOCKWISE_BUILTIN: + program.fInputs.fUseFlipRTUniform = !context.fConfig->fSettings.fForceNoRTFlip; + break; + } + } + } + + // Sort the referenced builtin functions into a consistent order; otherwise our output will + // become non-deterministic. The exact order isn't particularly important. + scanner.sortNewElements(); + + // Add all the newly-declared elements to the program, and update ProgramUsage to match. + program.fSharedElements.insert(program.fSharedElements.begin(), + scanner.fNewElements.begin(), + scanner.fNewElements.end()); + + for (const ProgramElement* element : scanner.fNewElements) { + program.fUsage->add(*element); + } +} + +} // namespace Transform +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLProgramWriter.h b/gfx/skia/skia/src/sksl/transform/SkSLProgramWriter.h new file mode 100644 index 0000000000..6e5988aa92 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLProgramWriter.h @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSLProgramWriter_DEFINED +#define SkSLProgramWriter_DEFINED + +#include "src/sksl/analysis/SkSLProgramVisitor.h" + +namespace SkSL { + +struct ProgramWriterTypes { + using Program = SkSL::Program; + using Expression = SkSL::Expression; + using Statement = SkSL::Statement; + using ProgramElement = SkSL::ProgramElement; + using UniquePtrExpression = std::unique_ptr<SkSL::Expression>; + using UniquePtrStatement = std::unique_ptr<SkSL::Statement>; +}; + +extern template class TProgramVisitor<ProgramWriterTypes>; + +class ProgramWriter : public TProgramVisitor<ProgramWriterTypes> { +public: + // Subclass these methods if you want access to the unique_ptrs of IRNodes in a program. + // This will allow statements or expressions to be replaced during a visit. + bool visitExpressionPtr(std::unique_ptr<Expression>& e) override { + return this->visitExpression(*e); + } + bool visitStatementPtr(std::unique_ptr<Statement>& s) override { + return this->visitStatement(*s); + } +}; + +} // namespace SkSL + +#endif diff --git a/gfx/skia/skia/src/sksl/transform/SkSLRenamePrivateSymbols.cpp b/gfx/skia/skia/src/sksl/transform/SkSLRenamePrivateSymbols.cpp new file mode 100644 index 0000000000..50bb88baf9 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLRenamePrivateSymbols.cpp @@ -0,0 +1,243 @@ +/* + * Copyright 2022 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLIRNode.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "include/private/SkSLStatement.h" +#include "include/private/SkSLSymbol.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLAnalysis.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLFunctionDeclaration.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLFunctionPrototype.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLVarDeclarations.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/transform/SkSLProgramWriter.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace SkSL { + +class ProgramUsage; +enum class ProgramKind : int8_t; + +static void strip_export_flag(Context& context, + const FunctionDeclaration* funcDecl, + SymbolTable* symbols) { + // Remove `$export` from every overload of this function. + Symbol* mutableSym = symbols->findMutable(funcDecl->name()); + while (mutableSym) { + FunctionDeclaration* mutableDecl = &mutableSym->as<FunctionDeclaration>(); + + Modifiers modifiers = mutableDecl->modifiers(); + modifiers.fFlags &= ~Modifiers::kExport_Flag; + mutableDecl->setModifiers(context.fModifiersPool->add(modifiers)); + + mutableSym = mutableDecl->mutableNextOverload(); + } +} + +void Transform::RenamePrivateSymbols(Context& context, + Module& module, + ProgramUsage* usage, + ProgramKind kind) { + class SymbolRenamer : public ProgramWriter { + public: + SymbolRenamer(Context& context, + ProgramUsage* usage, + std::shared_ptr<SymbolTable> symbolBase, + ProgramKind kind) + : fContext(context) + , fUsage(usage) + , fSymbolTableStack({std::move(symbolBase)}) + , fKind(kind) {} + + static std::string FindShortNameForSymbol(const Symbol* sym, + const SymbolTable* symbolTable, + std::string namePrefix) { + static constexpr std::string_view kLetters[] = { + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; + + // Try any single-letter option. + for (std::string_view letter : kLetters) { + std::string name = namePrefix + std::string(letter); + if (symbolTable->find(name) == nullptr) { + return name; + } + } + + // Try every two-letter option. + for (std::string_view letterA : kLetters) { + for (std::string_view letterB : kLetters) { + std::string name = namePrefix + std::string(letterA) + std::string(letterB); + if (symbolTable->find(name) == nullptr) { + return name; + } + } + } + + // We struck out. Somehow, all 2700 two-letter names have been claimed. + SkDEBUGFAILF("Unable to find unique name for '%s'", std::string(sym->name()).c_str()); + return std::string(sym->name()); + } + + void minifyVariableName(const Variable* var) { + // Some variables are associated with anonymous parameters--these don't have names and + // aren't present in the symbol table. Their names are already empty so there's no way + // to shrink them further. + if (var->name().empty()) { + return; + } + + // Ensure that this variable is properly set up in the symbol table. + SymbolTable* symbols = fSymbolTableStack.back().get(); + Symbol* mutableSym = symbols->findMutable(var->name()); + SkASSERTF(mutableSym != nullptr, + "symbol table missing '%.*s'", (int)var->name().size(), var->name().data()); + SkASSERTF(mutableSym == var, + "wrong symbol found for '%.*s'", (int)var->name().size(), var->name().data()); + + // Look for a new name for this symbol. + // Note: we always rename _every_ variable, even ones with single-letter names. This is + // a safeguard: if we claimed a name like `i`, and then the program itself contained an + // `i` later on, in a nested SymbolTable, the two names would clash. By always renaming + // everything, we can ignore that problem. + std::string shortName = FindShortNameForSymbol(var, symbols, ""); + SkASSERT(symbols->findMutable(shortName) == nullptr); + + // Update the symbol's name. + const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName)); + symbols->renameSymbol(mutableSym, *ownedName); + } + + void minifyFunctionName(const FunctionDeclaration* funcDecl) { + // Look for a new name for this function. + std::string namePrefix = ProgramConfig::IsRuntimeEffect(fKind) ? "" : "$"; + SymbolTable* symbols = fSymbolTableStack.back().get(); + std::string shortName = FindShortNameForSymbol(funcDecl, symbols, + std::move(namePrefix)); + SkASSERT(symbols->findMutable(shortName) == nullptr); + + if (shortName.size() < funcDecl->name().size()) { + // Update the function's name. (If the function has overloads, this will rename all + // of them at once.) + Symbol* mutableSym = symbols->findMutable(funcDecl->name()); + const std::string* ownedName = symbols->takeOwnershipOfString(std::move(shortName)); + symbols->renameSymbol(mutableSym, *ownedName); + } + } + + bool functionNameCanBeMinifiedSafely(const FunctionDeclaration& funcDecl) const { + if (ProgramConfig::IsRuntimeEffect(fKind)) { + // The only externally-accessible function in a runtime effect is main(). + return !funcDecl.isMain(); + } else { + // We will only minify $private_functions, and only ones not marked as $export. + return skstd::starts_with(funcDecl.name(), '$') && + !(funcDecl.modifiers().fFlags & Modifiers::kExport_Flag); + } + } + + void minifyFunction(FunctionDefinition& def) { + // If the function is private, minify its name. + const FunctionDeclaration* funcDecl = &def.declaration(); + if (this->functionNameCanBeMinifiedSafely(*funcDecl)) { + this->minifyFunctionName(funcDecl); + } + + // Minify the names of each function parameter. + Analysis::SymbolTableStackBuilder symbolTableStackBuilder(def.body().get(), + &fSymbolTableStack); + for (Variable* param : funcDecl->parameters()) { + this->minifyVariableName(param); + } + } + + void minifyPrototype(FunctionPrototype& proto) { + const FunctionDeclaration* funcDecl = &proto.declaration(); + if (funcDecl->definition()) { + // This function is defined somewhere; this isn't just a loose prototype. + return; + } + + // Eliminate the names of each function parameter. + // The parameter names aren't in the symbol table's name lookup map at all. + // All we need to do is blank out their names. + for (Variable* param : funcDecl->parameters()) { + param->setName(""); + } + } + + bool visitProgramElement(ProgramElement& elem) override { + switch (elem.kind()) { + case ProgramElement::Kind::kFunction: + this->minifyFunction(elem.as<FunctionDefinition>()); + return INHERITED::visitProgramElement(elem); + + case ProgramElement::Kind::kFunctionPrototype: + this->minifyPrototype(elem.as<FunctionPrototype>()); + return INHERITED::visitProgramElement(elem); + + default: + return false; + } + } + + bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override { + Analysis::SymbolTableStackBuilder symbolTableStackBuilder(stmt.get(), + &fSymbolTableStack); + if (stmt->is<VarDeclaration>()) { + // Minify the variable's name. + VarDeclaration& decl = stmt->as<VarDeclaration>(); + this->minifyVariableName(decl.var()); + } + + return INHERITED::visitStatementPtr(stmt); + } + + Context& fContext; + ProgramUsage* fUsage; + std::vector<std::shared_ptr<SymbolTable>> fSymbolTableStack; + ProgramKind fKind; + using INHERITED = ProgramWriter; + }; + + // Rename local variables and private functions. + SymbolRenamer renamer{context, usage, module.fSymbols, kind}; + for (std::unique_ptr<ProgramElement>& pe : module.fElements) { + renamer.visitProgramElement(*pe); + } + + // Strip off modifier `$export` from every function. (Only the minifier checks this flag, so we + // can remove it without affecting the meaning of the code.) + for (std::unique_ptr<ProgramElement>& pe : module.fElements) { + if (pe->is<FunctionDefinition>()) { + const FunctionDeclaration* funcDecl = &pe->as<FunctionDefinition>().declaration(); + if (funcDecl->modifiers().fFlags & Modifiers::kExport_Flag) { + strip_export_flag(context, funcDecl, module.fSymbols.get()); + } + } + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLReplaceConstVarsWithLiterals.cpp b/gfx/skia/skia/src/sksl/transform/SkSLReplaceConstVarsWithLiterals.cpp new file mode 100644 index 0000000000..e484104a33 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLReplaceConstVarsWithLiterals.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2021 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramElement.h" +#include "src/core/SkTHash.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLConstantFolder.h" +#include "src/sksl/analysis/SkSLProgramUsage.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLFunctionDefinition.h" +#include "src/sksl/ir/SkSLVariable.h" +#include "src/sksl/ir/SkSLVariableReference.h" +#include "src/sksl/transform/SkSLProgramWriter.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <cstddef> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +namespace SkSL { + +void Transform::ReplaceConstVarsWithLiterals(Module& module, ProgramUsage* usage) { + class ConstVarReplacer : public ProgramWriter { + public: + ConstVarReplacer(ProgramUsage* usage) : fUsage(usage) {} + + using ProgramWriter::visitProgramElement; + + bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override { + // If this is a variable... + if (expr->is<VariableReference>()) { + VariableReference& var = expr->as<VariableReference>(); + // ... and it's a candidate for size reduction... + if (fCandidates.contains(var.variable())) { + // ... get its constant value... + if (const Expression* value = + ConstantFolder::GetConstantValueOrNullForVariable(var)) { + // ... and replace it with that value. + fUsage->remove(expr.get()); + expr = value->clone(); + fUsage->add(expr.get()); + return false; + } + } + } + return INHERITED::visitExpressionPtr(expr); + } + + ProgramUsage* fUsage; + SkTHashSet<const Variable*> fCandidates; + + using INHERITED = ProgramWriter; + }; + + ConstVarReplacer visitor{usage}; + + for (const auto& [var, count] : usage->fVariableCounts) { + // We can only replace const variables that still exist, and that have initial values. + if (!count.fVarExists || count.fWrite != 1) { + continue; + } + if (!(var->modifiers().fFlags & Modifiers::kConst_Flag)) { + continue; + } + if (!var->initialValue()) { + continue; + } + // The current size is: + // strlen("const type varname=initialvalue;`") + count*strlen("varname"). + size_t initialvalueSize = ConstantFolder::GetConstantValueForVariable(*var->initialValue()) + ->description() + .size(); + size_t totalOldSize = var->description().size() + // const type varname + 1 + // = + initialvalueSize + // initialvalue + 1 + // ; + count.fRead * var->name().size(); // count * varname + // If we replace varname with initialvalue everywhere, the new size would be: + // count*strlen("initialvalue") + size_t totalNewSize = count.fRead * initialvalueSize; // count * initialvalue + + if (totalNewSize <= totalOldSize) { + visitor.fCandidates.add(var); + } + } + + if (!visitor.fCandidates.empty()) { + for (std::unique_ptr<ProgramElement>& pe : module.fElements) { + if (pe->is<FunctionDefinition>()) { + visitor.visitProgramElement(*pe); + } + } + } +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLRewriteIndexedSwizzle.cpp b/gfx/skia/skia/src/sksl/transform/SkSLRewriteIndexedSwizzle.cpp new file mode 100644 index 0000000000..21c68d97b1 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLRewriteIndexedSwizzle.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkSLDefines.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/ir/SkSLConstructorCompound.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLIndexExpression.h" +#include "src/sksl/ir/SkSLLiteral.h" +#include "src/sksl/ir/SkSLSwizzle.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/transform/SkSLTransform.h" + +#include <cstdint> +#include <memory> +#include <utility> + +namespace SkSL { + +std::unique_ptr<Expression> Transform::RewriteIndexedSwizzle(const Context& context, + const IndexExpression& indexExpr) { + // The index expression _must_ have a swizzle base for this transformation to be valid. + if (!indexExpr.base()->is<Swizzle>()) { + return nullptr; + } + const Swizzle& swizzle = indexExpr.base()->as<Swizzle>(); + + // Convert the swizzle components to a literal array. + ExpressionArray vecArray; + vecArray.reserve(swizzle.components().size()); + for (int8_t comp : swizzle.components()) { + vecArray.push_back(Literal::Make(indexExpr.fPosition, comp, context.fTypes.fInt.get())); + } + + // Make a compound constructor with the literal array. + const Type& vecType = context.fTypes.fInt->toCompound(context, vecArray.size(), /*rows=*/1); + std::unique_ptr<Expression> vec = + ConstructorCompound::Make(context, indexExpr.fPosition, vecType, std::move(vecArray)); + + // Create a rewritten inner-expression corresponding to `vec(1,2,3)[originalIndex]`. + std::unique_ptr<Expression> innerExpr = IndexExpression::Make( + context, indexExpr.fPosition, std::move(vec), indexExpr.index()->clone()); + + // Return a rewritten outer-expression corresponding to `base[vec(1,2,3)[originalIndex]]`. + return IndexExpression::Make( + context, indexExpr.fPosition, swizzle.base()->clone(), std::move(innerExpr)); +} + +} // namespace SkSL diff --git a/gfx/skia/skia/src/sksl/transform/SkSLTransform.h b/gfx/skia/skia/src/sksl/transform/SkSLTransform.h new file mode 100644 index 0000000000..e051ec8086 --- /dev/null +++ b/gfx/skia/skia/src/sksl/transform/SkSLTransform.h @@ -0,0 +1,103 @@ +/* + * Copyright 2021 Google LLC. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SKSL_TRANSFORM +#define SKSL_TRANSFORM + +#include "include/core/SkSpan.h" +#include <memory> +#include <vector> + +namespace SkSL { + +class Context; +class Expression; +class IndexExpression; +struct Modifiers; +struct Module; +struct Program; +class ProgramElement; +class ProgramUsage; +class Statement; +class Variable; +enum class ProgramKind : int8_t; + +namespace Transform { + +/** + * Checks to see if it would be safe to add `const` to the modifiers of a variable. If so, returns + * the modifiers with `const` applied; if not, returns the existing modifiers as-is. Adding `const` + * allows the inliner to fold away more values and generate tighter code. + */ +const Modifiers* AddConstToVarModifiers(const Context& context, + const Variable& var, + const Expression* initialValue, + const ProgramUsage* usage); + +/** + * Rewrites indexed swizzles of the form `myVec.zyx[i]` by replacing the swizzle with a lookup into + * a constant vector. e.g., the above expression would be rewritten as `myVec[vec3(2, 1, 0)[i]]`. + * This roughly matches glslang's handling of the code. + */ +std::unique_ptr<Expression> RewriteIndexedSwizzle(const Context& context, + const IndexExpression& swizzle); + +/** + * Copies built-in functions from modules into the program. Relies on ProgramUsage to determine + * which functions are necessary. + */ +void FindAndDeclareBuiltinFunctions(Program& program); + +/** + * Scans the finished program for built-in variables like `sk_FragColor` and adds them to the + * program's shared elements. + */ +void FindAndDeclareBuiltinVariables(Program& program); + +/** + * Eliminates statements in a block which cannot be reached; for example, a statement + * immediately after a `return` or `continue` can safely be eliminated. + */ +void EliminateUnreachableCode(Module& module, ProgramUsage* usage); +void EliminateUnreachableCode(Program& program); + +/** + * Eliminates empty statements in a module (Nops, or blocks holding only Nops). Not implemented for + * Programs because Nops are harmless, but they waste space in long-lived module IR. + */ +void EliminateEmptyStatements(Module& module); + +/** + * Eliminates functions in a program which are never called. Returns true if any changes were made. + */ +bool EliminateDeadFunctions(const Context& context, Module& module, ProgramUsage* usage); +bool EliminateDeadFunctions(Program& program); + +/** + * Eliminates variables in a program which are never read or written (past their initializer). + * Preserves side effects from initializers, if any. Returns true if any changes were made. + */ +bool EliminateDeadLocalVariables(const Context& context, + Module& module, + ProgramUsage* usage); +bool EliminateDeadLocalVariables(Program& program); +bool EliminateDeadGlobalVariables(const Context& context, + Module& module, + ProgramUsage* usage, + bool onlyPrivateGlobals); +bool EliminateDeadGlobalVariables(Program& program); + +/** Renames private functions and function-local variables to minimize code size. */ +void RenamePrivateSymbols(Context& context, Module& module, ProgramUsage* usage, ProgramKind kind); + +/** Replaces constant variables in a program with their equivalent values. */ +void ReplaceConstVarsWithLiterals(Module& module, ProgramUsage* usage); + +} // namespace Transform +} // namespace SkSL + +#endif |