Coverage Report

Created: 2025-06-24 08:20

/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