skia2/gm/samplelocations.cpp
John Stiles af9b58e287 Fix fragment processors and unit tests which mix ints and floats.
These need to change because type coercion in SkSL is about to become
more strict in a followup CL; we are disallowing expressions that mix
ints and floats without a cast.

Change-Id: Iff5e2820806b9419afdfcbf25d4a7f96f2eeeccb
Bug: skia:11164
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/353416
Commit-Queue: John Stiles <johnstiles@google.com>
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
2021-01-14 00:24:45 +00:00

398 lines
16 KiB
C++

/*
* 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 "gm/gm.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "include/gpu/GrRecordingContext.h"
#include "include/gpu/GrTypes.h"
#include "include/private/GrTypesPriv.h"
#include "include/private/SkColorData.h"
#include "src/gpu/GrBuffer.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrColorSpaceXform.h"
#include "src/gpu/GrDirectContextPriv.h"
#include "src/gpu/GrGeometryProcessor.h"
#include "src/gpu/GrMemoryPool.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrOpsRenderPass.h"
#include "src/gpu/GrPaint.h"
#include "src/gpu/GrPipeline.h"
#include "src/gpu/GrPrimitiveProcessor.h"
#include "src/gpu/GrProcessor.h"
#include "src/gpu/GrProcessorSet.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrSamplerState.h"
#include "src/gpu/GrShaderCaps.h"
#include "src/gpu/GrShaderVar.h"
#include "src/gpu/GrSurfaceDrawContext.h"
#include "src/gpu/GrSurfaceProxy.h"
#include "src/gpu/GrTextureProxy.h"
#include "src/gpu/GrUserStencilSettings.h"
#include "src/gpu/effects/GrPorterDuffXferProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLGeometryProcessor.h"
#include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/gpu/glsl/GrGLSLVarying.h"
#include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
#include "src/gpu/ops/GrDrawOp.h"
#include "src/gpu/ops/GrOp.h"
#include "tools/gpu/ProxyUtils.h"
#include <memory>
#include <utility>
class GrAppliedClip;
class GrGLSLProgramDataManager;
namespace skiagm {
enum class GradType : bool {
kHW,
kSW
};
/**
* This test ensures that the shaderBuilder's sample offsets and sample mask are correlated with
* actual HW sample locations. It does so by drawing pseudo-random subpixel boxes, and only turning
* off the samples whose locations fall inside the boxes.
*/
class SampleLocationsGM : public GpuGM {
public:
SampleLocationsGM(GradType gradType, GrSurfaceOrigin origin)
: fGradType(gradType)
, fOrigin(origin) {}
private:
SkString onShortName() override {
return SkStringPrintf("samplelocations%s%s",
(GradType::kHW == fGradType) ? "_hwgrad" : "_swgrad",
(kTopLeft_GrSurfaceOrigin == fOrigin) ? "_topleft" : "_botleft");
}
SkISize onISize() override { return SkISize::Make(200, 200); }
DrawResult onDraw(GrRecordingContext*, GrSurfaceDrawContext*,
SkCanvas*, SkString* errorMsg) override;
const GradType fGradType;
const GrSurfaceOrigin fOrigin;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// SkSL code.
class SampleLocationsTestProcessor : public GrGeometryProcessor {
public:
static GrGeometryProcessor* Make(SkArenaAlloc* arena, GradType gradType) {
return arena->make([&](void* ptr) {
return new (ptr) SampleLocationsTestProcessor(gradType);
});
}
const char* name() const override { return "SampleLocationsTestProcessor"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {
b->add32((uint32_t)fGradType);
}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const final;
private:
SampleLocationsTestProcessor(GradType gradType)
: GrGeometryProcessor(kSampleLocationsTestProcessor_ClassID)
, fGradType(gradType) {
this->setWillUseCustomFeature(CustomFeatures::kSampleLocations);
}
const GradType fGradType;
class Impl;
using INHERITED = GrGeometryProcessor;
};
class SampleLocationsTestProcessor::Impl : public GrGLSLGeometryProcessor {
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
const auto& proc = args.fGP.cast<SampleLocationsTestProcessor>();
auto* v = args.fVertBuilder;
auto* f = args.fFragBuilder;
GrGLSLVarying coord(kFloat2_GrSLType);
GrGLSLVarying grad(kFloat2_GrSLType);
args.fVaryingHandler->addVarying("coord", &coord);
if (GradType::kSW == proc.fGradType) {
args.fVaryingHandler->addVarying("grad", &grad);
}
// Pixel grid.
v->codeAppendf("int x = sk_InstanceID %% 200;");
v->codeAppendf("int y = sk_InstanceID / 200;");
// Create pseudo-random rectangles inside a 16x16 subpixel grid. This works out nicely
// because there are 17 positions on the grid (including both edges), and 17 is a great
// prime number for generating pseudo-random numbers.
v->codeAppendf("int ileft = (sk_InstanceID*929) %% 17;");
v->codeAppendf("int iright = ileft + 1 + ((sk_InstanceID*1637) %% (17 - ileft));");
v->codeAppendf("int itop = (sk_InstanceID*313) %% 17;");
v->codeAppendf("int ibot = itop + 1 + ((sk_InstanceID*1901) %% (17 - itop));");
// Outset (or inset) the rectangle, for the very likely scenario that samples fall on exact
// 16ths of a pixel. GL_SUBPIXEL_BITS is allowed to be as low as 4, so try not to let the
// outset value to get too small.
v->codeAppendf("float outset = 1/32.0;");
v->codeAppendf("outset = (0 == (x + y) %% 2) ? -outset : +outset;");
v->codeAppendf("float l = float(ileft)/16.0 - outset;");
v->codeAppendf("float r = float(iright)/16.0 + outset;");
v->codeAppendf("float t = float(itop)/16.0 - outset;");
v->codeAppendf("float b = float(ibot)/16.0 + outset;");
v->codeAppendf("float2 vertexpos;");
v->codeAppendf("vertexpos.x = float(x) + ((0 == (sk_VertexID %% 2)) ? l : r);");
v->codeAppendf("vertexpos.y = float(y) + ((0 == (sk_VertexID / 2)) ? t : b);");
gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertexpos");
v->codeAppendf("%s.x = (0 == (sk_VertexID %% 2)) ? -1 : +1;", coord.vsOut());
v->codeAppendf("%s.y = (0 == (sk_VertexID / 2)) ? -1 : +1;", coord.vsOut());
if (GradType::kSW == proc.fGradType) {
v->codeAppendf("%s = 2/float2(r - l, b - t);", grad.vsOut());
}
// Fragment shader: Output RED.
f->codeAppendf("%s = half4(1,0,0,1);", args.fOutputColor);
f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
// Now turn off all the samples inside our sub-rectangle. As long as the shaderBuilder's
// sample offsets and sample mask are correlated with actual HW sample locations, no red
// will bleed through.
f->codeAppendf("for (int i = 0; i < %i; ++i) {",
f->getProgramBuilder()->effectiveSampleCnt());
if (GradType::kHW == proc.fGradType) {
f->codeAppendf("float2x2 grad = float2x2(dFdx(%s), dFdy(%s));",
coord.fsIn(), coord.fsIn());
} else {
f->codeAppendf("float2x2 grad = float2x2(%s.x, 0, 0, %s.y);", grad.fsIn(), grad.fsIn());
}
f->codeAppendf( "float2 samplecoord = %s[i] * grad + %s;",
f->sampleOffsets(), coord.fsIn());
f->codeAppendf( "if (all(lessThanEqual(abs(samplecoord), float2(1)))) {");
f->maskOffMultisampleCoverage(
"~(1 << i)", GrGLSLFPFragmentBuilder::ScopeFlags::kInsideLoop);
f->codeAppendf( "}");
f->codeAppendf("}");
}
void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&) override {}
};
GrGLSLPrimitiveProcessor* SampleLocationsTestProcessor::createGLSLInstance(
const GrShaderCaps&) const {
return new Impl();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Draw Op.
static constexpr GrUserStencilSettings gStencilWrite(
GrUserStencilSettings::StaticInit<
0x0001,
GrUserStencilTest::kAlways,
0xffff,
GrUserStencilOp::kReplace,
GrUserStencilOp::kKeep,
0xffff>()
);
class SampleLocationsTestOp : public GrDrawOp {
public:
DEFINE_OP_CLASS_ID
static GrOp::Owner Make(
GrRecordingContext* ctx, const SkMatrix& viewMatrix, GradType gradType) {
return GrOp::Make<SampleLocationsTestOp>(ctx, gradType);
}
private:
SampleLocationsTestOp(GradType gradType) : GrDrawOp(ClassID()), fGradType(gradType) {
this->setBounds(SkRect::MakeIWH(200, 200), HasAABloat::kNo, IsHairline::kNo);
}
const char* name() const override { return "SampleLocationsTestOp"; }
FixedFunctionFlags fixedFunctionFlags() const override {
return FixedFunctionFlags::kUsesHWAA | FixedFunctionFlags::kUsesStencil;
}
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
bool hasMixedSampledCoverage, GrClampType) override {
return GrProcessorSet::EmptySetAnalysis();
}
GrProgramInfo* createProgramInfo(const GrCaps* caps,
SkArenaAlloc* arena,
const GrSurfaceProxyView& writeView,
GrAppliedClip&& appliedClip,
const GrXferProcessor::DstProxyView& dstProxyView,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) const {
GrGeometryProcessor* geomProc = SampleLocationsTestProcessor::Make(arena, fGradType);
GrPipeline::InputFlags flags = GrPipeline::InputFlags::kHWAntialias;
return sk_gpu_test::CreateProgramInfo(caps, arena, writeView,
std::move(appliedClip), dstProxyView,
geomProc, SkBlendMode::kSrcOver,
GrPrimitiveType::kTriangleStrip,
renderPassXferBarriers, colorLoadOp,
flags, &gStencilWrite);
}
GrProgramInfo* createProgramInfo(GrOpFlushState* flushState) const {
return this->createProgramInfo(&flushState->caps(),
flushState->allocator(),
flushState->writeView(),
flushState->detachAppliedClip(),
flushState->dstProxyView(),
flushState->renderPassBarriers(),
flushState->colorLoadOp());
}
void onPrePrepare(GrRecordingContext* context,
const GrSurfaceProxyView& writeView,
GrAppliedClip* clip,
const GrXferProcessor::DstProxyView& dstProxyView,
GrXferBarrierFlags renderPassXferBarriers,
GrLoadOp colorLoadOp) final {
// We're going to create the GrProgramInfo (and the GrPipeline and geometry processor
// it relies on) in the DDL-record-time arena.
SkArenaAlloc* arena = context->priv().recordTimeAllocator();
// This is equivalent to a GrOpFlushState::detachAppliedClip
GrAppliedClip appliedClip = clip ? std::move(*clip) : GrAppliedClip::Disabled();
fProgramInfo = this->createProgramInfo(context->priv().caps(), arena, writeView,
std::move(appliedClip), dstProxyView,
renderPassXferBarriers, colorLoadOp);
context->priv().recordProgramInfo(fProgramInfo);
}
void onPrepare(GrOpFlushState*) final {}
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) final {
if (!fProgramInfo) {
fProgramInfo = this->createProgramInfo(flushState);
}
flushState->bindPipelineAndScissorClip(*fProgramInfo, SkRect::MakeIWH(200, 200));
flushState->bindBuffers(nullptr, nullptr, nullptr);
flushState->drawInstanced(200*200, 0, 4, 0);
}
const GradType fGradType;
// The program info (and both the GrPipeline and GrPrimitiveProcessor it relies on), when
// allocated, are allocated in either the ddl-record-time or flush-time arena. It is the
// arena's job to free up their memory so we just have a bare programInfo pointer here. We
// don't even store the GrPipeline and GrPrimitiveProcessor pointers here bc they are
// guaranteed to have the same lifetime as the program info.
GrProgramInfo* fProgramInfo = nullptr;
friend class ::GrOp; // for ctor
using INHERITED = GrDrawOp;
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test.
DrawResult SampleLocationsGM::onDraw(GrRecordingContext* ctx, GrSurfaceDrawContext* rtc,
SkCanvas* canvas, SkString* errorMsg) {
if (!ctx->priv().caps()->sampleLocationsSupport()) {
*errorMsg = "Requires support for sample locations.";
return DrawResult::kSkip;
}
if (!ctx->priv().caps()->shaderCaps()->sampleMaskSupport()) {
*errorMsg = "Requires support for sample mask.";
return DrawResult::kSkip;
}
if (!ctx->priv().caps()->drawInstancedSupport()) {
*errorMsg = "Requires support for instanced rendering.";
return DrawResult::kSkip;
}
if (rtc->numSamples() <= 1 && !ctx->priv().caps()->mixedSamplesSupport()) {
*errorMsg = "MSAA and mixed samples only.";
return DrawResult::kSkip;
}
auto offscreenRTC = GrSurfaceDrawContext::Make(
ctx, rtc->colorInfo().colorType(), nullptr, SkBackingFit::kExact, {200, 200},
rtc->numSamples(), GrMipmapped::kNo, GrProtected::kNo, fOrigin);
if (!offscreenRTC) {
*errorMsg = "Failed to create offscreen render target.";
return DrawResult::kFail;
}
if (offscreenRTC->numSamples() <= 1 &&
!offscreenRTC->asRenderTargetProxy()->canUseMixedSamples(*ctx->priv().caps())) {
*errorMsg = "MSAA and mixed samples only.";
return DrawResult::kSkip;
}
static constexpr GrUserStencilSettings kStencilCover(
GrUserStencilSettings::StaticInit<
0x0000,
GrUserStencilTest::kNotEqual,
0xffff,
GrUserStencilOp::kZero,
GrUserStencilOp::kKeep,
0xffff>()
);
offscreenRTC->clear(SkPMColor4f{0, 1, 0, 1});
// Stencil.
offscreenRTC->addDrawOp(SampleLocationsTestOp::Make(ctx, canvas->getTotalMatrix(), fGradType));
// Cover.
GrPaint coverPaint;
coverPaint.setColor4f({1,0,0,1});
coverPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrcOver));
rtc->stencilRect(nullptr, &kStencilCover, std::move(coverPaint), GrAA::kNo, SkMatrix::I(),
SkRect::MakeWH(200, 200));
// Copy offscreen texture to canvas.
rtc->drawTexture(nullptr,
offscreenRTC->readSurfaceView(),
offscreenRTC->colorInfo().alphaType(),
GrSamplerState::Filter::kNearest,
GrSamplerState::MipmapMode::kNone,
SkBlendMode::kSrc,
SK_PMColor4fWHITE,
{0, 0, 200, 200},
{0, 0, 200, 200},
GrAA::kNo,
GrQuadAAFlags::kNone,
SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint,
SkMatrix::I(),
nullptr);
return skiagm::DrawResult::kOk;
}
DEF_GM( return new SampleLocationsGM(GradType::kHW, kTopLeft_GrSurfaceOrigin); )
DEF_GM( return new SampleLocationsGM(GradType::kHW, kBottomLeft_GrSurfaceOrigin); )
DEF_GM( return new SampleLocationsGM(GradType::kSW, kTopLeft_GrSurfaceOrigin); )
DEF_GM( return new SampleLocationsGM(GradType::kSW, kBottomLeft_GrSurfaceOrigin); )
} // namespace skiagm