xref: /aosp_15_r20/external/skia/src/text/gpu/VertexFiller.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2023 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/text/gpu/VertexFiller.h"
9 
10 #include "include/core/SkMatrix.h"
11 #include "include/core/SkPoint.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkScalar.h"
14 #include "include/core/SkTypes.h"
15 #include "include/private/base/SkTLogic.h"
16 #include "src/core/SkReadBuffer.h"
17 #include "src/core/SkWriteBuffer.h"
18 #include "src/gpu/AtlasTypes.h"
19 #include "src/text/gpu/SubRunAllocator.h"
20 #include "src/text/gpu/SubRunContainer.h"
21 
22 #include <cstdint>
23 #include <optional>
24 
25 #if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
26 #include "include/core/SkPoint3.h"
27 #include "src/base/SkZip.h"
28 #include "src/gpu/ganesh/ops/AtlasTextOp.h"
29 #include "src/text/gpu/Glyph.h"
30 #endif
31 
32 using MaskFormat = skgpu::MaskFormat;
33 
34 namespace sktext::gpu {
35 
VertexFiller(MaskFormat maskFormat,const SkMatrix & creationMatrix,SkRect creationBounds,SkSpan<const SkPoint> leftTop,bool canDrawDirect)36 VertexFiller::VertexFiller(MaskFormat maskFormat,
37                            const SkMatrix &creationMatrix,
38                            SkRect creationBounds,
39                            SkSpan<const SkPoint> leftTop,
40                            bool canDrawDirect)
41             : fMaskType{maskFormat}, fCanDrawDirect{canDrawDirect},
42               fCreationMatrix{creationMatrix}, fCreationBounds{creationBounds},
43               fLeftTop{leftTop} {}
44 
Make(MaskFormat maskType,const SkMatrix & creationMatrix,SkRect creationBounds,SkSpan<const SkPoint> positions,SubRunAllocator * alloc,FillerType fillerType)45 VertexFiller VertexFiller::Make(MaskFormat maskType,
46                                 const SkMatrix &creationMatrix,
47                                 SkRect creationBounds,
48                                 SkSpan<const SkPoint> positions,
49                                 SubRunAllocator *alloc,
50                                 FillerType fillerType) {
51     SkSpan<SkPoint> leftTop = alloc->makePODSpan<SkPoint>(positions);
52     return VertexFiller{
53             maskType, creationMatrix, creationBounds, leftTop, fillerType == kIsDirect};
54 }
55 
MakeFromBuffer(SkReadBuffer & buffer,SubRunAllocator * alloc)56 std::optional<VertexFiller> VertexFiller::MakeFromBuffer(SkReadBuffer &buffer,
57                                                          SubRunAllocator *alloc) {
58     int checkingMaskType = buffer.readInt();
59     if (!buffer.validate(
60             0 <= checkingMaskType && checkingMaskType < skgpu::kMaskFormatCount)) {
61         return std::nullopt;
62     }
63     MaskFormat maskType = (MaskFormat) checkingMaskType;
64 
65     const bool canDrawDirect = buffer.readBool();
66 
67     SkMatrix creationMatrix;
68     buffer.readMatrix(&creationMatrix);
69 
70     SkRect creationBounds = buffer.readRect();
71 
72     SkSpan<SkPoint> leftTop = MakePointsFromBuffer(buffer, alloc);
73     if (leftTop.empty()) { return std::nullopt; }
74 
75     SkASSERT(buffer.isValid());
76     return VertexFiller{maskType, creationMatrix, creationBounds, leftTop, canDrawDirect};
77 }
78 
flatten(SkWriteBuffer & buffer) const79 void VertexFiller::flatten(SkWriteBuffer &buffer) const {
80     buffer.writeInt(static_cast<int>(fMaskType));
81     buffer.writeBool(fCanDrawDirect);
82     buffer.writeMatrix(fCreationMatrix);
83     buffer.writeRect(fCreationBounds);
84     buffer.writePointArray(fLeftTop.data(), SkCount(fLeftTop));
85 }
86 
viewDifference(const SkMatrix & positionMatrix) const87 SkMatrix VertexFiller::viewDifference(const SkMatrix &positionMatrix) const {
88     if (SkMatrix inverse; fCreationMatrix.invert(&inverse)) {
89         return SkMatrix::Concat(positionMatrix, inverse);
90     }
91     return SkMatrix::I();
92 }
93 
94 // Check for integer translate with the same 2x2 matrix.
95 // Returns the translation, and true if the change from creation matrix to the position matrix
96 // supports using direct glyph masks.
can_use_direct(const SkMatrix & creationMatrix,const SkMatrix & positionMatrix)97 static std::tuple<bool, SkVector> can_use_direct(
98         const SkMatrix& creationMatrix, const SkMatrix& positionMatrix) {
99     // The existing direct glyph info can be used if the creationMatrix, and the
100     // positionMatrix have the same 2x2, the translation between them is integer, and no
101     // perspective is involved. Calculate the translation in source space to a translation in
102     // device space by mapping (0, 0) through both the creationMatrix and the positionMatrix;
103     // take the difference.
104     SkVector translation = positionMatrix.mapOrigin() - creationMatrix.mapOrigin();
105     return {creationMatrix.getScaleX() == positionMatrix.getScaleX() &&
106             creationMatrix.getScaleY() == positionMatrix.getScaleY() &&
107             creationMatrix.getSkewX()  == positionMatrix.getSkewX()  &&
108             creationMatrix.getSkewY()  == positionMatrix.getSkewY()  &&
109             !positionMatrix.hasPerspective() && !creationMatrix.hasPerspective() &&
110             SkScalarIsInt(translation.x()) && SkScalarIsInt(translation.y()),
111             translation};
112 }
113 
114 struct AtlasPt {
115     uint16_t u;
116     uint16_t v;
117 };
118 
119 #if defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
120 
121 // Normal text mask, SDFT, or color.
122 struct Mask2DVertex {
123     SkPoint devicePos;
124     GrColor color;
125     AtlasPt atlasPos;
126 };
127 
128 struct ARGB2DVertex {
ARGB2DVertexsktext::gpu::ARGB2DVertex129     ARGB2DVertex(SkPoint d, GrColor, AtlasPt a) : devicePos{d}, atlasPos{a} {}
130 
131     SkPoint devicePos;
132     AtlasPt atlasPos;
133 };
134 
135 // Perspective SDFT or SDFT forced to 3D or perspective color.
136 struct Mask3DVertex {
137     SkPoint3 devicePos;
138     GrColor color;
139     AtlasPt atlasPos;
140 };
141 
142 struct ARGB3DVertex {
ARGB3DVertexsktext::gpu::ARGB3DVertex143     ARGB3DVertex(SkPoint3 d, GrColor, AtlasPt a) : devicePos{d}, atlasPos{a} {}
144 
145     SkPoint3 devicePos;
146     AtlasPt atlasPos;
147 };
148 
vertexStride(const SkMatrix & matrix) const149 size_t VertexFiller::vertexStride(const SkMatrix &matrix) const {
150     if (fMaskType != MaskFormat::kARGB) {
151         // For formats MaskFormat::kA565 and MaskFormat::kA8 where A8 include SDF.
152         return matrix.hasPerspective() ? sizeof(Mask3DVertex) : sizeof(Mask2DVertex);
153     } else {
154         // For format MaskFormat::kARGB
155         return matrix.hasPerspective() ? sizeof(ARGB3DVertex) : sizeof(ARGB2DVertex);
156     }
157 }
158 
159 // The 99% case. Direct Mask, No clip, No RGB.
fillDirectNoClipping(SkZip<Mask2DVertex[4],const Glyph *,const SkPoint> quadData,GrColor color,SkPoint originOffset)160 void fillDirectNoClipping(SkZip<Mask2DVertex[4], const Glyph*, const SkPoint> quadData,
161                           GrColor color,
162                           SkPoint originOffset) {
163     for (auto[quad, glyph, leftTop] : quadData) {
164         auto[al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
165         SkScalar dl = leftTop.x() + originOffset.x(),
166                  dt = leftTop.y() + originOffset.y(),
167                  dr = dl + (ar - al),
168                  db = dt + (ab - at);
169 
170         quad[0] = {{dl, dt}, color, {al, at}};  // L,T
171         quad[1] = {{dl, db}, color, {al, ab}};  // L,B
172         quad[2] = {{dr, dt}, color, {ar, at}};  // R,T
173         quad[3] = {{dr, db}, color, {ar, ab}};  // R,B
174     }
175 }
176 
177 template <typename Rect>
LTBR(const Rect & r)178 static auto LTBR(const Rect& r) {
179     return std::make_tuple(r.left(), r.top(), r.right(), r.bottom());
180 }
181 
182 // Handle any combination of BW or color and clip or no clip.
183 template<typename Quad, typename VertexData>
fillDirectClipped(SkZip<Quad,const Glyph *,const VertexData> quadData,GrColor color,SkPoint originOffset,SkIRect * clip=nullptr)184 static void fillDirectClipped(SkZip<Quad, const Glyph*, const VertexData> quadData,
185                               GrColor color,
186                               SkPoint originOffset,
187                               SkIRect* clip = nullptr) {
188     for (auto[quad, glyph, leftTop] : quadData) {
189         auto[al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
190         uint16_t w = ar - al,
191                  h = ab - at;
192         SkScalar l = leftTop.x() + originOffset.x(),
193                  t = leftTop.y() + originOffset.y();
194         if (clip == nullptr) {
195             auto[dl, dt, dr, db] = SkRect::MakeLTRB(l, t, l + w, t + h);
196             quad[0] = {{dl, dt}, color, {al, at}};  // L,T
197             quad[1] = {{dl, db}, color, {al, ab}};  // L,B
198             quad[2] = {{dr, dt}, color, {ar, at}};  // R,T
199             quad[3] = {{dr, db}, color, {ar, ab}};  // R,B
200         } else {
201             SkIRect devIRect = SkIRect::MakeLTRB(l, t, l + w, t + h);
202             SkScalar dl, dt, dr, db;
203             if (!clip->containsNoEmptyCheck(devIRect)) {
204                 if (SkIRect clipped; clipped.intersect(devIRect, *clip)) {
205                     al += clipped.left()   - devIRect.left();
206                     at += clipped.top()    - devIRect.top();
207                     ar += clipped.right()  - devIRect.right();
208                     ab += clipped.bottom() - devIRect.bottom();
209                     std::tie(dl, dt, dr, db) = LTBR(clipped);
210                 } else {
211                     // TODO: omit generating any vertex data for fully clipped glyphs ?
212                     std::tie(dl, dt, dr, db) = std::make_tuple(0, 0, 0, 0);
213                     std::tie(al, at, ar, ab) = std::make_tuple(0, 0, 0, 0);
214                 }
215             } else {
216                 std::tie(dl, dt, dr, db) = LTBR(devIRect);
217             }
218             quad[0] = {{dl, dt}, color, {al, at}};  // L,T
219             quad[1] = {{dl, db}, color, {al, ab}};  // L,B
220             quad[2] = {{dr, dt}, color, {ar, at}};  // R,T
221             quad[3] = {{dr, db}, color, {ar, ab}};  // R,B
222         }
223     }
224 }
225 
226 template<typename Quad, typename VertexData>
fill2D(SkZip<Quad,const Glyph *,const VertexData> quadData,GrColor color,const SkMatrix & viewDifference)227 static void fill2D(SkZip<Quad, const Glyph*, const VertexData> quadData,
228                    GrColor color,
229                    const SkMatrix& viewDifference) {
230     for (auto [quad, glyph, leftTop] : quadData) {
231         auto [l, t] = leftTop;
232         auto [r, b] = leftTop + glyph->fAtlasLocator.widthHeight();
233         SkPoint lt = viewDifference.mapXY(l, t),
234                 lb = viewDifference.mapXY(l, b),
235                 rt = viewDifference.mapXY(r, t),
236                 rb = viewDifference.mapXY(r, b);
237         auto [al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
238         quad[0] = {lt, color, {al, at}};  // L,T
239         quad[1] = {lb, color, {al, ab}};  // L,B
240         quad[2] = {rt, color, {ar, at}};  // R,T
241         quad[3] = {rb, color, {ar, ab}};  // R,B
242     }
243 }
244 
245 template<typename Quad, typename VertexData>
fill3D(SkZip<Quad,const Glyph *,const VertexData> quadData,GrColor color,const SkMatrix & viewDifference)246 static void fill3D(SkZip<Quad, const Glyph*, const VertexData> quadData,
247                    GrColor color,
248                    const SkMatrix& viewDifference) {
249     auto mapXYZ = [&](SkScalar x, SkScalar y) {
250         SkPoint pt{x, y};
251         SkPoint3 result;
252         viewDifference.mapHomogeneousPoints(&result, &pt, 1);
253         return result;
254     };
255     for (auto [quad, glyph, leftTop] : quadData) {
256         auto [l, t] = leftTop;
257         auto [r, b] = leftTop + glyph->fAtlasLocator.widthHeight();
258         SkPoint3 lt = mapXYZ(l, t),
259                 lb = mapXYZ(l, b),
260                 rt = mapXYZ(r, t),
261                 rb = mapXYZ(r, b);
262         auto [al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
263         quad[0] = {lt, color, {al, at}};  // L,T
264         quad[1] = {lb, color, {al, ab}};  // L,B
265         quad[2] = {rt, color, {ar, at}};  // R,T
266         quad[3] = {rb, color, {ar, ab}};  // R,B
267     }
268 }
269 
fillVertexData(int offset,int count,SkSpan<const Glyph * > glyphs,GrColor color,const SkMatrix & positionMatrix,SkIRect clip,void * vertexBuffer) const270 void VertexFiller::fillVertexData(int offset, int count,
271                                   SkSpan<const Glyph*> glyphs,
272                                   GrColor color,
273                                   const SkMatrix& positionMatrix,
274                                   SkIRect clip,
275                                   void* vertexBuffer) const {
276     auto quadData = [&](auto dst) {
277         return SkMakeZip(dst,
278                          glyphs.subspan(offset, count),
279                          fLeftTop.subspan(offset, count));
280     };
281 
282     // Handle direct mask drawing specifically.
283     if (fCanDrawDirect) {
284         auto [noTransformNeeded, originOffset] =
285                 can_use_direct(fCreationMatrix, positionMatrix);
286 
287         if (noTransformNeeded) {
288             if (clip.isEmpty()) {
289                 if (fMaskType != MaskFormat::kARGB) {
290                     using Quad = Mask2DVertex[4];
291                     SkASSERT(sizeof(Mask2DVertex) == this->vertexStride(SkMatrix::I()));
292                     fillDirectNoClipping(quadData((Quad*)vertexBuffer), color, originOffset);
293                 } else {
294                     using Quad = ARGB2DVertex[4];
295                     SkASSERT(sizeof(ARGB2DVertex) == this->vertexStride(SkMatrix::I()));
296                     fillDirectClipped(quadData((Quad*)vertexBuffer), color, originOffset);
297                 }
298             } else {
299                 if (fMaskType != MaskFormat::kARGB) {
300                     using Quad = Mask2DVertex[4];
301                     SkASSERT(sizeof(Mask2DVertex) == this->vertexStride(SkMatrix::I()));
302                     fillDirectClipped(quadData((Quad*)vertexBuffer), color, originOffset, &clip);
303                 } else {
304                     using Quad = ARGB2DVertex[4];
305                     SkASSERT(sizeof(ARGB2DVertex) == this->vertexStride(SkMatrix::I()));
306                     fillDirectClipped(quadData((Quad*)vertexBuffer), color, originOffset, &clip);
307                 }
308             }
309             return;
310         }
311     }
312 
313     // Handle the general transformed case.
314     SkMatrix viewDifference = this->viewDifference(positionMatrix);
315     if (!positionMatrix.hasPerspective()) {
316         if (fMaskType == MaskFormat::kARGB) {
317             using Quad = ARGB2DVertex[4];
318             SkASSERT(sizeof(ARGB2DVertex) == this->vertexStride(positionMatrix));
319             fill2D(quadData((Quad*)vertexBuffer), color, viewDifference);
320         } else {
321             using Quad = Mask2DVertex[4];
322             SkASSERT(sizeof(Mask2DVertex) == this->vertexStride(positionMatrix));
323             fill2D(quadData((Quad*)vertexBuffer), color, viewDifference);
324         }
325     } else {
326         if (fMaskType == MaskFormat::kARGB) {
327             using Quad = ARGB3DVertex[4];
328             SkASSERT(sizeof(ARGB3DVertex) == this->vertexStride(positionMatrix));
329             fill3D(quadData((Quad*)vertexBuffer), color, viewDifference);
330         } else {
331             using Quad = Mask3DVertex[4];
332             SkASSERT(sizeof(Mask3DVertex) == this->vertexStride(positionMatrix));
333             fill3D(quadData((Quad*)vertexBuffer), color, viewDifference);
334         }
335     }
336 }
337 
338 using AtlasTextOp = skgpu::ganesh::AtlasTextOp;
opMaskType() const339 AtlasTextOp::MaskType VertexFiller::opMaskType() const {
340     switch (fMaskType) {
341         case MaskFormat::kA8:   return AtlasTextOp::MaskType::kGrayscaleCoverage;
342         case MaskFormat::kA565: return AtlasTextOp::MaskType::kLCDCoverage;
343         case MaskFormat::kARGB: return AtlasTextOp::MaskType::kColorBitmap;
344     }
345     SkUNREACHABLE;
346 }
347 #endif  // defined(SK_GANESH) || defined(SK_USE_LEGACY_GANESH_TEXT_APIS)
348 
isLCD() const349 bool VertexFiller::isLCD() const { return fMaskType == MaskFormat::kA565; }
350 
351 // Return true if the positionMatrix represents an integer translation. Return the device
352 // bounding box of all the glyphs. If the bounding box is empty, then something went singular
353 // and this operation should be dropped.
deviceRectAndCheckTransform(const SkMatrix & positionMatrix) const354 std::tuple<bool, SkRect> VertexFiller::deviceRectAndCheckTransform(
355             const SkMatrix &positionMatrix) const {
356     if (fCanDrawDirect) {
357         const auto [directDrawCompatible, offset] =
358                 can_use_direct(fCreationMatrix, positionMatrix);
359 
360         if (directDrawCompatible) {
361             return {true, fCreationBounds.makeOffset(offset)};
362         }
363     }
364 
365     if (SkMatrix inverse; fCreationMatrix.invert(&inverse)) {
366         SkMatrix viewDifference = SkMatrix::Concat(positionMatrix, inverse);
367         return {false, viewDifference.mapRect(fCreationBounds)};
368     }
369 
370     // initialPositionMatrix is singular. Do nothing.
371     return {false, SkRect::MakeEmpty()};
372 }
373 
374 }  // namespace sktext::gpu
375