blob: 69cf1ca9e4454fd7f2c115b47dfa24d5d7c7874e [file] [log] [blame]
Chris Daltona550cf22020-02-07 13:35:31 -07001/*
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/GrDynamicAtlas.h"
9
10#include "src/gpu/GrOnFlushResourceProvider.h"
11#include "src/gpu/GrProxyProvider.h"
12#include "src/gpu/GrRectanizerSkyline.h"
13#include "src/gpu/GrRenderTarget.h"
14#include "src/gpu/GrRenderTargetContext.h"
15
16// Each Node covers a sub-rectangle of the final atlas. When a GrDynamicAtlas runs out of room, we
17// create a new Node the same size as all combined nodes in the atlas as-is, and then place the new
18// Node immediately below or beside the others (thereby doubling the size of the GyDynamicAtlas).
19class GrDynamicAtlas::Node {
20public:
21 Node(std::unique_ptr<Node> previous, int l, int t, int r, int b)
22 : fPrevious(std::move(previous)), fX(l), fY(t), fRectanizer(r - l, b - t) {}
23
24 Node* previous() const { return fPrevious.get(); }
25
26 bool addRect(int w, int h, SkIPoint16* loc) {
27 // Pad all paths except those that are expected to take up an entire physical texture.
28 if (w < fRectanizer.width()) {
29 w = std::min(w + kPadding, fRectanizer.width());
30 }
31 if (h < fRectanizer.height()) {
32 h = std::min(h + kPadding, fRectanizer.height());
33 }
34 if (!fRectanizer.addRect(w, h, loc)) {
35 return false;
36 }
37 loc->fX += fX;
38 loc->fY += fY;
39 return true;
40 }
41
42private:
43 const std::unique_ptr<Node> fPrevious;
44 const int fX, fY;
45 GrRectanizerSkyline fRectanizer;
46};
47
48sk_sp<GrTextureProxy> GrDynamicAtlas::MakeLazyAtlasProxy(
49 const LazyInstantiateAtlasCallback& callback, GrColorType colorType,
50 InternalMultisample internalMultisample, const GrCaps& caps,
51 GrSurfaceProxy::UseAllocator useAllocator) {
52 GrBackendFormat format = caps.getDefaultBackendFormat(colorType, GrRenderable::kYes);
Chris Daltonf83d0342020-02-10 14:19:30 -070053
54 int sampleCount = 1;
55 if (!caps.mixedSamplesSupport() && InternalMultisample::kYes == internalMultisample) {
56 sampleCount = caps.internalMultisampleCount(format);
57 }
Chris Daltona550cf22020-02-07 13:35:31 -070058
59 auto instantiate = [cb = std::move(callback), format, sampleCount](GrResourceProvider* rp) {
60 return cb(rp, format, sampleCount);
61 };
62
63 GrSwizzle readSwizzle = caps.getReadSwizzle(format, colorType);
64
65 sk_sp<GrTextureProxy> proxy = GrProxyProvider::MakeFullyLazyProxy(
66 std::move(instantiate), format, readSwizzle, GrRenderable::kYes, sampleCount,
Greg Daniel3a365112020-02-14 10:47:18 -050067 GrProtected::kNo, caps, useAllocator);
Chris Daltona550cf22020-02-07 13:35:31 -070068
69 return proxy;
70}
71
72GrDynamicAtlas::GrDynamicAtlas(GrColorType colorType, InternalMultisample internalMultisample,
73 SkISize initialSize, int maxAtlasSize, const GrCaps& caps)
74 : fColorType(colorType)
75 , fInternalMultisample(internalMultisample)
76 , fMaxAtlasSize(maxAtlasSize) {
77 SkASSERT(fMaxAtlasSize <= caps.maxTextureSize());
78 this->reset(initialSize, caps);
79}
80
81GrDynamicAtlas::~GrDynamicAtlas() {
82}
83
84void GrDynamicAtlas::reset(SkISize initialSize, const GrCaps& caps) {
85 fWidth = std::min(SkNextPow2(initialSize.width()), fMaxAtlasSize);
86 fHeight = std::min(SkNextPow2(initialSize.height()), fMaxAtlasSize);
87 fTopNode = nullptr;
88 fDrawBounds.setEmpty();
89 fTextureProxy = MakeLazyAtlasProxy(
90 [this](GrResourceProvider* resourceProvider, const GrBackendFormat& format,
91 int sampleCount) {
92 if (!fBackingTexture) {
93 fBackingTexture = resourceProvider->createTexture(
94 {fWidth, fHeight}, format, GrRenderable::kYes, sampleCount,
95 GrMipMapped::kNo, SkBudgeted::kYes, GrProtected::kNo);
96 }
97 return GrSurfaceProxy::LazyCallbackResult(fBackingTexture);
98 },
99 fColorType, fInternalMultisample, caps, GrSurfaceProxy::UseAllocator::kNo);
100 fBackingTexture = nullptr;
101}
102
103bool GrDynamicAtlas::addRect(const SkIRect& devIBounds, SkIVector* offset) {
104 // This can't be called anymore once instantiate() has been called.
105 SkASSERT(!this->isInstantiated());
106
107 SkIPoint16 location;
108 if (!this->internalPlaceRect(devIBounds.width(), devIBounds.height(), &location)) {
109 return false;
110 }
111 offset->set(location.x() - devIBounds.left(), location.y() - devIBounds.top());
112
113 fDrawBounds.fWidth = std::max(fDrawBounds.width(), location.x() + devIBounds.width());
114 fDrawBounds.fHeight = std::max(fDrawBounds.height(), location.y() + devIBounds.height());
115 return true;
116}
117
118bool GrDynamicAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
119 if (std::max(h, w) > fMaxAtlasSize) {
120 return false;
121 }
122 if (std::min(h, w) <= 0) {
123 loc->set(0, 0);
124 return true;
125 }
126
127 if (!fTopNode) {
128 if (w > fWidth) {
129 fWidth = std::min(SkNextPow2(w), fMaxAtlasSize);
130 }
131 if (h > fHeight) {
132 fHeight = std::min(SkNextPow2(h), fMaxAtlasSize);
133 }
134 fTopNode = std::make_unique<Node>(nullptr, 0, 0, fWidth, fHeight);
135 }
136
137 for (Node* node = fTopNode.get(); node; node = node->previous()) {
138 if (node->addRect(w, h, loc)) {
139 return true;
140 }
141 }
142
143 // The rect didn't fit. Grow the atlas and try again.
144 do {
145 if (fWidth >= fMaxAtlasSize && fHeight >= fMaxAtlasSize) {
146 return false;
147 }
148 if (fHeight <= fWidth) {
149 int top = fHeight;
150 fHeight = std::min(fHeight * 2, fMaxAtlasSize);
151 fTopNode = std::make_unique<Node>(std::move(fTopNode), 0, top, fWidth, fHeight);
152 } else {
153 int left = fWidth;
154 fWidth = std::min(fWidth * 2, fMaxAtlasSize);
155 fTopNode = std::make_unique<Node>(std::move(fTopNode), left, 0, fWidth, fHeight);
156 }
157 } while (!fTopNode->addRect(w, h, loc));
158
159 return true;
160}
161
162std::unique_ptr<GrRenderTargetContext> GrDynamicAtlas::instantiate(
163 GrOnFlushResourceProvider* onFlushRP, sk_sp<GrTexture> backingTexture) {
164 SkASSERT(!this->isInstantiated()); // This method should only be called once.
165 // Caller should have cropped any paths to the destination render target instead of asking for
166 // an atlas larger than maxRenderTargetSize.
167 SkASSERT(std::max(fHeight, fWidth) <= fMaxAtlasSize);
168 SkASSERT(fMaxAtlasSize <= onFlushRP->caps()->maxRenderTargetSize());
169
170 // Finalize the content size of our proxy. The GPU can potentially make optimizations if it
171 // knows we only intend to write out a smaller sub-rectangle of the backing texture.
172 fTextureProxy->priv().setLazyDimensions(fDrawBounds);
173
174 if (backingTexture) {
175#ifdef SK_DEBUG
176 auto backingRT = backingTexture->asRenderTarget();
177 SkASSERT(backingRT);
178 SkASSERT(backingRT->backendFormat() == fTextureProxy->backendFormat());
179 SkASSERT(backingRT->numSamples() == fTextureProxy->asRenderTargetProxy()->numSamples());
180 SkASSERT(backingRT->width() == fWidth);
181 SkASSERT(backingRT->height() == fHeight);
182#endif
183 fBackingTexture = std::move(backingTexture);
184 }
Greg Danield11ae2e2020-02-10 16:36:07 -0500185 auto rtc = onFlushRP->makeRenderTargetContext(fTextureProxy, kTextureOrigin, fColorType,
186 nullptr, nullptr);
Chris Daltona550cf22020-02-07 13:35:31 -0700187 if (!rtc) {
Chris Dalton5a5fe792020-02-15 11:41:30 -0700188 onFlushRP->printWarningMessage(SkStringPrintf(
189 "WARNING: failed to allocate a %ix%i atlas. Some masks will not be drawn.\n",
190 fWidth, fHeight).c_str());
Chris Daltona550cf22020-02-07 13:35:31 -0700191 return nullptr;
192 }
193
194 SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
195 rtc->clear(&clearRect, SK_PMColor4fTRANSPARENT,
196 GrRenderTargetContext::CanClearFullscreen::kYes);
197 return rtc;
198}