blob: 7bc753c04f66c8cc91cb823fe8d07cf699ca55c1 [file] [edit]
/*
* 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 "include/gpu/graphite/Surface.h"
#include "include/private/SkLog.h"
#include "include/private/SkPixelStorage.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/RecorderPriv.h"
#include "src/gpu/graphite/Surface_Graphite.h"
#include "src/gpu/graphite/Texture.h"
#include "src/gpu/graphite/TextureUtils.h"
#include "src/gpu/graphite/task/Task.h"
namespace skgpu::graphite {
Image_Base::Image_Base(const SkImageInfo& info, uint32_t uniqueID, sk_sp<SkPixelStorage> storage)
: SkImage_Base(info, uniqueID, std::move(storage)) {}
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,
bool unlinkDevices) const {
SkASSERT(recorder);
SkASSERT(!drawContext || !unlinkDevices); // unlinkDevices can't be used with a DrawContext
// 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 || device->notifyInUse(recorder, drawContext) || unlinkDevices) {
// Already unlinked or notifyInUse() signals the device doesn't need to be linked
// anymore. reset() is a no-op if device is already holding null. If we are force
// unlinking a device, it should already be immutable (which do not return true from
// notifyInUse for scratch devices, hence the forcing).
SkASSERT(!device || !unlinkDevices || !device->recorder());
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;
}
namespace {
// Uses the label of the first proxy, since cases where we chain to make a new image will flatten
// any YUVA image.
std::string get_chained_label(const Image_Base* baseImage,
const char* forEmpty,
const char* forConcat) {
SkASSERT(baseImage && !baseImage->textureProxyViews().empty());
TextureProxy* proxy = baseImage->textureProxyViews()[0].proxy();
SkASSERT(proxy);
std::string label = proxy->label();
if (label.empty()) {
label = forEmpty;
} else {
label += forConcat;
}
return label;
}
} // anonymous namespace
sk_sp<Image_Base> Image_Base::makeNonBudgeted(Recorder* recorder) {
// First iterate the proxies held by the image and see if they are instantiated or need to be
// updated to Budgeted::kNo.
bool needsInstantiation = false;
for (TextureProxyView& view : this->textureProxyViews()) {
if (view.proxy()->isInstantiated()) {
// At this point, the properties of the TextureProxy are locked in.
const Texture* texture = view.proxy()->texture();
if (texture->budgeted() != Budgeted::kNo || texture->shareable() != Shareable::kNo) {
// Not compatible but instantiated, so make a copy that is non-budgeted.
return this->copyImage(recorder,
this->bounds(),
Budgeted::kNo,
this->hasMipmaps() ? Mipmapped::kYes : Mipmapped::kNo,
SkBackingFit::kExact,
get_chained_label(this, "NonBudgeted", "_NonBudgeted"));
}
// else this proxy is already consistent with the contract, so continue to any other
// planes that might need to be updated.
} else {
needsInstantiation = true;
}
}
if (needsInstantiation) {
// There are presumably tasks (either in the root task list already or on a linked Device)
// that will initialize this image's proxy. Since the tasks reference the existing
// TextureProxy, we can't create a new proxy that is non-budgeted. Instead we modify it
// directly and then unlink this Image from its devices.
for (TextureProxyView& view : this->textureProxyViews()) {
if (!view.proxy()->isInstantiated()) {
view.proxy()->setBudgeted(Budgeted::kNo);
if (!view.proxy()->instantiate(recorder->priv().resourceProvider())) {
return nullptr;
}
} else {
SkASSERT(view.proxy()->texture()->budgeted() == Budgeted::kNo &&
view.proxy()->texture()->shareable() == Shareable::kNo);
}
}
this->unlinkDevices(recorder);
}
// At this point we already were not dynamic, or we unlinked all our devices, or we should not
// have reached here and returned a copy instead.
SkASSERT(!this->isDynamic());
return sk_ref_sp(this);
}
// For now, CopyAsDraw is called with a nullptr for the drawContext, which causes the draw task
// to be pushed onto the root task list. However, this could be given a drawContext in the future.
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,
/*drawContext=*/nullptr, this, subset, this->imageInfo().colorInfo(),
budgeted, mipmapped, backingFit, label);
}
sk_sp<SkImage> Image_Base::onMakeSubset(SkRecorder* recorder,
const SkIRect& subset,
RequiredProperties requiredProps) const {
auto gRecorder = AsGraphiteRecorder(recorder);
if (!gRecorder) {
return nullptr;
}
// 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);
}
// The copied image is not considered budgeted because this is a client-invoked API and they
// will own the image.
return this->copyImage(gRecorder,
subset,
Budgeted::kNo,
requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo,
SkBackingFit::kExact,
get_chained_label(this, "ImageSubsetTexture", "_Subset"));
}
sk_sp<SkSurface> Image_Base::onMakeSurface(SkRecorder* recorder, const SkImageInfo& info) const {
auto gRecorder = AsGraphiteRecorder(recorder);
if (!gRecorder) {
return nullptr;
}
return SkSurfaces::RenderTarget(gRecorder, info);
}
sk_sp<SkImage> Image_Base::makeColorTypeAndColorSpace(SkRecorder* recorder,
SkColorType targetCT,
sk_sp<SkColorSpace> targetCS,
RequiredProperties requiredProps) const {
auto gRecorder = AsGraphiteRecorder(recorder);
if (!gRecorder) {
return nullptr;
}
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);
}
// 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(gRecorder,
/*drawContext=*/nullptr,
this,
this->bounds(),
dstColorInfo,
Budgeted::kNo,
requiredProps.fMipmapped ? Mipmapped::kYes : Mipmapped::kNo,
SkBackingFit::kExact,
get_chained_label(this, "ImageMakeCTandCSTexture", "_CTandCSConversion"));
}
// Ganesh APIs are no-ops
void Image_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
SkIRect srcRect,
RescaleGamma rescaleGamma,
RescaleMode rescaleMode,
ReadPixelsCallback callback,
ReadPixelsContext context) const {
SKIA_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 {
SKIA_LOG_W("Cannot use Ganesh async API with Graphite-backed image, use API on Context");
callback(context, nullptr);
}
} // namespace skgpu::graphite