Respect the max indexBuffer limits in the bulk texture draw API

This is required before we can lower the max AA quad count (again).

Bug: b/143572065 skia:9601
Change-Id: Id34123476ad49a57dc9ce7fe13f941c06f721b74
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/252603
Commit-Queue: Robert Phillips <robertphillips@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Robert Phillips 2019-11-13 10:01:24 -05:00 committed by Skia Commit-Bot
parent 99e73fa8c1
commit dd947ceb55
10 changed files with 396 additions and 72 deletions

View File

@ -24,6 +24,7 @@ tests_sources = [
"$_tests/BlendTest.cpp",
"$_tests/BlitMaskClip.cpp",
"$_tests/BlurTest.cpp",
"$_tests/BulkRectTest.cpp",
"$_tests/CTest.cpp",
"$_tests/CachedDataTest.cpp",
"$_tests/CachedDecodingPixelRefTest.cpp",

View File

@ -106,6 +106,11 @@ public:
SkDEBUGCODE(int numClips() const override { return fNumClips; })
SkDEBUGCODE(void visitProxies_debugOnly(const VisitSurfaceProxyFunc&) const override;)
#if GR_TEST_UTILS
int numOpChains() const { return fOpChains.count(); }
const GrOp* getChain(int index) const { return fOpChains[index].head(); }
#endif
private:
bool isNoOp() const {
// TODO: GrLoadOp::kDiscard (i.e., storing a discard) should also be grounds for skipping

View File

@ -903,15 +903,15 @@ void GrRenderTargetContext::drawTextureSet(const GrClip& clip, const TextureSetE
srcQuad, domain);
}
} else {
// Can use a single op, avoiding GrPaint creation, and can batch across proxies
// Create the minimum number of GrTextureOps needed to draw this set. Individual
// GrTextureOps can rebind the texture between draws thus avoiding GrPaint (re)creation.
AutoCheckFlush acf(this->drawingManager());
GrAAType aaType = this->chooseAAType(aa);
auto clampType = GrColorTypeClampType(this->colorInfo().colorType());
auto saturate = clampType == GrClampType::kManual ? GrTextureOp::Saturate::kYes
: GrTextureOp::Saturate::kNo;
auto op = GrTextureOp::MakeSet(fContext, set, cnt, filter, saturate, aaType, constraint,
viewMatrix, std::move(texXform));
this->addDrawOp(clip, std::move(op));
GrTextureOp::CreateTextureSetOps(this, clip, fContext, set, cnt, filter, saturate, aaType,
constraint, viewMatrix, std::move(texXform));
}
}

View File

@ -549,6 +549,7 @@ private:
friend class GrTessellatingPathRenderer; // for access to add[Mesh]DrawOp
friend class GrCCPerFlushResources; // for access to addDrawOp
friend class GrCoverageCountingPathRenderer; // for access to addDrawOp
friend class GrTextureOp; // for access to addDrawOp
// for a unit test
friend void test_draw_op(GrContext*,
GrRenderTargetContext*,

View File

@ -54,6 +54,11 @@ public:
}
#endif
#if GR_TEST_UTILS
// This is really only intended for GrTextureOp to override
virtual int numQuads() const { return -1; }
#endif
private:
typedef GrOp INHERITED;
};

View File

@ -166,6 +166,18 @@ sk_sp<const GrBuffer> GetIndexBuffer(GrMeshDrawOp::Target* target,
}
}
#ifdef SK_DEBUG
int QuadLimit(IndexBufferOption option) {
switch (option) {
case IndexBufferOption::kPictureFramed: return GrResourceProvider::MaxNumAAQuads();
case IndexBufferOption::kIndexedRects: return GrResourceProvider::MaxNumNonAAQuads();
case IndexBufferOption::kTriStrips: return SK_MaxS32; // not limited by an indexBuffer
}
SkUNREACHABLE;
}
#endif
void ConfigureMesh(GrMesh* mesh, const VertexSpec& spec,
int runningQuadCount, int quadsInDraw, int maxVerts,

View File

@ -151,6 +151,11 @@ namespace GrQuadPerEdgeAA {
// It will, correctly, return nullptr if the indexBufferOption is kTriStrips.
sk_sp<const GrBuffer> GetIndexBuffer(GrMeshDrawOp::Target*, IndexBufferOption);
#ifdef SK_DEBUG
// What is the maximum number of quads allowed for the specified indexBuffer option?
int QuadLimit(IndexBufferOption);
#endif
// This method will configure the vertex and index data of the provided 'mesh' to comply
// with the indexing method specified in the vertexSpec. It is up to the calling code
// to allocate and fill in the vertex data and acquire the correct indexBuffer if it is needed.

View File

@ -159,6 +159,7 @@ public:
color, saturate, aaType, aaFlags, deviceQuad, localQuad,
domain);
}
static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
const GrRenderTargetContext::TextureSetEntry set[],
int cnt,
@ -595,9 +596,11 @@ private:
auto textureType = fViewCountPairs[0].fProxyView.asTextureProxy()->textureType();
GrAAType aaType = this->aaType();
int quadCount = 0;
for (const auto& op : ChainRange<TextureOp>(this)) {
for (unsigned p = 0; p < op.fProxyCnt; ++p) {
auto* proxy = op.fViewCountPairs[p].fProxyView.asTextureProxy();
quadCount += op.fViewCountPairs[p].fQuadCnt;
SkASSERT(proxy);
SkASSERT(proxy->textureType() == textureType);
SkASSERT(op.fViewCountPairs[p].fProxyView.swizzle() ==
@ -612,9 +615,15 @@ private:
SkASSERT(aaType == GrAAType::kMSAA && op.aaType() == GrAAType::kMSAA);
}
}
SkASSERT(quadCount == this->numChainedQuads());
}
#endif
#if GR_TEST_UTILS
int numQuads() const final { return this->totNumQuads(); }
#endif
void characterize(PrePreparedDesc* desc) const {
GrQuad::Type quadType = GrQuad::Type::kAxisAligned;
ColorType colorType = ColorType::kNone;
@ -659,6 +668,8 @@ private:
desc->fVertexSpec = VertexSpec(quadType, colorType, srcQuadType, /* hasLocal */ true,
domain, overallAAType, /* alpha as coverage */ true,
indexBufferOption);
SkASSERT(desc->fNumTotalQuads <= GrQuadPerEdgeAA::QuadLimit(indexBufferOption));
}
int totNumQuads() const {
@ -879,21 +890,25 @@ private:
} // anonymous namespace
namespace GrTextureOp {
#if GR_TEST_UTILS
uint32_t GrTextureOp::ClassID() {
return TextureOp::ClassID();
}
#endif
std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
GrSurfaceProxyView proxyView,
GrColorType srcColorType,
sk_sp<GrColorSpaceXform> textureXform,
GrSamplerState::Filter filter,
const SkPMColor4f& color,
Saturate saturate,
SkBlendMode blendMode,
GrAAType aaType,
GrQuadAAFlags aaFlags,
const GrQuad& deviceQuad,
const GrQuad& localQuad,
const SkRect* domain) {
std::unique_ptr<GrDrawOp> GrTextureOp::Make(GrRecordingContext* context,
GrSurfaceProxyView proxyView,
GrColorType srcColorType,
sk_sp<GrColorSpaceXform> textureXform,
GrSamplerState::Filter filter,
const SkPMColor4f& color,
Saturate saturate,
SkBlendMode blendMode,
GrAAType aaType,
GrQuadAAFlags aaFlags,
const GrQuad& deviceQuad,
const GrQuad& localQuad,
const SkRect* domain) {
GrTextureProxy* proxy = proxyView.asTextureProxy();
// Apply optimizations that are valid whether or not using GrTextureOp or GrFillRectOp
if (domain && domain->contains(proxy->backingStoreBoundsRect())) {
@ -938,20 +953,143 @@ std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
}
}
std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext* context,
const GrRenderTargetContext::TextureSetEntry set[],
int cnt,
GrSamplerState::Filter filter,
Saturate saturate,
GrAAType aaType,
SkCanvas::SrcRectConstraint constraint,
const SkMatrix& viewMatrix,
sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
return TextureOp::Make(context, set, cnt, filter, saturate, aaType, constraint, viewMatrix,
std::move(textureColorSpaceXform));
}
// A helper class that assists in breaking up bulk API quad draws into manageable chunks.
class GrTextureOp::BatchSizeLimiter {
public:
BatchSizeLimiter(GrRenderTargetContext* rtc,
const GrClip& clip,
GrRecordingContext* context,
int numEntries,
GrSamplerState::Filter filter,
GrTextureOp::Saturate saturate,
SkCanvas::SrcRectConstraint constraint,
const SkMatrix& viewMatrix,
sk_sp<GrColorSpaceXform> textureColorSpaceXform)
: fRTC(rtc)
, fClip(clip)
, fContext(context)
, fFilter(filter)
, fSaturate(saturate)
, fConstraint(constraint)
, fViewMatrix(viewMatrix)
, fTextureColorSpaceXform(textureColorSpaceXform)
, fNumLeft(numEntries) {
}
} // namespace GrTextureOp
void createOp(const GrRenderTargetContext::TextureSetEntry set[],
int clumpSize,
GrAAType aaType) {
std::unique_ptr<GrDrawOp> op = TextureOp::Make(fContext, &set[fNumClumped], clumpSize,
fFilter, fSaturate, aaType,
fConstraint, fViewMatrix,
fTextureColorSpaceXform);
fRTC->addDrawOp(fClip, std::move(op));
fNumLeft -= clumpSize;
fNumClumped += clumpSize;
}
int numLeft() const { return fNumLeft; }
int baseIndex() const { return fNumClumped; }
private:
GrRenderTargetContext* fRTC;
const GrClip& fClip;
GrRecordingContext* fContext;
GrSamplerState::Filter fFilter;
GrTextureOp::Saturate fSaturate;
SkCanvas::SrcRectConstraint fConstraint;
const SkMatrix& fViewMatrix;
sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
int fNumLeft;
int fNumClumped = 0; // also the offset for the start of the next clump
};
// Greedily clump quad draws together until the index buffer limit is exceeded.
void GrTextureOp::CreateTextureSetOps(GrRenderTargetContext* rtc,
const GrClip& clip,
GrRecordingContext* context,
const GrRenderTargetContext::TextureSetEntry set[],
int cnt,
GrSamplerState::Filter filter,
Saturate saturate,
GrAAType aaType,
SkCanvas::SrcRectConstraint constraint,
const SkMatrix& viewMatrix,
sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
// First check if we can always just make a single op and avoid the extra iteration
// needed to clump things together.
if (cnt <= SkTMin(GrResourceProvider::MaxNumNonAAQuads(),
GrResourceProvider::MaxNumAAQuads())) {
auto op = TextureOp::Make(context, set, cnt, filter, saturate, aaType,
constraint, viewMatrix, std::move(textureColorSpaceXform));
rtc->addDrawOp(clip, std::move(op));
return;
}
BatchSizeLimiter state(rtc, clip, context, cnt, filter, saturate, constraint, viewMatrix,
std::move(textureColorSpaceXform));
// kNone and kMSAA never get altered
if (aaType == GrAAType::kNone || aaType == GrAAType::kMSAA) {
// Clump these into series of MaxNumNonAAQuads-sized GrTextureOps
while (state.numLeft() > 0) {
int clumpSize = SkTMin(state.numLeft(), GrResourceProvider::MaxNumNonAAQuads());
state.createOp(set, clumpSize, aaType);
}
} else {
// kCoverage can be downgraded to kNone. Note that the following is conservative. kCoverage
// can also get downgraded to kNone if all the quads are on integer coordinates and
// axis-aligned.
SkASSERT(aaType == GrAAType::kCoverage);
while (state.numLeft() > 0) {
GrAAType runningAA = GrAAType::kNone;
bool clumped = false;
for (int i = 0; i < state.numLeft(); ++i) {
int absIndex = state.baseIndex() + i;
if (set[absIndex].fAAFlags != GrQuadAAFlags::kNone) {
if (i >= GrResourceProvider::MaxNumAAQuads()) {
// Here we either need to boost the AA type to kCoverage, but doing so with
// all the accumulated quads would overflow, or we have a set of AA quads
// that has just gotten too large. In either case, calve off the existing
// quads as their own TextureOp.
state.createOp(
set,
runningAA == GrAAType::kNone ? i : GrResourceProvider::MaxNumAAQuads(),
runningAA); // maybe downgrading AA here
clumped = true;
break;
}
runningAA = GrAAType::kCoverage;
} else if (runningAA == GrAAType::kNone) {
if (i >= GrResourceProvider::MaxNumNonAAQuads()) {
// Here we've found a consistent batch of non-AA quads that has gotten too
// large. Calve it off as its own GrTextureOp.
state.createOp(set, GrResourceProvider::MaxNumNonAAQuads(),
GrAAType::kNone); // definitely downgrading AA here
clumped = true;
break;
}
}
}
if (!clumped) {
// We ran through the above loop w/o hitting a limit. Spit out this last clump of
// quads and call it a day.
state.createOp(set, state.numLeft(), runningAA); // maybe downgrading AA here
}
}
}
}
#if GR_TEST_UTILS
#include "include/private/GrRecordingContext.h"

View File

@ -20,49 +20,59 @@ class GrTextureProxy;
struct SkRect;
class SkMatrix;
namespace GrTextureOp {
class GrTextureOp {
public:
/**
* Controls whether saturate() is called after the texture is color-converted to ensure all
* color values are in 0..1 range.
*/
enum class Saturate : bool { kNo = false, kYes = true };
/**
* Controls whether saturate() is called after the texture is color-converted to ensure all
* color values are in 0..1 range.
*/
enum class Saturate : bool { kNo = false, kYes = true };
/**
* Creates an op that draws a sub-quadrilateral of a texture. The passed color is modulated by the
* texture's color. 'deviceQuad' specifies the device-space coordinates to draw, using 'localQuad'
* to map into the proxy's texture space. If non-null, 'domain' represents the boundary for the
* strict src rect constraint. If GrAAType is kCoverage then AA is applied to the edges
* indicated by GrQuadAAFlags. Otherwise, GrQuadAAFlags is ignored.
*
* This is functionally very similar to GrFillRectOp::Make, except that the GrPaint has been
* deconstructed into the texture, filter, modulating color, and blend mode. When blend mode is
* src over, this will return a GrFillRectOp with a paint that samples the proxy.
*/
std::unique_ptr<GrDrawOp> Make(GrRecordingContext*,
GrSurfaceProxyView,
GrColorType srcColorType,
sk_sp<GrColorSpaceXform>,
GrSamplerState::Filter,
const SkPMColor4f&,
Saturate,
SkBlendMode,
GrAAType,
GrQuadAAFlags,
const GrQuad& deviceQuad,
const GrQuad& localQuad,
const SkRect* domain = nullptr);
/**
* Creates an op that draws a sub-quadrilateral of a texture. The passed color is modulated by
* the texture's color. 'deviceQuad' specifies the device-space coordinates to draw, using
* 'localQuad' to map into the proxy's texture space. If non-null, 'domain' represents the
* boundary for the strict src rect constraint. If GrAAType is kCoverage then AA is applied to
* the edges indicated by GrQuadAAFlags. Otherwise, GrQuadAAFlags is ignored.
*
* This is functionally very similar to GrFillRectOp::Make, except that the GrPaint has been
* deconstructed into the texture, filter, modulating color, and blend mode. When blend mode is
* src over, this will return a GrFillRectOp with a paint that samples the proxy.
*/
static std::unique_ptr<GrDrawOp> Make(GrRecordingContext*,
GrSurfaceProxyView,
GrColorType srcColorType,
sk_sp<GrColorSpaceXform>,
GrSamplerState::Filter,
const SkPMColor4f&,
Saturate,
SkBlendMode,
GrAAType,
GrQuadAAFlags,
const GrQuad& deviceQuad,
const GrQuad& localQuad,
const SkRect* domain = nullptr);
// Unlike the single-proxy factory, this only supports src-over blending.
std::unique_ptr<GrDrawOp> MakeSet(GrRecordingContext*,
const GrRenderTargetContext::TextureSetEntry[],
int cnt,
GrSamplerState::Filter,
Saturate,
GrAAType,
SkCanvas::SrcRectConstraint,
const SkMatrix& viewMatrix,
sk_sp<GrColorSpaceXform> textureXform);
// Unlike the single-proxy factory, this only supports src-over blending.
static void CreateTextureSetOps(GrRenderTargetContext*,
const GrClip& clip,
GrRecordingContext*,
const GrRenderTargetContext::TextureSetEntry[],
int cnt,
GrSamplerState::Filter,
Saturate,
GrAAType,
SkCanvas::SrcRectConstraint,
const SkMatrix& viewMatrix,
sk_sp<GrColorSpaceXform> textureXform);
#if GR_TEST_UTILS
static uint32_t ClassID();
#endif
private:
class BatchSizeLimiter;
};
}
#endif // GrTextureOp_DEFINED

147
tests/BulkRectTest.cpp Normal file
View File

@ -0,0 +1,147 @@
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/GrClip.h"
#include "src/gpu/GrContextPriv.h"
#include "src/gpu/GrMemoryPool.h"
#include "src/gpu/GrRenderTargetContext.h"
#include "src/gpu/ops/GrTextureOp.h"
#include "tests/Test.h"
static std::unique_ptr<GrRenderTargetContext> new_RTC(GrContext* context) {
return context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, 128, 128,
GrColorType::kRGBA_8888, nullptr);
}
sk_sp<GrSurfaceProxy> create_proxy(GrContext* context) {
GrSurfaceDesc desc;
desc.fConfig = kRGBA_8888_GrPixelConfig;
desc.fWidth = 128;
desc.fHeight = 128;
const GrBackendFormat format = context->priv().caps()->getDefaultBackendFormat(
GrColorType::kRGBA_8888,
GrRenderable::kYes);
return context->priv().proxyProvider()->createProxy(
format, desc, GrRenderable::kYes, 1, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
SkBackingFit::kExact, SkBudgeted::kNo, GrProtected::kNo, GrInternalSurfaceFlags::kNone);
}
typedef GrQuadAAFlags (*GimmeSomeAAFunc)(int i);
static void bulk_rect_create_test(skiatest::Reporter* reporter, GrContext* context,
GimmeSomeAAFunc gimmeSomeAA, GrAAType overallAA,
int requestedTotNumQuads, int expectedNumOps) {
std::unique_ptr<GrRenderTargetContext> rtc = new_RTC(context);
sk_sp<GrSurfaceProxy> proxy = create_proxy(context);
GrSurfaceProxyView proxyView(std::move(proxy), kTopLeft_GrSurfaceOrigin, GrSwizzle::RGBA());
auto set = new GrRenderTargetContext::TextureSetEntry[requestedTotNumQuads];
for (int i = 0; i < requestedTotNumQuads; ++i) {
set[i].fProxyView = proxyView;
set[i].fSrcColorType = GrColorType::kRGBA_8888;
set[i].fSrcRect = SkRect::MakeWH(100.0f, 100.0f);
set[i].fDstRect = SkRect::MakeWH(100.5f, 100.5f); // prevent the int non-AA optimization
set[i].fDstClipQuad = nullptr;
set[i].fPreViewMatrix = nullptr;
set[i].fAlpha = 1.0f;
set[i].fAAFlags = gimmeSomeAA(i);
}
GrTextureOp::CreateTextureSetOps(rtc.get(), GrNoClip(), context, set, requestedTotNumQuads,
GrSamplerState::Filter::kNearest,
GrTextureOp::Saturate::kYes,
overallAA,
SkCanvas::kStrict_SrcRectConstraint,
SkMatrix::I(), nullptr);
GrOpsTask* opsTask = rtc->testingOnly_PeekLastOpsTask();
int actualNumOps = opsTask->numOpChains();
int actualTotNumQuads = 0;
for (int i = 0; i < actualNumOps; ++i) {
const GrOp* tmp = opsTask->getChain(i);
REPORTER_ASSERT(reporter, tmp->classID() == GrTextureOp::ClassID());
REPORTER_ASSERT(reporter, tmp->isChainTail());
actualTotNumQuads += ((GrDrawOp*) tmp)->numQuads();
}
REPORTER_ASSERT(reporter, expectedNumOps == actualNumOps);
REPORTER_ASSERT(reporter, requestedTotNumQuads == actualTotNumQuads);
context->flush();
delete[] set;
}
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(BulkRect, reporter, ctxInfo) {
GrContext* context = ctxInfo.grContext();
if (!context->priv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) {
return;
}
// This is the simple case where there is no AA at all. We expect 2 non-AA clumps of quads.
{
auto noAA = [](int i) -> GrQuadAAFlags {
return GrQuadAAFlags::kNone;
};
static const int kNumExpectedOps = 2;
bulk_rect_create_test(reporter, context, noAA, GrAAType::kNone,
2*GrResourceProvider::MaxNumNonAAQuads(), kNumExpectedOps);
}
// This is the same as the above case except the overall AA is kCoverage. However, since
// the per-quad AA is still none, all the quads should be downgraded to non-AA.
{
auto noAA = [](int i) -> GrQuadAAFlags {
return GrQuadAAFlags::kNone;
};
static const int kNumExpectedOps = 2;
bulk_rect_create_test(reporter, context, noAA, GrAAType::kCoverage,
2*GrResourceProvider::MaxNumNonAAQuads(), kNumExpectedOps);
}
// This case has an overall AA of kCoverage but the per-quad AA alternates.
// We should end up with several aa-sized clumps
{
auto alternateAA = [](int i) -> GrQuadAAFlags {
return (i % 2) ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
};
int numExpectedOps = 2*GrResourceProvider::MaxNumNonAAQuads() /
GrResourceProvider::MaxNumAAQuads();
bulk_rect_create_test(reporter, context, alternateAA, GrAAType::kCoverage,
2*GrResourceProvider::MaxNumNonAAQuads(), numExpectedOps);
}
// In this case we have a run of MaxNumAAQuads non-AA quads and then AA quads. This
// exercises the case where we have a clump of quads that can't be upgraded to AA bc of
// its size. We expect one clump of non-AA quads followed by one clump of AA quads.
{
auto runOfNonAA = [](int i) -> GrQuadAAFlags {
return (i < GrResourceProvider::MaxNumAAQuads()) ? GrQuadAAFlags::kNone
: GrQuadAAFlags::kAll;
};
static const int kNumExpectedOps = 2;
bulk_rect_create_test(reporter, context, runOfNonAA, GrAAType::kCoverage,
2*GrResourceProvider::MaxNumAAQuads(), kNumExpectedOps);
}
}