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

416 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
OpenGLVertexBuffer.cpp: OpenGL vertex buffer RHI implementation.
=============================================================================*/
#include "CoreMinimal.h"
#include "Containers/ResourceArray.h"
#include "HAL/IConsoleManager.h"
#include "HAL/LowLevelMemStats.h"
#include "ProfilingDebugging/AssetMetadataTrace.h"
#include "OpenGLDrv.h"
#include "RHICoreBufferInitializer.h"
namespace OpenGLConsoleVariables
{
#if PLATFORM_ANDROID
int32 bUseStagingBuffer = 0;
#else
int32 bUseStagingBuffer = 1;
#endif
static FAutoConsoleVariableRef CVarUseStagingBuffer(
TEXT("OpenGL.UseStagingBuffer"),
bUseStagingBuffer,
TEXT("Enables maps of dynamic vertex buffers to go to a staging buffer"),
ECVF_ReadOnly
);
extern int32 bUsePersistentMappingStagingBuffer;
};
static const uint32 MAX_ALIGNMENT_BITS = 8;
static const uint32 MAX_OFFSET_BITS = 32 - MAX_ALIGNMENT_BITS;
struct PoolAllocation
{
uint8* BasePointer;
uint32 SizeWithoutPadding;
uint32 Offset : MAX_OFFSET_BITS; // into the target buffer
uint32 AlignmentPadding : MAX_ALIGNMENT_BITS;
int32 FrameRetired;
};
static TArray<PoolAllocation*> AllocationList;
static TMap<void*,PoolAllocation*> AllocationMap;
static GLuint PoolVB = 0;
static uint8* PoolPointer = 0;
static uint32 FrameBytes = 0;
static uint32 FreeSpace = 0;
static uint32 OffsetVB = 0;
static const uint32 PerFrameMax = 1024*1024*4;
static const uint32 MaxAlignment = 1 << MAX_ALIGNMENT_BITS;
static const uint32 MaxOffset = 1 << MAX_OFFSET_BITS;
void* GetAllocation( void* Target, uint32 Size, uint32 Offset, uint32 Alignment = 16)
{
check(Alignment < MaxAlignment);
check(Offset < MaxOffset);
check(FMath::IsPowerOfTwo(Alignment));
uintptr_t AlignmentSubOne = Alignment - 1;
if (FOpenGL::SupportsBufferStorage() && OpenGLConsoleVariables::bUseStagingBuffer)
{
if (PoolVB == 0)
{
FOpenGL::GenBuffers(1, &PoolVB);
glBindBuffer(GL_COPY_READ_BUFFER, PoolVB);
FOpenGL::BufferStorage(GL_COPY_READ_BUFFER, PerFrameMax * 4, NULL, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
PoolPointer = (uint8*)FOpenGL::MapBufferRange(GL_COPY_READ_BUFFER, 0, PerFrameMax * 4, FOpenGL::EResourceLockMode::RLM_WriteOnlyPersistent);
FreeSpace = PerFrameMax * 4;
check(PoolPointer);
}
check (PoolVB);
uintptr_t AllocHeadPtr = *reinterpret_cast<const uintptr_t*>(&PoolPointer) + OffsetVB;
uint32 AlignmentPadBytes = ((AllocHeadPtr + AlignmentSubOne) & (~AlignmentSubOne)) - AllocHeadPtr;
uint32 SizeWithAlignmentPad = Size + AlignmentPadBytes;
if (SizeWithAlignmentPad > PerFrameMax - FrameBytes || SizeWithAlignmentPad > FreeSpace)
{
return nullptr;
}
if (SizeWithAlignmentPad > (PerFrameMax*4 - OffsetVB))
{
// We're wrapping, create dummy allocation and start at the begining
uint32 Leftover = PerFrameMax*4 - OffsetVB;
PoolAllocation* Alloc = new PoolAllocation;
Alloc->BasePointer = 0;
Alloc->Offset = 0;
Alloc->AlignmentPadding = 0;
Alloc->SizeWithoutPadding = Leftover;
Alloc->FrameRetired = GFrameNumberRenderThread;
AllocationList.Add(Alloc);
OffsetVB = 0;
FreeSpace -= Leftover;
AllocHeadPtr = *reinterpret_cast<const uintptr_t*>(&PoolPointer) + OffsetVB;
AlignmentPadBytes = ((AllocHeadPtr + AlignmentSubOne) & (~AlignmentSubOne)) - AllocHeadPtr;
SizeWithAlignmentPad = Size + AlignmentPadBytes;
}
//Again check if we have room
if (SizeWithAlignmentPad > FreeSpace)
{
return nullptr;
}
PoolAllocation* Alloc = new PoolAllocation;
Alloc->BasePointer = PoolPointer + OffsetVB;
Alloc->Offset = Offset;
Alloc->AlignmentPadding = AlignmentPadBytes;
Alloc->SizeWithoutPadding = Size;
Alloc->FrameRetired = -1;
AllocationList.Add(Alloc);
AllocationMap.Add(Target, Alloc);
OffsetVB += SizeWithAlignmentPad;
FreeSpace -= SizeWithAlignmentPad;
FrameBytes += SizeWithAlignmentPad;
return Alloc->BasePointer + Alloc->AlignmentPadding;
}
return nullptr;
}
bool RetireAllocation( FOpenGLBuffer* Target)
{
if (FOpenGL::SupportsBufferStorage() && OpenGLConsoleVariables::bUseStagingBuffer)
{
PoolAllocation *Alloc = 0;
if ( AllocationMap.RemoveAndCopyValue(Target, Alloc))
{
check(Alloc);
Target->Bind();
FOpenGL::CopyBufferSubData(GL_COPY_READ_BUFFER, GL_ARRAY_BUFFER, (Alloc->BasePointer + Alloc->AlignmentPadding) - PoolPointer, Alloc->Offset, Alloc->SizeWithoutPadding);
Alloc->FrameRetired = GFrameNumberRenderThread;
return true;
}
}
return false;
}
void BeginFrame_VertexBufferCleanup()
{
if (GFrameNumberRenderThread < 3)
{
return;
}
int32 NumToRetire = 0;
int32 FrameToRecover = GFrameNumberRenderThread - 3;
while (NumToRetire < AllocationList.Num())
{
PoolAllocation *Alloc = AllocationList[NumToRetire];
if (Alloc->FrameRetired < 0 || Alloc->FrameRetired > FrameToRecover)
{
break;
}
FreeSpace += (Alloc->SizeWithoutPadding + Alloc->AlignmentPadding);
delete Alloc;
NumToRetire++;
}
AllocationList.RemoveAt(0,NumToRetire);
FrameBytes = 0;
}
void FOpenGLBufferBase::Bind()
{
VERIFY_GL_SCOPE();
FOpenGLDynamicRHI::Get().CachedBindBuffer(Type, Resource);
}
void FOpenGLBufferBase::OnBufferDeletion()
{
FOpenGLDynamicRHI::Get().OnBufferDeletion(Type, Resource);
}
static GLenum GetNewBufferType(const FRHIBufferCreateDesc& CreateDesc)
{
GLenum BufferType = 0;
if (!CreateDesc.IsNull())
{
BufferType = GL_ARRAY_BUFFER;
if (EnumHasAnyFlags(CreateDesc.Usage, BUF_StructuredBuffer))
{
BufferType = GL_SHADER_STORAGE_BUFFER;
}
else if (EnumHasAnyFlags(CreateDesc.Usage, BUF_IndexBuffer))
{
BufferType = GL_ELEMENT_ARRAY_BUFFER;
}
}
return BufferType;
}
FRHIBufferInitializer FOpenGLDynamicRHI::RHICreateBufferInitializer(FRHICommandListBase& RHICmdList, const FRHIBufferCreateDesc& CreateDesc)
{
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);
const GLenum BufferType = GetNewBufferType(CreateDesc);
if (CreateDesc.IsNull())
{
FOpenGLBuffer* Buffer = new FOpenGLBuffer(&RHICmdList, BufferType, CreateDesc, nullptr);
return UE::RHICore::FDefaultBufferInitializer(RHICmdList, Buffer);
}
const void* InitialData = nullptr;
if (CreateDesc.InitAction == ERHIBufferInitAction::ResourceArray)
{
// TODO: move this initialization code path into this method
InitialData = CreateDesc.InitialData->GetResourceData();
}
FOpenGLBuffer* Buffer = new FOpenGLBuffer(&RHICmdList, BufferType, CreateDesc, InitialData);
if (CreateDesc.InitialData)
{
// Discard the resource array's contents.
CreateDesc.InitialData->Discard();
}
if (CreateDesc.InitAction == ERHIBufferInitAction::ResourceArray || CreateDesc.InitAction == ERHIBufferInitAction::Default)
{
return UE::RHICore::FDefaultBufferInitializer(RHICmdList, Buffer);
}
if (CreateDesc.InitAction == ERHIBufferInitAction::Zeroed)
{
void* WritableData = RHICmdList.LockBuffer(Buffer, 0, CreateDesc.Size, RLM_WriteOnly);
FMemory::Memzero(WritableData, CreateDesc.Size);
RHICmdList.UnlockBuffer(Buffer);
return UE::RHICore::FDefaultBufferInitializer(RHICmdList, Buffer);
}
if (CreateDesc.InitAction == ERHIBufferInitAction::Initializer)
{
// Use LockBuffer + UnlockBuffer to allow the caller to write initial buffer data
return UE::RHICore::FLockBufferInitializer(RHICmdList, Buffer);
}
return UE::RHICore::HandleUnknownBufferInitializerInitAction(RHICmdList, CreateDesc);
}
void* FOpenGLDynamicRHI::LockBuffer_BottomOfPipe(FRHICommandListBase& RHICmdList, FRHIBuffer* BufferRHI, uint32 Offset, uint32 Size, EResourceLockMode LockMode)
{
check(Size > 0);
VERIFY_GL_SCOPE();
FOpenGLBuffer* Buffer = ResourceCast(BufferRHI);
if (Buffer->IsDynamic() && LockMode == EResourceLockMode::RLM_WriteOnly)
{
void *Staging = GetAllocation(Buffer, Size, Offset);
if (Staging)
{
return Staging;
}
}
const bool bReadOnly = (LockMode == EResourceLockMode::RLM_ReadOnly);
const bool bDiscard = !bReadOnly; // Always use 'orphaning' on write as buffer could be in use by GPU atm
return (void*)Buffer->Lock(Offset, Size, bReadOnly, bDiscard);
}
void FOpenGLDynamicRHI::UnlockBuffer_BottomOfPipe(FRHICommandListBase& RHICmdList, FRHIBuffer* BufferRHI)
{
VERIFY_GL_SCOPE();
FOpenGLBuffer* Buffer = ResourceCast(BufferRHI);
if (!RetireAllocation(Buffer))
{
Buffer->Unlock();
}
}
void FOpenGLDynamicRHI::RHICopyBufferRegion(FRHIBuffer* DestBufferRHI, uint64 DstOffset, FRHIBuffer* SourceBufferRHI, uint64 SrcOffset, uint64 NumBytes)
{
VERIFY_GL_SCOPE();
FOpenGLBuffer* SourceBuffer = ResourceCast(SourceBufferRHI);
FOpenGLBuffer* DestBuffer = ResourceCast(DestBufferRHI);
glBindBuffer(GL_COPY_READ_BUFFER, SourceBuffer->Resource);
glBindBuffer(GL_COPY_WRITE_BUFFER, DestBuffer->Resource);
FOpenGL::CopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, SrcOffset, DstOffset, NumBytes);
glBindBuffer(GL_COPY_READ_BUFFER, 0);
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
}
FStagingBufferRHIRef FOpenGLDynamicRHI::RHICreateStagingBuffer()
{
return new FOpenGLStagingBuffer();
}
void FOpenGLStagingBuffer::Initialize()
{
ShadowBuffer = 0;
ShadowSize = 0;
Mapping = nullptr;
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&)
{
VERIFY_GL_SCOPE();
glGenBuffers(1, &ShadowBuffer);
});
RHITHREAD_GLTRACE_BLOCKING;
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
FOpenGLStagingBuffer::~FOpenGLStagingBuffer()
{
VERIFY_GL_SCOPE();
glDeleteBuffers(1, &ShadowBuffer);
}
// If we do not support the BufferStorage extension or if PersistentMapping is set to false, this will send the command to the RHI and flush it
// If we do support BufferStorage extension and PersistentMapping is set to true, we just return the pointer + offset
void* FOpenGLStagingBuffer::Lock(uint32 Offset, uint32 NumBytes)
{
if (!FOpenGL::SupportsBufferStorage() || !OpenGLConsoleVariables::bUsePersistentMappingStagingBuffer)
{
void* ReturnValue = nullptr;
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&) {
VERIFY_GL_SCOPE();
check(ShadowBuffer != 0);
glBindBuffer(GL_COPY_WRITE_BUFFER, ShadowBuffer);
void* LocalMapping = FOpenGL::MapBufferRange(GL_COPY_WRITE_BUFFER, 0, NumBytes, FOpenGL::EResourceLockMode::RLM_ReadOnly);
check(LocalMapping);
ReturnValue = reinterpret_cast<uint8*>(LocalMapping) + Offset;
});
RHITHREAD_GLTRACE_BLOCKING;
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
return ReturnValue;
}
else
{
check(Mapping != nullptr);
return reinterpret_cast<uint8*>(Mapping) + Offset;
}
}
// If we do not support the BufferStorage extension or if PersistentMapping is set to false, this will send the command to the RHI and flush it
// If we do support BufferStorage extension and PersistentMapping is set to true, we do nothing
void FOpenGLStagingBuffer::Unlock()
{
if (!FOpenGL::SupportsBufferStorage() || !OpenGLConsoleVariables::bUsePersistentMappingStagingBuffer)
{
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&) {
FOpenGL::UnmapBuffer(GL_COPY_WRITE_BUFFER);
Mapping = nullptr;
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
});
}
}
void* FOpenGLDynamicRHI::RHILockStagingBuffer(FRHIStagingBuffer* StagingBuffer, FRHIGPUFence* Fence, uint32 Offset, uint32 SizeRHI)
{
FOpenGLStagingBuffer* Buffer = ResourceCast(StagingBuffer);
return Buffer->Lock(Offset, SizeRHI);
}
void FOpenGLDynamicRHI::RHIUnlockStagingBuffer(FRHIStagingBuffer* StagingBuffer)
{
FOpenGLStagingBuffer* Buffer = ResourceCast(StagingBuffer);
Buffer->Unlock();
}
void* FOpenGLDynamicRHI::LockStagingBuffer_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHIStagingBuffer* StagingBuffer, FRHIGPUFence* Fence, uint32 Offset, uint32 SizeRHI)
{
check(IsInRenderingThread());
if (!Fence || !Fence->Poll() || Fence->NumPendingWriteCommands.GetValue() != 0)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FDynamicRHI_LockStagingBuffer_Flush);
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FDynamicRHI_LockStagingBuffer_RenderThread);
return GDynamicRHI->RHILockStagingBuffer(StagingBuffer, Fence, Offset, SizeRHI);
}
}
void FOpenGLDynamicRHI::UnlockStagingBuffer_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHIStagingBuffer* StagingBuffer)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FDynamicRHI_UnlockStagingBuffer_RenderThread);
check(IsInRenderingThread());
GDynamicRHI->RHIUnlockStagingBuffer(StagingBuffer);
}
#if ENABLE_LOW_LEVEL_MEM_TRACKER || UE_MEMORY_TRACE_ENABLED
void FOpenGLDynamicRHI::RHIUpdateAllocationTags(FRHICommandListBase& RHICmdList, FRHIBuffer* Buffer)
{
}
#endif