1 /*
2 * Copyright 2020 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/ganesh/GrDynamicAtlas.h"
9
10 #include "include/core/SkTypes.h"
11 #include "include/gpu/GpuTypes.h"
12 #include "include/gpu/ganesh/GrBackendSurface.h"
13 #include "src/base/SkMathPriv.h"
14 #include "src/core/SkIPoint16.h"
15 #include "src/gpu/Rectanizer.h"
16 #include "src/gpu/RectanizerPow2.h"
17 #include "src/gpu/RectanizerSkyline.h"
18 #include "src/gpu/ganesh/GrCaps.h"
19 #include "src/gpu/ganesh/GrOnFlushResourceProvider.h"
20 #include "src/gpu/ganesh/GrProxyProvider.h"
21 #include "src/gpu/ganesh/GrRenderTarget.h"
22 #include "src/gpu/ganesh/GrRenderTargetProxy.h"
23 #include "src/gpu/ganesh/GrResourceProvider.h"
24 #include "src/gpu/ganesh/GrSurfaceProxyPriv.h"
25 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
26 #include "src/gpu/ganesh/GrTexture.h"
27
28 #include <algorithm>
29 #include <functional>
30 #include <utility>
31
32 using namespace skgpu;
33
34 // Each Node covers a sub-rectangle of the final atlas. When a GrDynamicAtlas runs out of room, we
35 // create a new Node the same size as all combined nodes in the atlas as-is, and then place the new
36 // Node immediately below or beside the others (thereby doubling the size of the GyDynamicAtlas).
37 class GrDynamicAtlas::Node {
38 public:
Node(Node * previous,Rectanizer * rectanizer,int x,int y)39 Node(Node* previous, Rectanizer* rectanizer, int x, int y)
40 : fPrevious(previous), fRectanizer(rectanizer), fX(x), fY(y) {}
41
previous() const42 Node* previous() const { return fPrevious; }
43
addRect(int w,int h,SkIPoint16 * loc)44 bool addRect(int w, int h, SkIPoint16* loc) {
45 // Pad all paths except those that are expected to take up an entire physical texture.
46 if (w < fRectanizer->width()) {
47 w = std::min(w + kPadding, fRectanizer->width());
48 }
49 if (h < fRectanizer->height()) {
50 h = std::min(h + kPadding, fRectanizer->height());
51 }
52 if (!fRectanizer->addRect(w, h, loc)) {
53 return false;
54 }
55 loc->fX += fX;
56 loc->fY += fY;
57 return true;
58 }
59
60 private:
61 Node* const fPrevious;
62 Rectanizer* const fRectanizer;
63 const int fX, fY;
64 };
65
MakeLazyAtlasProxy(LazyInstantiateAtlasCallback && callback,GrColorType colorType,InternalMultisample internalMultisample,const GrCaps & caps,GrSurfaceProxy::UseAllocator useAllocator)66 sk_sp<GrTextureProxy> GrDynamicAtlas::MakeLazyAtlasProxy(
67 LazyInstantiateAtlasCallback&& callback,
68 GrColorType colorType,
69 InternalMultisample internalMultisample,
70 const GrCaps& caps,
71 GrSurfaceProxy::UseAllocator useAllocator) {
72 GrBackendFormat format = caps.getDefaultBackendFormat(colorType, GrRenderable::kYes);
73
74 int sampleCount = 1;
75 if (InternalMultisample::kYes == internalMultisample) {
76 sampleCount = caps.internalMultisampleCount(format);
77 }
78
79 sk_sp<GrTextureProxy> proxy =
80 GrProxyProvider::MakeFullyLazyProxy(std::move(callback), format, GrRenderable::kYes,
81 sampleCount, GrProtected::kNo, caps, useAllocator);
82
83 return proxy;
84 }
85
GrDynamicAtlas(GrColorType colorType,InternalMultisample internalMultisample,SkISize initialSize,int maxAtlasSize,const GrCaps & caps,RectanizerAlgorithm algorithm)86 GrDynamicAtlas::GrDynamicAtlas(GrColorType colorType, InternalMultisample internalMultisample,
87 SkISize initialSize, int maxAtlasSize, const GrCaps& caps,
88 RectanizerAlgorithm algorithm)
89 : fColorType(colorType)
90 , fInternalMultisample(internalMultisample)
91 , fMaxAtlasSize(maxAtlasSize)
92 , fRectanizerAlgorithm(algorithm) {
93 SkASSERT(fMaxAtlasSize <= caps.maxTextureSize());
94 this->reset(initialSize, caps);
95 }
96
~GrDynamicAtlas()97 GrDynamicAtlas::~GrDynamicAtlas() {
98 }
99
reset(SkISize initialSize,const GrCaps & caps)100 void GrDynamicAtlas::reset(SkISize initialSize, const GrCaps& caps) {
101 fNodeAllocator.reset();
102 fWidth = std::min(SkNextPow2(initialSize.width()), fMaxAtlasSize);
103 fHeight = std::min(SkNextPow2(initialSize.height()), fMaxAtlasSize);
104 fTopNode = nullptr;
105 fDrawBounds.setEmpty();
106 fTextureProxy = MakeLazyAtlasProxy(
107 [this](GrResourceProvider* resourceProvider, const LazyAtlasDesc& desc) {
108 if (!fBackingTexture) {
109 fBackingTexture =
110 resourceProvider->createTexture(fTextureProxy->backingStoreDimensions(),
111 desc.fFormat,
112 desc.fTextureType,
113 desc.fRenderable,
114 desc.fSampleCnt,
115 desc.fMipmapped,
116 desc.fBudgeted,
117 desc.fProtected,
118 desc.fLabel);
119 }
120 return GrSurfaceProxy::LazyCallbackResult(fBackingTexture);
121 },
122 fColorType, fInternalMultisample, caps, GrSurfaceProxy::UseAllocator::kNo);
123 fBackingTexture = nullptr;
124 }
125
makeNode(Node * previous,int l,int t,int r,int b)126 GrDynamicAtlas::Node* GrDynamicAtlas::makeNode(Node* previous, int l, int t, int r, int b) {
127 int width = r - l;
128 int height = b - t;
129 Rectanizer* rectanizer = (fRectanizerAlgorithm == RectanizerAlgorithm::kSkyline)
130 ? (Rectanizer*)fNodeAllocator.make<RectanizerSkyline>(width, height)
131 : fNodeAllocator.make<RectanizerPow2>(width, height);
132 return fNodeAllocator.make<Node>(previous, rectanizer, l, t);
133 }
134
readView(const GrCaps & caps) const135 GrSurfaceProxyView GrDynamicAtlas::readView(const GrCaps& caps) const {
136 return {fTextureProxy, kTextureOrigin,
137 caps.getReadSwizzle(fTextureProxy->backendFormat(), fColorType)};
138 }
139
writeView(const GrCaps & caps) const140 GrSurfaceProxyView GrDynamicAtlas::writeView(const GrCaps& caps) const {
141 return {fTextureProxy, kTextureOrigin,
142 caps.getWriteSwizzle(fTextureProxy->backendFormat(), fColorType)};
143 }
144
addRect(int width,int height,SkIPoint16 * location)145 bool GrDynamicAtlas::addRect(int width, int height, SkIPoint16* location) {
146 // This can't be called anymore once instantiate() has been called.
147 SkASSERT(!this->isInstantiated());
148
149 if (!this->internalPlaceRect(width, height, location)) {
150 return false;
151 }
152
153 fDrawBounds.fWidth = std::max(fDrawBounds.width(), location->x() + width);
154 fDrawBounds.fHeight = std::max(fDrawBounds.height(), location->y() + height);
155 return true;
156 }
157
internalPlaceRect(int w,int h,SkIPoint16 * loc)158 bool GrDynamicAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
159 if (std::max(h, w) > fMaxAtlasSize) {
160 return false;
161 }
162 if (std::min(h, w) <= 0) {
163 loc->set(0, 0);
164 return true;
165 }
166
167 if (!fTopNode) {
168 if (w > fWidth) {
169 fWidth = std::min(SkNextPow2(w), fMaxAtlasSize);
170 }
171 if (h > fHeight) {
172 fHeight = std::min(SkNextPow2(h), fMaxAtlasSize);
173 }
174 fTopNode = this->makeNode(nullptr, 0, 0, fWidth, fHeight);
175 }
176
177 for (Node* node = fTopNode; node; node = node->previous()) {
178 if (node->addRect(w, h, loc)) {
179 return true;
180 }
181 }
182
183 // The rect didn't fit. Grow the atlas and try again.
184 do {
185 if (fWidth >= fMaxAtlasSize && fHeight >= fMaxAtlasSize) {
186 return false;
187 }
188 if (fHeight <= fWidth) {
189 int top = fHeight;
190 fHeight = std::min(fHeight * 2, fMaxAtlasSize);
191 fTopNode = this->makeNode(fTopNode, 0, top, fWidth, fHeight);
192 } else {
193 int left = fWidth;
194 fWidth = std::min(fWidth * 2, fMaxAtlasSize);
195 fTopNode = this->makeNode(fTopNode, left, 0, fWidth, fHeight);
196 }
197 } while (!fTopNode->addRect(w, h, loc));
198
199 return true;
200 }
201
instantiate(GrOnFlushResourceProvider * onFlushRP,sk_sp<GrTexture> backingTexture)202 bool GrDynamicAtlas::instantiate(GrOnFlushResourceProvider* onFlushRP,
203 sk_sp<GrTexture> backingTexture) {
204 SkASSERT(!this->isInstantiated()); // This method should only be called once.
205 // Caller should have cropped any paths to the destination render target instead of asking for
206 // an atlas larger than maxRenderTargetSize.
207 SkASSERT(std::max(fHeight, fWidth) <= fMaxAtlasSize);
208 SkASSERT(fMaxAtlasSize <= onFlushRP->caps()->maxRenderTargetSize());
209
210 if (fTextureProxy->isFullyLazy()) {
211 // Finalize the content size of our proxy. The GPU can potentially make optimizations if it
212 // knows we only intend to write out a smaller sub-rectangle of the backing texture.
213 fTextureProxy->priv().setLazyDimensions(fDrawBounds);
214 }
215 SkASSERT(fTextureProxy->dimensions() == fDrawBounds);
216
217 if (backingTexture) {
218 #ifdef SK_DEBUG
219 auto backingRT = backingTexture->asRenderTarget();
220 SkASSERT(backingRT);
221 SkASSERT(backingRT->backendFormat() == fTextureProxy->backendFormat());
222 SkASSERT(backingRT->numSamples() == fTextureProxy->asRenderTargetProxy()->numSamples());
223 SkASSERT(backingRT->dimensions() == fTextureProxy->backingStoreDimensions());
224 #endif
225 // This works bc 'fTextureProxy' is a lazy proxy and, in its LazyInstantiateAtlasCallback,
226 // it will just wrap 'fBackingTexture' if it is non-null.
227 fBackingTexture = std::move(backingTexture);
228 }
229 return onFlushRP->instantiateProxy(fTextureProxy.get());
230 }
231