| // Copyright 2024 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "dawn/native/RenderPassWorkaroundsHelper.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/inlined_vector.h" |
| #include "dawn/common/Assert.h" |
| #include "dawn/native/AttachmentState.h" |
| #include "dawn/native/BlitColorToColorWithDraw.h" |
| #include "dawn/native/CommandEncoder.h" |
| #include "dawn/native/Commands.h" |
| #include "dawn/native/Device.h" |
| #include "dawn/native/Texture.h" |
| |
| namespace dawn::native { |
| |
| namespace { |
| // Tracks the temporary resolve attachments used when the AlwaysResolveIntoZeroLevelAndLayer toggle |
| // is active so that the results can be copied from the temporary resolve attachment into the |
| // intended target after the render pass is complete. Also used by the |
| // ResolveMultipleAttachmentInSeparatePasses toggle to track resolves that need to be done in their |
| // own passes. |
| struct TemporaryResolveAttachment { |
| TemporaryResolveAttachment(Ref<TextureViewBase> src, |
| Ref<TextureViewBase> dst, |
| wgpu::StoreOp storeOp = wgpu::StoreOp::Store) |
| : copySrc(std::move(src)), copyDst(std::move(dst)), storeOp(storeOp) {} |
| |
| Ref<TextureViewBase> copySrc; |
| Ref<TextureViewBase> copyDst; |
| wgpu::StoreOp storeOp; |
| }; |
| |
| void CopyTextureView(CommandEncoder* encoder, TextureViewBase* src, TextureViewBase* dst) { |
| TexelCopyTextureInfo srcTexelCopyTextureInfo = {}; |
| srcTexelCopyTextureInfo.texture = src->GetTexture(); |
| srcTexelCopyTextureInfo.aspect = wgpu::TextureAspect::All; |
| srcTexelCopyTextureInfo.mipLevel = src->GetBaseMipLevel(); |
| srcTexelCopyTextureInfo.origin = {0, 0, src->GetBaseArrayLayer()}; |
| |
| TexelCopyTextureInfo dstTexelCopyTextureInfo = {}; |
| dstTexelCopyTextureInfo.texture = dst->GetTexture(); |
| dstTexelCopyTextureInfo.aspect = wgpu::TextureAspect::All; |
| dstTexelCopyTextureInfo.mipLevel = dst->GetBaseMipLevel(); |
| dstTexelCopyTextureInfo.origin = {0, 0, dst->GetBaseArrayLayer()}; |
| |
| Extent3D extent3D = src->GetSingleSubresourceVirtualSize(); |
| |
| auto internalUsageScope = encoder->MakeInternalUsageScope(); |
| encoder->APICopyTextureToTexture(&srcTexelCopyTextureInfo, &dstTexelCopyTextureInfo, &extent3D); |
| } |
| |
| void ResolveWithRenderPass(CommandEncoder* encoder, |
| TextureViewBase* src, |
| TextureViewBase* dst, |
| wgpu::StoreOp storeOp) { |
| RenderPassColorAttachment attachment = {}; |
| attachment.view = src; |
| attachment.resolveTarget = dst; |
| attachment.loadOp = wgpu::LoadOp::Load; |
| attachment.storeOp = storeOp; |
| |
| RenderPassDescriptor resolvePass = {}; |
| resolvePass.colorAttachmentCount = 1; |
| resolvePass.colorAttachments = &attachment; |
| |
| // Begin and end an empty render pass to force the resolve. |
| Ref<RenderPassEncoder> rpEncoder = encoder->BeginRenderPass(&resolvePass); |
| rpEncoder->End(); |
| } |
| |
| void DiscardWithRenderPass(CommandEncoder* encoder, TextureViewBase* view) { |
| ResolveWithRenderPass(encoder, view, nullptr, wgpu::StoreOp::Discard); |
| } |
| |
| } // namespace |
| |
| RenderPassWorkaroundsHelper::RenderPassWorkaroundsHelper() = default; |
| RenderPassWorkaroundsHelper::~RenderPassWorkaroundsHelper() = default; |
| |
| MaybeError RenderPassWorkaroundsHelper::Initialize( |
| CommandEncoder* encoder, |
| const UnpackedPtr<RenderPassDescriptor>& renderPassDescriptor) { |
| DeviceBase* device = encoder->GetDevice(); |
| |
| // dawn:56, dawn:1569 |
| // Handle Toggle AlwaysResolveIntoZeroLevelAndLayer. This swaps out the given resolve attachment |
| // for a temporary one that has no layers or mip levels. The results are copied from the |
| // temporary attachment into the given attachment when the render pass ends. (Handled in |
| // Apply()) |
| if (device->IsToggleEnabled(Toggle::AlwaysResolveIntoZeroLevelAndLayer)) { |
| for (uint8_t i = 0; i < renderPassDescriptor->colorAttachmentCount; ++i) { |
| ColorAttachmentIndex colorIdx(i); |
| const auto& colorAttachment = renderPassDescriptor->colorAttachments[i]; |
| TextureViewBase* resolveTarget = colorAttachment.resolveTarget; |
| |
| if (resolveTarget != nullptr && (resolveTarget->GetBaseMipLevel() != 0 || |
| resolveTarget->GetBaseArrayLayer() != 0)) { |
| DAWN_ASSERT(colorAttachment.view); |
| // Create a temporary texture to resolve into |
| // TODO(dawn:1618): Defer allocation of temporary textures till submit time. |
| TextureDescriptor descriptor = {}; |
| descriptor.usage = wgpu::TextureUsage::RenderAttachment | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | |
| wgpu::TextureUsage::TextureBinding; |
| descriptor.format = resolveTarget->GetFormat().format; |
| descriptor.size = resolveTarget->GetSingleSubresourceVirtualSize(); |
| descriptor.dimension = wgpu::TextureDimension::e2D; |
| descriptor.mipLevelCount = 1; |
| |
| // We are creating new resources. Device must already be locked via |
| // APIBeginRenderPass. |
| // TODO(crbug.com/dawn/1618): In future, all temp resources should be created at |
| // Command Submit time, so the locking would be removed from here at that point. |
| Ref<TextureBase> temporaryResolveTexture; |
| Ref<TextureViewBase> temporaryResolveView; |
| { |
| DAWN_ASSERT(device->IsLockedByCurrentThreadIfNeeded()); |
| |
| DAWN_TRY_ASSIGN(temporaryResolveTexture, device->CreateTexture(&descriptor)); |
| |
| TextureViewDescriptor viewDescriptor = {}; |
| DAWN_TRY_ASSIGN( |
| temporaryResolveView, |
| device->CreateTextureView(temporaryResolveTexture.Get(), &viewDescriptor)); |
| |
| if (colorAttachment.loadOp == wgpu::LoadOp::ExpandResolveTexture) { |
| // Since we want to load the original resolve target, we need to copy it to |
| // the temp resolve target. |
| CopyTextureView(encoder, resolveTarget, temporaryResolveView.Get()); |
| } |
| } |
| |
| mTempResolveTargets[colorIdx] = {std::move(temporaryResolveTexture), |
| std::move(temporaryResolveView)}; |
| |
| mTempResolveTargetsMask.set(colorIdx); |
| } |
| } |
| } |
| |
| return {}; |
| } |
| |
| MaybeError RenderPassWorkaroundsHelper::ApplyOnPostEncoding( |
| CommandEncoder* encoder, |
| const UnpackedPtr<RenderPassDescriptor>& renderPassDescriptor, |
| RenderPassResourceUsageTracker* usageTracker, |
| BeginRenderPassCmd* cmd, |
| RenderPassEncoder::EndCallback* passEndCallbackOut) { |
| auto device = encoder->GetDevice(); |
| |
| // List of operations to perform on render pass end. |
| absl::InlinedVector<RenderPassEncoder::EndCallback, 1> passEndOperations; |
| |
| // dawn:56, dawn:1569 |
| // swap the resolve targets for the temp resolve textures. |
| if (mTempResolveTargetsMask.any()) { |
| std::vector<TemporaryResolveAttachment> temporaryResolveAttachments; |
| |
| for (auto index : mTempResolveTargetsMask) { |
| TextureViewBase* resolveTarget = cmd->colorAttachments[index].resolveTarget.Get(); |
| TextureViewBase* temporaryResolveView = mTempResolveTargets[index].view.Get(); |
| |
| DAWN_ASSERT(resolveTarget); |
| |
| // Save the temporary and given render targets together for copying after |
| // the render pass ends. |
| temporaryResolveAttachments.emplace_back(temporaryResolveView, resolveTarget); |
| |
| // Replace the given resolve attachment with the temporary one. |
| usageTracker->TextureViewUsedAs(temporaryResolveView, |
| wgpu::TextureUsage::RenderAttachment); |
| cmd->colorAttachments[index].resolveTarget = temporaryResolveView; |
| } |
| |
| passEndOperations.emplace_back([encoder, temporaryResolveAttachments = std::move( |
| temporaryResolveAttachments)]() -> MaybeError { |
| // Called once the render pass has been ended. |
| // Handle any copies needed for the AlwaysResolveIntoZeroLevelAndLayer |
| // workaround immediately after the render pass ends and before any additional |
| // commands are recorded. |
| for (auto& copyTarget : temporaryResolveAttachments) { |
| CopyTextureView(encoder, copyTarget.copySrc.Get(), copyTarget.copyDst.Get()); |
| } |
| return {}; |
| }); |
| } |
| |
| mShouldApplyExpandResolveEmulation = |
| cmd->attachmentState->GetExpandResolveInfo().attachmentsToExpandResolve.any() && |
| device->CanTextureLoadResolveTargetInTheSameRenderpass(); |
| |
| std::optional<RenderPassDescriptorResolveRect> expandResolveRect; |
| if (auto* legacyResolveRect = |
| renderPassDescriptor.Get<RenderPassDescriptorExpandResolveRect>()) { |
| RenderPassDescriptorResolveRect rect; |
| rect.colorOffsetX = legacyResolveRect->x; |
| rect.colorOffsetY = legacyResolveRect->y; |
| rect.resolveOffsetX = legacyResolveRect->x; |
| rect.resolveOffsetY = legacyResolveRect->y; |
| rect.width = legacyResolveRect->width; |
| rect.height = legacyResolveRect->height; |
| expandResolveRect = rect; |
| |
| } else if (auto* resolveRect = renderPassDescriptor.Get<RenderPassDescriptorResolveRect>()) { |
| expandResolveRect = *resolveRect; |
| } |
| // Handle partial resolve. This identifies passes where there are MSAA color attachments with |
| // wgpu::LoadOp::ExpandResolveTexture. If that's the case then the resolves are deferred by |
| // removing the resolve targets and forcing the storeOp to Store. After the pass has ended an |
| // new pass is recorded for each resolve target that resolves it separately. |
| if (expandResolveRect) { |
| std::vector<TemporaryResolveAttachment> temporaryResolveAttachments; |
| |
| for (auto i : cmd->attachmentState->GetColorAttachmentsMask()) { |
| auto& attachmentInfo = cmd->colorAttachments[i]; |
| TextureViewBase* resolveTarget = attachmentInfo.resolveTarget.Get(); |
| if (!resolveTarget) { |
| continue; |
| } |
| // Save the color and resolve targets together for an explicit resolve pass |
| // after this one ends, then remove the resolve target from this pass and |
| // force the storeOp to Store. |
| temporaryResolveAttachments.emplace_back(attachmentInfo.view.Get(), resolveTarget, |
| attachmentInfo.storeOp); |
| attachmentInfo.storeOp = wgpu::StoreOp::Store; |
| attachmentInfo.resolveTarget = nullptr; |
| } |
| for (auto& deferredResolve : temporaryResolveAttachments) { |
| passEndOperations.emplace_back([device, encoder, resolveRect = *expandResolveRect, |
| deferredResolve]() -> MaybeError { |
| // Do partial resolve first in one render pass. |
| DAWN_TRY(ResolveMultisampleWithDraw(device, encoder, resolveRect, |
| deferredResolve.copySrc.Get(), |
| deferredResolve.copyDst.Get())); |
| |
| switch (deferredResolve.storeOp) { |
| case wgpu::StoreOp::Store: |
| // 'Store' has been handled in the main render pass already. |
| break; |
| case wgpu::StoreOp::Discard: |
| // Handle 'Discard', tagging the subresource as uninitialized. |
| DiscardWithRenderPass(encoder, deferredResolve.copySrc.Get()); |
| break; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| return {}; |
| }); |
| } |
| } |
| |
| // dawn:1550 |
| // Handle toggle ResolveMultipleAttachmentInSeparatePasses. This identifies passes where there |
| // are multiple MSAA color targets and at least one of them has a resolve target. If that's the |
| // case then the resolves are deferred by removing the resolve targets and forcing the storeOp |
| // to Store. After the pass has ended an new pass is recorded for each resolve target that |
| // resolves it separately. |
| if (device->IsToggleEnabled(Toggle::ResolveMultipleAttachmentInSeparatePasses) && |
| cmd->attachmentState->GetColorAttachmentsMask().count() > 1) { |
| bool splitResolvesIntoSeparatePasses = false; |
| |
| // This workaround needs to apply if there are multiple MSAA color targets (checked above) |
| // and at least one resolve target. |
| for (auto i : cmd->attachmentState->GetColorAttachmentsMask()) { |
| if (cmd->colorAttachments[i].resolveTarget.Get() != nullptr) { |
| splitResolvesIntoSeparatePasses = true; |
| break; |
| } |
| } |
| |
| if (splitResolvesIntoSeparatePasses) { |
| std::vector<TemporaryResolveAttachment> temporaryResolveAttachments; |
| |
| for (auto i : cmd->attachmentState->GetColorAttachmentsMask()) { |
| auto& attachmentInfo = cmd->colorAttachments[i]; |
| TextureViewBase* resolveTarget = attachmentInfo.resolveTarget.Get(); |
| if (resolveTarget != nullptr) { |
| // Save the color and resolve targets together for an explicit resolve pass |
| // after this one ends, then remove the resolve target from this pass and |
| // force the storeOp to Store. |
| temporaryResolveAttachments.emplace_back(attachmentInfo.view.Get(), |
| resolveTarget, attachmentInfo.storeOp); |
| attachmentInfo.storeOp = wgpu::StoreOp::Store; |
| attachmentInfo.resolveTarget = nullptr; |
| } |
| } |
| |
| passEndOperations.emplace_back( |
| [encoder, temporaryResolveAttachments = |
| std::move(temporaryResolveAttachments)]() -> MaybeError { |
| // Called once the render pass has been ended. |
| // Handles any separate resolve passes needed for the |
| // ResolveMultipleAttachmentInSeparatePasses workaround immediately after the |
| // render pass ends and before any additional commands are recorded. |
| for (auto& deferredResolve : temporaryResolveAttachments) { |
| ResolveWithRenderPass(encoder, deferredResolve.copySrc.Get(), |
| deferredResolve.copyDst.Get(), |
| deferredResolve.storeOp); |
| } |
| return {}; |
| }); |
| } |
| } |
| |
| *passEndCallbackOut = [passEndOperations = std::move(passEndOperations)]() -> MaybeError { |
| // Apply the operations in reverse order. Typically they can be: |
| // 1.a) Full resolve pass. |
| // 1.b) Partial resolve pass, and conditionally followed with a MSAA texture Discard pass. |
| // 2) Copy from temp resolve to the actual resolve target. |
| for (auto opIte = passEndOperations.rbegin(); opIte != passEndOperations.rend(); ++opIte) { |
| DAWN_TRY((*opIte)()); |
| } |
| return {}; |
| }; |
| |
| return {}; |
| } |
| |
| MaybeError RenderPassWorkaroundsHelper::ApplyOnRenderPassStart( |
| RenderPassEncoder* rpEncoder, |
| const UnpackedPtr<RenderPassDescriptor>& rpDesc) { |
| DeviceBase* device = rpEncoder->GetDevice(); |
| if (mShouldApplyExpandResolveEmulation) { |
| // Perform ExpandResolveTexture load op's emulation after the render pass starts. |
| // Backend that doesn't support CanTextureLoadResolveTargetInTheSameRenderpass() can |
| // implement this load op internally. |
| DAWN_TRY(ExpandResolveTextureWithDraw(device, rpEncoder, rpDesc)); |
| } |
| |
| return {}; |
| } |
| |
| } // namespace dawn::native |