835 lines
24 KiB
C++
835 lines
24 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MetalRHIContext.h"
|
|
#include "MetalRHIPrivate.h"
|
|
#include "MetalRHIRenderQuery.h"
|
|
#include "MetalRHIVisionOSBridge.h"
|
|
#include "MetalBindlessDescriptors.h"
|
|
#include "MetalDevice.h"
|
|
#include "MetalCommandBuffer.h"
|
|
#include "MetalDynamicRHI.h"
|
|
|
|
#if PLATFORM_VISIONOS
|
|
#import <CompositorServices/CompositorServices.h>
|
|
#endif
|
|
|
|
void METALRHI_API SafeReleaseMetalObject(NS::Object* Object)
|
|
{
|
|
if(GIsMetalInitialized && GDynamicRHI && Object)
|
|
{
|
|
if(!IsRunningRHIInSeparateThread())
|
|
{
|
|
FMetalDynamicRHI::Get().DeferredDelete(Object);
|
|
}
|
|
else
|
|
{
|
|
FFunctionGraphTask::CreateAndDispatchWhenReady(
|
|
[Object]()
|
|
{
|
|
FMetalDynamicRHI::Get().DeferredDelete(Object);
|
|
},
|
|
QUICK_USE_CYCLE_STAT(FExecuteRHIThreadTask, STATGROUP_TaskGraphTasks), nullptr, ENamedThreads::RHIThread);
|
|
}
|
|
|
|
return;
|
|
}
|
|
Object->release();
|
|
}
|
|
|
|
FMetalRHICommandContext::FMetalRHICommandContext(FMetalDevice& MetalDevice, class FMetalProfiler* InProfiler)
|
|
: Device(MetalDevice)
|
|
, CommandQueue(Device.GetCommandQueue(EMetalQueueType::Direct))
|
|
, CommandList(Device.GetCommandQueue(EMetalQueueType::Direct))
|
|
, CurrentEncoder(MetalDevice, CommandList)
|
|
, StateCache(MetalDevice, true)
|
|
, QueryBuffer(new FMetalQueryBufferPool(MetalDevice))
|
|
, RenderPassDesc(nullptr)
|
|
, Profiler(InProfiler)
|
|
, bWithinRenderPass(false)
|
|
{
|
|
GlobalUniformBuffers.AddZeroed(FUniformBufferStaticSlotRegistry::Get().GetSlotCount());
|
|
}
|
|
|
|
FMetalRHICommandContext::~FMetalRHICommandContext()
|
|
{
|
|
CurrentEncoder.Release();
|
|
}
|
|
|
|
void FMetalRHICommandContext::ResetContext()
|
|
{
|
|
#if PLATFORM_SUPPORTS_BINDLESS_RENDERING
|
|
check(!ComputeDescriptorIndices.Num());
|
|
#endif
|
|
|
|
// Reset cached state in the encoder
|
|
StateCache.Reset();
|
|
|
|
// Reset the current encoder
|
|
CurrentEncoder.Reset();
|
|
|
|
// Reallocate if necessary to ensure >= 80% usage, otherwise we're just too wasteful
|
|
CurrentEncoder.GetRingBuffer().Shrink();
|
|
|
|
// make sure first SetRenderTarget goes through
|
|
StateCache.InvalidateRenderTargets();
|
|
|
|
bIsParallelContext = false;
|
|
}
|
|
|
|
void FMetalRHICommandContext::SetupParallelContext(const FRHIParallelRenderPassInfo* InRenderPassInfo)
|
|
{
|
|
MTL_SCOPED_AUTORELEASE_POOL;
|
|
|
|
FMetalParallelRenderPassInfo* ParallelInfo = static_cast<FMetalParallelRenderPassInfo*>(InRenderPassInfo->RHIPlatformData);
|
|
|
|
CurrentEncoder.BeginRenderCommandEncoding(ParallelInfo->RenderPassDesc, ParallelInfo->ParallelEncoder);
|
|
|
|
RenderPassInfo = *static_cast<const FRHIRenderPassInfo*>(InRenderPassInfo);
|
|
RenderPassDesc = ParallelInfo->RenderPassDesc;
|
|
|
|
StateCache.StartRenderPass(RenderPassInfo, nullptr, RenderPassDesc, true);
|
|
|
|
StateCache.SetRenderTargetsActive(true);
|
|
StateCache.SetRenderStoreActions(CurrentEncoder, false);
|
|
|
|
bWithinRenderPass = true;
|
|
bIsParallelContext = true;
|
|
}
|
|
|
|
static uint32_t MAX_COLOR_RENDER_TARGETS_PER_DESC = 8;
|
|
|
|
void FMetalRHICommandContext::BeginComputeEncoder()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_MetalSwitchToComputeTime);
|
|
|
|
check(!bWithinRenderPass);
|
|
check(IsInParallelRenderingThread());
|
|
|
|
if (!CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
StartCommandBuffer();
|
|
check(CurrentEncoder.GetCommandBuffer());
|
|
}
|
|
|
|
StateCache.SetStateDirty();
|
|
|
|
if(!CurrentEncoder.IsComputeCommandEncoderActive())
|
|
{
|
|
StateCache.ClearPreviousComputeState();
|
|
if(CurrentEncoder.IsAnyCommandEncoderActive())
|
|
{
|
|
CurrentEncoderFence = CurrentEncoder.EndEncoding();
|
|
}
|
|
bool bUseStageCounterSamples = Device.SupportsFeature(EMetalFeaturesStageCounterSampling);
|
|
CurrentEncoder.BeginComputeCommandEncoding(MTL::DispatchTypeSerial,
|
|
bUseStageCounterSamples ? Device.GetCounterSampler() : nullptr);
|
|
}
|
|
|
|
if (CurrentEncoderFence)
|
|
{
|
|
CurrentEncoder.WaitForFence(CurrentEncoderFence);
|
|
CurrentEncoderFence = nullptr;
|
|
}
|
|
|
|
check(CurrentEncoder.IsComputeCommandEncoderActive());
|
|
}
|
|
|
|
void FMetalRHICommandContext::EndComputeEncoder()
|
|
{
|
|
check(CurrentEncoder.IsComputeCommandEncoderActive());
|
|
|
|
// If we are using breadcrumbs then end the encoding here so that our stat tracking is correct
|
|
#if WITH_RHI_BREADCRUMBS
|
|
CurrentEncoderFence = CurrentEncoder.EndEncoding();
|
|
#endif
|
|
StateCache.SetRenderTargetsActive(false);
|
|
}
|
|
|
|
void FMetalRHICommandContext::BeginBlitEncoder()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_MetalSwitchToBlitTime);
|
|
check(!bWithinRenderPass);
|
|
|
|
if (!CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
StartCommandBuffer();
|
|
check(CurrentEncoder.GetCommandBuffer());
|
|
}
|
|
|
|
if(!CurrentEncoder.IsBlitCommandEncoderActive())
|
|
{
|
|
if(CurrentEncoder.IsAnyCommandEncoderActive())
|
|
{
|
|
CurrentEncoderFence = CurrentEncoder.EndEncoding();
|
|
}
|
|
bool bUseStageCounterSamples = Device.SupportsFeature(EMetalFeaturesStageCounterSampling);
|
|
CurrentEncoder.BeginBlitCommandEncoding(bUseStageCounterSamples ? Device.GetCounterSampler() : nullptr);
|
|
}
|
|
|
|
if (CurrentEncoderFence)
|
|
{
|
|
CurrentEncoder.WaitForFence(CurrentEncoderFence);
|
|
CurrentEncoderFence = nullptr;
|
|
}
|
|
|
|
check(CurrentEncoder.IsBlitCommandEncoderActive());
|
|
}
|
|
|
|
void FMetalRHICommandContext::EndBlitEncoder()
|
|
{
|
|
check(CurrentEncoder.IsBlitCommandEncoderActive());
|
|
|
|
#if WITH_RHI_BREADCRUMBS
|
|
CurrentEncoderFence = CurrentEncoder.EndEncoding();
|
|
#endif
|
|
|
|
StateCache.SetRenderTargetsActive(false);
|
|
}
|
|
|
|
void FMetalRHICommandContext::PushDescriptorUpdates()
|
|
{
|
|
MTL_SCOPED_AUTORELEASE_POOL;
|
|
|
|
#if PLATFORM_SUPPORTS_BINDLESS_RENDERING
|
|
if(IsMetalBindlessEnabled())
|
|
{
|
|
check(!bWithinRenderPass);
|
|
Device.GetBindlessDescriptorManager()->UpdateDescriptorsWithGPU(this, MoveTemp(ComputeDescriptorEntries), MoveTemp(ComputeDescriptorIndices));
|
|
ComputeDescriptorEntries.Empty();
|
|
ComputeDescriptorIndices.Empty();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FMetalRHICommandContext::RHIBeginParallelRenderPass(TSharedPtr<FRHIParallelRenderPassInfo> InInfo, const TCHAR* InName)
|
|
{
|
|
MTL_SCOPED_AUTORELEASE_POOL;
|
|
|
|
PushDescriptorUpdates();
|
|
|
|
RenderPassInfo = *StaticCastSharedPtr<FRHIRenderPassInfo>(InInfo);
|
|
|
|
ParallelRenderPassInfo = new FMetalParallelRenderPassInfo;
|
|
InInfo->RHIPlatformData = ParallelRenderPassInfo;
|
|
|
|
if (!CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
StartCommandBuffer();
|
|
check(CurrentEncoder.GetCommandBuffer());
|
|
}
|
|
|
|
check(CurrentEncoder.GetCommandBuffer());
|
|
|
|
StateCache.SetStateDirty();
|
|
StateCache.SetRenderTargetsActive(true);
|
|
|
|
FMetalQueryBuffer* VisBuffer = nullptr;
|
|
if(RenderPassInfo.NumOcclusionQueries > 0)
|
|
{
|
|
VisBuffer = QueryBuffer->AcquireQueryBuffer(RenderPassInfo.NumOcclusionQueries);
|
|
}
|
|
|
|
StateCache.StartRenderPass(RenderPassInfo, VisBuffer, nullptr, false);
|
|
RenderPassDesc = StateCache.GetRenderPassDescriptor();
|
|
|
|
check(!CurrentEncoder.IsAnyCommandEncoderActive());
|
|
check(IsInParallelRenderingThread());
|
|
|
|
CurrentEncoder.SetRenderPassDescriptor(RenderPassDesc);
|
|
bool bUseStageCounterSamples = Device.SupportsFeature(EMetalFeaturesStageCounterSampling);
|
|
MTLParallelRenderCommandEncoderPtr Encoder = CurrentEncoder.BeginParallelRenderCommandEncoding(bUseStageCounterSamples ? Device.GetCounterSampler() : nullptr);
|
|
|
|
ParallelRenderPassInfo->ParallelEncoder = Encoder;
|
|
ParallelRenderPassInfo->RenderPassDesc = RenderPassDesc;
|
|
|
|
if (CurrentEncoderFence)
|
|
{
|
|
CurrentEncoder.WaitForFence(CurrentEncoderFence);
|
|
CurrentEncoderFence = nullptr;
|
|
}
|
|
|
|
StateCache.SetRenderStoreActions(CurrentEncoder);
|
|
|
|
bWithinRenderPass = true;
|
|
|
|
// Set the viewport to the full size of render target 0.
|
|
if (RenderPassInfo.ColorRenderTargets[0].RenderTarget)
|
|
{
|
|
const FRHIRenderPassInfo::FColorEntry& RenderTargetView = RenderPassInfo.ColorRenderTargets[0];
|
|
FMetalSurface* RenderTarget = GetMetalSurfaceFromRHITexture(RenderTargetView.RenderTarget);
|
|
|
|
uint32 Width = FMath::Max((uint32)(RenderTarget->Texture->width() >> RenderTargetView.MipIndex), (uint32)1);
|
|
uint32 Height = FMath::Max((uint32)(RenderTarget->Texture->height() >> RenderTargetView.MipIndex), (uint32)1);
|
|
|
|
RHISetViewport(0.0f, 0.0f, 0.0f, (float)Width, (float)Height, 1.0f);
|
|
}
|
|
}
|
|
|
|
void FMetalRHICommandContext::RHIEndParallelRenderPass()
|
|
{
|
|
check(bWithinRenderPass);
|
|
|
|
StateCache.FlushVisibilityResults(CurrentEncoder);
|
|
|
|
CurrentEncoder.EndEncoding();
|
|
|
|
bWithinRenderPass = false;
|
|
|
|
// Uses a Blit encoder so need to run after end encoding
|
|
UE::RHICore::ResolveRenderPassTargets(RenderPassInfo, [this](UE::RHICore::FResolveTextureInfo Info)
|
|
{
|
|
ResolveTexture(Info);
|
|
});
|
|
|
|
StateCache.SetRenderTargetsActive(false);
|
|
RenderPassDesc = nullptr;
|
|
|
|
delete ParallelRenderPassInfo;
|
|
}
|
|
|
|
void FMetalRHICommandContext::RHIBeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* InName)
|
|
{
|
|
MTL_SCOPED_AUTORELEASE_POOL;
|
|
|
|
PushDescriptorUpdates();
|
|
|
|
RenderPassInfo = InInfo;
|
|
|
|
if (!CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
StartCommandBuffer();
|
|
check(CurrentEncoder.GetCommandBuffer());
|
|
}
|
|
|
|
StateCache.SetStateDirty();
|
|
StateCache.SetRenderTargetsActive(true);
|
|
|
|
FMetalQueryBuffer* VisBuffer = nullptr;
|
|
if(InInfo.NumOcclusionQueries > 0)
|
|
{
|
|
VisBuffer = QueryBuffer->AcquireQueryBuffer(InInfo.NumOcclusionQueries);
|
|
}
|
|
|
|
StateCache.StartRenderPass(InInfo, VisBuffer, nullptr, false);
|
|
|
|
RenderPassDesc = StateCache.GetRenderPassDescriptor();
|
|
|
|
if(!CurrentEncoder.IsRenderCommandEncoderActive())
|
|
{
|
|
if(CurrentEncoder.IsAnyCommandEncoderActive())
|
|
{
|
|
CurrentEncoderFence = CurrentEncoder.EndEncoding();
|
|
}
|
|
CurrentEncoder.SetRenderPassDescriptor(RenderPassDesc);
|
|
bool bUseStageCounterSamples = Device.SupportsFeature(EMetalFeaturesStageCounterSampling);
|
|
CurrentEncoder.BeginRenderCommandEncoding(bUseStageCounterSamples ? Device.GetCounterSampler() : nullptr);
|
|
}
|
|
|
|
if (CurrentEncoderFence)
|
|
{
|
|
CurrentEncoder.WaitForFence(CurrentEncoderFence);
|
|
CurrentEncoderFence = nullptr;
|
|
}
|
|
StateCache.SetRenderStoreActions(CurrentEncoder, false);
|
|
check(CurrentEncoder.IsRenderCommandEncoderActive());
|
|
|
|
bWithinRenderPass = true;
|
|
|
|
// Set the viewport to the full size of render target 0.
|
|
if (InInfo.ColorRenderTargets[0].RenderTarget)
|
|
{
|
|
const FRHIRenderPassInfo::FColorEntry& RenderTargetView = InInfo.ColorRenderTargets[0];
|
|
FMetalSurface* RenderTarget = GetMetalSurfaceFromRHITexture(RenderTargetView.RenderTarget);
|
|
|
|
uint32 Width = FMath::Max((uint32)(RenderTarget->Texture->width() >> RenderTargetView.MipIndex), (uint32)1);
|
|
uint32 Height = FMath::Max((uint32)(RenderTarget->Texture->height() >> RenderTargetView.MipIndex), (uint32)1);
|
|
|
|
RHISetViewport(0.0f, 0.0f, 0.0f, (float)Width, (float)Height, 1.0f);
|
|
}
|
|
}
|
|
|
|
void FMetalRHICommandContext::RHIEndRenderPass()
|
|
{
|
|
check(bWithinRenderPass);
|
|
check(CurrentEncoder.IsRenderCommandEncoderActive());
|
|
|
|
StateCache.FlushVisibilityResults(CurrentEncoder);
|
|
|
|
CurrentEncoderFence = CurrentEncoder.EndEncoding();
|
|
|
|
bWithinRenderPass = false;
|
|
|
|
// Uses a Blit encoder so need to run after end encoding
|
|
UE::RHICore::ResolveRenderPassTargets(RenderPassInfo, [this](UE::RHICore::FResolveTextureInfo Info)
|
|
{
|
|
ResolveTexture(Info);
|
|
});
|
|
|
|
StateCache.EndRenderPass();
|
|
StateCache.SetRenderTargetsActive(false);
|
|
RenderPassDesc = nullptr;
|
|
}
|
|
|
|
void FMetalRHICommandContext::ResolveTexture(UE::RHICore::FResolveTextureInfo Info)
|
|
{
|
|
MTL_SCOPED_AUTORELEASE_POOL;
|
|
|
|
FMetalSurface* Source = GetMetalSurfaceFromRHITexture(Info.SourceTexture);
|
|
FMetalSurface* Destination = GetMetalSurfaceFromRHITexture(Info.DestTexture);
|
|
|
|
const FRHITextureDesc& SourceDesc = Source->GetDesc();
|
|
const FRHITextureDesc& DestinationDesc = Destination->GetDesc();
|
|
|
|
const bool bDepthStencil = SourceDesc.Format == PF_DepthStencil;
|
|
const bool bSupportsMSAADepthResolve = Device.SupportsFeature(EMetalFeaturesMSAADepthResolve);
|
|
const bool bSupportsMSAAStoreAndResolve = Device.SupportsFeature(EMetalFeaturesMSAAStoreAndResolve);
|
|
// Resolve required - Device must support this - Using Shader for resolve not supported amd NumSamples should be 1
|
|
check((!bDepthStencil && bSupportsMSAAStoreAndResolve) || (bDepthStencil && bSupportsMSAADepthResolve));
|
|
|
|
MTL::Origin Origin(0, 0, 0);
|
|
MTL::Size Size(0, 0, 1);
|
|
|
|
if (Info.ResolveRect.IsValid())
|
|
{
|
|
Origin.x = Info.ResolveRect.X1;
|
|
Origin.y = Info.ResolveRect.Y1;
|
|
Size.width = Info.ResolveRect.X2 - Info.ResolveRect.X1;
|
|
Size.height = Info.ResolveRect.Y2 - Info.ResolveRect.Y1;
|
|
}
|
|
else
|
|
{
|
|
Size.width = FMath::Max<uint32>(1, SourceDesc.Extent.X >> Info.MipLevel);
|
|
Size.height = FMath::Max<uint32>(1, SourceDesc.Extent.Y >> Info.MipLevel);
|
|
}
|
|
|
|
#if RHI_NEW_GPU_PROFILER == 0
|
|
if (Profiler)
|
|
{
|
|
Profiler->RegisterGPUWork();
|
|
}
|
|
#endif
|
|
|
|
int32 ArraySliceBegin = Info.ArraySlice;
|
|
int32 ArraySliceEnd = Info.ArraySlice + 1;
|
|
|
|
if (Info.ArraySlice < 0)
|
|
{
|
|
ArraySliceBegin = 0;
|
|
ArraySliceEnd = SourceDesc.ArraySize;
|
|
}
|
|
|
|
BeginBlitEncoder();
|
|
|
|
MTL::BlitCommandEncoder* Encoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
|
|
check(Encoder);
|
|
|
|
for (int32 ArraySlice = ArraySliceBegin; ArraySlice < ArraySliceEnd; ArraySlice++)
|
|
{
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), __FUNCTION__));
|
|
Encoder->copyFromTexture(Source->MSAAResolveTexture.get(), ArraySlice, Info.MipLevel, Origin, Size, Destination->Texture.get(), ArraySlice, Info.MipLevel, Origin);
|
|
}
|
|
|
|
EndBlitEncoder();
|
|
}
|
|
|
|
void FMetalRHICommandContext::RHINextSubpass()
|
|
{
|
|
#if PLATFORM_MAC
|
|
if (RenderPassInfo.SubpassHint == ESubpassHint::DepthReadSubpass)
|
|
{
|
|
if (CurrentEncoder.IsRenderCommandEncoderActive())
|
|
{
|
|
MTL::RenderCommandEncoder* RenderEncoder = CurrentEncoder.GetRenderCommandEncoder();
|
|
check(RenderEncoder);
|
|
RenderEncoder->memoryBarrier(MTL::BarrierScopeRenderTargets, MTL::RenderStageFragment, MTL::RenderStageVertex);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FMetalRHICommandContext::RHICalibrateTimers(FRHITimestampCalibrationQuery* CalibrationQuery)
|
|
{
|
|
MTL::Device* MTLDevice = Device.GetDevice();
|
|
|
|
MTL::Timestamp CPUTimeStamp, GPUTimestamp;
|
|
MTLDevice->sampleTimestamps(&CPUTimeStamp, &GPUTimestamp);
|
|
|
|
CalibrationQuery->CPUMicroseconds[0] = uint64(CPUTimeStamp / 1000.0);
|
|
CalibrationQuery->GPUMicroseconds[0] = uint64(GPUTimestamp / 1000.0);
|
|
}
|
|
|
|
void FMetalRHICommandContext::FillBuffer(MTL::Buffer* Buffer, NS::Range Range, uint8 Value)
|
|
{
|
|
check(Buffer);
|
|
|
|
MTL::BlitCommandEncoder *TargetEncoder;
|
|
|
|
BeginBlitEncoder();
|
|
TargetEncoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), FString::Printf(TEXT("FillBuffer: %p %llu %llu"), Buffer, Range.location, Range.length)));
|
|
|
|
check(TargetEncoder);
|
|
|
|
TargetEncoder->fillBuffer(Buffer, Range, Value);
|
|
|
|
EndBlitEncoder();
|
|
}
|
|
|
|
void FMetalRHICommandContext::CopyFromTextureToBuffer(MTL::Texture* Texture, uint32 sourceSlice, uint32 sourceLevel, MTL::Origin sourceOrigin, MTL::Size sourceSize, FMetalBufferPtr toBuffer, uint32 destinationOffset, uint32 destinationBytesPerRow, uint32 destinationBytesPerImage, MTL::BlitOption options)
|
|
{
|
|
BeginBlitEncoder();
|
|
MTL::BlitCommandEncoder* Encoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
check(Encoder);
|
|
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), __FUNCTION__));
|
|
{
|
|
if(Texture)
|
|
{
|
|
Encoder->copyFromTexture(Texture, sourceSlice, sourceLevel, sourceOrigin, sourceSize,
|
|
toBuffer->GetMTLBuffer(), destinationOffset + toBuffer->GetOffset(), destinationBytesPerRow, destinationBytesPerImage, options);
|
|
}
|
|
}
|
|
EndBlitEncoder();
|
|
}
|
|
|
|
void FMetalRHICommandContext::CopyFromBufferToTexture(FMetalBufferPtr Buffer, uint32 sourceOffset, uint32 sourceBytesPerRow, uint32 sourceBytesPerImage, MTL::Size sourceSize, MTL::Texture* toTexture, uint32 destinationSlice, uint32 destinationLevel, MTL::Origin destinationOrigin, MTL::BlitOption options)
|
|
{
|
|
BeginBlitEncoder();
|
|
MTL::BlitCommandEncoder* Encoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
check(Encoder);
|
|
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), __FUNCTION__));
|
|
if (options == MTL::BlitOptionNone)
|
|
{
|
|
Encoder->copyFromBuffer(Buffer->GetMTLBuffer(), sourceOffset + Buffer->GetOffset(), sourceBytesPerRow, sourceBytesPerImage, sourceSize,
|
|
toTexture, destinationSlice, destinationLevel, destinationOrigin);
|
|
}
|
|
else
|
|
{
|
|
Encoder->copyFromBuffer(Buffer->GetMTLBuffer(), sourceOffset + Buffer->GetOffset(), sourceBytesPerRow, sourceBytesPerImage, sourceSize,
|
|
toTexture, destinationSlice, destinationLevel, destinationOrigin, options);
|
|
}
|
|
|
|
EndBlitEncoder();
|
|
}
|
|
|
|
void FMetalRHICommandContext::CopyFromTextureToTexture(MTL::Texture* Texture, uint32 sourceSlice, uint32 sourceLevel, MTL::Origin sourceOrigin, MTL::Size sourceSize, MTL::Texture* toTexture, uint32 destinationSlice, uint32 destinationLevel, MTL::Origin destinationOrigin)
|
|
{
|
|
BeginBlitEncoder();
|
|
|
|
MTL::BlitCommandEncoder* Encoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
check(Encoder);
|
|
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), __FUNCTION__));
|
|
Encoder->copyFromTexture(Texture, sourceSlice, sourceLevel, sourceOrigin, sourceSize, toTexture, destinationSlice, destinationLevel, destinationOrigin);
|
|
|
|
EndBlitEncoder();
|
|
}
|
|
|
|
void FMetalRHICommandContext::CopyFromBufferToBuffer(FMetalBufferPtr SourceBuffer, NS::UInteger SourceOffset, FMetalBufferPtr DestinationBuffer, NS::UInteger DestinationOffset, NS::UInteger Size)
|
|
{
|
|
BeginBlitEncoder();
|
|
|
|
MTL::BlitCommandEncoder* Encoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
check(Encoder);
|
|
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), __FUNCTION__));
|
|
|
|
Encoder->copyFromBuffer(SourceBuffer->GetMTLBuffer(), SourceOffset + SourceBuffer->GetOffset(),
|
|
DestinationBuffer->GetMTLBuffer(), DestinationOffset + DestinationBuffer->GetOffset(), Size);
|
|
|
|
EndBlitEncoder();
|
|
}
|
|
|
|
void FMetalRHICommandContext::Finalize(TArray<FMetalPayload*>& OutPayloads)
|
|
{
|
|
MTL_SCOPED_AUTORELEASE_POOL;
|
|
|
|
if(CurrentEncoder.IsAnyCommandEncoderActive())
|
|
{
|
|
if(CurrentEncoder.IsRenderCommandEncoderActive())
|
|
{
|
|
RHIEndRenderPass();
|
|
}
|
|
else
|
|
{
|
|
CurrentEncoder.EndEncoding();
|
|
}
|
|
}
|
|
|
|
PushDescriptorUpdates();
|
|
|
|
// No command buffer if we are running parallel
|
|
if (CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
check(!bIsParallelContext);
|
|
EndCommandBuffer();
|
|
}
|
|
|
|
// Collect the context's batch of sync points to wait/signal
|
|
if (BatchedSyncPoints.ToWait.Num())
|
|
{
|
|
FMetalPayload* Payload = Payloads.Num()
|
|
? Payloads[0]
|
|
: GetPayload(EPhase::Wait);
|
|
|
|
Payload->SyncPointsToWait.Append(BatchedSyncPoints.ToWait);
|
|
BatchedSyncPoints.ToWait.Reset();
|
|
}
|
|
|
|
if (BatchedSyncPoints.ToSignal.Num())
|
|
{
|
|
GetPayload(EPhase::Signal)->SyncPointsToSignal.Append(BatchedSyncPoints.ToSignal);
|
|
BatchedSyncPoints.ToSignal.Reset();
|
|
}
|
|
|
|
OutPayloads.Append(MoveTemp(Payloads));
|
|
}
|
|
|
|
void FMetalRHICommandContext::SignalSyncPoint(FMetalSyncPoint* SyncPoint)
|
|
{
|
|
if(CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
EndCommandBuffer();
|
|
}
|
|
|
|
GetPayload(EPhase::Signal)->SyncPointsToSignal.Add(SyncPoint);
|
|
}
|
|
|
|
void FMetalRHICommandContext::WaitSyncPoint(FMetalSyncPoint* SyncPoint)
|
|
{
|
|
if(CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
EndCommandBuffer();
|
|
}
|
|
|
|
GetPayload(EPhase::Wait)->SyncPointsToWait.Add(SyncPoint);
|
|
}
|
|
|
|
void FMetalRHICommandContext::SignalEvent(MTLEventPtr Event, uint32_t SignalCount)
|
|
{
|
|
if(!CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
StartCommandBuffer();
|
|
}
|
|
CurrentEncoder.SignalEvent(Event, SignalCount);
|
|
}
|
|
|
|
void FMetalRHICommandContext::WaitForEvent(MTLEventPtr Event, uint32_t SignalCount)
|
|
{
|
|
if(!CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
StartCommandBuffer();
|
|
}
|
|
CurrentEncoder.WaitForEvent(Event, SignalCount);
|
|
}
|
|
|
|
void FMetalRHICommandContext::StartCommandBuffer()
|
|
{
|
|
check(!CurrentEncoder.GetCommandBuffer());
|
|
|
|
CurrentEncoder.StartCommandBuffer();
|
|
|
|
#if RHI_NEW_GPU_PROFILER
|
|
auto& Event = CurrentEncoder.GetCommandBuffer()->EmplaceProfilerEvent<UE::RHI::GPUProfiler::FEvent::FBeginWork>(0);
|
|
Event.GPUTimestampTOP = UINT64_MAX;
|
|
CurrentEncoder.GetCommandBuffer()->SetBeginWorkTimestamp(&Event.GPUTimestampTOP);
|
|
#endif
|
|
|
|
// Add new command buffer to payload
|
|
GetPayload(EPhase::Execute)->CommandBuffersToExecute.Add(CurrentEncoder.GetCommandBuffer());
|
|
}
|
|
|
|
void FMetalRHICommandContext::EndCommandBuffer()
|
|
{
|
|
check(CurrentEncoder.GetCommandBuffer());
|
|
check(!bWithinRenderPass);
|
|
|
|
#if RHI_NEW_GPU_PROFILER
|
|
auto& Event = CurrentEncoder.GetCommandBuffer()->EmplaceProfilerEvent<UE::RHI::GPUProfiler::FEvent::FEndWork>();
|
|
Event.GPUTimestampBOP = 0;
|
|
CurrentEncoder.GetCommandBuffer()->SetEndWorkTimestamp(&Event.GPUTimestampBOP);
|
|
#endif
|
|
|
|
if(CurrentEncoder.IsAnyCommandEncoderActive())
|
|
{
|
|
CurrentEncoder.EndEncoding();
|
|
}
|
|
CurrentEncoder.EndCommandBuffer(this);
|
|
}
|
|
|
|
void FMetalRHICommandContext::StartTiming(class FMetalEventNode* EventNode)
|
|
{
|
|
#if RHI_NEW_GPU_PROFILER == 0
|
|
bool const bHasCurrentCommandBuffer = CurrentEncoder.GetCommandBuffer();
|
|
|
|
if(!bHasCurrentCommandBuffer)
|
|
{
|
|
StartCommandBuffer();
|
|
}
|
|
|
|
CurrentEncoder.GetCommandBuffer()->ActiveEventNodes.Add(EventNode);
|
|
|
|
EventNode->SyncPoint = GetContextSyncPoint();
|
|
#endif
|
|
}
|
|
|
|
void FMetalRHICommandContext::EndTiming(class FMetalEventNode* EventNode)
|
|
{
|
|
#if RHI_NEW_GPU_PROFILER == 0
|
|
if(CurrentEncoder.GetCommandBuffer())
|
|
{
|
|
CurrentEncoder.GetCommandBuffer()->ActiveEventNodes.Remove(EventNode);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FMetalRHICommandContext::SynchronizeResource(MTL::Resource* Resource)
|
|
{
|
|
check(Resource);
|
|
#if PLATFORM_MAC
|
|
BeginBlitEncoder();
|
|
MTL::BlitCommandEncoder* Encoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
check(Encoder);
|
|
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), __FUNCTION__));
|
|
Encoder->synchronizeResource(Resource);
|
|
EndBlitEncoder();
|
|
#endif
|
|
}
|
|
|
|
void FMetalRHICommandContext::SynchronizeTexture(MTL::Texture* Texture, uint32 Slice, uint32 Level)
|
|
{
|
|
check(Texture);
|
|
#if PLATFORM_MAC
|
|
BeginBlitEncoder();
|
|
MTL::BlitCommandEncoder* Encoder = CurrentEncoder.GetBlitCommandEncoder();
|
|
check(Encoder);
|
|
|
|
METAL_GPUPROFILE(FMetalProfiler::GetProfiler()->EncodeBlit(CurrentEncoder.GetCommandBufferStats(), __FUNCTION__));
|
|
Encoder->synchronizeTexture(Texture, Slice, Level);
|
|
EndBlitEncoder();
|
|
#endif
|
|
}
|
|
|
|
FMetalCommandBuffer* FMetalRHICommandContext::GetCurrentCommandBuffer()
|
|
{
|
|
FMetalCommandBuffer* CommandBuffer = CurrentEncoder.GetCommandBuffer();
|
|
if(!CommandBuffer)
|
|
{
|
|
StartCommandBuffer();
|
|
}
|
|
return CurrentEncoder.GetCommandBuffer();
|
|
}
|
|
|
|
|
|
#if RHI_NEW_GPU_PROFILER
|
|
void FMetalRHICommandContext::FlushProfilerStats()
|
|
{
|
|
// Flush accumulated draw stats
|
|
if (StatEvent)
|
|
{
|
|
CurrentEncoder.GetCommandBuffer()->EmplaceProfilerEvent<UE::RHI::GPUProfiler::FEvent::FStats>() = StatEvent;
|
|
StatEvent = {};
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void FMetalRHICommandContext::FlushCommands(EMetalFlushFlags FlushFlags)
|
|
{
|
|
FMetalSyncPointRef SyncPoint;
|
|
if (EnumHasAnyFlags(FlushFlags, EMetalFlushFlags::WaitForCompletion))
|
|
{
|
|
SyncPoint = FMetalSyncPoint::Create(EMetalSyncPointType::GPUAndCPU);
|
|
SignalSyncPoint(SyncPoint);
|
|
}
|
|
|
|
FGraphEventRef SubmissionEvent;
|
|
if (EnumHasAnyFlags(FlushFlags, EMetalFlushFlags::WaitForSubmission))
|
|
{
|
|
SubmissionEvent = FGraphEvent::CreateGraphEvent();
|
|
GetPayload(EPhase::Signal)->SubmissionEvent = SubmissionEvent;
|
|
}
|
|
|
|
FMetalFinalizedCommands* FinalizedPayloads = new FMetalFinalizedCommands;
|
|
Finalize(*FinalizedPayloads);
|
|
|
|
FDynamicRHI::FRHISubmitCommandListsArgs Args;
|
|
Args.CommandLists.Add(FinalizedPayloads);
|
|
FMetalDynamicRHI::Get().RHISubmitCommandLists(MoveTemp(Args));
|
|
|
|
if (SyncPoint)
|
|
{
|
|
SyncPoint->Wait();
|
|
}
|
|
|
|
if (SubmissionEvent && !SubmissionEvent->IsComplete())
|
|
{
|
|
SCOPED_NAMED_EVENT_TEXT("Submission_Wait", FColor::Turquoise);
|
|
SubmissionEvent->Wait();
|
|
}
|
|
}
|
|
|
|
FMetalRHIUploadContext::FMetalRHIUploadContext(FMetalDevice& Device)
|
|
{
|
|
UploadContext = new FMetalRHICommandContext(Device, nullptr);
|
|
UploadContext->ResetContext();
|
|
|
|
WaitContext = new FMetalRHICommandContext(Device, nullptr);
|
|
WaitContext->ResetContext();
|
|
|
|
UploadSyncEvent = Device.CreateEvent();
|
|
}
|
|
|
|
FMetalRHIUploadContext::~FMetalRHIUploadContext()
|
|
{
|
|
delete UploadContext;
|
|
delete WaitContext;
|
|
}
|
|
|
|
void FMetalRHIUploadContext::Finalize(TArray<FMetalPayload*>& OutPayloads)
|
|
{
|
|
for(auto& Function : UploadFunctions)
|
|
{
|
|
Function(UploadContext);
|
|
}
|
|
|
|
UploadSyncCounter++;
|
|
UploadContext->SignalEvent(UploadSyncEvent, UploadSyncCounter);
|
|
|
|
UploadContext->Finalize(OutPayloads);
|
|
|
|
UploadFunctions.Reset();
|
|
UploadContext->ResetContext();
|
|
|
|
WaitContext->WaitForEvent(UploadSyncEvent, UploadSyncCounter);
|
|
WaitContext->Finalize(OutPayloads);
|
|
|
|
WaitContext->ResetContext();
|
|
}
|
|
|
|
FMetalContextArray::FMetalContextArray(FRHIContextArray const& Contexts)
|
|
: TRHIPipelineArray(InPlace, nullptr)
|
|
{
|
|
for (ERHIPipeline Pipeline : MakeFlagsRange(ERHIPipeline::All))
|
|
{
|
|
IRHIComputeContext* Context = Contexts[Pipeline];
|
|
|
|
switch (Pipeline)
|
|
{
|
|
default:
|
|
checkNoEntry();
|
|
break;
|
|
|
|
case ERHIPipeline::Graphics:
|
|
(*this)[Pipeline] = Context ? static_cast<FMetalRHICommandContext*>(&Context->GetLowestLevelContext()) : nullptr;
|
|
break;
|
|
|
|
case ERHIPipeline::AsyncCompute:
|
|
(*this)[Pipeline] = Context ? static_cast<FMetalRHICommandContext*>(&Context->GetLowestLevelContext()) : nullptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|