1 /*
2 * Copyright 2017 Google Inc.
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/ccpr/GrCCAtlas.h"
9
10 #include "include/gpu/GrTexture.h"
11 #include "src/core/SkIPoint16.h"
12 #include "src/core/SkMakeUnique.h"
13 #include "src/core/SkMathPriv.h"
14 #include "src/gpu/GrCaps.h"
15 #include "src/gpu/GrOnFlushResourceProvider.h"
16 #include "src/gpu/GrProxyProvider.h"
17 #include "src/gpu/GrRectanizer_skyline.h"
18 #include "src/gpu/GrRenderTarget.h"
19 #include "src/gpu/GrRenderTargetContext.h"
20 #include "src/gpu/GrTextureProxy.h"
21 #include "src/gpu/ccpr/GrCCPathCache.h"
22 #include <atomic>
23
24 class GrCCAtlas::Node {
25 public:
Node(std::unique_ptr<Node> previous,int l,int t,int r,int b)26 Node(std::unique_ptr<Node> previous, int l, int t, int r, int b)
27 : fPrevious(std::move(previous)), fX(l), fY(t), fRectanizer(r - l, b - t) {}
28
previous() const29 Node* previous() const { return fPrevious.get(); }
30
addRect(int w,int h,SkIPoint16 * loc,int maxAtlasSize)31 bool addRect(int w, int h, SkIPoint16* loc, int maxAtlasSize) {
32 // Pad all paths except those that are expected to take up an entire physical texture.
33 if (w < maxAtlasSize) {
34 w = SkTMin(w + kPadding, maxAtlasSize);
35 }
36 if (h < maxAtlasSize) {
37 h = SkTMin(h + kPadding, maxAtlasSize);
38 }
39 if (!fRectanizer.addRect(w, h, loc)) {
40 return false;
41 }
42 loc->fX += fX;
43 loc->fY += fY;
44 return true;
45 }
46
47 private:
48 const std::unique_ptr<Node> fPrevious;
49 const int fX, fY;
50 GrRectanizerSkyline fRectanizer;
51 };
52
MakeLazyAtlasProxy(const LazyInstantiateAtlasCallback & callback,CoverageType coverageType,const GrCaps & caps)53 sk_sp<GrTextureProxy> GrCCAtlas::MakeLazyAtlasProxy(
54 const LazyInstantiateAtlasCallback& callback, CoverageType coverageType, const GrCaps& caps) {
55 GrPixelConfig pixelConfig;
56 int sampleCount;
57
58 auto colorType = CoverageTypeToColorType(coverageType);
59 GrBackendFormat format = caps.getDefaultBackendFormat(colorType, GrRenderable::kYes);
60 switch (coverageType) {
61 case CoverageType::kFP16_CoverageCount:
62 pixelConfig = kAlpha_half_GrPixelConfig;
63 sampleCount = 1;
64 break;
65 case CoverageType::kA8_Multisample:
66 SkASSERT(caps.internalMultisampleCount(format) > 1);
67 pixelConfig = kAlpha_8_GrPixelConfig;
68 sampleCount = (caps.mixedSamplesSupport()) ? 1 : caps.internalMultisampleCount(format);
69 break;
70 case CoverageType::kA8_LiteralCoverage:
71 pixelConfig = kAlpha_8_GrPixelConfig;
72 sampleCount = 1;
73 break;
74 }
75
76 auto instantiate = [cb = std::move(callback), pixelConfig, format,
77 sampleCount](GrResourceProvider* rp) {
78 return cb(rp, pixelConfig, format, sampleCount);
79 };
80 sk_sp<GrTextureProxy> proxy = GrProxyProvider::MakeFullyLazyProxy(
81 std::move(instantiate), format, GrRenderable::kYes, sampleCount, GrProtected::kNo,
82 kTextureOrigin, pixelConfig, caps);
83
84 return proxy;
85 }
86
GrCCAtlas(CoverageType coverageType,const Specs & specs,const GrCaps & caps)87 GrCCAtlas::GrCCAtlas(CoverageType coverageType, const Specs& specs, const GrCaps& caps)
88 : fCoverageType(coverageType)
89 , fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth),
90 specs.fMaxPreferredTextureSize)) {
91 // Caller should have cropped any paths to the destination render target instead of asking for
92 // an atlas larger than maxRenderTargetSize.
93 SkASSERT(fMaxTextureSize <= caps.maxTextureSize());
94 SkASSERT(specs.fMaxPreferredTextureSize > 0);
95
96 // Begin with the first pow2 dimensions whose area is theoretically large enough to contain the
97 // pending paths, favoring height over width if necessary.
98 int log2area = SkNextLog2(SkTMax(specs.fApproxNumPixels, 1));
99 fHeight = 1 << ((log2area + 1) / 2);
100 fWidth = 1 << (log2area / 2);
101
102 fWidth = SkTClamp(fWidth, specs.fMinTextureSize, specs.fMaxPreferredTextureSize);
103 fHeight = SkTClamp(fHeight, specs.fMinTextureSize, specs.fMaxPreferredTextureSize);
104
105 if (fWidth < specs.fMinWidth || fHeight < specs.fMinHeight) {
106 // They want to stuff a particularly large path into the atlas. Just punt and go with their
107 // min width and height. The atlas will grow as needed.
108 fWidth = SkTMin(specs.fMinWidth + kPadding, fMaxTextureSize);
109 fHeight = SkTMin(specs.fMinHeight + kPadding, fMaxTextureSize);
110 }
111
112 fTopNode = skstd::make_unique<Node>(nullptr, 0, 0, fWidth, fHeight);
113
114 fTextureProxy = MakeLazyAtlasProxy(
115 [this](GrResourceProvider* resourceProvider, GrPixelConfig pixelConfig,
116 const GrBackendFormat& format, int sampleCount) {
117 if (!fBackingTexture) {
118 GrSurfaceDesc desc;
119 desc.fWidth = fWidth;
120 desc.fHeight = fHeight;
121 desc.fConfig = pixelConfig;
122 fBackingTexture = resourceProvider->createTexture(
123 desc, format, GrRenderable::kYes, sampleCount, SkBudgeted::kYes,
124 GrProtected::kNo, GrResourceProvider::Flags::kNoPendingIO);
125 }
126 return fBackingTexture;
127 },
128 fCoverageType, caps);
129
130 fTextureProxy->priv().setIgnoredByResourceAllocator();
131 }
132
~GrCCAtlas()133 GrCCAtlas::~GrCCAtlas() {
134 }
135
addRect(const SkIRect & devIBounds,SkIVector * offset)136 bool GrCCAtlas::addRect(const SkIRect& devIBounds, SkIVector* offset) {
137 // This can't be called anymore once makeRenderTargetContext() has been called.
138 SkASSERT(!fTextureProxy->isInstantiated());
139
140 SkIPoint16 location;
141 if (!this->internalPlaceRect(devIBounds.width(), devIBounds.height(), &location)) {
142 return false;
143 }
144 offset->set(location.x() - devIBounds.left(), location.y() - devIBounds.top());
145
146 fDrawBounds.fWidth = SkTMax(fDrawBounds.width(), location.x() + devIBounds.width());
147 fDrawBounds.fHeight = SkTMax(fDrawBounds.height(), location.y() + devIBounds.height());
148 return true;
149 }
150
internalPlaceRect(int w,int h,SkIPoint16 * loc)151 bool GrCCAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
152 for (Node* node = fTopNode.get(); node; node = node->previous()) {
153 if (node->addRect(w, h, loc, fMaxTextureSize)) {
154 return true;
155 }
156 }
157
158 // The rect didn't fit. Grow the atlas and try again.
159 do {
160 if (fWidth == fMaxTextureSize && fHeight == fMaxTextureSize) {
161 return false;
162 }
163 if (fHeight <= fWidth) {
164 int top = fHeight;
165 fHeight = SkTMin(fHeight * 2, fMaxTextureSize);
166 fTopNode = skstd::make_unique<Node>(std::move(fTopNode), 0, top, fWidth, fHeight);
167 } else {
168 int left = fWidth;
169 fWidth = SkTMin(fWidth * 2, fMaxTextureSize);
170 fTopNode = skstd::make_unique<Node>(std::move(fTopNode), left, 0, fWidth, fHeight);
171 }
172 } while (!fTopNode->addRect(w, h, loc, fMaxTextureSize));
173
174 return true;
175 }
176
setFillBatchID(int id)177 void GrCCAtlas::setFillBatchID(int id) {
178 // This can't be called anymore once makeRenderTargetContext() has been called.
179 SkASSERT(!fTextureProxy->isInstantiated());
180 fFillBatchID = id;
181 }
182
setStrokeBatchID(int id)183 void GrCCAtlas::setStrokeBatchID(int id) {
184 // This can't be called anymore once makeRenderTargetContext() has been called.
185 SkASSERT(!fTextureProxy->isInstantiated());
186 fStrokeBatchID = id;
187 }
188
setEndStencilResolveInstance(int idx)189 void GrCCAtlas::setEndStencilResolveInstance(int idx) {
190 // This can't be called anymore once makeRenderTargetContext() has been called.
191 SkASSERT(!fTextureProxy->isInstantiated());
192 fEndStencilResolveInstance = idx;
193 }
194
next_atlas_unique_id()195 static uint32_t next_atlas_unique_id() {
196 static std::atomic<uint32_t> nextID;
197 return nextID++;
198 }
199
refOrMakeCachedAtlas(GrOnFlushResourceProvider * onFlushRP)200 sk_sp<GrCCCachedAtlas> GrCCAtlas::refOrMakeCachedAtlas(GrOnFlushResourceProvider* onFlushRP) {
201 if (!fCachedAtlas) {
202 static const GrUniqueKey::Domain kAtlasDomain = GrUniqueKey::GenerateDomain();
203
204 GrUniqueKey atlasUniqueKey;
205 GrUniqueKey::Builder builder(&atlasUniqueKey, kAtlasDomain, 1, "CCPR Atlas");
206 builder[0] = next_atlas_unique_id();
207 builder.finish();
208
209 onFlushRP->assignUniqueKeyToProxy(atlasUniqueKey, fTextureProxy.get());
210
211 fCachedAtlas = sk_make_sp<GrCCCachedAtlas>(fCoverageType, atlasUniqueKey, fTextureProxy);
212 }
213
214 SkASSERT(fCachedAtlas->coverageType() == fCoverageType);
215 SkASSERT(fCachedAtlas->getOnFlushProxy() == fTextureProxy.get());
216 return fCachedAtlas;
217 }
218
makeRenderTargetContext(GrOnFlushResourceProvider * onFlushRP,sk_sp<GrTexture> backingTexture)219 sk_sp<GrRenderTargetContext> GrCCAtlas::makeRenderTargetContext(
220 GrOnFlushResourceProvider* onFlushRP, sk_sp<GrTexture> backingTexture) {
221 SkASSERT(!fTextureProxy->isInstantiated()); // This method should only be called once.
222 // Caller should have cropped any paths to the destination render target instead of asking for
223 // an atlas larger than maxRenderTargetSize.
224 SkASSERT(SkTMax(fHeight, fWidth) <= fMaxTextureSize);
225 SkASSERT(fMaxTextureSize <= onFlushRP->caps()->maxRenderTargetSize());
226
227 // Finalize the content size of our proxy. The GPU can potentially make optimizations if it
228 // knows we only intend to write out a smaller sub-rectangle of the backing texture.
229 fTextureProxy->priv().setLazySize(fDrawBounds.width(), fDrawBounds.height());
230
231 if (backingTexture) {
232 #ifdef SK_DEBUG
233 auto backingRT = backingTexture->asRenderTarget();
234 SkASSERT(backingRT);
235 SkASSERT(backingRT->config() == fTextureProxy->config());
236 SkASSERT(backingRT->numSamples() == fTextureProxy->asRenderTargetProxy()->numSamples());
237 SkASSERT(backingRT->width() == fWidth);
238 SkASSERT(backingRT->height() == fHeight);
239 #endif
240 fBackingTexture = std::move(backingTexture);
241 }
242 auto colorType = (CoverageType::kFP16_CoverageCount == fCoverageType)
243 ? GrColorType::kAlpha_F16 : GrColorType::kAlpha_8;
244 sk_sp<GrRenderTargetContext> rtc =
245 onFlushRP->makeRenderTargetContext(fTextureProxy, colorType, nullptr, nullptr);
246 if (!rtc) {
247 SkDebugf("WARNING: failed to allocate a %ix%i atlas. Some paths will not be drawn.\n",
248 fWidth, fHeight);
249 return nullptr;
250 }
251
252 SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
253 rtc->clear(&clearRect, SK_PMColor4fTRANSPARENT,
254 GrRenderTargetContext::CanClearFullscreen::kYes);
255 return rtc;
256 }
257
addRect(const SkIRect & devIBounds,SkIVector * devToAtlasOffset)258 GrCCAtlas* GrCCAtlasStack::addRect(const SkIRect& devIBounds, SkIVector* devToAtlasOffset) {
259 GrCCAtlas* retiredAtlas = nullptr;
260 if (fAtlases.empty() || !fAtlases.back().addRect(devIBounds, devToAtlasOffset)) {
261 // The retired atlas is out of room and can't grow any bigger.
262 retiredAtlas = !fAtlases.empty() ? &fAtlases.back() : nullptr;
263 fAtlases.emplace_back(fCoverageType, fSpecs, *fCaps);
264 SkASSERT(devIBounds.width() <= fSpecs.fMinWidth);
265 SkASSERT(devIBounds.height() <= fSpecs.fMinHeight);
266 SkAssertResult(fAtlases.back().addRect(devIBounds, devToAtlasOffset));
267 }
268 return retiredAtlas;
269 }
270