d358cbebd4
This CL adds a new type GrDstSampleType to say how we will sample the dst. We add tracking of the GrDstSampleType in the recording of GrOps and then during execution passing the information along to the GrPipeline. In general the tracking of GrDstSampleType is a global state of a GrOpsTask so it is kept separate fro the DstProxyView which is more specific to a single Op on the GrOpsTask. Bug: skia:10409 Change-Id: Ie843c31f2e48a887daf96cee99ed159b196cb545 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/315645 Commit-Queue: Greg Daniel <egdaniel@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com>
398 lines
16 KiB
C++
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/GrContextPriv.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/GrRenderTargetContext.h"
|
|
#include "src/gpu/GrRenderTargetContextPriv.h"
|
|
#include "src/gpu/GrSamplerState.h"
|
|
#include "src/gpu/GrShaderCaps.h"
|
|
#include "src/gpu/GrShaderVar.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*, GrRenderTargetContext*,
|
|
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<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:
|
|
friend class ::SkArenaAlloc; // for access to ctor
|
|
|
|
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 = ileft/16.0 - outset;");
|
|
v->codeAppendf("float r = iright/16.0 + outset;");
|
|
v->codeAppendf("float t = itop/16.0 - outset;");
|
|
v->codeAppendf("float b = 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 std::unique_ptr<GrDrawOp> Make(
|
|
GrRecordingContext* ctx, const SkMatrix& viewMatrix, GradType gradType) {
|
|
GrOpMemoryPool* pool = ctx->priv().opMemoryPool();
|
|
return pool->allocate<SampleLocationsTestOp>(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) 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,
|
|
flags, &gStencilWrite);
|
|
}
|
|
|
|
GrProgramInfo* createProgramInfo(GrOpFlushState* flushState) const {
|
|
return this->createProgramInfo(&flushState->caps(),
|
|
flushState->allocator(),
|
|
flushState->writeView(),
|
|
flushState->detachAppliedClip(),
|
|
flushState->dstProxyView(),
|
|
flushState->renderPassBarriers());
|
|
}
|
|
|
|
void onPrePrepare(GrRecordingContext* context,
|
|
const GrSurfaceProxyView* writeView,
|
|
GrAppliedClip* clip,
|
|
const GrXferProcessor::DstProxyView& dstProxyView,
|
|
GrXferBarrierFlags renderPassXferBarriers) 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);
|
|
|
|
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 ::GrOpMemoryPool; // for ctor
|
|
|
|
using INHERITED = GrDrawOp;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Test.
|
|
|
|
DrawResult SampleLocationsGM::onDraw(GrRecordingContext* ctx, GrRenderTargetContext* 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 = GrRenderTargetContext::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({0,1,0,1});
|
|
|
|
// Stencil.
|
|
offscreenRTC->priv().testingOnly_addDrawOp(
|
|
SampleLocationsTestOp::Make(ctx, canvas->getTotalMatrix(), fGradType));
|
|
|
|
// Cover.
|
|
GrPaint coverPaint;
|
|
coverPaint.setColor4f({1,0,0,1});
|
|
coverPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrcOver));
|
|
rtc->priv().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
|