| /* | |
| * Copyright 2021 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/Surface_Graphite.h" | |
| #include "include/core/SkCapabilities.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkRecorder.h" | |
| #include "include/gpu/graphite/BackendTexture.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "src/core/SkSurfacePriv.h" | |
| #include "src/gpu/RefCntedCallback.h" | |
| #include "src/gpu/SkBackingFit.h" | |
| #include "src/gpu/graphite/Caps.h" | |
| #include "src/gpu/graphite/Device.h" | |
| #include "src/gpu/graphite/Image_Graphite.h" | |
| #include "src/gpu/graphite/Log.h" | |
| #include "src/gpu/graphite/RecorderPriv.h" | |
| #include "src/gpu/graphite/ResourceProvider.h" | |
| #include "src/gpu/graphite/Texture.h" | |
| #include "src/gpu/graphite/TextureFormat.h" | |
| namespace skgpu::graphite { | |
| Surface::Surface(sk_sp<Device> device) | |
| : SkSurface_Base(device->width(), device->height(), &device->surfaceProps()) | |
| , fDevice(std::move(device)) | |
| , fImageView(Image::WrapDevice(fDevice)) {} | |
| Surface::~Surface() { | |
| // Mark the device immutable when the Surface is destroyed to flush any pending work to the | |
| // recorder and to flag the device so that any linked image views can detach from the Device | |
| // when they are next drawn. | |
| fDevice->setImmutable(); | |
| } | |
| SkImageInfo Surface::imageInfo() const { | |
| return fDevice->imageInfo(); | |
| } | |
| Recorder* Surface::onGetRecorder() const { return fDevice->recorder(); } | |
| SkRecorder* Surface::onGetBaseRecorder() const { return fDevice->recorder(); } | |
| TextureProxyView Surface::readSurfaceView() const { | |
| return fDevice->readSurfaceView(); | |
| } | |
| SkCanvas* Surface::onNewCanvas() { return new SkCanvas(fDevice); } | |
| sk_sp<SkSurface> Surface::onNewSurface(const SkImageInfo& ii) { | |
| return fDevice->makeSurface(ii, this->props()); | |
| } | |
| sk_sp<SkImage> Surface::onNewImageSnapshot(const SkIRect* subset) { | |
| return this->makeImageCopy(subset, fDevice->target()->mipmapped()); | |
| } | |
| sk_sp<Image> Surface::asImage() const { | |
| if (this->hasCachedImage()) { | |
| SKGPU_LOG_W("Intermingling makeImageSnapshot and asImage calls may produce " | |
| "unexpected results. Please use either the old _or_ new API."); | |
| } | |
| return fImageView; | |
| } | |
| sk_sp<SkImage> Surface::onMakeTemporaryImage() { | |
| if (this->hasCachedImage()) { | |
| SKGPU_LOG_W("Intermingling makeImageSnapshot and makeTemporaryImage calls may produce " | |
| "unexpected results. Please use either the old _or_ new API."); | |
| } | |
| return this->asImage(); | |
| } | |
| sk_sp<Image> Surface::makeImageCopy(const SkIRect* subset, Mipmapped mipmapped) const { | |
| if (this->hasCachedImage()) { | |
| SKGPU_LOG_W("Intermingling makeImageSnapshot and asImage calls may produce " | |
| "unexpected results. Please use either the old _or_ new API."); | |
| } | |
| SkIRect srcRect = subset ? *subset : SkIRect::MakeSize(this->imageInfo().dimensions()); | |
| // NOTE: Must copy through fDevice and not fImageView if the surface's texture is not sampleable | |
| return fDevice->makeImageCopy(srcRect, Budgeted::kNo, mipmapped, SkBackingFit::kExact); | |
| } | |
| void Surface::onWritePixels(const SkPixmap& pixmap, int x, int y) { | |
| fDevice->writePixels(pixmap, x, y); | |
| } | |
| bool Surface::onCopyOnWrite(ContentChangeMode) { return true; } | |
| void Surface::onAsyncRescaleAndReadPixels(const SkImageInfo& info, | |
| SkIRect srcRect, | |
| RescaleGamma rescaleGamma, | |
| RescaleMode rescaleMode, | |
| ReadPixelsCallback callback, | |
| ReadPixelsContext context) { | |
| // Not supported for Graphite. Use Context::asyncRescaleAndReadPixels instead. | |
| callback(context, nullptr); | |
| } | |
| void Surface::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace, | |
| bool readAlpha, | |
| sk_sp<SkColorSpace> dstColorSpace, | |
| SkIRect srcRect, | |
| SkISize dstSize, | |
| RescaleGamma rescaleGamma, | |
| RescaleMode rescaleMode, | |
| ReadPixelsCallback callback, | |
| ReadPixelsContext context) { | |
| // Not supported for Graphite. Use Context::asyncRescaleAndReadPixelsYUV420 instead. | |
| callback(context, nullptr); | |
| } | |
| sk_sp<const SkCapabilities> Surface::onCapabilities() { | |
| return fDevice->recorder()->priv().caps()->capabilities(); | |
| } | |
| TextureProxy* Surface::backingTextureProxy() const { return fDevice->target(); } | |
| // Note, devices flushed with this method add their tasks to the provided drawContext's task list, | |
| // but no last task is tracked. If no drawContext is provided, the task is added to the root task | |
| // list and if the device is a scratch device, the last task is recorded. | |
| void Surface::flushToDrawContext(DrawContext* drawContext) { | |
| this->fDevice->flushPendingWork(drawContext); | |
| } | |
| sk_sp<Surface> Surface::Make(Recorder* recorder, | |
| const SkImageInfo& info, | |
| std::string_view label, | |
| Budgeted budgeted, | |
| Mipmapped mipmapped, | |
| SkBackingFit backingFit, | |
| const SkSurfaceProps* props, | |
| LoadOp initialLoadOp, | |
| bool registerWithRecorder) { | |
| sk_sp<Device> device = Device::Make(recorder, | |
| info, | |
| budgeted, | |
| mipmapped, | |
| backingFit, | |
| SkSurfacePropsCopyOrDefault(props), | |
| initialLoadOp, | |
| std::move(label), | |
| registerWithRecorder); | |
| if (!device) { | |
| return nullptr; | |
| } | |
| // A non-budgeted surface should be fully instantiated before we return it | |
| // to the client. | |
| SkASSERT(budgeted == Budgeted::kYes || device->target()->isInstantiated()); | |
| return sk_make_sp<Surface>(std::move(device)); | |
| } | |
| void Flush(sk_sp<SkSurface> surface) { | |
| return Flush(surface.get()); | |
| } | |
| void Flush(SkSurface* surface) { | |
| if (!surface) { | |
| return; | |
| } | |
| auto sb = asSB(surface); | |
| if (!sb->isGraphiteBacked()) { | |
| return; | |
| } | |
| auto gs = static_cast<Surface*>(surface); | |
| gs->fDevice->flushPendingWork(/*drawContext=*/nullptr); | |
| } | |
| } // namespace skgpu::graphite | |
| using namespace skgpu::graphite; | |
| namespace { | |
| bool validate_backend_texture(const Caps* caps, | |
| const BackendTexture& texture, | |
| const SkColorInfo& info) { | |
| if (!texture.isValid() || | |
| texture.dimensions().width() <= 0 || | |
| texture.dimensions().height() <= 0) { | |
| return false; | |
| } | |
| if (!SkColorInfoIsValid(info)) { | |
| return false; | |
| } | |
| if (!caps->isRenderable(texture.info())) { | |
| return false; | |
| } | |
| return caps->areColorTypeAndTextureInfoCompatible(info.colorType(), texture.info()); | |
| } | |
| } // anonymous namespace | |
| namespace SkSurfaces { | |
| sk_sp<SkImage> AsImage(sk_sp<const SkSurface> surface) { | |
| if (!surface) { | |
| return nullptr; | |
| } | |
| auto sb = asConstSB(surface.get()); | |
| if (!sb->isGraphiteBacked()) { | |
| return nullptr; | |
| } | |
| auto gs = static_cast<const Surface*>(surface.get()); | |
| return gs->asImage(); | |
| } | |
| sk_sp<SkImage> AsImageCopy(sk_sp<const SkSurface> surface, | |
| const SkIRect* subset, | |
| skgpu::Mipmapped mipmapped) { | |
| if (!surface) { | |
| return nullptr; | |
| } | |
| auto sb = asConstSB(surface.get()); | |
| if (!sb->isGraphiteBacked()) { | |
| return nullptr; | |
| } | |
| auto gs = static_cast<const Surface*>(surface.get()); | |
| return gs->makeImageCopy(subset, mipmapped); | |
| } | |
| sk_sp<SkSurface> RenderTarget(Recorder* recorder, | |
| const SkImageInfo& info, | |
| skgpu::Mipmapped mipmapped, | |
| const SkSurfaceProps* props, | |
| std::string_view label) { | |
| if (label.empty()) { | |
| label = "SkSurfaceRenderTarget"; | |
| } | |
| // The client is getting the ref on this surface so it must be unbudgeted. | |
| return skgpu::graphite::Surface::Make(recorder, info, std::move(label), skgpu::Budgeted::kNo, | |
| mipmapped, SkBackingFit::kExact, props); | |
| } | |
| sk_sp<SkSurface> WrapBackendTexture(Recorder* recorder, | |
| const BackendTexture& backendTex, | |
| SkColorType ct, | |
| sk_sp<SkColorSpace> cs, | |
| const SkSurfaceProps* props, | |
| TextureReleaseProc releaseP, | |
| ReleaseContext releaseC, | |
| std::string_view label) { | |
| auto releaseHelper = skgpu::RefCntedCallback::Make(releaseP, releaseC); | |
| if (!recorder) { | |
| return nullptr; | |
| } | |
| const Caps* caps = recorder->priv().caps(); | |
| SkColorInfo info(ct, kPremul_SkAlphaType, std::move(cs)); | |
| if (!validate_backend_texture(caps, backendTex, info)) { | |
| SKGPU_LOG_E("validate_backend_texture failed: backendTex.info = %s; colorType = %d", | |
| backendTex.info().toString().c_str(), | |
| info.colorType()); | |
| return nullptr; | |
| } | |
| if (label.empty()) { | |
| label = "SkSurfaceWrappedTexture"; | |
| } | |
| sk_sp<Texture> texture = | |
| recorder->priv().resourceProvider()->createWrappedTexture(backendTex, std::move(label)); | |
| if (!texture) { | |
| return nullptr; | |
| } | |
| texture->setReleaseCallback(std::move(releaseHelper)); | |
| sk_sp<TextureProxy> proxy = TextureProxy::Wrap(std::move(texture)); | |
| SkISize deviceSize = proxy->dimensions(); | |
| // Use kLoad for this device to preserve the existing contents of the wrapped backend texture. | |
| sk_sp<Device> device = Device::Make(recorder, | |
| std::move(proxy), | |
| deviceSize, | |
| info, | |
| SkSurfacePropsCopyOrDefault(props), | |
| LoadOp::kLoad); | |
| return device ? sk_make_sp<Surface>(std::move(device)) : nullptr; | |
| } | |
| } // namespace SkSurfaces |