/src/skia/src/gpu/graphite/Image_Base_Graphite.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
25 | | Image_Base::Image_Base(const SkImageInfo& info, uint32_t uniqueID) |
26 | 0 | : SkImage_Base(info, uniqueID) {} |
27 | | |
28 | 0 | Image_Base::~Image_Base() = default; |
29 | | |
30 | 0 | void Image_Base::linkDevices(const Image_Base* other) { |
31 | 0 | SkASSERT(other); |
32 | |
|
33 | 0 | SkAutoSpinlock lock{other->fDeviceLinkLock}; |
34 | 0 | for (const auto& device : other->fLinkedDevices) { |
35 | 0 | this->linkDevice(device); |
36 | 0 | } |
37 | 0 | } Unexecuted instantiation: skgpu::graphite::Image_Base::linkDevices(skgpu::graphite::Image_Base const*) Unexecuted instantiation: skgpu::graphite::Image_Base::linkDevices(skgpu::graphite::Image_Base const*) |
38 | | |
39 | 0 | 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 | 0 | SkAutoSpinlock lock{fDeviceLinkLock}; |
44 | 0 | fLinkedDevices.push_back(std::move(device)); |
45 | 0 | } |
46 | | |
47 | 0 | void Image_Base::notifyInUse(Recorder* recorder, DrawContext* drawContext) const { |
48 | 0 | 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 | 0 | SkAutoSpinlock lock{fDeviceLinkLock}; |
56 | |
|
57 | 0 | if (!fLinkedDevices.empty()) { |
58 | 0 | int emptyCount = 0; |
59 | 0 | for (sk_sp<Device>& device : fLinkedDevices) { |
60 | 0 | if (!device) { |
61 | 0 | emptyCount++; // Already unlinked but array isn't empty yet |
62 | 0 | } else { |
63 | 0 | if (device->isScratchDevice()) { |
64 | 0 | sk_sp<Task> deviceDrawTask = device->lastDrawTask(); |
65 | 0 | if (deviceDrawTask) { |
66 | | // Increment the pending read count for the device's target |
67 | 0 | recorder->priv().addPendingRead(device->target()); |
68 | 0 | if (drawContext) { |
69 | | // Add a reference to the device's drawTask to `drawContext` if that's |
70 | | // provided. |
71 | 0 | drawContext->recordDependency(std::move(deviceDrawTask)); |
72 | 0 | } 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 | 0 | recorder->priv().add(std::move(deviceDrawTask)); |
77 | 0 | } |
78 | 0 | } 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 | 0 | SkASSERT(device->recorder() && device->recorder() == recorder); |
89 | 0 | } |
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 | 0 | } 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 | 0 | 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 | 0 | device->flushPendingWorkToRecorder(); |
106 | 0 | } |
107 | 0 | 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 | 0 | device.reset(); |
111 | 0 | emptyCount++; |
112 | 0 | } |
113 | 0 | } |
114 | 0 | } |
115 | 0 | } |
116 | |
|
117 | 0 | if (emptyCount == fLinkedDevices.size()) { |
118 | 0 | fLinkedDevices.clear(); |
119 | 0 | } |
120 | 0 | } |
121 | 0 | } Unexecuted instantiation: skgpu::graphite::Image_Base::notifyInUse(skgpu::graphite::Recorder*, skgpu::graphite::DrawContext*) const Unexecuted instantiation: skgpu::graphite::Image_Base::notifyInUse(skgpu::graphite::Recorder*, skgpu::graphite::DrawContext*) const |
122 | | |
123 | 0 | bool Image_Base::isDynamic() const { |
124 | 0 | SkAutoSpinlock lock{fDeviceLinkLock}; |
125 | 0 | int emptyCount = 0; |
126 | 0 | if (!fLinkedDevices.empty()) { |
127 | 0 | for (sk_sp<Device>& device : fLinkedDevices) { |
128 | 0 | if (!device || !device->recorder() || device->unique()) { |
129 | 0 | device.reset(); |
130 | 0 | emptyCount++; |
131 | 0 | } |
132 | 0 | } |
133 | 0 | if (emptyCount == fLinkedDevices.size()) { |
134 | 0 | fLinkedDevices.clear(); |
135 | 0 | emptyCount = 0; |
136 | 0 | } |
137 | 0 | } |
138 | |
|
139 | 0 | return emptyCount > 0; |
140 | 0 | } |
141 | | |
142 | | sk_sp<Image> Image_Base::copyImage(Recorder* recorder, |
143 | | const SkIRect& subset, |
144 | | Budgeted budgeted, |
145 | | Mipmapped mipmapped, |
146 | | SkBackingFit backingFit, |
147 | 0 | std::string_view label) const { |
148 | 0 | return CopyAsDraw(recorder, this, subset, this->imageInfo().colorInfo(), |
149 | 0 | budgeted, mipmapped, backingFit, std::move(label)); |
150 | 0 | } |
151 | | |
152 | | namespace { |
153 | | |
154 | 0 | TextureProxy* get_base_proxy_for_label(const Image_Base* baseImage) { |
155 | 0 | if (baseImage->type() == SkImage_Base::Type::kGraphite) { |
156 | 0 | const Image* img = static_cast<const Image*>(baseImage); |
157 | 0 | return img->textureProxyView().proxy(); |
158 | 0 | } |
159 | 0 | 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 | 0 | const Image_YUVA* img = static_cast<const Image_YUVA*>(baseImage); |
163 | 0 | return img->proxyView(0).proxy(); |
164 | 0 | } Unexecuted instantiation: Image_Base_Graphite.cpp:skgpu::graphite::(anonymous namespace)::get_base_proxy_for_label(skgpu::graphite::Image_Base const*) Unexecuted instantiation: Image_Base_Graphite.cpp:skgpu::graphite::(anonymous namespace)::get_base_proxy_for_label(skgpu::graphite::Image_Base const*) |
165 | | |
166 | | } // anonymous namespace |
167 | | |
168 | | sk_sp<SkImage> Image_Base::onMakeSubset(Recorder* recorder, |
169 | | const SkIRect& subset, |
170 | 0 | RequiredProperties requiredProps) const { |
171 | | // optimization : return self if the subset == our bounds and requirements met and the image's |
172 | | // texture is immutable |
173 | 0 | if (this->bounds() == subset && |
174 | 0 | (!requiredProps.fMipmapped || this->hasMipmaps()) && |
175 | 0 | !this->isDynamic()) { |
176 | 0 | return sk_ref_sp(this); |
177 | 0 | } |
178 | | |
179 | 0 | TextureProxy* proxy = get_base_proxy_for_label(this); |
180 | 0 | SkASSERT(proxy); |
181 | 0 | std::string label = proxy->label(); |
182 | 0 | if (label.empty()) { |
183 | 0 | label = "ImageSubsetTexture"; |
184 | 0 | } else { |
185 | 0 | label += "_Subset"; |
186 | 0 | } |
187 | | |
188 | | // The copied image is not considered budgeted because this is a client-invoked API and they |
189 | | // will own the image. |
190 | 0 | return this->copyImage(recorder, |
191 | 0 | subset, |
192 | 0 | Budgeted::kNo, |
193 | 0 | requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo, |
194 | 0 | SkBackingFit::kExact, |
195 | 0 | label); |
196 | 0 | } Unexecuted instantiation: skgpu::graphite::Image_Base::onMakeSubset(skgpu::graphite::Recorder*, SkIRect const&, SkImage::RequiredProperties) const Unexecuted instantiation: skgpu::graphite::Image_Base::onMakeSubset(skgpu::graphite::Recorder*, SkIRect const&, SkImage::RequiredProperties) const |
197 | | |
198 | 0 | sk_sp<SkSurface> Image_Base::onMakeSurface(SkRecorder* recorder, const SkImageInfo& info) const { |
199 | 0 | auto gRecorder = AsGraphiteRecorder(recorder); |
200 | 0 | if (!gRecorder) { |
201 | 0 | return nullptr; |
202 | 0 | } |
203 | 0 | return SkSurfaces::RenderTarget(gRecorder, info); |
204 | 0 | } |
205 | | |
206 | | sk_sp<SkImage> Image_Base::makeColorTypeAndColorSpace(SkRecorder* recorder, |
207 | | SkColorType targetCT, |
208 | | sk_sp<SkColorSpace> targetCS, |
209 | 0 | RequiredProperties requiredProps) const { |
210 | 0 | auto gRecorder = AsGraphiteRecorder(recorder); |
211 | 0 | if (!gRecorder) { |
212 | 0 | return nullptr; |
213 | 0 | } |
214 | | |
215 | 0 | SkColorInfo dstColorInfo{targetCT, this->alphaType(), std::move(targetCS)}; |
216 | | // optimization : return self if there's no color type/space change and the image's texture |
217 | | // is immutable |
218 | 0 | if (this->imageInfo().colorInfo() == dstColorInfo && !this->isDynamic()) { |
219 | 0 | return sk_ref_sp(this); |
220 | 0 | } |
221 | | |
222 | 0 | TextureProxy* proxy = get_base_proxy_for_label(this); |
223 | 0 | SkASSERT(proxy); |
224 | 0 | std::string label = proxy->label(); |
225 | 0 | if (label.empty()) { |
226 | 0 | label = "ImageMakeCTandCSTexture"; |
227 | 0 | } else { |
228 | 0 | label += "_CTandCSConversion"; |
229 | 0 | } |
230 | | |
231 | | // Use CopyAsDraw directly to perform the color space changes. The copied image is not |
232 | | // considered budgeted because this is a client-invoked API and they will own the image. |
233 | 0 | return CopyAsDraw(gRecorder, |
234 | 0 | this, |
235 | 0 | this->bounds(), |
236 | 0 | dstColorInfo, |
237 | 0 | Budgeted::kNo, |
238 | 0 | requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo, |
239 | 0 | SkBackingFit::kExact, |
240 | 0 | label); |
241 | 0 | } Unexecuted instantiation: skgpu::graphite::Image_Base::makeColorTypeAndColorSpace(SkRecorder*, SkColorType, sk_sp<SkColorSpace>, SkImage::RequiredProperties) const Unexecuted instantiation: skgpu::graphite::Image_Base::makeColorTypeAndColorSpace(SkRecorder*, SkColorType, sk_sp<SkColorSpace>, SkImage::RequiredProperties) const |
242 | | |
243 | | // Ganesh APIs are no-ops |
244 | | |
245 | 0 | sk_sp<SkImage> Image_Base::onMakeSubset(GrDirectContext*, const SkIRect&) const { |
246 | 0 | SKGPU_LOG_W("Cannot convert Graphite-backed image to Ganesh"); |
247 | 0 | return nullptr; |
248 | 0 | } |
249 | | |
250 | | sk_sp<SkImage> Image_Base::onMakeColorTypeAndColorSpace(SkColorType, |
251 | | sk_sp<SkColorSpace>, |
252 | 0 | GrDirectContext*) const { |
253 | 0 | SKGPU_LOG_W("Cannot convert Graphite-backed image to Ganesh"); |
254 | 0 | return nullptr; |
255 | 0 | } |
256 | | |
257 | | void Image_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info, |
258 | | SkIRect srcRect, |
259 | | RescaleGamma rescaleGamma, |
260 | | RescaleMode rescaleMode, |
261 | | ReadPixelsCallback callback, |
262 | 0 | ReadPixelsContext context) const { |
263 | 0 | SKGPU_LOG_W("Cannot use Ganesh async API with Graphite-backed image, use API on Context"); |
264 | 0 | callback(context, nullptr); |
265 | 0 | } |
266 | | |
267 | | void Image_Base::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, |
268 | | bool readAlpha, |
269 | | sk_sp<SkColorSpace> dstColorSpace, |
270 | | const SkIRect srcRect, |
271 | | const SkISize dstSize, |
272 | | RescaleGamma rescaleGamma, |
273 | | RescaleMode rescaleMode, |
274 | | ReadPixelsCallback callback, |
275 | 0 | ReadPixelsContext context) const { |
276 | 0 | SKGPU_LOG_W("Cannot use Ganesh async API with Graphite-backed image, use API on Context"); |
277 | 0 | callback(context, nullptr); |
278 | 0 | } |
279 | | |
280 | | } // namespace skgpu::graphite |