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