7780b83fdc
Fix writing to the correct mip in Metal Set default m_outputScale to 1 in order to fix a 1/0 error Add support for logging/printing errors pertaining to GPU crashes. Setting Release queue's collection latency to MaxFrames. Fix managed mem synchronization related offset bug
410 lines
20 KiB
C++
410 lines
20 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
#include "Atom_RHI_Metal_precompiled.h"
|
|
#include <Atom/RHI/CpuProfiler.h>
|
|
#include <Atom/RHI/ImageScopeAttachment.h>
|
|
#include <Atom/RHI/ResolveScopeAttachment.h>
|
|
#include <AzCore/Debug/EventTrace.h>
|
|
#include <RHI/CommandList.h>
|
|
#include <RHI/Conversions.h>
|
|
#include <RHI/Device.h>
|
|
#include <RHI/ImageView.h>
|
|
#include <RHI/ResourcePoolResolver.h>
|
|
#include <RHI/Scope.h>
|
|
#include <Atom/RHI/SwapChainFrameAttachment.h>
|
|
|
|
namespace Platform
|
|
{
|
|
void ApplyTileDimentions(MTLRenderPassDescriptor* mtlRenderPassDescriptor);
|
|
}
|
|
|
|
namespace AZ
|
|
{
|
|
namespace Metal
|
|
{
|
|
RHI::Ptr<Scope> Scope::Create()
|
|
{
|
|
return aznew Scope();
|
|
}
|
|
|
|
void Scope::DeactivateInternal()
|
|
{
|
|
for (RHI::ResourcePoolResolver* resolvePolicyBase : GetResourcePoolResolves())
|
|
{
|
|
static_cast<ResourcePoolResolver*>(resolvePolicyBase)->Deactivate();
|
|
}
|
|
m_waitFencesByQueue = { {0} };
|
|
m_signalFenceValue = 0;
|
|
m_renderPassDescriptor = nil;
|
|
|
|
for (size_t i = 0; i < static_cast<uint32_t>(ResourceFenceAction::Count); ++i)
|
|
{
|
|
m_resourceFences[i].clear();
|
|
}
|
|
m_queryPoolAttachments.clear();
|
|
m_residentHeaps.clear();
|
|
|
|
}
|
|
|
|
void Scope::ApplyMSAACustomPositions(const ImageView* imageView)
|
|
{
|
|
const Image& image = imageView->GetImage();
|
|
const RHI::ImageDescriptor& imageDescriptor = image.GetDescriptor();
|
|
|
|
m_scopeMultisampleState = imageDescriptor.m_multisampleState;
|
|
if(m_scopeMultisampleState.m_customPositionsCount > 0)
|
|
{
|
|
AZStd::vector<MTLSamplePosition> mtlCustomSampleLocations;
|
|
AZStd::transform( m_scopeMultisampleState.m_customPositions.begin(),
|
|
m_scopeMultisampleState.m_customPositions.begin() + m_scopeMultisampleState.m_customPositionsCount,
|
|
AZStd::back_inserter(mtlCustomSampleLocations), [&](const auto& item)
|
|
{
|
|
return ConvertSampleLocation(item);
|
|
});
|
|
|
|
MTLSamplePosition samplePositions[m_scopeMultisampleState.m_customPositionsCount];
|
|
|
|
for(int i = 0 ; i < m_scopeMultisampleState.m_customPositionsCount; i++)
|
|
{
|
|
samplePositions[i] = mtlCustomSampleLocations[i];
|
|
}
|
|
[m_renderPassDescriptor setSamplePositions:samplePositions count:m_scopeMultisampleState.m_customPositionsCount];
|
|
}
|
|
}
|
|
|
|
void Scope::CompileInternal(RHI::Device& device)
|
|
{
|
|
Device& mtlDevice = static_cast<Device&>(device);
|
|
for (RHI::ResourcePoolResolver* resolvePolicyBase : GetResourcePoolResolves())
|
|
{
|
|
static_cast<ResourcePoolResolver*>(resolvePolicyBase)->Compile();
|
|
}
|
|
|
|
if(GetImageAttachments().size() > 0)
|
|
{
|
|
AZ_Assert(m_renderPassDescriptor == nil, "m_renderPassDescriptor should be null");
|
|
m_renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
|
Platform::ApplyTileDimentions(m_renderPassDescriptor);
|
|
}
|
|
|
|
if(GetEstimatedItemCount())
|
|
{
|
|
m_residentHeaps.insert(mtlDevice.GetNullDescriptorManager().GetNullDescriptorHeap());
|
|
}
|
|
|
|
int colorAttachmentIndex = 0;
|
|
AZStd::unordered_map<RHI::AttachmentId, ResolveAttachmentData> attachmentsIndex;
|
|
|
|
for (const RHI::ImageScopeAttachment* scopeAttachment : GetImageAttachments())
|
|
{
|
|
m_isWritingToSwapChainScope = scopeAttachment->IsSwapChainAttachment() && scopeAttachment->HasUsage(RHI::ScopeAttachmentUsage::RenderTarget);
|
|
if(m_isWritingToSwapChainScope)
|
|
{
|
|
//Check if the scope attachment for the next scope is going to capture the frame.
|
|
//We can use this information to cache the swapchain texture for reading purposes.
|
|
const RHI::ScopeAttachment* frameCaptureScopeAttachment = scopeAttachment->GetNext();
|
|
if(frameCaptureScopeAttachment)
|
|
{
|
|
m_isSwapChainAndFrameCaptureEnabled = frameCaptureScopeAttachment->HasAccessAndUsage(RHI::ScopeAttachmentUsage::Copy, RHI::ScopeAttachmentAccess::Read);
|
|
}
|
|
|
|
//Cache this as we will use this to request the drawable from the driver in the Execute phase (i.e Scope::Begin)
|
|
m_swapChainAttachment = scopeAttachment;
|
|
}
|
|
|
|
const ImageView* imageView = static_cast<const ImageView*>(scopeAttachment->GetImageView());
|
|
const RHI::ImageScopeAttachmentDescriptor& bindingDescriptor = scopeAttachment->GetDescriptor();
|
|
id<MTLTexture> imageViewMtlTexture = imageView->GetMemoryView().GetGpuAddress<id<MTLTexture>>();
|
|
|
|
const bool isFullView = imageView->IsFullView();
|
|
const bool isClearAction = bindingDescriptor.m_loadStoreAction.m_loadAction == RHI::AttachmentLoadAction::Clear;
|
|
const bool isClearActionStencil = bindingDescriptor.m_loadStoreAction.m_loadActionStencil == RHI::AttachmentLoadAction::Clear;
|
|
|
|
const bool isLoadAction = bindingDescriptor.m_loadStoreAction.m_loadAction == RHI::AttachmentLoadAction::Load;
|
|
const bool isLoadActionStencil = bindingDescriptor.m_loadStoreAction.m_loadActionStencil == RHI::AttachmentLoadAction::Load;
|
|
|
|
const bool isStoreAction = bindingDescriptor.m_loadStoreAction.m_storeAction == RHI::AttachmentStoreAction::Store;
|
|
const bool isStoreActionStencil = bindingDescriptor.m_loadStoreAction.m_storeActionStencil == RHI::AttachmentStoreAction::Store;
|
|
|
|
|
|
MTLLoadAction mtlLoadAction = MTLLoadActionDontCare;
|
|
MTLStoreAction mtlStoreAction = MTLStoreActionDontCare;
|
|
MTLLoadAction mtlLoadActionStencil = MTLLoadActionDontCare;
|
|
MTLStoreAction mtlStoreActionStencil = MTLStoreActionDontCare;
|
|
|
|
if(isClearAction)
|
|
{
|
|
mtlLoadAction = MTLLoadActionClear;
|
|
}
|
|
else if(isLoadAction)
|
|
{
|
|
mtlLoadAction = MTLLoadActionLoad;
|
|
}
|
|
|
|
if(isStoreAction)
|
|
{
|
|
mtlStoreAction = MTLStoreActionStore;
|
|
}
|
|
|
|
if(isClearActionStencil)
|
|
{
|
|
mtlLoadActionStencil = MTLLoadActionClear;
|
|
}
|
|
else if(isLoadAction)
|
|
{
|
|
mtlLoadActionStencil = MTLLoadActionLoad;
|
|
}
|
|
|
|
if(isStoreActionStencil)
|
|
{
|
|
mtlStoreActionStencil = MTLStoreActionStore;
|
|
}
|
|
const RHI::ImageViewDescriptor& imgViewDescriptor = imageView->GetDescriptor();
|
|
const AZStd::vector<RHI::ScopeAttachmentUsageAndAccess>& usagesAndAccesses = scopeAttachment->GetUsageAndAccess();
|
|
for (const RHI::ScopeAttachmentUsageAndAccess& usageAndAccess : usagesAndAccesses)
|
|
{
|
|
switch (usageAndAccess.m_usage)
|
|
{
|
|
case RHI::ScopeAttachmentUsage::Shader:
|
|
{
|
|
break;
|
|
}
|
|
case RHI::ScopeAttachmentUsage::RenderTarget:
|
|
{
|
|
id<MTLTexture> renderTargetTexture = imageViewMtlTexture;
|
|
m_renderPassDescriptor.colorAttachments[colorAttachmentIndex].texture = renderTargetTexture;
|
|
|
|
if(!m_isWritingToSwapChainScope)
|
|
{
|
|
if(renderTargetTexture.textureType == MTLTextureType3D)
|
|
{
|
|
m_renderPassDescriptor.colorAttachments[colorAttachmentIndex].depthPlane = imgViewDescriptor.m_depthSliceMin;
|
|
}
|
|
else
|
|
{
|
|
m_renderPassDescriptor.colorAttachments[colorAttachmentIndex].slice = imgViewDescriptor.m_arraySliceMin;
|
|
}
|
|
m_renderPassDescriptor.colorAttachments[colorAttachmentIndex].level = imgViewDescriptor.m_mipSliceMin;
|
|
}
|
|
|
|
MTLRenderPassColorAttachmentDescriptor* colorAttachment = m_renderPassDescriptor.colorAttachments[colorAttachmentIndex];
|
|
colorAttachment.loadAction = mtlLoadAction;
|
|
colorAttachment.storeAction = mtlStoreAction;
|
|
|
|
RHI::ClearValue clearVal = bindingDescriptor.m_loadStoreAction.m_clearValue;
|
|
if (mtlLoadAction == MTLLoadActionClear)
|
|
{
|
|
m_isClearNeeded = true;
|
|
if(clearVal.m_type == RHI::ClearValueType::Vector4Float)
|
|
{
|
|
colorAttachment.clearColor = MTLClearColorMake(clearVal.m_vector4Float[0], clearVal.m_vector4Float[1], clearVal.m_vector4Float[2], clearVal.m_vector4Float[3]);
|
|
}
|
|
else if(clearVal.m_type == RHI::ClearValueType::Vector4Uint)
|
|
{
|
|
colorAttachment.clearColor = MTLClearColorMake(clearVal.m_vector4Uint[0], clearVal.m_vector4Uint[1], clearVal.m_vector4Uint[2], clearVal.m_vector4Uint[3]);
|
|
}
|
|
}
|
|
|
|
ApplyMSAACustomPositions(imageView);
|
|
attachmentsIndex[bindingDescriptor.m_attachmentId] = ResolveAttachmentData{colorAttachmentIndex, m_renderPassDescriptor, RHI::ScopeAttachmentUsage::RenderTarget, isStoreAction};
|
|
colorAttachmentIndex++;
|
|
break;
|
|
}
|
|
case RHI::ScopeAttachmentUsage::DepthStencil:
|
|
{
|
|
m_renderPassDescriptor.depthAttachment.texture = imageViewMtlTexture;
|
|
if(IsDepthStencilMerged(imageView->GetSpecificFormat()))
|
|
{
|
|
m_renderPassDescriptor.stencilAttachment.texture = imageViewMtlTexture;
|
|
}
|
|
|
|
m_renderPassDescriptor.depthAttachment.slice = imgViewDescriptor.m_arraySliceMin;
|
|
|
|
MTLRenderPassDepthAttachmentDescriptor* depthAttachment = m_renderPassDescriptor.depthAttachment;
|
|
MTLRenderPassStencilAttachmentDescriptor* stencilAttachment = m_renderPassDescriptor.stencilAttachment;
|
|
depthAttachment.loadAction = mtlLoadAction;
|
|
depthAttachment.storeAction = mtlStoreAction;
|
|
stencilAttachment.loadAction = mtlLoadActionStencil;
|
|
stencilAttachment.storeAction = mtlStoreActionStencil;
|
|
if (bindingDescriptor.m_loadStoreAction.m_clearValue.m_type == RHI::ClearValueType::DepthStencil)
|
|
{
|
|
m_isClearNeeded = true;
|
|
depthAttachment.clearDepth = bindingDescriptor.m_loadStoreAction.m_clearValue.m_depthStencil.m_depth;
|
|
stencilAttachment.clearStencil = bindingDescriptor.m_loadStoreAction.m_clearValue.m_depthStencil.m_stencil;
|
|
}
|
|
ApplyMSAACustomPositions(imageView);
|
|
attachmentsIndex[bindingDescriptor.m_attachmentId] = ResolveAttachmentData{-1, m_renderPassDescriptor, RHI::ScopeAttachmentUsage::DepthStencil, isStoreAction};
|
|
|
|
break;
|
|
}
|
|
case RHI::ScopeAttachmentUsage::Resolve:
|
|
{
|
|
auto resolveScopeAttachment = static_cast<const RHI::ResolveScopeAttachment*>(scopeAttachment);
|
|
auto resolveAttachmentId = resolveScopeAttachment->GetDescriptor().m_resolveAttachmentId;
|
|
AZ_Assert(attachmentsIndex.find(resolveAttachmentId) != attachmentsIndex.end(), "Failed to find resolvable attachment %s", resolveAttachmentId.GetCStr());
|
|
ResolveAttachmentData resolveAttachmentData = attachmentsIndex[resolveAttachmentId];
|
|
MTLRenderPassDescriptor* renderPassDesc = resolveAttachmentData.m_renderPassDesc;
|
|
|
|
id<MTLTexture> renderTargetTexture = imageViewMtlTexture;
|
|
|
|
MTLStoreAction resolveStoreAction = resolveAttachmentData.m_isStoreAction ? MTLStoreActionStoreAndMultisampleResolve : MTLStoreActionMultisampleResolve;
|
|
|
|
if(resolveAttachmentData.m_attachmentUsage == RHI::ScopeAttachmentUsage::RenderTarget)
|
|
{
|
|
renderPassDesc.colorAttachments[resolveAttachmentData.m_colorAttachmentIndex].resolveTexture = renderTargetTexture;
|
|
MTLRenderPassColorAttachmentDescriptor* colorAttachment = renderPassDesc.colorAttachments[resolveAttachmentData.m_colorAttachmentIndex];
|
|
colorAttachment.storeAction = resolveStoreAction;
|
|
}
|
|
else if (resolveAttachmentData.m_attachmentUsage == RHI::ScopeAttachmentUsage::DepthStencil)
|
|
{
|
|
renderPassDesc.depthAttachment.resolveTexture = renderTargetTexture;
|
|
MTLRenderPassDepthAttachmentDescriptor* depthAttachment = renderPassDesc.depthAttachment;
|
|
depthAttachment.storeAction = resolveStoreAction;
|
|
//Metal drivers support min/max depth resolve filters but there is no way to set them at a higher level yet.
|
|
}
|
|
m_isResolveNeeded = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scope::Begin(
|
|
CommandList& commandList,
|
|
AZ::u32 commandListIndex,
|
|
AZ::u32 commandListCount) const
|
|
{
|
|
AZ_TRACE_METHOD();
|
|
|
|
if(m_isWritingToSwapChainScope)
|
|
{
|
|
//Metal requires you to request for swapchain drawable as late as possible in the frame. Hence we call for the drawable
|
|
//here and attach it directly to the colorAttachment. The assumption here is that this scope should be the
|
|
//CopyToSwapChain scope. With this way if the internal resolution differs from the window resolution the compositer
|
|
//within the metal driver will perform the appropriate upscale/downscale at the end.
|
|
const RHI::SwapChain* swapChain = (azrtti_cast<const RHI::SwapChainFrameAttachment*>(&m_swapChainAttachment->GetFrameAttachment()))->GetSwapChain();
|
|
SwapChain* metalSwapChain = static_cast<SwapChain*>(const_cast<RHI::SwapChain*>(swapChain));
|
|
m_renderPassDescriptor.colorAttachments[0].texture = metalSwapChain->RequestDrawable(m_isSwapChainAndFrameCaptureEnabled);
|
|
}
|
|
|
|
const bool isPrologue = commandListIndex == 0;
|
|
commandList.SetName(GetId());
|
|
commandList.SetRenderPassInfo(m_renderPassDescriptor, m_scopeMultisampleState, m_residentHeaps);
|
|
|
|
if (isPrologue)
|
|
{
|
|
for (const auto& fence : m_resourceFences[static_cast<int>(ResourceFenceAction::Wait)])
|
|
{
|
|
commandList.WaitOnResourceFence(fence);
|
|
}
|
|
|
|
for (RHI::ResourcePoolResolver* resolvePolicyBase : GetResourcePoolResolves())
|
|
{
|
|
static_cast<ResourcePoolResolver*>(resolvePolicyBase)->Resolve(commandList);
|
|
}
|
|
}
|
|
|
|
for (const auto& queryPoolAttachment : m_queryPoolAttachments)
|
|
{
|
|
if (!RHI::CheckBitsAll(queryPoolAttachment.m_access, RHI::ScopeAttachmentAccess::Write))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Attach occlusion testing related visibility buffer
|
|
if(queryPoolAttachment.m_pool->GetDescriptor().m_type == RHI::QueryType::Occlusion)
|
|
{
|
|
commandList.AttachVisibilityBuffer(queryPoolAttachment.m_pool->GetVisibilityBuffer());
|
|
}
|
|
}
|
|
|
|
//If a scope has resolve texture or if it is using a clear load action we know the encoder type is Render
|
|
//and hence we create the encoder here even though there may not be any draw commands by the scope. This will
|
|
//allow us to use the driver to clear a render target or do a msaa resolve within a scope with no draw work.
|
|
if(m_isResolveNeeded || m_isClearNeeded)
|
|
{
|
|
commandList.CreateEncoder(CommandEncoderType::Render);
|
|
}
|
|
}
|
|
|
|
void Scope::End(
|
|
CommandList& commandList,
|
|
AZ::u32 commandListIndex,
|
|
AZ::u32 commandListCount) const
|
|
{
|
|
AZ_TRACE_METHOD();
|
|
const bool isEpilogue = (commandListIndex + 1) == commandListCount;
|
|
|
|
commandList.FlushEncoder();
|
|
|
|
if (isEpilogue)
|
|
{
|
|
for (const auto& fence : m_resourceFences[static_cast<int>(ResourceFenceAction::Signal)])
|
|
{
|
|
commandList.SignalResourceFence(fence);
|
|
}
|
|
}
|
|
}
|
|
|
|
MTLRenderPassDescriptor* Scope::GetRenderPassDescriptor() const
|
|
{
|
|
return m_renderPassDescriptor;
|
|
}
|
|
|
|
void Scope::SetSignalFenceValue(uint64_t fenceValue)
|
|
{
|
|
m_signalFenceValue = fenceValue;
|
|
}
|
|
|
|
bool Scope::HasSignalFence() const
|
|
{
|
|
return m_signalFenceValue > 0;
|
|
}
|
|
|
|
bool Scope::HasWaitFences() const
|
|
{
|
|
bool hasWaitFences = false;
|
|
for (uint32_t i = 0; i < RHI::HardwareQueueClassCount; ++i)
|
|
{
|
|
hasWaitFences = hasWaitFences || (m_waitFencesByQueue[i] > 0);
|
|
}
|
|
return hasWaitFences;
|
|
}
|
|
|
|
uint64_t Scope::GetSignalFenceValue() const
|
|
{
|
|
return m_signalFenceValue;
|
|
}
|
|
|
|
void Scope::SetWaitFenceValueByQueue(RHI::HardwareQueueClass hardwareQueueClass, uint64_t fenceValue)
|
|
{
|
|
m_waitFencesByQueue[static_cast<uint32_t>(hardwareQueueClass)] = fenceValue;
|
|
}
|
|
|
|
uint64_t Scope::GetWaitFenceValueByQueue(RHI::HardwareQueueClass hardwareQueueClass) const
|
|
{
|
|
return m_waitFencesByQueue[static_cast<uint32_t>(hardwareQueueClass)];
|
|
}
|
|
|
|
const FenceValueSet& Scope::GetWaitFences() const
|
|
{
|
|
return m_waitFencesByQueue;
|
|
}
|
|
|
|
void Scope::QueueResourceFence(ResourceFenceAction fenceAction, Fence& fence)
|
|
{
|
|
m_resourceFences[static_cast<uint32_t>(fenceAction)].push_back(fence);
|
|
}
|
|
|
|
void Scope::AddQueryPoolUse(RHI::Ptr<RHI::QueryPool> queryPool, const RHI::Interval& interval, RHI::ScopeAttachmentAccess access)
|
|
{
|
|
m_queryPoolAttachments.push_back({ AZStd::static_pointer_cast<QueryPool>(queryPool), interval, access });
|
|
}
|
|
}
|
|
}
|