Don't key progams/pipelines on origin.

SkSL language features that are origin sensitive now use a uniform
to conditionally flip their result rather than generating different
code.

Previously we would insert a "rt height" uniform if sk_FragCoord needed
to be flipped. sk_FragCoord,y was implemented as "realFragCoord.y" or
"rtHeight - realFragCoord.y" depending on SkSL::ProgramSettings::fFlipY.

Now we instead use a two component vector rtFlip and sk_FragCoord.y is
always "rtFlip.x + rtFlip.y*realFragCoord.y". We configure rtFlip as
either (0, 1) or (rtHeight, -1). sk_Clockwise and dFdy simiarly use
rtFlip.y to emit code that always works with either origin.

Bug: skia:12037

Change-Id: I7a09d0caac60a58d72b76645ff31bcabde4086b6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/414796
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
diff --git a/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp b/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
index 1775f30..8d9c074 100644
--- a/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLSPIRVCodeGenerator.cpp
@@ -9,11 +9,14 @@
 
 #include "src/sksl/GLSL.std.450.h"
 
+#include "include/sksl/DSLCore.h"
 #include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/SkSLOperators.h"
+#include "src/sksl/dsl/priv/DSLWriter.h"
 #include "src/sksl/ir/SkSLBlock.h"
 #include "src/sksl/ir/SkSLExpressionStatement.h"
 #include "src/sksl/ir/SkSLExtension.h"
+#include "src/sksl/ir/SkSLField.h"
 #include "src/sksl/ir/SkSLIndexExpression.h"
 #include "src/sksl/ir/SkSLVariableReference.h"
 
@@ -23,6 +26,9 @@
 
 #define kLast_Capability SpvCapabilityMultiViewport
 
+constexpr int DEVICE_FRAGCOORDS_BUILTIN = -1000;
+constexpr int DEVICE_CLOCKWISE_BUILTIN  = -1001;
+
 namespace SkSL {
 
 static const int32_t SKSL_MAGIC  = 0x0; // FIXME: we should probably register a magic number
@@ -844,14 +850,35 @@
     }
 }
 
+SpvId SPIRVCodeGenerator::vectorize(const Expression& arg, int vectorSize, OutputStream& out) {
+    SkASSERT(vectorSize >= 1 && vectorSize <= 4);
+    const Type& argType = arg.type();
+    SpvId raw = this->writeExpression(arg, out);
+    if (argType.isScalar()) {
+        if (vectorSize == 1) {
+            return raw;
+        }
+        SpvId vector = this->nextId(&argType);
+        this->writeOpCode(SpvOpCompositeConstruct, 3 + vectorSize, out);
+        this->writeWord(this->getType(argType.toCompound(fContext, vectorSize, 1)), out);
+        this->writeWord(vector, out);
+        for (int i = 0; i < vectorSize; i++) {
+            this->writeWord(raw, out);
+        }
+        return vector;
+    } else {
+        SkASSERT(vectorSize == argType.columns());
+        return raw;
+    }
+}
+
 std::vector<SpvId> SPIRVCodeGenerator::vectorize(const ExpressionArray& args, OutputStream& out) {
-    int vectorSize = 0;
+    int vectorSize = 1;
     for (const auto& a : args) {
         if (a->type().isVector()) {
-            if (vectorSize) {
+            if (vectorSize > 1) {
                 SkASSERT(a->type().columns() == vectorSize);
-            }
-            else {
+            } else {
                 vectorSize = a->type().columns();
             }
         }
@@ -859,20 +886,7 @@
     std::vector<SpvId> result;
     result.reserve(args.size());
     for (const auto& arg : args) {
-        const Type& argType = arg->type();
-        SpvId raw = this->writeExpression(*arg, out);
-        if (vectorSize && argType.isScalar()) {
-            SpvId vector = this->nextId(&arg->type());
-            this->writeOpCode(SpvOpCompositeConstruct, 3 + vectorSize, out);
-            this->writeWord(this->getType(argType.toCompound(fContext, vectorSize, 1)), out);
-            this->writeWord(vector, out);
-            for (int i = 0; i < vectorSize; i++) {
-                this->writeWord(raw, out);
-            }
-            result.push_back(vector);
-        } else {
-            result.push_back(raw);
-        }
+        result.push_back(this->vectorize(*arg, vectorSize, out));
     }
     return result;
 }
@@ -1045,13 +1059,15 @@
             this->writeWord(this->getType(callType), out);
             this->writeWord(result, out);
             this->writeWord(fn, out);
-            if (fProgram.fConfig->fSettings.fFlipY) {
-                // Flipping Y also negates the Y derivatives.
-                SpvId flipped = this->nextId(&callType);
-                this->writeInstruction(SpvOpFNegate, this->getType(callType), flipped, result,
-                                       out);
-                result = flipped;
-            }
+            this->addRTFlipUniform(c.fOffset);
+            using namespace dsl;
+            DSLExpression rtFlip(DSLWriter::IRGenerator().convertIdentifier(/*offset=*/-1,
+                                                                            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: {
@@ -2032,137 +2048,98 @@
 }
 
 SpvId SPIRVCodeGenerator::writeVariableReference(const VariableReference& ref, OutputStream& out) {
-    SpvId result = this->getLValue(ref, out)->load(out);
+    if (ref.variable()->modifiers().fLayout.fBuiltin == 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::DSLVar fragCoord("sk_FragCoord");
+        return this->getLValue(*dsl::DSLExpression(fragCoord).release(), out)->load(out);
+    }
+    if (ref.variable()->modifiers().fLayout.fBuiltin == 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::DSLVar clockwise("sk_Clockwise");
+        return this->getLValue(*dsl::DSLExpression(clockwise).release(), out)->load(out);
+    }
 
-    // Handle the "flipY" setting when reading sk_FragCoord.
+    // Handle inserting use of uniform to flip y when referencing sk_FragCoord.
     const Variable* variable = ref.variable();
-    if (variable->modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN &&
-        fProgram.fConfig->fSettings.fFlipY) {
-        // The x component never changes, so just grab it
-        SpvId xId = this->nextId(Precision::kDefault);
-        this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fTypes.fFloat), xId,
-                               result, 0, out);
-
-        // Calculate the y component which may need to be flipped
-        SpvId rawYId = this->nextId(nullptr);
-        this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fTypes.fFloat),
-                               rawYId, result, 1, out);
-        SpvId flippedYId = 0;
-        if (fProgram.fConfig->fSettings.fFlipY) {
-            // need to remap to a top-left coordinate system
-            if (fRTHeightStructId == (SpvId)-1) {
-                // height variable hasn't been written yet
-                SkASSERT(fRTHeightFieldIndex == (SpvId)-1);
-                std::vector<Type::Field> fields;
-                if (fProgram.fConfig->fSettings.fRTHeightOffset < 0) {
-                    fErrors.error(ref.fOffset, "RTHeightOffset is negative");
-                }
-                fields.emplace_back(
-                        Modifiers(Layout(/*flags=*/0, /*location=*/-1,
-                                         fProgram.fConfig->fSettings.fRTHeightOffset,
-                                         /*binding=*/-1, /*index=*/-1, /*set=*/-1, /*builtin=*/-1,
-                                         /*inputAttachmentIndex=*/-1,
-                                         Layout::kUnspecified_Primitive, /*maxVertices=*/1,
-                                         /*invocations=*/-1, /*when=*/"", Layout::CType::kDefault),
-                                  /*flags=*/0),
-                        SKSL_RTHEIGHT_NAME, fContext.fTypes.fFloat.get());
-                String name("sksl_synthetic_uniforms");
-                std::unique_ptr<Type> intfStruct = Type::MakeStructType(/*offset=*/-1, name,
-                                                                        fields);
-                int binding = fProgram.fConfig->fSettings.fRTHeightBinding;
-                if (binding == -1) {
-                    fErrors.error(ref.fOffset, "layout(binding=...) is required in SPIR-V");
-                }
-                int set = fProgram.fConfig->fSettings.fRTHeightSet;
-                if (set == -1) {
-                    fErrors.error(ref.fOffset, "layout(set=...) is required in SPIR-V");
-                }
-                bool usePushConstants = fProgram.fConfig->fSettings.fUsePushConstants;
-                int flags = usePushConstants ? Layout::Flag::kPushConstant_Flag : 0;
-                Modifiers modifiers(
-                        Layout(flags, /*location=*/-1, /*offset=*/-1, binding, /*index=*/-1,
-                               set, /*builtin=*/-1, /*inputAttachmentIndex=*/-1,
-                               Layout::kUnspecified_Primitive,
-                               /*maxVertices=*/-1, /*invocations=*/-1, /*when=*/"",
-                               Layout::CType::kDefault),
-                        Modifiers::kUniform_Flag);
-                const Variable* intfVar = fSynthetics.takeOwnershipOfSymbol(
-                        std::make_unique<Variable>(/*offset=*/-1,
-                                                   fProgram.fModifiers->add(modifiers),
-                                                   name,
-                                                   intfStruct.get(),
-                                                   /*builtin=*/false,
-                                                   Variable::Storage::kGlobal));
-                InterfaceBlock intf(/*offset=*/-1,
-                                    intfVar,
-                                    name,
-                                    /*instanceName=*/"",
-                                    /*arraySize=*/0,
-                                    std::make_shared<SymbolTable>(&fErrors, /*builtin=*/false));
-
-                fRTHeightStructId = this->writeInterfaceBlock(intf, false);
-                fRTHeightFieldIndex = 0;
-                fRTHeightStorageClass = usePushConstants ? SpvStorageClassPushConstant
-                                                         : SpvStorageClassUniform;
+    if (variable->modifiers().fLayout.fBuiltin == SK_FRAGCOORD_BUILTIN) {
+        this->addRTFlipUniform(ref.fOffset);
+        // Use sk_RTAdjust to compute the flipped coordinate
+        using namespace dsl;
+        const char* DEVICE_COORDS_NAME = "__device_FragCoords";
+        SymbolTable& symbols = *dsl::DSLWriter::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(DSLWriter::IRGenerator().convertIdentifier(/*offset=*/-1,
+                                                                        SKSL_RTFLIP_NAME));
+        if (!symbols[DEVICE_COORDS_NAME]) {
+            Modifiers modifiers;
+            modifiers.fLayout.fBuiltin = DEVICE_FRAGCOORDS_BUILTIN;
+            if (fProgram.fPool) {
+                fProgram.fPool->attachToThread();
             }
-            SkASSERT(fRTHeightFieldIndex != (SpvId)-1);
-
-            IntLiteral fieldIndex(/*offset=*/-1, fRTHeightFieldIndex, fContext.fTypes.fInt.get());
-            SpvId fieldIndexId = this->writeIntLiteral(fieldIndex);
-            SpvId heightPtr = this->nextId(nullptr);
-            this->writeOpCode(SpvOpAccessChain, 5, out);
-            this->writeWord(this->getPointerType(*fContext.fTypes.fFloat, fRTHeightStorageClass),
-                            out);
-            this->writeWord(heightPtr, out);
-            this->writeWord(fRTHeightStructId, out);
-            this->writeWord(fieldIndexId, out);
-            SpvId heightRead = this->nextId(nullptr);
-            this->writeInstruction(SpvOpLoad, this->getType(*fContext.fTypes.fFloat), heightRead,
-                                   heightPtr, out);
-
-            flippedYId = this->nextId(nullptr);
-            this->writeInstruction(SpvOpFSub, this->getType(*fContext.fTypes.fFloat), flippedYId,
-                                   heightRead, rawYId, out);
+            symbols.add(std::make_unique<Variable>(/*offset=*/-1,
+                                                   fContext.fModifiersPool->add(modifiers),
+                                                   DEVICE_COORDS_NAME,
+                                                   fContext.fTypes.fFloat4.get(),
+                                                   true,
+                                                   Variable::Storage::kGlobal));
+            if (fProgram.fPool) {
+                fProgram.fPool->detachFromThread();
+            }
         }
-
-        // The z component will always be zero so we just get an id to the 0 literal
-        FloatLiteral zero(/*offset=*/-1, /*value=*/0.0, fContext.fTypes.fFloat.get());
-        SpvId zeroId = writeFloatLiteral(zero);
-
-        // Calculate the w component
-        SpvId rawWId = this->nextId(nullptr);
-        this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fTypes.fFloat),
-                               rawWId, result, 3, out);
-
-        // Fill in the new fragcoord with the components from above
-        SpvId adjusted = this->nextId(nullptr);
-        this->writeOpCode(SpvOpCompositeConstruct, 7, out);
-        this->writeWord(this->getType(*fContext.fTypes.fFloat4), out);
-        this->writeWord(adjusted, out);
-        this->writeWord(xId, out);
-        if (fProgram.fConfig->fSettings.fFlipY) {
-            this->writeWord(flippedYId, out);
-        } else {
-            this->writeWord(rawYId, out);
-        }
-        this->writeWord(zeroId, out);
-        this->writeWord(rawWId, out);
-
-        return adjusted;
+        DSLVar 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);
     }
 
-    // Handle the "flipY" setting when reading sk_Clockwise.
-    if (variable->modifiers().fLayout.fBuiltin == SK_CLOCKWISE_BUILTIN &&
-        !fProgram.fConfig->fSettings.fFlipY) {
-        // 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".
-        SpvId inverse = this->nextId(nullptr);
-        this->writeInstruction(SpvOpLogicalNot, this->getType(*fContext.fTypes.fBool), inverse,
-                               result, out);
-        return inverse;
+    // Handle flipping sk_Clockwise.
+    if (variable->modifiers().fLayout.fBuiltin == SK_CLOCKWISE_BUILTIN) {
+        this->addRTFlipUniform(ref.fOffset);
+        using namespace dsl;
+        const char* DEVICE_CLOCKWISE_NAME = "__device_Clockwise";
+        SymbolTable& symbols = *dsl::DSLWriter::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(DSLWriter::IRGenerator().convertIdentifier(/*offset=*/-1,
+                                                                        SKSL_RTFLIP_NAME));
+        if (!symbols[DEVICE_CLOCKWISE_NAME]) {
+            Modifiers modifiers;
+            modifiers.fLayout.fBuiltin = DEVICE_CLOCKWISE_BUILTIN;
+            if (fProgram.fPool) {
+                fProgram.fPool->attachToThread();
+            }
+            symbols.add(std::make_unique<Variable>(/*offset=*/-1,
+                                                   fContext.fModifiersPool->add(modifiers),
+                                                   DEVICE_CLOCKWISE_NAME,
+                                                   fContext.fTypes.fBool.get(),
+                                                   true,
+                                                   Variable::Storage::kGlobal));
+            if (fProgram.fPool) {
+                fProgram.fPool->detachFromThread();
+            }
+        }
+        DSLVar 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);
     }
 
-    return result;
+    return this->getLValue(ref, out)->load(out);
 }
 
 SpvId SPIRVCodeGenerator::writeIndexExpression(const IndexExpression& expr, OutputStream& out) {
@@ -3048,31 +3025,68 @@
     }
 }
 
-SpvId SPIRVCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTHeight) {
+SpvId SPIRVCodeGenerator::writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip) {
     MemoryLayout memoryLayout = this->memoryLayoutForVariable(intf.variable());
     SpvId result = this->nextId(nullptr);
-    std::unique_ptr<Type> rtHeightStructType;
-    const Type* type = &intf.variable().type();
-    if (!MemoryLayout::LayoutIsSupported(*type)) {
-        fErrors.error(type->fOffset, "type '" + type->name() + "' is not permitted here");
+    const Variable& intfVar = intf.variable();
+    const Type& type = intfVar.type();
+    if (!MemoryLayout::LayoutIsSupported(type)) {
+        fErrors.error(type.fOffset, "type '" + type.name() + "' is not permitted here");
         return this->nextId(nullptr);
     }
     SpvStorageClass_ storageClass = get_storage_class(intf.variable(), SpvStorageClassFunction);
-    if (fProgram.fInputs.fRTHeight && appendRTHeight) {
-        SkASSERT(fRTHeightStructId == (SpvId) -1);
-        SkASSERT(fRTHeightFieldIndex == (SpvId) -1);
-        std::vector<Type::Field> fields = type->fields();
-        fRTHeightStructId = result;
-        fRTHeightFieldIndex = fields.size();
-        fRTHeightStorageClass = storageClass;
-        fields.emplace_back(Modifiers(), skstd::string_view(SKSL_RTHEIGHT_NAME),
-                            fContext.fTypes.fFloat.get());
-        rtHeightStructType = Type::MakeStructType(type->fOffset, String(type->name()),
-                                                  std::move(fields));
-        type = rtHeightStructType.get();
+    if (fProgram.fInputs.fUseFlipRTUniform && appendRTFlip) {
+        // 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(Modifiers(Layout(/*flags=*/0,
+                                             /*location=*/-1,
+                                             fProgram.fConfig->fSettings.fRTFlipOffset,
+                                             /*binding=*/-1,
+                                             /*index=*/-1,
+                                             /*set=*/-1,
+                                             /*builtin=*/-1,
+                                             /*inputAttachmentIndex=*/-1,
+                                             Layout::kUnspecified_Primitive,
+                                             /*maxVertices=*/1,
+                                             /*invocations=*/-1,
+                                             /*when=*/"",
+                                             Layout::CType::kDefault),
+                                      /*flags=*/0),
+                            SKSL_RTFLIP_NAME,
+                            fContext.fTypes.fFloat2.get());
+        if (fProgram.fPool) {
+            fProgram.fPool->attachToThread();
+        }
+        const Type* rtFlipStructType = fProgram.fSymbols->takeOwnershipOfSymbol(
+                Type::MakeStructType(type.fOffset, String(type.name()), std::move(fields)));
+        const Variable* modifiedVar = fProgram.fSymbols->takeOwnershipOfSymbol(
+                std::make_unique<Variable>(intfVar.fOffset,
+                                           &intfVar.modifiers(),
+                                           intfVar.name(),
+                                           rtFlipStructType,
+                                           intfVar.isBuiltin(),
+                                           intfVar.storage()));
+        InterfaceBlock modifiedCopy(intf.fOffset,
+                                    modifiedVar,
+                                    intf.typeName(),
+                                    intf.instanceName(),
+                                    intf.arraySize(),
+                                    intf.typeOwner());
+        SpvId result = this->writeInterfaceBlock(modifiedCopy, false);
+        fProgram.fSymbols->add(std::make_unique<Field>(
+                /*offset=*/-1, modifiedVar, rtFlipStructType->fields().size() - 1));
+        if (fProgram.fPool) {
+            fProgram.fPool->detachFromThread();
+        }
+        fVariableMap[&intfVar] = result;
+        fWroteRTFlip = true;
+        return result;
     }
     SpvId typeId;
-    const Modifiers& intfModifiers = intf.variable().modifiers();
+    const Modifiers& intfModifiers = intfVar.modifiers();
     if (intfModifiers.fLayout.fBuiltin == SK_IN_BUILTIN) {
         for (const ProgramElement* e : fProgram.elements()) {
             if (e->is<ModifiersDeclaration>()) {
@@ -3080,11 +3094,10 @@
                 update_sk_in_count(m, &fSkInCount);
             }
         }
-        typeId = this->getType(
-                *Type::MakeArrayType("sk_in", intf.variable().type().componentType(), fSkInCount),
-                memoryLayout);
+        typeId = this->getType(*Type::MakeArrayType("sk_in", type.componentType(), fSkInCount),
+                               memoryLayout);
     } else {
-        typeId = this->getType(*type, memoryLayout);
+        typeId = this->getType(type, memoryLayout);
     }
     if (intfModifiers.fLayout.fBuiltin == -1) {
         this->writeInstruction(SpvOpDecorate, typeId, SpvDecorationBlock, fDecorationBuffer);
@@ -3097,7 +3110,7 @@
         layout.fSet = 0;
     }
     this->writeLayout(layout, result);
-    fVariableMap[&intf.variable()] = result;
+    fVariableMap[&intfVar] = result;
     return result;
 }
 
@@ -3556,6 +3569,91 @@
     fUniformBufferId = this->writeInterfaceBlock(*fUniformBuffer.fInterfaceBlock);
 }
 
+void SPIRVCodeGenerator::addRTFlipUniform(int offset) {
+    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) {
+        fErrors.error(offset, "RTFlipOffset is negative");
+    }
+    fields.emplace_back(Modifiers(Layout(/*flags=*/0,
+                                         /*location=*/-1,
+                                         fProgram.fConfig->fSettings.fRTFlipOffset,
+                                         /*binding=*/-1,
+                                         /*index=*/-1,
+                                         /*set=*/-1,
+                                         /*builtin=*/-1,
+                                         /*inputAttachmentIndex=*/-1,
+                                         Layout::kUnspecified_Primitive,
+                                         /*maxVertices=*/1,
+                                         /*invocations=*/-1,
+                                         /*when=*/"",
+                                         Layout::CType::kDefault),
+                                  /*flags=*/0),
+                        SKSL_RTFLIP_NAME,
+                        fContext.fTypes.fFloat2.get());
+    String name("sksl_synthetic_uniforms");
+    const Type* intfStruct =
+            fSynthetics.takeOwnershipOfSymbol(Type::MakeStructType(/*offset=*/-1, name, fields));
+    int binding = fProgram.fConfig->fSettings.fRTFlipBinding;
+    if (binding == -1) {
+        fErrors.error(offset, "layout(binding=...) is required in SPIR-V");
+    }
+    int set = fProgram.fConfig->fSettings.fRTFlipSet;
+    if (set == -1) {
+        fErrors.error(offset, "layout(set=...) is required in SPIR-V");
+    }
+    bool usePushConstants = fProgram.fConfig->fSettings.fUsePushConstants;
+    int flags = usePushConstants ? Layout::Flag::kPushConstant_Flag : 0;
+    if (fProgram.fPool) {
+        fProgram.fPool->attachToThread();
+    }
+    Modifiers modifiers(Layout(flags,
+                               /*location=*/-1,
+                               /*offset=*/-1,
+                               binding,
+                               /*index=*/-1,
+                               set,
+                               /*builtin=*/-1,
+                               /*inputAttachmentIndex=*/-1,
+                               Layout::kUnspecified_Primitive,
+                               /*maxVertices=*/-1,
+                               /*invocations=*/-1,
+                               /*when=*/"",
+                               Layout::CType::kDefault),
+                        Modifiers::kUniform_Flag);
+    const Modifiers* modsPtr = fProgram.fModifiers->add(modifiers);
+    if (fProgram.fPool) {
+        fProgram.fPool->detachFromThread();
+    }
+    const Variable* intfVar = fSynthetics.takeOwnershipOfSymbol(
+            std::make_unique<Variable>(/*offset=*/-1,
+                                       modsPtr,
+                                       name,
+                                       intfStruct,
+                                       /*builtin=*/false,
+                                       Variable::Storage::kGlobal));
+    if (fProgram.fPool) {
+        fProgram.fPool->attachToThread();
+    }
+    fProgram.fSymbols->add(std::make_unique<Field>(/*offset=*/-1, intfVar, /*field=*/0));
+    if (fProgram.fPool) {
+        fProgram.fPool->detachFromThread();
+    }
+    InterfaceBlock intf(/*offset=*/-1,
+                        intfVar,
+                        name,
+                        /*instanceName=*/"",
+                        /*arraySize=*/0,
+                        std::make_shared<SymbolTable>(&fErrors, /*builtin=*/false));
+
+    this->writeInterfaceBlock(intf, false);
+}
+
 void SPIRVCodeGenerator::writeInstructions(const Program& program, OutputStream& out) {
     fGLSLExtendedInstructions = this->nextId(nullptr);
     StringStream body;