/* * 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/ResourceProvider.h" #include "include/core/SkSamplingOptions.h" #include "include/core/SkTileMode.h" #include "include/gpu/graphite/BackendTexture.h" #include "src/core/SkTraceEvent.h" #include "src/gpu/graphite/Buffer.h" #include "src/gpu/graphite/Caps.h" #include "src/gpu/graphite/CommandBuffer.h" #include "src/gpu/graphite/ComputePipeline.h" #include "src/gpu/graphite/ContextPriv.h" #include "src/gpu/graphite/ContextUtils.h" #include "src/gpu/graphite/GlobalCache.h" #include "src/gpu/graphite/GraphicsPipeline.h" #include "src/gpu/graphite/GraphicsPipelineDesc.h" #include "src/gpu/graphite/Log.h" #include "src/gpu/graphite/RenderPassDesc.h" #include "src/gpu/graphite/RendererProvider.h" #include "src/gpu/graphite/ResourceCache.h" #include "src/gpu/graphite/Sampler.h" #include "src/gpu/graphite/SharedContext.h" #include "src/gpu/graphite/Texture.h" #include "src/sksl/SkSLCompiler.h" namespace skgpu::graphite { // This is only used when tracing is enabled at compile time. [[maybe_unused]] static std::string to_str(const SharedContext* ctx, const GraphicsPipelineDesc& gpDesc, const RenderPassDesc& rpDesc) { const ShaderCodeDictionary* dict = ctx->shaderCodeDictionary(); const RenderStep* step = ctx->rendererProvider()->lookup(gpDesc.renderStepID()); return GetPipelineLabel(dict, rpDesc, step, gpDesc.paintParamsID()); } ResourceProvider::ResourceProvider(SharedContext* sharedContext,\ SingleOwner* singleOwner, uint32_t recorderID, size_t resourceBudget) : fSharedContext(sharedContext) , fResourceCache(ResourceCache::Make(singleOwner, recorderID, resourceBudget)) {} ResourceProvider::~ResourceProvider() { fResourceCache->shutdown(); } sk_sp ResourceProvider::findOrCreateGraphicsPipeline( const RuntimeEffectDictionary* runtimeDict, const GraphicsPipelineDesc& pipelineDesc, const RenderPassDesc& renderPassDesc, SkEnumBitMask pipelineCreationFlags) { auto globalCache = fSharedContext->globalCache(); UniqueKey pipelineKey = fSharedContext->caps()->makeGraphicsPipelineKey(pipelineDesc, renderPassDesc); sk_sp pipeline = globalCache->findGraphicsPipeline(pipelineKey); if (!pipeline) { // Haven't encountered this pipeline, so create a new one. Since pipelines are shared // across Recorders, we could theoretically create equivalent pipelines on different // threads. If this happens, GlobalCache returns the first-through-gate pipeline and we // discard the redundant pipeline. While this is wasted effort in the rare event of a race, // it allows pipeline creation to be performed without locking the global cache. // NOTE: The parameters to TRACE_EVENT are only evaluated inside an if-block when the // category is enabled. TRACE_EVENT1_ALWAYS( "skia.shaders", "createGraphicsPipeline", "desc", TRACE_STR_COPY(to_str(fSharedContext, pipelineDesc, renderPassDesc).c_str())); pipeline = this->createGraphicsPipeline(runtimeDict, pipelineDesc, renderPassDesc, pipelineCreationFlags); if (pipeline) { // TODO: Should we store a null pipeline if we failed to create one so that subsequent // usage immediately sees that the pipeline cannot be created, vs. retrying every time? pipeline = globalCache->addGraphicsPipeline(pipelineKey, std::move(pipeline)); } } return pipeline; } sk_sp ResourceProvider::findOrCreateComputePipeline( const ComputePipelineDesc& pipelineDesc) { auto globalCache = fSharedContext->globalCache(); UniqueKey pipelineKey = fSharedContext->caps()->makeComputePipelineKey(pipelineDesc); sk_sp pipeline = globalCache->findComputePipeline(pipelineKey); if (!pipeline) { pipeline = this->createComputePipeline(pipelineDesc); if (pipeline) { pipeline = globalCache->addComputePipeline(pipelineKey, std::move(pipeline)); } } return pipeline; } //////////////////////////////////////////////////////////////////////////////////////////////// sk_sp ResourceProvider::findOrCreateScratchTexture(SkISize dimensions, const TextureInfo& info, std::string_view label, skgpu::Budgeted budgeted) { SkASSERT(info.isValid()); static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); GraphiteResourceKey key; // Scratch textures are not shareable fSharedContext->caps()->buildKeyForTexture(dimensions, info, kType, Shareable::kNo, &key); return this->findOrCreateTextureWithKey(dimensions, info, key, std::move(label), budgeted); } sk_sp ResourceProvider::findOrCreateDepthStencilAttachment(SkISize dimensions, const TextureInfo& info) { SkASSERT(info.isValid()); static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); GraphiteResourceKey key; // We always make depth and stencil attachments shareable. Between any render pass the values // are reset. Thus it is safe to be used by multiple different render passes without worry of // stomping on each other's data. fSharedContext->caps()->buildKeyForTexture(dimensions, info, kType, Shareable::kYes, &key); return this->findOrCreateTextureWithKey(dimensions, info, key, "DepthStencilAttachment", skgpu::Budgeted::kYes); } sk_sp ResourceProvider::findOrCreateDiscardableMSAAAttachment(SkISize dimensions, const TextureInfo& info) { SkASSERT(info.isValid()); static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); GraphiteResourceKey key; // We always make discardable msaa attachments shareable. Between any render pass we discard // the values of the MSAA texture. Thus it is safe to be used by multiple different render // passes without worry of stomping on each other's data. It is the callings code's // responsibility to populate the discardable MSAA texture with data at the start of the // render pass. fSharedContext->caps()->buildKeyForTexture(dimensions, info, kType, Shareable::kYes, &key); return this->findOrCreateTextureWithKey(dimensions, info, key, "DiscardableMSAAAttachment", skgpu::Budgeted::kYes); } sk_sp ResourceProvider::findOrCreateTextureWithKey(SkISize dimensions, const TextureInfo& info, const GraphiteResourceKey& key, std::string_view label, skgpu::Budgeted budgeted) { // If the resource is shareable it should be budgeted since it shouldn't be backing any client // owned object. SkASSERT(key.shareable() == Shareable::kNo || budgeted == skgpu::Budgeted::kYes); if (Resource* resource = fResourceCache->findAndRefResource(key, budgeted)) { resource->setLabel(std::move(label)); return sk_sp(static_cast(resource)); } auto tex = this->createTexture(dimensions, info, budgeted); if (!tex) { return nullptr; } tex->setKey(key); tex->setLabel(std::move(label)); fResourceCache->insertResource(tex.get()); return tex; } sk_sp ResourceProvider::createWrappedTexture(const BackendTexture& backendTexture, std::string_view label) { sk_sp texture = this->onCreateWrappedTexture(backendTexture); if (texture) { texture->setLabel(std::move(label)); } return texture; } sk_sp ResourceProvider::findOrCreateCompatibleSampler(const SamplerDesc& samplerDesc) { GraphiteResourceKey key = fSharedContext->caps()->makeSamplerKey(samplerDesc); if (Resource* resource = fResourceCache->findAndRefResource(key, skgpu::Budgeted::kYes)) { return sk_sp(static_cast(resource)); } sk_sp sampler = this->createSampler(samplerDesc); if (!sampler) { return nullptr; } sampler->setKey(key); fResourceCache->insertResource(sampler.get()); return sampler; } sk_sp ResourceProvider::findOrCreateBuffer(size_t size, BufferType type, AccessPattern accessPattern, std::string_view label) { static const ResourceType kType = GraphiteResourceKey::GenerateResourceType(); GraphiteResourceKey key; { // For the key we need ((sizeof(size_t) + (sizeof(uint32_t) - 1)) / (sizeof(uint32_t)) // uint32_t's for the size and one uint32_t for the rest. static_assert(sizeof(uint32_t) == 4); static const int kSizeKeyNum32DataCnt = (sizeof(size_t) + 3) / 4; static const int kKeyNum32DataCnt = kSizeKeyNum32DataCnt + 1; SkASSERT(static_cast(type) < (1u << 4)); SkASSERT(static_cast(accessPattern) < (1u << 1)); GraphiteResourceKey::Builder builder(&key, kType, kKeyNum32DataCnt, Shareable::kNo); builder[0] = (static_cast(type) << 0) | (static_cast(accessPattern) << 4); size_t szKey = size; for (int i = 0; i < kSizeKeyNum32DataCnt; ++i) { builder[i + 1] = (uint32_t) szKey; // If size_t is 4 bytes, we cannot do a shift of 32 or else we get a warning/error that // shift amount is >= width of the type. if constexpr(kSizeKeyNum32DataCnt > 1) { szKey = szKey >> 32; } } } skgpu::Budgeted budgeted = skgpu::Budgeted::kYes; if (Resource* resource = fResourceCache->findAndRefResource(key, budgeted)) { resource->setLabel(std::move(label)); return sk_sp(static_cast(resource)); } auto buffer = this->createBuffer(size, type, accessPattern); if (!buffer) { return nullptr; } buffer->setKey(key); buffer->setLabel(std::move(label)); fResourceCache->insertResource(buffer.get()); return buffer; } namespace { bool dimensions_are_valid(const int maxTextureSize, const SkISize& dimensions) { if (dimensions.isEmpty() || dimensions.width() > maxTextureSize || dimensions.height() > maxTextureSize) { SKGPU_LOG_W("Call to createBackendTexture has requested dimensions (%d, %d) larger than the" " supported gpu max texture size: %d. Or the dimensions are empty.", dimensions.fWidth, dimensions.fHeight, maxTextureSize); return false; } return true; } } BackendTexture ResourceProvider::createBackendTexture(SkISize dimensions, const TextureInfo& info) { if (!dimensions_are_valid(fSharedContext->caps()->maxTextureSize(), dimensions)) { return {}; } return this->onCreateBackendTexture(dimensions, info); } #ifdef SK_BUILD_FOR_ANDROID BackendTexture ResourceProvider::createBackendTexture(AHardwareBuffer* hardwareBuffer, bool isRenderable, bool isProtectedContent, SkISize dimensions, bool fromAndroidWindow) const { if (!dimensions_are_valid(fSharedContext->caps()->maxTextureSize(), dimensions)) { return {}; } return this->onCreateBackendTexture(hardwareBuffer, isRenderable, isProtectedContent, dimensions, fromAndroidWindow); } BackendTexture ResourceProvider::onCreateBackendTexture(AHardwareBuffer*, bool isRenderable, bool isProtectedContent, SkISize dimensions, bool fromAndroidWindow) const { return {}; } #endif void ResourceProvider::deleteBackendTexture(const BackendTexture& texture) { this->onDeleteBackendTexture(texture); } void ResourceProvider::freeGpuResources() { this->onFreeGpuResources(); // TODO: Are there Resources that are ref'd by the ResourceProvider or its subclasses that need // be released? If we ever find that we're holding things directly on the ResourceProviders we // call down into the subclasses to allow them to release things. fResourceCache->purgeResources(); } void ResourceProvider::purgeResourcesNotUsedSince(StdSteadyClock::time_point purgeTime) { this->onPurgeResourcesNotUsedSince(purgeTime); fResourceCache->purgeResourcesNotUsedSince(purgeTime); } } // namespace skgpu::graphite