xref: /aosp_15_r20/external/skia/src/gpu/graphite/Image_Base_Graphite.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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