Handle insane number of glyphs in GrAtlasTextOp.

Cap size of draws by vertex buffer size.

Remove size limit in combining since we now enforce a limit
when preparing draws. (This limit wasn't effective in bug
repro scenario as the large number of glyphs are in a single
run).

Fix another bug discovered along the way: We increase the
number of proxies in a FixedDynamicState after having recorded
draws. However, the draws take refs on the proxies in FDS and
unref them when destroyed. By adding the proxies late we cause
the ref count to underflow. (This needs a more systematic fix
outside the scope of this change).

Bug: chromium:1018511
Change-Id: I79fe446d97c573196653a18792462bd46b9ac4c6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/262001
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
This commit is contained in:
Brian Salomon 2020-01-03 22:09:12 -05:00 committed by Skia Commit-Bot
parent c0724ec00a
commit 43cbd7229f
4 changed files with 100 additions and 69 deletions

View File

@ -326,12 +326,14 @@ void GrAtlasTextOp::onPrepareDraws(Target* target) {
samplerState, maskFormat, localMatrix, vmPerspective);
}
flushInfo.fGlyphsToFlush = 0;
size_t vertexStride = flushInfo.fGeometryProcessor->vertexStride();
int glyphCount = this->numGlyphs();
void* vertices = target->makeVertexSpace(vertexStride, glyphCount * kVerticesPerGlyph,
// Ensure we don't request an insanely large contiguous vertex allocation.
static const size_t kMaxVertexBytes = GrBufferAllocPool::kDefaultBufferSize;
int maxGlyphsInBuffer = kMaxVertexBytes / (vertexStride * kVerticesPerGlyph);
int totalGlyphCount = this->numGlyphs();
int bufferGlyphCount = std::min(totalGlyphCount, maxGlyphsInBuffer);
void* vertices = target->makeVertexSpace(vertexStride, bufferGlyphCount * kVerticesPerGlyph,
&flushInfo.fVertexBuffer, &flushInfo.fVertexOffset);
flushInfo.fIndexBuffer = resourceProvider->refNonAAQuadIndexBuffer();
if (!vertices || !flushInfo.fVertexBuffer) {
@ -349,46 +351,65 @@ void GrAtlasTextOp::onPrepareDraws(Target* target) {
resourceProvider, args.fSubRunPtr, args.fDrawMatrix, args.fDrawOrigin,
args.fColor.toBytes_RGBA(), target->deferredUploadTarget(), glyphCache,
atlasManager);
bool done = false;
while (!done) {
// This loop issues draws until regenerator says we're done with this geo. Regenerator
// breaks things up if inline uploads are necessary.
while (true) {
GrTextBlob::VertexRegenerator::Result result;
if (!regenerator.regenerate(&result)) {
// Copy regenerated vertices from the blob to our vertex buffer. If we overflow our
// vertex buffer we'll issue a draw and then get more vertex buffer space.
do {
if (!bufferGlyphCount) {
this->flush(target, &flushInfo);
bufferGlyphCount = std::min(totalGlyphCount, maxGlyphsInBuffer);
vertices = target->makeVertexSpace(
vertexStride, bufferGlyphCount * kVerticesPerGlyph,
&flushInfo.fVertexBuffer, &flushInfo.fVertexOffset);
currVertex = reinterpret_cast<char*>(vertices);
}
if (!regenerator.regenerate(&result, bufferGlyphCount)) {
return;
}
int glyphCount = std::min(result.fGlyphsRegenerated, bufferGlyphCount);
int vertexCount = glyphCount * kVerticesPerGlyph;
size_t vertexBytes = vertexCount * vertexStride;
if (args.fClipRect.isEmpty()) {
memcpy(currVertex, result.fFirstVertex, vertexBytes);
} else {
SkASSERT(!vmPerspective);
clip_quads(args.fClipRect, currVertex, result.fFirstVertex, vertexStride,
glyphCount);
}
if (fNeedsGlyphTransform && !args.fDrawMatrix.isIdentity()) {
// We always do the distance field view matrix transformation after copying
// rather than during blob vertex generation time in the blob as handling
// successive arbitrary transformations would be complicated and accumulate
// error.
if (args.fDrawMatrix.hasPerspective()) {
auto* pos = reinterpret_cast<SkPoint3*>(currVertex);
SkMatrixPriv::MapHomogeneousPointsWithStride(args.fDrawMatrix, pos,
vertexStride, pos,
vertexStride, vertexCount);
} else {
auto* pos = reinterpret_cast<SkPoint*>(currVertex);
SkMatrixPriv::MapPointsWithStride(args.fDrawMatrix, pos, vertexStride,
vertexCount);
}
}
flushInfo.fGlyphsToFlush += glyphCount;
currVertex += vertexBytes;
result.fFirstVertex += vertexBytes;
result.fGlyphsRegenerated -= glyphCount;
bufferGlyphCount -= glyphCount;
totalGlyphCount -= glyphCount;
} while (result.fGlyphsRegenerated);
if (result.fFinished) {
break;
}
done = result.fFinished;
// Copy regenerated vertices from the blob to our vertex buffer.
size_t vertexBytes = result.fGlyphsRegenerated * kVerticesPerGlyph * vertexStride;
if (args.fClipRect.isEmpty()) {
memcpy(currVertex, result.fFirstVertex, vertexBytes);
} else {
SkASSERT(!vmPerspective);
clip_quads(args.fClipRect, currVertex, result.fFirstVertex, vertexStride,
result.fGlyphsRegenerated);
}
if (fNeedsGlyphTransform && !args.fDrawMatrix.isIdentity()) {
// We always do the distance field view matrix transformation after copying rather
// than during blob vertex generation time in the blob as handling successive
// arbitrary transformations would be complicated and accumulate error.
if (args.fDrawMatrix.hasPerspective()) {
auto* pos = reinterpret_cast<SkPoint3*>(currVertex);
SkMatrixPriv::MapHomogeneousPointsWithStride(
args.fDrawMatrix, pos, vertexStride, pos, vertexStride,
result.fGlyphsRegenerated * kVerticesPerGlyph);
} else {
auto* pos = reinterpret_cast<SkPoint*>(currVertex);
SkMatrixPriv::MapPointsWithStride(
args.fDrawMatrix, pos, vertexStride,
result.fGlyphsRegenerated * kVerticesPerGlyph);
}
}
flushInfo.fGlyphsToFlush += result.fGlyphsRegenerated;
if (!result.fFinished) {
this->flush(target, &flushInfo);
}
currVertex += vertexBytes;
this->flush(target, &flushInfo);
}
}
SkASSERT(!bufferGlyphCount);
SkASSERT(!totalGlyphCount);
this->flush(target, &flushInfo);
}
@ -425,6 +446,10 @@ void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) co
// This op does not know its atlas proxies when it is added to a GrOpsTasks, so the
// proxies don't get added during the visitProxies call. Thus we add them here.
target->sampledProxyArray()->push_back(views[i].proxy());
// These will get unreffed when the previously recorded draws destruct.
for (int d = 0; d < flushInfo->fNumDraws; ++d) {
flushInfo->fFixedDynamicState->fPrimitiveProcessorTextures[i]->ref();
}
}
if (this->usesDistanceFields()) {
if (this->isLCD()) {
@ -450,6 +475,7 @@ void GrAtlasTextOp::flush(GrMeshDrawOp::Target* target, FlushInfo* flushInfo) co
nullptr, GrPrimitiveType::kTriangles);
flushInfo->fVertexOffset += kVerticesPerGlyph * flushInfo->fGlyphsToFlush;
flushInfo->fGlyphsToFlush = 0;
++flushInfo->fNumDraws;
}
GrOp::CombineResult GrAtlasTextOp::onCombineIfPossible(GrOp* t, GrRecordingContext::Arenas*,
@ -493,14 +519,6 @@ GrOp::CombineResult GrAtlasTextOp::onCombineIfPossible(GrOp* t, GrRecordingConte
}
}
// Keep the batch vertex buffer size below 32K so we don't have to create a special one
// We use the largest possible vertex size for this
static const int kVertexSize = sizeof(SkPoint) + sizeof(SkColor) + 2 * sizeof(uint16_t);
static const int kMaxGlyphs = 32768 / (kVerticesPerGlyph * kVertexSize);
if (this->fNumGlyphs + that->fNumGlyphs > kMaxGlyphs) {
return CombineResult::kCannotCombine;
}
fNumGlyphs += that->numGlyphs();
// Reallocate space for geo data if necessary and then import that geo's data.

View File

@ -106,8 +106,9 @@ private:
sk_sp<const GrBuffer> fIndexBuffer;
GrGeometryProcessor* fGeometryProcessor;
GrPipeline::FixedDynamicState* fFixedDynamicState;
int fGlyphsToFlush;
int fVertexOffset;
int fGlyphsToFlush = 0;
int fVertexOffset = 0;
int fNumDraws = 0;
};
void onPrepareDraws(Target*) override;

View File

@ -872,7 +872,8 @@ GrTextBlob::VertexRegenerator::VertexRegenerator(GrResourceProvider* resourcePro
fSubRun->translateVerticesIfNeeded(drawMatrix, drawOrigin);
}
bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Result* result) {
bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Result* result,
int maxGlyphs) {
SkASSERT(!fActions.regenStrike || fActions.regenTextureCoordinates);
if (fActions.regenTextureCoordinates) {
fSubRun->resetBulkUseToken();
@ -907,8 +908,12 @@ bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Resul
auto vertexStride = fSubRun->vertexStride();
char* currVertex = fSubRun->fVertexData.data() + fCurrGlyph * kVerticesPerGlyph * vertexStride;
result->fFirstVertex = currVertex;
for (; fCurrGlyph < (int)fSubRun->fGlyphs.size(); fCurrGlyph++) {
int glyphLimit = (int)fSubRun->fGlyphs.size();
if (glyphLimit > fCurrGlyph + maxGlyphs) {
glyphLimit = fCurrGlyph + maxGlyphs;
result->fFinished = false;
}
for (; fCurrGlyph < glyphLimit; fCurrGlyph++) {
if (fActions.regenTextureCoordinates) {
GrGlyph* glyph = fSubRun->fGlyphs[fCurrGlyph];
SkASSERT(glyph && glyph->fMaskFormat == fSubRun->maskFormat());
@ -956,7 +961,8 @@ bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Resul
return true;
}
bool GrTextBlob::VertexRegenerator::regenerate(GrTextBlob::VertexRegenerator::Result* result) {
bool GrTextBlob::VertexRegenerator::regenerate(GrTextBlob::VertexRegenerator::Result* result,
int maxGlyphs) {
uint64_t currentAtlasGen = fFullAtlasManager->atlasGeneration(fSubRun->maskFormat());
// If regenerate() is called multiple times then the atlas gen may have changed. So we check
// this each time.
@ -964,25 +970,29 @@ bool GrTextBlob::VertexRegenerator::regenerate(GrTextBlob::VertexRegenerator::Re
if (fActions.regenStrike
|fActions.regenTextureCoordinates) {
return this->doRegen(result);
return this->doRegen(result, maxGlyphs);
} else {
auto vertexStride = fSubRun->vertexStride();
result->fFinished = true;
result->fGlyphsRegenerated = fSubRun->fGlyphs.size() - fCurrGlyph;
int glyphsLeft = fSubRun->fGlyphs.size() - fCurrGlyph;
if (glyphsLeft <= maxGlyphs) {
result->fFinished = true;
result->fGlyphsRegenerated = glyphsLeft;
} else {
result->fFinished = false;
result->fGlyphsRegenerated = maxGlyphs;
}
result->fFirstVertex = fSubRun->fVertexData.data() +
fCurrGlyph * kVerticesPerGlyph * vertexStride;
fCurrGlyph = fSubRun->fGlyphs.size();
fCurrGlyph += result->fGlyphsRegenerated;
// set use tokens for all of the glyphs in our subrun. This is only valid if we
// have a valid atlas generation
fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
fUploadTarget->tokenTracker()->nextDrawToken(),
fSubRun->maskFormat());
if (result->fFinished) {
// set use tokens for all of the glyphs in our subrun. This is only valid if we
// have a valid atlas generation
fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
fUploadTarget->tokenTracker()->nextDrawToken(),
fSubRun->maskFormat());
}
return true;
}
SK_ABORT("Should not get here");
}

View File

@ -24,6 +24,8 @@
#include "src/gpu/text/GrTextContext.h"
#include "src/gpu/text/GrTextTarget.h"
#include <limits>
class GrAtlasManager;
class GrAtlasTextOp;
struct GrDistanceFieldAdjustTable;
@ -314,13 +316,13 @@ public:
/**
* Pointer where the caller finds the first regenerated vertex.
*/
const char* fFirstVertex;
const char* fFirstVertex = nullptr;
};
bool regenerate(Result*);
bool regenerate(Result*, int maxGlyphs = std::numeric_limits<int>::max());
private:
bool doRegen(Result* result);
bool doRegen(Result* result, int maxGlyphs);
GrResourceProvider* fResourceProvider;
GrDeferredUploadTarget* fUploadTarget;