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

394 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VulkanContext.h"
#include "VulkanCommandBuffer.h"
#include "VulkanMemory.h"
#include "VulkanResources.h"
#include "VulkanRHIPrivate.h"
#include "VulkanDevice.h"
#include "VulkanPendingState.h"
#include "VulkanQuery.h"
#include "DynamicRHI.h"
FVulkanContextCommon::FVulkanContextCommon(FVulkanDevice& InDevice, FVulkanQueue& InQueue, EVulkanCommandBufferType InCommandBufferType)
: Device(InDevice)
, Queue(InQueue)
, Pool(*InQueue.AcquireCommandBufferPool(InCommandBufferType))
{
}
FVulkanContextCommon::~FVulkanContextCommon()
{
Queue.ReleaseCommandBufferPool(&Pool);
}
void FVulkanContextCommon::NewPayload()
{
EndPayload();
Payloads.Add(new FVulkanPayload(Queue));
CurrentPhase = EPhase::Wait;
}
void FVulkanContextCommon::EndPayload()
{
if (Payloads.Num() > 0)
{
FlushPendingSyncPoints();
FVulkanPayload* Payload = Payloads.Last();
if (Payload->CommandBuffers.Num() > 0)
{
FVulkanCommandBuffer* CommandBuffer = Payload->CommandBuffers.Last();
checkSlow(!CommandBuffer->IsSubmitted() && CommandBuffer->HasBegun());
const bool bIsPrimaryCommandBuffer = (CommandBuffer->GetCommandBufferType() == EVulkanCommandBufferType::Primary);
if (!CommandBuffer->IsOutsideRenderPass() && bIsPrimaryCommandBuffer)
{
UE_LOG(LogVulkanRHI, Warning, TEXT("Forcing EndRenderPass() for submission"));
CommandBuffer->EndRenderPass();
}
// Only record begin/end timestamps on primary command buffers
FVulkanQueryPool* TimestampQueryPool =
GRHIGlobals.SupportsTimestampRenderQueries && bIsPrimaryCommandBuffer ?
GetCurrentTimestampQueryPool(*Payload) : nullptr;
CommandBuffer->End(TimestampQueryPool);
}
}
}
// Complete recording of the current command list set, and appends the resulting
// payloads to the given array. Resets the context so new commands can be recorded.
void FVulkanContextCommon::Finalize(TArray<FVulkanPayload*>& OutPayloads)
{
FlushPendingSyncPoints();
if (ContextSyncPoint.IsValid())
{
SignalSyncPoint(ContextSyncPoint);
ContextSyncPoint = nullptr;
}
EndPayload();
OutPayloads.Append(MoveTemp(Payloads));
}
void FVulkanContextCommon::FlushCommands(EVulkanFlushFlags FlushFlags)
{
FVulkanSyncPointRef SyncPoint;
if (EnumHasAnyFlags(FlushFlags, EVulkanFlushFlags::WaitForCompletion))
{
SyncPoint = GetContextSyncPoint();
}
FGraphEventRef SubmissionEvent;
if (EnumHasAnyFlags(FlushFlags, EVulkanFlushFlags::WaitForSubmission))
{
SubmissionEvent = FGraphEvent::CreateGraphEvent();
AddSubmissionEvent(SubmissionEvent);
}
FVulkanPlatformCommandList* FinalizedPayloads = new FVulkanPlatformCommandList;
Finalize(*FinalizedPayloads);
FDynamicRHI::FRHISubmitCommandListsArgs Args;
Args.CommandLists.Add(FinalizedPayloads);
FVulkanDynamicRHI::Get().RHISubmitCommandLists(MoveTemp(Args));
if (SyncPoint)
{
FVulkanDynamicRHI::Get().ProcessInterruptQueueUntil(SyncPoint);
}
if (SubmissionEvent && !SubmissionEvent->IsComplete())
{
SCOPED_NAMED_EVENT_TEXT("Submission_Wait", FColor::Turquoise);
SubmissionEvent->Wait();
}
}
#if VULKAN_DELETE_STALE_CMDBUFFERS
struct FRHICommandFreeUnusedCmdBuffers final : public FRHICommand<FRHICommandFreeUnusedCmdBuffers>
{
FVulkanCommandBufferPool* Pool;
FVulkanQueue* Queue;
bool bTrimMemory;
FRHICommandFreeUnusedCmdBuffers(FVulkanCommandBufferPool* InPool, FVulkanQueue* InQueue, bool bInTrimMemory)
: Pool(InPool)
, Queue(InQueue)
, bTrimMemory(bInTrimMemory)
{
}
void Execute(FRHICommandListBase& CmdList)
{
Pool->FreeUnusedCmdBuffers(Queue, bTrimMemory);
}
};
#endif
void FVulkanContextCommon::FreeUnusedCmdBuffers(bool bTrimMemory)
{
#if VULKAN_DELETE_STALE_CMDBUFFERS
FRHICommandList& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
if (!IsInRenderingThread() || (RHICmdList.Bypass() || !IsRunningRHIInSeparateThread()))
{
Pool.FreeUnusedCmdBuffers(&Queue, bTrimMemory);
}
else
{
check(IsInRenderingThread());
ALLOC_COMMAND_CL(RHICmdList, FRHICommandFreeUnusedCmdBuffers)(&Pool, &Queue, bTrimMemory);
}
#endif
}
void FVulkanContextCommon::PrepareNewCommandBuffer(FVulkanPayload& Payload)
{
FScopeLock ScopeLock(&Pool.CS);
FVulkanCommandBuffer* NewCommandBuffer = nullptr;
for (int32 Index = 0; Index < Pool.CmdBuffers.Num(); ++Index)
{
FVulkanCommandBuffer* CmdBuffer = Pool.CmdBuffers[Index];
if (CmdBuffer->State == FVulkanCommandBuffer::EState::ReadyForBegin || CmdBuffer->State == FVulkanCommandBuffer::EState::NeedReset)
{
NewCommandBuffer = CmdBuffer;
break;
}
else
{
check(CmdBuffer->IsSubmitted() || CmdBuffer->HasEnded());
}
}
// All cmd buffers are being executed still, create a new one
if (!NewCommandBuffer)
{
NewCommandBuffer = Pool.Create();
}
Payload.CommandBuffers.Add(NewCommandBuffer);
// Only record begin/end timestamps on primary command buffers
const bool bIsPrimaryCommandBuffer = (NewCommandBuffer->GetCommandBufferType() == EVulkanCommandBufferType::Primary);
FVulkanQueryPool* TimestampQueryPool = GRHIGlobals.SupportsTimestampRenderQueries && bIsPrimaryCommandBuffer ?
GetCurrentTimestampQueryPool(Payload) : nullptr;
VkRenderPass RenderPassHandle = GetParallelRenderPassInfo() ? GetParallelRenderPassInfo()->RenderPassHandle : VK_NULL_HANDLE;
NewCommandBuffer->Begin(TimestampQueryPool, RenderPassHandle);
}
FVulkanCommandListContext::FVulkanCommandListContext(FVulkanDevice& InDevice, ERHIPipeline InPipeline, FVulkanCommandListContext* InImmediate)
: FVulkanContextCommon(InDevice, *InDevice.GetQueue(InPipeline), EVulkanCommandBufferType::Primary)
, Immediate(InImmediate)
, RHIPipeline(InPipeline)
, bSupportsBreadcrumbs(GRHIGlobals.SupportsTimestampRenderQueries)
#if (RHI_NEW_GPU_PROFILER == 0)
, GpuProfiler(this, &InDevice)
#endif
{
#if (RHI_NEW_GPU_PROFILER == 0)
FrameTiming = new FVulkanGPUTiming(this, &InDevice);
FrameTiming->Initialize();
#endif
// Create Pending state, contains pipeline states such as current shader and etc..
PendingGfxState = new FVulkanPendingGfxState(Device);
PendingComputeState = new FVulkanPendingComputeState(Device);
GlobalUniformBuffers.AddZeroed(FUniformBufferStaticSlotRegistry::Get().GetSlotCount());
}
FVulkanCommandListContext::FVulkanCommandListContext(FVulkanDevice& InDevice, FVulkanCommandListContext* InImmediate, FVulkanParallelRenderPassInfo* InParallelRenderPassInfo)
: FVulkanContextCommon(InDevice, *InDevice.GetQueue(ERHIPipeline::Graphics), EVulkanCommandBufferType::Secondary)
, Immediate(InImmediate)
, RHIPipeline(ERHIPipeline::Graphics)
, bSupportsBreadcrumbs(false)
, CurrentParallelRenderPassInfo(InParallelRenderPassInfo)
#if (RHI_NEW_GPU_PROFILER == 0)
, GpuProfiler(this, &InDevice)
#endif
{
checkf(CurrentParallelRenderPassInfo, TEXT("Secondary command buffers should be created with a FVulkanParallelRenderPassInfo."));
// Only graphic commands can be used
PendingGfxState = new FVulkanPendingGfxState(Device);
GlobalUniformBuffers.AddZeroed(FUniformBufferStaticSlotRegistry::Get().GetSlotCount());
}
void FVulkanCommandListContext::ReleasePendingState()
{
delete PendingGfxState;
PendingGfxState = nullptr;
delete PendingComputeState;
PendingComputeState = nullptr;
}
FVulkanCommandListContext::~FVulkanCommandListContext()
{
if (GSupportsTimestampRenderQueries)
{
#if (RHI_NEW_GPU_PROFILER == 0)
if (FrameTiming)
{
FrameTiming->Release();
delete FrameTiming;
FrameTiming = nullptr;
}
#endif
}
ReleasePendingState();
}
void FVulkanCommandListContext::RHIBeginParallelRenderPass(TSharedPtr<FRHIParallelRenderPassInfo> InInfo, const TCHAR* InName)
{
checkf(CurrentParallelRenderPassInfo == nullptr, TEXT("There is already a parallel render pass in progress!"));
CurrentParallelRenderPassInfo = new FVulkanParallelRenderPassInfo();
RHIBeginRenderPass(*InInfo.Get(), InName);
CurrentParallelRenderPassInfo->RenderPassHandle = CurrentRenderPass->GetHandle();
InInfo->RHIPlatformData = CurrentParallelRenderPassInfo;
}
void FVulkanCommandListContext::RHIEndParallelRenderPass()
{
if (CurrentParallelRenderPassInfo->SecondaryPayloads.Num())
{
FVulkanPayload& ParentPayload = GetPayload(EPhase::Execute);
FVulkanCommandBuffer& ParentCommandBuffer = GetCommandBuffer();
TArray<VkCommandBuffer> CommandBufferHandles;
CommandBufferHandles.Reserve(CurrentParallelRenderPassInfo->SecondaryPayloads.Num()); // should be 1 cmdbuffer per payload
for (FVulkanPayload* Payload : CurrentParallelRenderPassInfo->SecondaryPayloads)
{
check(Payload->SignalSemaphores.Num() == 0);
check(Payload->WaitSemaphores.Num() == 0);
for (int32 QueryPoolTypeIndex = 0; QueryPoolTypeIndex < (int32)EVulkanQueryPoolType::Count; ++QueryPoolTypeIndex)
{
ParentPayload.QueryPools[QueryPoolTypeIndex].Append(MoveTemp(Payload->QueryPools[QueryPoolTypeIndex]));
}
for (FVulkanCommandBuffer* SecondaryCommandBuffer : Payload->CommandBuffers)
{
CommandBufferHandles.Add(SecondaryCommandBuffer->GetHandle());
#if RHI_NEW_GPU_PROFILER
// :todo: Flush the event stream if there is one (only use the parent timings for now)
UE::RHI::GPUProfiler::FEventStream Dummy(Device.GetQueue(EVulkanQueueType::Graphics)->GetProfilerQueue());
SecondaryCommandBuffer->FlushProfilerEvents(Dummy, FPlatformTime::Cycles64());
#endif
}
ParentCommandBuffer.ExecutedSecondaryCommandBuffers.Append(MoveTemp(Payload->CommandBuffers));
for (const FVulkanSyncPointRef& Sync : Payload->SyncPoints)
{
AddPendingSyncPoint(Sync);
}
delete Payload;
}
if (CommandBufferHandles.Num())
{
VulkanRHI::vkCmdExecuteCommands(ParentCommandBuffer.GetHandle(), CommandBufferHandles.Num(), CommandBufferHandles.GetData());
}
}
RHIEndRenderPass();
delete CurrentParallelRenderPassInfo;
CurrentParallelRenderPassInfo = nullptr;
}
void FVulkanCommandListContext::Finalize(TArray<FVulkanPayload*>& OutPayloads)
{
FlushProfilerStats();
if (CurrentDescriptorPoolSetContainer)
{
GetPayload(EPhase::Signal).DescriptorPoolSetContainers.Add(CurrentDescriptorPoolSetContainer);
CurrentDescriptorPoolSetContainer = nullptr;
TypedDescriptorPoolSets.Reset();
}
else
{
check(TypedDescriptorPoolSets.Num() == 0);
}
FVulkanContextCommon::Finalize(OutPayloads);
}
void FVulkanCommandListContext::AcquirePoolSetContainer()
{
if (!CurrentDescriptorPoolSetContainer)
{
CurrentDescriptorPoolSetContainer = &Device.GetDescriptorPoolsManager().AcquirePoolSetContainer();
ensure(TypedDescriptorPoolSets.Num() == 0);
}
}
bool FVulkanCommandListContext::AcquirePoolSetAndDescriptorsIfNeeded(const FVulkanDescriptorSetsLayout& Layout, bool bNeedDescriptors, VkDescriptorSet* OutDescriptors)
{
AcquirePoolSetContainer();
const uint32 Hash = VULKAN_HASH_POOLS_WITH_TYPES_USAGE_ID ? Layout.GetTypesUsageID() : GetTypeHash(Layout);
FVulkanTypedDescriptorPoolSet*& FoundTypedSet = TypedDescriptorPoolSets.FindOrAdd(Hash);
if (!FoundTypedSet)
{
FoundTypedSet = CurrentDescriptorPoolSetContainer->AcquireTypedPoolSet(Layout);
bNeedDescriptors = true;
}
if (bNeedDescriptors)
{
return FoundTypedSet->AllocateDescriptorSets(Layout, OutDescriptors);
}
return false;
}
TLockFreePointerListUnordered<FVulkanUploadContext, PLATFORM_CACHE_LINE_SIZE> FVulkanUploadContext::Pool;
void FVulkanUploadContext::DestroyPool()
{
while (FVulkanUploadContext* Context = FVulkanUploadContext::Pool.Pop())
{
delete Context;
}
}
FVulkanUploadContext::FVulkanUploadContext(FVulkanDevice& InDevice, FVulkanQueue& InQueue)
: FVulkanContextCommon(InDevice, InQueue, EVulkanCommandBufferType::Primary)
{
}
FVulkanUploadContext::~FVulkanUploadContext()
{
}
IRHIUploadContext* FVulkanDynamicRHI::RHIGetUploadContext()
{
FVulkanUploadContext* Context = FVulkanUploadContext::Pool.Pop();
if (!Context)
{
// :todo-jn: locked to graphics queue for now
Context = new FVulkanUploadContext(*Device, *Device->GetGraphicsQueue());
}
return Context;
}