/* * 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 "include/gpu/graphite/Recording.h" #include "src/core/SkChecksum.h" #include "src/gpu/RefCntedCallback.h" #include "src/gpu/graphite/CommandBuffer.h" #include "src/gpu/graphite/ContextPriv.h" #include "src/gpu/graphite/Log.h" #include "src/gpu/graphite/RecordingPriv.h" #include "src/gpu/graphite/Resource.h" #include "src/gpu/graphite/ResourceProvider.h" #include "src/gpu/graphite/Surface_Graphite.h" #include "src/gpu/graphite/Texture.h" #include "src/gpu/graphite/TextureProxy.h" #include "src/gpu/graphite/task/TaskList.h" #include using namespace skia_private; namespace skgpu::graphite { Recording::Recording(uint32_t uniqueID, uint32_t recorderID, std::unordered_set, ProxyHash>&& nonVolatileLazyProxies, std::unordered_set, ProxyHash>&& volatileLazyProxies, std::unique_ptr targetProxyData, TArray>&& finishedProcs) : fUniqueID(uniqueID) , fRecorderID(recorderID) , fRootTaskList(new TaskList) , fNonVolatileLazyProxies(std::move(nonVolatileLazyProxies)) , fVolatileLazyProxies(std::move(volatileLazyProxies)) , fTargetProxyData(std::move(targetProxyData)) , fFinishedProcs(std::move(finishedProcs)) {} Recording::~Recording() { // Any finished procs that haven't been passed to a CommandBuffer fail this->priv().setFailureResultForFinishedProcs(); } std::size_t Recording::ProxyHash::operator()(const sk_sp &proxy) const { return SkGoodHash()(proxy.get()); } Recording::LazyProxyData::LazyProxyData(const Caps* caps, SkISize dimensions, const TextureInfo& textureInfo) { auto onInstantiate = [this](ResourceProvider*) { SkASSERT(SkToBool(fTarget)); return std::move(fTarget); }; // If the texture info specifies that mipmapping is required, that implies that the final // surface used to instantiate this proxy will be mipmapped, and that the dimensions of that // surface are known already. fTargetProxy = textureInfo.mipmapped() == Mipmapped::kYes ? TextureProxy::MakeLazy(caps, dimensions, textureInfo, skgpu::Budgeted::kNo, Volatile::kYes, std::move(onInstantiate)) : TextureProxy::MakeFullyLazy(textureInfo, skgpu::Budgeted::kNo, Volatile::kYes, std::move(onInstantiate)); } TextureProxy* Recording::LazyProxyData::lazyProxy() { return fTargetProxy.get(); } sk_sp Recording::LazyProxyData::refLazyProxy() { return fTargetProxy; } bool Recording::LazyProxyData::lazyInstantiate(ResourceProvider* resourceProvider, sk_sp texture) { fTarget = std::move(texture); return fTargetProxy->lazyInstantiate(resourceProvider); } //////////////////////////////////////////////////////////////////////////////// bool RecordingPriv::hasNonVolatileLazyProxies() const { return !fRecording->fNonVolatileLazyProxies.empty(); } bool RecordingPriv::instantiateNonVolatileLazyProxies(ResourceProvider* resourceProvider) { SkASSERT(this->hasNonVolatileLazyProxies()); for (const auto& proxy : fRecording->fNonVolatileLazyProxies) { if (!proxy->lazyInstantiate(resourceProvider)) { return false; } } // Note: once all the lazy proxies have been instantiated, that's it - there are no more // chances to instantiate. fRecording->fNonVolatileLazyProxies.clear(); return true; } bool RecordingPriv::hasVolatileLazyProxies() const { return !fRecording->fVolatileLazyProxies.empty(); } bool RecordingPriv::instantiateVolatileLazyProxies(ResourceProvider* resourceProvider) { SkASSERT(this->hasVolatileLazyProxies()); for (const auto& proxy : fRecording->fVolatileLazyProxies) { if (!proxy->lazyInstantiate(resourceProvider)) { return false; } } return true; } void RecordingPriv::deinstantiateVolatileLazyProxies() { if (!this->hasVolatileLazyProxies()) { return; } for (const auto& proxy : fRecording->fVolatileLazyProxies) { SkASSERT(proxy->isVolatile()); proxy->deinstantiate(); } } void RecordingPriv::setFailureResultForFinishedProcs() { for (int i = 0; i < fRecording->fFinishedProcs.size(); ++i) { fRecording->fFinishedProcs[i]->setFailureResult(); } fRecording->fFinishedProcs.clear(); } TextureProxy* RecordingPriv::deferredTargetProxy() { return fRecording->fTargetProxyData ? fRecording->fTargetProxyData->lazyProxy() : nullptr; } const Texture* RecordingPriv::setupDeferredTarget(ResourceProvider* resourceProvider, Surface* targetSurface, SkIVector targetTranslation, SkIRect targetClip) { SkASSERT(targetSurface && fRecording->fTargetProxyData); TextureProxy* surfaceTexture = targetSurface->backingTextureProxy(); SkASSERT(surfaceTexture->isInstantiated()); const TextureProxy* targetProxy = fRecording->fTargetProxyData->lazyProxy(); if (surfaceTexture->mipmapped() != targetProxy->mipmapped()) { SKGPU_LOG_E("Deferred canvas mipmap settings don't match instantiating target's."); return nullptr; } // If the deferred canvas's texture proxy is not fully lazy, that means we used it for draws // that require specific dimensions and no translation. The only time this happens is when a // client requests a mipmapped deferred canvas and we automatically insert commands to // regenerate mipmaps. if (!targetProxy->isFullyLazy()) { SkASSERT(targetProxy->mipmapped() == skgpu::Mipmapped::kYes); if (targetProxy->dimensions() != surfaceTexture->dimensions()) { SKGPU_LOG_E( "Deferred canvas dimensions don't match instantiating target's dimensions."); return nullptr; } if (!targetTranslation.isZero()) { SKGPU_LOG_E( "Replay translation is not allowed when replaying draws to a mipmapped " "deferred canvas."); return nullptr; } if (!targetClip.isEmpty()) { SKGPU_LOG_E( "Replay clip is not allowed when replaying draws to a mipmapped deferred " "canvas."); return nullptr; } } if (!fRecording->fTargetProxyData->lazyInstantiate(resourceProvider, surfaceTexture->refTexture())) { SKGPU_LOG_E("Could not instantiate deferred texture proxy."); return nullptr; } return surfaceTexture->texture(); } bool RecordingPriv::addCommands(Context* context, CommandBuffer* commandBuffer, const Texture* replayTarget, SkIVector targetTranslation, SkIRect targetClip) { for (size_t i = 0; i < fRecording->fExtraResourceRefs.size(); ++i) { commandBuffer->trackResource(fRecording->fExtraResourceRefs[i]); } // There's no need to differentiate kSuccess and kDiscard at the root list level; if every task // is discarded, the Recording will automatically be a no-op on replay while still correctly // notifying any finish procs the client may have added. if (fRecording->fRootTaskList->addCommands( context, commandBuffer, {replayTarget, targetTranslation, targetClip}) == Task::Status::kFail) { return false; } for (int i = 0; i < fRecording->fFinishedProcs.size(); ++i) { commandBuffer->addFinishedProc(std::move(fRecording->fFinishedProcs[i])); } fRecording->fFinishedProcs.clear(); return true; } void RecordingPriv::addResourceRef(sk_sp resource) { fRecording->fExtraResourceRefs.push_back(std::move(resource)); } #if defined(GPU_TEST_UTILS) bool RecordingPriv::isTargetProxyInstantiated() const { return fRecording->fTargetProxyData->lazyProxy()->isInstantiated(); } int RecordingPriv::numVolatilePromiseImages() const { return fRecording->fVolatileLazyProxies.size(); } int RecordingPriv::numNonVolatilePromiseImages() const { return fRecording->fNonVolatileLazyProxies.size(); } bool RecordingPriv::hasTasks() const { return fRecording->fRootTaskList->hasTasks(); } #endif } // namespace skgpu::graphite