581 lines
21 KiB
C++
581 lines
21 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
VulkanIndexBuffer.cpp: Vulkan Index buffer RHI implementation.
|
|
=============================================================================*/
|
|
|
|
#include "VulkanRHIPrivate.h"
|
|
#include "VulkanDevice.h"
|
|
#include "VulkanContext.h"
|
|
#include "Containers/ResourceArray.h"
|
|
#include "VulkanLLM.h"
|
|
#include "VulkanRayTracing.h"
|
|
#include "VulkanTransientResourceAllocator.h"
|
|
#include "RHICoreBufferInitializer.h"
|
|
#include "RHICoreStats.h"
|
|
#include "HAL/LowLevelMemStats.h"
|
|
#include "ProfilingDebugging/AssetMetadataTrace.h"
|
|
|
|
struct FVulkanPendingBufferLock
|
|
{
|
|
VulkanRHI::FStagingBuffer* StagingBuffer = nullptr;
|
|
uint32 Offset = 0;
|
|
uint32 Size = 0;
|
|
EResourceLockMode LockMode = RLM_Num;
|
|
bool FirstLock = false;
|
|
};
|
|
|
|
static TMap<FVulkanBuffer*, FVulkanPendingBufferLock> GPendingLocks;
|
|
static FCriticalSection GPendingLockMutex;
|
|
|
|
int32 GVulkanForceStagingBufferOnLock = 0;
|
|
static FAutoConsoleVariableRef CVarVulkanForceStagingBufferOnLock(
|
|
TEXT("r.Vulkan.ForceStagingBufferOnLock"),
|
|
GVulkanForceStagingBufferOnLock,
|
|
TEXT("When nonzero, non-volatile buffer locks will always use staging buffers. Useful for debugging.\n")
|
|
TEXT("default: 0"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static FORCEINLINE FVulkanPendingBufferLock GetPendingBufferLock(FVulkanBuffer* Buffer)
|
|
{
|
|
FVulkanPendingBufferLock PendingLock;
|
|
|
|
// Found only if it was created for Write
|
|
FScopeLock ScopeLock(&GPendingLockMutex);
|
|
const bool bFound = GPendingLocks.RemoveAndCopyValue(Buffer, PendingLock);
|
|
|
|
checkf(bFound, TEXT("Mismatched Buffer Lock/Unlock!"));
|
|
return PendingLock;
|
|
}
|
|
|
|
static FORCEINLINE void AddPendingBufferLock(FVulkanBuffer* Buffer, FVulkanPendingBufferLock& PendingLock)
|
|
{
|
|
FScopeLock ScopeLock(&GPendingLockMutex);
|
|
check(!GPendingLocks.Contains(Buffer));
|
|
GPendingLocks.Add(Buffer, PendingLock);
|
|
}
|
|
|
|
static void UpdateVulkanBufferStats(const FRHIBufferDesc& BufferDesc, int64 BufferSize, bool bAllocating)
|
|
{
|
|
UE::RHICore::UpdateGlobalBufferStats(BufferDesc, BufferSize, bAllocating);
|
|
}
|
|
|
|
static VkDeviceAddress GetBufferDeviceAddress(FVulkanDevice* Device, VkBuffer Buffer)
|
|
{
|
|
if (Device->GetOptionalExtensions().HasBufferDeviceAddress)
|
|
{
|
|
VkBufferDeviceAddressInfoKHR DeviceAddressInfo;
|
|
ZeroVulkanStruct(DeviceAddressInfo, VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO);
|
|
DeviceAddressInfo.buffer = Buffer;
|
|
return VulkanRHI::vkGetBufferDeviceAddressKHR(Device->GetInstanceHandle(), &DeviceAddressInfo);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
VkBufferUsageFlags FVulkanBuffer::UEToVKBufferUsageFlags(FVulkanDevice* InDevice, EBufferUsageFlags InUEUsage, bool bZeroSize)
|
|
{
|
|
// Always include TRANSFER_SRC since hardware vendors confirmed it wouldn't have any performance cost and we need it for some debug functionalities.
|
|
VkBufferUsageFlags OutVkUsage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
|
|
|
auto TranslateFlag = [&OutVkUsage, &InUEUsage](EBufferUsageFlags SearchUEFlag, VkBufferUsageFlags AddedIfFound, VkBufferUsageFlags AddedIfNotFound = 0)
|
|
{
|
|
const bool HasFlag = EnumHasAnyFlags(InUEUsage, SearchUEFlag);
|
|
OutVkUsage |= HasFlag ? AddedIfFound : AddedIfNotFound;
|
|
};
|
|
|
|
TranslateFlag(BUF_VertexBuffer, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
|
TranslateFlag(BUF_IndexBuffer, VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
|
|
TranslateFlag(BUF_ByteAddressBuffer|BUF_StructuredBuffer, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
|
|
TranslateFlag(BUF_UniformBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT);
|
|
TranslateFlag(BUF_AccelerationStructure, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR);
|
|
|
|
if (!bZeroSize)
|
|
{
|
|
TranslateFlag(BUF_UnorderedAccess, VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT);
|
|
TranslateFlag(BUF_DrawIndirect, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT);
|
|
TranslateFlag(BUF_KeepCPUAccessible, (VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT));
|
|
TranslateFlag(BUF_ShaderResource, VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT);
|
|
|
|
TranslateFlag(BUF_Volatile, 0, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
|
|
|
|
if (InDevice->GetOptionalExtensions().HasRaytracingExtensions())
|
|
{
|
|
OutVkUsage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
|
|
|
TranslateFlag(BUF_AccelerationStructure, 0, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR);
|
|
}
|
|
|
|
// For descriptors buffers
|
|
if (InDevice->GetOptionalExtensions().HasBufferDeviceAddress)
|
|
{
|
|
OutVkUsage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
|
}
|
|
}
|
|
|
|
return OutVkUsage;
|
|
}
|
|
|
|
FVulkanBuffer::FVulkanBuffer(FVulkanDevice* InDevice, const FRHIBufferCreateDesc& CreateDesc, const FRHITransientHeapAllocation* InTransientHeapAllocation)
|
|
: FRHIBuffer(CreateDesc)
|
|
, VulkanRHI::FDeviceChild(InDevice)
|
|
{
|
|
VULKAN_TRACK_OBJECT_CREATE(FVulkanBuffer, this);
|
|
|
|
const bool bZeroSize = (CreateDesc.Size == 0);
|
|
BufferUsageFlags = UEToVKBufferUsageFlags(InDevice, CreateDesc.Usage, bZeroSize);
|
|
|
|
if (!bZeroSize)
|
|
{
|
|
check(InDevice);
|
|
|
|
const bool bUnifiedMem = InDevice->HasUnifiedMemory();
|
|
const uint32 BufferAlignment = VulkanRHI::FMemoryManager::CalculateBufferAlignment(*InDevice, CreateDesc.Usage, bZeroSize);
|
|
|
|
if (InTransientHeapAllocation != nullptr)
|
|
{
|
|
CurrentBufferAlloc.Alloc = FVulkanTransientHeap::GetVulkanAllocation(*InTransientHeapAllocation);
|
|
CurrentBufferAlloc.HostPtr = bUnifiedMem ? CurrentBufferAlloc.Alloc.GetMappedPointer(Device) : nullptr;
|
|
CurrentBufferAlloc.DeviceAddress = GetBufferDeviceAddress(InDevice, CurrentBufferAlloc.Alloc.GetBufferHandle()) + CurrentBufferAlloc.Alloc.Offset;
|
|
check(CurrentBufferAlloc.Alloc.Offset % BufferAlignment == 0);
|
|
check(CurrentBufferAlloc.Alloc.Size >= CreateDesc.Size);
|
|
}
|
|
else
|
|
{
|
|
AllocateMemory(CurrentBufferAlloc);
|
|
}
|
|
}
|
|
}
|
|
|
|
FVulkanBuffer::~FVulkanBuffer()
|
|
{
|
|
VULKAN_TRACK_OBJECT_DELETE(FVulkanBuffer, this);
|
|
ReleaseOwnership();
|
|
}
|
|
|
|
|
|
void FVulkanBuffer::AllocateMemory(FBufferAlloc& OutAlloc)
|
|
{
|
|
VkMemoryPropertyFlags BufferMemFlags = 0;
|
|
const bool bUnifiedMem = Device->HasUnifiedMemory();
|
|
const bool bDynamic = EnumHasAnyFlags(GetUsage(), BUF_Dynamic) || EnumHasAnyFlags(GetUsage(), BUF_Volatile);
|
|
if (bUnifiedMem)
|
|
{
|
|
BufferMemFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
}
|
|
else if (bDynamic)
|
|
{
|
|
BufferMemFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
}
|
|
else
|
|
{
|
|
BufferMemFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
|
}
|
|
const uint32 BufferSize = Align(GetSize(), 4); // keep allocated size a multiple of 4 (for use with vkCmdFillBuffer)
|
|
const uint32 BufferAlignment = VulkanRHI::FMemoryManager::CalculateBufferAlignment(*Device, GetUsage(), (BufferSize == 0));
|
|
|
|
FBufferAlloc& NewBufferAlloc = OutAlloc;
|
|
if (!Device->GetMemoryManager().AllocateBufferPooled(NewBufferAlloc.Alloc, nullptr, BufferSize, BufferAlignment, BufferUsageFlags, BufferMemFlags, VulkanRHI::EVulkanAllocationMetaMultiBuffer, __FILE__, __LINE__))
|
|
{
|
|
Device->GetMemoryManager().HandleOOM();
|
|
}
|
|
NewBufferAlloc.HostPtr = (bUnifiedMem || bDynamic) ? NewBufferAlloc.Alloc.GetMappedPointer(Device) : nullptr;
|
|
NewBufferAlloc.DeviceAddress = GetBufferDeviceAddress(Device, NewBufferAlloc.Alloc.GetBufferHandle()) + NewBufferAlloc.Alloc.Offset;
|
|
|
|
UpdateVulkanBufferStats(GetDesc(), NewBufferAlloc.Alloc.Size, true);
|
|
}
|
|
|
|
void* FVulkanBuffer::Lock(FRHICommandListBase& RHICmdList, EResourceLockMode LockMode, uint32 LockSize, uint32 Offset)
|
|
{
|
|
void* Data = nullptr;
|
|
uint32 DataOffset = 0;
|
|
|
|
check(LockStatus == ELockStatus::Unlocked);
|
|
|
|
LockStatus = ELockStatus::Locked;
|
|
const bool bIsFirstLock = (0 == LockCounter++);
|
|
|
|
// Dynamic: Allocate a new Host_Visible buffer, swap this new buffer in on RHI thread and update views.
|
|
// GPU reads directly from host memory, but no copy is required so it can be used in render passes.
|
|
// Static: A single Device_Local buffer is allocated at creation. For Lock/Unlock, use a staging buffer for the upload:
|
|
// host writes to staging buffer on lock, a copy on GPU is issued on unlock to update the device_local memory.
|
|
|
|
const bool bUnifiedMem = Device->HasUnifiedMemory();
|
|
const bool bDynamic = EnumHasAnyFlags(GetUsage(), BUF_Dynamic) || EnumHasAnyFlags(GetUsage(), BUF_Volatile);
|
|
const bool bStatic = EnumHasAnyFlags(GetUsage(), BUF_Static) || !bDynamic;
|
|
const bool bUAV = EnumHasAnyFlags(GetUsage(), BUF_UnorderedAccess);
|
|
const bool bSR = EnumHasAnyFlags(GetUsage(), BUF_ShaderResource);
|
|
|
|
check(bStatic || bDynamic || bUAV || bSR);
|
|
|
|
if (LockMode == RLM_ReadOnly)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
if (bUnifiedMem)
|
|
{
|
|
Data = CurrentBufferAlloc.HostPtr;
|
|
DataOffset = Offset;
|
|
LockStatus = ELockStatus::PersistentMapping;
|
|
}
|
|
else
|
|
{
|
|
// Create a staging buffer we can use to copy data from device to cpu.
|
|
VulkanRHI::FStagingBuffer* StagingBuffer = Device->GetStagingManager().AcquireBuffer(LockSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
|
|
|
VkBufferCopy Regions;
|
|
Regions.size = LockSize;
|
|
Regions.srcOffset = Offset + CurrentBufferAlloc.Alloc.Offset;
|
|
Regions.dstOffset = 0;
|
|
|
|
VkBuffer BufferHandle = CurrentBufferAlloc.Alloc.GetBufferHandle();
|
|
|
|
FRHICommandListImmediate& ImmCmdList = RHICmdList.GetAsImmediate();
|
|
ImmCmdList.EnqueueLambda([StagingBuffer, Regions, BufferHandle](FRHICommandListBase& ExecutingCmdList)
|
|
{
|
|
FVulkanCommandListContext& Context = FVulkanCommandListContext::Get(ExecutingCmdList);
|
|
FVulkanCommandBuffer& CommandBuffer = Context.GetCommandBuffer();
|
|
|
|
// Make sure any previous tasks have finished on the source buffer.
|
|
VkMemoryBarrier BarrierBefore = { VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT };
|
|
VulkanRHI::vkCmdPipelineBarrier(CommandBuffer.GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &BarrierBefore, 0, nullptr, 0, nullptr);
|
|
|
|
// Fill the staging buffer with the data on the device.
|
|
VulkanRHI::vkCmdCopyBuffer(CommandBuffer.GetHandle(), BufferHandle, StagingBuffer->GetHandle(), 1, &Regions);
|
|
|
|
// Setup barrier.
|
|
VkMemoryBarrier BarrierAfter = { VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_HOST_READ_BIT };
|
|
VulkanRHI::vkCmdPipelineBarrier(CommandBuffer.GetHandle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &BarrierAfter, 0, nullptr, 0, nullptr);
|
|
|
|
});
|
|
|
|
// We need to execute the command list so we can read the data from the map below
|
|
ImmCmdList.SubmitAndBlockUntilGPUIdle();
|
|
|
|
// Flush.
|
|
StagingBuffer->FlushMappedMemory();
|
|
|
|
// Get mapped pointer.
|
|
Data = StagingBuffer->GetMappedPointer();
|
|
|
|
// Release temp staging buffer during unlock.
|
|
FVulkanPendingBufferLock PendingLock;
|
|
PendingLock.Offset = 0;
|
|
PendingLock.Size = LockSize;
|
|
PendingLock.LockMode = LockMode;
|
|
PendingLock.StagingBuffer = StagingBuffer;
|
|
AddPendingBufferLock(this, PendingLock);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
check((LockMode == RLM_WriteOnly) || (LockMode == RLM_WriteOnly_NoOverwrite));
|
|
|
|
// If this is the first lock on host visible memory, then the memory is still untouched so use it directly
|
|
const bool bIsHostVisible = (bUnifiedMem || bDynamic);
|
|
if (bIsHostVisible && (bIsFirstLock || (LockMode == RLM_WriteOnly_NoOverwrite)))
|
|
{
|
|
check(CurrentBufferAlloc.HostPtr);
|
|
Data = CurrentBufferAlloc.HostPtr;
|
|
DataOffset = Offset;
|
|
LockStatus = ELockStatus::PersistentMapping;
|
|
}
|
|
else if (bStatic || GVulkanForceStagingBufferOnLock)
|
|
{
|
|
FVulkanPendingBufferLock PendingLock;
|
|
PendingLock.Offset = Offset;
|
|
PendingLock.Size = LockSize;
|
|
PendingLock.LockMode = LockMode;
|
|
PendingLock.FirstLock = bIsFirstLock;
|
|
|
|
VulkanRHI::FStagingBuffer* StagingBuffer = Device->GetStagingManager().AcquireBuffer(LockSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
|
PendingLock.StagingBuffer = StagingBuffer;
|
|
Data = StagingBuffer->GetMappedPointer();
|
|
|
|
AddPendingBufferLock(this, PendingLock);
|
|
}
|
|
else
|
|
{
|
|
FBufferAlloc NewAlloc;
|
|
AllocateMemory(NewAlloc);
|
|
NewAlloc.Alloc.Disown();
|
|
|
|
RHICmdList.EnqueueLambda(TEXT("FVulkanBuffer::Lock"), [Buffer = this, NewAlloc](FRHICommandListBase& CmdList)
|
|
{
|
|
UpdateVulkanBufferStats(Buffer->GetDesc(), Buffer->CurrentBufferAlloc.Alloc.Size, false);
|
|
Buffer->CurrentBufferAlloc.Alloc.Free(*Buffer->GetParent());
|
|
Buffer->CurrentBufferAlloc = NewAlloc;
|
|
Buffer->CurrentBufferAlloc.Alloc.Own();
|
|
Buffer->UpdateLinkedViews();
|
|
});
|
|
|
|
if (RHICmdList.IsTopOfPipe())
|
|
{
|
|
RHICmdList.RHIThreadFence(true);
|
|
}
|
|
|
|
Data = NewAlloc.HostPtr;
|
|
DataOffset = Offset;
|
|
LockStatus = ELockStatus::PersistentMapping;
|
|
}
|
|
}
|
|
|
|
check(Data);
|
|
return (uint8*)Data + DataOffset;
|
|
}
|
|
|
|
|
|
void FVulkanBuffer::Unlock(FRHICommandListBase& RHICmdList)
|
|
{
|
|
const bool bUnifiedMem = Device->HasUnifiedMemory();
|
|
const bool bDynamic = EnumHasAnyFlags(GetUsage(), BUF_Dynamic) || EnumHasAnyFlags(GetUsage(), BUF_Volatile);
|
|
const bool bStatic = EnumHasAnyFlags(GetUsage(), BUF_Static) || !bDynamic;
|
|
const bool bSR = EnumHasAnyFlags(GetUsage(), BUF_ShaderResource);
|
|
|
|
check(LockStatus != ELockStatus::Unlocked);
|
|
|
|
if (LockStatus == ELockStatus::PersistentMapping)
|
|
{
|
|
// Do nothing
|
|
}
|
|
else
|
|
{
|
|
check(bStatic || bDynamic || bSR);
|
|
|
|
FVulkanPendingBufferLock PendingLock = GetPendingBufferLock(this);
|
|
|
|
RHICmdList.EnqueueLambda(TEXT("FVulkanBuffer::Unlock"), [Buffer=this, PendingLock](FRHICommandListBase& CmdList)
|
|
{
|
|
VulkanRHI::FStagingBuffer* StagingBuffer = PendingLock.StagingBuffer;
|
|
check(StagingBuffer);
|
|
StagingBuffer->FlushMappedMemory();
|
|
|
|
if (PendingLock.LockMode == RLM_ReadOnly)
|
|
{
|
|
// Just remove the staging buffer here.
|
|
Buffer->Device->GetStagingManager().ReleaseBuffer(nullptr, StagingBuffer);
|
|
}
|
|
else if (PendingLock.LockMode == RLM_WriteOnly)
|
|
{
|
|
// We need to do this on the active command buffer instead of using an upload command buffer. The high level code sometimes reuses the same
|
|
// buffer in sequences of upload / dispatch, upload / dispatch, so we need to order the copy commands correctly with respect to the dispatches.
|
|
// Unless this is the first time any data is pushed into this buffer, then ordering doesn't matter and UploadContext can be used
|
|
FVulkanContextCommon* CommonContext = nullptr;
|
|
if (PendingLock.FirstLock)
|
|
{
|
|
CommonContext = &FVulkanUploadContext::Get(CmdList);
|
|
}
|
|
else
|
|
{
|
|
CommonContext = &FVulkanCommandListContext::Get(CmdList);
|
|
}
|
|
FVulkanCommandBuffer& CommandBuffer = CommonContext->GetCommandBuffer();
|
|
check(CommandBuffer.IsOutsideRenderPass());
|
|
VkCommandBuffer CommandBufferHandle = CommandBuffer.GetHandle();
|
|
|
|
VulkanRHI::DebugHeavyWeightBarrier(CommandBufferHandle, 16);
|
|
|
|
VkBufferCopy Region;
|
|
FMemory::Memzero(Region);
|
|
Region.size = PendingLock.Size;
|
|
//Region.srcOffset = 0;
|
|
Region.dstOffset = PendingLock.Offset + Buffer->CurrentBufferAlloc.Alloc.Offset;
|
|
VulkanRHI::vkCmdCopyBuffer(CommandBufferHandle, StagingBuffer->GetHandle(), Buffer->CurrentBufferAlloc.Alloc.GetBufferHandle(), 1, &Region);
|
|
|
|
// High level code expects the data in Buffer to be ready to read
|
|
VkMemoryBarrier BarrierAfter = { VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT };
|
|
VulkanRHI::vkCmdPipelineBarrier(CommandBufferHandle, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &BarrierAfter, 0, nullptr, 0, nullptr);
|
|
|
|
Buffer->GetParent()->GetStagingManager().ReleaseBuffer(CommonContext, StagingBuffer);
|
|
}
|
|
});
|
|
}
|
|
|
|
LockStatus = ELockStatus::Unlocked;
|
|
}
|
|
|
|
void FVulkanBuffer::TakeOwnership(FVulkanBuffer& Other)
|
|
{
|
|
check(Other.LockStatus == ELockStatus::Unlocked);
|
|
check(GetParent() == Other.GetParent());
|
|
|
|
// Clean up any resource this buffer already owns
|
|
ReleaseOwnership();
|
|
|
|
// Transfer ownership of Other's resources to this instance
|
|
FRHIBuffer::TakeOwnership(Other);
|
|
|
|
BufferUsageFlags = Other.BufferUsageFlags;
|
|
CurrentBufferAlloc = Other.CurrentBufferAlloc;
|
|
|
|
Other.BufferUsageFlags = {};
|
|
Other.CurrentBufferAlloc = {};
|
|
}
|
|
|
|
void FVulkanBuffer::ReleaseOwnership()
|
|
{
|
|
check(LockStatus == ELockStatus::Unlocked);
|
|
|
|
if (CurrentBufferAlloc.Alloc.HasAllocation())
|
|
{
|
|
UpdateVulkanBufferStats(GetDesc(), CurrentBufferAlloc.Alloc.Size, false);
|
|
Device->GetMemoryManager().FreeVulkanAllocation(CurrentBufferAlloc.Alloc);
|
|
}
|
|
|
|
FRHIBuffer::ReleaseOwnership();
|
|
}
|
|
|
|
FRHIBufferInitializer FVulkanDynamicRHI::RHICreateBufferInitializer(FRHICommandListBase& RHICmdList, const FRHIBufferCreateDesc& CreateDesc)
|
|
{
|
|
#if VULKAN_USE_LLM
|
|
LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanBuffers);
|
|
#else
|
|
LLM_SCOPE(EnumHasAnyFlags(CreateDesc.Usage, EBufferUsageFlags::VertexBuffer | EBufferUsageFlags::IndexBuffer) ? ELLMTag::Meshes : ELLMTag::RHIMisc);
|
|
#endif
|
|
LLM_SCOPE_DYNAMIC_STAT_OBJECTPATH_FNAME(CreateDesc.OwnerName, ELLMTagSet::Assets);
|
|
LLM_SCOPE_DYNAMIC_STAT_OBJECTPATH_FNAME(CreateDesc.GetTraceClassName(), ELLMTagSet::AssetClasses);
|
|
UE_TRACE_METADATA_SCOPE_ASSET_FNAME(CreateDesc.DebugName, CreateDesc.GetTraceClassName(), CreateDesc.OwnerName);
|
|
|
|
FVulkanBuffer* Buffer = new FVulkanBuffer(Device, CreateDesc);
|
|
|
|
if (CreateDesc.IsNull() || CreateDesc.InitAction == ERHIBufferInitAction::Default)
|
|
{
|
|
return UE::RHICore::FDefaultBufferInitializer(RHICmdList, Buffer);
|
|
}
|
|
|
|
if (CreateDesc.InitAction == ERHIBufferInitAction::Zeroed)
|
|
{
|
|
if (void* HostPointer = Buffer->GetCurrentHostPointer())
|
|
{
|
|
FMemory::Memzero(HostPointer, CreateDesc.Size);
|
|
|
|
return UE::RHICore::FDefaultBufferInitializer(RHICmdList, Buffer);
|
|
}
|
|
|
|
return UE::RHICore::FCustomBufferInitializer(RHICmdList, Buffer, nullptr, CreateDesc.Size,
|
|
[Buffer = TRefCountPtr<FVulkanBuffer>(Buffer)](FRHICommandListBase& RHICmdList) mutable
|
|
{
|
|
FVulkanUploadContext& UploadContext = FVulkanUploadContext::Get(RHICmdList);
|
|
|
|
FVulkanCommandBuffer& CommandBuffer = UploadContext.GetCommandBuffer();
|
|
const VulkanRHI::FVulkanAllocation& Allocation = Buffer->GetCurrentAllocation();
|
|
|
|
const auto Offset = Buffer->GetCurrentAllocation().Offset;
|
|
|
|
VulkanRHI::vkCmdFillBuffer(
|
|
CommandBuffer.GetHandle(),
|
|
Allocation.GetBufferHandle(),
|
|
Allocation.Offset,
|
|
Allocation.Size,
|
|
0
|
|
);
|
|
|
|
// High level code expects the data in Buffer to be ready to read
|
|
const VkMemoryBarrier BarrierAfter =
|
|
{
|
|
VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT
|
|
};
|
|
VulkanRHI::vkCmdPipelineBarrier(
|
|
CommandBuffer.GetHandle(),
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
|
0,
|
|
1,
|
|
&BarrierAfter,
|
|
0,
|
|
nullptr,
|
|
0,
|
|
nullptr
|
|
);
|
|
|
|
return TRefCountPtr<FRHIBuffer>(MoveTemp(Buffer));
|
|
});
|
|
}
|
|
|
|
void* WritableData = nullptr;
|
|
bool bUsingLock = false;
|
|
|
|
if (void* HostPointer = Buffer->GetCurrentHostPointer())
|
|
{
|
|
WritableData = HostPointer;
|
|
Buffer->IncrementLockCounter();
|
|
}
|
|
else
|
|
{
|
|
WritableData = Buffer->Lock(RHICmdList, RLM_WriteOnly, CreateDesc.Size, 0);
|
|
bUsingLock = true;
|
|
}
|
|
|
|
if (CreateDesc.InitAction == ERHIBufferInitAction::ResourceArray)
|
|
{
|
|
check(CreateDesc.InitialData);
|
|
|
|
FMemory::Memcpy(WritableData, CreateDesc.InitialData->GetResourceData(), CreateDesc.InitialData->GetResourceDataSize());
|
|
|
|
// Discard the resource array's contents.
|
|
CreateDesc.InitialData->Discard();
|
|
|
|
if (bUsingLock)
|
|
{
|
|
Buffer->Unlock(RHICmdList);
|
|
}
|
|
|
|
return UE::RHICore::FDefaultBufferInitializer(RHICmdList, Buffer);
|
|
}
|
|
|
|
if (CreateDesc.InitAction == ERHIBufferInitAction::Initializer)
|
|
{
|
|
return UE::RHICore::FCustomBufferInitializer(RHICmdList, Buffer, WritableData, CreateDesc.Size,
|
|
[Buffer = TRefCountPtr<FVulkanBuffer>(Buffer), bUsingLock](FRHICommandListBase& RHICmdList) mutable
|
|
{
|
|
if (bUsingLock)
|
|
{
|
|
Buffer->Unlock(RHICmdList);
|
|
}
|
|
return TRefCountPtr<FRHIBuffer>(MoveTemp(Buffer));
|
|
});
|
|
}
|
|
|
|
return UE::RHICore::HandleUnknownBufferInitializerInitAction(RHICmdList, CreateDesc);
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::LockBuffer_BottomOfPipe(FRHICommandListBase& RHICmdList, FRHIBuffer* BufferRHI, uint32 Offset, uint32 Size, EResourceLockMode LockMode)
|
|
{
|
|
LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanBuffers);
|
|
FVulkanBuffer* Buffer = ResourceCast(BufferRHI);
|
|
return Buffer->Lock(RHICmdList, LockMode, Size, Offset);
|
|
}
|
|
|
|
void FVulkanDynamicRHI::UnlockBuffer_BottomOfPipe(FRHICommandListBase& RHICmdList, FRHIBuffer* BufferRHI)
|
|
{
|
|
LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanBuffers);
|
|
FVulkanBuffer* Buffer = ResourceCast(BufferRHI);
|
|
Buffer->Unlock(RHICmdList);
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHILockBuffer(FRHICommandListBase& RHICmdList, FRHIBuffer* BufferRHI, uint32 Offset, uint32 Size, EResourceLockMode LockMode)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_LockBuffer_RenderThread);
|
|
LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanBuffers);
|
|
FVulkanBuffer* Buffer = ResourceCast(BufferRHI);
|
|
return Buffer->Lock(RHICmdList, LockMode, Size, Offset);
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIUnlockBuffer(FRHICommandListBase& RHICmdList, FRHIBuffer* BufferRHI)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_UnlockBuffer_RenderThread);
|
|
LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanBuffers);
|
|
FVulkanBuffer* Buffer = ResourceCast(BufferRHI);
|
|
Buffer->Unlock(RHICmdList);
|
|
}
|
|
|
|
#if ENABLE_LOW_LEVEL_MEM_TRACKER || UE_MEMORY_TRACE_ENABLED
|
|
void FVulkanDynamicRHI::RHIUpdateAllocationTags(FRHICommandListBase& RHICmdList, FRHIBuffer* Buffer)
|
|
{
|
|
check(RHICmdList.IsBottomOfPipe());
|
|
// LLM tracking happens through LLM_TRACK_VULKAN_HIGH_LEVEL_ALLOC but the pointer used is the heap itself, where buffers are sub-allocated, so it's not trivial to
|
|
// move tags as long as we don't track the GPU VA directly
|
|
}
|
|
#endif
|
|
|