skia2/samplecode/SampleCCPRGeometry.cpp
Chris Dalton a3e9271ec4 CCPR: Eliminate texel buffers
Points cost 8 bytes and indices cost 4. If a point is accessed twice,
it's a wash whether we duplicate it or index. This change eliminates
texel buffers by duplicating points across instance arrays. This
reduces our dependence on extensions as well as getting rid of our
indirect memory access pattern in vertex shaders.

As a result of this change, memory usage by GPU buffers will only be a
fraction larger at worst, and slightly better at best.

Bug: skia:
Cq-Include-Trybots: skia.primary:Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SKNX_NO_SIMD
Change-Id: I3c7f03772edd4f850d5fdd7b55552647335c1b52
Reviewed-on: https://skia-review.googlesource.com/79185
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Chris Dalton <csmartdalton@google.com>
2017-12-05 21:12:48 +00:00

389 lines
14 KiB
C++

/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkTypes.h"
#if SK_SUPPORT_GPU
#include "GrContextPriv.h"
#include "GrPathUtils.h"
#include "GrRenderTargetContext.h"
#include "GrRenderTargetContextPriv.h"
#include "GrResourceProvider.h"
#include "SampleCode.h"
#include "SkCanvas.h"
#include "SkMakeUnique.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkView.h"
#include "ccpr/GrCCPRCoverageProcessor.h"
#include "ccpr/GrCCPRGeometry.h"
#include "gl/GrGLGpu.cpp"
#include "ops/GrDrawOp.h"
using TriangleInstance = GrCCPRCoverageProcessor::TriangleInstance;
using CubicInstance = GrCCPRCoverageProcessor::CubicInstance;
using RenderPass = GrCCPRCoverageProcessor::RenderPass;
static constexpr float kDebugBloat = 40;
static int is_quadratic(RenderPass renderPass) {
return renderPass >= RenderPass::kQuadraticHulls && renderPass < RenderPass::kSerpentineHulls;
}
/**
* This sample visualizes the AA bloat geometry generated by the ccpr geometry shaders. It
* increases the AA bloat by 50x and outputs color instead of coverage (coverage=+1 -> green,
* coverage=0 -> black, coverage=-1 -> red). Use the keys 1-7 to cycle through the different
* geometry processors.
*/
class CCPRGeometryView : public SampleView {
public:
CCPRGeometryView() { this->updateGpuData(); }
void onDrawContent(SkCanvas*) override;
SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned) override;
bool onClick(SampleView::Click*) override;
bool onQuery(SkEvent* evt) override;
private:
class Click;
class Op;
void updateAndInval() {
this->updateGpuData();
}
void updateGpuData();
RenderPass fRenderPass = RenderPass::kTriangleHulls;
SkMatrix fCubicKLM;
SkPoint fPoints[4] = {
{100.05f, 100.05f},
{400.75f, 100.05f},
{400.75f, 300.95f},
{100.05f, 300.95f}
};
SkTArray<TriangleInstance> fTriangleInstances;
SkTArray<CubicInstance> fCubicInstances;
typedef SampleView INHERITED;
};
class CCPRGeometryView::Op : public GrDrawOp {
DEFINE_OP_CLASS_ID
public:
Op(CCPRGeometryView* view)
: INHERITED(ClassID())
, fView(view) {
this->setBounds(SkRect::MakeLargest(), GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
}
const char* name() const override { return "[Testing/Sample code] CCPRGeometryView::Op"; }
private:
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
GrPixelConfigIsClamped) override {
return RequiresDstTexture::kNo;
}
bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
void onPrepare(GrOpFlushState*) override {}
void onExecute(GrOpFlushState*) override;
CCPRGeometryView* fView;
typedef GrDrawOp INHERITED;
};
static void draw_klm_line(int w, int h, SkCanvas* canvas, const SkScalar line[3], SkColor color) {
SkPoint p1, p2;
if (SkScalarAbs(line[1]) > SkScalarAbs(line[0])) {
// Draw from vertical edge to vertical edge.
p1 = {0, -line[2] / line[1]};
p2 = {(SkScalar) w, (-line[2] - w * line[0]) / line[1]};
} else {
// Draw from horizontal edge to horizontal edge.
p1 = {-line[2] / line[0], 0};
p2 = {(-line[2] - h * line[1]) / line[0], (SkScalar) h};
}
SkPaint linePaint;
linePaint.setColor(color);
linePaint.setAlpha(128);
linePaint.setStyle(SkPaint::kStroke_Style);
linePaint.setStrokeWidth(0);
linePaint.setAntiAlias(true);
canvas->drawLine(p1, p2, linePaint);
}
void CCPRGeometryView::onDrawContent(SkCanvas* canvas) {
SkAutoCanvasRestore acr(canvas, true);
canvas->setMatrix(SkMatrix::I());
SkPath outline;
outline.moveTo(fPoints[0]);
if (GrCCPRCoverageProcessor::RenderPassIsCubic(fRenderPass)) {
outline.cubicTo(fPoints[1], fPoints[2], fPoints[3]);
} else if (is_quadratic(fRenderPass)) {
outline.quadTo(fPoints[1], fPoints[3]);
} else {
outline.lineTo(fPoints[1]);
outline.lineTo(fPoints[3]);
outline.close();
}
SkPaint outlinePaint;
outlinePaint.setColor(0x30000000);
outlinePaint.setStyle(SkPaint::kStroke_Style);
outlinePaint.setStrokeWidth(0);
outlinePaint.setAntiAlias(true);
canvas->drawPath(outline, outlinePaint);
#if 0
SkPaint gridPaint;
gridPaint.setColor(0x10000000);
gridPaint.setStyle(SkPaint::kStroke_Style);
gridPaint.setStrokeWidth(0);
gridPaint.setAntiAlias(true);
for (int y = 0; y < this->height(); y += kDebugBloat) {
canvas->drawLine(0, y, this->width(), y, gridPaint);
}
for (int x = 0; x < this->width(); x += kDebugBloat) {
canvas->drawLine(x, 0, x, this->height(), outlinePaint);
}
#endif
const char* caption = "Use GPU backend to visualize geometry.";
if (GrRenderTargetContext* rtc =
canvas->internal_private_accessTopLayerRenderTargetContext()) {
rtc->priv().testingOnly_addDrawOp(skstd::make_unique<Op>(this));
caption = GrCCPRCoverageProcessor::GetRenderPassName(fRenderPass);
}
SkPaint pointsPaint;
pointsPaint.setColor(SK_ColorBLUE);
pointsPaint.setStrokeWidth(8);
pointsPaint.setAntiAlias(true);
if (GrCCPRCoverageProcessor::RenderPassIsCubic(fRenderPass)) {
int w = this->width(), h = this->height();
canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, fPoints, pointsPaint);
draw_klm_line(w, h, canvas, &fCubicKLM[0], SK_ColorYELLOW);
draw_klm_line(w, h, canvas, &fCubicKLM[3], SK_ColorBLUE);
draw_klm_line(w, h, canvas, &fCubicKLM[6], SK_ColorRED);
} else {
canvas->drawPoints(SkCanvas::kPoints_PointMode, 2, fPoints, pointsPaint);
canvas->drawPoints(SkCanvas::kPoints_PointMode, 1, fPoints + 3, pointsPaint);
}
SkPaint captionPaint;
captionPaint.setTextSize(20);
captionPaint.setColor(SK_ColorBLACK);
captionPaint.setAntiAlias(true);
canvas->drawText(caption, strlen(caption), 10, 30, captionPaint);
}
void CCPRGeometryView::updateGpuData() {
fTriangleInstances.reset();
fCubicInstances.reset();
if (GrCCPRCoverageProcessor::RenderPassIsCubic(fRenderPass)) {
double t[2], s[2];
SkCubicType type = GrPathUtils::getCubicKLM(fPoints, &fCubicKLM, t, s);
if (RenderPass::kSerpentineHulls == fRenderPass && SkCubicType::kLoop == type) {
fRenderPass = RenderPass::kLoopHulls;
}
if (RenderPass::kSerpentineCorners == fRenderPass && SkCubicType::kLoop == type) {
fRenderPass = RenderPass::kLoopCorners;
}
if (RenderPass::kLoopHulls == fRenderPass && SkCubicType::kLoop != type) {
fRenderPass = RenderPass::kSerpentineHulls;
}
if (RenderPass::kLoopCorners == fRenderPass && SkCubicType::kLoop != type) {
fRenderPass = RenderPass::kSerpentineCorners;
}
GrCCPRGeometry geometry;
geometry.beginContour(fPoints[0]);
geometry.cubicTo(fPoints[1], fPoints[2], fPoints[3], kDebugBloat/2, kDebugBloat/2);
geometry.endContour();
int ptsIdx = 0;
for (GrCCPRGeometry::Verb verb : geometry.verbs()) {
switch (verb) {
case GrCCPRGeometry::Verb::kLineTo:
++ptsIdx;
continue;
case GrCCPRGeometry::Verb::kMonotonicQuadraticTo:
ptsIdx += 2;
continue;
case GrCCPRGeometry::Verb::kMonotonicSerpentineTo:
case GrCCPRGeometry::Verb::kMonotonicLoopTo:
fCubicInstances.push_back().set(&geometry.points()[ptsIdx], 0, 0);
ptsIdx += 3;
continue;
default: continue;
}
}
} else if (is_quadratic(fRenderPass)) {
GrCCPRGeometry geometry;
geometry.beginContour(fPoints[0]);
geometry.quadraticTo(fPoints[1], fPoints[3]);
geometry.endContour();
int ptsIdx = 0;
for (GrCCPRGeometry::Verb verb : geometry.verbs()) {
if (GrCCPRGeometry::Verb::kBeginContour == verb ||
GrCCPRGeometry::Verb::kEndOpenContour == verb ||
GrCCPRGeometry::Verb::kEndClosedContour == verb) {
continue;
}
SkASSERT(GrCCPRGeometry::Verb::kMonotonicQuadraticTo == verb);
fTriangleInstances.push_back().set(&geometry.points()[ptsIdx], Sk2f(0, 0));
ptsIdx += 2;
}
} else {
fTriangleInstances.push_back().set(fPoints[0], fPoints[1], fPoints[3], Sk2f(0, 0));
}
}
void CCPRGeometryView::Op::onExecute(GrOpFlushState* state) {
GrResourceProvider* rp = state->resourceProvider();
GrContext* context = state->gpu()->getContext();
GrGLGpu* glGpu = kOpenGL_GrBackend == context->contextPriv().getBackend() ?
static_cast<GrGLGpu*>(state->gpu()) : nullptr;
bool isCubic = GrCCPRCoverageProcessor::RenderPassIsCubic(fView->fRenderPass);
GrMesh mesh(isCubic ? GrPrimitiveType::kLinesAdjacency : GrPrimitiveType::kTriangles);
if (isCubic) {
if (fView->fCubicInstances.empty()) {
return;
}
sk_sp<GrBuffer> instBuff(rp->createBuffer(fView->fCubicInstances.count() *
sizeof(CubicInstance), kVertex_GrBufferType,
kDynamic_GrAccessPattern,
GrResourceProvider::kNoPendingIO_Flag |
GrResourceProvider::kRequireGpuMemory_Flag,
fView->fCubicInstances.begin()));
if (!instBuff) {
return;
}
mesh.setInstanced(instBuff.get(), fView->fCubicInstances.count(), 0, 4);
} else {
if (fView->fTriangleInstances.empty()) {
return;
}
sk_sp<GrBuffer> instBuff(rp->createBuffer(fView->fTriangleInstances.count() *
sizeof(TriangleInstance), kVertex_GrBufferType,
kDynamic_GrAccessPattern,
GrResourceProvider::kNoPendingIO_Flag |
GrResourceProvider::kRequireGpuMemory_Flag,
fView->fTriangleInstances.begin()));
if (!instBuff) {
return;
}
mesh.setInstanced(instBuff.get(), fView->fTriangleInstances.count(), 0, 3);
}
GrPipeline pipeline(state->drawOpArgs().fProxy, GrPipeline::ScissorState::kDisabled,
SkBlendMode::kSrcOver);
GrCCPRCoverageProcessor ccprProc(fView->fRenderPass);
SkDEBUGCODE(ccprProc.enableDebugVisualizations(kDebugBloat);)
if (glGpu) {
glGpu->handleDirtyContext();
GR_GL_CALL(glGpu->glInterface(), PolygonMode(GR_GL_FRONT_AND_BACK, GR_GL_LINE));
GR_GL_CALL(glGpu->glInterface(), Enable(GR_GL_LINE_SMOOTH));
}
state->rtCommandBuffer()->draw(pipeline, ccprProc, &mesh, nullptr, 1, this->bounds());
if (glGpu) {
context->resetContext(kMisc_GrGLBackendState);
}
}
class CCPRGeometryView::Click : public SampleView::Click {
public:
Click(SkView* target, int ptIdx) : SampleView::Click(target), fPtIdx(ptIdx) {}
void doClick(SkPoint points[]) {
if (fPtIdx >= 0) {
this->dragPoint(points, fPtIdx);
} else {
for (int i = 0; i < 4; ++i) {
this->dragPoint(points, i);
}
}
}
private:
void dragPoint(SkPoint points[], int idx) {
SkIPoint delta = fICurr - fIPrev;
points[idx] += SkPoint::Make(delta.x(), delta.y());
}
int fPtIdx;
};
SkView::Click* CCPRGeometryView::onFindClickHandler(SkScalar x, SkScalar y, unsigned) {
for (int i = 0; i < 4; ++i) {
if (!GrCCPRCoverageProcessor::RenderPassIsCubic(fRenderPass) && 2 == i) {
continue;
}
if (fabs(x - fPoints[i].x()) < 20 && fabsf(y - fPoints[i].y()) < 20) {
return new Click(this, i);
}
}
return new Click(this, -1);
}
bool CCPRGeometryView::onClick(SampleView::Click* click) {
Click* myClick = (Click*) click;
myClick->doClick(fPoints);
this->updateAndInval();
return true;
}
bool CCPRGeometryView::onQuery(SkEvent* evt) {
if (SampleCode::TitleQ(*evt)) {
SampleCode::TitleR(evt, "CCPRGeometry");
return true;
}
SkUnichar unichar;
if (SampleCode::CharQ(*evt, &unichar)) {
if (unichar >= '1' && unichar <= '7') {
fRenderPass = RenderPass(unichar - '1');
if (fRenderPass >= RenderPass::kLoopHulls) {
// '6' -> kSerpentineHulls, '7' -> kSerpentineCorners. updateGpuData converts to
// kLoop* if needed.
fRenderPass = RenderPass(int(fRenderPass) + 1);
}
this->updateAndInval();
return true;
}
if (unichar == 'D') {
SkDebugf(" SkPoint fPoints[4] = {\n");
SkDebugf(" {%ff, %ff},\n", fPoints[0].x(), fPoints[0].y());
SkDebugf(" {%ff, %ff},\n", fPoints[1].x(), fPoints[1].y());
SkDebugf(" {%ff, %ff},\n", fPoints[2].x(), fPoints[2].y());
SkDebugf(" {%ff, %ff}\n", fPoints[3].x(), fPoints[3].y());
SkDebugf(" };\n");
return true;
}
}
return this->INHERITED::onQuery(evt);
}
DEF_SAMPLE( return new CCPRGeometryView; )
#endif // SK_SUPPORT_GPU