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>
This commit is contained in:
Chris Dalton 2018-07-27 12:38:35 -06:00 committed by Skia Commit-Bot
parent f4f6bbfada
commit 49d14e98fe
13 changed files with 255 additions and 33 deletions

191
gm/clockwise.cpp Normal file
View File

@ -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(); )
}

View File

@ -70,6 +70,7 @@ gm_sources = [
"$_gm/clip_strokerect.cpp",
"$_gm/clipdrawdraw.cpp",
"$_gm/clippedbitmapshaders.cpp",
"$_gm/clockwise.cpp",
"$_gm/color4f.cpp",
"$_gm/coloremoji.cpp",
"$_gm/coloremoji_blendmodes.cpp",

View File

@ -71,6 +71,7 @@ public:
kButtCapStrokedCircleGeometryProcessor_ClassID,
kCircleGeometryProcessor_ClassID,
kCircularRRectEffect_ClassID,
kClockwiseTestProcessor_ClassID,
kColorMatrixEffect_ClassID,
kColorTableEffect_ClassID,
kComposeOneFragmentProcessor_ClassID,

View File

@ -359,9 +359,9 @@ void GrGLGpu::onResetContext(uint32_t resetBits) {
// 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 @@ bool GrGLGpu::flushGLState(const GrPrimitiveProcessor& primProc,
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 @@ bool GrGLGpu::flushGLState(const GrPrimitiveProcessor& primProc,
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 @@ void GrGLGpu::clear(const GrFixedClip& clip, GrColor color,
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 @@ GrGpuTextureCommandBuffer* GrGLGpu::createCommandBuffer(GrTexture* texture,
}
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 @@ void GrGLGpu::clearStencilClipAsDraw(const GrFixedClip& clip, bool insideStencil
}
GrGLRenderTarget* glRT = static_cast<GrGLRenderTarget*>(rt->asRenderTarget());
this->flushRenderTarget(glRT);
this->flushRenderTarget(glRT, origin);
this->flushProgram(fStencilClipClearProgram);

View File

@ -341,9 +341,7 @@ private:
// 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 @@ private:
GrStencilSettings fHWStencilSettings;
TriState fHWStencilTestEnabled;
GrGLenum fHWFrontFace;
TriState fHWWriteToColor;
GrGpuResource::UniqueID fHWBoundRenderTargetUniqueID;

View File

@ -88,11 +88,12 @@ void GrGLPathRendering::onStencilPath(const StencilPathArgs& args, const GrPath*
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);

View File

@ -436,7 +436,9 @@ static void setup_raster_state(const GrPipeline& pipeline,
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;

View File

@ -204,19 +204,23 @@ GrVkPipelineState* GrVkPipelineStateBuilder::finalize(const GrStencilSettings& s
//////////////////////////////////////////////////////////////////////////////
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 @@ bool GrVkPipelineStateBuilder::Desc::Build(Desc* desc,
stencil.genKey(&b);
b.add32(get_blend_info_key(pipeline));
b.add32(get_pipeline_info_key(pipeline));
b.add32((uint32_t)primitiveType);

View File

@ -45,6 +45,8 @@ Differences from GLSL
* 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 @@ Creating a new .fp file
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.
changes to the .fp file.

View File

@ -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

View File

@ -689,6 +689,9 @@ void GLSLCodeGenerator::writeVariableReference(const VariableReference& ref) {
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;

View File

@ -2,7 +2,9 @@ STRINGIFY(
// 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

View File

@ -1154,6 +1154,17 @@ DEF_TEST(SkSLFragCoord, r) {
"}\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; }",