diff --git a/src/dawn/native/metal/CommandBufferMTL.h b/src/dawn/native/metal/CommandBufferMTL.h index 6fd68cf10d..d5612d7494 100644 --- a/src/dawn/native/metal/CommandBufferMTL.h +++ b/src/dawn/native/metal/CommandBufferMTL.h @@ -53,15 +53,7 @@ namespace dawn::native::metal { using CommandBufferBase::CommandBufferBase; MaybeError EncodeComputePass(CommandRecordingContext* commandContext); - MaybeError EncodeRenderPass(CommandRecordingContext* commandContext, - MTLRenderPassDescriptor* mtlRenderPass, - uint32_t width, - uint32_t height); - - MaybeError EncodeRenderPassInternal(CommandRecordingContext* commandContext, - MTLRenderPassDescriptor* mtlRenderPass, - uint32_t width, - uint32_t height); + MaybeError EncodeRenderPass(id encoder); }; } // namespace dawn::native::metal diff --git a/src/dawn/native/metal/CommandBufferMTL.mm b/src/dawn/native/metal/CommandBufferMTL.mm index fd232b4687..b8861ff3ac 100644 --- a/src/dawn/native/metal/CommandBufferMTL.mm +++ b/src/dawn/native/metal/CommandBufferMTL.mm @@ -38,15 +38,6 @@ namespace dawn::native::metal { namespace { - // Allows this file to use MTLStoreActionStoreAndMultismapleResolve because the logic is - // first to compute what the "best" Metal render pass descriptor is, then fix it up if we - // are not on macOS 10.12 (i.e. the EmulateStoreAndMSAAResolve toggle is on). -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" - constexpr MTLStoreAction kMTLStoreActionStoreAndMultisampleResolve = - MTLStoreActionStoreAndMultisampleResolve; -#pragma clang diagnostic pop - MTLIndexType MTLIndexFormat(wgpu::IndexFormat format) { switch (format) { case wgpu::IndexFormat::Uint16: @@ -213,83 +204,6 @@ namespace dawn::native::metal { return descriptorRef; } - // Helper function for Toggle EmulateStoreAndMSAAResolve - void ResolveInAnotherRenderPass( - CommandRecordingContext* commandContext, - const MTLRenderPassDescriptor* mtlRenderPass, - const std::array, kMaxColorAttachments>& resolveTextures) { - // Note that this creates a descriptor that's autoreleased so we don't use AcquireNSRef - NSRef mtlRenderPassForResolveRef = - [MTLRenderPassDescriptor renderPassDescriptor]; - MTLRenderPassDescriptor* mtlRenderPassForResolve = mtlRenderPassForResolveRef.Get(); - - for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { - if (resolveTextures[i] == nullptr) { - continue; - } - - mtlRenderPassForResolve.colorAttachments[i].texture = - mtlRenderPass.colorAttachments[i].texture; - mtlRenderPassForResolve.colorAttachments[i].loadAction = MTLLoadActionLoad; - mtlRenderPassForResolve.colorAttachments[i].storeAction = - MTLStoreActionMultisampleResolve; - mtlRenderPassForResolve.colorAttachments[i].resolveTexture = resolveTextures[i]; - mtlRenderPassForResolve.colorAttachments[i].resolveLevel = - mtlRenderPass.colorAttachments[i].resolveLevel; - mtlRenderPassForResolve.colorAttachments[i].resolveSlice = - mtlRenderPass.colorAttachments[i].resolveSlice; - } - - commandContext->BeginRender(mtlRenderPassForResolve); - commandContext->EndRender(); - } - - // Helper functions for Toggle AlwaysResolveIntoZeroLevelAndLayer - ResultOrError>> CreateResolveTextureForWorkaround( - Device* device, - MTLPixelFormat mtlFormat, - uint32_t width, - uint32_t height) { - NSRef mtlDescRef = AcquireNSRef([MTLTextureDescriptor new]); - MTLTextureDescriptor* mtlDesc = mtlDescRef.Get(); - - mtlDesc.textureType = MTLTextureType2D; - mtlDesc.usage = MTLTextureUsageRenderTarget; - mtlDesc.pixelFormat = mtlFormat; - mtlDesc.width = width; - mtlDesc.height = height; - mtlDesc.depth = 1; - mtlDesc.mipmapLevelCount = 1; - mtlDesc.arrayLength = 1; - mtlDesc.storageMode = MTLStorageModePrivate; - mtlDesc.sampleCount = 1; - - id texture = [device->GetMTLDevice() newTextureWithDescriptor:mtlDesc]; - if (texture == nil) { - return DAWN_OUT_OF_MEMORY_ERROR("Allocation of temporary texture failed."); - } - - return AcquireNSPRef(texture); - } - - void CopyIntoTrueResolveTarget(CommandRecordingContext* commandContext, - id mtlTrueResolveTexture, - uint32_t trueResolveLevel, - uint32_t trueResolveSlice, - id temporaryResolveTexture, - uint32_t width, - uint32_t height) { - [commandContext->EnsureBlit() copyFromTexture:temporaryResolveTexture - sourceSlice:0 - sourceLevel:0 - sourceOrigin:MTLOriginMake(0, 0, 0) - sourceSize:MTLSizeMake(width, height, 1) - toTexture:mtlTrueResolveTexture - destinationSlice:trueResolveSlice - destinationLevel:trueResolveLevel - destinationOrigin:MTLOriginMake(0, 0, 0)]; - } - // Metal uses a physical addressing mode which means buffers in the shading language are // just pointers to the virtual address of their start. This means there is no way to know // the length of a buffer to compute the length() of unsized arrays at the end of storage @@ -731,8 +645,11 @@ namespace dawn::native::metal { LazyClearRenderPassAttachments(cmd); NSRef descriptor = CreateMTLRenderPassDescriptor(cmd); - DAWN_TRY(EncodeRenderPass(commandContext, descriptor.Get(), cmd->width, - cmd->height)); + DAWN_TRY(EncodeMetalRenderPass( + ToBackend(GetDevice()), commandContext, descriptor.Get(), cmd->width, + cmd->height, [this](id encoder) -> MaybeError { + return this->EncodeRenderPass(encoder); + })); nextRenderPassNumber++; break; @@ -1192,102 +1109,7 @@ namespace dawn::native::metal { UNREACHABLE(); } - MaybeError CommandBuffer::EncodeRenderPass(CommandRecordingContext* commandContext, - MTLRenderPassDescriptor* mtlRenderPass, - uint32_t width, - uint32_t height) { - ASSERT(mtlRenderPass); - - Device* device = ToBackend(GetDevice()); - - // Handle Toggle AlwaysResolveIntoZeroLevelAndLayer. We must handle this before applying - // the store + MSAA resolve workaround, otherwise this toggle will never be handled because - // the resolve texture is removed when applying the store + MSAA resolve workaround. - if (device->IsToggleEnabled(Toggle::AlwaysResolveIntoZeroLevelAndLayer)) { - std::array, kMaxColorAttachments> trueResolveTextures = {}; - std::array trueResolveLevels = {}; - std::array trueResolveSlices = {}; - - // Use temporary resolve texture on the resolve targets with non-zero resolveLevel or - // resolveSlice. - bool useTemporaryResolveTexture = false; - std::array>, kMaxColorAttachments> temporaryResolveTextures = {}; - for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { - if (mtlRenderPass.colorAttachments[i].resolveTexture == nullptr) { - continue; - } - - if (mtlRenderPass.colorAttachments[i].resolveLevel == 0 && - mtlRenderPass.colorAttachments[i].resolveSlice == 0) { - continue; - } - - trueResolveTextures[i] = mtlRenderPass.colorAttachments[i].resolveTexture; - trueResolveLevels[i] = mtlRenderPass.colorAttachments[i].resolveLevel; - trueResolveSlices[i] = mtlRenderPass.colorAttachments[i].resolveSlice; - - const MTLPixelFormat mtlFormat = trueResolveTextures[i].pixelFormat; - DAWN_TRY_ASSIGN(temporaryResolveTextures[i], CreateResolveTextureForWorkaround( - device, mtlFormat, width, height)); - - mtlRenderPass.colorAttachments[i].resolveTexture = - temporaryResolveTextures[i].Get(); - mtlRenderPass.colorAttachments[i].resolveLevel = 0; - mtlRenderPass.colorAttachments[i].resolveSlice = 0; - useTemporaryResolveTexture = true; - } - - // If we need to use a temporary resolve texture we need to copy the result of MSAA - // resolve back to the true resolve targets. - if (useTemporaryResolveTexture) { - DAWN_TRY(EncodeRenderPass(commandContext, mtlRenderPass, width, height)); - for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { - if (trueResolveTextures[i] == nullptr) { - continue; - } - - ASSERT(temporaryResolveTextures[i] != nullptr); - CopyIntoTrueResolveTarget(commandContext, trueResolveTextures[i], - trueResolveLevels[i], trueResolveSlices[i], - temporaryResolveTextures[i].Get(), width, height); - } - return {}; - } - } - - // Handle Store + MSAA resolve workaround (Toggle EmulateStoreAndMSAAResolve). - if (device->IsToggleEnabled(Toggle::EmulateStoreAndMSAAResolve)) { - bool hasStoreAndMSAAResolve = false; - - // Remove any store + MSAA resolve and remember them. - std::array, kMaxColorAttachments> resolveTextures = {}; - for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { - if (mtlRenderPass.colorAttachments[i].storeAction == - kMTLStoreActionStoreAndMultisampleResolve) { - hasStoreAndMSAAResolve = true; - resolveTextures[i] = mtlRenderPass.colorAttachments[i].resolveTexture; - - mtlRenderPass.colorAttachments[i].storeAction = MTLStoreActionStore; - mtlRenderPass.colorAttachments[i].resolveTexture = nullptr; - } - } - - // If we found a store + MSAA resolve we need to resolve in a different render pass. - if (hasStoreAndMSAAResolve) { - DAWN_TRY(EncodeRenderPass(commandContext, mtlRenderPass, width, height)); - ResolveInAnotherRenderPass(commandContext, mtlRenderPass, resolveTextures); - return {}; - } - } - - DAWN_TRY(EncodeRenderPassInternal(commandContext, mtlRenderPass, width, height)); - return {}; - } - - MaybeError CommandBuffer::EncodeRenderPassInternal(CommandRecordingContext* commandContext, - MTLRenderPassDescriptor* mtlRenderPass, - uint32_t width, - uint32_t height) { + MaybeError CommandBuffer::EncodeRenderPass(id encoder) { bool enableVertexPulling = GetDevice()->IsToggleEnabled(Toggle::MetalEnableVertexPulling); RenderPipeline* lastPipeline = nullptr; id indexBuffer = nullptr; @@ -1299,8 +1121,6 @@ namespace dawn::native::metal { VertexBufferTracker vertexBuffers(&storageBufferLengths); BindGroupTracker bindGroups(&storageBufferLengths); - id encoder = commandContext->BeginRender(mtlRenderPass); - auto EncodeRenderBundleCommand = [&](CommandIterator* iter, Command type) { switch (type) { case Command::Draw: { @@ -1489,7 +1309,6 @@ namespace dawn::native::metal { switch (type) { case Command::EndRenderPass: { mCommands.NextCommand(); - commandContext->EndRender(); return {}; } diff --git a/src/dawn/native/metal/UtilsMetal.h b/src/dawn/native/metal/UtilsMetal.h index 3a0d76dc6f..5b71d73a4b 100644 --- a/src/dawn/native/metal/UtilsMetal.h +++ b/src/dawn/native/metal/UtilsMetal.h @@ -81,6 +81,27 @@ namespace dawn::native::metal { uint32_t sampleMask = 0xFFFFFFFF, const RenderPipeline* renderPipeline = nullptr); + // Allow use MTLStoreActionStoreAndMultismapleResolve because the logic in the backend is + // first to compute what the "best" Metal render pass descriptor is, then fix it up if we + // are not on macOS 10.12 (i.e. the EmulateStoreAndMSAAResolve toggle is on). +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + constexpr MTLStoreAction kMTLStoreActionStoreAndMultisampleResolve = + MTLStoreActionStoreAndMultisampleResolve; +#pragma clang diagnostic pop + + // Helper functions to encode Metal render passes that take care of multiple workarounds that + // happen at the render pass start and end. Because workarounds wrap the encoding of the render + // pass, the encoding must be entirely done by the `encodeInside` callback. + // At the end of this function, `commandContext` will have no encoder open. + using EncodeInsideRenderPass = std::function)>; + MaybeError EncodeMetalRenderPass(Device* device, + CommandRecordingContext* commandContext, + MTLRenderPassDescriptor* mtlRenderPass, + uint32_t width, + uint32_t height, + EncodeInsideRenderPass encodeInside); + } // namespace dawn::native::metal #endif // SRC_DAWN_NATIVE_METAL_UTILSMETAL_H_ diff --git a/src/dawn/native/metal/UtilsMetal.mm b/src/dawn/native/metal/UtilsMetal.mm index e2e0ba3357..505f5a0517 100644 --- a/src/dawn/native/metal/UtilsMetal.mm +++ b/src/dawn/native/metal/UtilsMetal.mm @@ -21,6 +21,85 @@ namespace dawn::native::metal { + namespace { + // Helper function for Toggle EmulateStoreAndMSAAResolve + void ResolveInAnotherRenderPass( + CommandRecordingContext* commandContext, + const MTLRenderPassDescriptor* mtlRenderPass, + const std::array, kMaxColorAttachments>& resolveTextures) { + // Note that this creates a descriptor that's autoreleased so we don't use AcquireNSRef + NSRef mtlRenderPassForResolveRef = + [MTLRenderPassDescriptor renderPassDescriptor]; + MTLRenderPassDescriptor* mtlRenderPassForResolve = mtlRenderPassForResolveRef.Get(); + + for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { + if (resolveTextures[i] == nullptr) { + continue; + } + + mtlRenderPassForResolve.colorAttachments[i].texture = + mtlRenderPass.colorAttachments[i].texture; + mtlRenderPassForResolve.colorAttachments[i].loadAction = MTLLoadActionLoad; + mtlRenderPassForResolve.colorAttachments[i].storeAction = + MTLStoreActionMultisampleResolve; + mtlRenderPassForResolve.colorAttachments[i].resolveTexture = resolveTextures[i]; + mtlRenderPassForResolve.colorAttachments[i].resolveLevel = + mtlRenderPass.colorAttachments[i].resolveLevel; + mtlRenderPassForResolve.colorAttachments[i].resolveSlice = + mtlRenderPass.colorAttachments[i].resolveSlice; + } + + commandContext->BeginRender(mtlRenderPassForResolve); + commandContext->EndRender(); + } + + // Helper functions for Toggle AlwaysResolveIntoZeroLevelAndLayer + ResultOrError>> CreateResolveTextureForWorkaround( + Device* device, + MTLPixelFormat mtlFormat, + uint32_t width, + uint32_t height) { + NSRef mtlDescRef = AcquireNSRef([MTLTextureDescriptor new]); + MTLTextureDescriptor* mtlDesc = mtlDescRef.Get(); + + mtlDesc.textureType = MTLTextureType2D; + mtlDesc.usage = MTLTextureUsageRenderTarget; + mtlDesc.pixelFormat = mtlFormat; + mtlDesc.width = width; + mtlDesc.height = height; + mtlDesc.depth = 1; + mtlDesc.mipmapLevelCount = 1; + mtlDesc.arrayLength = 1; + mtlDesc.storageMode = MTLStorageModePrivate; + mtlDesc.sampleCount = 1; + + id texture = [device->GetMTLDevice() newTextureWithDescriptor:mtlDesc]; + if (texture == nil) { + return DAWN_OUT_OF_MEMORY_ERROR("Allocation of temporary texture failed."); + } + + return AcquireNSPRef(texture); + } + + void CopyIntoTrueResolveTarget(CommandRecordingContext* commandContext, + id mtlTrueResolveTexture, + uint32_t trueResolveLevel, + uint32_t trueResolveSlice, + id temporaryResolveTexture, + uint32_t width, + uint32_t height) { + [commandContext->EnsureBlit() copyFromTexture:temporaryResolveTexture + sourceSlice:0 + sourceLevel:0 + sourceOrigin:MTLOriginMake(0, 0, 0) + sourceSize:MTLSizeMake(width, height, 1) + toTexture:mtlTrueResolveTexture + destinationSlice:trueResolveSlice + destinationLevel:trueResolveLevel + destinationOrigin:MTLOriginMake(0, 0, 0)]; + } + } // anonymous namespace + MTLCompareFunction ToMetalCompareFunction(wgpu::CompareFunction compareFunction) { switch (compareFunction) { case wgpu::CompareFunction::Never: @@ -285,4 +364,98 @@ namespace dawn::native::metal { return {}; } + MaybeError EncodeMetalRenderPass(Device* device, + CommandRecordingContext* commandContext, + MTLRenderPassDescriptor* mtlRenderPass, + uint32_t width, + uint32_t height, + EncodeInsideRenderPass encodeInside) { + // Handle Toggle AlwaysResolveIntoZeroLevelAndLayer. We must handle this before applying + // the store + MSAA resolve workaround, otherwise this toggle will never be handled because + // the resolve texture is removed when applying the store + MSAA resolve workaround. + if (device->IsToggleEnabled(Toggle::AlwaysResolveIntoZeroLevelAndLayer)) { + std::array, kMaxColorAttachments> trueResolveTextures = {}; + std::array trueResolveLevels = {}; + std::array trueResolveSlices = {}; + + // Use temporary resolve texture on the resolve targets with non-zero resolveLevel or + // resolveSlice. + bool useTemporaryResolveTexture = false; + std::array>, kMaxColorAttachments> temporaryResolveTextures = {}; + for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { + if (mtlRenderPass.colorAttachments[i].resolveTexture == nullptr) { + continue; + } + + if (mtlRenderPass.colorAttachments[i].resolveLevel == 0 && + mtlRenderPass.colorAttachments[i].resolveSlice == 0) { + continue; + } + + trueResolveTextures[i] = mtlRenderPass.colorAttachments[i].resolveTexture; + trueResolveLevels[i] = mtlRenderPass.colorAttachments[i].resolveLevel; + trueResolveSlices[i] = mtlRenderPass.colorAttachments[i].resolveSlice; + + const MTLPixelFormat mtlFormat = trueResolveTextures[i].pixelFormat; + DAWN_TRY_ASSIGN(temporaryResolveTextures[i], CreateResolveTextureForWorkaround( + device, mtlFormat, width, height)); + + mtlRenderPass.colorAttachments[i].resolveTexture = + temporaryResolveTextures[i].Get(); + mtlRenderPass.colorAttachments[i].resolveLevel = 0; + mtlRenderPass.colorAttachments[i].resolveSlice = 0; + useTemporaryResolveTexture = true; + } + + // If we need to use a temporary resolve texture we need to copy the result of MSAA + // resolve back to the true resolve targets. + if (useTemporaryResolveTexture) { + DAWN_TRY(EncodeMetalRenderPass(device, commandContext, mtlRenderPass, width, height, + std::move(encodeInside))); + + for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { + if (trueResolveTextures[i] == nullptr) { + continue; + } + + ASSERT(temporaryResolveTextures[i] != nullptr); + CopyIntoTrueResolveTarget(commandContext, trueResolveTextures[i], + trueResolveLevels[i], trueResolveSlices[i], + temporaryResolveTextures[i].Get(), width, height); + } + return {}; + } + } + + // Handle Store + MSAA resolve workaround (Toggle EmulateStoreAndMSAAResolve). + if (device->IsToggleEnabled(Toggle::EmulateStoreAndMSAAResolve)) { + bool hasStoreAndMSAAResolve = false; + + // Remove any store + MSAA resolve and remember them. + std::array, kMaxColorAttachments> resolveTextures = {}; + for (uint32_t i = 0; i < kMaxColorAttachments; ++i) { + if (mtlRenderPass.colorAttachments[i].storeAction == + kMTLStoreActionStoreAndMultisampleResolve) { + hasStoreAndMSAAResolve = true; + resolveTextures[i] = mtlRenderPass.colorAttachments[i].resolveTexture; + + mtlRenderPass.colorAttachments[i].storeAction = MTLStoreActionStore; + mtlRenderPass.colorAttachments[i].resolveTexture = nullptr; + } + } + + // If we found a store + MSAA resolve we need to resolve in a different render pass. + if (hasStoreAndMSAAResolve) { + DAWN_TRY(EncodeMetalRenderPass(device, commandContext, mtlRenderPass, width, height, + std::move(encodeInside))); + + ResolveInAnotherRenderPass(commandContext, mtlRenderPass, resolveTextures); + return {}; + } + } + + DAWN_TRY(encodeInside(commandContext->BeginRender(mtlRenderPass))); + commandContext->EndRender(); + return {}; + } } // namespace dawn::native::metal