| /* | |
| * Copyright 2022 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/dawn/DawnResourceProvider.h" | |
| #include "include/core/SkString.h" | |
| #include "include/gpu/graphite/BackendTexture.h" | |
| #include "include/gpu/graphite/TextureInfo.h" | |
| #include "include/gpu/graphite/dawn/DawnGraphiteTypes.h" | |
| #include "include/private/SingleOwner.h" | |
| #include "include/private/SkAlign.h" | |
| #include "src/gpu/graphite/ComputePipeline.h" | |
| #include "src/gpu/graphite/RenderPassDesc.h" | |
| #include "src/gpu/graphite/StorageContext.h" | |
| #include "src/gpu/graphite/dawn/DawnBuffer.h" | |
| #include "src/gpu/graphite/dawn/DawnCommandBuffer.h" | |
| #include "src/gpu/graphite/dawn/DawnComputePipeline.h" | |
| #include "src/gpu/graphite/dawn/DawnErrorChecker.h" | |
| #include "src/gpu/graphite/dawn/DawnGraphicsPipeline.h" | |
| #include "src/gpu/graphite/dawn/DawnGraphiteUtils.h" | |
| #include "src/gpu/graphite/dawn/DawnSampler.h" | |
| #include "src/gpu/graphite/dawn/DawnSharedContext.h" | |
| #include "src/gpu/graphite/dawn/DawnTexture.h" | |
| #include "src/sksl/SkSLCompiler.h" | |
| namespace skgpu::graphite { | |
| namespace { | |
| constexpr uint32_t kBufferBindingSizeAlignment = 16; | |
| wgpu::ShaderModule create_shader_module(const wgpu::Device& device, const char* source) { | |
| #if defined(__EMSCRIPTEN__) | |
| wgpu::ShaderModuleWGSLDescriptor wgslDesc; | |
| #else | |
| wgpu::ShaderSourceWGSL wgslDesc; | |
| #endif | |
| wgslDesc.code = source; | |
| wgpu::ShaderModuleDescriptor descriptor; | |
| descriptor.nextInChain = &wgslDesc; | |
| return device.CreateShaderModule(&descriptor); | |
| } | |
| wgpu::RenderPipeline create_blit_render_pipeline(const DawnSharedContext* sharedContext, | |
| const char* label, | |
| wgpu::ShaderModule shaderModule, | |
| const char* vsEntryPoint, | |
| const char* fsEntryPoint, | |
| wgpu::TextureFormat renderPassColorFormat, | |
| wgpu::TextureFormat renderPassDepthStencilFormat, | |
| SampleCount sampleCount) { | |
| wgpu::RenderPipelineDescriptor descriptor; | |
| descriptor.label = label; | |
| descriptor.layout = nullptr; | |
| wgpu::ColorTargetState colorTarget; | |
| colorTarget.format = renderPassColorFormat; | |
| colorTarget.blend = nullptr; | |
| colorTarget.writeMask = wgpu::ColorWriteMask::All; | |
| wgpu::DepthStencilState depthStencil; | |
| if (renderPassDepthStencilFormat != wgpu::TextureFormat::Undefined) { | |
| depthStencil.format = renderPassDepthStencilFormat; | |
| depthStencil.depthWriteEnabled = false; | |
| depthStencil.depthCompare = wgpu::CompareFunction::Always; | |
| descriptor.depthStencil = &depthStencil; | |
| } | |
| wgpu::FragmentState fragment; | |
| fragment.module = shaderModule; | |
| fragment.entryPoint = fsEntryPoint; | |
| fragment.constantCount = 0; | |
| fragment.constants = nullptr; | |
| fragment.targetCount = 1; | |
| fragment.targets = &colorTarget; | |
| descriptor.fragment = &fragment; | |
| descriptor.vertex.module = shaderModule; | |
| descriptor.vertex.entryPoint = vsEntryPoint; | |
| descriptor.vertex.constantCount = 0; | |
| descriptor.vertex.constants = nullptr; | |
| descriptor.vertex.bufferCount = 0; | |
| descriptor.vertex.buffers = nullptr; | |
| descriptor.primitive.frontFace = wgpu::FrontFace::CCW; | |
| descriptor.primitive.cullMode = wgpu::CullMode::None; | |
| descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip; | |
| descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Undefined; | |
| descriptor.multisample.count = (uint8_t) sampleCount; | |
| descriptor.multisample.mask = 0xFFFFFFFF; | |
| descriptor.multisample.alphaToCoverageEnabled = false; | |
| std::optional<DawnErrorChecker> errorChecker; | |
| if (sharedContext->dawnCaps()->allowScopedErrorChecks()) { | |
| errorChecker.emplace(sharedContext); | |
| } | |
| auto pipeline = sharedContext->device().CreateRenderPipeline(&descriptor); | |
| if (errorChecker.has_value() && errorChecker->popErrorScopes() != DawnErrorType::kNoError) { | |
| return nullptr; | |
| } | |
| return pipeline; | |
| } | |
| } // namespace | |
| // Wraps a Dawn buffer, and tracks the intrinsic blocks residing in this buffer. | |
| class DawnResourceProvider::IntrinsicBuffer final { | |
| public: | |
| static constexpr int kNumSlots = 8; | |
| IntrinsicBuffer(sk_sp<DawnBuffer> dawnBuffer) : fDawnBuffer(std::move(dawnBuffer)) {} | |
| ~IntrinsicBuffer() = default; | |
| sk_sp<DawnBuffer> buffer() const { return fDawnBuffer; } | |
| // Track that 'intrinsicValues' is stored in the buffer at the 'offset'. | |
| void trackIntrinsic(UniformDataBlock intrinsicValues, uint32_t offset) { | |
| fCachedIntrinsicValues.set(UniformDataBlock::Make(intrinsicValues, &fUniformData), offset); | |
| } | |
| // Find the offset of 'intrinsicValues' in the buffer. If not found, return nullptr. | |
| uint32_t* findIntrinsic(UniformDataBlock intrinsicValues) const { | |
| return fCachedIntrinsicValues.find(intrinsicValues); | |
| } | |
| int slotsUsed() const { return fCachedIntrinsicValues.count(); } | |
| void updateAccessTime() { | |
| fLastAccess = skgpu::StdSteadyClock::now(); | |
| } | |
| skgpu::StdSteadyClock::time_point lastAccessTime() const { | |
| return fLastAccess; | |
| } | |
| private: | |
| skia_private::THashMap<UniformDataBlock, uint32_t, UniformDataBlock::Hash> | |
| fCachedIntrinsicValues; | |
| SkArenaAlloc fUniformData{0}; | |
| sk_sp<DawnBuffer> fDawnBuffer; | |
| skgpu::StdSteadyClock::time_point fLastAccess; | |
| SK_DECLARE_INTERNAL_LLIST_INTERFACE(IntrinsicBuffer); | |
| }; | |
| // DawnResourceProvider::IntrinsicConstantsManager | |
| // ---------------------------------------------------------------------------- | |
| /** | |
| * Since Dawn does not currently provide push constants, this helper class manages rotating through | |
| * buffers and writing each new occurrence of a set of intrinsic uniforms into the current buffer. | |
| */ | |
| class DawnResourceProvider::IntrinsicConstantsManager { | |
| public: | |
| explicit IntrinsicConstantsManager(DawnResourceProvider* resourceProvider) | |
| : fResourceProvider(resourceProvider) {} | |
| ~IntrinsicConstantsManager() { | |
| auto alwaysTrue = [](IntrinsicBuffer* buffer) { return true; }; | |
| this->purgeBuffersUntilDoneOrFalse(alwaysTrue); | |
| this->releasePendingIntrinsicBuffers(); | |
| SkASSERT(fIntrinsicBuffersLRU.isEmpty()); | |
| } | |
| // Find or create a bind buffer info for the given intrinsic values used in the given command | |
| // buffer. | |
| BindBufferInfo add(DawnCommandBuffer* cb, UniformDataBlock intrinsicValues); | |
| void purgeResourcesNotUsedSince(StdSteadyClock::time_point purgeTime, | |
| std::optional<StdSteadyClock::time_point> quitPurgingTime) { | |
| auto bufferShouldBePurged = [&](IntrinsicBuffer* buffer) { | |
| // We always keep the current buffer as it is likely to be used again soon. If we | |
| // surpass quitPurgingTime, further buffers should not be purged. | |
| return ( !quitPurgingTime.has_value() || | |
| skgpu::StdSteadyClock::now() < quitPurgingTime.value()) && | |
| (buffer != fCurrentBuffer && buffer->lastAccessTime() < purgeTime); | |
| }; | |
| this->purgeBuffersUntilDoneOrFalse(bufferShouldBePurged); | |
| } | |
| void releasePendingIntrinsicBuffers() { | |
| using Iter = SkTInternalLList<IntrinsicBuffer>::Iter; | |
| Iter iter; | |
| auto* curr = iter.init(fPendingIntrinsicBuffers, Iter::kHead_IterStart); | |
| while (curr != nullptr) { | |
| auto* next = iter.next(); | |
| fPendingIntrinsicBuffers.remove(curr); | |
| delete curr; | |
| curr = next; | |
| } | |
| } | |
| void freeGpuResources() { | |
| this->purgeResourcesNotUsedSince(skgpu::StdSteadyClock::now(), | |
| /*quitPurgingTime=*/std::nullopt); | |
| } | |
| private: | |
| // The max number of intrinsic buffers to keep around in the cache. | |
| static constexpr uint32_t kMaxNumBuffers = 16; | |
| // Traverse the intrinsic buffers, purging all the purgeable LRU buffers until either all of | |
| // them are purged OR until `pred` returns false. | |
| template<typename T> void purgeBuffersUntilDoneOrFalse(T pred); | |
| DawnResourceProvider* const fResourceProvider; | |
| // The current buffer being filled up, as well as the how much of it has been written to. | |
| IntrinsicBuffer* fCurrentBuffer = nullptr; | |
| // All cached intrinsic buffers, in LRU order. | |
| SkTInternalLList<IntrinsicBuffer> fIntrinsicBuffersLRU; | |
| // The number of intrinsic buffers currently in the cache. | |
| uint32_t fNumBuffers = 0; | |
| // Linked list of instrinsic buffers which have been bumped out of the LRU. Is cleared when the | |
| // command buffer is finished. | |
| SkTInternalLList<IntrinsicBuffer> fPendingIntrinsicBuffers; | |
| }; | |
| // Find or create a bind buffer info for the given intrinsic values used in the given command | |
| // buffer. | |
| BindBufferInfo DawnResourceProvider::IntrinsicConstantsManager::add( | |
| DawnCommandBuffer* cb, UniformDataBlock intrinsicValues) { | |
| using Iter = SkTInternalLList<IntrinsicBuffer>::Iter; | |
| Iter iter; | |
| auto* curr = iter.init(fIntrinsicBuffersLRU, Iter::kHead_IterStart); | |
| uint32_t* offset = nullptr; | |
| // Find the buffer that contains the given intrinsic values. | |
| while (curr != nullptr) { | |
| offset = curr->findIntrinsic(intrinsicValues); | |
| if (offset != nullptr) { | |
| break; | |
| } | |
| curr = iter.next(); | |
| } | |
| // If we found the buffer, we can return the bind buffer info directly. | |
| if (curr != nullptr && offset != nullptr) { | |
| // Move the buffer to the head of the LRU list. | |
| fIntrinsicBuffersLRU.remove(curr); | |
| fIntrinsicBuffersLRU.addToHead(curr); | |
| // Track the dawn buffer's usage by the command buffer. | |
| cb->trackResource(curr->buffer()); | |
| curr->updateAccessTime(); | |
| return {curr->buffer().get(), *offset, SkTo<uint32_t>(intrinsicValues.size())}; | |
| } | |
| // TODO: https://b.corp.google.com/issues/259267703 | |
| // Make updating intrinsic constants faster. Metal has setVertexBytes method to quickly send | |
| // intrinsic constants to vertex shader without any buffer. But Dawn doesn't have similar | |
| // capability. So we have to use WriteBuffer(), and this method is not allowed to be called when | |
| // there is an active render pass. | |
| SkASSERT(!cb->hasActivePassEncoder()); | |
| const Caps* caps = fResourceProvider->dawnSharedContext()->caps(); | |
| const uint32_t stride = | |
| SkAlignTo(intrinsicValues.size(), caps->requiredUniformBufferAlignment()); | |
| // In any one of the following cases, we need to create a new buffer: | |
| // (1) There is no current buffer. | |
| // (2) The current buffer is full. | |
| if (!fCurrentBuffer || fCurrentBuffer->slotsUsed() == IntrinsicBuffer::kNumSlots) { | |
| // We can just replace the current buffer; any prior buffer was already tracked in the LRU | |
| // list and the intrinsic constants were written directly to the Dawn queue. | |
| DawnResourceProvider* resourceProvider = fResourceProvider; | |
| auto dawnBuffer = | |
| resourceProvider->findOrCreateDawnBuffer(stride * IntrinsicBuffer::kNumSlots, | |
| BufferType::kUniform, | |
| AccessPattern::kGpuOnly, | |
| "IntrinsicConstantBuffer"); | |
| if (!dawnBuffer) { | |
| // If we failed to create a GPU buffer to hold the intrinsic uniforms, we will fail the | |
| // Recording being inserted, so return an empty bind info. | |
| return {}; | |
| } | |
| fCurrentBuffer = new IntrinsicBuffer(dawnBuffer); | |
| fIntrinsicBuffersLRU.addToHead(fCurrentBuffer); | |
| fNumBuffers++; | |
| // If we have too many buffers, remove the least used one. | |
| if (fNumBuffers > kMaxNumBuffers) { | |
| auto* tail = fIntrinsicBuffersLRU.tail(); | |
| fIntrinsicBuffersLRU.remove(tail); | |
| fPendingIntrinsicBuffers.addToHead(tail); | |
| fNumBuffers--; | |
| } | |
| } | |
| SkASSERT(fCurrentBuffer && fCurrentBuffer->slotsUsed() < IntrinsicBuffer::kNumSlots); | |
| uint32_t newOffset = (fCurrentBuffer->slotsUsed()) * stride; | |
| fResourceProvider->dawnSharedContext()->queue().WriteBuffer( | |
| fCurrentBuffer->buffer()->dawnBuffer(), | |
| newOffset, | |
| intrinsicValues.data(), | |
| intrinsicValues.size()); | |
| // Track the intrinsic values in the buffer. | |
| fCurrentBuffer->trackIntrinsic(intrinsicValues, newOffset); | |
| cb->trackResource(fCurrentBuffer->buffer()); | |
| fCurrentBuffer->updateAccessTime(); | |
| return {fCurrentBuffer->buffer().get(), newOffset, SkTo<uint32_t>(intrinsicValues.size())}; | |
| } | |
| template <typename T> | |
| void DawnResourceProvider::IntrinsicConstantsManager::purgeBuffersUntilDoneOrFalse(T pred) { | |
| using Iter = SkTInternalLList<IntrinsicBuffer>::Iter; | |
| Iter iter; | |
| auto* curr = iter.init(fIntrinsicBuffersLRU, Iter::kHead_IterStart); | |
| while (curr != nullptr) { | |
| auto* next = iter.next(); | |
| if (pred(curr)) { | |
| fIntrinsicBuffersLRU.remove(curr); | |
| fNumBuffers--; | |
| delete curr; | |
| } else { | |
| // If 'pred' returns false, we stop the process of purging buffers. | |
| return; | |
| } | |
| curr = next; | |
| } | |
| } | |
| // DawnResourceProvider::IntrinsicConstantsManager | |
| // ---------------------------------------------------------------------------- | |
| // DawnResourceProvider::BlitWithDrawEncoder | |
| DawnResourceProvider::BlitWithDrawEncoder::BlitWithDrawEncoder(wgpu::RenderPipeline pipeline, | |
| bool srcIsMSAA) | |
| : fPipeline(std::move(pipeline)), fSrcIsMSAA(srcIsMSAA) {} | |
| void DawnResourceProvider::BlitWithDrawEncoder::EncodeBlit( | |
| const wgpu::Device& device, | |
| const wgpu::RenderPassEncoder& renderEncoder, | |
| const wgpu::TextureView& srcTextureView, | |
| const SkIPoint& srcOffset, | |
| const SkIRect& dstBounds) { | |
| SkASSERT(fPipeline); | |
| renderEncoder.SetPipeline(fPipeline); | |
| // TODO(b/260368758): cache single texture's bind group creation. | |
| wgpu::BindGroupEntry entry; | |
| entry.binding = fSrcIsMSAA ? 1 : 0; | |
| entry.textureView = srcTextureView; | |
| wgpu::BindGroupDescriptor desc; | |
| desc.layout = fPipeline.GetBindGroupLayout(0); | |
| desc.entryCount = 1; | |
| desc.entries = &entry; | |
| auto bindGroup = device.CreateBindGroup(&desc); | |
| renderEncoder.SetBindGroup(0, bindGroup); | |
| renderEncoder.SetScissorRect( | |
| dstBounds.left(), dstBounds.top(), dstBounds.width(), dstBounds.height()); | |
| renderEncoder.SetViewport( | |
| dstBounds.left(), dstBounds.top(), dstBounds.width(), dstBounds.height(), 0, 1); | |
| // In fragment shader, the sampling coords are calculated as: | |
| // - x = fragPosition.x - dstX + srcX = fragPosition.x - (dxtX - srcX) | |
| // - y = fragPosition.y - dstY + srcX = fragPosition.y - (dxtY - srcY) | |
| int32_t deltaX = dstBounds.left() - srcOffset.x(); | |
| int32_t deltaY = dstBounds.top() - srcOffset.y(); | |
| // Since texture's sizes are never larger than 16 bits, we can encode the offsets's (x, y) | |
| // in one single 32 bits instance index value. In future, once push constants are implemented in | |
| // Dawn, we should use them instead. | |
| SkASSERT(std::abs(deltaX) < std::numeric_limits<int16_t>::max()); | |
| SkASSERT(std::abs(deltaY) < std::numeric_limits<int16_t>::max()); | |
| int32_t baseInstance = (deltaX & 0xffff) | (deltaY << 16); | |
| // NOTE(b/457887457): need to cast baseInstance to uint32_t explicitly otherwise it would cause | |
| // TypeError in emscripten, because baseInstance value could be negative in signed integer | |
| // representation. | |
| renderEncoder.Draw(/*vertexCount=*/3, | |
| /*instanceCount=*/ 1, | |
| /*firstVertex=*/0, | |
| /*firstInstance=*/static_cast<uint32_t>(baseInstance)); | |
| } | |
| // ---------------------------------------------------------------------------- | |
| DawnResourceProvider::DawnResourceProvider(SharedContext* sharedContext, | |
| SingleOwner* singleOwner, | |
| uint32_t recorderID, | |
| size_t resourceBudget) | |
| : ResourceProvider(sharedContext, singleOwner, recorderID, resourceBudget) | |
| , fSingleOwner(singleOwner) { | |
| fIntrinsicConstantsManager = std::make_unique<IntrinsicConstantsManager>(this); | |
| // Only used for debug asserts so this avoids compile errors. | |
| (void)fSingleOwner; | |
| } | |
| DawnResourceProvider::~DawnResourceProvider() = default; | |
| DawnResourceProvider::BlitWithDrawEncoder DawnResourceProvider::findOrCreateBlitWithDrawEncoder( | |
| const RenderPassDesc& renderPassDesc, SampleCount srcSampleCount) { | |
| // Currently Dawn only supports k1 and k4. So we can optimize the pipeline key by specifying | |
| // whether the source has MSAA or not. | |
| SkASSERT(srcSampleCount == SampleCount::k1 || srcSampleCount == SampleCount::k4); | |
| const bool srcIsMSAA = srcSampleCount > SampleCount::k1; | |
| const uint32_t pipelineKey = this->dawnSharedContext()->dawnCaps()->getRenderPassDescKeyForPipeline( | |
| renderPassDesc, srcIsMSAA); | |
| wgpu::RenderPipeline pipeline = fBlitWithDrawPipelines[pipelineKey]; | |
| if (!pipeline) { | |
| // Since texture's sizes are never larger than 16 bits, we can encode the offsets's (x, y) | |
| // in one single 32 bits instance index value. | |
| static constexpr char kShaderSrc[] = | |
| "struct VertexOutput {" | |
| "@builtin(position) position: vec4f," | |
| "@location(1) @interpolate(flat, either) srcOffset: vec2i," | |
| "};" | |
| "var<private> fullscreenTriPositions : array<vec2<f32>, 3> = array<vec2<f32>, 3>(" | |
| "vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0));" | |
| "@vertex " | |
| "fn VS(@builtin(vertex_index) vertexIndex : u32," | |
| "@builtin(instance_index) instanceIndex : u32)" | |
| "-> VertexOutput {" | |
| "var out: VertexOutput;" | |
| "out.position = vec4(fullscreenTriPositions[vertexIndex], 1.0, 1.0);" | |
| "var srcOffset = vec2u(" | |
| "bitcast<u32>(instanceIndex & 0xffff)," | |
| "bitcast<u32>(instanceIndex >> 16)" | |
| ");" | |
| // Sign extending from 16 bits to 32 bits | |
| "let hasSignBit = (srcOffset & vec2u(0x8000)) != vec2u(0u);" | |
| "srcOffset = select(srcOffset, srcOffset | vec2u(0xffff0000), hasSignBit);" | |
| "out.srcOffset = bitcast<vec2i>(srcOffset);" | |
| "return out;" | |
| "}" | |
| "fn getSamplingCoords(input: VertexOutput) -> vec2i {" | |
| "var coords : vec2<i32> = vec2<i32>(i32(input.position.x), i32(input.position.y));" | |
| "return coords - input.srcOffset;" | |
| "}" | |
| "@group(0) @binding(0) var colorMap: texture_2d<f32>;" | |
| "@fragment " | |
| "fn SampleFS(input: VertexOutput) -> @location(0) vec4<f32> {" | |
| "let coords = getSamplingCoords(input);" | |
| "return textureLoad(colorMap, coords, 0);" | |
| "}" | |
| "@group(0) @binding(1) var msColorMap: texture_multisampled_2d<f32>;" | |
| "@fragment\n" | |
| "fn SampleMSAAFS(input: VertexOutput) -> @location(0) vec4<f32> {" | |
| "let coords = getSamplingCoords(input);" | |
| "const sampleCount = %u;" | |
| "var sum = vec4f(0.0);" | |
| "for (var i: u32 = 0; i < sampleCount; i = i + 1) {" | |
| "sum += textureLoad(msColorMap, coords, i);" | |
| "}" | |
| "return sum * (1.0 / f32(sampleCount));" | |
| "}"; | |
| auto shaderModule = create_shader_module( | |
| dawnSharedContext()->device(), | |
| SkStringPrintf(kShaderSrc, (unsigned) srcSampleCount).c_str()); | |
| pipeline = create_blit_render_pipeline( | |
| dawnSharedContext(), | |
| /*label=*/"BlitWithDraw", | |
| std::move(shaderModule), | |
| "VS", | |
| srcIsMSAA ? "SampleMSAAFS": "SampleFS", | |
| /*renderPassColorFormat=*/ | |
| TextureFormatToDawnFormat(renderPassDesc.fColorAttachment.fFormat), | |
| /*renderPassDepthStencilFormat=*/ | |
| TextureFormatToDawnFormat(renderPassDesc.fDepthStencilAttachment.fFormat), | |
| renderPassDesc.fColorAttachment.fSampleCount); | |
| if (pipeline) { | |
| fBlitWithDrawPipelines.set(pipelineKey, pipeline); | |
| } | |
| } | |
| return BlitWithDrawEncoder(std::move(pipeline), srcIsMSAA); | |
| } | |
| sk_sp<Texture> DawnResourceProvider::onCreateWrappedTexture(const BackendTexture& texture, | |
| std::string_view label) { | |
| // Convert to smart pointers. wgpu::Texture* constructor will increment the ref count. | |
| wgpu::Texture dawnTexture = BackendTextures::GetDawnTexturePtr(texture); | |
| wgpu::TextureView dawnTextureView = BackendTextures::GetDawnTextureViewPtr(texture); | |
| SkASSERT(!dawnTexture || !dawnTextureView); | |
| if (!dawnTexture && !dawnTextureView) { | |
| return {}; | |
| } | |
| if (dawnTexture) { | |
| return DawnTexture::MakeWrapped(this->dawnSharedContext(), | |
| texture.dimensions(), | |
| texture.info(), | |
| std::move(dawnTexture), | |
| label); | |
| } else { | |
| return DawnTexture::MakeWrapped(this->dawnSharedContext(), | |
| texture.dimensions(), | |
| texture.info(), | |
| std::move(dawnTextureView), | |
| label); | |
| } | |
| } | |
| sk_sp<DawnTexture> DawnResourceProvider::findOrCreateDiscardableMSAALoadTexture( | |
| SkISize dimensions, const TextureInfo& msaaInfo) { | |
| SkASSERT(msaaInfo.isValid()); | |
| // Derive the load texture's info from MSAA texture's info. | |
| DawnTextureInfo dawnMsaaLoadTextureInfo = TextureInfoPriv::Get<DawnTextureInfo>(msaaInfo); | |
| dawnMsaaLoadTextureInfo.fSampleCount = SampleCount::k1; | |
| dawnMsaaLoadTextureInfo.fUsage |= wgpu::TextureUsage::TextureBinding; | |
| #if !defined(__EMSCRIPTEN__) | |
| // MSAA texture can be transient attachment (memoryless) but the load texture cannot be. | |
| // This is because the load texture will need to have its content retained between two passes | |
| // loading: | |
| // - first pass: the resolve texture is blitted to the load texture. | |
| // - 2nd pass: the actual render pass is started and the load texture is blitted to the MSAA | |
| // texture. | |
| dawnMsaaLoadTextureInfo.fUsage &= (~wgpu::TextureUsage::TransientAttachment); | |
| #endif | |
| auto texture = this->findOrCreateShareableTexture( | |
| dimensions, | |
| TextureInfos::MakeDawn(dawnMsaaLoadTextureInfo), | |
| "DiscardableLoadMSAATexture"); | |
| return sk_sp<DawnTexture>(static_cast<DawnTexture*>(texture.release())); | |
| } | |
| sk_sp<ComputePipeline> DawnResourceProvider::createComputePipeline( | |
| const ComputePipelineDesc& desc) { | |
| return DawnComputePipeline::Make(this->dawnSharedContext(), desc); | |
| } | |
| sk_sp<Texture> DawnResourceProvider::createTexture( | |
| SkISize dimensions, const TextureInfo& info, std::string_view label) { | |
| return DawnTexture::Make(this->dawnSharedContext(), dimensions, info, label); | |
| } | |
| sk_sp<Buffer> DawnResourceProvider::createBuffer(size_t size, | |
| BufferType type, | |
| AccessPattern accessPattern, | |
| std::string_view label) { | |
| return DawnBuffer::Make(this->dawnSharedContext(), size, type, accessPattern, label); | |
| } | |
| sk_sp<Sampler> DawnResourceProvider::createSampler(const SamplerDesc& samplerDesc) { | |
| return DawnSampler::Make(this->dawnSharedContext(), samplerDesc); | |
| } | |
| BackendTexture DawnResourceProvider::onCreateBackendTexture(SkISize dimensions, | |
| const TextureInfo& info) { | |
| wgpu::Texture texture = DawnTexture::MakeDawnTexture(this->dawnSharedContext(), | |
| dimensions, | |
| info); | |
| if (!texture) { | |
| return {}; | |
| } | |
| return BackendTextures::MakeDawn(texture.MoveToCHandle()); | |
| } | |
| void DawnResourceProvider::onDeleteBackendTexture(const BackendTexture& texture) { | |
| SkASSERT(texture.isValid()); | |
| SkASSERT(texture.backend() == BackendApi::kDawn); | |
| // Automatically release the pointers in wgpu::TextureView & wgpu::Texture's dtor. | |
| // Acquire() won't increment the ref count. | |
| wgpu::TextureView::Acquire(BackendTextures::GetDawnTextureViewPtr(texture)); | |
| // We need to explicitly call Destroy() here since since that is the recommended way to delete | |
| // a Dawn texture predictably versus just dropping a ref and relying on garbage collection. | |
| // | |
| // Additionally this helps to work around an issue where Skia may have cached a BindGroup that | |
| // references the underlying texture. Skia currently doesn't destroy BindGroups when its use of | |
| // the texture goes away, thus a ref to the texture remains on the BindGroup and memory is never | |
| // cleared up unless we call Destroy() here. | |
| wgpu::Texture::Acquire(BackendTextures::GetDawnTexturePtr(texture)).Destroy(); | |
| } | |
| DawnSharedContext* DawnResourceProvider::dawnSharedContext() const { | |
| return static_cast<DawnSharedContext*>(fSharedContext); | |
| } | |
| sk_sp<DawnBuffer> DawnResourceProvider::findOrCreateDawnBuffer(size_t size, | |
| BufferType type, | |
| AccessPattern accessPattern, | |
| std::string_view label) { | |
| sk_sp<Buffer> buffer = this->findOrCreateNonShareableBuffer(size, type, accessPattern, label); | |
| DawnBuffer* ptr = static_cast<DawnBuffer*>(buffer.release()); | |
| return sk_sp<DawnBuffer>(ptr); | |
| } | |
| const wgpu::Buffer& DawnResourceProvider::getOrCreateNullBuffer() { | |
| if (!fNullBuffer) { | |
| wgpu::BufferDescriptor desc; | |
| if (fSharedContext->caps()->setBackendLabels()) { | |
| desc.label = "UnusedBufferSlot"; | |
| } | |
| desc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform | | |
| wgpu::BufferUsage::Storage; | |
| desc.size = kBufferBindingSizeAlignment; | |
| desc.mappedAtCreation = false; | |
| fNullBuffer = this->dawnSharedContext()->device().CreateBuffer(&desc); | |
| SkASSERT(fNullBuffer); | |
| } | |
| return fNullBuffer; | |
| } | |
| const wgpu::TextureView& DawnResourceProvider::getOrCreateNullTextureView() { | |
| if (!fNullTextureView) { | |
| wgpu::TextureDescriptor desc; | |
| if (fSharedContext->caps()->setBackendLabels()) { | |
| desc.label = "UnusedTextureSlot"; | |
| } | |
| desc.usage = wgpu::TextureUsage::TextureBinding; | |
| desc.dimension = wgpu::TextureDimension::e2D; | |
| desc.size = {1, 1, 1}; | |
| desc.format = TextureFormatToDawnFormat(StorageContext::kTextureFormat); | |
| desc.mipLevelCount = 1; | |
| desc.sampleCount = 1; | |
| wgpu::Texture nullTexture = this->dawnSharedContext()->device().CreateTexture(&desc); | |
| if (nullTexture) { | |
| fNullTextureView = nullTexture.CreateView(); | |
| } | |
| SkASSERT(fNullTextureView); | |
| } | |
| return fNullTextureView; | |
| } | |
| wgpu::BindGroup DawnResourceProvider::createBindGroup(SkSpan<wgpu::BindGroupEntry> entries, | |
| const wgpu::BindGroupLayout layout) { | |
| const auto& device = this->dawnSharedContext()->device(); | |
| wgpu::BindGroupDescriptor desc; | |
| desc.layout = layout; | |
| desc.entryCount = entries.size(); | |
| desc.entries = entries.data(); | |
| return device.CreateBindGroup(&desc); | |
| } | |
| wgpu::BindGroup DawnResourceProvider::findOrCreateSingleUniformBindGroup( | |
| const BindBufferInfo& bufferInfo) { | |
| SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner) | |
| // We should only hit the single-uniform case if push constant usage is supported for intrinsic | |
| // constants. | |
| SkASSERT(this->dawnSharedContext()->dawnCaps()-> | |
| resourceBindingRequirements().fUsePushConstantsForIntrinsicConstants); | |
| auto buffer = static_cast<const DawnBuffer*>(bufferInfo.fBuffer); | |
| if (auto cachedBindGroup = buffer->getCachedSingleBufferBindGroup(bufferInfo.fSize)) { | |
| return *cachedBindGroup; | |
| } | |
| // We should be able to assume that if we only have one uniform that it is the combined uniform | |
| // buffer. Construct a list of bind group entries to represent the single combined uniform | |
| // buffer case. | |
| wgpu::BindGroupEntry intrinsicConstantNullEntry; | |
| intrinsicConstantNullEntry.binding = DawnGraphicsPipeline::kIntrinsicUniformBufferIndex; | |
| intrinsicConstantNullEntry.buffer = this->getOrCreateNullBuffer(); | |
| wgpu::BindGroupEntry combinedUniformEntry; | |
| combinedUniformEntry.binding = DawnGraphicsPipeline::kCombinedUniformIndex; | |
| combinedUniformEntry.offset = 0; // Use dynamic offsets; ignore bufferInfo.fOffset | |
| combinedUniformEntry.buffer = buffer->dawnBuffer(); | |
| combinedUniformEntry.size = SkAlignTo(bufferInfo.fSize, kBufferBindingSizeAlignment); | |
| wgpu::BindGroupEntry storageBufferNullEntry; | |
| storageBufferNullEntry.binding = DawnGraphicsPipeline::kStorageBufferIndex; | |
| if (this->dawnSharedContext()->caps()->storageBufferSupport()) { | |
| storageBufferNullEntry.buffer = this->getOrCreateNullBuffer(); | |
| } else { | |
| storageBufferNullEntry.textureView = this->getOrCreateNullTextureView(); | |
| } | |
| std::array<wgpu::BindGroupEntry, kNumUniformEntries> entries = { | |
| intrinsicConstantNullEntry, | |
| combinedUniformEntry, | |
| storageBufferNullEntry | |
| }; | |
| wgpu::BindGroup bindGroup = this->createBindGroup( | |
| entries, | |
| this->dawnSharedContext()->getUniformBuffersBindGroupLayout(wgpu::ShaderStage::None)); | |
| buffer->addCachedSingleBufferBindGroup(bindGroup, bufferInfo.fSize); | |
| return bindGroup; | |
| } | |
| wgpu::BindGroup DawnResourceProvider::findOrCreateSingleTextureSamplerBindGroup( | |
| const DawnSampler* sampler, const DawnTexture* texture) { | |
| SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner) | |
| // First check if we already have a cached bind group we can use. | |
| auto cachedBindGroup = texture->getCachedSingleTextureBindGroup(sampler); | |
| if (cachedBindGroup) { | |
| return *cachedBindGroup; | |
| } | |
| // Otherwise, create one and store it on the Texture for potential future reuse. | |
| std::array<wgpu::BindGroupEntry, 2> entries; | |
| entries[0].binding = 0; | |
| entries[0].sampler = sampler->dawnSampler(); | |
| entries[1].binding = 1; | |
| entries[1].textureView = texture->sampleTextureView(); | |
| wgpu::BindGroup bindGroup = this->createBindGroup( | |
| entries, this->dawnSharedContext()->getSingleTextureSamplerBindGroupLayout()); | |
| texture->addCachedSingleTextureBindGroup(bindGroup, sampler); | |
| return bindGroup; | |
| } | |
| void DawnResourceProvider::onFreeGpuResources() { | |
| SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner) | |
| fIntrinsicConstantsManager->freeGpuResources(); | |
| } | |
| void DawnResourceProvider::onPurgeResourcesNotUsedSince( | |
| StdSteadyClock::time_point purgeTime, | |
| std::optional<StdSteadyClock::time_point> quitPurgingTime) { | |
| fIntrinsicConstantsManager->purgeResourcesNotUsedSince(purgeTime, quitPurgingTime); | |
| } | |
| BindBufferInfo DawnResourceProvider::findOrCreateIntrinsicBindBufferInfo( | |
| DawnCommandBuffer* cb, UniformDataBlock intrinsicValues) { | |
| return fIntrinsicConstantsManager->add(cb, intrinsicValues); | |
| } | |
| void DawnResourceProvider::releasePendingIntrinsicBuffers() { | |
| fIntrinsicConstantsManager->releasePendingIntrinsicBuffers(); | |
| } | |
| DawnThreadSafeResourceProvider::DawnThreadSafeResourceProvider( | |
| std::unique_ptr<ResourceProvider> resourceProvider) | |
| : ThreadSafeResourceProvider(std::move(resourceProvider)) {} | |
| } // namespace skgpu::graphite |