blob: 455d48be4fd6c7cb9b1a51889b1e21d525b6ed64 [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/gpu/graphite/Image_Base_Graphite.h"
#include "include/core/SkColorSpace.h"
#include "include/gpu/graphite/Image.h"
#include "include/gpu/graphite/Recorder.h"
#include "src/gpu/graphite/Device.h"
#include "src/gpu/graphite/DrawContext.h"
#include "src/gpu/graphite/Image_Graphite.h"
#include "src/gpu/graphite/Image_YUVA_Graphite.h"
#include "src/gpu/graphite/Log.h"
#include "src/gpu/graphite/RecorderPriv.h"
#include "src/gpu/graphite/Surface_Graphite.h"
#include "src/gpu/graphite/TextureUtils.h"
namespace skgpu::graphite {
Image_Base::Image_Base(const SkImageInfo& info, uint32_t uniqueID)
: SkImage_Base(info, uniqueID) {}
Image_Base::~Image_Base() = default;
void Image_Base::linkDevices(const Image_Base* other) {
SkASSERT(other);
SkAutoSpinlock lock{other->fDeviceLinkLock};
for (const auto& device : other->fLinkedDevices) {
this->linkDevice(device);
}
}
void Image_Base::linkDevice(sk_sp<Device> device) {
// Technically this lock isn't needed since this is only called before the Image is returned to
// user code that could expose it to multiple threads. But this quiets threading warnings and
// should be uncontested.
SkAutoSpinlock lock{fDeviceLinkLock};
fLinkedDevices.push_back(std::move(device));
}
void Image_Base::notifyInUse(Recorder* recorder, DrawContext* drawContext) const {
SkASSERT(recorder);
// The ref counts stored on each linked device are thread safe, but the Image's sk_sp's that
// track the refs its responsible for are *not* thread safe. Use a spin lock since the majority
// of device-linked images will be used only on the Recorder's thread. Since it should be
// uncontended, the empty check is also done inside the lock vs. a double-checked locking
// pattern that is non-trivial to ensure correctness in C++.
SkAutoSpinlock lock{fDeviceLinkLock};
if (!fLinkedDevices.empty()) {
int emptyCount = 0;
for (sk_sp<Device>& device : fLinkedDevices) {
if (!device) {
emptyCount++; // Already unlinked but array isn't empty yet
} else {
if (device->isScratchDevice()) {
sk_sp<Task> deviceDrawTask = device->lastDrawTask();
if (deviceDrawTask) {
// Increment the pending read count for the device's target
recorder->priv().addPendingRead(device->target());
if (drawContext) {
// Add a reference to the device's drawTask to `drawContext` if that's
// provided.
drawContext->recordDependency(std::move(deviceDrawTask));
} else {
// If there's no `drawContext` this notify represents a copy, so for
// now append the task to the root task list since that is where the
// subsequent copy task will go as well.
recorder->priv().add(std::move(deviceDrawTask));
}
} else {
// If there's no draw task yet, the device is being drawn into a child
// scratch device (backdrop filter or init-from-prev layer), and the child
// will later on be drawn back into the device's `drawContext`. In this case
// `device` should already have performed an internal flush and have no
// pending work, and not yet be marked immutable. The correct action at this
// point in time is to do nothing: the final task order in the device's
// DrawTask will be pre-notified tasks into the device's target, then the
// child's DrawTask when it's drawn back into `device`, and then any post
// tasks that further modify the `device`'s target.
SkASSERT(device->recorder() && device->recorder() == recorder);
}
// Scratch devices are often already marked immutable, but they are also the
// way in which Image finds the last snapped DrawTask so we don't unlink
// scratch devices. The scratch image view will be short-lived as well, or the
// device will transition to a non-scratch device in a future Recording and then
// it will be unlinked then.
} else {
// Automatic flushing of image views only happens when mixing reads and writes
// on the originating Recorder. Draws of the view on another Recorder will
// always see the texture content dependent on how Recordings are inserted.
if (device->recorder() == recorder) {
// Non-scratch devices push their tasks to the root task list to maintain
// an order consistent with the client-triggering actions. Because of this,
// there's no need to add references to the `drawContext` that the device
// is being drawn into.
device->flushPendingWorkToRecorder();
}
if (!device->recorder() || device->unique()) {
// The device will not record any more commands that modify the texture, so
// the image doesn't need to be linked
device.reset();
emptyCount++;
}
}
}
}
if (emptyCount == fLinkedDevices.size()) {
fLinkedDevices.clear();
}
}
}
bool Image_Base::isDynamic() const {
SkAutoSpinlock lock{fDeviceLinkLock};
int emptyCount = 0;
if (!fLinkedDevices.empty()) {
for (sk_sp<Device>& device : fLinkedDevices) {
if (!device || !device->recorder() || device->unique()) {
device.reset();
emptyCount++;
}
}
if (emptyCount == fLinkedDevices.size()) {
fLinkedDevices.clear();
emptyCount = 0;
}
}
return emptyCount > 0;
}
sk_sp<Image> Image_Base::copyImage(Recorder* recorder,
const SkIRect& subset,
Budgeted budgeted,
Mipmapped mipmapped,
SkBackingFit backingFit,
std::string_view label) const {
return CopyAsDraw(recorder, this, subset, this->imageInfo().colorInfo(),
budgeted, mipmapped, backingFit, std::move(label));
}
namespace {
TextureProxy* get_base_proxy_for_label(const Image_Base* baseImage) {
if (baseImage->type() == SkImage_Base::Type::kGraphite) {
const Image* img = static_cast<const Image*>(baseImage);
return img->textureProxyView().proxy();
}
SkASSERT(baseImage->type() == SkImage_Base::Type::kGraphiteYUVA);
// We will end up flattening to RGBA for a YUVA image when we get a subset. We just grab
// the label off of the first channel's proxy and use that to be the stand in label.
const Image_YUVA* img = static_cast<const Image_YUVA*>(baseImage);
return img->proxyView(0).proxy();
}
} // anonymous namespace
sk_sp<SkImage> Image_Base::onMakeSubset(Recorder* recorder,
const SkIRect& subset,
RequiredProperties requiredProps) const {
// optimization : return self if the subset == our bounds and requirements met and the image's
// texture is immutable
if (this->bounds() == subset &&
(!requiredProps.fMipmapped || this->hasMipmaps()) &&
!this->isDynamic()) {
return sk_ref_sp(this);
}
TextureProxy* proxy = get_base_proxy_for_label(this);
SkASSERT(proxy);
std::string label = proxy->label();
if (label.empty()) {
label = "ImageSubsetTexture";
} else {
label += "_Subset";
}
// The copied image is not considered budgeted because this is a client-invoked API and they
// will own the image.
return this->copyImage(recorder,
subset,
Budgeted::kNo,
requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo,
SkBackingFit::kExact,
label);
}
sk_sp<SkImage> Image_Base::makeColorTypeAndColorSpace(Recorder* recorder,
SkColorType targetCT,
sk_sp<SkColorSpace> targetCS,
RequiredProperties requiredProps) const {
SkColorInfo dstColorInfo{targetCT, this->alphaType(), std::move(targetCS)};
// optimization : return self if there's no color type/space change and the image's texture
// is immutable
if (this->imageInfo().colorInfo() == dstColorInfo && !this->isDynamic()) {
return sk_ref_sp(this);
}
TextureProxy* proxy = get_base_proxy_for_label(this);
SkASSERT(proxy);
std::string label = proxy->label();
if (label.empty()) {
label = "ImageMakeCTandCSTexture";
} else {
label += "_CTandCSConversion";
}
// Use CopyAsDraw directly to perform the color space changes. The copied image is not
// considered budgeted because this is a client-invoked API and they will own the image.
return CopyAsDraw(recorder,
this,
this->bounds(),
dstColorInfo,
Budgeted::kNo,
requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo,
SkBackingFit::kExact,
label);
}
// Ganesh APIs are no-ops
sk_sp<SkImage> Image_Base::onMakeSubset(GrDirectContext*, const SkIRect&) const {
SKGPU_LOG_W("Cannot convert Graphite-backed image to Ganesh");
return nullptr;
}
sk_sp<SkImage> Image_Base::onMakeColorTypeAndColorSpace(SkColorType,
sk_sp<SkColorSpace>,
GrDirectContext*) const {
SKGPU_LOG_W("Cannot convert Graphite-backed image to Ganesh");
return nullptr;
}
void Image_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
SkIRect srcRect,
RescaleGamma rescaleGamma,
RescaleMode rescaleMode,
ReadPixelsCallback callback,
ReadPixelsContext context) const {
SKGPU_LOG_W("Cannot use Ganesh async API with Graphite-backed image, use API on Context");
callback(context, nullptr);
}
void Image_Base::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
bool readAlpha,
sk_sp<SkColorSpace> dstColorSpace,
const SkIRect srcRect,
const SkISize dstSize,
RescaleGamma rescaleGamma,
RescaleMode rescaleMode,
ReadPixelsCallback callback,
ReadPixelsContext context) const {
SKGPU_LOG_W("Cannot use Ganesh async API with Graphite-backed image, use API on Context");
callback(context, nullptr);
}
} // namespace skgpu::graphite