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/Image_Base_Graphite.h"
9
10 #include "include/core/SkColorSpace.h"
11 #include "include/gpu/graphite/Image.h"
12 #include "include/gpu/graphite/Recorder.h"
13 #include "include/gpu/graphite/Surface.h"
14 #include "src/gpu/graphite/Device.h"
15 #include "src/gpu/graphite/DrawContext.h"
16 #include "src/gpu/graphite/Image_Graphite.h"
17 #include "src/gpu/graphite/Image_YUVA_Graphite.h"
18 #include "src/gpu/graphite/Log.h"
19 #include "src/gpu/graphite/RecorderPriv.h"
20 #include "src/gpu/graphite/Surface_Graphite.h"
21 #include "src/gpu/graphite/TextureUtils.h"
22
23 namespace skgpu::graphite {
24
Image_Base(const SkImageInfo & info,uint32_t uniqueID)25 Image_Base::Image_Base(const SkImageInfo& info, uint32_t uniqueID)
26 : SkImage_Base(info, uniqueID) {}
27
28 Image_Base::~Image_Base() = default;
29
linkDevices(const Image_Base * other)30 void Image_Base::linkDevices(const Image_Base* other) {
31 SkASSERT(other);
32
33 SkAutoSpinlock lock{other->fDeviceLinkLock};
34 for (const auto& device : other->fLinkedDevices) {
35 this->linkDevice(device);
36 }
37 }
38
linkDevice(sk_sp<Device> device)39 void Image_Base::linkDevice(sk_sp<Device> device) {
40 // Technically this lock isn't needed since this is only called before the Image is returned to
41 // user code that could expose it to multiple threads. But this quiets threading warnings and
42 // should be uncontested.
43 SkAutoSpinlock lock{fDeviceLinkLock};
44 fLinkedDevices.push_back(std::move(device));
45 }
46
notifyInUse(Recorder * recorder,DrawContext * drawContext) const47 void Image_Base::notifyInUse(Recorder* recorder, DrawContext* drawContext) const {
48 SkASSERT(recorder);
49
50 // The ref counts stored on each linked device are thread safe, but the Image's sk_sp's that
51 // track the refs its responsible for are *not* thread safe. Use a spin lock since the majority
52 // of device-linked images will be used only on the Recorder's thread. Since it should be
53 // uncontended, the empty check is also done inside the lock vs. a double-checked locking
54 // pattern that is non-trivial to ensure correctness in C++.
55 SkAutoSpinlock lock{fDeviceLinkLock};
56
57 if (!fLinkedDevices.empty()) {
58 int emptyCount = 0;
59 for (sk_sp<Device>& device : fLinkedDevices) {
60 if (!device) {
61 emptyCount++; // Already unlinked but array isn't empty yet
62 } else {
63 if (device->isScratchDevice()) {
64 sk_sp<Task> deviceDrawTask = device->lastDrawTask();
65 if (deviceDrawTask) {
66 // Increment the pending read count for the device's target
67 recorder->priv().addPendingRead(device->target());
68 if (drawContext) {
69 // Add a reference to the device's drawTask to `drawContext` if that's
70 // provided.
71 drawContext->recordDependency(std::move(deviceDrawTask));
72 } else {
73 // If there's no `drawContext` this notify represents a copy, so for
74 // now append the task to the root task list since that is where the
75 // subsequent copy task will go as well.
76 recorder->priv().add(std::move(deviceDrawTask));
77 }
78 } else {
79 // If there's no draw task yet, the device is being drawn into a child
80 // scratch device (backdrop filter or init-from-prev layer), and the child
81 // will later on be drawn back into the device's `drawContext`. In this case
82 // `device` should already have performed an internal flush and have no
83 // pending work, and not yet be marked immutable. The correct action at this
84 // point in time is to do nothing: the final task order in the device's
85 // DrawTask will be pre-notified tasks into the device's target, then the
86 // child's DrawTask when it's drawn back into `device`, and then any post
87 // tasks that further modify the `device`'s target.
88 SkASSERT(device->recorder() && device->recorder() == recorder);
89 }
90
91 // Scratch devices are often already marked immutable, but they are also the
92 // way in which Image finds the last snapped DrawTask so we don't unlink
93 // scratch devices. The scratch image view will be short-lived as well, or the
94 // device will transition to a non-scratch device in a future Recording and then
95 // it will be unlinked then.
96 } else {
97 // Automatic flushing of image views only happens when mixing reads and writes
98 // on the originating Recorder. Draws of the view on another Recorder will
99 // always see the texture content dependent on how Recordings are inserted.
100 if (device->recorder() == recorder) {
101 // Non-scratch devices push their tasks to the root task list to maintain
102 // an order consistent with the client-triggering actions. Because of this,
103 // there's no need to add references to the `drawContext` that the device
104 // is being drawn into.
105 device->flushPendingWorkToRecorder();
106 }
107 if (!device->recorder() || device->unique()) {
108 // The device will not record any more commands that modify the texture, so
109 // the image doesn't need to be linked
110 device.reset();
111 emptyCount++;
112 }
113 }
114 }
115 }
116
117 if (emptyCount == fLinkedDevices.size()) {
118 fLinkedDevices.clear();
119 }
120 }
121 }
122
isDynamic() const123 bool Image_Base::isDynamic() const {
124 SkAutoSpinlock lock{fDeviceLinkLock};
125 int emptyCount = 0;
126 if (!fLinkedDevices.empty()) {
127 for (sk_sp<Device>& device : fLinkedDevices) {
128 if (!device || !device->recorder() || device->unique()) {
129 device.reset();
130 emptyCount++;
131 }
132 }
133 if (emptyCount == fLinkedDevices.size()) {
134 fLinkedDevices.clear();
135 emptyCount = 0;
136 }
137 }
138
139 return emptyCount > 0;
140 }
141
copyImage(Recorder * recorder,const SkIRect & subset,Budgeted budgeted,Mipmapped mipmapped,SkBackingFit backingFit,std::string_view label) const142 sk_sp<Image> Image_Base::copyImage(Recorder* recorder,
143 const SkIRect& subset,
144 Budgeted budgeted,
145 Mipmapped mipmapped,
146 SkBackingFit backingFit,
147 std::string_view label) const {
148 return CopyAsDraw(recorder, this, subset, this->imageInfo().colorInfo(),
149 budgeted, mipmapped, backingFit, std::move(label));
150 }
151
152 namespace {
153
get_base_proxy_for_label(const Image_Base * baseImage)154 TextureProxy* get_base_proxy_for_label(const Image_Base* baseImage) {
155 if (baseImage->type() == SkImage_Base::Type::kGraphite) {
156 const Image* img = static_cast<const Image*>(baseImage);
157 return img->textureProxyView().proxy();
158 }
159 SkASSERT(baseImage->type() == SkImage_Base::Type::kGraphiteYUVA);
160 // We will end up flattening to RGBA for a YUVA image when we get a subset. We just grab
161 // the label off of the first channel's proxy and use that to be the stand in label.
162 const Image_YUVA* img = static_cast<const Image_YUVA*>(baseImage);
163 return img->proxyView(0).proxy();
164 }
165
166 } // anonymous namespace
167
onMakeSubset(Recorder * recorder,const SkIRect & subset,RequiredProperties requiredProps) const168 sk_sp<SkImage> Image_Base::onMakeSubset(Recorder* recorder,
169 const SkIRect& subset,
170 RequiredProperties requiredProps) const {
171 // optimization : return self if the subset == our bounds and requirements met and the image's
172 // texture is immutable
173 if (this->bounds() == subset &&
174 (!requiredProps.fMipmapped || this->hasMipmaps()) &&
175 !this->isDynamic()) {
176 return sk_ref_sp(this);
177 }
178
179 TextureProxy* proxy = get_base_proxy_for_label(this);
180 SkASSERT(proxy);
181 std::string label = proxy->label();
182 if (label.empty()) {
183 label = "ImageSubsetTexture";
184 } else {
185 label += "_Subset";
186 }
187
188 // The copied image is not considered budgeted because this is a client-invoked API and they
189 // will own the image.
190 return this->copyImage(recorder,
191 subset,
192 Budgeted::kNo,
193 requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo,
194 SkBackingFit::kExact,
195 label);
196 }
197
onMakeSurface(Recorder * recorder,const SkImageInfo & info) const198 sk_sp<SkSurface> Image_Base::onMakeSurface(Recorder* recorder, const SkImageInfo& info) const {
199 if (!recorder) {
200 return nullptr;
201 }
202 return SkSurfaces::RenderTarget(recorder, info);
203 }
204
makeColorTypeAndColorSpace(Recorder * recorder,SkColorType targetCT,sk_sp<SkColorSpace> targetCS,RequiredProperties requiredProps) const205 sk_sp<SkImage> Image_Base::makeColorTypeAndColorSpace(Recorder* recorder,
206 SkColorType targetCT,
207 sk_sp<SkColorSpace> targetCS,
208 RequiredProperties requiredProps) const {
209 SkColorInfo dstColorInfo{targetCT, this->alphaType(), std::move(targetCS)};
210 // optimization : return self if there's no color type/space change and the image's texture
211 // is immutable
212 if (this->imageInfo().colorInfo() == dstColorInfo && !this->isDynamic()) {
213 return sk_ref_sp(this);
214 }
215
216 TextureProxy* proxy = get_base_proxy_for_label(this);
217 SkASSERT(proxy);
218 std::string label = proxy->label();
219 if (label.empty()) {
220 label = "ImageMakeCTandCSTexture";
221 } else {
222 label += "_CTandCSConversion";
223 }
224
225 // Use CopyAsDraw directly to perform the color space changes. The copied image is not
226 // considered budgeted because this is a client-invoked API and they will own the image.
227 return CopyAsDraw(recorder,
228 this,
229 this->bounds(),
230 dstColorInfo,
231 Budgeted::kNo,
232 requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo,
233 SkBackingFit::kExact,
234 label);
235 }
236
237 // Ganesh APIs are no-ops
238
onMakeSubset(GrDirectContext *,const SkIRect &) const239 sk_sp<SkImage> Image_Base::onMakeSubset(GrDirectContext*, const SkIRect&) const {
240 SKGPU_LOG_W("Cannot convert Graphite-backed image to Ganesh");
241 return nullptr;
242 }
243
onMakeColorTypeAndColorSpace(SkColorType,sk_sp<SkColorSpace>,GrDirectContext *) const244 sk_sp<SkImage> Image_Base::onMakeColorTypeAndColorSpace(SkColorType,
245 sk_sp<SkColorSpace>,
246 GrDirectContext*) const {
247 SKGPU_LOG_W("Cannot convert Graphite-backed image to Ganesh");
248 return nullptr;
249 }
250
onAsyncRescaleAndReadPixels(const SkImageInfo & info,SkIRect srcRect,RescaleGamma rescaleGamma,RescaleMode rescaleMode,ReadPixelsCallback callback,ReadPixelsContext context) const251 void Image_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
252 SkIRect srcRect,
253 RescaleGamma rescaleGamma,
254 RescaleMode rescaleMode,
255 ReadPixelsCallback callback,
256 ReadPixelsContext context) const {
257 SKGPU_LOG_W("Cannot use Ganesh async API with Graphite-backed image, use API on Context");
258 callback(context, nullptr);
259 }
260
onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,bool readAlpha,sk_sp<SkColorSpace> dstColorSpace,const SkIRect srcRect,const SkISize dstSize,RescaleGamma rescaleGamma,RescaleMode rescaleMode,ReadPixelsCallback callback,ReadPixelsContext context) const261 void Image_Base::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
262 bool readAlpha,
263 sk_sp<SkColorSpace> dstColorSpace,
264 const SkIRect srcRect,
265 const SkISize dstSize,
266 RescaleGamma rescaleGamma,
267 RescaleMode rescaleMode,
268 ReadPixelsCallback callback,
269 ReadPixelsContext context) const {
270 SKGPU_LOG_W("Cannot use Ganesh async API with Graphite-backed image, use API on Context");
271 callback(context, nullptr);
272 }
273
274 } // namespace skgpu::graphite
275