diff --git a/gm/atlastext.cpp b/gm/atlastext.cpp index 832a604e6e..ac5b3e4582 100644 --- a/gm/atlastext.cpp +++ b/gm/atlastext.cpp @@ -49,7 +49,8 @@ static SkScalar draw_string(SkAtlasTextTarget* target, const SkString& text, SkS target->drawText(glyphs.get(), positions.get(), cnt, color, *font); - return positions[cnt - 1].fX + widths[cnt - 1]; + // Return the width of the of draw. + return positions[cnt - 1].fX + widths[cnt - 1] - positions[0].fX; } class AtlasTextGM : public skiagm::GM { @@ -109,12 +110,30 @@ private: auto size = 2 * s; for (const auto& typeface : fTypefaces) { for (const auto& text : kTexts) { - uint32_t color = random.nextU(); - x = size + draw_string(fTarget.get(), text, x, y, color, typeface, size); + // Choose a random color but don't let alpha be too small to see. + uint32_t color = random.nextU() | 0x40000000; + fTarget->save(); + // Randomly add a little bit of perspective + if (random.nextBool()) { + SkMatrix persp; + persp.reset(); + persp.setPerspY(0.0005f); + persp.preTranslate(-x, -y + s); + persp.postTranslate(x, y - s); + fTarget->concat(persp); + } + // Randomly switch between positioning with a matrix vs x, y passed to draw. + SkScalar drawX = x, drawY = y; + if (random.nextBool()) { + fTarget->translate(x, y); + drawX = drawY = 0; + } + x += size + + draw_string(fTarget.get(), text, drawX, drawY, color, typeface, size); x = SkScalarCeilToScalar(x); - // Flush periodically to test continued drawing after a flush. Using color - // to avoid churning the RNG and having to rebaseline images. - if (!(color & 0xf)) { + fTarget->restore(); + // Flush periodically to test continued drawing after a flush. + if ((random.nextU() % 8) == 0) { fTarget->flush(); } if (x + 100 > kSize) { diff --git a/include/atlastext/SkAtlasTextRenderer.h b/include/atlastext/SkAtlasTextRenderer.h index a78e270edb..f5eb5240c9 100644 --- a/include/atlastext/SkAtlasTextRenderer.h +++ b/include/atlastext/SkAtlasTextRenderer.h @@ -5,7 +5,7 @@ * found in the LICENSE file. */ -#include "SkPoint.h" +#include "SkPoint3.h" #include "SkRefCnt.h" #ifndef SkAtlasTextRenderer_DEFINED @@ -25,8 +25,8 @@ public: }; struct SDFVertex { - /** Position in device space (not normalized). */ - SkPoint fPosition; + /** Position in device space (not normalized). The third component is w (not z). */ + SkPoint3 fPosition; /** Color, same value for all four corners of a glyph quad. */ uint32_t fColor; /** Texture coordinate (in texel units, not normalized). */ diff --git a/include/atlastext/SkAtlasTextTarget.h b/include/atlastext/SkAtlasTextTarget.h index ff879617d8..c29f381dc8 100644 --- a/include/atlastext/SkAtlasTextTarget.h +++ b/include/atlastext/SkAtlasTextTarget.h @@ -9,11 +9,13 @@ #define SkAtlasTextTarget_DEFINED #include +#include "SkDeque.h" #include "SkRefCnt.h" #include "SkScalar.h" class SkAtlasTextContext; class SkAtlasTextFont; +class SkMatrix; struct SkPoint; /** Represents a client-created renderable surface and is used to draw text into the surface. */ @@ -46,15 +48,47 @@ public: SkAtlasTextContext* context() const { return fContext.get(); } + /** Saves the current matrix in a stack. Returns the prior depth of the saved matrix stack. */ + int save(); + /** Pops the top matrix on the stack if the stack is not empty. */ + void restore(); + /** + * Pops the matrix stack until the stack depth is count. Does nothing if the depth is already + * less than count. + */ + void restoreToCount(int count); + + /** Pre-translates the current CTM. */ + void translate(SkScalar dx, SkScalar dy); + /** Pre-scales the current CTM. */ + void scale(SkScalar sx, SkScalar sy); + /** Pre-rotates the current CTM about the origin. */ + void rotate(SkScalar degrees); + /** Pre-rotates the current CTM about the (px, py). */ + void rotate(SkScalar degrees, SkScalar px, SkScalar py); + /** Pre-skews the current CTM. */ + void skew(SkScalar sx, SkScalar sy); + /** Pre-concats the current CTM. */ + void concat(const SkMatrix& matrix); + protected: SkAtlasTextTarget(sk_sp, int width, int height, void* handle); + const SkMatrix& ctm() const { return *static_cast(fMatrixStack.back()); } + void* const fHandle; const sk_sp fContext; const int fWidth; const int fHeight; private: + SkDeque fMatrixStack; + int fSaveCnt; + + SkMatrix* accessCTM() const { + return static_cast(const_cast(fMatrixStack.back())); + } + SkAtlasTextTarget() = delete; SkAtlasTextTarget(const SkAtlasTextContext&) = delete; SkAtlasTextTarget& operator=(const SkAtlasTextContext&) = delete; diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h index 41872be78a..2640d51e1b 100644 --- a/include/gpu/GrContextOptions.h +++ b/include/gpu/GrContextOptions.h @@ -187,6 +187,14 @@ struct GrContextOptions { */ bool fDisableImageMultitexturing = false; #endif + +#if SK_SUPPORT_ATLAS_TEXT + /** + * Controls whether distance field glyph vertices always have 3 components even when the view + * matrix does not have perspective. + */ + Enable fDistanceFieldGlyphVerticesAlwaysHaveW = Enable::kDefault; +#endif }; #endif diff --git a/src/atlastext/SkAtlasTextTarget.cpp b/src/atlastext/SkAtlasTextTarget.cpp index 81dbb07a16..0a318e0c8e 100644 --- a/src/atlastext/SkAtlasTextTarget.cpp +++ b/src/atlastext/SkAtlasTextTarget.cpp @@ -21,10 +21,53 @@ static constexpr int kMaxBatchLookBack = 10; SkAtlasTextTarget::SkAtlasTextTarget(sk_sp context, int width, int height, void* handle) - : fHandle(handle), fContext(std::move(context)), fWidth(width), fHeight(height) {} + : fHandle(handle) + , fContext(std::move(context)) + , fWidth(width) + , fHeight(height) + , fMatrixStack(sizeof(SkMatrix), 4) + , fSaveCnt(0) { + fMatrixStack.push_back(); + this->accessCTM()->reset(); +} SkAtlasTextTarget::~SkAtlasTextTarget() { fContext->renderer()->targetDeleted(fHandle); } +int SkAtlasTextTarget::save() { + const auto& currCTM = this->ctm(); + *static_cast(fMatrixStack.push_back()) = currCTM; + return fSaveCnt++; +} + +void SkAtlasTextTarget::restore() { + if (fSaveCnt) { + fMatrixStack.pop_back(); + fSaveCnt--; + } +} + +void SkAtlasTextTarget::restoreToCount(int count) { + while (fSaveCnt > count) { + this->restore(); + } +} + +void SkAtlasTextTarget::translate(SkScalar dx, SkScalar dy) { + this->accessCTM()->preTranslate(dx, dy); +} + +void SkAtlasTextTarget::scale(SkScalar sx, SkScalar sy) { this->accessCTM()->preScale(sx, sy); } + +void SkAtlasTextTarget::rotate(SkScalar degrees) { this->accessCTM()->preRotate(degrees); } + +void SkAtlasTextTarget::rotate(SkScalar degrees, SkScalar px, SkScalar py) { + this->accessCTM()->preRotate(degrees, px, py); +} + +void SkAtlasTextTarget::skew(SkScalar sx, SkScalar sy) { this->accessCTM()->preSkew(sx, sy); } + +void SkAtlasTextTarget::concat(const SkMatrix& matrix) { this->accessCTM()->preConcat(matrix); } + ////////////////////////////////////////////////////////////////////////////// static const GrColorSpaceInfo kColorSpaceInfo(nullptr, kRGBA_8888_GrPixelConfig); @@ -95,7 +138,7 @@ void SkInternalAtlasTextTarget::drawText(const SkGlyphID glyphs[], const SkPoint auto atlasTextContext = grContext->contextPriv().drawingManager()->getAtlasTextContext(); size_t byteLength = sizeof(SkGlyphID) * glyphCnt; const SkScalar* pos = &positions->fX; - atlasTextContext->drawPosText(grContext, this, GrNoClip(), paint, SkMatrix::I(), props, + atlasTextContext->drawPosText(grContext, this, GrNoClip(), paint, this->ctm(), props, (const char*)glyphs, byteLength, pos, 2, {0, 0}, bounds); } @@ -149,7 +192,8 @@ void GrAtlasTextOp::executeForTextTarget(SkAtlasTextTarget* target) { GrAtlasTextBlob::VertexRegenerator::Result result; do { result = regenerator.regenerate(); - context.recordDraw(result.fFirstVertex, result.fGlyphsRegenerated, target->handle()); + context.recordDraw(result.fFirstVertex, result.fGlyphsRegenerated, + fGeoData[i].fViewMatrix, target->handle()); if (!result.fFinished) { // Make space in the atlas so we can continue generating vertices. context.flush(); diff --git a/src/atlastext/SkInternalAtlasTextContext.cpp b/src/atlastext/SkInternalAtlasTextContext.cpp index 858ae4da01..6dde9ae096 100644 --- a/src/atlastext/SkInternalAtlasTextContext.cpp +++ b/src/atlastext/SkInternalAtlasTextContext.cpp @@ -30,6 +30,7 @@ SkInternalAtlasTextContext::SkInternalAtlasTextContext(sk_sp(vertexDataSize); memcpy(vertexData, srcVertexData, vertexDataSize); @@ -72,6 +73,7 @@ void SkInternalAtlasTextContext::recordDraw(const void* srcVertexData, int glyph // This isn't expected by SkAtlasTextRenderer subclasses. vertex->fTextureCoord.fX /= 2; vertex->fTextureCoord.fY /= 2; + matrix.mapHomogeneousPoints(&vertex->fPosition, &vertex->fPosition, 1); } fDraws.append(&fArena, Draw{glyphCnt, this->issueDrawToken(), targetHandle, vertexData}); } diff --git a/src/atlastext/SkInternalAtlasTextContext.h b/src/atlastext/SkInternalAtlasTextContext.h index 1bb12cee5a..ea89435580 100644 --- a/src/atlastext/SkInternalAtlasTextContext.h +++ b/src/atlastext/SkInternalAtlasTextContext.h @@ -14,6 +14,7 @@ #include "SkRefCnt.h" class SkAtlasTextRenderer; +class SkMatrix; class GrContext; class GrAtlasGlyphCache; class GrTextBlobCache; @@ -38,7 +39,7 @@ public: GrDeferredUploadToken addASAPUpload(GrDeferredTextureUploadFn&&) override; - void recordDraw(const void* vertexData, int glyphCnt, void* targetHandle); + void recordDraw(const void* vertexData, int glyphCnt, const SkMatrix&, void* targetHandle); void flush(); diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index c410874fd9..9240d63b4a 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -234,6 +234,12 @@ bool GrContext::init(const GrContextOptions& options) { GrAtlasTextContext::Options atlasTextContextOptions; atlasTextContextOptions.fMaxDistanceFieldFontSize = options.fGlyphsAsPathsFontSize; atlasTextContextOptions.fMinDistanceFieldFontSize = options.fMinDistanceFieldFontSize; + atlasTextContextOptions.fDistanceFieldVerticesAlwaysHaveW = false; +#if SK_SUPPORT_ATLAS_TEXT + if (GrContextOptions::Enable::kYes == options.fDistanceFieldGlyphVerticesAlwaysHaveW) { + atlasTextContextOptions.fDistanceFieldVerticesAlwaysHaveW = true; + } +#endif fDrawingManager.reset( new GrDrawingManager(this, prcOptions, atlasTextContextOptions, &fSingleOwner)); diff --git a/src/gpu/text/GrAtlasTextContext.cpp b/src/gpu/text/GrAtlasTextContext.cpp index 3faa0f7b87..73a27100d3 100644 --- a/src/gpu/text/GrAtlasTextContext.cpp +++ b/src/gpu/text/GrAtlasTextContext.cpp @@ -39,6 +39,7 @@ GrAtlasTextContext::GrAtlasTextContext(const Options& options) fMinDistanceFieldFontSize = options.fMinDistanceFieldFontSize < 0.f ? kDefaultMinDistanceFieldFontSize : options.fMinDistanceFieldFontSize; + fDistanceFieldVerticesAlwaysHaveW = options.fDistanceFieldVerticesAlwaysHaveW; } std::unique_ptr GrAtlasTextContext::Make(const Options& options) { @@ -646,7 +647,8 @@ void GrAtlasTextContext::drawDFPosText(GrAtlasTextBlob* blob, int runIndex, SkTDArray fallbackTxt; SkTDArray fallbackPos; - bool hasWCoord = viewMatrix.hasPerspective(); + bool hasWCoord = viewMatrix.hasPerspective() || fDistanceFieldVerticesAlwaysHaveW; + // Setup distance field paint and text ratio SkScalar textRatio; SkPaint dfPaint(paint); diff --git a/src/gpu/text/GrAtlasTextContext.h b/src/gpu/text/GrAtlasTextContext.h index 8db0d7e050..52c8dfcb87 100644 --- a/src/gpu/text/GrAtlasTextContext.h +++ b/src/gpu/text/GrAtlasTextContext.h @@ -38,6 +38,8 @@ public: * be rendered from outline as individual paths. Negative means use a default value. */ SkScalar fMaxDistanceFieldFontSize = -1.f; + /** Forces all distance field vertices to use 3 components, not just when in perspective. */ + bool fDistanceFieldVerticesAlwaysHaveW = false; }; static std::unique_ptr Make(const Options& options); @@ -142,6 +144,7 @@ private: SkScalar fMinDistanceFieldFontSize; SkScalar fMaxDistanceFieldFontSize; + bool fDistanceFieldVerticesAlwaysHaveW; #if GR_TEST_UTILS static const uint32_t kTextBlobOpScalerContextFlags = diff --git a/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp b/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp index ef416b2e39..677ce5a924 100644 --- a/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp +++ b/tools/gpu/atlastext/GLTestAtlasTextRenderer.cpp @@ -125,7 +125,7 @@ GLTestAtlasTextRenderer::GLTestAtlasTextRenderer(std::unique_ptr uniform vec4 uDstScaleAndTranslate; uniform vec2 uAtlasInvSize; - layout (location = 0) in vec2 inPosition; + layout (location = 0) in vec3 inPosition; layout (location = 1) in vec4 inColor; layout (location = 2) in uvec2 inTextureCoords; @@ -143,7 +143,7 @@ GLTestAtlasTextRenderer::GLTestAtlasTextRenderer(std::unique_ptr vColor = inColor; gl_Position = vec4(inPosition.x * uDstScaleAndTranslate.x + uDstScaleAndTranslate.y, inPosition.y * uDstScaleAndTranslate.z + uDstScaleAndTranslate.w, - 0.0, 1.0); + 0.0, inPosition.z); } )"; strings[1] = kVS; @@ -360,8 +360,8 @@ void GLTestAtlasTextRenderer::drawSDFGlyphs(void* targetHandle, void* textureHan callgl(BindVertexArray, 0); callgl(BindBuffer, GR_GL_ARRAY_BUFFER, 0); callgl(BindBuffer, GR_GL_ELEMENT_ARRAY_BUFFER, 0); - callgl(VertexAttribPointer, 0, 2, GR_GL_FLOAT, GR_GL_FALSE, sizeof(SDFVertex), vertices); - size_t colorOffset = 2 * sizeof(float); + callgl(VertexAttribPointer, 0, 3, GR_GL_FLOAT, GR_GL_FALSE, sizeof(SDFVertex), vertices); + size_t colorOffset = 3 * sizeof(float); callgl(VertexAttribPointer, 1, 4, GR_GL_UNSIGNED_BYTE, GR_GL_TRUE, sizeof(SDFVertex), reinterpret_cast(vertices) + colorOffset); size_t texOffset = colorOffset + sizeof(uint32_t);