| /* | |
| * 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/DawnCommandBuffer.h" | |
| #include "include/gpu/graphite/TextureInfo.h" | |
| #include "include/private/SkAttributes.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/gpu/graphite/ContextUtils.h" | |
| #include "src/gpu/graphite/RenderPassDesc.h" | |
| #include "src/gpu/graphite/TextureProxy.h" | |
| #include "src/gpu/graphite/UniformManager.h" | |
| #include "src/gpu/graphite/compute/DispatchGroup.h" | |
| #include "src/gpu/graphite/dawn/DawnBuffer.h" | |
| #include "src/gpu/graphite/dawn/DawnCaps.h" | |
| #include "src/gpu/graphite/dawn/DawnComputePipeline.h" | |
| #include "src/gpu/graphite/dawn/DawnGraphicsPipeline.h" | |
| #include "src/gpu/graphite/dawn/DawnGraphiteUtils.h" | |
| #include "src/gpu/graphite/dawn/DawnQueueManager.h" | |
| #include "src/gpu/graphite/dawn/DawnSampler.h" | |
| #include "src/gpu/graphite/dawn/DawnSharedContext.h" | |
| #include "src/gpu/graphite/dawn/DawnTexture.h" | |
| #if defined(__EMSCRIPTEN__) | |
| #include <emscripten/version.h> | |
| namespace wgpu { | |
| using TexelCopyBufferInfo = ImageCopyBuffer; | |
| using TexelCopyTextureInfo = ImageCopyTexture; | |
| } // namespace wgpu | |
| #endif | |
| namespace skgpu::graphite { | |
| // On emsdk before 3.1.48 the API for RenderPass and ComputePass timestamps was different | |
| // and does not map to current webgpu. We check this in DawnCaps but we also must avoid | |
| // naming the types from the new API because they aren't defined. | |
| #if defined(__EMSCRIPTEN__) \ | |
| && ((__EMSCRIPTEN_major__ < 3) \ | |
| || (__EMSCRIPTEN_major__ == 3 && __EMSCRIPTEN_minor__ < 1) \ | |
| || (__EMSCRIPTEN_major__ == 3 && __EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ < 48)) | |
| #define WGPU_TIMESTAMP_WRITES_DEFINED 0 | |
| #else | |
| #define WGPU_TIMESTAMP_WRITES_DEFINED 1 | |
| #endif | |
| // DawnCommandBuffer | |
| // ---------------------------------------------------------------------------- | |
| std::unique_ptr<DawnCommandBuffer> DawnCommandBuffer::Make(const DawnSharedContext* sharedContext, | |
| DawnResourceProvider* resourceProvider) { | |
| std::unique_ptr<DawnCommandBuffer> cmdBuffer( | |
| new DawnCommandBuffer(sharedContext, resourceProvider)); | |
| if (!cmdBuffer->setNewCommandBufferResources()) { | |
| return {}; | |
| } | |
| return cmdBuffer; | |
| } | |
| DawnCommandBuffer::DawnCommandBuffer(const DawnSharedContext* sharedContext, | |
| DawnResourceProvider* resourceProvider) | |
| : CommandBuffer(Protected::kNo) // Dawn doesn't support protected memory | |
| , fSharedContext(sharedContext) | |
| , fResourceProvider(resourceProvider) {} | |
| DawnCommandBuffer::~DawnCommandBuffer() {} | |
| bool DawnCommandBuffer::startStatsQuery(GpuStatsFlags) { | |
| if (fHasStatsQuery) { | |
| SKIA_LOG_W( | |
| "startTimerQuery called more than once for the same command " | |
| "buffer. Currently, stats queries are only supported when " | |
| "each recording gets its own submission."); | |
| return false; | |
| } | |
| wgpu::QuerySet querySet = std::move(fTimestampQuerySet); | |
| auto buffer = fResourceProvider->findOrCreateDawnBuffer(2 * sizeof(uint64_t), | |
| BufferType::kQuery, | |
| AccessPattern::kHostVisible, | |
| "TimerQuery"); | |
| if (!buffer) { | |
| SKIA_LOG_W("Failed to create buffer for resolving results, timer query will " | |
| "not be reported."); | |
| return false; | |
| } | |
| sk_sp<DawnBuffer> xferBuffer; | |
| if (!fSharedContext->caps()->drawBufferCanBeMapped()) { | |
| xferBuffer = fResourceProvider->findOrCreateDawnBuffer(2 * sizeof(uint64_t), | |
| BufferType::kXferGpuToCpu, | |
| AccessPattern::kHostVisible, | |
| "TimerQueryXfer"); | |
| if (!xferBuffer) { | |
| SKIA_LOG_W("Failed to create buffer for transferring timestamp results, timer " | |
| "query will not be reported."); | |
| return false; | |
| } | |
| } | |
| if (!querySet) { | |
| wgpu::QuerySetDescriptor descriptor; | |
| descriptor.count = 2; | |
| descriptor.label = "Command Buffer Timer Query"; | |
| descriptor.type = wgpu::QueryType::Timestamp; | |
| descriptor.nextInChain = nullptr; | |
| querySet = fSharedContext->device().CreateQuerySet(&descriptor); | |
| if (!querySet) { | |
| SKIA_LOG_W("Failed to create query set, timer query will not be reported."); | |
| return false; | |
| } | |
| } | |
| fTimestampQuerySet = std::move(querySet); | |
| fTimestampQueryBuffer = std::move(buffer); | |
| fTimestampQueryXferBuffer = std::move(xferBuffer); | |
| if (fSharedContext->dawnCaps()->supportsCommandBufferTimestamps()) { | |
| // In native Dawn we use the writeTimeStamp method on CommandBuffer to bracket | |
| // the whole command buffer. | |
| fCommandEncoder.WriteTimestamp(fTimestampQuerySet, 0); | |
| } else { | |
| // On WebGPU the best we can do is add timestamps to each render/compute pass as we record | |
| // them. Since we don't know a priori how many passes there will be we write a begin | |
| // timestamp on the first pass and an end on every pass, overwriting the second query in the | |
| // set. | |
| fWroteFirstPassTimestamps = false; | |
| } | |
| fHasStatsQuery = true; | |
| return true; | |
| } | |
| void DawnCommandBuffer::endStatsQuery(GpuStatsFlags) { | |
| // Only called if startTimerQuery succeeded. | |
| SkASSERT(fTimestampQuerySet); | |
| SkASSERT(fTimestampQueryBuffer); | |
| if (fSharedContext->dawnCaps()->supportsCommandBufferTimestamps()) { | |
| fCommandEncoder.WriteTimestamp(fTimestampQuerySet, 1); | |
| } else if (!fWroteFirstPassTimestamps) { | |
| // If we didn't have any passes then we can't report anything. | |
| fTimestampQueryBuffer = nullptr; | |
| fTimestampQueryXferBuffer = nullptr; | |
| return; | |
| } | |
| // This resolve covers both timestamp code paths (command encoder and render/compute pass). | |
| fCommandEncoder.ResolveQuerySet( | |
| fTimestampQuerySet, 0, 2, fTimestampQueryBuffer->dawnBuffer(), 0); | |
| if (fTimestampQueryXferBuffer) { | |
| SkASSERT(fTimestampQueryBuffer->size() == fTimestampQueryBuffer->size()); | |
| fCommandEncoder.CopyBufferToBuffer(fTimestampQueryBuffer->dawnBuffer(), | |
| /*sourceOffset=*/0, | |
| fTimestampQueryXferBuffer->dawnBuffer(), | |
| /*destinationOffset=*/0, | |
| /*size=*/fTimestampQueryBuffer->size()); | |
| fTimestampQueryBuffer.reset(); | |
| } | |
| if (fSharedContext->caps()->bufferMapsAreAsync()) { | |
| sk_sp<Buffer> buffer = | |
| fTimestampQueryXferBuffer ? fTimestampQueryXferBuffer : fTimestampQueryBuffer; | |
| this->addBuffersToAsyncMapOnSubmit({&buffer, 1}); | |
| } | |
| } | |
| std::optional<GpuStats> DawnCommandBuffer::gpuStats() { | |
| auto& buffer = fTimestampQueryXferBuffer ? fTimestampQueryXferBuffer : fTimestampQueryBuffer; | |
| if (!buffer) { | |
| return {}; | |
| } | |
| if (fSharedContext->caps()->bufferMapsAreAsync()) { | |
| if (!buffer->isMapped()) { | |
| return {}; | |
| } | |
| } | |
| uint64_t* results = static_cast<uint64_t*>(buffer->map()); | |
| if (!results) { | |
| SKIA_LOG_W("Failed to get timer query results because buffer couldn't be mapped."); | |
| return {}; | |
| } | |
| if (SK_UNSAFE_TODO(results[1]) < results[0]) { | |
| return {}; | |
| } | |
| GpuStats stats; | |
| stats.elapsedTime = SK_UNSAFE_TODO(results[1]) - results[0]; | |
| return stats; | |
| } | |
| wgpu::CommandBuffer DawnCommandBuffer::finishEncoding() { | |
| SkASSERT(fCommandEncoder); | |
| wgpu::CommandBuffer cmdBuffer = fCommandEncoder.Finish(); | |
| fCommandEncoder = nullptr; | |
| fResourceProvider->releasePendingIntrinsicBuffers(); | |
| return cmdBuffer; | |
| } | |
| void DawnCommandBuffer::onResetCommandBuffer() { | |
| fActiveGraphicsPipeline = nullptr; | |
| fActiveRenderPassEncoder = nullptr; | |
| fActiveComputePassEncoder = nullptr; | |
| fCommandEncoder = nullptr; | |
| for (auto& bufferSlot : fBoundUniforms) { | |
| bufferSlot = {}; | |
| } | |
| fBoundStorageFallbackTexture = nullptr; | |
| fBoundUniformBuffersDirty = true; | |
| if (fTimestampQueryBuffer && fTimestampQueryBuffer->isUnmappable()) { | |
| fTimestampQueryBuffer->unmap(); | |
| } | |
| if (fTimestampQueryXferBuffer && fTimestampQueryXferBuffer->isUnmappable()) { | |
| fTimestampQueryXferBuffer->unmap(); | |
| } | |
| fTimestampQueryBuffer = {}; | |
| fTimestampQueryXferBuffer = {}; | |
| fWroteFirstPassTimestamps = false; | |
| fHasStatsQuery = false; | |
| } | |
| bool DawnCommandBuffer::setNewCommandBufferResources() { | |
| SkASSERT(!fCommandEncoder); | |
| fCommandEncoder = fSharedContext->device().CreateCommandEncoder(); | |
| SkASSERT(fCommandEncoder); | |
| return true; | |
| } | |
| // Requests a sampler. Dynamic samplers live in the global cache, requiring no tracking, but | |
| // immutable samplers are created on the current graphics pipeline, and may outlive it, requiring | |
| // further tracking. | |
| const DawnSampler* DawnCommandBuffer::getSampler( | |
| const DrawPassCommands::BindTexturesAndSamplers& command, int32_t index) { | |
| auto desc = SK_UNSAFE_TODO(command.fSamplers[index]); | |
| if (desc.isImmutable()) { | |
| const DawnSampler* immutableSampler = fActiveGraphicsPipeline->immutableSampler(index); | |
| if (immutableSampler) { | |
| this->trackResource(sk_ref_sp<Sampler>(immutableSampler)); | |
| } | |
| return immutableSampler; | |
| } else { | |
| // Use the shared Sampler held in the global cache | |
| return static_cast<const DawnSampler*>( | |
| fSharedContext->globalCache()->getDynamicSampler(desc)); | |
| } | |
| } | |
| bool DawnCommandBuffer::onAddRenderPass(const RenderPassDesc& renderPassDesc, | |
| const Texture* colorTexture, | |
| const Texture* resolveTexture, | |
| const Texture* depthStencilTexture, | |
| SkIPoint resolveOffset, | |
| SkIRect viewport, | |
| const DrawPassList& drawPasses) { | |
| // `viewport` has already been translated by the replay translation by the base CommandBuffer | |
| if (!SkIRect::Intersects(viewport, fRenderTargetBounds)) SK_UNLIKELY { | |
| // The entire pass is offscreen | |
| return true; | |
| } | |
| UniformManager intrinsicValues{Layout::kStd140}; | |
| CollectIntrinsicUniforms(fSharedContext->caps(), viewport, fDstReadBounds, &intrinsicValues); | |
| const auto uniformData = UniformDataBlock::Wrap(&intrinsicValues); | |
| const bool usePushConstant = fSharedContext->dawnCaps() | |
| ->resourceBindingRequirements() | |
| .fUsePushConstantsForIntrinsicConstants; | |
| // If push constant is not supported, update the intrinsic constant buffer before starting a | |
| // render pass. | |
| if (!usePushConstant && !this->updateIntrinsicUniformsAsUBO(uniformData)) SK_UNLIKELY { | |
| return false; | |
| } | |
| if (!this->beginRenderPass(renderPassDesc, | |
| resolveOffset, | |
| colorTexture, | |
| resolveTexture, | |
| depthStencilTexture)) SK_UNLIKELY { | |
| return false; | |
| } | |
| // If push constant is supported, update the intrinsic constants after starting a render pass. | |
| if (usePushConstant && !this->updateIntrinsicUniformsAsPushConstant(uniformData)) SK_UNLIKELY { | |
| return false; | |
| } | |
| this->setViewport(viewport); | |
| for (const auto& drawPass : drawPasses) { | |
| if (!this->addDrawPass(drawPass.get())) SK_UNLIKELY { | |
| this->endRenderPass(); | |
| return false; | |
| } | |
| } | |
| return this->endRenderPass(); | |
| } | |
| bool DawnCommandBuffer::onAddComputePass(DispatchGroupSpan groups) { | |
| this->beginComputePass(); | |
| for (const auto& group : groups) { | |
| group->addResourceRefs(this); | |
| for (const auto& dispatch : group->dispatches()) { | |
| this->bindComputePipeline(group->getPipeline(dispatch.fPipelineIndex)); | |
| this->bindDispatchResources(*group, dispatch); | |
| if (const WorkgroupSize* globalSize = | |
| std::get_if<WorkgroupSize>(&dispatch.fGlobalSizeOrIndirect)) { | |
| this->dispatchWorkgroups(*globalSize); | |
| } else { | |
| SkASSERT(std::holds_alternative<BindBufferInfo>(dispatch.fGlobalSizeOrIndirect)); | |
| const BindBufferInfo& indirect = | |
| *std::get_if<BindBufferInfo>(&dispatch.fGlobalSizeOrIndirect); | |
| this->dispatchWorkgroupsIndirect(indirect.fBuffer, indirect.fOffset); | |
| } | |
| } | |
| } | |
| this->endComputePass(); | |
| return true; | |
| } | |
| bool DawnCommandBuffer::beginRenderPass(const RenderPassDesc& renderPassDesc, | |
| const SkIPoint& resolveOffset, | |
| const Texture* colorTexture, | |
| const Texture* resolveTexture, | |
| const Texture* depthStencilTexture) { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| SkASSERT(!fActiveComputePassEncoder); | |
| const wgpu::LoadOp wgpuLoadActionMap[]{ | |
| wgpu::LoadOp::Load, | |
| wgpu::LoadOp::Clear, | |
| fSharedContext->dawnCaps()->discardLoadOp() | |
| }; | |
| static_assert((int)LoadOp::kLoad == 0); | |
| static_assert((int)LoadOp::kClear == 1); | |
| static_assert((int)LoadOp::kDiscard == 2); | |
| static_assert(std::size(wgpuLoadActionMap) == kLoadOpCount); | |
| const wgpu::StoreOp wgpuStoreActionMap[]{ | |
| wgpu::StoreOp::Store, | |
| fSharedContext->dawnCaps()->discardStoreOp() | |
| }; | |
| static_assert((int)StoreOp::kStore == 0); | |
| static_assert((int)StoreOp::kDiscard == 1); | |
| static_assert(std::size(wgpuStoreActionMap) == kStoreOpCount); | |
| wgpu::RenderPassDescriptor wgpuRenderPass = {}; | |
| wgpu::RenderPassColorAttachment wgpuColorAttachment; | |
| wgpu::RenderPassDepthStencilAttachment wgpuDepthStencilAttachment; | |
| // Set up color attachment. | |
| #if !defined(__EMSCRIPTEN__) | |
| wgpu::DawnRenderPassSampleCount mssaRenderToSingleSampledDesc; | |
| wgpu::RenderPassDescriptorResolveRect wgpuPartialRect = {}; | |
| wgpu::RenderPassRenderAreaRect wgpuRenderArea = {}; | |
| if (fSharedContext->dawnCaps()->supportsRenderPassRenderArea()) { | |
| wgpuRenderArea.origin.x = fRenderAreaBounds.x(); | |
| wgpuRenderArea.origin.y = fRenderAreaBounds.y(); | |
| wgpuRenderArea.size.width = fRenderAreaBounds.width(); | |
| wgpuRenderArea.size.height = fRenderAreaBounds.height(); | |
| wgpuRenderPass.nextInChain = &wgpuRenderArea; | |
| } | |
| #endif | |
| #if WGPU_TIMESTAMP_WRITES_DEFINED | |
| #if defined(__EMSCRIPTEN__) | |
| wgpu::RenderPassTimestampWrites wgpuTimestampWrites; | |
| #else | |
| wgpu::PassTimestampWrites wgpuTimestampWrites; | |
| #endif | |
| if (!fSharedContext->dawnCaps()->supportsCommandBufferTimestamps() && fTimestampQueryBuffer) { | |
| SkASSERT(fTimestampQuerySet); | |
| wgpuTimestampWrites.querySet = fTimestampQuerySet; | |
| if (!fWroteFirstPassTimestamps) { | |
| wgpuTimestampWrites.beginningOfPassWriteIndex = 0; | |
| fWroteFirstPassTimestamps = true; | |
| } | |
| wgpuTimestampWrites.endOfPassWriteIndex = 1; | |
| wgpuRenderPass.timestampWrites = &wgpuTimestampWrites; | |
| } | |
| #else | |
| SkASSERT(!fTimestampQueryBuffer); | |
| #endif | |
| // Validate attachment descs and textures | |
| const auto& colorInfo = renderPassDesc.fColorAttachment; | |
| const auto& resolveInfo = renderPassDesc.fColorResolveAttachment; | |
| const auto& depthStencilInfo = renderPassDesc.fDepthStencilAttachment; | |
| SkASSERT(colorTexture ? colorInfo.isCompatible(colorTexture->textureInfo()) | |
| : colorInfo.fFormat == TextureFormat::kUnsupported); | |
| SkASSERT(resolveTexture ? resolveInfo.isCompatible(resolveTexture->textureInfo()) | |
| : resolveInfo.fFormat == TextureFormat::kUnsupported); | |
| SkASSERT(depthStencilTexture ? depthStencilInfo.isCompatible(depthStencilTexture->textureInfo()) | |
| : depthStencilInfo.fFormat == TextureFormat::kUnsupported); | |
| // Set up color attachment | |
| bool emulateLoadStoreResolveTexture = false; | |
| if (colorTexture) { | |
| wgpuRenderPass.colorAttachments = &wgpuColorAttachment; | |
| wgpuRenderPass.colorAttachmentCount = 1; | |
| const auto* dawnColorTexture = static_cast<const DawnTexture*>(colorTexture); | |
| SkASSERT(dawnColorTexture->renderTextureView()); | |
| wgpuColorAttachment.view = dawnColorTexture->renderTextureView(); | |
| const std::array<float, 4>& clearColor = renderPassDesc.fClearColor; | |
| wgpuColorAttachment.clearValue = { | |
| clearColor[0], clearColor[1], clearColor[2], clearColor[3]}; | |
| wgpuColorAttachment.loadOp = | |
| SK_UNSAFE_TODO(wgpuLoadActionMap[static_cast<int>(colorInfo.fLoadOp)]); | |
| wgpuColorAttachment.storeOp = | |
| SK_UNSAFE_TODO(wgpuStoreActionMap[static_cast<int>(colorInfo.fStoreOp)]); | |
| // Set up resolve attachment | |
| if (resolveTexture) { | |
| SkASSERT(resolveInfo.fStoreOp == StoreOp::kStore); | |
| const auto* dawnResolveTexture = static_cast<const DawnTexture*>(resolveTexture); | |
| SkASSERT(dawnResolveTexture->renderTextureView()); | |
| wgpuColorAttachment.resolveTarget = dawnResolveTexture->renderTextureView(); | |
| // Inclusion of a resolve texture implies the client wants to finish the | |
| // renderpass with a resolve. | |
| SkASSERT(wgpuColorAttachment.storeOp == | |
| fSharedContext->dawnCaps()->discardStoreOp()); | |
| // But it also means we might have to load the resolve texture into the MSAA color attachment | |
| if (fSharedContext->dawnCaps()->emulateLoadStoreResolve()) { | |
| emulateLoadStoreResolveTexture = true; | |
| } else if (resolveInfo.fLoadOp == LoadOp::kLoad) { | |
| std::optional<wgpu::LoadOp> resolveLoadOp = | |
| fSharedContext->dawnCaps()->resolveTextureLoadOp(); | |
| if (resolveLoadOp.has_value()) { | |
| wgpuColorAttachment.loadOp = *resolveLoadOp; | |
| } else { | |
| // No Dawn built-in support, we need to manually load the resolve texture. | |
| emulateLoadStoreResolveTexture = true; | |
| } | |
| } | |
| if (!emulateLoadStoreResolveTexture) { | |
| #if !defined(__EMSCRIPTEN__) | |
| if (fSharedContext->dawnCaps()->supportsPartialLoadResolve()) { | |
| SkIRect msaaArea = fRenderAreaBounds; | |
| SkAssertResult(msaaArea.intersect(SkIRect::MakeSize( | |
| colorTexture->dimensions()))); | |
| wgpuPartialRect.colorOffsetX = msaaArea.x(); | |
| wgpuPartialRect.colorOffsetY = msaaArea.y(); | |
| SkIRect resolveArea = fRenderAreaBounds; | |
| resolveArea.offset(resolveOffset); | |
| SkAssertResult(resolveArea.intersect(SkIRect::MakeSize( | |
| resolveTexture->dimensions()))); | |
| wgpuPartialRect.resolveOffsetX = resolveArea.x(); | |
| wgpuPartialRect.resolveOffsetY = resolveArea.y(); | |
| wgpuPartialRect.width = resolveArea.width(); | |
| wgpuPartialRect.height = resolveArea.height(); | |
| wgpuPartialRect.nextInChain = wgpuRenderPass.nextInChain; | |
| wgpuRenderPass.nextInChain = &wgpuPartialRect; | |
| } else | |
| #endif | |
| { | |
| SkASSERT(resolveOffset.isZero()); | |
| } | |
| } | |
| // TODO: If the color resolve texture is read-only we can use a private (vs. memoryless) | |
| // msaa attachment that's coupled to the framebuffer and the StoreAndMultisampleResolve | |
| // action instead of loading as a draw. | |
| } else { | |
| [[maybe_unused]] bool isMSAAToSingleSampled = | |
| renderPassDesc.fSampleCount > SampleCount::k1 && | |
| colorTexture->sampleCount() == SampleCount::k1; | |
| #if defined(__EMSCRIPTEN__) | |
| SkASSERT(!isMSAAToSingleSampled); | |
| #else | |
| if (isMSAAToSingleSampled) { | |
| // If render pass is multi sampled but the color attachment is single sampled, we | |
| // need to activate multisampled render to single sampled feature for this render | |
| // pass. | |
| SkASSERT(fSharedContext->device().HasFeature( | |
| wgpu::FeatureName::MSAARenderToSingleSampled)); | |
| mssaRenderToSingleSampledDesc.nextInChain = wgpuRenderPass.nextInChain; | |
| wgpuRenderPass.nextInChain = &mssaRenderToSingleSampledDesc; | |
| mssaRenderToSingleSampledDesc.sampleCount = (uint8_t)renderPassDesc.fSampleCount; | |
| } | |
| #endif | |
| } | |
| } | |
| // Set up stencil/depth attachment | |
| if (depthStencilTexture) { | |
| const auto* dawnDepthStencilTexture = static_cast<const DawnTexture*>(depthStencilTexture); | |
| SkASSERT(dawnDepthStencilTexture->renderTextureView()); | |
| wgpuDepthStencilAttachment.view = dawnDepthStencilTexture->renderTextureView(); | |
| if (TextureFormatHasDepth(depthStencilInfo.fFormat)) { | |
| wgpuDepthStencilAttachment.depthClearValue = renderPassDesc.fClearDepth; | |
| wgpuDepthStencilAttachment.depthLoadOp = | |
| SK_UNSAFE_TODO(wgpuLoadActionMap[static_cast<int>(depthStencilInfo.fLoadOp)]); | |
| wgpuDepthStencilAttachment.depthStoreOp = | |
| SK_UNSAFE_TODO(wgpuStoreActionMap[static_cast<int>(depthStencilInfo.fStoreOp)]); | |
| } | |
| if (TextureFormatHasStencil(depthStencilInfo.fFormat)) { | |
| wgpuDepthStencilAttachment.stencilClearValue = renderPassDesc.fClearStencil; | |
| wgpuDepthStencilAttachment.stencilLoadOp = | |
| SK_UNSAFE_TODO(wgpuLoadActionMap[static_cast<int>(depthStencilInfo.fLoadOp)]); | |
| wgpuDepthStencilAttachment.stencilStoreOp = | |
| SK_UNSAFE_TODO(wgpuStoreActionMap[static_cast<int>(depthStencilInfo.fStoreOp)]); | |
| } | |
| wgpuRenderPass.depthStencilAttachment = &wgpuDepthStencilAttachment; | |
| } | |
| if (emulateLoadStoreResolveTexture) { | |
| if (!this->emulateLoadMSAAFromResolveAndBeginRenderPassEncoder( | |
| renderPassDesc, | |
| wgpuRenderPass, | |
| resolveOffset, | |
| static_cast<const DawnTexture*>(colorTexture), | |
| static_cast<const DawnTexture*>(resolveTexture))) { | |
| return false; | |
| } | |
| } | |
| else { | |
| fActiveRenderPassEncoder = fCommandEncoder.BeginRenderPass(&wgpuRenderPass); | |
| } | |
| return true; | |
| } | |
| bool DawnCommandBuffer::emulateLoadMSAAFromResolveAndBeginRenderPassEncoder( | |
| const RenderPassDesc& intendedRenderPassDesc, | |
| const wgpu::RenderPassDescriptor& intendedDawnRenderPassDesc, | |
| const SkIPoint& resolveOffset, | |
| const DawnTexture* msaaTexture, | |
| const DawnTexture* resolveTexture) { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| const bool loadResolve = | |
| intendedRenderPassDesc.fColorResolveAttachment.fLoadOp == LoadOp::kLoad; | |
| // Override the render pass to exclude the resolve texture. We will emulate the loading & | |
| // resolve via blit. The resovle step will be done separately after endRenderPass() | |
| RenderPassDesc renderPassWithoutResolveDesc = intendedRenderPassDesc; | |
| renderPassWithoutResolveDesc.fColorResolveAttachment = {}; | |
| wgpu::RenderPassColorAttachment dawnColorAttachmentWithoutResolve = | |
| intendedDawnRenderPassDesc.colorAttachments[0]; | |
| dawnColorAttachmentWithoutResolve.resolveTarget = nullptr; | |
| dawnColorAttachmentWithoutResolve.storeOp = wgpu::StoreOp::Store; | |
| if (loadResolve) { | |
| // If we intend to load the resolve texture, then override the loadOp of the MSAA attachment | |
| // to Load instead of Clear. | |
| // This path is intended to be used when the device doesn't support transient attachments. | |
| // Which most likely means it is a non-tiled GPU. On non-tiled GPUs, load is a no-op so it | |
| // should be faster than clearing the whole MSAA attachment. Note: Dawn doesn't have any | |
| // DontCare loadOp. | |
| dawnColorAttachmentWithoutResolve.loadOp = wgpu::LoadOp::Load; | |
| } | |
| wgpu::RenderPassDescriptor dawnRenderPassDescWithoutResolve = intendedDawnRenderPassDesc; | |
| dawnRenderPassDescWithoutResolve.colorAttachments = &dawnColorAttachmentWithoutResolve; | |
| dawnRenderPassDescWithoutResolve.colorAttachmentCount = 1; | |
| auto renderPassEncoder = fCommandEncoder.BeginRenderPass(&dawnRenderPassDescWithoutResolve); | |
| SkIRect msaaArea = fRenderAreaBounds; | |
| msaaArea.intersect(SkIRect::MakeSize(msaaTexture->dimensions())); | |
| SkIRect resolveArea = fRenderAreaBounds; | |
| resolveArea.offset(resolveOffset); | |
| resolveArea.intersect(SkIRect::MakeSize(resolveTexture->dimensions())); | |
| if (loadResolve) { | |
| // Blit from the resolve texture to the MSAA texture | |
| if (!this->doBlitWithDraw(renderPassEncoder, | |
| renderPassWithoutResolveDesc, | |
| /*srcTextureView=*/resolveTexture->renderTextureView(), | |
| /*srcSampleCount=*/SampleCount::k1, | |
| /*srcOffset=*/resolveArea.topLeft(), | |
| /*dstBounds=*/msaaArea)) { | |
| renderPassEncoder.End(); | |
| return false; | |
| } | |
| } | |
| fActiveRenderPassEncoder = renderPassEncoder; | |
| fResolveStepEmulationInfo = {msaaTexture, resolveTexture, msaaArea.topLeft(), resolveArea}; | |
| return true; | |
| } | |
| bool DawnCommandBuffer::doBlitWithDraw(const wgpu::RenderPassEncoder& renderEncoder, | |
| const RenderPassDesc& frontendRenderPassDescKey, | |
| const wgpu::TextureView& srcTextureView, | |
| SampleCount srcSampleCount, | |
| const SkIPoint& srcOffset, | |
| const SkIRect& dstBounds) { | |
| DawnResourceProvider::BlitWithDrawEncoder blit = | |
| fResourceProvider->findOrCreateBlitWithDrawEncoder(frontendRenderPassDescKey, | |
| srcSampleCount); | |
| if (!blit) { | |
| SKIA_LOG_E("Unable to create pipeline to blit with draw"); | |
| return false; | |
| } | |
| SkASSERT(renderEncoder); | |
| blit.EncodeBlit(fSharedContext->device(), renderEncoder, srcTextureView, srcOffset, dstBounds); | |
| return true; | |
| } | |
| bool DawnCommandBuffer::endRenderPass() { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| fActiveRenderPassEncoder.End(); | |
| fActiveRenderPassEncoder = nullptr; | |
| fBoundStorageFallbackTexture = nullptr; | |
| // Return early if no resolve step's emulation is needed. | |
| if (!fResolveStepEmulationInfo.has_value()) { | |
| return true; | |
| } | |
| // Creating an intermediate render pass to copy from the MSAA texture -> resolve texture. | |
| RenderPassDesc intermediateRenderPassDesc = {}; | |
| intermediateRenderPassDesc.fColorAttachment = { | |
| TextureInfoPriv::ViewFormat(fResolveStepEmulationInfo->fResolveTexture->textureInfo()), | |
| LoadOp::kLoad, | |
| StoreOp::kStore, | |
| SampleCount::k1 }; | |
| wgpu::RenderPassColorAttachment dawnIntermediateColorAttachment; | |
| dawnIntermediateColorAttachment.clearValue = {0.f, 0.f, 0.f, 0.f}; // ignored | |
| dawnIntermediateColorAttachment.loadOp = wgpu::LoadOp::Load; | |
| dawnIntermediateColorAttachment.storeOp = wgpu::StoreOp::Store; | |
| dawnIntermediateColorAttachment.view = | |
| fResolveStepEmulationInfo->fResolveTexture->renderTextureView(); | |
| wgpu::RenderPassDescriptor dawnIntermediateRenderPassDesc; | |
| dawnIntermediateRenderPassDesc.colorAttachmentCount = 1; | |
| dawnIntermediateRenderPassDesc.colorAttachments = &dawnIntermediateColorAttachment; | |
| auto renderPassEncoder = fCommandEncoder.BeginRenderPass(&dawnIntermediateRenderPassDesc); | |
| bool blitSucceeded = this->doBlitWithDraw( | |
| renderPassEncoder, | |
| intermediateRenderPassDesc, | |
| /*srcTextureView=*/fResolveStepEmulationInfo->fMSAATexture->renderTextureView(), | |
| /*srcSampleCount=*/fResolveStepEmulationInfo->fMSAATexture->textureInfo().sampleCount(), | |
| /*srcOffset=*/fResolveStepEmulationInfo->fMSAAAOffset, | |
| /*dstBounds=*/fResolveStepEmulationInfo->fResolveArea); | |
| renderPassEncoder.End(); | |
| fResolveStepEmulationInfo.reset(); | |
| return blitSucceeded; | |
| } | |
| bool DawnCommandBuffer::addDrawPass(DrawPass* drawPass) { | |
| if (!drawPass->addResourceRefs(fResourceProvider, this)) SK_UNLIKELY { | |
| return false; | |
| } | |
| // If there is storage data to bind, it must be done prior to draws; otherwise clear the slot so | |
| // state from a prior DrawPass in the same RenderPass does not leak. | |
| if (fSharedContext->caps()->storageBufferSupport()) { | |
| this->bindUniformBuffer(drawPass->storageBufferInfo(), UniformSlot::kStorage); | |
| } else { | |
| const TextureProxy* proxy = drawPass->storageFallbackTexture(); | |
| fBoundStorageFallbackTexture = proxy ? proxy->texture() : nullptr; | |
| fBoundUniformBuffersDirty = true; | |
| } | |
| for (auto [type, cmdPtr] : drawPass->commands()) { | |
| switch (type) { | |
| case DrawPassCommands::Type::kBindGraphicsPipeline: { | |
| auto bgp = static_cast<DrawPassCommands::BindGraphicsPipeline*>(cmdPtr); | |
| if (!this->bindGraphicsPipeline(drawPass->getPipeline(bgp->fPipelineIndex))) | |
| SK_UNLIKELY { return false; } | |
| break; | |
| } | |
| case DrawPassCommands::Type::kSetBlendConstants: { | |
| auto sbc = static_cast<DrawPassCommands::SetBlendConstants*>(cmdPtr); | |
| this->setBlendConstants(sbc->fBlendConstants); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kBindUniformBuffer: { | |
| auto bub = static_cast<DrawPassCommands::BindUniformBuffer*>(cmdPtr); | |
| this->bindUniformBuffer(bub->fInfo, bub->fSlot); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kBindStaticDataBuffer: { | |
| auto bdb = static_cast<DrawPassCommands::BindStaticDataBuffer*>(cmdPtr); | |
| this->bindInputBuffer(bdb->fStaticData.fBuffer, bdb->fStaticData.fOffset, | |
| DawnGraphicsPipeline::kStaticDataBufferIndex); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kBindAppendDataBuffer: { | |
| auto bdb = static_cast<DrawPassCommands::BindAppendDataBuffer*>(cmdPtr); | |
| this->bindInputBuffer(bdb->fAppendData.fBuffer, bdb->fAppendData.fOffset, | |
| DawnGraphicsPipeline::kAppendDataBufferIndex); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kBindIndexBuffer: { | |
| auto bdb = static_cast<DrawPassCommands::BindIndexBuffer*>(cmdPtr); | |
| this->bindIndexBuffer( | |
| bdb->fIndices.fBuffer, bdb->fIndices.fOffset); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kBindIndirectBuffer: { | |
| auto bdb = static_cast<DrawPassCommands::BindIndirectBuffer*>(cmdPtr); | |
| this->bindIndirectBuffer( | |
| bdb->fIndirect.fBuffer, bdb->fIndirect.fOffset); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kBindTexturesAndSamplers: { | |
| auto bts = static_cast<DrawPassCommands::BindTexturesAndSamplers*>(cmdPtr); | |
| this->bindTextureAndSamplers(*drawPass, *bts); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kSetScissor: { | |
| auto ss = static_cast<DrawPassCommands::SetScissor*>(cmdPtr); | |
| this->setScissor(ss->fScissor); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kDraw: { | |
| auto draw = static_cast<DrawPassCommands::Draw*>(cmdPtr); | |
| this->draw(draw->fType, draw->fBaseVertex, draw->fVertexCount); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kDrawIndexed: { | |
| auto draw = static_cast<DrawPassCommands::DrawIndexed*>(cmdPtr); | |
| this->drawIndexed( | |
| draw->fType, draw->fBaseIndex, draw->fIndexCount, draw->fBaseVertex); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kDrawInstanced: { | |
| auto draw = static_cast<DrawPassCommands::DrawInstanced*>(cmdPtr); | |
| this->drawInstanced(draw->fType, | |
| draw->fBaseVertex, | |
| draw->fVertexCount, | |
| draw->fBaseInstance, | |
| draw->fInstanceCount); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kDrawIndexedInstanced: { | |
| auto draw = static_cast<DrawPassCommands::DrawIndexedInstanced*>(cmdPtr); | |
| this->drawIndexedInstanced(draw->fType, | |
| draw->fBaseIndex, | |
| draw->fIndexCount, | |
| draw->fBaseVertex, | |
| draw->fBaseInstance, | |
| draw->fInstanceCount); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kDrawIndirect: { | |
| auto draw = static_cast<DrawPassCommands::DrawIndirect*>(cmdPtr); | |
| this->drawIndirect(draw->fType); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kDrawIndexedIndirect: { | |
| auto draw = static_cast<DrawPassCommands::DrawIndexedIndirect*>(cmdPtr); | |
| this->drawIndexedIndirect(draw->fType); | |
| break; | |
| } | |
| case DrawPassCommands::Type::kAddBarrier: { | |
| SKIA_LOG_E("DawnCommandBuffer does not support the addition of barriers."); | |
| break; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool DawnCommandBuffer::bindGraphicsPipeline(const GraphicsPipeline* graphicsPipeline) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| auto* dawnGraphicsPipeline = static_cast<const DawnGraphicsPipeline*>(graphicsPipeline); | |
| auto& wgpuPipeline = dawnGraphicsPipeline->dawnRenderPipeline(); | |
| if (!wgpuPipeline) SK_UNLIKELY { | |
| return false; | |
| } | |
| fActiveGraphicsPipeline = dawnGraphicsPipeline; | |
| fActiveRenderPassEncoder.SetPipeline(wgpuPipeline); | |
| fBoundUniformBuffersDirty = true; | |
| if (fActiveGraphicsPipeline->dstReadStrategy() == DstReadStrategy::kTextureCopy && | |
| fActiveGraphicsPipeline->numFragTexturesAndSamplers() == 2) { | |
| // The pipeline has a single paired texture+sampler and uses texture copies for dst reads. | |
| // This situation comes about when the program requires complex blending but otherwise | |
| // is not referencing any images. Since there are no other images in play, the DrawPass | |
| // will not have a BindTexturesAndSamplers command that we can tack the dstCopy on to. | |
| // Instead we need to set the texture BindGroup ASAP to just the dstCopy. | |
| // TODO(b/366254117): Once we standardize on a pipeline layout across all backends, the dst | |
| // copy texture may not go in a group with the regular textures, in which case this binding | |
| // can hopefully happen in a single place (e.g. here or at the start of the renderpass and | |
| // not also every other time the textures are changed). | |
| const auto* texture = static_cast<const DawnTexture*>(fDstCopy.first); | |
| const auto* sampler = static_cast<const DawnSampler*>(fDstCopy.second); | |
| wgpu::BindGroup bindGroup = | |
| fResourceProvider->findOrCreateSingleTextureSamplerBindGroup(sampler, texture); | |
| fActiveRenderPassEncoder.SetBindGroup( | |
| DawnGraphicsPipeline::kTextureBindGroupIndex, bindGroup); | |
| } | |
| return true; | |
| } | |
| void DawnCommandBuffer::bindUniformBuffer(const BindBufferInfo& info, UniformSlot slot) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| unsigned int bufferIndex; | |
| switch (slot) { | |
| case UniformSlot::kCombinedUniforms: | |
| bufferIndex = DawnGraphicsPipeline::kCombinedUniformIndex; | |
| break; | |
| case UniformSlot::kStorage: | |
| bufferIndex = DawnGraphicsPipeline::kStorageBufferIndex; | |
| break; | |
| } | |
| fBoundUniforms[bufferIndex] = info; | |
| fBoundUniformBuffersDirty = true; | |
| } | |
| void DawnCommandBuffer::bindInputBuffer(const Buffer* buffer, size_t offset, | |
| uint32_t bindingIndex) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| if (buffer) { | |
| auto dawnBuffer = static_cast<const DawnBuffer*>(buffer)->dawnBuffer(); | |
| fActiveRenderPassEncoder.SetVertexBuffer(bindingIndex, dawnBuffer, offset); | |
| } | |
| } | |
| void DawnCommandBuffer::bindIndexBuffer(const Buffer* indexBuffer, size_t offset) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| if (indexBuffer) { | |
| auto dawnBuffer = static_cast<const DawnBuffer*>(indexBuffer)->dawnBuffer(); | |
| fActiveRenderPassEncoder.SetIndexBuffer( | |
| dawnBuffer, wgpu::IndexFormat::Uint16, offset); | |
| } | |
| } | |
| void DawnCommandBuffer::bindIndirectBuffer(const Buffer* indirectBuffer, size_t offset) { | |
| if (indirectBuffer) { | |
| fCurrentIndirectBuffer = static_cast<const DawnBuffer*>(indirectBuffer)->dawnBuffer(); | |
| fCurrentIndirectBufferOffset = offset; | |
| } else { | |
| fCurrentIndirectBuffer = nullptr; | |
| fCurrentIndirectBufferOffset = 0; | |
| } | |
| } | |
| void DawnCommandBuffer::bindTextureAndSamplers( | |
| const DrawPass& drawPass, const DrawPassCommands::BindTexturesAndSamplers& command) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(fActiveGraphicsPipeline); | |
| // When there's an active graphics pipeline with a texture-copy dstread requirement, add one | |
| // to account for the intrinsic dstCopy texture we bind here. | |
| // NOTE: This is in units of pairs of textures and samplers, whereas the value reported by | |
| // the current pipeline is in net bindings (textures + samplers). | |
| int numTexturesAndSamplers = command.fNumTexSamplers; | |
| if (fActiveGraphicsPipeline->dstReadStrategy() == DstReadStrategy::kTextureCopy) { | |
| numTexturesAndSamplers++; | |
| } | |
| SkASSERT(fActiveGraphicsPipeline->numFragTexturesAndSamplers() == 2*numTexturesAndSamplers); | |
| // If possible, it's ideal to optimize for the common case of using a single texture with one | |
| // dynamic sampler. When using only one sampler, determine whether it is static or dynamic. | |
| bool usingSingleStaticSampler = false; | |
| #if !defined(__EMSCRIPTEN__) | |
| if (command.fNumTexSamplers == 1) { | |
| const wgpu::YCbCrVkDescriptor& ycbcrDesc = | |
| TextureInfoPriv::Get<DawnTextureInfo>( | |
| command.fTextures[0]->texture()->textureInfo()) | |
| .fYcbcrVkDescriptor; | |
| usingSingleStaticSampler = DawnDescriptorIsValid(ycbcrDesc); | |
| } | |
| #endif | |
| const auto& groupLayouts = fActiveGraphicsPipeline->dawnGroupLayouts(); | |
| wgpu::BindGroupLayout groupLayout = groupLayouts[DawnGraphicsPipeline::kTextureBindGroupIndex]; | |
| wgpu::BindGroup bindGroup; | |
| // Optimize for single texture with dynamic sampling. | |
| if (numTexturesAndSamplers == 1 && !usingSingleStaticSampler) { | |
| SkASSERT(fActiveGraphicsPipeline->numFragTexturesAndSamplers() == 2); | |
| SkASSERT(fActiveGraphicsPipeline->dstReadStrategy() != DstReadStrategy::kTextureCopy); | |
| const auto* texture = static_cast<const DawnTexture*>(command.fTextures[0]->texture()); | |
| const auto* sampler = this->getSampler(command, 0); | |
| bindGroup = fResourceProvider->findOrCreateSingleTextureSamplerBindGroup(sampler, texture); | |
| } else { | |
| std::vector<wgpu::BindGroupEntry> entries; | |
| for (int i = 0; i < command.fNumTexSamplers; ++i) { | |
| const auto* texture = static_cast<const DawnTexture*>( | |
| SK_UNSAFE_TODO(command.fTextures[i]->texture())); | |
| const auto* sampler = this->getSampler(command, i); | |
| auto& wgpuTextureView = texture->sampleTextureView(); | |
| auto& wgpuSampler = sampler->dawnSampler(); | |
| #if !defined(__EMSCRIPTEN__) | |
| // Assuming shader generator assigns binding slot to sampler then texture, | |
| // then the next sampler and texture, and so on, we need to use | |
| // 2 * i as base binding index of the sampler and texture. | |
| // TODO: https://b.corp.google.com/issues/259457090: | |
| // Better configurable way of assigning samplers and textures' bindings. | |
| const wgpu::YCbCrVkDescriptor& ycbcrDesc = | |
| texture->dawnTextureInfo().fYcbcrVkDescriptor; | |
| // Only add a sampler as a bind group entry if it's a regular dynamic sampler. A valid | |
| // YCbCrVkDescriptor indicates the usage of a static sampler, which should not be | |
| // included here. They should already be fully specified in the bind group layout. | |
| if (!DawnDescriptorIsValid(ycbcrDesc)) { | |
| #endif | |
| wgpu::BindGroupEntry samplerEntry; | |
| samplerEntry.binding = 2 * i; | |
| samplerEntry.sampler = wgpuSampler; | |
| entries.push_back(samplerEntry); | |
| #if !defined(__EMSCRIPTEN__) | |
| } | |
| #endif | |
| wgpu::BindGroupEntry textureEntry; | |
| textureEntry.binding = 2 * i + 1; | |
| textureEntry.textureView = wgpuTextureView; | |
| entries.push_back(textureEntry); | |
| } | |
| if (fActiveGraphicsPipeline->dstReadStrategy() == DstReadStrategy::kTextureCopy) { | |
| // Append the dstCopy sampler and texture as the very last two bind group entries | |
| wgpu::BindGroupEntry samplerEntry; | |
| samplerEntry.binding = 2*numTexturesAndSamplers - 2; | |
| samplerEntry.sampler = static_cast<const DawnSampler*>(fDstCopy.second)->dawnSampler(); | |
| entries.push_back(samplerEntry); | |
| wgpu::BindGroupEntry textureEntry; | |
| textureEntry.binding = 2*numTexturesAndSamplers - 1; | |
| textureEntry.textureView = | |
| static_cast<const DawnTexture*>(fDstCopy.first)->sampleTextureView(); | |
| entries.push_back(textureEntry); | |
| } | |
| bindGroup = fResourceProvider->createBindGroup(entries, groupLayout); | |
| } | |
| fActiveRenderPassEncoder.SetBindGroup(DawnGraphicsPipeline::kTextureBindGroupIndex, bindGroup); | |
| } | |
| void DawnCommandBuffer::syncUniformBuffers() { | |
| if (!fBoundUniformBuffersDirty) { | |
| return; | |
| } | |
| fBoundUniformBuffersDirty = false; | |
| bool usePushConstants = fSharedContext->dawnCaps()-> | |
| resourceBindingRequirements().fUsePushConstantsForIntrinsicConstants; | |
| // We expect to have up to 3 uniforms in this bind group. | |
| static constexpr int kMaxUniformsInGroup = 3; | |
| // Until/unless uniform bind group structure gets reorganized, this should be equivalent to the | |
| // size of our bound uniform array. | |
| SkASSERT(kMaxUniformsInGroup == fBoundUniforms.size()); | |
| const bool useStorageBuffers = fSharedContext->caps()->storageBufferSupport(); | |
| uint32_t numDynamicOffsets = useStorageBuffers ? 3 : 2; | |
| std::array<uint32_t, kMaxUniformsInGroup> dynamicOffsets {0}; | |
| wgpu::BindGroup bindGroup; | |
| // Check if we can use an optimized route for single-uniform buffer bind groups: | |
| if (usePushConstants && | |
| !fActiveGraphicsPipeline->usesStorageBuffer() && | |
| fActiveGraphicsPipeline->hasCombinedUniforms()) { | |
| const BindBufferInfo& bufferInfo = | |
| fBoundUniforms[DawnGraphicsPipeline::kCombinedUniformIndex]; | |
| bindGroup = fResourceProvider->findOrCreateSingleUniformBindGroup(bufferInfo); | |
| dynamicOffsets[DawnGraphicsPipeline::kCombinedUniformIndex] = bufferInfo.fOffset; | |
| } else { | |
| std::array<bool, kMaxUniformsInGroup> enabled = { | |
| !usePushConstants, // intrinsic uniforms | |
| fActiveGraphicsPipeline->hasCombinedUniforms(), // paint AND renderstep uniforms! | |
| fActiveGraphicsPipeline->usesStorageBuffer(), // storage SSBO | |
| }; | |
| constexpr uint32_t kBindingIndices[] = { | |
| DawnGraphicsPipeline::kIntrinsicUniformBufferIndex, | |
| DawnGraphicsPipeline::kCombinedUniformIndex, | |
| DawnGraphicsPipeline::kStorageBufferIndex, | |
| }; | |
| std::array<wgpu::BindGroupEntry, kMaxUniformsInGroup> bindGroupEntries {}; | |
| for (int i = 0; i < kMaxUniformsInGroup; ++i) { | |
| bindGroupEntries[i].binding = SK_UNSAFE_TODO(kBindingIndices[i]); | |
| if (i == 2 && !useStorageBuffers) { | |
| if (fBoundStorageFallbackTexture) { | |
| bindGroupEntries[i].textureView = | |
| static_cast<const DawnTexture*>(fBoundStorageFallbackTexture) | |
| ->sampleTextureView(); | |
| } else { | |
| SkASSERT(!fActiveGraphicsPipeline->usesStorageBuffer()); | |
| bindGroupEntries[i].textureView = | |
| fResourceProvider->getOrCreateNullTextureView(); | |
| } | |
| } else { | |
| if (enabled[i] && fBoundUniforms[i]) { | |
| bindGroupEntries[i].size = fBoundUniforms[i].fSize; | |
| bindGroupEntries[i].buffer = | |
| static_cast<const DawnBuffer*>(fBoundUniforms[i].fBuffer)->dawnBuffer(); | |
| dynamicOffsets[i] = fBoundUniforms[i].fOffset; | |
| } else { | |
| // Unused or null binding | |
| bindGroupEntries[i].buffer = fResourceProvider->getOrCreateNullBuffer(); | |
| dynamicOffsets[i] = 0; | |
| } | |
| } | |
| } | |
| const auto& groupLayouts = fActiveGraphicsPipeline->dawnGroupLayouts(); | |
| bindGroup = fResourceProvider->createBindGroup( | |
| bindGroupEntries, | |
| groupLayouts[DawnGraphicsPipeline::kUniformBufferBindGroupIndex]); | |
| } | |
| fActiveRenderPassEncoder.SetBindGroup(DawnGraphicsPipeline::kUniformBufferBindGroupIndex, | |
| bindGroup, | |
| numDynamicOffsets, | |
| dynamicOffsets.data()); | |
| } | |
| void DawnCommandBuffer::setScissor(const Scissor& scissor) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkIRect rect = scissor.getRect(fReplayTranslation, fRenderAreaBounds); | |
| fActiveRenderPassEncoder.SetScissorRect(rect.x(), rect.y(), rect.width(), rect.height()); | |
| } | |
| bool DawnCommandBuffer::updateIntrinsicUniformsAsUBO(UniformDataBlock uniformData) { | |
| BindBufferInfo binding = | |
| fResourceProvider->findOrCreateIntrinsicBindBufferInfo(this, uniformData); | |
| if (!binding) { | |
| return false; | |
| } else if (binding == fBoundUniforms[DawnGraphicsPipeline::kIntrinsicUniformBufferIndex]) { | |
| return true; // no binding change needed | |
| } | |
| fBoundUniforms[DawnGraphicsPipeline::kIntrinsicUniformBufferIndex] = binding; | |
| fBoundUniformBuffersDirty = true; | |
| return true; | |
| } | |
| bool DawnCommandBuffer::updateIntrinsicUniformsAsPushConstant(UniformDataBlock uniformData) { | |
| #if !defined(__EMSCRIPTEN__) | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(uniformData.size() <= DawnGraphicsPipeline::kIntrinsicUniformSize); | |
| fActiveRenderPassEncoder.SetImmediates(0, uniformData.data(), uniformData.size()); | |
| return true; | |
| #else | |
| SkASSERT(false); // No push constant support in WASM yet | |
| return false; | |
| #endif | |
| } | |
| void DawnCommandBuffer::setViewport(SkIRect viewport) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| fActiveRenderPassEncoder.SetViewport( | |
| viewport.x(), viewport.y(), viewport.width(), viewport.height(), 0, 1); | |
| } | |
| void DawnCommandBuffer::setBlendConstants(std::array<float, 4> blendConstants) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| wgpu::Color blendConst = { | |
| blendConstants[0], blendConstants[1], blendConstants[2], blendConstants[3]}; | |
| fActiveRenderPassEncoder.SetBlendConstant(&blendConst); | |
| } | |
| void DawnCommandBuffer::draw(PrimitiveType type, | |
| unsigned int baseVertex, | |
| unsigned int vertexCount) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(fActiveGraphicsPipeline->primitiveType() == type); | |
| this->syncUniformBuffers(); | |
| fActiveRenderPassEncoder.Draw(vertexCount, /*instanceCount=*/1, baseVertex); | |
| } | |
| void DawnCommandBuffer::drawIndexed(PrimitiveType type, | |
| unsigned int baseIndex, | |
| unsigned int indexCount, | |
| unsigned int baseVertex) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(fActiveGraphicsPipeline->primitiveType() == type); | |
| this->syncUniformBuffers(); | |
| fActiveRenderPassEncoder.DrawIndexed(indexCount, /*instanceCount=*/1, baseIndex, baseVertex); | |
| } | |
| void DawnCommandBuffer::drawInstanced(PrimitiveType type, | |
| unsigned int baseVertex, | |
| unsigned int vertexCount, | |
| unsigned int baseInstance, | |
| unsigned int instanceCount) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(fActiveGraphicsPipeline->primitiveType() == type); | |
| this->syncUniformBuffers(); | |
| fActiveRenderPassEncoder.Draw(vertexCount, instanceCount, baseVertex, baseInstance); | |
| } | |
| void DawnCommandBuffer::drawIndexedInstanced(PrimitiveType type, | |
| unsigned int baseIndex, | |
| unsigned int indexCount, | |
| unsigned int baseVertex, | |
| unsigned int baseInstance, | |
| unsigned int instanceCount) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(fActiveGraphicsPipeline->primitiveType() == type); | |
| this->syncUniformBuffers(); | |
| fActiveRenderPassEncoder.DrawIndexed( | |
| indexCount, instanceCount, baseIndex, baseVertex, baseInstance); | |
| } | |
| void DawnCommandBuffer::drawIndirect(PrimitiveType type) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(fActiveGraphicsPipeline->primitiveType() == type); | |
| SkASSERT(fCurrentIndirectBuffer); | |
| this->syncUniformBuffers(); | |
| fActiveRenderPassEncoder.DrawIndirect(fCurrentIndirectBuffer, fCurrentIndirectBufferOffset); | |
| } | |
| void DawnCommandBuffer::drawIndexedIndirect(PrimitiveType type) { | |
| SkASSERT(fActiveRenderPassEncoder); | |
| SkASSERT(fActiveGraphicsPipeline->primitiveType() == type); | |
| SkASSERT(fCurrentIndirectBuffer); | |
| this->syncUniformBuffers(); | |
| fActiveRenderPassEncoder.DrawIndexedIndirect(fCurrentIndirectBuffer, | |
| fCurrentIndirectBufferOffset); | |
| } | |
| void DawnCommandBuffer::beginComputePass() { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| SkASSERT(!fActiveComputePassEncoder); | |
| wgpu::ComputePassDescriptor wgpuComputePassDescriptor = {}; | |
| #if WGPU_TIMESTAMP_WRITES_DEFINED | |
| #if defined(__EMSCRIPTEN__) | |
| wgpu::ComputePassTimestampWrites wgpuTimestampWrites; | |
| #else | |
| wgpu::PassTimestampWrites wgpuTimestampWrites; | |
| #endif | |
| if (!fSharedContext->dawnCaps()->supportsCommandBufferTimestamps() && fTimestampQueryBuffer) { | |
| SkASSERT(fTimestampQuerySet); | |
| wgpuTimestampWrites.querySet = fTimestampQuerySet; | |
| if (!fWroteFirstPassTimestamps) { | |
| wgpuTimestampWrites.beginningOfPassWriteIndex = 0; | |
| fWroteFirstPassTimestamps = true; | |
| } | |
| wgpuTimestampWrites.endOfPassWriteIndex = 1; | |
| wgpuComputePassDescriptor.timestampWrites = &wgpuTimestampWrites; | |
| } | |
| #else | |
| SkASSERT(!fTimestampQueryBuffer); | |
| #endif | |
| fActiveComputePassEncoder = fCommandEncoder.BeginComputePass(&wgpuComputePassDescriptor); | |
| } | |
| void DawnCommandBuffer::bindComputePipeline(const ComputePipeline* computePipeline) { | |
| SkASSERT(fActiveComputePassEncoder); | |
| fActiveComputePipeline = static_cast<const DawnComputePipeline*>(computePipeline); | |
| fActiveComputePassEncoder.SetPipeline(fActiveComputePipeline->dawnComputePipeline()); | |
| } | |
| void DawnCommandBuffer::bindDispatchResources(const DispatchGroup& group, | |
| const DispatchGroup::Dispatch& dispatch) { | |
| SkASSERT(fActiveComputePassEncoder); | |
| SkASSERT(fActiveComputePipeline); | |
| // Bind all pipeline resources to a single new bind group at index 0. | |
| // NOTE: Caching the bind groups here might be beneficial based on the layout and the bound | |
| // resources (though it's questionable how often a bind group will end up getting reused since | |
| // the bound objects change often). | |
| skia_private::TArray<wgpu::BindGroupEntry> entries; | |
| entries.reserve(dispatch.fBindings.size()); | |
| for (const ResourceBinding& binding : dispatch.fBindings) { | |
| wgpu::BindGroupEntry& entry = entries.push_back(); | |
| entry.binding = binding.fIndex; | |
| if (const BindBufferInfo* buffer = std::get_if<BindBufferInfo>(&binding.fResource)) { | |
| entry.buffer = static_cast<const DawnBuffer*>(buffer->fBuffer)->dawnBuffer(); | |
| entry.offset = buffer->fOffset; | |
| entry.size = buffer->fSize; | |
| } else if (const TextureIndex* texIdx = std::get_if<TextureIndex>(&binding.fResource)) { | |
| const DawnTexture* texture = | |
| static_cast<const DawnTexture*>(group.getTexture(texIdx->fValue)); | |
| SkASSERT(texture); | |
| entry.textureView = texture->sampleTextureView(); | |
| } else if (const SamplerIndex* samplerIdx = std::get_if<SamplerIndex>(&binding.fResource)) { | |
| const DawnSampler* sampler = | |
| static_cast<const DawnSampler*>(group.getSampler(samplerIdx->fValue)); | |
| entry.sampler = sampler->dawnSampler(); | |
| } else { | |
| SK_ABORT("unsupported dispatch resource type"); | |
| } | |
| } | |
| wgpu::BindGroupDescriptor desc; | |
| desc.layout = fActiveComputePipeline->dawnGroupLayout(); | |
| desc.entryCount = entries.size(); | |
| desc.entries = entries.data(); | |
| auto bindGroup = fSharedContext->device().CreateBindGroup(&desc); | |
| fActiveComputePassEncoder.SetBindGroup(0, bindGroup); | |
| } | |
| void DawnCommandBuffer::dispatchWorkgroups(const WorkgroupSize& globalSize) { | |
| SkASSERT(fActiveComputePassEncoder); | |
| SkASSERT(fActiveComputePipeline); | |
| fActiveComputePassEncoder.DispatchWorkgroups( | |
| globalSize.fWidth, globalSize.fHeight, globalSize.fDepth); | |
| } | |
| void DawnCommandBuffer::dispatchWorkgroupsIndirect(const Buffer* indirectBuffer, | |
| size_t indirectBufferOffset) { | |
| SkASSERT(fActiveComputePassEncoder); | |
| SkASSERT(fActiveComputePipeline); | |
| auto& wgpuIndirectBuffer = static_cast<const DawnBuffer*>(indirectBuffer)->dawnBuffer(); | |
| fActiveComputePassEncoder.DispatchWorkgroupsIndirect(wgpuIndirectBuffer, indirectBufferOffset); | |
| } | |
| void DawnCommandBuffer::endComputePass() { | |
| SkASSERT(fActiveComputePassEncoder); | |
| fActiveComputePassEncoder.End(); | |
| fActiveComputePassEncoder = nullptr; | |
| } | |
| bool DawnCommandBuffer::onCopyBufferToBuffer(const Buffer* srcBuffer, | |
| size_t srcOffset, | |
| const Buffer* dstBuffer, | |
| size_t dstOffset, | |
| size_t size) { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| SkASSERT(!fActiveComputePassEncoder); | |
| auto& wgpuBufferSrc = static_cast<const DawnBuffer*>(srcBuffer)->dawnBuffer(); | |
| auto& wgpuBufferDst = static_cast<const DawnBuffer*>(dstBuffer)->dawnBuffer(); | |
| fCommandEncoder.CopyBufferToBuffer(wgpuBufferSrc, srcOffset, wgpuBufferDst, dstOffset, size); | |
| return true; | |
| } | |
| bool DawnCommandBuffer::onCopyTextureToBuffer(const Texture* texture, | |
| SkIRect srcRect, | |
| const Buffer* buffer, | |
| size_t bufferOffset, | |
| size_t bufferRowBytes) { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| SkASSERT(!fActiveComputePassEncoder); | |
| const auto* wgpuTexture = static_cast<const DawnTexture*>(texture); | |
| auto& wgpuBuffer = static_cast<const DawnBuffer*>(buffer)->dawnBuffer(); | |
| wgpu::TexelCopyTextureInfo src; | |
| src.texture = wgpuTexture->dawnTexture(); | |
| src.origin.x = srcRect.x(); | |
| src.origin.y = srcRect.y(); | |
| src.aspect = wgpuTexture->dawnTextureInfo().fAspect; | |
| wgpu::TexelCopyBufferInfo dst; | |
| dst.buffer = wgpuBuffer; | |
| dst.layout.offset = bufferOffset; | |
| dst.layout.bytesPerRow = bufferRowBytes; | |
| wgpu::Extent3D copySize = { | |
| static_cast<uint32_t>(srcRect.width()), static_cast<uint32_t>(srcRect.height()), 1}; | |
| fCommandEncoder.CopyTextureToBuffer(&src, &dst, ©Size); | |
| return true; | |
| } | |
| bool DawnCommandBuffer::onCopyBufferToTexture(const Buffer* buffer, | |
| const Texture* texture, | |
| const BufferTextureCopyData* copyData, | |
| int count) { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| SkASSERT(!fActiveComputePassEncoder); | |
| auto& wgpuTexture = static_cast<const DawnTexture*>(texture)->dawnTexture(); | |
| auto& wgpuBuffer = static_cast<const DawnBuffer*>(buffer)->dawnBuffer(); | |
| wgpu::TexelCopyBufferInfo src; | |
| src.buffer = wgpuBuffer; | |
| wgpu::TexelCopyTextureInfo dst; | |
| dst.texture = wgpuTexture; | |
| for (int i = 0; i < count; ++i) { | |
| src.layout.offset = SK_UNSAFE_TODO(copyData[i].fBufferOffset); | |
| src.layout.bytesPerRow = SK_UNSAFE_TODO(copyData[i].fBufferRowBytes); | |
| dst.origin.x = SK_UNSAFE_TODO(copyData[i].fRect.x()); | |
| dst.origin.y = SK_UNSAFE_TODO(copyData[i].fRect.y()); | |
| dst.mipLevel = SK_UNSAFE_TODO(copyData[i].fMipLevel); | |
| wgpu::Extent3D copySize = { | |
| static_cast<uint32_t>(SK_UNSAFE_TODO(copyData[i].fRect.width())), | |
| static_cast<uint32_t>(SK_UNSAFE_TODO(copyData[i].fRect.height())), | |
| 1}; | |
| fCommandEncoder.CopyBufferToTexture(&src, &dst, ©Size); | |
| } | |
| return true; | |
| } | |
| bool DawnCommandBuffer::onCopyTextureToTexture(const Texture* src, | |
| SkIRect srcRect, | |
| const Texture* dst, | |
| SkIPoint dstPoint, | |
| int mipLevel) { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| SkASSERT(!fActiveComputePassEncoder); | |
| auto& wgpuTextureSrc = static_cast<const DawnTexture*>(src)->dawnTexture(); | |
| auto& wgpuTextureDst = static_cast<const DawnTexture*>(dst)->dawnTexture(); | |
| wgpu::TexelCopyTextureInfo srcArgs; | |
| srcArgs.texture = wgpuTextureSrc; | |
| srcArgs.origin.x = srcRect.fLeft; | |
| srcArgs.origin.y = srcRect.fTop; | |
| wgpu::TexelCopyTextureInfo dstArgs; | |
| dstArgs.texture = wgpuTextureDst; | |
| dstArgs.origin.x = dstPoint.fX; | |
| dstArgs.origin.y = dstPoint.fY; | |
| dstArgs.mipLevel = mipLevel; | |
| wgpu::Extent3D copySize = { | |
| static_cast<uint32_t>(srcRect.width()), static_cast<uint32_t>(srcRect.height()), 1}; | |
| fCommandEncoder.CopyTextureToTexture(&srcArgs, &dstArgs, ©Size); | |
| return true; | |
| } | |
| bool DawnCommandBuffer::onSynchronizeBufferToCpu(const Buffer* buffer, bool* outDidResultInWork) { | |
| return true; | |
| } | |
| bool DawnCommandBuffer::onClearBuffer(const Buffer* buffer, size_t offset, size_t size) { | |
| SkASSERT(!fActiveRenderPassEncoder); | |
| SkASSERT(!fActiveComputePassEncoder); | |
| auto& wgpuBuffer = static_cast<const DawnBuffer*>(buffer)->dawnBuffer(); | |
| fCommandEncoder.ClearBuffer(wgpuBuffer, offset, size); | |
| return true; | |
| } | |
| } // namespace skgpu::graphite |