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.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 clipped mask bounds relative to the full mask to distinguish
59 // between clips of the same size.
60 Rect shapeDevBounds = localToDevice.mapRect(shape.bounds());
61 skvx::float2 clippedMaskOrigin = maskBounds.topLeft() - shapeDevBounds.topLeft();
62 SkIVector transformedMaskOffset = SkIVector::Make(maskBounds.topLeft().x(),
63 maskBounds.topLeft().y());
64 const TextureProxy* atlasProxy = this->onAddShape(shape,
65 localToDevice,
66 style,
67 skvx::cast<uint16_t>(clippedMaskOrigin),
68 maskInfo.fMaskSize,
69 transformedMaskOffset,
70 &maskInfo.fTextureOrigin);
71 if (!atlasProxy) {
72 return std::make_pair(nullptr, std::nullopt);
73 }
74
75 std::optional<PathAtlas::MaskAndOrigin> atlasMask =
76 std::make_pair(CoverageMaskShape(shape, atlasProxy, localToDevice.inverse(), maskInfo),
77 SkIPoint{(int) maskBounds.left(), (int) maskBounds.top()});
78 return std::make_pair(fRecorder->priv().rendererProvider()->coverageMask(), atlasMask);
79 }
80
81 /////////////////////////////////////////////////////////////////////////////////////////
82
DrawAtlasMgr(size_t width,size_t height,size_t plotWidth,size_t plotHeight,DrawAtlas::UseStorageTextures useStorageTextures,std::string_view label,const Caps * caps)83 PathAtlas::DrawAtlasMgr::DrawAtlasMgr(size_t width, size_t height,
84 size_t plotWidth, size_t plotHeight,
85 DrawAtlas::UseStorageTextures useStorageTextures,
86 std::string_view label,
87 const Caps* caps) {
88 static constexpr SkColorType colorType = kAlpha_8_SkColorType;
89
90 fDrawAtlas = DrawAtlas::Make(colorType,
91 SkColorTypeBytesPerPixel(colorType),
92 width, height,
93 plotWidth, plotHeight,
94 /*generationCounter=*/this,
95 caps->allowMultipleAtlasTextures() ?
96 DrawAtlas::AllowMultitexturing::kYes :
97 DrawAtlas::AllowMultitexturing::kNo,
98 useStorageTextures,
99 /*evictor=*/this,
100 label);
101 SkASSERT(fDrawAtlas);
102 fKeyLists.resize(fDrawAtlas->numPlots() * fDrawAtlas->maxPages());
103 for (int i = 0; i < fKeyLists.size(); ++i) {
104 fKeyLists[i].reset();
105 }
106 }
107
findOrCreateEntry(Recorder * recorder,const Shape & shape,const Transform & localToDevice,const SkStrokeRec & strokeRec,skvx::half2 maskOrigin,skvx::half2 maskSize,SkIVector transformedMaskOffset,skvx::half2 * outPos)108 const TextureProxy* PathAtlas::DrawAtlasMgr::findOrCreateEntry(Recorder* recorder,
109 const Shape& shape,
110 const Transform& localToDevice,
111 const SkStrokeRec& strokeRec,
112 skvx::half2 maskOrigin,
113 skvx::half2 maskSize,
114 SkIVector transformedMaskOffset,
115 skvx::half2* outPos) {
116 // Shapes must have a key to use this method
117 skgpu::UniqueKey maskKey = GeneratePathMaskKey(shape, localToDevice, strokeRec,
118 maskOrigin, maskSize);
119 AtlasLocator* cachedLocator = fShapeCache.find(maskKey);
120 if (cachedLocator) {
121 SkIPoint topLeft = cachedLocator->topLeft();
122 *outPos = skvx::half2(topLeft.x() + kEntryPadding, topLeft.y() + kEntryPadding);
123 fDrawAtlas->setLastUseToken(*cachedLocator,
124 recorder->priv().tokenTracker()->nextFlushToken());
125 return fDrawAtlas->getProxies()[cachedLocator->pageIndex()].get();
126 }
127
128 AtlasLocator locator;
129 const TextureProxy* proxy = this->addToAtlas(recorder, shape, localToDevice, strokeRec,
130 maskSize, transformedMaskOffset, outPos, &locator);
131 if (!proxy) {
132 return nullptr;
133 }
134
135 // Add locator to ShapeCache.
136 fShapeCache.set(maskKey, locator);
137 // Add key to Plot's ShapeKeyList.
138 uint32_t index = fDrawAtlas->getListIndex(locator.plotLocator());
139 ShapeKeyEntry* keyEntry = new ShapeKeyEntry();
140 keyEntry->fKey = maskKey;
141 fKeyLists[index].addToTail(keyEntry);
142
143 return proxy;
144 }
145
addToAtlas(Recorder * recorder,const Shape & shape,const Transform & localToDevice,const SkStrokeRec & strokeRec,skvx::half2 maskSize,SkIVector transformedMaskOffset,skvx::half2 * outPos,AtlasLocator * locator)146 const TextureProxy* PathAtlas::DrawAtlasMgr::addToAtlas(Recorder* recorder,
147 const Shape& shape,
148 const Transform& localToDevice,
149 const SkStrokeRec& strokeRec,
150 skvx::half2 maskSize,
151 SkIVector transformedMaskOffset,
152 skvx::half2* outPos,
153 AtlasLocator* locator) {
154 // Render mask.
155 SkIRect iShapeBounds = SkIRect::MakeXYWH(0, 0, maskSize.x(), maskSize.y());
156 // Outset to take padding into account
157 SkIRect iAtlasBounds = iShapeBounds.makeOutset(kEntryPadding, kEntryPadding);
158
159 // Request space in DrawAtlas.
160 DrawAtlas::ErrorCode errorCode = fDrawAtlas->addRect(recorder,
161 iAtlasBounds.width(),
162 iAtlasBounds.height(),
163 locator);
164 if (errorCode != DrawAtlas::ErrorCode::kSucceeded) {
165 return nullptr;
166 }
167 SkIPoint topLeft = locator->topLeft();
168 *outPos = skvx::half2(topLeft.x()+kEntryPadding, topLeft.y()+kEntryPadding);
169
170 // If the mask is empty, just return.
171 // TODO: this may not be needed if we can handle clipped out bounds with inverse fills
172 // another way. See PathAtlas::addShape().
173 if (!all(maskSize)) {
174 fDrawAtlas->setLastUseToken(*locator,
175 recorder->priv().tokenTracker()->nextFlushToken());
176 return fDrawAtlas->getProxies()[locator->pageIndex()].get();
177 }
178
179 if (!this->onAddToAtlas(shape, localToDevice, strokeRec, iShapeBounds, transformedMaskOffset,
180 *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 = fDrawAtlas->getListIndex(plotLocator);
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)213 void PathAtlas::DrawAtlasMgr::compact(Recorder* recorder) {
214 fDrawAtlas->compact(recorder->priv().tokenTracker()->nextFlushToken());
215 }
216
freeGpuResources(Recorder * recorder)217 void PathAtlas::DrawAtlasMgr::freeGpuResources(Recorder* recorder) {
218 fDrawAtlas->freeGpuResources(recorder->priv().tokenTracker()->nextFlushToken());
219 }
220
221 } // namespace skgpu::graphite
222