Files
UnrealEngine/Engine/Plugins/Media/MediaIOFramework/Source/GPUTextureTransfer/Private/GPUTextureTransferModule.cpp
2025-05-18 13:04:45 +08:00

397 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GPUTextureTransferModule.h"
#if DVP_SUPPORTED_PLATFORM
#include "D3D11TextureTransfer.h"
#include "D3D12TextureTransfer.h"
#include "RenderingThread.h"
#include "VulkanTextureTransfer.h"
#include "TextureTransferBase.h"
#endif
#if DVP_SUPPORTED_PLATFORM || PLATFORM_LINUX
#define VULKAN_PLATFORM 1
#else
#define VULKAN_PLATFORM 0
#endif
#if VULKAN_PLATFORM
#include "IVulkanDynamicRHI.h"
#endif
#include "CoreMinimal.h"
#include "GPUTextureTransfer.h"
#include "GenericPlatform/GenericPlatformDriver.h"
#include "HAL/PlatformMisc.h"
#include "Misc/App.h"
#include "Misc/CoreDelegates.h"
#include "Modules/ModuleManager.h"
#include <unordered_map>
DEFINE_LOG_CATEGORY(LogGPUTextureTransfer);
static TAutoConsoleVariable<bool> CVarMediaIOEnableGPUDirect(
TEXT("MediaIO.EnableGPUDirect"), false,
TEXT("Whether to enable GPU direct for faster video frame copies. (Experimental)"),
ECVF_RenderThreadSafe);
namespace
{
auto ConvertRHI = [](ERHIInterfaceType RHI)
{
switch (RHI)
{
case ERHIInterfaceType::D3D11: return UE::GPUTextureTransfer::ERHI::D3D11;
case ERHIInterfaceType::D3D12: return UE::GPUTextureTransfer::ERHI::D3D12;
case ERHIInterfaceType::Vulkan: return UE::GPUTextureTransfer::ERHI::Vulkan;
default: return UE::GPUTextureTransfer::ERHI::Invalid;
}
};
}
bool FGPUTextureTransferModule::IsAvailable()
{
#if DVP_SUPPORTED_PLATFORM
return FGPUTextureTransferModule::Get().IsInitialized() && FGPUTextureTransferModule::Get().IsEnabled();
#else
return false;
#endif
}
void FGPUTextureTransferModule::StartupModule()
{
if (CVarMediaIOEnableGPUDirect.GetValueOnAnyThread())
{
FCoreDelegates::OnAllModuleLoadingPhasesComplete.AddRaw(this, &FGPUTextureTransferModule::Initialize);
}
CVarMediaIOEnableGPUDirect.AsVariable()->OnChangedDelegate().AddRaw(this, &FGPUTextureTransferModule::OnEnableGPUDirectCVarChange);
// Cache this information since GetGPUDriverInfo has to be called on game thread because of a call to GetValueOnGameThread.
CachedDriverInfo = FPlatformMisc::GetGPUDriverInfo(GRHIAdapterName);
}
void FGPUTextureTransferModule::ShutdownModule()
{
if (bInitialized)
{
UninitializeTextureTransfer();
}
}
void FGPUTextureTransferModule::Initialize()
{
if (FApp::CanEverRender())
{
if (LoadGPUDirectBinary())
{
// Always provide the necessary Vulkan extensions (it will just get ignored if a different RHI is used)
{
#if DVP_SUPPORTED_PLATFORM
const TArray<const ANSICHAR*> ExtentionsToAdd{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME, VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME };
#elif PLATFORM_LINUX
const TArray<const ANSICHAR*> ExtentionsToAdd{ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_SURFACE_EXTENSION_NAME };
#endif
#if VULKAN_PLATFORM
IVulkanDynamicRHI::AddEnabledDeviceExtensionsAndLayers(ExtentionsToAdd, TArray<const ANSICHAR*>());
#endif
}
TransferObjects.AddDefaulted(RHI_MAX);
InitializeTextureTransfer();
}
}
}
UE::GPUTextureTransfer::TextureTransferPtr FGPUTextureTransferModule::GetTextureTransfer()
{
#if DVP_SUPPORTED_PLATFORM
if (!IsInitialized())
{
UE_LOG(LogGPUTextureTransfer, Warning, TEXT("GetTextureTransfer was called without initializing the library. This will cause a hitch since we have to block while waiting for the library to finish initializing."));
Initialize();
// Initialization is done on the rendering thread.
FlushRenderingCommands();
}
#endif
if (FApp::CanEverRender())
{
#if DVP_SUPPORTED_PLATFORM
UE::GPUTextureTransfer::ERHI SupportedRHI = ConvertRHI(RHIGetInterfaceType());
if (SupportedRHI == UE::GPUTextureTransfer::ERHI::Invalid)
{
UE_LOG(LogGPUTextureTransfer, Error, TEXT("The current RHI is not supported with GPU Texture Transfer."));
return nullptr;
}
const uint8 RHIIndex = static_cast<uint8>(SupportedRHI);
if (TransferObjects[RHIIndex])
{
return TransferObjects[RHIIndex];
}
#endif
}
return nullptr;
}
bool FGPUTextureTransferModule::IsInitialized() const
{
#if DVP_SUPPORTED_PLATFORM
return bInitialized;
#else
return false;
#endif
}
bool FGPUTextureTransferModule::IsEnabled() const
{
return CVarMediaIOEnableGPUDirect.GetValueOnAnyThread();
}
FGPUTextureTransferModule& FGPUTextureTransferModule::Get()
{
return FModuleManager::LoadModuleChecked<FGPUTextureTransferModule>("GPUTextureTransfer");
}
bool FGPUTextureTransferModule::LoadGPUDirectBinary()
{
#if DVP_SUPPORTED_PLATFORM
FString GPUDirectPath = FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries/ThirdParty/NVIDIA/GPUDirect"), FPlatformProcess::GetBinariesSubdirectory());
FPlatformProcess::PushDllDirectory(*GPUDirectPath);
FString DVPDll;
DVPDll = TEXT("dvp.dll");
DVPDll = FPaths::Combine(GPUDirectPath, DVPDll);
TextureTransferHandle = FPlatformProcess::GetDllHandle(*DVPDll);
if (TextureTransferHandle == nullptr)
{
UE_LOG(LogGPUTextureTransfer, Display, TEXT("Failed to load required library %s. GPU Texture transfer will not be functional."), *DVPDll);
}
FPlatformProcess::PopDllDirectory(*GPUDirectPath);
#endif
return !!TextureTransferHandle;
}
void FGPUTextureTransferModule::InitializeTextureTransfer()
{
#if DVP_SUPPORTED_PLATFORM
bool bInitializationSuccess = true;
static const TArray<FString> SupportedGPUPrefixes = {
TEXT("RTX A4"),
TEXT("RTX A5"),
TEXT("RTX A6"),
TEXT("Quadro")
};
bInitializationSuccess = CachedDriverInfo.IsNVIDIA() && !CachedDriverInfo.DeviceDescription.Contains(TEXT("Tesla"));
const bool bRenderDocAttached = FParse::Param(FCommandLine::Get(), TEXT("AttachRenderDoc"));
if (bRenderDocAttached)
{
bInitializationSuccess = false;
// Render doc clashes with GPU Direct.
UE_LOG(LogGPUTextureTransfer, Display, TEXT("GPU Texture Transfer disabled because render is attached."))
}
if (bInitializationSuccess)
{
bInitializationSuccess = false;
for (const FString& GPUPrefix : SupportedGPUPrefixes)
{
if (CachedDriverInfo.DeviceDescription.Contains(GPUPrefix))
{
bInitializationSuccess = true;
break;
}
}
}
if (!bInitializationSuccess)
{
return;
}
if (!GDynamicRHI)
{
return;
}
ENQUEUE_RENDER_COMMAND(InitializeTextureTransfer)([this](FRHICommandListImmediate&)
{
UE::GPUTextureTransfer::TextureTransferPtr TextureTransfer;
UE::GPUTextureTransfer::ERHI RHI = ConvertRHI(RHIGetInterfaceType());
switch (RHI)
{
case UE::GPUTextureTransfer::ERHI::D3D11:
TextureTransfer = MakeShared<UE::GPUTextureTransfer::Private::FD3D11TextureTransfer>();
break;
case UE::GPUTextureTransfer::ERHI::D3D12:
TextureTransfer = MakeShared<UE::GPUTextureTransfer::Private::FD3D12TextureTransfer>();
break;
case UE::GPUTextureTransfer::ERHI::Vulkan:
TextureTransfer = MakeShared<UE::GPUTextureTransfer::Private::FVulkanTextureTransfer>();
break;
default:
ensureAlways(false);
break;
}
UE::GPUTextureTransfer::FInitializeDMAArgs InitializeArgs;
InitializeArgs.RHI = RHI;
InitializeArgs.RHIDevice = GDynamicRHI->RHIGetNativeDevice();
InitializeArgs.RHICommandQueue = GDynamicRHI->RHIGetNativeGraphicsQueue();
#if VULKAN_PLATFORM
if (RHI == UE::GPUTextureTransfer::ERHI::Vulkan)
{
IVulkanDynamicRHI* DynRHI = GetIVulkanDynamicRHI();
InitializeArgs.VulkanInstance = DynRHI->RHIGetVkInstance();
FMemory::Memcpy(InitializeArgs.RHIDeviceUUID, DynRHI->RHIGetVulkanDeviceUUID(), 16);
}
#endif
const uint8 RHIIndex = static_cast<uint8>(RHI);
UE_LOG(LogGPUTextureTransfer, Display, TEXT("Initializing GPU Texture transfer"));
if (TextureTransfer->Initialize(InitializeArgs))
{
TransferObjects[RHIIndex] = TextureTransfer;
}
bInitialized = true;
});
#endif // DVP_SUPPORTED_PLATFORM
}
void FGPUTextureTransferModule::UninitializeTextureTransfer()
{
#if DVP_SUPPORTED_PLATFORM
for (uint8 RhiIt = 1; RhiIt < RHI_MAX; RhiIt++)
{
if (const UE::GPUTextureTransfer::TextureTransferPtr& TextureTransfer = TransferObjects[RhiIt])
{
TextureTransfer->Uninitialize();
}
}
#endif
}
void FGPUTextureTransferModule::OnEnableGPUDirectCVarChange(IConsoleVariable* ConsoleVariable)
{
if (ConsoleVariable->GetBool() && !IsInitialized())
{
Initialize();
}
}
namespace UE::GPUTextureTransfer
{
struct FTextureTransfersWrapper
{
static constexpr uint8_t RHI_MAX = static_cast<uint8_t>(ERHI::RHI_MAX);
TArray<ITextureTransfer*> Transfers;
FTextureTransfersWrapper()
{
Transfers.SetNumUninitialized(RHI_MAX);
for (uint8_t RhiIt = 0; RhiIt < RHI_MAX; RhiIt++)
{
Transfers[RhiIt] = nullptr;
}
}
~FTextureTransfersWrapper()
{
// 0 is Invalid RHI
for (uint8_t RhiIt = 1; RhiIt < RHI_MAX; RhiIt++)
{
ITextureTransfer* TextureTransfer = Transfers[RhiIt];
if (TextureTransfer)
{
TextureTransfer->Uninitialize();
delete TextureTransfer;
Transfers[RhiIt] = nullptr;
}
}
}
void CleanupTextureTransfer(ITextureTransfer* TextureTransfer)
{
if (TextureTransfer)
{
for (uint8_t RhiIt = 1; RhiIt < RHI_MAX; RhiIt++)
{
if (Transfers[RhiIt] && Transfers[RhiIt] == TextureTransfer)
{
Transfers[RhiIt]->Uninitialize();
delete Transfers[RhiIt];
Transfers[RhiIt] = nullptr;
}
}
}
}
} TextureTransfersWrapper;
ITextureTransfer* GetTextureTransfer(const UE::GPUTextureTransfer::FInitializeDMAArgs& Args)
{
#if DVP_SUPPORTED_PLATFORM
const uint8_t RHIIndex = static_cast<uint8_t>(Args.RHI);
if (TextureTransfersWrapper.Transfers[RHIIndex])
{
return TextureTransfersWrapper.Transfers[RHIIndex];
}
ITextureTransfer* TextureTransfer = nullptr;
switch (Args.RHI)
{
case ERHI::D3D11:
TextureTransfer = new Private::FD3D11TextureTransfer();
break;
case ERHI::D3D12:
TextureTransfer = new Private::FD3D12TextureTransfer();
break;
default:
return nullptr;
}
if ((Private::FTextureTransferBase*)(TextureTransfer)->Initialize(Args))
{
TextureTransfersWrapper.Transfers[RHIIndex] = TextureTransfer;
}
else
{
delete TextureTransfer;
}
return TextureTransfersWrapper.Transfers[RHIIndex];
#else
return nullptr;
#endif
}
void CleanupTextureTransfer(ITextureTransfer* TextureTransfer)
{
TextureTransfersWrapper.CleanupTextureTransfer(TextureTransfer);
}
}
IMPLEMENT_MODULE(FGPUTextureTransferModule, GPUTextureTransfer);