sksl: Add a "sk_Clockwise" built-in

This allows us to identify clockwise-winding triangles, in terms of
Skia device space, in all backends and with all render target origins.

Bug: skia:
Change-Id: I220e1c459e0129d1cc4dee6458ef94277fbedd21
Reviewed-on: https://skia-review.googlesource.com/142662
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
diff --git a/gm/clockwise.cpp b/gm/clockwise.cpp
new file mode 100644
index 0000000..3c01a99
--- /dev/null
+++ b/gm/clockwise.cpp
@@ -0,0 +1,191 @@
+/*
+ * 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 "gm.h"
+
+#include "GrClip.h"
+#include "GrContext.h"
+#include "GrGpuCommandBuffer.h"
+#include "GrMemoryPool.h"
+#include "GrOpFlushState.h"
+#include "GrRenderTargetContext.h"
+#include "GrRenderTargetContextPriv.h"
+#include "GrRenderTarget.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "glsl/GrGLSLGeometryProcessor.h"
+#include "glsl/GrGLSLVarying.h"
+#include "glsl/GrGLSLVertexGeoBuilder.h"
+
+namespace skiagm {
+
+static constexpr GrGeometryProcessor::Attribute gVertex{"vertex", kFloat2_GrVertexAttribType};
+
+/**
+ * This is a GPU-backend specific test. It ensures that SkSL properly identifies clockwise-winding
+ * triangles (sk_Clockwise), in terms of to Skia device space, in all backends and with all render
+ * target origins. We draw clockwise triangles green and counter-clockwise red.
+ */
+class ClockwiseGM : public GM {
+private:
+    SkString onShortName() final { return SkString("clockwise"); }
+    SkISize onISize() override { return SkISize::Make(300, 200); }
+    void onDraw(SkCanvas*) override;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkSL code.
+
+class ClockwiseTestProcessor : public GrGeometryProcessor {
+public:
+    ClockwiseTestProcessor(bool readSkFragCoord)
+            : GrGeometryProcessor(kClockwiseTestProcessor_ClassID)
+            , fReadSkFragCoord(readSkFragCoord) {
+        this->setVertexAttributeCnt(1);
+    }
+    const char* name() const override { return "ClockwiseTestProcessor"; }
+    void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
+        b->add32(fReadSkFragCoord);
+    }
+    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
+
+private:
+    const Attribute& onVertexAttribute(int i) const override { return gVertex; }
+
+    const bool fReadSkFragCoord;
+
+    friend class GLSLClockwiseTestProcessor;
+};
+
+class GLSLClockwiseTestProcessor : public GrGLSLGeometryProcessor {
+    void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&,
+                 FPCoordTransformIter&& transformIter) override {}
+
+    void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
+        const ClockwiseTestProcessor& proc = args.fGP.cast<ClockwiseTestProcessor>();
+        args.fVaryingHandler->emitAttributes(proc);
+        gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
+        args.fFragBuilder->codeAppendf(
+                "%s = sk_Clockwise ? half4(0,1,0,1) : half4(1,0,0,1);", args.fOutputColor);
+        if (!proc.fReadSkFragCoord) {
+            args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
+        } else {
+            // Verify layout(origin_upper_left) on gl_FragCoord does not affect gl_FrontFacing.
+            args.fFragBuilder->codeAppendf("%s = half4(min(sk_FragCoord.y, 1));",
+                                           args.fOutputCoverage);
+        }
+    }
+};
+
+GrGLSLPrimitiveProcessor* ClockwiseTestProcessor::createGLSLInstance(
+        const GrShaderCaps&) const {
+    return new GLSLClockwiseTestProcessor;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Draw Op.
+
+class ClockwiseTestOp : public GrDrawOp {
+public:
+    DEFINE_OP_CLASS_ID
+
+    static std::unique_ptr<GrDrawOp> Make(GrContext* context, bool readSkFragCoord, int y = 0) {
+        GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
+        return pool->allocate<ClockwiseTestOp>(readSkFragCoord, y);
+    }
+
+private:
+    ClockwiseTestOp(bool readSkFragCoord, float y)
+            : GrDrawOp(ClassID()), fReadSkFragCoord(readSkFragCoord), fY(y) {
+        this->setBounds(SkRect::MakeIWH(300, 100), HasAABloat::kNo, IsZeroArea::kNo);
+    }
+
+    const char* name() const override { return "ClockwiseTestOp"; }
+    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
+    RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override {
+        return RequiresDstTexture::kNo;
+    }
+    bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
+    void onPrepare(GrOpFlushState*) override {}
+    void onExecute(GrOpFlushState* flushState) override {
+        SkPoint vertices[4] = {
+            {100, fY},
+            {0, fY+100},
+            {0, fY},
+            {100, fY+100},
+        };
+        sk_sp<GrBuffer> vertexBuffer(flushState->resourceProvider()->createBuffer(
+                sizeof(vertices), kVertex_GrBufferType, kStatic_GrAccessPattern,
+                GrResourceProvider::kNone_Flag, vertices));
+        GrPipeline pipeline(flushState->drawOpArgs().fProxy, GrPipeline::ScissorState::kDisabled,
+                            SkBlendMode::kPlus);
+        GrMesh mesh(GrPrimitiveType::kTriangleStrip);
+        mesh.setNonIndexedNonInstanced(4);
+        mesh.setVertexData(vertexBuffer.get());
+        flushState->rtCommandBuffer()->draw(ClockwiseTestProcessor(fReadSkFragCoord), pipeline,
+                                            nullptr, nullptr, &mesh, 1, SkRect::MakeIWH(100, 100));
+    }
+
+    const bool fReadSkFragCoord;
+    const float fY;
+
+    friend class ::GrOpMemoryPool; // for ctor
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Test.
+
+void ClockwiseGM::onDraw(SkCanvas* canvas) {
+    GrContext* ctx = canvas->getGrContext();
+    GrRenderTargetContext* rtc = canvas->internal_private_accessTopLayerRenderTargetContext();
+    if (!ctx || !rtc) {
+        DrawGpuOnlyMessage(canvas);
+        return;
+    }
+
+    rtc->clear(nullptr, GrColorPackRGBA(0,0,0,255),
+               GrRenderTargetContext::CanClearFullscreen::kYes);
+
+    // Draw the test directly to the frame buffer.
+    rtc->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, false, 0));
+    rtc->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, true, 100));
+
+    // Draw the test to an off-screen, top-down render target.
+    if (auto topLeftRTC = ctx->contextPriv().makeDeferredRenderTargetContext(
+                    SkBackingFit::kExact, 100, 200, rtc->accessRenderTarget()->config(),
+                    nullptr, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin, nullptr,
+                    SkBudgeted::kYes)) {
+        topLeftRTC->clear(nullptr, 0, GrRenderTargetContext::CanClearFullscreen::kYes);
+        topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, false, 0));
+        topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, true, 100));
+        rtc->drawTexture(GrNoClip(), sk_ref_sp(topLeftRTC->asTextureProxy()),
+                         GrSamplerState::Filter::kNearest, 0xffffffff, {0,0,100,200},
+                         {100,0,200,200}, GrAA::kNo,
+                         SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, SkMatrix::I(),
+                         nullptr);
+    }
+
+    // Draw the test to an off-screen, bottom-up render target.
+    if (auto topLeftRTC = ctx->contextPriv().makeDeferredRenderTargetContext(
+                    SkBackingFit::kExact, 100, 200, rtc->accessRenderTarget()->config(),
+                    nullptr, 1, GrMipMapped::kNo, kBottomLeft_GrSurfaceOrigin, nullptr,
+                    SkBudgeted::kYes)) {
+        topLeftRTC->clear(nullptr, 0, GrRenderTargetContext::CanClearFullscreen::kYes);
+        topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, false, 0));
+        topLeftRTC->priv().testingOnly_addDrawOp(ClockwiseTestOp::Make(ctx, true, 100));
+        rtc->drawTexture(GrNoClip(), sk_ref_sp(topLeftRTC->asTextureProxy()),
+                         GrSamplerState::Filter::kNearest, 0xffffffff, {0,0,100,200},
+                         {200,0,300,200}, GrAA::kNo,
+                         SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, SkMatrix::I(),
+                         nullptr);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+DEF_GM( return new ClockwiseGM(); )
+
+}
diff --git a/gn/gm.gni b/gn/gm.gni
index 587f83b..e146b1f 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -70,6 +70,7 @@
   "$_gm/clip_strokerect.cpp",
   "$_gm/clipdrawdraw.cpp",
   "$_gm/clippedbitmapshaders.cpp",
+  "$_gm/clockwise.cpp",
   "$_gm/color4f.cpp",
   "$_gm/coloremoji.cpp",
   "$_gm/coloremoji_blendmodes.cpp",
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index 4afc928..5d83ce3 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -71,6 +71,7 @@
         kButtCapStrokedCircleGeometryProcessor_ClassID,
         kCircleGeometryProcessor_ClassID,
         kCircularRRectEffect_ClassID,
+        kClockwiseTestProcessor_ClassID,
         kColorMatrixEffect_ClassID,
         kColorTableEffect_ClassID,
         kComposeOneFragmentProcessor_ClassID,
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 18ff4c6..e04a40d 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -359,9 +359,9 @@
 
         // We don't use face culling.
         GL_CALL(Disable(GR_GL_CULL_FACE));
-        // We do use separate stencil. Our algorithms don't care which face is front vs. back so
-        // just set this to the default for self-consistency.
-        GL_CALL(FrontFace(GR_GL_CCW));
+
+        // Setting the front face keeps gl_FrontFacing consistent in device space.
+        fHWFrontFace = GR_GL_NONE;
 
         fHWBufferState[kTexel_GrBufferType].invalidate();
         fHWBufferState[kDrawIndirect_GrBufferType].invalidate();
@@ -1704,6 +1704,7 @@
     fHWProgram->setData(primProc, pipeline);
 
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(pipeline.renderTarget());
+    GrSurfaceOrigin origin = pipeline.proxy()->origin();
     GrStencilSettings stencil;
     if (pipeline.isStencilEnabled()) {
         // TODO: attach stencil and create settings during render target flush.
@@ -1715,16 +1716,16 @@
     if (pipeline.isScissorEnabled()) {
         static constexpr SkIRect kBogusScissor{0, 0, 1, 1};
         GrScissorState state(fixedDynamicState ? fixedDynamicState->fScissorRect : kBogusScissor);
-        this->flushScissor(state, glRT->getViewport(), pipeline.proxy()->origin());
+        this->flushScissor(state, glRT->getViewport(), origin);
     } else {
         this->disableScissor();
     }
-    this->flushWindowRectangles(pipeline.getWindowRectsState(), glRT, pipeline.proxy()->origin());
+    this->flushWindowRectangles(pipeline.getWindowRectsState(), glRT, origin);
     this->flushHWAAState(glRT, pipeline.isHWAntialiasState(), !stencil.isDisabled());
 
     // This must come after textures are flushed because a texture may need
     // to be msaa-resolved (which will modify bound FBO state).
-    this->flushRenderTarget(glRT);
+    this->flushRenderTarget(glRT, origin);
 
     return true;
 }
@@ -1856,9 +1857,9 @@
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(target);
 
     if (clip.scissorEnabled()) {
-        this->flushRenderTarget(glRT, origin, clip.scissorRect());
+        this->flushRenderTarget(glRT, origin, &clip.scissorRect());
     } else {
-        this->flushRenderTarget(glRT);
+        this->flushRenderTarget(glRT, origin);
     }
     this->flushScissor(clip.scissorState(), glRT->getViewport(), origin);
     this->flushWindowRectangles(clip.windowRectsState(), glRT, origin);
@@ -2121,14 +2122,17 @@
 }
 
 void GrGLGpu::flushRenderTarget(GrGLRenderTarget* target, GrSurfaceOrigin origin,
-                                const SkIRect& bounds) {
+                                const SkIRect* bounds) {
     this->flushRenderTargetNoColorWrites(target);
-    this->didWriteToSurface(target, origin, &bounds);
-}
 
-void GrGLGpu::flushRenderTarget(GrGLRenderTarget* target) {
-    this->flushRenderTargetNoColorWrites(target);
-    this->didWriteToSurface(target, kTopLeft_GrSurfaceOrigin, nullptr);
+    // A triangle is front-facing if it winds clockwise in device space.
+    GrGLenum frontFace = (kBottomLeft_GrSurfaceOrigin == origin) ? GR_GL_CW : GR_GL_CCW;
+    if (frontFace != fHWFrontFace) {
+        GL_CALL(FrontFace(frontFace));
+        fHWFrontFace = frontFace;
+    }
+
+    this->didWriteToSurface(target, origin, bounds);
 }
 
 void GrGLGpu::flushRenderTargetNoColorWrites(GrGLRenderTarget* target) {
@@ -3360,7 +3364,7 @@
     }
 
     GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(rt->asRenderTarget());
-    this->flushRenderTarget(glRT);
+    this->flushRenderTarget(glRT, origin);
 
     this->flushProgram(fStencilClipClearProgram);
 
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 78ae024..3e6dbaa 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -341,9 +341,7 @@
 
     // The passed bounds contains the render target's color values that will subsequently be
     // written.
-    void flushRenderTarget(GrGLRenderTarget*, GrSurfaceOrigin, const SkIRect& bounds);
-    // This version has an implicit bounds of the entire render target.
-    void flushRenderTarget(GrGLRenderTarget*);
+    void flushRenderTarget(GrGLRenderTarget*, GrSurfaceOrigin, const SkIRect* bounds = nullptr);
     // This version can be used when the render target's colors will not be written.
     void flushRenderTargetNoColorWrites(GrGLRenderTarget*);
 
@@ -560,6 +558,7 @@
     GrStencilSettings                       fHWStencilSettings;
     TriState                                fHWStencilTestEnabled;
 
+    GrGLenum                                fHWFrontFace;
 
     TriState                                fHWWriteToColor;
     GrGpuResource::UniqueID                 fHWBoundRenderTargetUniqueID;
diff --git a/src/gpu/gl/GrGLPathRendering.cpp b/src/gpu/gl/GrGLPathRendering.cpp
index 85c129f..8a53f88 100644
--- a/src/gpu/gl/GrGLPathRendering.cpp
+++ b/src/gpu/gl/GrGLPathRendering.cpp
@@ -88,11 +88,12 @@
     gpu->flushColorWrite(false);
 
     GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(args.fProxy->priv().peekRenderTarget());
+    GrSurfaceOrigin origin = args.fProxy->origin();
     SkISize size = SkISize::Make(rt->width(), rt->height());
-    this->setProjectionMatrix(*args.fViewMatrix, size, args.fProxy->origin());
-    gpu->flushScissor(*args.fScissor, rt->getViewport(), args.fProxy->origin());
+    this->setProjectionMatrix(*args.fViewMatrix, size, origin);
+    gpu->flushScissor(*args.fScissor, rt->getViewport(), origin);
     gpu->flushHWAAState(rt, args.fUseHWAA, true);
-    gpu->flushRenderTarget(rt);
+    gpu->flushRenderTarget(rt, origin);
 
     const GrGLPath* glPath = static_cast<const GrGLPath*>(path);
 
diff --git a/src/gpu/vk/GrVkPipeline.cpp b/src/gpu/vk/GrVkPipeline.cpp
index 60e7f43..6cdfc4e 100644
--- a/src/gpu/vk/GrVkPipeline.cpp
+++ b/src/gpu/vk/GrVkPipeline.cpp
@@ -436,7 +436,9 @@
     rasterInfo->polygonMode = caps->wireframeMode() ? VK_POLYGON_MODE_LINE
                                                     : VK_POLYGON_MODE_FILL;
     rasterInfo->cullMode = VK_CULL_MODE_NONE;
-    rasterInfo->frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+    // A triangle is front-facing if it winds clockwise in device space.
+    rasterInfo->frontFace = (kTopLeft_GrSurfaceOrigin == pipeline.proxy()->origin())
+        ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE;
     rasterInfo->depthBiasEnable = VK_FALSE;
     rasterInfo->depthBiasConstantFactor = 0.0f;
     rasterInfo->depthBiasClamp = 0.0f;
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index 91685c6..bcccbc0 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -204,19 +204,23 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-uint32_t get_blend_info_key(const GrPipeline& pipeline) {
-    GrXferProcessor::BlendInfo blendInfo;
-    pipeline.getXferProcessor().getBlendInfo(&blendInfo);
-
-    static const uint32_t kBlendWriteShift = 1;
+uint32_t get_pipeline_info_key(const GrPipeline& pipeline) {
     static const uint32_t kBlendCoeffShift = 5;
     GR_STATIC_ASSERT(kLast_GrBlendCoeff < (1 << kBlendCoeffShift));
     GR_STATIC_ASSERT(kFirstAdvancedGrBlendEquation - 1 < 4);
 
-    uint32_t key = blendInfo.fWriteColor;
-    key |= (blendInfo.fSrcBlend << kBlendWriteShift);
-    key |= (blendInfo.fDstBlend << (kBlendWriteShift + kBlendCoeffShift));
-    key |= (blendInfo.fEquation << (kBlendWriteShift + 2 * kBlendCoeffShift));
+    GrXferProcessor::BlendInfo blendInfo;
+    pipeline.getXferProcessor().getBlendInfo(&blendInfo);
+
+    GrSurfaceOrigin origin = pipeline.proxy()->origin();
+    SkASSERT(0 == origin || 1 == origin);
+
+    uint32_t key;
+    key = blendInfo.fEquation;
+    key = blendInfo.fDstBlend | (key << kBlendCoeffShift);
+    key = blendInfo.fSrcBlend | (key << kBlendCoeffShift);
+    key = (int)blendInfo.fWriteColor | (key << 1);
+    key = origin | (key << 1);
 
     return key;
 }
@@ -238,7 +242,7 @@
 
     stencil.genKey(&b);
 
-    b.add32(get_blend_info_key(pipeline));
+    b.add32(get_pipeline_info_key(pipeline));
 
     b.add32((uint32_t)primitiveType);
 
diff --git a/src/sksl/README b/src/sksl/README
index bb2dd05..dd0af98 100644
--- a/src/sksl/README
+++ b/src/sksl/README
@@ -45,6 +45,8 @@
 * 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
@@ -149,4 +151,4 @@
 7. At this point you can reference the new fragment processor from within Skia.
 
 Once you have done this initial setup, simply re-build Skia to pick up any
-changes to the .fp file.
\ No newline at end of file
+changes to the .fp file.
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
index b55bf40..c840bd8 100644
--- a/src/sksl/SkSLCompiler.h
+++ b/src/sksl/SkSLCompiler.h
@@ -27,6 +27,7 @@
 #define SK_OUT_BUILTIN                 10007
 #define SK_LASTFRAGCOLOR_BUILTIN       10008
 #define SK_FRAGCOORD_BUILTIN              15
+#define SK_CLOCKWISE_BUILTIN              17
 #define SK_VERTEXID_BUILTIN               42
 #define SK_INSTANCEID_BUILTIN             43
 #define SK_CLIPDISTANCE_BUILTIN            3
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index 3c697d8..d08c913 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -689,6 +689,9 @@
         case SK_FRAGCOORD_BUILTIN:
             this->writeFragCoord();
             break;
+        case SK_CLOCKWISE_BUILTIN:
+            this->write("gl_FrontFacing");
+            break;
         case SK_VERTEXID_BUILTIN:
             this->write("gl_VertexID");
             break;
diff --git a/src/sksl/sksl_frag.inc b/src/sksl/sksl_frag.inc
index 429d05a..202747a 100644
--- a/src/sksl/sksl_frag.inc
+++ b/src/sksl/sksl_frag.inc
@@ -2,7 +2,9 @@
 
 // defines built-in interfaces supported by SkiaSL 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(builtin=3) float sk_ClipDistance[1];
 
 // 9999 is a temporary value that causes us to ignore these declarations beyond
diff --git a/tests/SkSLGLSLTest.cpp b/tests/SkSLGLSLTest.cpp
index 275fb2b..5b083fa 100644
--- a/tests/SkSLGLSLTest.cpp
+++ b/tests/SkSLGLSLTest.cpp
@@ -1154,6 +1154,17 @@
          "}\n");
 }
 
+DEF_TEST(SkSLClockwise, r) {
+    test(r,
+         "void main() { sk_FragColor = half4(sk_Clockwise ? +1 : -1); }",
+         *SkSL::ShaderCapsFactory::Default(),
+         "#version 400\n"
+         "out vec4 sk_FragColor;\n"
+         "void main() {\n"
+         "    sk_FragColor = vec4(float(gl_FrontFacing ? 1 : -1));\n"
+         "}\n");
+}
+
 DEF_TEST(SkSLVertexID, r) {
     test(r,
          "out int id; void main() { id = sk_VertexID; }",