Files
UnrealEngine/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalRHIRenderQuery.cpp
2025-05-18 13:04:45 +08:00

421 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MetalRHIRenderQuery.cpp: Metal RHI Render Query Implementation.
=============================================================================*/
#include "MetalRHIRenderQuery.h"
#include "MetalDevice.h"
#include "MetalRHIPrivate.h"
#include "MetalDynamicRHI.h"
#include "MetalProfiler.h"
#include "MetalLLM.h"
#include "MetalCommandBuffer.h"
#include "MetalRHIContext.h"
#include "HAL/PThreadEvent.h"
#include "RenderCore.h"
//------------------------------------------------------------------------------
#pragma mark - Metal RHI Private Query Buffer Resource Class -
static int32 GMetalMaxQueryBufferSize = 1024 * 256;
static FAutoConsoleVariableRef CVarMetalMaxQueryBufferSize(
TEXT("rhi.Metal.MaxQueryBufferSize"),
GMetalMaxQueryBufferSize,
TEXT("Maximum size of the the query buffer in a single context. Default = 512kb on Mac and 256k on iOS"),
ECVF_ReadOnly);
FMetalQueryBuffer::FMetalQueryBuffer(FMetalQueryBufferPool* InPool, FMetalBufferPtr InBuffer)
: FRHIResource(RRT_TimestampCalibrationQuery)
, Pool(InPool)
, Buffer(InBuffer)
, WriteOffset(0)
{
// void
}
FMetalQueryBuffer::~FMetalQueryBuffer()
{
if (GIsMetalInitialized)
{
if (Buffer)
{
if (Pool)
{
Pool->ReleaseQueryBuffer(Buffer);
}
}
}
}
uint64 FMetalQueryBuffer::GetResult(uint32 Offset)
{
uint64 Result = 0;
if(Buffer.IsValid())
{
MTL_SCOPED_AUTORELEASE_POOL;
{
Result = *((uint64 const*)(((uint8*)Buffer->Contents()) + Offset));
}
}
return Result;
}
//------------------------------------------------------------------------------
#pragma mark - Metal RHI Private Query Buffer Pool Class -
FMetalQueryBufferPool::FMetalQueryBufferPool(FMetalDevice& InDevice)
: CurrentBuffer{nullptr}
, Buffers{}
, Device{InDevice}
{
BufferSize = GMetalMaxQueryBufferSize;
if (!Device.GetDevice()->supportsFamily(MTL::GPUFamilyApple7))
{
// On A13 and below devices the offset in setVisibilityResultMode needs to be <= 65528
BufferSize = FMath::Min(65528, GMetalMaxQueryBufferSize);
}
}
FMetalQueryBufferPool::~FMetalQueryBufferPool()
{
// void
}
void FMetalQueryBufferPool::Allocate(FMetalQueryResult& NewQuery)
{
check(IsValidRef(CurrentBuffer));
FMetalQueryBuffer* QB = CurrentBuffer.GetReference();
uint32 Offset = Align(QB->WriteOffset, EQueryBufferAlignment);
uint32 End = Align(Offset + EQueryResultMaxSize, EQueryBufferAlignment);
if (Align(QB->WriteOffset, EQueryBufferAlignment) + EQueryResultMaxSize <= BufferSize)
{
NewQuery.SourceBuffer = QB;
NewQuery.Offset = Align(QB->WriteOffset, EQueryBufferAlignment);
QB->WriteOffset = Align(QB->WriteOffset, EQueryBufferAlignment) + EQueryResultMaxSize;
}
}
FMetalQueryBuffer* FMetalQueryBufferPool::AcquireQueryBuffer(uint32_t NumOcclusionQueries)
{
uint32_t RequiredSize = NumOcclusionQueries * EQueryResultMaxSize;
if(CurrentBuffer)
{
// If we currently have a buffer and the results fit, then use it
if(Align(CurrentBuffer->WriteOffset, EQueryBufferAlignment) + RequiredSize <= BufferSize)
{
return CurrentBuffer;
}
else
{
ReleaseCurrentQueryBuffer();
}
}
// Need to resize if queries don't fit in our current buffer size
if(RequiredSize > BufferSize)
{
BufferSize = FMath::RoundUpToPowerOfTwo(RequiredSize);
for(FMetalBufferPtr Buffer : Buffers)
{
FMetalDynamicRHI::Get().DeferredDelete(Buffer);
}
Buffers.Empty();
}
FMetalBufferPtr Buffer;
if (Buffers.Num())
{
Buffer = Buffers.Pop();
}
else
{
METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("AllocBuffer: %llu, %llu"), BufferSize, MTL::ResourceStorageModeShared)));
MTL::ResourceOptions HazardTrackingMode = MTL::ResourceHazardTrackingModeUntracked;
static bool bSupportsHeaps = Device.SupportsFeature(EMetalFeaturesHeaps);
if(bSupportsHeaps)
{
HazardTrackingMode = MTL::ResourceHazardTrackingModeTracked;
}
Buffer = Device.GetResourceHeap().CreateBuffer(BufferSize, 16, BUF_Dynamic, FMetalCommandQueue::GetCompatibleResourceOptions((MTL::ResourceOptions)(BUFFER_CACHE_MODE | HazardTrackingMode | MTL::ResourceStorageModeShared)), true);
FMemory::Memzero((((uint8*)Buffer->Contents())), BufferSize);
}
check(Buffer);
CurrentBuffer = new FMetalQueryBuffer(this, MoveTemp(Buffer));
return CurrentBuffer.GetReference();
}
FMetalQueryBuffer* FMetalQueryBufferPool::GetCurrentQueryBuffer()
{
return CurrentBuffer.GetReference();
}
void FMetalQueryBufferPool::ReleaseCurrentQueryBuffer()
{
if (IsValidRef(CurrentBuffer) && (CurrentBuffer->WriteOffset > 0))
{
FMetalDynamicRHI::Get().DeferredDelete([InBuffer = MoveTemp(CurrentBuffer)]() mutable
{
InBuffer.SafeRelease();
});
}
}
void FMetalQueryBufferPool::ReleaseQueryBuffer(FMetalBufferPtr Buffer)
{
if(Buffer->GetLength() >= BufferSize)
{
Buffers.Add(Buffer);
}
else
{
FMetalDynamicRHI::Get().DeferredDelete(Buffer);
}
}
//------------------------------------------------------------------------------
#pragma mark - Metal RHI Private Query Result Class -
void FMetalQueryResult::Reset()
{
bCompleted = false;
}
uint64 FMetalQueryResult::GetResult()
{
if (IsValidRef(SourceBuffer))
{
return SourceBuffer->GetResult(Offset);
}
return 0;
}
//------------------------------------------------------------------------------
#pragma mark - Metal RHI Command Context Functions -
void FMetalDynamicRHI::RHIBeginRenderQuery_TopOfPipe(FRHICommandListBase& RHICmdList, FRHIRenderQuery* RenderQuery)
{
FMetalRHIRenderQuery* Query = ResourceCast(RenderQuery);
Query->Begin_TopOfPipe();
FDynamicRHI::RHIBeginRenderQuery_TopOfPipe(RHICmdList, RenderQuery);
}
void FMetalDynamicRHI::RHIEndRenderQuery_TopOfPipe(FRHICommandListBase& RHICmdList, FRHIRenderQuery* RenderQuery)
{
FMetalRHIRenderQuery* Query = ResourceCast(RenderQuery);
auto& QueryBatchData = RHICmdList.GetQueryBatchData(Query->Type);
if (QueryBatchData[0])
{
// This query belongs to a batch. Use the sync point we created earlier
Query->SyncPoint = static_cast<FMetalSyncPoint*>(QueryBatchData[0]);
}
else
{
// Queries issued outside of a batch use one sync point per query.
Query->SyncPoint = FMetalSyncPoint::Create(EMetalSyncPointType::GPUAndCPU);
RHICmdList.EnqueueLambda([SyncPoint = Query->SyncPoint](FRHICommandListBase& RHICmdList) mutable
{
FMetalRHICommandContext& Context = FMetalRHICommandContext::Get(RHICmdList);
Context.BatchedSyncPoints.ToSignal.Emplace(MoveTemp(SyncPoint));
});
}
FDynamicRHI::RHIEndRenderQuery_TopOfPipe(RHICmdList, RenderQuery);
}
void FMetalDynamicRHI::RHIBeginRenderQueryBatch_TopOfPipe(FRHICommandListBase& RHICmdList, ERenderQueryType QueryType)
{
auto& QueryBatchData = RHICmdList.GetQueryBatchData(QueryType);
FMetalSyncPointRef QuerySyncPoint = FMetalSyncPoint::Create(EMetalSyncPointType::GPUAndCPU);
QueryBatchData[0] = QuerySyncPoint.GetReference();
QuerySyncPoint->AddRef();
}
void FMetalDynamicRHI::RHIEndRenderQueryBatch_TopOfPipe(FRHICommandListBase& RHICmdList, ERenderQueryType QueryType)
{
auto& QueryBatchData = RHICmdList.GetQueryBatchData(QueryType);
checkf(QueryBatchData[0], TEXT("A query batch for this type is not open on this command list."));
FMetalSyncPointRef SyncPoint = static_cast<FMetalSyncPoint*>(QueryBatchData[0]);
// Clear the sync point reference on the RHI command list
SyncPoint->Release();
QueryBatchData[0] = nullptr;
RHICmdList.EnqueueLambda([SyncPoint = MoveTemp(SyncPoint), QueryType](FRHICommandListBase& ExecutingCmdList)
{
FMetalRHICommandContext& Context = FMetalRHICommandContext::Get(ExecutingCmdList);
Context.BatchedSyncPoints.ToSignal.Add(SyncPoint);
});
}
void FMetalRHICommandContext::RHIBeginRenderQuery(FRHIRenderQuery* QueryRHI)
{
MTL_SCOPED_AUTORELEASE_POOL;
FMetalRHIRenderQuery* Query = ResourceCast(QueryRHI);
Query->Begin(this);
}
void FMetalRHICommandContext::RHIEndRenderQuery(FRHIRenderQuery* QueryRHI)
{
MTL_SCOPED_AUTORELEASE_POOL;
FMetalRHIRenderQuery* Query = ResourceCast(QueryRHI);
Query->End(this);
}
//------------------------------------------------------------------------------
#pragma mark - Metal RHI Render Query Class -
FMetalRHIRenderQuery::FMetalRHIRenderQuery(FMetalDevice& MetalDevice, ERenderQueryType InQueryType)
: Device(MetalDevice)
, Type{InQueryType}
, Buffer{}
, Result{0}
, bAvailable{false}
{
// void
}
FMetalRHIRenderQuery::~FMetalRHIRenderQuery()
{
Buffer.SourceBuffer.SafeRelease();
Buffer.Offset = 0;
}
void FMetalRHIRenderQuery::Begin_TopOfPipe()
{
Buffer.Reset();
bAvailable = false;
}
void FMetalRHIRenderQuery::End_TopOfPipe()
{
if (Type == RQT_AbsoluteTime)
{
Buffer.Reset();
}
bAvailable = false;
}
void FMetalRHIRenderQuery::Begin(FMetalRHICommandContext* Context)
{
Buffer.SourceBuffer.SafeRelease();
Buffer.Offset = 0;
Result = 0;
bAvailable = false;
switch (Type)
{
case RQT_Occlusion:
{
// allocate our space in the current buffer
Context->GetQueryBufferPool()->Allocate(Buffer);
Buffer.bCompleted = false;
if ((GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5) && Device.SupportsFeature(EMetalFeaturesCountingQueries))
{
Context->GetStateCache().SetVisibilityResultMode(MTL::VisibilityResultModeCounting, Buffer.Offset);
}
else
{
Context->GetStateCache().SetVisibilityResultMode(MTL::VisibilityResultModeBoolean, Buffer.Offset);
}
break;
}
case RQT_AbsoluteTime:
{
break;
}
default:
{
check(0);
break;
}
}
}
void FMetalRHIRenderQuery::End(FMetalRHICommandContext* Context)
{
switch (Type)
{
case RQT_Occlusion:
{
// switch back to non-occlusion rendering
Context->GetStateCache().SetVisibilityResultMode(MTL::VisibilityResultModeDisabled, 0);
Context->GetCurrentCommandBuffer()->OcclusionQueries.Add(this);
break;
}
case RQT_AbsoluteTime:
{
AddRef();
// Reset the result availability state
Buffer.SourceBuffer.SafeRelease();
Buffer.Offset = 0;
Buffer.bCompleted = false;
Result = 0;
bAvailable = false;
CommandBuffer = Context->GetCurrentCommandBuffer();
CommandBuffer->TimestampQueries.Add(this);
break;
}
default:
{
check(0);
break;
}
}
}
bool FMetalRHIRenderQuery::GetResult(uint64& OutNumPixels, bool bWait, uint32 GPUIndex)
{
if (!bAvailable)
{
if (!SyncPoint->IsComplete())
{
if (bWait)
{
SyncPoint->Wait();
}
else
{
return false;
}
}
}
OutNumPixels = Result;
return true;
}