From 9c81e521715387629754556d38d33c05d7d6de2a Mon Sep 17 00:00:00 2001 From: Herb Derby Date: Mon, 9 Nov 2020 10:28:50 -0500 Subject: [PATCH] use initial device coordinates as subrun positions Originally, the positions had a bias of -initialOrigin, so all that was needed was to add the new drawingOrigin in. Store the actual device coordinates thus eliminating the -initialOrigin, and calculate the origin offset drawingOrigin - initialOrigin at vertex fill time. Change-Id: I5a7fe0074f0c7fd01c5fe90a47e67509631379fe Reviewed-on: https://skia-review.googlesource.com/c/skia/+/333127 Reviewed-by: Ben Wagner Commit-Queue: Herb Derby --- src/core/SkGlyphBuffer.cpp | 14 +++--- src/core/SkGlyphBuffer.h | 97 +++++-------------------------------- src/gpu/text/GrTextBlob.cpp | 32 ++++++------ 3 files changed, 34 insertions(+), 109 deletions(-) diff --git a/src/core/SkGlyphBuffer.cpp b/src/core/SkGlyphBuffer.cpp index 00e1955601..49d97042a6 100644 --- a/src/core/SkGlyphBuffer.cpp +++ b/src/core/SkGlyphBuffer.cpp @@ -75,6 +75,8 @@ SkPoint SkDrawableGlyphBuffer::startGPUDevice( fInputSize = source.size(); fDrawableSize = 0; + // Build up the mapping from source space to device space. Add the rounding constant + // halfSampleFreq so we just need to floor to get the device result. SkMatrix device = viewMatrix; SkPoint halfSampleFreq = roundingSpec.halfAxisSampleFreq; device.postTranslate(halfSampleFreq.x(), halfSampleFreq.y()); @@ -87,19 +89,19 @@ SkPoint SkDrawableGlyphBuffer::startGPUDevice( return {SkScalarFloorToScalar(pt.x()), SkScalarFloorToScalar(pt.y())}; }; - // q = [Q](0,0,1) = [R][V][O](0,0,1). - SkPoint q = device.mapXY(0, 0); - SkPoint qFloor = floor(q); + // Map the origin from source space to device space without the halfSampleFreq offset. + SkPoint originMappedToDevice = viewMatrix.mapXY(origin.x(), origin.y()); for (auto [packedGlyphID, glyphID, pos] : SkMakeZip(fMultiBuffer.get(), source.get<0>(), fPositions.get())) { packedGlyphID = SkPackedGlyphID{glyphID, pos, roundingSpec.ignorePositionFieldMask}; - pos = floor(pos - qFloor); + // Store rounded device coords back in pos. + pos = floor(pos); } SkDEBUGCODE(fPhase = kInput); - // Return the residual = Floor(q) - q + (rx,ry,0). - return qFloor - q + roundingSpec.halfAxisSampleFreq; + // Return the origin mapped through the initial matrix. + return originMappedToDevice; } diff --git a/src/core/SkGlyphBuffer.h b/src/core/SkGlyphBuffer.h index d48ec85fbd..cf3245966e 100644 --- a/src/core/SkGlyphBuffer.h +++ b/src/core/SkGlyphBuffer.h @@ -155,95 +155,20 @@ public: // Load the buffer with SkPackedGlyphIDs, calculating positions so they can be constant. // - // We are looking for constant values for the x,y positions for all the glyphs that are not - // dependant on the device origin mapping Q such that we can just add a new value to translate - // all the glyph positions to a new device origin mapping Q'. We want (cx,cy,0) + [Q'](0,0,1) - // draw the blob with device origin Q'. Ultimately we show there is an integer solution for - // the glyph positions where (ix,iy,0) + ([Q'](0,0,1) + (sx,sy,0)) both parts of the top - // level + are integers, and preserve all the flooring properties. + // The positions are calculated integer positions in devices space, and the mapping of the + // the source origin through the initial matrix is returned. It is given that these positions + // are only reused when the blob is translated by an integral amount. Thus the shifted + // positions are given by the following equation where (ix, iy) is the integer positions of + // the glyph, initialMappedOrigin is (0,0) in source mapped to the device using the initial + // matrix, and newMappedOrigin is (0,0) in source mapped to the device using the current + // drawing matrix. // - // Given (px,py) the glyph origin in source space. The glyph origin in device space (x,y) is: - // (x,y,1) = Floor([R][V][O](px,py,1)) - // where: - // * R - is the rounding matrix given as translate(sampling_freq_x/2, sampling_freq_y/2). - // * V - is the mapping from source space to device space. - // * O - is the blob origin given, as translate(origin.x(), origin.y()). - // * (px,py,1) - is the vector of the glyph origin in source space. There is a position for - // each glyph. + // (ix', iy') = (ix, iy) + round(newMappedOrigin - initialMappedOrigin) // - // It is given that if there is a change in position from V to V', and O to O' that the upper - // 2x2 of V and V' are the same. + // In theory, newMappedOrigin - initialMappedOrigin should be integer, but the vagaries of + // floating point don't guarantee that, so force it to integer. // - // The three matrices R,V, and O constitute the device mapping [Q] = [R][V][O], and the - // device origin is given by q = [Q](0,0,1). Thus, - // (x,y,1) = Floor([Q](0,0,1) + [V](px,py,0)) = Floor(q + [V](px,py,0)) - // Note: [V](px,py,0) is the vector transformed without the translation portion of V. That - // translation of V is accounted for in q. - // - // If we want to translate the blob from the device mapping Q to the device mapping - // [Q'] = [R'][V'][O], we can use the following translation. Restate as q' - q. - // (x',y',1) = Floor(q + [V](px,py,0) + q' - q). - // - // We are given that q' - q is an integer translation. We can move the integer translation out - // from the Floor expression as: - // (x',y',1) = Floor(q + [V](px,py,0)) + q' - q (1) - // - // We can now see that (cx,cy,0) is constructed by dropping q' from above. - // (cx,cy,0) = Floor(q + [V](px,py,0)) - q - // - // Notice that cx and cy are not guaranteed to be integers because q is not - // constrained to be integer; only q' - q is constrained to be an integer. - // - // Let Floor(q) be the integer portion the vector elements and {q} be the fractional portion - // which is calculated as q - Floor(q). This vector has a zero in the third place due to the - // subtraction. - // Rewriting (1) with this substitution of Floor(q) + {q} for q. - // (x',y',1) = Floor(q + [V](px,py,0)) + q' - q - // becomes, - // (x',y',1) = Floor(Floor(q) + {q} + [V](px,py,0)) + q' - (q + {q}) - // simplifying by moving Floor(q) out of the Floor() because it is integer, - // (x',y',1) = Floor({q} + [V](px,py,0)) + q' + Floor(q) - Floor(q) - {q} - // removing terms that result in zero gives, - // (x',y',1) = Floor({q} + [V](px,py,0)) + q' - {q} - // Notice that q' - {q} and Floor({q} + [V](px,py,0)) are integer. - // Let, - // (ix,iy,0) = Floor({q} + [V](px,py,0)), - // (sx,sy,0) = -{q}. - // I call the (sx,sy,0) value the residual. - // Thus, - // (x',y',1) = (ix,iy,0) + (q' + (sx,sy,0)). (2) - // - // As a matter of practicality, we have the following already calculated for sub-pixel - // positioning, and use it to calculate (ix,iy,0): - // (fx,fy,1) = [R][V][O](px,py,1) - // = [Q](0,0,1) + [V](px,py,0) - // = q + [V](px,py,0) - // = Floor(q) + {q} + [V](px,py,0) - // So, - // (ix,iy,0) = Floor((fx,fy,1) - Floor(q)). - // - // When calculating [Q'] = [R][V'][O'] we don't have the values for [R]. Notice that [R] is a - // post translation to [V'][O']. This means that the values of R are added directly to the - // translation values of [V'][O']. So, if [V'][O'](0,0,1) results in the vector (tx,ty,1) - // then [R](tx,ty,0) = (tx + rx, ty + ry, 0). So, in practice we don't have the full [Q'] what - // is available is [Q''] = [V'][O']. We can add the rounding terms to the residual - // to account for not having [R]. Substituting -{q} for (sx,sy,0) in (2), gives: - // (x',y',1) = (ix,iy,0) + (q' - {q}). - // = (ix,iy,0) + ([Q'](0,0,1) - {q}) - // = (ix,iy,0) + ([R][V'][O'](0,0,1) - {q}) - // = (ix,iy,0) + ((rx,ry,0) + [V'][O'](0,0,1) - {q}) - // = (ix,iy,0) + ([V'][O'](0,0,1) + (rx,ry,0) - {q}. - // So we redefine the residual to include the needed rounding terms. - // (sx',sy',0) = (rx,ry,0) - (q - Floor(q)) - // = (rx,ry,0) + Floor(q) - q. - // - // Putting it all together: - // Q'' = [V'][O'](0,0,1) - // q'' = Q''(0, 0, 1) - // (x',y',1) = (ix,iy,0) + (q'' + (sx',sy',0)). - - - // Returns the residual -- (sx',sy',0). + // Returns the origin mapped through the initial matrix. SkPoint startGPUDevice( const SkZip& source, SkPoint origin, const SkMatrix& viewMatrix, diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp index 99d7e4dc07..79731dfe33 100644 --- a/src/gpu/text/GrTextBlob.cpp +++ b/src/gpu/text/GrTextBlob.cpp @@ -93,11 +93,11 @@ SkPMColor4f calculate_colors(GrRenderTargetContext* rtc, // The 99% case. No clip. Non-color only. void direct_2D(SkZip quadData, GrColor color, - SkIPoint deviceOrigin) { + SkIPoint integralOriginOffset) { for (auto[quad, glyph, leftTop] : quadData) { auto[al, at, ar, ab] = glyph->fAtlasLocator.getUVs(); - SkScalar dl = leftTop.x() + deviceOrigin.x(), - dt = leftTop.y() + deviceOrigin.y(), + SkScalar dl = leftTop.x() + integralOriginOffset.x(), + dt = leftTop.y() + integralOriginOffset.y(), dr = dl + (ar - al), db = dt + (ab - at); @@ -117,13 +117,13 @@ auto ltbr(const Rect& r) { template void generalized_direct_2D(SkZip quadData, GrColor color, - SkIPoint deviceOrigin, + SkIPoint integralOriginOffset, SkIRect* clip = nullptr) { for (auto[quad, glyph, leftTop] : quadData) { auto[al, at, ar, ab] = glyph->fAtlasLocator.getUVs(); uint16_t w = ar - al, h = ab - at; - auto[l, t] = leftTop + deviceOrigin; + auto[l, t] = leftTop + integralOriginOffset; if (clip == nullptr) { auto[dl, dt, dr, db] = SkRect::MakeLTRB(l, t, l + w, t + h); quad[0] = {{dl, dt}, color, {al, at}}; // L,T @@ -556,7 +556,7 @@ private: SkRect deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const; const GrMaskFormat fMaskFormat; - const SkPoint fResidual; + const SkPoint fInitialMappedOrigin; GrTextBlob* const fBlob; // The vertex bounds in device space. The bounds are the joined rectangles of all the glyphs. const SkRect fVertexBounds; @@ -574,7 +574,7 @@ DirectMaskSubRun::DirectMaskSubRun(GrMaskFormat format, SkSpan vertexData, GlyphVector glyphs) : fMaskFormat{format} - , fResidual{residual} + , fInitialMappedOrigin{residual} , fBlob{blob} , fVertexBounds{bounds} , fVertexData{vertexData} @@ -724,39 +724,37 @@ void DirectMaskSubRun::fillVertexData(void* vertexDst, int offset, int count, Gr fVertexData.subspan(offset, count)); }; - SkMatrix matrix = drawMatrix; - matrix.preTranslate(drawOrigin.x(), drawOrigin.y()); - SkPoint o = matrix.mapXY(0, 0) + fResidual; - SkIPoint originInDeviceSpace = {SkScalarRoundToInt(o.x()), SkScalarRoundToInt(o.y())}; + SkPoint originOffset = drawMatrix.mapXY(drawOrigin.x(), drawOrigin.y()) - fInitialMappedOrigin; + SkIPoint integralOriginOffset = + {SkScalarRoundToInt(originOffset.x()), SkScalarRoundToInt(originOffset.y())}; if (clip.isEmpty()) { if (fMaskFormat != kARGB_GrMaskFormat) { using Quad = Mask2DVertex[4]; SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); - direct_2D(quadData((Quad*)vertexDst), color, originInDeviceSpace); + direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset); } else { using Quad = ARGB2DVertex[4]; SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); - generalized_direct_2D(quadData((Quad*)vertexDst), color, originInDeviceSpace); + generalized_direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset); } } else { if (fMaskFormat != kARGB_GrMaskFormat) { using Quad = Mask2DVertex[4]; SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); - generalized_direct_2D(quadData((Quad*)vertexDst), color, originInDeviceSpace, &clip); + generalized_direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset, &clip); } else { using Quad = ARGB2DVertex[4]; SkASSERT(sizeof(Quad) == this->vertexStride() * kVerticesPerGlyph); - generalized_direct_2D(quadData((Quad*)vertexDst), color, originInDeviceSpace, &clip); + generalized_direct_2D(quadData((Quad*)vertexDst), color, integralOriginOffset, &clip); } } - } SkRect DirectMaskSubRun::deviceRect(const SkMatrix& drawMatrix, SkPoint drawOrigin) const { SkRect outBounds = fVertexBounds; - SkPoint offset = drawMatrix.mapXY(drawOrigin.x(), drawOrigin.y()); + SkPoint offset = drawMatrix.mapXY(drawOrigin.x(), drawOrigin.y()) - fInitialMappedOrigin; // The vertex bounds are already {0, 0} based, so just add the new origin offset. outBounds.offset(offset);