• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 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/gpu/graphite/RasterPathUtils.h"
9 
10 #include "include/core/SkStrokeRec.h"
11 #include "include/private/base/SkFixed.h"
12 #include "src/base/SkFloatBits.h"
13 #include "src/core/SkBlitter_A8.h"
14 #include "src/gpu/graphite/geom/Shape.h"
15 #include "src/gpu/graphite/geom/Transform.h"
16 
17 namespace skgpu::graphite {
18 
init(SkISize pixmapSize,SkIVector transformedMaskOffset)19 bool RasterMaskHelper::init(SkISize pixmapSize, SkIVector transformedMaskOffset) {
20     if (!fPixels) {
21         return false;
22     }
23 
24     // Allocate pixmap if needed
25     if (!fPixels->addr()) {
26         const SkImageInfo bmImageInfo = SkImageInfo::MakeA8(pixmapSize);
27         if (!fPixels->tryAlloc(bmImageInfo)) {
28             return false;
29         }
30         fPixels->erase(0);
31     } else if (fPixels->dimensions() != pixmapSize) {
32         return false;
33     }
34 
35     fDraw.fBlitterChooser = SkA8Blitter_Choose;
36     fDraw.fDst = *fPixels;
37     fDraw.fRC = &fRasterClip;
38     fTransformedMaskOffset = transformedMaskOffset;
39     return true;
40 }
41 
clear(uint8_t alpha,const SkIRect & shapeBounds)42 void RasterMaskHelper::clear(uint8_t alpha, const SkIRect& shapeBounds) {
43     fPixels->erase(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF), shapeBounds);
44 }
45 
drawShape(const Shape & shape,const Transform & localToDevice,const SkStrokeRec & strokeRec,const SkIRect & shapeBounds)46 void RasterMaskHelper::drawShape(const Shape& shape,
47                                  const Transform& localToDevice,
48                                  const SkStrokeRec& strokeRec,
49                                  const SkIRect& shapeBounds) {
50     fRasterClip.setRect(shapeBounds);
51 
52     SkPaint paint;
53     paint.setBlendMode(SkBlendMode::kSrc);  // "Replace" mode
54     paint.setAntiAlias(true);
55     // SkPaint's color is unpremul so this will produce alpha in every channel.
56     paint.setColor(SK_ColorWHITE);
57     strokeRec.applyToPaint(&paint);
58 
59     SkMatrix translatedMatrix = SkMatrix(localToDevice);
60     // The atlas transform of the shape is `localToDevice` translated by the top-left offset of the
61     // resultBounds and the inverse of the base mask transform offset for the current set of shapes.
62     // We will need to translate draws so the bound's UL corner is at the origin
63     translatedMatrix.postTranslate(shapeBounds.x() - fTransformedMaskOffset.x(),
64                                    shapeBounds.y() - fTransformedMaskOffset.y());
65 
66     fDraw.fCTM = &translatedMatrix;
67     // TODO: use drawRect, drawRRect, drawArc
68     SkPath path = shape.asPath();
69     if (path.isInverseFillType()) {
70         // The shader will handle the inverse fill in this case
71         path.toggleInverseFillType();
72     }
73     fDraw.drawPathCoverage(path, paint);
74 }
75 
drawClip(const Shape & shape,const Transform & localToDevice,uint8_t alpha,const SkIRect & resultBounds)76 void RasterMaskHelper::drawClip(const Shape& shape,
77                                 const Transform& localToDevice,
78                                 uint8_t alpha,
79                                 const SkIRect& resultBounds) {
80     fRasterClip.setRect(resultBounds);
81 
82     SkPaint paint;
83     paint.setBlendMode(SkBlendMode::kSrc);  // "Replace" mode
84     paint.setAntiAlias(true);
85     // SkPaint's color is unpremul so this will produce alpha in every channel.
86     paint.setColor(SkColorSetARGB(alpha, 0xFF, 0xFF, 0xFF));
87 
88     SkMatrix translatedMatrix = SkMatrix(localToDevice);
89     // The atlas transform of the shape is `localToDevice` translated by the top-left offset of the
90     // resultBounds and the inverse of the base mask transform offset for the current set of shapes.
91     // We will need to translate draws so the bound's UL corner is at the origin
92     translatedMatrix.postTranslate(resultBounds.x() - fTransformedMaskOffset.x(),
93                                    resultBounds.y() - fTransformedMaskOffset.y());
94 
95     fDraw.fCTM = &translatedMatrix;
96     // TODO: use drawRect, drawRRect, drawArc
97     SkPath path = shape.asPath();
98     // Because we could be combining multiple paths into one entry we don't touch
99     // the inverse fill in this case.
100     if (0xFF == alpha) {
101         SkASSERT(0xFF == paint.getAlpha());
102         fDraw.drawPathCoverage(path, paint);
103     } else {
104         fDraw.drawPath(path, paint, nullptr, true);
105     }
106 }
107 
add_transform_key(skgpu::UniqueKey::Builder * builder,int startIndex,const Transform & transform)108 uint32_t add_transform_key(skgpu::UniqueKey::Builder* builder,
109                            int startIndex,
110                            const Transform& transform) {
111     // We require the upper left 2x2 of the matrix to match exactly for a cache hit.
112     SkMatrix mat = transform.matrix().asM33();
113     SkScalar sx = mat.get(SkMatrix::kMScaleX);
114     SkScalar sy = mat.get(SkMatrix::kMScaleY);
115     SkScalar kx = mat.get(SkMatrix::kMSkewX);
116     SkScalar ky = mat.get(SkMatrix::kMSkewY);
117 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
118     // Fractional translate does not affect caching on Android. This is done for better cache
119     // hit ratio and speed and is matching HWUI behavior, which didn't consider the matrix
120     // at all when caching paths.
121     SkFixed fracX = 0;
122     SkFixed fracY = 0;
123 #else
124     SkScalar tx = mat.get(SkMatrix::kMTransX);
125     SkScalar ty = mat.get(SkMatrix::kMTransY);
126     // Allow 8 bits each in x and y of subpixel positioning.
127     SkFixed fracX = SkScalarToFixed(SkScalarFraction(tx)) & 0x0000FF00;
128     SkFixed fracY = SkScalarToFixed(SkScalarFraction(ty)) & 0x0000FF00;
129 #endif
130     (*builder)[startIndex + 0] = SkFloat2Bits(sx);
131     (*builder)[startIndex + 1] = SkFloat2Bits(sy);
132     (*builder)[startIndex + 2] = SkFloat2Bits(kx);
133     (*builder)[startIndex + 3] = SkFloat2Bits(ky);
134     // FracX and fracY are &ed with 0x0000ff00, so need to shift one down to fill 16 bits.
135     uint32_t fracBits = fracX | (fracY >> 8);
136 
137     return fracBits;
138 }
139 
GeneratePathMaskKey(const Shape & shape,const Transform & transform,const SkStrokeRec & strokeRec,skvx::half2 maskOrigin,skvx::half2 maskSize)140 skgpu::UniqueKey GeneratePathMaskKey(const Shape& shape,
141                                      const Transform& transform,
142                                      const SkStrokeRec& strokeRec,
143                                      skvx::half2 maskOrigin,
144                                      skvx::half2 maskSize) {
145     skgpu::UniqueKey maskKey;
146     {
147         static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
148         int styleKeySize = 7;
149         if (!strokeRec.isHairlineStyle() && !strokeRec.isFillStyle()) {
150             // Add space for width and miter if needed
151             styleKeySize += 2;
152         }
153         skgpu::UniqueKey::Builder builder(&maskKey, kDomain, styleKeySize + shape.keySize(),
154                                           "Raster Path Mask");
155         builder[0] = maskOrigin.x() | (maskOrigin.y() << 16);
156         builder[1] = maskSize.x() | (maskSize.y() << 16);
157 
158         // Add transform key and get packed fractional translation bits
159         uint32_t fracBits = add_transform_key(&builder, 2, transform);
160         // Distinguish between path styles. For anything but fill, we also need to include
161         // the cap. (SW grows hairlines by 0.5 pixel with round and square caps). For stroke
162         // or fill-and-stroke we need to include the join, width, and miter.
163         static_assert(SkStrokeRec::kStyleCount <= (1 << 2));
164         static_assert(SkPaint::kCapCount <= (1 << 2));
165         static_assert(SkPaint::kJoinCount <= (1 << 2));
166         uint32_t styleBits = strokeRec.getStyle();
167         if (!strokeRec.isFillStyle()) {
168             styleBits |= (strokeRec.getCap() << 2);
169         }
170         if (!strokeRec.isHairlineStyle() && !strokeRec.isFillStyle()) {
171             styleBits |= (strokeRec.getJoin() << 4);
172             builder[6] = SkFloat2Bits(strokeRec.getWidth());
173             builder[7] = SkFloat2Bits(strokeRec.getMiter());
174         }
175         builder[styleKeySize-1] = fracBits | (styleBits << 16);
176         shape.writeKey(&builder[styleKeySize], /*includeInverted=*/false);
177     }
178     return maskKey;
179 }
180 
GenerateClipMaskKey(uint32_t stackRecordID,const ClipStack::ElementList * elementsForMask)181 skgpu::UniqueKey GenerateClipMaskKey(uint32_t stackRecordID,
182                                      const ClipStack::ElementList* elementsForMask) {
183     skgpu::UniqueKey maskKey;
184     {
185         static constexpr int kMaxShapeCountForKey = 2;
186 
187         static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
188         // if the element list is too large we just use the stackRecordID
189         if (elementsForMask->size() <= kMaxShapeCountForKey) {
190             constexpr int kXformKeySize = 5;
191             int keySize = 0;
192             bool canCreateKey = true;
193             // Iterate through to get key size and see if we can create a key at all
194             for (int i = 0; i < elementsForMask->size(); ++i) {
195                 int shapeKeySize = (*elementsForMask)[i]->fShape.keySize();
196                 if (shapeKeySize < 0) {
197                     canCreateKey = false;
198                     break;
199                 }
200                 keySize += kXformKeySize + shapeKeySize;
201             }
202             if (canCreateKey) {
203                 skgpu::UniqueKey::Builder builder(&maskKey, kDomain, keySize,
204                                                   "Clip Path Mask");
205                 int elementKeyIndex = 0;
206                 for (int i = 0; i < elementsForMask->size(); ++i) {
207                     const ClipStack::Element* element = (*elementsForMask)[i];
208 
209                     // Add transform key and get packed fractional translation bits
210                     uint32_t fracBits = add_transform_key(&builder,
211                                                           elementKeyIndex,
212                                                           element->fLocalToDevice);
213                     uint32_t opBits = static_cast<uint32_t>(element->fOp);
214                     builder[elementKeyIndex + 4] = fracBits | (opBits << 16);
215 
216                     const Shape& shape = element->fShape;
217                     shape.writeKey(&builder[elementKeyIndex + kXformKeySize],
218                                    /*includeInverted=*/true);
219 
220                     elementKeyIndex += kXformKeySize + shape.keySize();
221                 }
222 
223                 return maskKey;
224             }
225         }
226 
227         // Either we have too many elements or at least one shape can't create a key
228         skgpu::UniqueKey::Builder builder(&maskKey, kDomain, 1, "Clip Path Mask");
229         builder[0] = stackRecordID;
230     }
231 
232     return maskKey;
233 }
234 
235 }  // namespace skgpu::graphite
236