Add triangle support to GrPathOuterCurveTessellator

Supports drawing exact triangles as conics with infinite weight. This
is necessary for drawing inner fans, breadcrumb triangles, and extra
triangles that will be introduced when we start splitting curves that
don't fit in a single patch.

Bug: skia:10419
Change-Id: If72560be4eb38340512be55dccea93b3a083fad5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/410836
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/bench/TessellateBench.cpp b/bench/TessellateBench.cpp
index fbbbf63..2206e7e 100644
--- a/bench/TessellateBench.cpp
+++ b/bench/TessellateBench.cpp
@@ -116,7 +116,7 @@
 }
 
 DEF_PATH_TESS_BENCH(GrPathOuterCurveTessellator, make_cubic_path(), SkMatrix::I()) {
-    GrPathOuterCurveTessellator tess;
+    GrPathOuterCurveTessellator tess(GrPathTessellator::DrawInnerFan::kNo);
     tess.prepare(fTarget.get(), fMatrix, fPath, nullptr);
 }
 
diff --git a/samplecode/SamplePathTessellators.cpp b/samplecode/SamplePathTessellators.cpp
index 8a499a4..d3cf120 100644
--- a/samplecode/SamplePathTessellators.cpp
+++ b/samplecode/SamplePathTessellators.cpp
@@ -71,7 +71,7 @@
         switch (fMode) {
             case Mode::kCurveMiddleOut:
                 fTessellator = alloc->make<GrPathIndirectTessellator>(
-                        fMatrix, fPath, GrPathIndirectTessellator::DrawInnerFan::kYes);
+                        fMatrix, fPath, GrPathTessellator::DrawInnerFan::kYes);
                 shader = alloc->make<GrCurveMiddleOutShader>(fMatrix);
                 break;
             case Mode::kWedgeTessellate:
@@ -79,7 +79,8 @@
                 shader = alloc->make<GrWedgeTessellateShader>(fMatrix);
                 break;
             case Mode::kCurveTessellate:
-                fTessellator = alloc->make<GrPathOuterCurveTessellator>();
+                fTessellator = alloc->make<GrPathOuterCurveTessellator>(
+                        GrPathTessellator::DrawInnerFan::kYes);
                 shader = alloc->make<GrCurveTessellateShader>(fMatrix);
                 break;
         }
diff --git a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
index c3924d8..5716593 100644
--- a/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
+++ b/src/gpu/tessellate/GrPathInnerTriangulateOp.cpp
@@ -99,9 +99,8 @@
         // and the middle-out topology used by indirect draws is easier on the rasterizer than what
         // we can do with hw tessellation. So far we haven't found any platforms where trying to use
         // hw tessellation here is worth it.
-        using DrawInnerFan = GrPathIndirectTessellator::DrawInnerFan;
-        fTessellator = args.fArena->make<GrPathIndirectTessellator>(fViewMatrix, fPath,
-                                                                    DrawInnerFan::kNo);
+        fTessellator = args.fArena->make<GrPathIndirectTessellator>(
+                fViewMatrix, fPath, GrPathTessellator::DrawInnerFan::kNo);
         fStencilCurvesProgram = GrStencilPathShader::MakeStencilProgram<GrCurveMiddleOutShader>(
                 args, fViewMatrix, pipelineForStencils, fPath.getFillType());
     }
diff --git a/src/gpu/tessellate/GrPathStencilFillOp.cpp b/src/gpu/tessellate/GrPathStencilFillOp.cpp
index 4544494..f38ee3b 100644
--- a/src/gpu/tessellate/GrPathStencilFillOp.cpp
+++ b/src/gpu/tessellate/GrPathStencilFillOp.cpp
@@ -43,7 +43,6 @@
 
 void GrPathStencilFillOp::prePreparePrograms(const GrPathShader::ProgramArgs& args,
                                              GrAppliedClip&& appliedClip) {
-    using DrawInnerFan = GrPathIndirectTessellator::DrawInnerFan;
     SkASSERT(!fStencilFanProgram);
     SkASSERT(!fStencilPathProgram);
     SkASSERT(!fFillBBoxProgram);
@@ -61,7 +60,8 @@
             args, fAAType, fOpFlags, appliedClip.hardClip());
     if (drawTrianglesAsIndirectCurveDraw || (fOpFlags & OpFlags::kDisableHWTessellation)) {
         fTessellator = args.fArena->make<GrPathIndirectTessellator>(
-                fViewMatrix, fPath, DrawInnerFan(drawTrianglesAsIndirectCurveDraw));
+                fViewMatrix, fPath,
+                GrPathTessellator::DrawInnerFan(drawTrianglesAsIndirectCurveDraw));
         if (!drawTrianglesAsIndirectCurveDraw) {
             fStencilFanProgram = GrStencilPathShader::MakeStencilProgram<GrStencilTriangleShader>(
                     args, fViewMatrix, stencilPassPipeline, fPath.getFillType());
@@ -82,7 +82,8 @@
         const SkRect& bounds = fPath.getBounds();
         float rasterEdgeWork = (bounds.height() + bounds.width()) * scales[1] * fPath.countVerbs();
         if (rasterEdgeWork > 300 * 300) {
-            fTessellator = args.fArena->make<GrPathOuterCurveTessellator>();
+            fTessellator = args.fArena->make<GrPathOuterCurveTessellator>(
+                    GrPathTessellator::DrawInnerFan::kNo);
             fStencilFanProgram = GrStencilPathShader::MakeStencilProgram<GrStencilTriangleShader>(
                     args, fViewMatrix, stencilPassPipeline, fPath.getFillType());
             fStencilPathProgram = GrStencilPathShader::MakeStencilProgram<GrCurveTessellateShader>(
diff --git a/src/gpu/tessellate/GrPathTessellator.cpp b/src/gpu/tessellate/GrPathTessellator.cpp
index d511f62..35b4d81 100644
--- a/src/gpu/tessellate/GrPathTessellator.cpp
+++ b/src/gpu/tessellate/GrPathTessellator.cpp
@@ -45,6 +45,45 @@
     }
 }
 
+// Returns an upper bound on the number of segments (lineTo, quadTo, conicTo, cubicTo) in a path,
+// also accounting for any implicit lineTos from closing contours.
+static int max_segments_in_path(const SkPath& path) {
+    // There might be an implicit kClose at the end, but the path always begins with kMove. So the
+    // max number of segments in the path is equal to the number of verbs.
+    SkASSERT(path.countVerbs() == 0 || SkPathPriv::VerbData(path)[0] == SkPath::kMove_Verb);
+    return path.countVerbs();
+}
+
+// Returns an upper bound on the number of triangles it would require to fan a path's inner polygon,
+// in the case where no additional vertices are introduced.
+static int max_triangles_in_inner_fan(const SkPath& path) {
+    int maxEdgesInFan = max_segments_in_path(path);
+    return maxEdgesInFan - 2;  // An n-sided polygon is fanned by n-2 triangles.
+}
+
+static int write_breadcrumb_triangles(
+        GrVertexWriter* writer,
+        const GrInnerFanTriangulator::BreadcrumbTriangleList* breadcrumbTriangleList) {
+    int numWritten = 0;
+    SkDEBUGCODE(int count = 0;)
+    for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
+        SkDEBUGCODE(++count;)
+        const SkPoint* p = tri->fPts;
+        if ((p[0].fX == p[1].fX && p[1].fX == p[2].fX) ||
+            (p[0].fY == p[1].fY && p[1].fY == p[2].fY)) {
+            // Completely degenerate triangles have undefined winding. And T-junctions shouldn't
+            // happen on axis-aligned edges.
+            continue;
+        }
+        writer->writeArray(p, 3);
+        // Mark this instance as a triangle by setting it to a conic with w=Inf.
+        writer->fill(GrVertexWriter::kIEEE_32_infinity, 2);
+        ++numWritten;
+    }
+    SkASSERT(count == breadcrumbTriangleList->count());
+    return numWritten;
+}
+
 void GrPathIndirectTessellator::prepare(GrMeshDrawOp::Target* target, const SkMatrix& viewMatrix,
                                         const SkPath& path,
                                         const BreadcrumbTriangleList* breadcrumbTriangleList) {
@@ -54,9 +93,7 @@
 
     int instanceLockCount = fOuterCurveInstanceCount;
     if (fDrawInnerFan) {
-        // No initial moveTo, plus an implicit close at the end; n-2 triangles fill an n-gon.
-        int maxInnerTriangles = path.countVerbs() - 1;
-        instanceLockCount += maxInnerTriangles;
+        instanceLockCount += max_triangles_in_inner_fan(path);
     }
     if (breadcrumbTriangleList) {
         instanceLockCount += breadcrumbTriangleList->count();
@@ -82,22 +119,8 @@
                 GrMiddleOutPolygonTriangulator::OutputType::kConicsWithInfiniteWeight, path);
     }
     if (breadcrumbTriangleList) {
-        SkDEBUGCODE(int count = 0;)
-        for (const auto* tri = breadcrumbTriangleList->head(); tri; tri = tri->fNext) {
-            SkDEBUGCODE(++count;)
-            const SkPoint* p = tri->fPts;
-            if ((p[0].fX == p[1].fX && p[1].fX == p[2].fX) ||
-                (p[0].fY == p[1].fY && p[1].fY == p[2].fY)) {
-                // Completely degenerate triangles have undefined winding. And T-junctions shouldn't
-                // happen on axis-aligned edges.
-                continue;
-            }
-            instanceWriter.writeArray(p, 3);
-            // Mark this instance as a triangle by setting it to a conic with w=Inf.
-            instanceWriter.fill(GrVertexWriter::kIEEE_32_infinity, 2);
-            ++numTrianglesAtBeginningOfData;
-        }
-        SkASSERT(count == breadcrumbTriangleList->count());
+        numTrianglesAtBeginningOfData += write_breadcrumb_triangles(&instanceWriter,
+                                                                    breadcrumbTriangleList);
     }
 
     // Allocate space for the GrDrawIndexedIndirectCommand structs. Allocate enough for each
@@ -226,36 +249,60 @@
                                           const SkPath& path,
                                           const BreadcrumbTriangleList* breadcrumbTriangleList) {
     SkASSERT(target->caps().shaderCaps()->tessellationSupport());
-    SkASSERT(!breadcrumbTriangleList);
     SkASSERT(!fPatchBuffer);
     SkASSERT(fPatchVertexCount == 0);
 
     int vertexLockCount = path.countVerbs() * 4;
+    if (fDrawInnerFan) {
+        vertexLockCount += max_triangles_in_inner_fan(path) * 4;
+    }
+    if (breadcrumbTriangleList) {
+        vertexLockCount += breadcrumbTriangleList->count() * 4;
+    }
     GrEagerDynamicVertexAllocator vertexAlloc(target, &fPatchBuffer, &fBasePatchVertex);
-    auto* vertexData = vertexAlloc.lock<SkPoint>(vertexLockCount);
-    if (!vertexData) {
+    GrVertexWriter vertexWriter = vertexAlloc.lock<SkPoint>(vertexLockCount);
+    if (!vertexWriter) {
         return;
     }
 
+    GrMiddleOutPolygonTriangulator middleOut(
+            &vertexWriter, GrMiddleOutPolygonTriangulator::OutputType::kConicsWithInfiniteWeight,
+            path.countVerbs());
     for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) {
         switch (verb) {
-            default:
+            case SkPathVerb::kMove:
+                if (fDrawInnerFan) {
+                    middleOut.closeAndMove(pts[0]);
+                }
                 continue;
+            case SkPathVerb::kClose:
+                continue;
+            case SkPathVerb::kLine:
+                break;
             case SkPathVerb::kQuad:
-                SkASSERT(fPatchVertexCount + 4 <= vertexLockCount);
-                GrPathUtils::convertQuadToCubic(pts, vertexData + fPatchVertexCount);
+                GrPathUtils::writeQuadAsCubic(pts, &vertexWriter);
+                fPatchVertexCount += 4;
                 break;
             case SkPathVerb::kCubic:
-                SkASSERT(fPatchVertexCount + 4 <= vertexLockCount);
-                memcpy(vertexData + fPatchVertexCount, pts, sizeof(SkPoint) * 4);
+                vertexWriter.writeArray(pts, 4);
+                fPatchVertexCount += 4;
                 break;
             case SkPathVerb::kConic:
-                SkASSERT(fPatchVertexCount + 4 <= vertexLockCount);
-                GrPathShader::WriteConicPatch(pts, *w, vertexData + fPatchVertexCount);
+                GrPathShader::WriteConicPatch(pts, *w, &vertexWriter);
+                fPatchVertexCount += 4;
                 break;
         }
-        fPatchVertexCount += 4;
+        if (fDrawInnerFan) {
+            middleOut.pushVertex(pts[SkPathPriv::PtsInIter((unsigned)verb) - 1]);
+        }
     }
+    if (fDrawInnerFan) {
+        fPatchVertexCount += middleOut.close() * 4;
+    }
+    if (breadcrumbTriangleList) {
+        fPatchVertexCount += write_breadcrumb_triangles(&vertexWriter, breadcrumbTriangleList) * 4;
+    }
+    SkASSERT(fPatchVertexCount <= vertexLockCount);
 
     vertexAlloc.unlock(fPatchVertexCount);
 }
@@ -268,9 +315,8 @@
     SkASSERT(!fPatchBuffer);
     SkASSERT(fPatchVertexCount == 0);
 
-    // No initial moveTo, one wedge per verb, plus an implicit close at the end.
-    // Each wedge has 5 vertices.
-    int maxVertices = (path.countVerbs() + 1) * 5;
+    // We emit one wedge per path segment. Each wedge has 5 vertices.
+    int maxVertices = max_segments_in_path(path) * 5;
 
     GrEagerDynamicVertexAllocator vertexAlloc(target, &fPatchBuffer, &fBasePatchVertex);
     auto* vertexData = vertexAlloc.lock<SkPoint>(maxVertices);
diff --git a/src/gpu/tessellate/GrPathTessellator.h b/src/gpu/tessellate/GrPathTessellator.h
index 96494c4..76a9781 100644
--- a/src/gpu/tessellate/GrPathTessellator.h
+++ b/src/gpu/tessellate/GrPathTessellator.h
@@ -20,6 +20,13 @@
 public:
     using BreadcrumbTriangleList = GrInnerFanTriangulator::BreadcrumbTriangleList;
 
+    // For subclasses that use this enum, if DrawInnerFan is kNo, the class only emits the path's
+    // outer curves. In that case the caller is responsible to handle the path's inner fan.
+    enum class DrawInnerFan : bool {
+        kNo = false,
+        kYes
+    };
+
     // Called before draw(). Prepares GPU buffers containing the geometry to tessellate. If the
     // given BreadcrumbTriangleList is non-null, then this class will also include the breadcrumb
     // triangles in its draw.
@@ -38,16 +45,12 @@
     virtual ~GrPathTessellator() {}
 };
 
-// Draws tessellations of the path's outer curves using indirect draw commands. Quadratics are
-// converted to cubics. An outer curve is an independent, 4-point closed contour that represents
-// either a cubic or a conic.
-//
-// For performance reasons we can often express triangles as one of these indirect draws and sneak
-// them in alongside the other curves. If DrawInnerFan is kYes, then this class also draws the
-// path's inner fan along with the outer curves.
+// Draws tessellations of the path's outer curves and, optionally, inner fan triangles using
+// indirect draw commands. Quadratics are converted to cubics and triangles are converted to conics
+// with w=Inf. An outer curve is an independent, 4-point closed contour that represents either a
+// cubic or a conic.
 class GrPathIndirectTessellator final : public GrPathTessellator {
 public:
-    enum class DrawInnerFan : bool { kNo = false, kYes };
     GrPathIndirectTessellator(const SkMatrix&, const SkPath&, DrawInnerFan);
 
     void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
@@ -86,20 +89,24 @@
     int fPatchVertexCount = 0;
 };
 
-// Draws an array of "outer curve" patches for GrCubicTessellateShader. Each patch is an independent
-// 4-point curve, representing either a cubic or conic. Qudaratics are converted to cubics. The
-// caller is responsible to stencil the path's inner fan along with these outer cubics.
+// Draws an array of "outer curve" patches and, optionally, inner fan triangles for
+// GrCubicTessellateShader. Each patch is an independent 4-point curve, representing either a cubic
+// or a conic. Quadratics are converted to cubics and triangles are converted to conics with w=Inf.
 class GrPathOuterCurveTessellator final : public GrPathHardwareTessellator {
 public:
-    GrPathOuterCurveTessellator() = default;
+    GrPathOuterCurveTessellator(DrawInnerFan drawInnerFan)
+            : fDrawInnerFan(drawInnerFan == DrawInnerFan::kYes) {}
 
     void prepare(GrMeshDrawOp::Target*, const SkMatrix&, const SkPath&,
                  const BreadcrumbTriangleList*) override;
+
+private:
+    const bool fDrawInnerFan;
 };
 
 // Draws an array of "wedge" patches for GrWedgeTessellateShader. A wedge is an independent, 5-point
 // closed contour consisting of 4 control points plus an anchor point fanning from the center of the
-// curve's resident contour. A wedge can be either a cubic or a conic. Qudaratics and lines are
+// curve's resident contour. A wedge can be either a cubic or a conic. Quadratics and lines are
 // converted to cubics. Once stencilled, these wedges alone define the complete path.
 class GrPathWedgeTessellator final : public GrPathHardwareTessellator {
 public:
diff --git a/src/gpu/tessellate/GrStencilPathShader.cpp b/src/gpu/tessellate/GrStencilPathShader.cpp
index ec72587..b070157 100644
--- a/src/gpu/tessellate/GrStencilPathShader.cpp
+++ b/src/gpu/tessellate/GrStencilPathShader.cpp
@@ -65,7 +65,7 @@
             v->codeAppendf("float2 vertexpos = (%s * float3(inputPoint, 1)).xy;", viewMatrix);
             if (shader.willUseTessellationShaders()) {
                 // If y is infinity then x is a conic weight. Don't transform.
-                v->codeAppendf("vertexpos = (isinf(vertexpos.y)) ? inputPoint : vertexpos;");
+                v->codeAppendf("vertexpos = (isinf(inputPoint.y)) ? inputPoint : vertexpos;");
             }
             vertexPos.set(kFloat2_GrSLType, "vertexpos");
         }
@@ -118,30 +118,36 @@
             patch out float rationalCubicW;
 
             void main() {
-                vec2 p0=P[0], p1w=P[1], p2=P[2], p3=P[3];
-                float w = -1;
-                if (isinf(p3.y)) {
+                float w = -1;  // w<0 means a cubic.
+                vec2 p1w = P[1];
+                if (isinf(P[3].y)) {
                     // This patch is actually a conic. Project to homogeneous space.
-                    w = p3.x;
+                    w = P[3].x;
                     p1w *= w;
                 }
 
                 // Chop the curve at T=1/2.
-                vec2 ab = (p0 + p1w) * .5;
-                vec2 bc = (p1w + p2) * .5;
-                vec2 cd = (p2 + p3) * .5;
+                vec2 ab = (P[0] + p1w) * .5;
+                vec2 bc = (p1w + P[2]) * .5;
+                vec2 cd = (P[2] + P[3]) * .5;
                 vec2 abc = (ab + bc) * .5;
                 vec2 bcd = (bc + cd) * .5;
                 vec2 abcd = (abc + bcd) * .5;
 
                 float n0, n1;
-                if (w < 0) {
-                    // The patch is a cubic. Calculate how many segments are needed to linearize
-                    // each half of the curve.
-                    n0 = wangs_formula(PRECISION, p0, ab, abc, abcd, -1);  // w<0 means cubic.
-                    n1 = wangs_formula(PRECISION, abcd, bcd, cd, p3, -1);
-                    rationalCubicXY = mat4x2(p0, p1w, p2, p3);  // p1w == p1 because w == 1.
-                    rationalCubicW = 1;
+                if (w < 0 || isinf(w)) {
+                    if (w < 0) {
+                        // The patch is a cubic. Calculate how many segments are required to
+                        // linearize each half of the curve.
+                        n0 = wangs_formula(PRECISION, P[0], ab, abc, abcd, -1);  // w<0 means cubic.
+                        n1 = wangs_formula(PRECISION, abcd, bcd, cd, P[3], -1);
+                        rationalCubicW = 1;
+                    } else {
+                        // The patch is a triangle (a conic with infinite weight).
+                        n0 = n1 = 1;
+                        rationalCubicW = -1;  // In the next stage, rationalCubicW<0 means triangle.
+                    }
+                    rationalCubicXY = mat4x2(P[0], P[1], P[2], P[3]);
                 } else {
                     // The patch is a conic. Unproject p0..5. w1 == w2 == w3 when chopping at .5.
                     // (See SkConic::chopAt().)
@@ -150,12 +156,12 @@
                     // Put in "standard form" where w0 == w2 == w4 == 1.
                     float w_ = inversesqrt(r);  // Both halves have the same w' when chopping at .5.
                     // Calculate how many segments are needed to linearize each half of the curve.
-                    n0 = wangs_formula(PRECISION, p0, ab, abc, float2(0), w_);
-                    n1 = wangs_formula(PRECISION, abc, bc, p2, float2(0), w_);
+                    n0 = wangs_formula(PRECISION, P[0], ab, abc, float2(0), w_);
+                    n1 = wangs_formula(PRECISION, abc, bc, P[2], float2(0), w_);
                     // Covert the conic to a rational cubic in projected form.
-                    rationalCubicXY = mat4x2(p0,
-                                             mix(float4(p0,p2), p1w.xyxy, 2.0/3.0),
-                                             p2);
+                    rationalCubicXY = mat4x2(P[0],
+                                             mix(float4(P[0],P[2]), p1w.xyxy, 2.0/3.0),
+                                             P[2]);
                     rationalCubicW = fma(w, 2.0/3.0, 1.0/3.0);
                 }
 
@@ -188,20 +194,29 @@
             patch in float rationalCubicW;
 
             void main() {
-                // Locate our parametric point of interest. T ramps from [0..1/2] on the left edge
-                // of the triangle, and [1/2..1] on the right. If we are the patch's interior
-                // vertex, then we want T=1/2. Since the barycentric coords are (1/3, 1/3, 1/3) at
-                // the interior vertex, the below fma() works in all 3 scenarios.
-                float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
+                vec2 vertexpos;
+                if (rationalCubicW < 0) {  // rationalCubicW < 0 means a triangle now.
+                    vertexpos = (gl_TessCoord.x != 0) ? rationalCubicXY[0]
+                              : (gl_TessCoord.y != 0) ? rationalCubicXY[1]
+                                                      : rationalCubicXY[2];
+                } else {
+                    // Locate our parametric point of interest. T ramps from [0..1/2] on the left
+                    // edge of the triangle, and [1/2..1] on the right. If we are the patch's
+                    // interior vertex, then we want T=1/2. Since the barycentric coords are
+                    // (1/3, 1/3, 1/3) at the interior vertex, the below fma() works in all 3
+                    // scenarios.
+                    float T = fma(.5, gl_TessCoord.y, gl_TessCoord.z);
 
-                mat4x3 P = mat4x3(rationalCubicXY[0], 1,
-                                  rationalCubicXY[1], rationalCubicW,
-                                  rationalCubicXY[2], rationalCubicW,
-                                  rationalCubicXY[3], 1);
-                vec2 vertexpos = eval_rational_cubic(P, T);
-                if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
-                    // We are the interior point of the patch; center it inside [C(0), C(.5), C(1)].
-                    vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
+                    mat4x3 P = mat4x3(rationalCubicXY[0], 1,
+                                      rationalCubicXY[1], rationalCubicW,
+                                      rationalCubicXY[2], rationalCubicW,
+                                      rationalCubicXY[3], 1);
+                    vertexpos = eval_rational_cubic(P, T);
+                    if (all(notEqual(gl_TessCoord.xz, vec2(0)))) {
+                        // We are the interior point of the patch; center it inside
+                        // [C(0), C(.5), C(1)].
+                        vertexpos = (P[0].xy + vertexpos + P[3].xy) / 3.0;
+                    }
                 }
 
                 gl_Position = vec4(vertexpos * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0);