| /* |
| * 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 |