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/gpu/graphite/PathAtlas.h"
9
10 #include "include/gpu/graphite/Recorder.h"
11 #include "src/gpu/graphite/Caps.h"
12 #include "src/gpu/graphite/RasterPathUtils.h"
13 #include "src/gpu/graphite/RecorderPriv.h"
14 #include "src/gpu/graphite/RendererProvider.h"
15 #include "src/gpu/graphite/TextureProxy.h"
16 #include "src/gpu/graphite/geom/Transform_graphite.h"
17
18 namespace skgpu::graphite {
19 namespace {
20
21 constexpr int kMinAtlasTextureSize = 512; // the smallest we want the PathAtlas textures to be
22 // unless the device requires smaller
23
24 } // namespace
25
PathAtlas(Recorder * recorder,uint32_t requestedWidth,uint32_t requestedHeight)26 PathAtlas::PathAtlas(Recorder* recorder, uint32_t requestedWidth, uint32_t requestedHeight)
27 : fRecorder(recorder) {
28 const Caps* caps = recorder->priv().caps();
29 int maxTextureSize = std::max(caps->maxPathAtlasTextureSize(), kMinAtlasTextureSize);
30 maxTextureSize = std::min(maxTextureSize, caps->maxTextureSize());
31
32 fWidth = SkPrevPow2(std::min<uint32_t>(requestedWidth, maxTextureSize));
33 fHeight = SkPrevPow2(std::min<uint32_t>(requestedHeight, maxTextureSize));
34 }
35
36 PathAtlas::~PathAtlas() = default;
37
addShape(const Rect & transformedShapeBounds,const Shape & shape,const Transform & localToDevice,const SkStrokeRec & style)38 std::pair<const Renderer*, std::optional<PathAtlas::MaskAndOrigin>> PathAtlas::addShape(
39 const Rect& transformedShapeBounds,
40 const Shape& shape,
41 const Transform& localToDevice,
42 const SkStrokeRec& style) {
43 // It is possible for the transformed shape bounds to be fully clipped out while the draw still
44 // produces coverage due to an inverse fill. In this case, don't render any mask;
45 // CoverageMaskShapeRenderStep will automatically handle the simple fill. We'll handle this
46 // by adding an empty mask.
47 // TODO: We could have addShape() handle this fully except we need a valid TextureProxy still.
48 const bool emptyMask = transformedShapeBounds.isEmptyNegativeOrNaN();
49
50 // Round out the shape bounds to preserve any fractional offset so that it is present in the
51 // translation that we use when deriving the atlas-space transform later.
52 Rect maskBounds = transformedShapeBounds.makeRoundOut();
53
54 CoverageMaskShape::MaskInfo maskInfo;
55 // This size does *not* include any padding that the atlas may place around the mask. This size
56 // represents the area the shape can actually modify.
57 maskInfo.fMaskSize = emptyMask ? skvx::half2(0) : skvx::cast<uint16_t>(maskBounds.size());
58 // We use the origin of the mask bounds to distinguish between clips of the same size.
59 Rect shapeDevBounds = localToDevice.mapRect(shape.bounds());
60 skvx::float2 shapeMaskOrigin = maskBounds.topLeft() - shapeDevBounds.topLeft();
61 Transform atlasTransform = localToDevice.postTranslate(-maskBounds.left(), -maskBounds.top());
62 const TextureProxy* atlasProxy = this->onAddShape(shape,
63 atlasTransform,
64 style,
65 skvx::cast<uint16_t>(shapeMaskOrigin),
66 maskInfo.fMaskSize,
67 &maskInfo.fTextureOrigin);
68 if (!atlasProxy) {
69 return std::make_pair(nullptr, std::nullopt);
70 }
71
72 std::optional<PathAtlas::MaskAndOrigin> atlasMask =
73 std::make_pair(CoverageMaskShape(shape, atlasProxy, localToDevice.inverse(), maskInfo),
74 SkIPoint{(int) maskBounds.left(), (int) maskBounds.top()});
75 return std::make_pair(fRecorder->priv().rendererProvider()->coverageMask(), atlasMask);
76 }
77
78 /////////////////////////////////////////////////////////////////////////////////////////
79
DrawAtlasMgr(size_t width,size_t height,size_t plotWidth,size_t plotHeight,DrawAtlas::UseStorageTextures useStorageTextures,std::string_view label,const Caps * caps)80 PathAtlas::DrawAtlasMgr::DrawAtlasMgr(size_t width, size_t height,
81 size_t plotWidth, size_t plotHeight,
82 DrawAtlas::UseStorageTextures useStorageTextures,
83 std::string_view label,
84 const Caps* caps) {
85 static constexpr SkColorType colorType = kAlpha_8_SkColorType;
86
87 fDrawAtlas = DrawAtlas::Make(colorType,
88 SkColorTypeBytesPerPixel(colorType),
89 width, height,
90 plotWidth, plotHeight,
91 /*generationCounter=*/this,
92 caps->allowMultipleAtlasTextures() ?
93 DrawAtlas::AllowMultitexturing::kYes :
94 DrawAtlas::AllowMultitexturing::kNo,
95 useStorageTextures,
96 /*evictor=*/this,
97 label);
98 SkASSERT(fDrawAtlas);
99 fKeyLists.resize(fDrawAtlas->numPlots() * fDrawAtlas->maxPages());
100 for (int i = 0; i < fKeyLists.size(); ++i) {
101 fKeyLists[i].reset();
102 }
103 }
104
105 namespace {
shape_key_list_index(const PlotLocator & locator,const DrawAtlas * drawAtlas)106 uint32_t shape_key_list_index(const PlotLocator& locator, const DrawAtlas* drawAtlas) {
107 return locator.pageIndex() * drawAtlas->numPlots() + locator.plotIndex();
108 }
109 } // namespace
110
findOrCreateEntry(Recorder * recorder,const Shape & shape,const Transform & transform,const SkStrokeRec & strokeRec,skvx::half2 maskOrigin,skvx::half2 maskSize,skvx::half2 * outPos)111 const TextureProxy* PathAtlas::DrawAtlasMgr::findOrCreateEntry(Recorder* recorder,
112 const Shape& shape,
113 const Transform& transform,
114 const SkStrokeRec& strokeRec,
115 skvx::half2 maskOrigin,
116 skvx::half2 maskSize,
117 skvx::half2* outPos) {
118 // Shapes must have a key to use this method
119 skgpu::UniqueKey maskKey = GeneratePathMaskKey(shape, transform, strokeRec,
120 maskOrigin, maskSize);
121 AtlasLocator* cachedLocator = fShapeCache.find(maskKey);
122 if (cachedLocator) {
123 SkIPoint topLeft = cachedLocator->topLeft();
124 *outPos = skvx::half2(topLeft.x() + kEntryPadding, topLeft.y() + kEntryPadding);
125 fDrawAtlas->setLastUseToken(*cachedLocator,
126 recorder->priv().tokenTracker()->nextFlushToken());
127 return fDrawAtlas->getProxies()[cachedLocator->pageIndex()].get();
128 }
129
130 AtlasLocator locator;
131 const TextureProxy* proxy = this->addToAtlas(recorder, shape, transform, strokeRec,
132 maskSize, outPos, &locator);
133 if (!proxy) {
134 return nullptr;
135 }
136
137 // Add locator to ShapeCache.
138 fShapeCache.set(maskKey, locator);
139 // Add key to Plot's ShapeKeyList.
140 uint32_t index = shape_key_list_index(locator.plotLocator(), fDrawAtlas.get());
141 ShapeKeyEntry* keyEntry = new ShapeKeyEntry();
142 keyEntry->fKey = maskKey;
143 fKeyLists[index].addToTail(keyEntry);
144
145 return proxy;
146 }
147
addToAtlas(Recorder * recorder,const Shape & shape,const Transform & transform,const SkStrokeRec & strokeRec,skvx::half2 maskSize,skvx::half2 * outPos,AtlasLocator * locator)148 const TextureProxy* PathAtlas::DrawAtlasMgr::addToAtlas(Recorder* recorder,
149 const Shape& shape,
150 const Transform& transform,
151 const SkStrokeRec& strokeRec,
152 skvx::half2 maskSize,
153 skvx::half2* outPos,
154 AtlasLocator* locator) {
155 // Render mask.
156 SkIRect iShapeBounds = SkIRect::MakeXYWH(0, 0, maskSize.x(), maskSize.y());
157 // Outset to take padding into account
158 SkIRect iAtlasBounds = iShapeBounds.makeOutset(kEntryPadding, kEntryPadding);
159
160 // Request space in DrawAtlas.
161 DrawAtlas::ErrorCode errorCode = fDrawAtlas->addRect(recorder,
162 iAtlasBounds.width(),
163 iAtlasBounds.height(),
164 locator);
165 if (errorCode != DrawAtlas::ErrorCode::kSucceeded) {
166 return nullptr;
167 }
168 SkIPoint topLeft = locator->topLeft();
169 *outPos = skvx::half2(topLeft.x()+kEntryPadding, topLeft.y()+kEntryPadding);
170
171 // If the mask is empty, just return.
172 // TODO: this may not be needed if we can handle clipped out bounds with inverse fills
173 // another way. See PathAtlas::addShape().
174 if (!all(maskSize)) {
175 fDrawAtlas->setLastUseToken(*locator,
176 recorder->priv().tokenTracker()->nextFlushToken());
177 return fDrawAtlas->getProxies()[locator->pageIndex()].get();
178 }
179
180 if (!this->onAddToAtlas(shape, transform, strokeRec, iShapeBounds, *locator)) {
181 return nullptr;
182 }
183
184 fDrawAtlas->setLastUseToken(*locator,
185 recorder->priv().tokenTracker()->nextFlushToken());
186
187 return fDrawAtlas->getProxies()[locator->pageIndex()].get();
188 }
189
recordUploads(DrawContext * dc,Recorder * recorder)190 bool PathAtlas::DrawAtlasMgr::recordUploads(DrawContext* dc, Recorder* recorder) {
191 return fDrawAtlas->recordUploads(dc, recorder);
192 }
193
evict(PlotLocator plotLocator)194 void PathAtlas::DrawAtlasMgr::evict(PlotLocator plotLocator) {
195 // Remove all entries for this Plot from the ShapeCache
196 uint32_t index = shape_key_list_index(plotLocator, fDrawAtlas.get());
197 ShapeKeyList::Iter iter;
198 iter.init(fKeyLists[index], ShapeKeyList::Iter::kHead_IterStart);
199 ShapeKeyEntry* currEntry;
200 while ((currEntry = iter.get())) {
201 iter.next();
202 fShapeCache.remove(currEntry->fKey);
203 fKeyLists[index].remove(currEntry);
204 delete currEntry;
205 }
206 }
207
evictAll()208 void PathAtlas::DrawAtlasMgr::evictAll() {
209 fDrawAtlas->evictAllPlots();
210 SkASSERT(fShapeCache.empty());
211 }
212
compact(Recorder * recorder,bool forceCompact)213 void PathAtlas::DrawAtlasMgr::compact(Recorder* recorder, bool forceCompact) {
214 fDrawAtlas->compact(recorder->priv().tokenTracker()->nextFlushToken(), forceCompact);
215 }
216
217 } // namespace skgpu::graphite
218