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