2400 lines
87 KiB
C++
2400 lines
87 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
VulkanRHI.cpp: Vulkan device RHI implementation.
|
|
=============================================================================*/
|
|
|
|
#include "VulkanRHIPrivate.h"
|
|
#include "BuildSettings.h"
|
|
#include "HardwareInfo.h"
|
|
#include "VulkanShaderResources.h"
|
|
#include "VulkanResources.h"
|
|
#include "VulkanPendingState.h"
|
|
#include "VulkanContext.h"
|
|
#include "VulkanBarriers.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "GenericPlatform/GenericPlatformDriver.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "VulkanPipelineState.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "VulkanLLM.h"
|
|
#include "Misc/EngineVersion.h"
|
|
#include "GlobalShader.h"
|
|
#include "VulkanResourceCollection.h"
|
|
#include "RHIValidation.h"
|
|
#include "RHIUtilities.h"
|
|
#include "ShaderDiagnostics.h"
|
|
#include "IHeadMountedDisplayModule.h"
|
|
#include "VulkanRenderpass.h"
|
|
#include "VulkanTransientResourceAllocator.h"
|
|
#include "VulkanExtensions.h"
|
|
#include "VulkanRayTracing.h"
|
|
#include "VulkanChunkedPipelineCache.h"
|
|
#if PLATFORM_ANDROID
|
|
#include "Android/AndroidPlatformMisc.h"
|
|
#endif
|
|
|
|
// Use Vulkan Profiles to verify feature level support on startup
|
|
void VulkanProfilePrint(const char* Msg)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT(" - %s"), ANSI_TO_TCHAR(Msg));
|
|
}
|
|
#define VP_DEBUG_MESSAGE_CALLBACK VulkanProfilePrint
|
|
#include "vulkan_profiles_ue.h"
|
|
#undef VP_DEBUG_MESSAGE_CALLBACK
|
|
|
|
|
|
static_assert(sizeof(VkStructureType) == sizeof(int32), "ZeroVulkanStruct() assumes VkStructureType is int32!");
|
|
|
|
TAtomic<uint64> GVulkanBufferHandleIdCounter{ 0 };
|
|
TAtomic<uint64> GVulkanBufferViewHandleIdCounter{ 0 };
|
|
TAtomic<uint64> GVulkanImageViewHandleIdCounter{ 0 };
|
|
TAtomic<uint64> GVulkanSamplerHandleIdCounter{ 0 };
|
|
TAtomic<uint64> GVulkanDSetLayoutHandleIdCounter{ 0 };
|
|
|
|
#if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT
|
|
#include "IHeadMountedDisplayModule.h"
|
|
#endif
|
|
|
|
#define LOCTEXT_NAMESPACE "VulkanRHI"
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
TAutoConsoleVariable<int32> GRHIThreadCvar(
|
|
TEXT("r.Vulkan.RHIThread"),
|
|
2,
|
|
TEXT("0 to only use Render Thread\n")
|
|
TEXT("1 to use ONE RHI Thread\n")
|
|
TEXT("2 to use multiple RHI Thread\n")
|
|
);
|
|
|
|
int32 GVulkanInputAttachmentShaderRead = 0;
|
|
static FAutoConsoleVariableRef GCVarInputAttachmentShaderRead(
|
|
TEXT("r.Vulkan.InputAttachmentShaderRead"),
|
|
GVulkanInputAttachmentShaderRead,
|
|
TEXT("Whether to use VK_ACCESS_SHADER_READ_BIT an input attachments to workaround rendering issues\n")
|
|
TEXT("0 use: VK_ACCESS_INPUT_ATTACHMENT_READ_BIT (default)\n")
|
|
TEXT("1 use: VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT\n"),
|
|
ECVF_ReadOnly
|
|
);
|
|
|
|
int32 GVulkanEnableTransientResourceAllocator = 1;
|
|
static FAutoConsoleVariableRef GCVarEnableTransientResourceAllocator(
|
|
TEXT("r.Vulkan.EnableTransientResourceAllocator"),
|
|
GVulkanEnableTransientResourceAllocator,
|
|
TEXT("Whether to enable the TransientResourceAllocator to reduce memory usage\n")
|
|
TEXT("0 to disabled (default)\n")
|
|
TEXT("1 to enable\n"),
|
|
ECVF_ReadOnly
|
|
);
|
|
|
|
static TAutoConsoleVariable<bool> CVarAllowVulkanPSOPrecache(
|
|
TEXT("r.Vulkan.AllowPSOPrecaching"),
|
|
true,
|
|
TEXT("true: if r.PSOPrecaching=1 Vulkan RHI will use precaching. (default)\n")
|
|
TEXT("false: Vulkan RHI will disable precaching (even if r.PSOPrecaching=1)."),
|
|
ECVF_RenderThreadSafe | ECVF_ReadOnly);
|
|
|
|
// If precaching is active we should not need the file cache.
|
|
// however, precaching and filecache are compatible with each other, there maybe some scenarios in which both could be used.
|
|
static TAutoConsoleVariable<bool> CVarEnableVulkanPSOFileCacheWhenPrecachingActive(
|
|
TEXT("r.Vulkan.EnablePSOFileCacheWhenPrecachingActive"),
|
|
false,
|
|
TEXT("false: If precaching is available (r.PSOPrecaching=1, r.Vulkan.UseChunkedPSOCache=1) then disable the PSO filecache. (default)\n")
|
|
TEXT("true: Allow both PSO file cache and precaching."),
|
|
ECVF_RenderThreadSafe | ECVF_ReadOnly);
|
|
|
|
int32 GVulkanAMDCompatibilityMode = 1;
|
|
static FAutoConsoleVariableRef GCVarVulkanAMDCompatibilityMode(
|
|
TEXT("r.Vulkan.AMDCompatibilityMode"),
|
|
GVulkanAMDCompatibilityMode,
|
|
TEXT("Used to tweak enabled Vulkan feature set in order to ensure wider compatibility with all AMD GPUs on all platforms. (default:1)"),
|
|
ECVF_ReadOnly
|
|
);
|
|
|
|
|
|
extern TAutoConsoleVariable<int32> GVulkanRayTracingCVar;
|
|
|
|
// All shader stages supported by VK device - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, FRAGMENT etc
|
|
uint32 GVulkanDevicePipelineStageBits = 0;
|
|
|
|
DEFINE_LOG_CATEGORY(LogVulkan)
|
|
|
|
// Selects the device to us for the provided instance
|
|
static VkPhysicalDevice SelectPhysicalDevice(VkInstance InInstance)
|
|
{
|
|
VkResult Result;
|
|
|
|
uint32 PhysicalDeviceCount = 0;
|
|
Result = VulkanRHI::vkEnumeratePhysicalDevices(InInstance, &PhysicalDeviceCount, nullptr);
|
|
if ((Result != VK_SUCCESS) || (PhysicalDeviceCount == 0))
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log,
|
|
TEXT("SelectPhysicalDevice could not find a compatible Vulkan device or driver (EnumeratePhysicalDevices returned '%s' and %d devices). ")
|
|
TEXT("Make sure your video card supports Vulkan and try updating your video driver to a more recent version (proceed with any pending reboots).")
|
|
, VK_TYPE_TO_STRING(VkResult, Result), PhysicalDeviceCount
|
|
);
|
|
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
TArray<VkPhysicalDevice> PhysicalDevices;
|
|
PhysicalDevices.AddZeroed(PhysicalDeviceCount);
|
|
VERIFYVULKANRESULT(VulkanRHI::vkEnumeratePhysicalDevices(InInstance, &PhysicalDeviceCount, PhysicalDevices.GetData()));
|
|
checkf(PhysicalDeviceCount >= 1, TEXT("Couldn't enumerate physical devices on second attempt! Make sure your drivers are up to date and that you are not pending a reboot."));
|
|
|
|
struct FPhysicalDeviceInfo
|
|
{
|
|
FPhysicalDeviceInfo() = delete;
|
|
FPhysicalDeviceInfo(uint32 InOriginalIndex, VkPhysicalDevice InPhysicalDevice)
|
|
: OriginalIndex(InOriginalIndex)
|
|
, PhysicalDevice(InPhysicalDevice)
|
|
{
|
|
ZeroVulkanStruct(PhysicalDeviceProperties2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2);
|
|
ZeroVulkanStruct(PhysicalDeviceIDProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES);
|
|
PhysicalDeviceProperties2.pNext = &PhysicalDeviceIDProperties;
|
|
VulkanRHI::vkGetPhysicalDeviceProperties2(PhysicalDevice, &PhysicalDeviceProperties2);
|
|
}
|
|
|
|
uint32 OriginalIndex;
|
|
VkPhysicalDevice PhysicalDevice;
|
|
VkPhysicalDeviceProperties2 PhysicalDeviceProperties2;
|
|
VkPhysicalDeviceIDProperties PhysicalDeviceIDProperties;
|
|
};
|
|
TArray<FPhysicalDeviceInfo> PhysicalDeviceInfos;
|
|
PhysicalDeviceInfos.Reserve(PhysicalDeviceCount);
|
|
|
|
// Fill the array with each devices properties
|
|
for (uint32 Index = 0; Index < PhysicalDeviceCount; ++Index)
|
|
{
|
|
PhysicalDeviceInfos.Emplace(Index, PhysicalDevices[Index]);
|
|
}
|
|
|
|
// Allow HMD to override which graphics adapter is chosen, so we pick the adapter where the HMD is connected
|
|
#if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT
|
|
if (IHeadMountedDisplayModule::IsAvailable())
|
|
{
|
|
static_assert(sizeof(uint64) == VK_LUID_SIZE);
|
|
const uint64 HmdGraphicsAdapterLuid = IHeadMountedDisplayModule::Get().GetGraphicsAdapterLuid();
|
|
|
|
for (int32 Index = 0; Index < PhysicalDeviceInfos.Num(); ++Index)
|
|
{
|
|
if (FMemory::Memcmp(&HmdGraphicsAdapterLuid, &PhysicalDeviceInfos[Index].PhysicalDeviceIDProperties.deviceLUID, VK_LUID_SIZE_KHR) == 0)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("HMD device at index %d of %u being used as default..."), Index, PhysicalDeviceCount);
|
|
return PhysicalDeviceInfos[Index].PhysicalDevice;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Use the device as forced by CVar or CommandLine arg
|
|
auto* CVarGraphicsAdapter = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GraphicsAdapter"));
|
|
int32 ExplicitAdapterValue = CVarGraphicsAdapter ? CVarGraphicsAdapter->GetValueOnAnyThread() : -1;
|
|
const bool bUsingCmdLine = FParse::Value(FCommandLine::Get(), TEXT("graphicsadapter="), ExplicitAdapterValue);
|
|
const TCHAR* GraphicsAdapterOriginTxt = bUsingCmdLine ? TEXT("command line") : TEXT("'r.GraphicsAdapter'");
|
|
if (ExplicitAdapterValue >= 0) // Use adapter at the specified index
|
|
{
|
|
if (ExplicitAdapterValue >= PhysicalDeviceInfos.Num())
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Tried to use graphics adapter at index %d as specified by %s, but only %d Adapter(s) found. Falling back to first device..."),
|
|
ExplicitAdapterValue, GraphicsAdapterOriginTxt, PhysicalDeviceInfos.Num());
|
|
ExplicitAdapterValue = 0;
|
|
}
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Using device at index %d of %u as specfified by %s..."), ExplicitAdapterValue, PhysicalDeviceCount, GraphicsAdapterOriginTxt);
|
|
return PhysicalDeviceInfos[ExplicitAdapterValue].PhysicalDevice;
|
|
}
|
|
else if (ExplicitAdapterValue == -2) // Take the first one that fulfills the criteria
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Using first device (of %u) without any sorting as specfified by %s..."), PhysicalDeviceCount, GraphicsAdapterOriginTxt);
|
|
return PhysicalDeviceInfos[0].PhysicalDevice;
|
|
}
|
|
else if (ExplicitAdapterValue == -1) // Favour non integrated because there are usually faster
|
|
{
|
|
// Reoreder the list to place discrete adapters first
|
|
PhysicalDeviceInfos.Sort([](const FPhysicalDeviceInfo& Lhs, const FPhysicalDeviceInfo& Rhs)
|
|
{
|
|
// For devices of the same type, jsut keep the original order
|
|
if (Lhs.PhysicalDeviceProperties2.properties.deviceType == Rhs.PhysicalDeviceProperties2.properties.deviceType)
|
|
{
|
|
return Lhs.OriginalIndex < Rhs.OriginalIndex;
|
|
}
|
|
|
|
// Prefer discrete GPUs first, then integrated, then CPU
|
|
return (Lhs.PhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) ||
|
|
(Rhs.PhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU);
|
|
});
|
|
}
|
|
|
|
|
|
// If a preferred vendor is specified, return the first device from that vendor
|
|
const EGpuVendorId PreferredVendor = RHIGetPreferredAdapterVendor();
|
|
if (PreferredVendor != EGpuVendorId::Unknown)
|
|
{
|
|
// Check for preferred
|
|
for (int32 Index = 0; Index < PhysicalDeviceInfos.Num(); ++Index)
|
|
{
|
|
if (RHIConvertToGpuVendorId(PhysicalDeviceInfos[Index].PhysicalDeviceProperties2.properties.vendorID) == PreferredVendor)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Using preferred vendor device at index %d of %u..."), Index, PhysicalDeviceCount);
|
|
return PhysicalDeviceInfos[Index].PhysicalDevice;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip all CPU devices if they aren't permitted
|
|
const bool bAllowCPUDevices = FParse::Param(FCommandLine::Get(), TEXT("AllowCPUDevices"));
|
|
for (int32 Index = 0; Index < PhysicalDeviceInfos.Num(); ++Index)
|
|
{
|
|
if (!bAllowCPUDevices && (PhysicalDeviceInfos[Index].PhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return PhysicalDeviceInfos[Index].PhysicalDevice;
|
|
}
|
|
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("None of the %u devices meet all the criteria!"), PhysicalDeviceCount);
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
static uint32 GetVulkanApiVersionForFeatureLevel(ERHIFeatureLevel::Type FeatureLevel, bool bRaytracing)
|
|
{
|
|
const FString ProfileName = FVulkanPlatform::GetVulkanProfileNameForFeatureLevel(FeatureLevel, bRaytracing);
|
|
VpProfileProperties ProfileProperties;
|
|
FMemory::Memzero(ProfileProperties);
|
|
FCStringAnsi::Strncpy(ProfileProperties.profileName, TCHAR_TO_ANSI(*ProfileName), VP_MAX_PROFILE_NAME_SIZE);
|
|
|
|
const uint32 minApiVersion = vpGetProfileAPIVersion(&ProfileProperties);
|
|
if (minApiVersion)
|
|
{
|
|
return minApiVersion;
|
|
}
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Using default apiVersion for platform..."));
|
|
return UE_VK_API_VERSION;
|
|
}
|
|
|
|
// Returns the API version for the provided feautre level, returns 0 if not supported
|
|
static bool CheckVulkanProfile(ERHIFeatureLevel::Type FeatureLevel, bool bRaytracing)
|
|
{
|
|
const FString ProfileName = FVulkanPlatform::GetVulkanProfileNameForFeatureLevel(FeatureLevel, bRaytracing);
|
|
|
|
if (!FVulkanGenericPlatform::SupportsProfileChecks())
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Skipping Vulkan Profile check for %s:"), *ProfileName);
|
|
return true;
|
|
}
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Starting Vulkan Profile check for %s:"), *ProfileName);
|
|
ON_SCOPE_EXIT
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Vulkan Profile check complete."));
|
|
};
|
|
|
|
VpProfileProperties ProfileProperties;
|
|
FMemory::Memzero(ProfileProperties);
|
|
FCStringAnsi::Strncpy(ProfileProperties.profileName, TCHAR_TO_ANSI(*ProfileName), VP_MAX_PROFILE_NAME_SIZE);
|
|
|
|
VkBool32 bInstanceSupported = VK_FALSE;
|
|
VkResult InstanceResult = vpGetInstanceProfileSupport(nullptr, &ProfileProperties, &bInstanceSupported); // :todo-jn: no VERIFYVULKANRESULT, this can fail and it's fine
|
|
if ((InstanceResult == VK_SUCCESS) && bInstanceSupported)
|
|
{
|
|
VkInstanceCreateInfo InstanceCreateInfo;
|
|
ZeroVulkanStruct(InstanceCreateInfo, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
|
|
|
|
VpInstanceCreateInfo ProfileInstanceCreateInfo;
|
|
FMemory::Memzero(ProfileInstanceCreateInfo);
|
|
ProfileInstanceCreateInfo.enabledFullProfileCount = 1;
|
|
ProfileInstanceCreateInfo.pEnabledFullProfiles = &ProfileProperties;
|
|
ProfileInstanceCreateInfo.pCreateInfo = &InstanceCreateInfo;
|
|
|
|
VkInstance TempInstance = VK_NULL_HANDLE;
|
|
VERIFYVULKANRESULT(vpCreateInstance(&ProfileInstanceCreateInfo, VULKAN_CPU_ALLOCATOR, &TempInstance));
|
|
|
|
// Use FVulkanGenericPlatform on purpose here, we only want basic common functionality (no platform specific stuff)
|
|
FVulkanGenericPlatform::LoadVulkanInstanceFunctions(TempInstance);
|
|
|
|
ON_SCOPE_EXIT
|
|
{
|
|
// Keep nothing around from the temporary instance we created
|
|
if (TempInstance)
|
|
{
|
|
VulkanRHI::vkDestroyInstance(TempInstance, VULKAN_CPU_ALLOCATOR);
|
|
TempInstance = VK_NULL_HANDLE;
|
|
FVulkanPlatform::ClearVulkanInstanceFunctions();
|
|
}
|
|
};
|
|
|
|
// Pick the device we would use on this instance
|
|
const VkPhysicalDevice PhysicalDevice = SelectPhysicalDevice(TempInstance);
|
|
if (PhysicalDevice)
|
|
{
|
|
VkBool32 bDeviceSupported = VK_FALSE;
|
|
VERIFYVULKANRESULT(vpGetPhysicalDeviceProfileSupport(TempInstance, PhysicalDevice, &ProfileProperties, &bDeviceSupported));
|
|
if (bDeviceSupported)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FVulkanDynamicRHIModule::StartupModule()
|
|
{
|
|
#if VULKAN_USE_LLM
|
|
LLM(VulkanLLM::Initialize());
|
|
#endif
|
|
}
|
|
|
|
bool FVulkanDynamicRHIModule::IsSupported()
|
|
{
|
|
if (FVulkanPlatform::IsSupported())
|
|
{
|
|
return FVulkanPlatform::LoadVulkanLibrary();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FVulkanDynamicRHIModule::IsSupported(ERHIFeatureLevel::Type FeatureLevel)
|
|
{
|
|
if (IsSupported())
|
|
{
|
|
if (FeatureLevel == ERHIFeatureLevel::ES3_1)
|
|
{
|
|
return !GIsEditor;
|
|
}
|
|
else if (!FVulkanPlatform::SupportsProfileChecks())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return CheckVulkanProfile(FeatureLevel, false);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FDynamicRHI* FVulkanDynamicRHIModule::CreateRHI(ERHIFeatureLevel::Type InRequestedFeatureLevel)
|
|
{
|
|
GMaxRHIFeatureLevel = FVulkanPlatform::GetFeatureLevel(InRequestedFeatureLevel);
|
|
checkf(GMaxRHIFeatureLevel != ERHIFeatureLevel::Num, TEXT("Invalid feature level requested!"));
|
|
|
|
EShaderPlatform ShaderPlatformForFeatureLevel[ERHIFeatureLevel::Num];
|
|
FVulkanPlatform::SetupFeatureLevels(ShaderPlatformForFeatureLevel);
|
|
GMaxRHIShaderPlatform = ShaderPlatformForFeatureLevel[GMaxRHIFeatureLevel];
|
|
checkf(GMaxRHIShaderPlatform != SP_NumPlatforms, TEXT("Requested feature level [%s] mapped to unsupported shader platform!"), *LexToString(InRequestedFeatureLevel));
|
|
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan RHI ShaderPlatform for %s: %s."), *LexToString(InRequestedFeatureLevel), *LexToString(GMaxRHIShaderPlatform, false));
|
|
|
|
GVulkanRHI = new FVulkanDynamicRHI();
|
|
FDynamicRHI* FinalRHI = GVulkanRHI;
|
|
|
|
#if ENABLE_RHI_VALIDATION
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("RHIValidation")))
|
|
{
|
|
FinalRHI = new FValidationRHI(FinalRHI);
|
|
}
|
|
#endif
|
|
|
|
for (int32 Index = 0; Index < ERHIFeatureLevel::Num; ++Index)
|
|
{
|
|
if (ShaderPlatformForFeatureLevel[Index] != SP_NumPlatforms)
|
|
{
|
|
check(GMaxTextureSamplers >= (int32)FDataDrivenShaderPlatformInfo::GetMaxSamplers(ShaderPlatformForFeatureLevel[Index]));
|
|
if (GMaxTextureSamplers < (int32)FDataDrivenShaderPlatformInfo::GetMaxSamplers(ShaderPlatformForFeatureLevel[Index]))
|
|
{
|
|
UE_LOG(LogVulkanRHI, Error, TEXT("Shader platform requires at least: %d samplers, device supports: %d."), FDataDrivenShaderPlatformInfo::GetMaxSamplers(ShaderPlatformForFeatureLevel[Index]), GMaxTextureSamplers);
|
|
}
|
|
}
|
|
}
|
|
|
|
return FinalRHI;
|
|
}
|
|
|
|
IMPLEMENT_MODULE(FVulkanDynamicRHIModule, VulkanRHI);
|
|
|
|
|
|
FVulkanCommandListContextImmediate::FVulkanCommandListContextImmediate(FVulkanDevice& InDevice)
|
|
: FVulkanCommandListContext(InDevice, ERHIPipeline::Graphics, nullptr)
|
|
{
|
|
}
|
|
|
|
|
|
FVulkanDynamicRHI::FVulkanDynamicRHI()
|
|
: Instance(VK_NULL_HANDLE)
|
|
, Device(nullptr)
|
|
, DrawingViewport(nullptr)
|
|
{
|
|
// This should be called once at the start
|
|
check(IsInGameThread());
|
|
check(!GIsThreadedRendering);
|
|
|
|
GPoolSizeVRAMPercentage = 0;
|
|
GTexturePoolSize = 0;
|
|
GRHISupportsMultithreading = true;
|
|
GRHISupportsMultithreadedResources = true;
|
|
GRHITransitionPrivateData_SizeInBytes = sizeof(FVulkanTransitionData);
|
|
GRHITransitionPrivateData_AlignInBytes = alignof(FVulkanTransitionData);
|
|
GConfig->GetInt(TEXT("TextureStreaming"), TEXT("PoolSizeVRAMPercentage"), GPoolSizeVRAMPercentage, GEngineIni);
|
|
|
|
GRHIGlobals.SupportsBarycentricsSemantic = true;
|
|
|
|
GRHISupportsPSOPrecaching = CVarAllowVulkanPSOPrecache.GetValueOnAnyThread();
|
|
GRHISupportsPipelineFileCache = !GRHISupportsPSOPrecaching || CVarEnableVulkanPSOFileCacheWhenPrecachingActive.GetValueOnAnyThread();
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Vulkan PSO Precaching = %d, PipelineFileCache = %d"), GRHISupportsPSOPrecaching, GRHISupportsPipelineFileCache);
|
|
|
|
// Copy source requires its own image layout.
|
|
EnumRemoveFlags(GRHIMergeableAccessMask, ERHIAccess::CopySrc);
|
|
|
|
// Setup the validation requests ready before we load dlls
|
|
SetupValidationRequests();
|
|
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Built with Vulkan header version %u.%u.%u"), VK_API_VERSION_MAJOR(VK_HEADER_VERSION_COMPLETE), VK_API_VERSION_MINOR(VK_HEADER_VERSION_COMPLETE), VK_API_VERSION_PATCH(VK_HEADER_VERSION_COMPLETE));
|
|
|
|
CreateInstance();
|
|
SelectDevice();
|
|
}
|
|
|
|
FVulkanDynamicRHI::~FVulkanDynamicRHI() = default;
|
|
|
|
void FVulkanDynamicRHI::Init()
|
|
{
|
|
InitInstance();
|
|
|
|
bIsStandaloneStereoDevice = IHeadMountedDisplayModule::IsAvailable() && IHeadMountedDisplayModule::Get().IsStandaloneStereoOnlyDevice();
|
|
|
|
static const auto CVarStreamingTexturePoolSize = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Streaming.PoolSize"));
|
|
int32 StreamingPoolSizeValue = CVarStreamingTexturePoolSize->GetValueOnAnyThread();
|
|
|
|
if (GPoolSizeVRAMPercentage > 0)
|
|
{
|
|
const uint64 TotalGPUMemory = Device->GetDeviceMemoryManager().GetTotalMemory(true);
|
|
|
|
float PoolSize = float(GPoolSizeVRAMPercentage) * 0.01f * float(TotalGPUMemory);
|
|
|
|
// Truncate GTexturePoolSize to MB (but still counted in bytes)
|
|
GTexturePoolSize = int64(FGenericPlatformMath::TruncToFloat(PoolSize / 1024.0f / 1024.0f)) * 1024 * 1024;
|
|
|
|
UE_LOG(LogRHI, Log, TEXT("Texture pool is %llu MB (%d%% of %llu MB)"),
|
|
GTexturePoolSize / 1024 / 1024,
|
|
GPoolSizeVRAMPercentage,
|
|
TotalGPUMemory / 1024 / 1024);
|
|
}
|
|
else if (StreamingPoolSizeValue > 0)
|
|
{
|
|
GTexturePoolSize = (int64)StreamingPoolSizeValue * 1024 * 1024;
|
|
|
|
const uint64 TotalGPUMemory = Device->GetDeviceMemoryManager().GetTotalMemory(true);
|
|
UE_LOG(LogRHI,Log,TEXT("Texture pool is %llu MB (of %llu MB total graphics mem)"),
|
|
GTexturePoolSize / 1024 / 1024,
|
|
TotalGPUMemory / 1024 / 1024);
|
|
}
|
|
}
|
|
|
|
void FVulkanDynamicRHI::PostInit()
|
|
{
|
|
if (GRHISupportsRayTracing)
|
|
{
|
|
Device->InitializeRayTracing();
|
|
}
|
|
}
|
|
|
|
void FVulkanDynamicRHI::Shutdown()
|
|
{
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("savevulkanpsocacheonexit")))
|
|
{
|
|
SavePipelineCache();
|
|
}
|
|
|
|
check(IsInGameThread() && IsInRenderingThread());
|
|
check(Device);
|
|
|
|
Device->PrepareForDestroy();
|
|
|
|
EmptyCachedBoundShaderStates();
|
|
|
|
FVulkanVertexDeclaration::EmptyCache();
|
|
|
|
if (GIsRHIInitialized)
|
|
{
|
|
// Reset the RHI initialized flag.
|
|
GIsRHIInitialized = false;
|
|
|
|
FVulkanPlatform::OverridePlatformHandlers(false);
|
|
|
|
GRHINeedsExtraDeletionLatency = false;
|
|
|
|
check(!GIsCriticalError);
|
|
|
|
// Ask all initialized FRenderResources to release their RHI resources.
|
|
FRenderResource::ReleaseRHIForAllResources();
|
|
|
|
{
|
|
for (auto& Pair : Device->SamplerMap)
|
|
{
|
|
FVulkanSamplerState* SamplerState = (FVulkanSamplerState*)Pair.Value.GetReference();
|
|
VulkanRHI::vkDestroySampler(Device->GetInstanceHandle(), SamplerState->Sampler, VULKAN_CPU_ALLOCATOR);
|
|
}
|
|
Device->SamplerMap.Empty();
|
|
}
|
|
|
|
Device->CleanUpRayTracing();
|
|
|
|
// Flush all pending deletes before destroying the device.
|
|
FRHICommandListImmediate::Get().ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
|
|
|
|
ShutdownSubmissionPipe();
|
|
}
|
|
|
|
Device->Destroy();
|
|
|
|
delete Device;
|
|
Device = nullptr;
|
|
|
|
// Release the early HMD interface used to query extra extensions - if any was used
|
|
HMDVulkanExtensions = nullptr;
|
|
|
|
#if VULKAN_HAS_DEBUGGING_ENABLED
|
|
RemoveDebugLayerCallback();
|
|
#endif
|
|
|
|
VulkanRHI::vkDestroyInstance(Instance, VULKAN_CPU_ALLOCATOR);
|
|
|
|
IConsoleManager::Get().UnregisterConsoleObject(SavePipelineCacheCmd);
|
|
IConsoleManager::Get().UnregisterConsoleObject(RebuildPipelineCacheCmd);
|
|
|
|
#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
|
|
IConsoleManager::Get().UnregisterConsoleObject(DumpMemoryCmd);
|
|
IConsoleManager::Get().UnregisterConsoleObject(DumpMemoryFullCmd);
|
|
IConsoleManager::Get().UnregisterConsoleObject(DumpStagingMemoryCmd);
|
|
IConsoleManager::Get().UnregisterConsoleObject(DumpLRUCmd);
|
|
IConsoleManager::Get().UnregisterConsoleObject(TrimLRUCmd);
|
|
#endif
|
|
|
|
FVulkanPlatform::FreeVulkanLibrary();
|
|
|
|
#if VULKAN_ENABLE_DUMP_LAYER
|
|
VulkanRHI::FlushDebugWrapperLog();
|
|
#endif
|
|
}
|
|
|
|
void FVulkanDynamicRHI::CreateInstance()
|
|
{
|
|
// Engine registration can be disabled via console var. Also disable automatically if ShaderDevelopmentMode is on.
|
|
auto* CVarDisableEngineAndAppRegistration = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DisableEngineAndAppRegistration"));
|
|
bool bDisableEngineRegistration = (CVarDisableEngineAndAppRegistration && CVarDisableEngineAndAppRegistration->GetValueOnAnyThread() != 0) ||
|
|
IsShaderDevelopmentModeEnabled();
|
|
|
|
// Use the API version stored in the profile
|
|
ApiVersion = GetVulkanApiVersionForFeatureLevel(GMaxRHIFeatureLevel, false);
|
|
|
|
// Run a profile check to see if this device can support our raytacing requirements since it might change the required API version of the instance
|
|
if (FVulkanPlatform::SupportsProfileChecks() && GVulkanRayTracingCVar.GetValueOnAnyThread())
|
|
{
|
|
const bool bRayTracingAllowedOnCurrentShaderPlatform = (GMaxRHIShaderPlatform == SP_VULKAN_SM6 || IsVulkanMobileSM5Platform(GMaxRHIShaderPlatform));
|
|
|
|
if (CheckVulkanProfile(GMaxRHIFeatureLevel, true) && bRayTracingAllowedOnCurrentShaderPlatform)
|
|
{
|
|
// Raytracing is supported, update the required API version
|
|
ApiVersion = GetVulkanApiVersionForFeatureLevel(GMaxRHIFeatureLevel, true);
|
|
}
|
|
else
|
|
{
|
|
// Raytracing is not supported, disable it completely instead of only loading parts of it
|
|
GVulkanRayTracingCVar->Set(0, ECVF_SetByCode);
|
|
|
|
if (!bRayTracingAllowedOnCurrentShaderPlatform)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan RayTracing disabled because SM6 shader platform is required."));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan RayTracing disabled because of failed profile check."));
|
|
}
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Using API Version %u.%u."), VK_API_VERSION_MAJOR(ApiVersion), VK_API_VERSION_MINOR(ApiVersion));
|
|
|
|
// EngineName will be of the form "UnrealEngine4.21", with the minor version ("21" in this example)
|
|
// updated with every quarterly release
|
|
FString EngineName = FApp::GetEpicProductIdentifier() + FEngineVersion::Current().ToString(EVersionComponent::Minor);
|
|
FTCHARToUTF8 EngineNameConverter(*EngineName);
|
|
FTCHARToUTF8 ProjectNameConverter(FApp::GetProjectName());
|
|
|
|
VkApplicationInfo AppInfo;
|
|
ZeroVulkanStruct(AppInfo, VK_STRUCTURE_TYPE_APPLICATION_INFO);
|
|
AppInfo.pApplicationName = bDisableEngineRegistration ? nullptr : ProjectNameConverter.Get();
|
|
AppInfo.applicationVersion = static_cast<uint32>(BuildSettings::GetCurrentChangelist()) | (BuildSettings::IsLicenseeVersion() ? 0x80000000 : 0);
|
|
AppInfo.pEngineName = bDisableEngineRegistration ? nullptr : EngineNameConverter.Get();
|
|
AppInfo.engineVersion = FEngineVersion::Current().GetMinor();
|
|
AppInfo.apiVersion = ApiVersion;
|
|
|
|
VkInstanceCreateInfo InstInfo;
|
|
ZeroVulkanStruct(InstInfo, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
|
|
InstInfo.pApplicationInfo = &AppInfo;
|
|
|
|
FVulkanInstanceExtensionArray UEInstanceExtensions = FVulkanInstanceExtension::GetUESupportedInstanceExtensions(ApiVersion);
|
|
InstanceLayers = SetupInstanceLayers(UEInstanceExtensions);
|
|
for (TUniquePtr<FVulkanInstanceExtension>& Extension : UEInstanceExtensions)
|
|
{
|
|
if (Extension->InUse())
|
|
{
|
|
InstanceExtensions.Add(Extension->GetExtensionName());
|
|
Extension->PreCreateInstance(InstInfo, OptionalInstanceExtensions);
|
|
}
|
|
}
|
|
|
|
InstInfo.enabledExtensionCount = InstanceExtensions.Num();
|
|
InstInfo.ppEnabledExtensionNames = InstInfo.enabledExtensionCount > 0 ? (const ANSICHAR* const*)InstanceExtensions.GetData() : nullptr;
|
|
|
|
InstInfo.enabledLayerCount = InstanceLayers.Num();
|
|
InstInfo.ppEnabledLayerNames = InstInfo.enabledLayerCount > 0 ? InstanceLayers.GetData() : nullptr;
|
|
|
|
VkResult Result = VulkanRHI::vkCreateInstance(&InstInfo, VULKAN_CPU_ALLOCATOR, &Instance);
|
|
|
|
if (Result == VK_ERROR_LAYER_NOT_PRESENT)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Vulkan instance creation returned an error with the requested layers (%d):"), InstanceLayers.Num());
|
|
|
|
for (const ANSICHAR* AnsiLayerName : InstanceLayers)
|
|
{
|
|
const FString LayerStr = ANSI_TO_TCHAR(AnsiLayerName);
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("- %s"), *LayerStr);
|
|
}
|
|
|
|
const EAppReturnType::Type MsgBoxResult = FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, TEXT(
|
|
"ERROR: Vulkan driver couldn't load one of the requested layers (see log for details).\n\n"
|
|
"Retry without layers?"),
|
|
TEXT("Incompatible Vulkan layer found!"));
|
|
|
|
if (MsgBoxResult == EAppReturnType::Yes)
|
|
{
|
|
InstInfo.enabledLayerCount = 0;
|
|
Result = VulkanRHI::vkCreateInstance(&InstInfo, VULKAN_CPU_ALLOCATOR, &Instance);
|
|
}
|
|
else
|
|
{
|
|
FPlatformMisc::RequestExitWithStatus(true, 1);
|
|
// unreachable
|
|
return;
|
|
}
|
|
}
|
|
|
|
FVulkanPlatform::NotifyFoundInstanceLayersAndExtensions(InstanceLayers, InstanceExtensions);
|
|
|
|
if (Result == VK_ERROR_INCOMPATIBLE_DRIVER)
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT(
|
|
"Cannot find a compatible Vulkan driver (ICD).\n\nPlease look at the Getting Started guide for "
|
|
"additional information."), TEXT("Incompatible Vulkan driver found!"));
|
|
FPlatformMisc::RequestExitWithStatus(true, 1);
|
|
// unreachable
|
|
return;
|
|
}
|
|
else if(Result == VK_ERROR_EXTENSION_NOT_PRESENT)
|
|
{
|
|
// Check for missing extensions
|
|
FString MissingExtensions;
|
|
|
|
uint32_t PropertyCount;
|
|
VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &PropertyCount, nullptr);
|
|
|
|
TArray<VkExtensionProperties> Properties;
|
|
Properties.SetNum(PropertyCount);
|
|
VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &PropertyCount, Properties.GetData());
|
|
|
|
for (const ANSICHAR* Extension : InstanceExtensions)
|
|
{
|
|
bool bExtensionFound = false;
|
|
|
|
for (uint32_t PropertyIndex = 0; PropertyIndex < PropertyCount; PropertyIndex++)
|
|
{
|
|
const char* PropertyExtensionName = Properties[PropertyIndex].extensionName;
|
|
|
|
if (!FCStringAnsi::Strcmp(PropertyExtensionName, Extension))
|
|
{
|
|
bExtensionFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bExtensionFound)
|
|
{
|
|
FString ExtensionStr = ANSI_TO_TCHAR(Extension);
|
|
UE_LOG(LogVulkanRHI, Error, TEXT("Missing required Vulkan extension: %s"), *ExtensionStr);
|
|
MissingExtensions += ExtensionStr + TEXT("\n");
|
|
}
|
|
}
|
|
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *FString::Printf(TEXT(
|
|
"Vulkan driver doesn't contain specified extensions:\n%s;\n\
|
|
make sure your layers path is set appropriately."), *MissingExtensions), TEXT("Incomplete Vulkan driver found!"));
|
|
}
|
|
else if (Result != VK_SUCCESS)
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT(
|
|
"Vulkan failed to create instance.\n\nDo you have a compatible Vulkan "
|
|
"driver (ICD) installed?\nPlease look at "
|
|
"the Getting Started guide for additional information."), TEXT("No Vulkan driver found!"));
|
|
FPlatformMisc::RequestExitWithStatus(true, 1);
|
|
// unreachable
|
|
return;
|
|
}
|
|
|
|
VERIFYVULKANRESULT(Result);
|
|
|
|
if (!FVulkanPlatform::LoadVulkanInstanceFunctions(Instance))
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT(
|
|
"Failed to find all required Vulkan entry points! Try updating your driver."), TEXT("No Vulkan entry points found!"));
|
|
}
|
|
|
|
#if VULKAN_HAS_DEBUGGING_ENABLED
|
|
SetupDebugLayerCallback();
|
|
#endif
|
|
}
|
|
|
|
void FVulkanDynamicRHI::SelectDevice()
|
|
{
|
|
VkPhysicalDevice PhysicalDevice = SelectPhysicalDevice(Instance);
|
|
if (PhysicalDevice == VK_NULL_HANDLE)
|
|
{
|
|
// Shouldn't be possible if profile checks passed prior to this
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok,
|
|
TEXT("Vulkan failed to select physical device after passing profile checks."), TEXT("No Vulkan driver found!"));
|
|
FPlatformMisc::RequestExitWithStatus(true, 1);
|
|
return;
|
|
}
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Creating Vulkan Device using VkPhysicalDevice 0x%x."), reinterpret_cast<const void*>(PhysicalDevice));
|
|
Device = new FVulkanDevice(this, PhysicalDevice);
|
|
|
|
const VkPhysicalDeviceProperties& Props = Device->GetDeviceProperties();
|
|
bool bUseVendorIdAsIs = true;
|
|
if (Props.vendorID > 0xffff)
|
|
{
|
|
bUseVendorIdAsIs = false;
|
|
VkVendorId VendorId = (VkVendorId)Props.vendorID;
|
|
switch (VendorId)
|
|
{
|
|
case VK_VENDOR_ID_VIV: GRHIVendorId = (uint32)EGpuVendorId::Vivante; break;
|
|
case VK_VENDOR_ID_VSI: GRHIVendorId = (uint32)EGpuVendorId::VeriSilicon; break;
|
|
case VK_VENDOR_ID_KAZAN: GRHIVendorId = (uint32)EGpuVendorId::Kazan; break;
|
|
case VK_VENDOR_ID_CODEPLAY: GRHIVendorId = (uint32)EGpuVendorId::Codeplay; break;
|
|
case VK_VENDOR_ID_MESA: GRHIVendorId = (uint32)EGpuVendorId::Mesa; break;
|
|
default:
|
|
// Unhandled case
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Unhandled VkVendorId %d"), (int32)VendorId);
|
|
bUseVendorIdAsIs = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bUseVendorIdAsIs)
|
|
{
|
|
GRHIVendorId = Props.vendorID;
|
|
}
|
|
GRHIAdapterName = ANSI_TO_TCHAR(Props.deviceName);
|
|
|
|
if (PLATFORM_ANDROID)
|
|
{
|
|
GRHIAdapterName.Append(TEXT(" Vulkan"));
|
|
// On Android GL version string often contains extra information such as an actual driver version on the device.
|
|
#if PLATFORM_ANDROID
|
|
FString GLVersion = FAndroidMisc::GetGLVersion();
|
|
#else
|
|
FString GLVersion = "";
|
|
#endif
|
|
GRHIAdapterInternalDriverVersion = FString::Printf(TEXT("%d.%d.%d|%s"), VK_VERSION_MAJOR(Props.apiVersion), VK_VERSION_MINOR(Props.apiVersion), VK_VERSION_PATCH(Props.apiVersion), *GLVersion);
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("API Version: %s"), *GRHIAdapterInternalDriverVersion);
|
|
}
|
|
else if (PLATFORM_WINDOWS)
|
|
{
|
|
GRHIDeviceId = Props.deviceID;
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("API Version: %d.%d.%d"), VK_VERSION_MAJOR(Props.apiVersion), VK_VERSION_MINOR(Props.apiVersion), VK_VERSION_PATCH(Props.apiVersion));
|
|
}
|
|
else if(PLATFORM_UNIX)
|
|
{
|
|
if (Device->GetVendorId() == EGpuVendorId::Nvidia)
|
|
{
|
|
UNvidiaDriverVersion NvidiaVersion;
|
|
static_assert(sizeof(NvidiaVersion) == sizeof(Props.driverVersion), "Mismatched Nvidia pack driver version!");
|
|
NvidiaVersion.Packed = Props.driverVersion;
|
|
GRHIAdapterUserDriverVersion = FString::Printf(TEXT("%d.%02d"), NvidiaVersion.Major, NvidiaVersion.Minor);
|
|
}
|
|
else
|
|
{
|
|
GRHIAdapterUserDriverVersion = FString::Printf(TEXT("%d.%d.%d"), VK_VERSION_MAJOR(Props.driverVersion), VK_VERSION_MINOR(Props.driverVersion), VK_VERSION_PATCH(Props.driverVersion));
|
|
}
|
|
|
|
GRHIDeviceId = Props.deviceID;
|
|
GRHIAdapterInternalDriverVersion = GRHIAdapterUserDriverVersion;
|
|
GRHIAdapterDriverDate = TEXT("01-01-01"); // Unused on unix systems, pick a date that will fail test if compared but passes IsValid() check
|
|
UE_LOG(LogVulkanRHI, Log, TEXT(" API Version: %d.%d.%d"), VK_VERSION_MAJOR(Props.apiVersion), VK_VERSION_MINOR(Props.apiVersion), VK_VERSION_PATCH(Props.apiVersion));
|
|
}
|
|
|
|
GRHIPersistentThreadGroupCount = 1440; // TODO: Revisit based on vendor/adapter/perf query
|
|
|
|
GRHIGlobals.SupportsTimestampRenderQueries = FVulkanPlatform::SupportsTimestampRenderQueries() && (Device->GetLimits().timestampPeriod > 0.0f);
|
|
}
|
|
|
|
void FVulkanDynamicRHI::InitInstance()
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (!GIsRHIInitialized)
|
|
{
|
|
// Wait for the rendering thread to go idle.
|
|
FlushRenderingCommands();
|
|
|
|
FVulkanPlatform::OverridePlatformHandlers(true);
|
|
|
|
GRHISupportsAsyncTextureCreation = false;
|
|
|
|
Device->InitGPU();
|
|
|
|
#if VULKAN_HAS_DEBUGGING_ENABLED
|
|
if (GRenderDocFound)
|
|
{
|
|
EnableIdealGPUCaptureOptions(true);
|
|
}
|
|
#endif
|
|
|
|
const VkPhysicalDeviceProperties& Props = Device->GetDeviceProperties();
|
|
const VkPhysicalDeviceLimits& Limits = Device->GetLimits();
|
|
|
|
// Initialize the RHI capabilities.
|
|
GRHISupportsFirstInstance = true;
|
|
GRHISupportsDynamicResolution = FVulkanPlatform::SupportsDynamicResolution();
|
|
GRHISupportsFrameCyclesBubblesRemoval = true;
|
|
GSupportsDepthBoundsTest = Device->GetPhysicalDeviceFeatures().Core_1_0.depthBounds != 0;
|
|
GSupportsRenderTargetFormat_PF_G8 = false; // #todo-rco
|
|
GRHISupportsTextureStreaming = true;
|
|
GRHISupportsGPUTimestampBubblesRemoval = true;
|
|
GSupportsMobileMultiView = Device->GetOptionalExtensions().HasKHRMultiview ? true : false;
|
|
GRHISupportsMSAAShaderResolve = Device->GetOptionalExtensions().HasQcomRenderPassShaderResolve ? true : false;
|
|
GRHISupportsRayTracing = RHI_RAYTRACING && RHISupportsRayTracing(GMaxRHIShaderPlatform) && Device->GetOptionalExtensions().HasRaytracingExtensions();
|
|
GRHIGlobals.SupportsMapWriteNoOverwrite = true;
|
|
|
|
GRHIGlobals.NeedsExtraTransitions = true;
|
|
|
|
// Use this compatibility mode avoid known issues at launch time with latest drivers at the time of release 5.5. This will:
|
|
// - disable inline ray tracing and use ray tracing pipelines everywhere (instead of a mix of both)
|
|
// - disable mesh shaders until issues can be resolved (holes in Nanite meshes)
|
|
// - disable ray tracing for RADV driver prior to version 24.3.2 in Linux (raytracing pipeline compilation crash)
|
|
|
|
// :todo-jn: to be removed when the official minimum RADV version is set to 24.3.2 in BaseHardware.ini
|
|
const bool bUseAMDCompatibilityMode = GVulkanAMDCompatibilityMode && (Device->GetVendorId() == EGpuVendorId::Amd);
|
|
if (GVulkanAMDCompatibilityMode &&
|
|
(Device->GetOptionalExtensionProperties().PhysicalDeviceDriverProperties.driverID == VK_DRIVER_ID_MESA_RADV) &&
|
|
(Props.driverVersion < VK_MAKE_VERSION(24, 3, 2)))
|
|
{
|
|
GRHISupportsRayTracing = false;
|
|
GRHISupportsRayTracingShaders = false;
|
|
GRHISupportsInlineRayTracing = false;
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Using MESA RADV version prior to 24.3.2, ray tracing disabled."));
|
|
}
|
|
|
|
if (GRHISupportsRayTracing)
|
|
{
|
|
GRHISupportsRayTracingShaders = RHISupportsRayTracingShaders(GMaxRHIShaderPlatform) && Device->GetOptionalExtensions().HasRayTracingPipeline;
|
|
GRHISupportsInlineRayTracing = !bUseAMDCompatibilityMode && RHISupportsInlineRayTracing(GMaxRHIShaderPlatform) && Device->GetOptionalExtensions().HasRayQuery;
|
|
|
|
// Inline RayTracing SBT is needed if raytracing position fetch isn't available
|
|
GRHIGlobals.RayTracing.RequiresInlineRayTracingSBT = !VULKAN_SUPPORTS_RAY_TRACING_POSITION_FETCH;
|
|
|
|
GRHIRayTracingAccelerationStructureAlignment = 256; // TODO (currently handled by FVulkanAccelerationStructureBuffer)
|
|
//Some devices have 64 for min AS offset alignment meanwhile engine AS alignment is 256. hence using round up value
|
|
GRHIRayTracingScratchBufferAlignment = FPlatformMath::Max<uint32>(GRHIRayTracingAccelerationStructureAlignment,
|
|
Device->GetOptionalExtensionProperties().AccelerationStructureProps.minAccelerationStructureScratchOffsetAlignment);
|
|
|
|
GRHIRayTracingInstanceDescriptorSize = uint32(sizeof(VkAccelerationStructureInstanceKHR));
|
|
|
|
// Loose parameters are always placed in the shader record after the FVulkanHitGroupSystemParameters in Vulkan (see VulkanRayTracing.h and VulkanCommon.ush)
|
|
GRHIGlobals.RayTracing.SupportsLooseParamsInShaderRecord = true;
|
|
}
|
|
|
|
#if VULKAN_ENABLE_DUMP_LAYER
|
|
// Disable RHI thread by default if the dump layer is enabled
|
|
GRHISupportsRHIThread = false;
|
|
GRHISupportsParallelRHIExecute = false;
|
|
#else
|
|
GRHISupportsRHIThread = GRHIThreadCvar->GetInt() != 0;
|
|
GRHISupportsParallelRHIExecute = Device->SupportsParallelRendering() && (GRHIThreadCvar->GetInt() > 1);
|
|
#endif
|
|
|
|
GRHISupportsParallelRenderPasses = GRHISupportsParallelRHIExecute;
|
|
GRHIParallelRHIExecuteChildWait = GRHISupportsParallelRHIExecute;
|
|
GRHIParallelRHIExecuteParentWait = GRHISupportsParallelRHIExecute;
|
|
|
|
GRHISupportsUAVFormatAliasing = true;
|
|
|
|
// Some platforms might only have CPU for an RHI thread, but not for parallel tasks
|
|
GSupportsParallelRenderingTasksWithSeparateRHIThread = GRHISupportsRHIThread ? FVulkanPlatform::SupportParallelRenderingTasks() : false;
|
|
|
|
//#todo-rco: Add newer Nvidia also
|
|
GSupportsEfficientAsyncCompute = Device->HasAsyncComputeQueue();
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan Async Compute has been %s."), GSupportsEfficientAsyncCompute ? TEXT("ENABLED") : TEXT("DISABLED"));
|
|
|
|
GSupportsVolumeTextureRendering = FVulkanPlatform::SupportsVolumeTextureRendering();
|
|
|
|
// Indicate that the RHI needs to use the engine's deferred deletion queue.
|
|
GRHINeedsExtraDeletionLatency = true;
|
|
|
|
GMaxShadowDepthBufferSizeX = FPlatformMath::Min<int32>(Props.limits.maxImageDimension2D, GMaxShadowDepthBufferSizeX);
|
|
GMaxShadowDepthBufferSizeY = FPlatformMath::Min<int32>(Props.limits.maxImageDimension2D, GMaxShadowDepthBufferSizeY);
|
|
GMaxTextureDimensions = Props.limits.maxImageDimension2D;
|
|
GRHIGlobals.MaxViewDimensionForTypedBuffer = Props.limits.maxTexelBufferElements;
|
|
GRHIGlobals.MaxViewSizeBytesForNonTypedBuffer = Props.limits.maxStorageBufferRange;
|
|
GMaxComputeSharedMemory = Props.limits.maxComputeSharedMemorySize;
|
|
GMaxTextureMipCount = FPlatformMath::CeilLogTwo( GMaxTextureDimensions ) + 1;
|
|
GMaxTextureMipCount = FPlatformMath::Min<int32>( MAX_TEXTURE_MIP_COUNT, GMaxTextureMipCount );
|
|
GMaxCubeTextureDimensions = Props.limits.maxImageDimensionCube;
|
|
GMaxVolumeTextureDimensions = Props.limits.maxImageDimension3D;
|
|
GMaxWorkGroupInvocations = Props.limits.maxComputeWorkGroupInvocations;
|
|
GMaxTextureArrayLayers = Props.limits.maxImageArrayLayers;
|
|
GRHISupportsBaseVertexIndex = true;
|
|
GSupportsSeparateRenderTargetBlendState = true;
|
|
GSupportsDualSrcBlending = Device->GetPhysicalDeviceFeatures().Core_1_0.dualSrcBlend == VK_TRUE;
|
|
GRHISupportsSeparateDepthStencilCopyAccess = Device->SupportsParallelRendering();
|
|
GRHIGlobals.bSupportsBindless = Device->SupportsBindless() && FDataDrivenShaderPlatformInfo::GetSupportsBindless(GMaxRHIShaderPlatform);
|
|
GMaxTextureSamplers = (int32)FMath::Min<uint32>(MAX_int32, Props.limits.maxPerStageDescriptorSamplers);
|
|
GRHISupportsLossyFramebufferCompression = Device->GetOptionalExtensions().HasEXTImageCompressionControl;
|
|
GRHIMaxDispatchThreadGroupsPerDimension.X = FMath::Min<uint32>(Limits.maxComputeWorkGroupCount[0], 0x7fffffff);
|
|
GRHIMaxDispatchThreadGroupsPerDimension.Y = FMath::Min<uint32>(Limits.maxComputeWorkGroupCount[1], 0x7fffffff);
|
|
GRHIMaxDispatchThreadGroupsPerDimension.Z = FMath::Min<uint32>(Limits.maxComputeWorkGroupCount[2], 0x7fffffff);
|
|
GRHISupportsBindingTexArrayPerSlice = true;
|
|
|
|
// Note: While the 2022/2024 profile limits state a minimum of 16, other profiles (even core) go down to 4
|
|
// (see https://vulkan.lunarg.com/doc/view/1.3.290.0/windows/profiles_definitions.html)
|
|
// Since the RHI has historically always supported 8 UAV's, let's leave those specific devices out
|
|
uint32 DeviceMaxStorageDescriptorPerStage = FMath::Min(Props.limits.maxPerStageDescriptorStorageBuffers, Props.limits.maxPerStageDescriptorStorageImages);
|
|
GRHIGlobals.MaxSimultaneousUAVs = DeviceMaxStorageDescriptorPerStage >= 16 ? 16 : 8;
|
|
|
|
FVulkanPlatform::SetupFeatureLevels(GRHIGlobals.ShaderPlatformForFeatureLevel);
|
|
|
|
GRHIRequiresRenderTargetForPixelShaderUAVs = true;
|
|
|
|
GUseTexture3DBulkDataRHI = false;
|
|
|
|
// these are supported by all devices
|
|
GVulkanDevicePipelineStageBits = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
|
VkShaderStageFlags VulkanDeviceShaderStageBits = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
|
|
|
|
// optional shader stages
|
|
if (Device->GetPhysicalDeviceFeatures().Core_1_0.geometryShader)
|
|
{
|
|
GVulkanDevicePipelineStageBits |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
|
|
VulkanDeviceShaderStageBits |= VK_SHADER_STAGE_GEOMETRY_BIT;
|
|
}
|
|
|
|
#if PLATFORM_SUPPORTS_MESH_SHADERS
|
|
// If mesh shaders are enabled in DDPI (currently SM6), then the profile check will ensure it's supported
|
|
if (!bUseAMDCompatibilityMode && Device->GetOptionalExtensions().HasEXTMeshShader)
|
|
{
|
|
GRHIGlobals.SupportsMeshShadersTier0 = RHISupportsMeshShadersTier0(GMaxRHIShaderPlatform);
|
|
GRHIGlobals.SupportsMeshShadersTier1 = RHISupportsMeshShadersTier1(GMaxRHIShaderPlatform);
|
|
|
|
GVulkanDevicePipelineStageBits |= VK_PIPELINE_STAGE_TASK_SHADER_BIT_EXT | VK_PIPELINE_STAGE_MESH_SHADER_BIT_EXT;
|
|
VulkanDeviceShaderStageBits |= VK_SHADER_STAGE_TASK_BIT_EXT | VK_SHADER_STAGE_MESH_BIT_EXT;
|
|
}
|
|
#endif
|
|
|
|
const VkShaderStageFlags RequiredSubgroupShaderStageFlags = FVulkanPlatform::RequiredWaveOpsShaderStageFlags(VulkanDeviceShaderStageBits);
|
|
|
|
// Check for wave ops support (only filled on platforms creating Vulkan 1.1 or greater instances)
|
|
const VkSubgroupFeatureFlags RequiredSubgroupFlags = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_VOTE_BIT |
|
|
VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT |
|
|
VK_SUBGROUP_FEATURE_SHUFFLE_BIT;
|
|
GRHISupportsWaveOperations = VKHasAllFlags(Device->GetDeviceSubgroupProperties().supportedStages, RequiredSubgroupShaderStageFlags) &&
|
|
VKHasAllFlags(Device->GetDeviceSubgroupProperties().supportedOperations, RequiredSubgroupFlags);
|
|
|
|
if (GRHISupportsWaveOperations)
|
|
{
|
|
// Use default size if VK_EXT_subgroup_size_control didn't fill them
|
|
if (!GRHIMinimumWaveSize || !GRHIMaximumWaveSize)
|
|
{
|
|
GRHIMinimumWaveSize = GRHIMaximumWaveSize = Device->GetDeviceSubgroupProperties().subgroupSize;
|
|
}
|
|
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Wave Operations have been ENABLED (wave size: min=%d max=%d)."), GRHIMinimumWaveSize, GRHIMaximumWaveSize);
|
|
}
|
|
else
|
|
{
|
|
const uint32 MissingStageFlags = (Device->GetDeviceSubgroupProperties().supportedStages & RequiredSubgroupShaderStageFlags) ^ RequiredSubgroupShaderStageFlags;
|
|
const uint32 MissingOperationFlags = (Device->GetDeviceSubgroupProperties().supportedOperations & RequiredSubgroupFlags) ^ RequiredSubgroupFlags;
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Wave Operations have been DISABLED (missing stages=0x%x operations=0x%x)."), MissingStageFlags, MissingOperationFlags);
|
|
}
|
|
|
|
FHardwareInfo::RegisterHardwareInfo(NAME_RHI, TEXT("Vulkan"));
|
|
|
|
SavePipelineCacheCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.SavePipelineCache"),
|
|
TEXT("Save pipeline cache."),
|
|
FConsoleCommandDelegate::CreateStatic(SavePipelineCache),
|
|
ECVF_Default
|
|
);
|
|
|
|
RebuildPipelineCacheCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.RebuildPipelineCache"),
|
|
TEXT("Rebuilds pipeline cache."),
|
|
FConsoleCommandDelegate::CreateStatic(RebuildPipelineCache),
|
|
ECVF_Default
|
|
);
|
|
|
|
#if VULKAN_SUPPORTS_VALIDATION_CACHE
|
|
#if VULKAN_HAS_DEBUGGING_ENABLED
|
|
if (GValidationCvar.GetValueOnAnyThread() > 0)
|
|
{
|
|
SaveValidationCacheCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.SaveValidationCache"),
|
|
TEXT("Save validation cache."),
|
|
FConsoleCommandDelegate::CreateStatic(SaveValidationCache),
|
|
ECVF_Default
|
|
);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
|
|
DumpMemoryCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.DumpMemory"),
|
|
TEXT("Dumps memory map."),
|
|
FConsoleCommandDelegate::CreateStatic(DumpMemory),
|
|
ECVF_Default
|
|
);
|
|
DumpMemoryFullCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.DumpMemoryFull"),
|
|
TEXT("Dumps full memory map."),
|
|
FConsoleCommandDelegate::CreateStatic(DumpMemoryFull),
|
|
ECVF_Default
|
|
);
|
|
DumpStagingMemoryCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.DumpStagingMemory"),
|
|
TEXT("Dumps staging memory map."),
|
|
FConsoleCommandDelegate::CreateStatic(DumpStagingMemory),
|
|
ECVF_Default
|
|
);
|
|
|
|
DumpLRUCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.DumpPSOLRU"),
|
|
TEXT("Dumps Vulkan PSO LRU."),
|
|
FConsoleCommandDelegate::CreateStatic(DumpLRU),
|
|
ECVF_Default
|
|
);
|
|
TrimLRUCmd = IConsoleManager::Get().RegisterConsoleCommand(
|
|
TEXT("r.Vulkan.TrimPSOLRU"),
|
|
TEXT("Trim Vulkan PSO LRU."),
|
|
FConsoleCommandDelegate::CreateStatic(TrimLRU),
|
|
ECVF_Default
|
|
);
|
|
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS || PLATFORM_UNIX
|
|
GRHIDeviceIsIntegrated = (Device->GetDeviceProperties().deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU);
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Integrated GPU (iGPU): %s"), GRHIDeviceIsIntegrated ? TEXT("true") : TEXT("false"));
|
|
#endif
|
|
|
|
InitializeSubmissionPipe();
|
|
|
|
FRenderResource::InitPreRHIResources();
|
|
GIsRHIInitialized = true;
|
|
}
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIEndFrame_RenderThread(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
// @todo dev-pr - refactor RHIEndFrame_RenderThread to reduce use of the immediate command list, and move cleanup work to RHIEndFrame() below.
|
|
|
|
RHICmdList.EnqueueLambdaMultiPipe(ERHIPipeline::Graphics, FRHICommandListBase::EThreadFence::Enabled, TEXT("Vulkan EndFrame"),
|
|
[this](FVulkanContextArray const& Contexts)
|
|
{
|
|
FVulkanCommandListContext& Context = *Contexts[ERHIPipeline::Graphics];
|
|
|
|
check(Context.IsImmediate());
|
|
|
|
#if (RHI_NEW_GPU_PROFILER == 0)
|
|
Context.GpuProfiler.EndFrame();
|
|
#endif
|
|
|
|
bool bTrimMemory = false;
|
|
Context.FreeUnusedCmdBuffers(bTrimMemory);
|
|
|
|
Context.Device.GetStagingManager().ProcessPendingFree(false, true);
|
|
Context.Device.GetMemoryManager().ReleaseFreedPages(Context);
|
|
Context.Device.GetDeferredDeletionQueue().ReleaseResources();
|
|
|
|
if (UseVulkanDescriptorCache())
|
|
{
|
|
Context.Device.GetDescriptorSetCache().GC();
|
|
}
|
|
Context.Device.GetDescriptorPoolsManager().GC();
|
|
|
|
Context.Device.RemoveStaleQueryPools();
|
|
|
|
Context.Device.GetPipelineStateCache()->TickLRU();
|
|
|
|
Context.Device.GetBindlessDescriptorManager()->UpdateUBAllocator();
|
|
Context.Device.GetTempBlockAllocator().UpdateBlocks();
|
|
});
|
|
|
|
FDynamicRHI::RHIEndFrame_RenderThread(RHICmdList);
|
|
|
|
RHICmdList.EnqueueLambdaMultiPipe(ERHIPipeline::Graphics, FRHICommandListBase::EThreadFence::Enabled, TEXT("Vulkan BeginFrame"),
|
|
[this](FVulkanContextArray const& Contexts)
|
|
{
|
|
FVulkanCommandListContext& Context = *Contexts[ERHIPipeline::Graphics];
|
|
|
|
check(Context.IsImmediate());
|
|
|
|
extern uint32 GVulkanRHIDeletionFrameNumber;
|
|
++GVulkanRHIDeletionFrameNumber;
|
|
|
|
#if (RHI_NEW_GPU_PROFILER == 0)
|
|
Context.GpuProfiler.BeginFrame();
|
|
#endif
|
|
|
|
if (GRHISupportsRayTracing)
|
|
{
|
|
Context.Device.GetRayTracingCompactionRequestHandler()->Update(Context);
|
|
}
|
|
});
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIEndFrame(const FRHIEndFrameArgs& Args)
|
|
{
|
|
#if RHI_NEW_GPU_PROFILER
|
|
// Close the previous frame's timing and start a new one
|
|
auto Lambda = [this, OldTiming = MoveTemp(CurrentTimingPerQueue)]()
|
|
{
|
|
TArray<UE::RHI::GPUProfiler::FEventStream, TInlineAllocator<(int32)EVulkanQueueType::Count>> Streams;
|
|
for (auto const& Timing : OldTiming)
|
|
{
|
|
Streams.Add(MoveTemp(Timing->EventStream));
|
|
}
|
|
UE::RHI::GPUProfiler::ProcessEvents(Streams);
|
|
};
|
|
|
|
EnqueueEndOfPipeTask(MoveTemp(Lambda), [&](FVulkanPayload& Payload)
|
|
{
|
|
// Modify the payloads the EOP task will submit to include
|
|
// a new timing struct and a frame boundary event.
|
|
|
|
Payload.Timing = CurrentTimingPerQueue.CreateNew(Payload.Queue);
|
|
|
|
ERHIPipeline Pipeline;
|
|
switch (Payload.Queue.QueueType)
|
|
{
|
|
default: checkNoEntry(); [[fallthrough]];
|
|
case EVulkanQueueType::Graphics: Pipeline = ERHIPipeline::Graphics; break;
|
|
case EVulkanQueueType::AsyncCompute: Pipeline = ERHIPipeline::AsyncCompute; break;
|
|
|
|
case EVulkanQueueType::Transfer:
|
|
// There is currently no high level RHI copy queue support
|
|
Pipeline = ERHIPipeline::None;
|
|
break;
|
|
}
|
|
|
|
// CPU timestamp for the frame boundary event is filled in by the submission thread
|
|
Payload.EndFrameEvent = UE::RHI::GPUProfiler::FEvent::FFrameBoundary(0, Args.FrameNumber
|
|
#if WITH_RHI_BREADCRUMBS
|
|
, (Pipeline != ERHIPipeline::None) ? Args.GPUBreadcrumbs[Pipeline] : nullptr
|
|
#endif
|
|
#if STATS
|
|
, Args.StatsFrame
|
|
#endif
|
|
);
|
|
});
|
|
#else
|
|
{
|
|
FVulkanPlatformCommandList* Payloads = new FVulkanPlatformCommandList;
|
|
FVulkanPayload* Payload = new FVulkanPayload(*Device->GetGraphicsQueue());
|
|
Payload->bEndFrame = true;
|
|
Payloads->Add(Payload);
|
|
PendingPayloadsForSubmission.Enqueue(Payloads);
|
|
}
|
|
#endif
|
|
|
|
// Pump the interrupt queue to gather completed events
|
|
// (required if we're not using an interrupt thread).
|
|
ProcessInterruptQueueUntil(nullptr);
|
|
}
|
|
|
|
#if RHI_NEW_GPU_PROFILER
|
|
FVulkanTiming::FVulkanTiming(FVulkanQueue& InQueue)
|
|
: Queue(InQueue)
|
|
, EventStream(InQueue.GetProfilerQueue())
|
|
{
|
|
}
|
|
#endif
|
|
|
|
void FVulkanCommandListContext::RHIBeginDrawingViewport(FRHIViewport* ViewportRHI, FRHITexture* RenderTargetRHI)
|
|
{
|
|
//FRCLog::Printf(FString::Printf(TEXT("FVulkanCommandListContext::RHIBeginDrawingViewport\n")));
|
|
check(ViewportRHI);
|
|
FVulkanViewport* Viewport = ResourceCast(ViewportRHI);
|
|
FVulkanDynamicRHI::Get().DrawingViewport = Viewport;
|
|
|
|
FRHICustomPresent* CustomPresent = Viewport->GetCustomPresent();
|
|
if (CustomPresent)
|
|
{
|
|
CustomPresent->BeginDrawing();
|
|
}
|
|
}
|
|
|
|
void FVulkanCommandListContext::RHIEndDrawingViewport(FRHIViewport* ViewportRHI, bool bPresent, bool bLockToVsync)
|
|
{
|
|
LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanMisc);
|
|
//FRCLog::Printf(FString::Printf(TEXT("FVulkanCommandListContext::RHIEndDrawingViewport()")));
|
|
check(IsImmediate());
|
|
FVulkanViewport* Viewport = ResourceCast(ViewportRHI);
|
|
check(Viewport == FVulkanDynamicRHI::Get().DrawingViewport);
|
|
|
|
//#todo-rco: Unbind all pending state
|
|
/*
|
|
check(bPresent);
|
|
RHI->Present();
|
|
*/
|
|
FVulkanCommandBuffer& CommandBuffer = GetCommandBuffer();
|
|
check(!CommandBuffer.HasEnded() && !CommandBuffer.IsInsideRenderPass());
|
|
|
|
bool bNativePresent = Viewport->Present(*this, Device.GetPresentQueue(), bLockToVsync);
|
|
if (bNativePresent)
|
|
{
|
|
//#todo-rco: Check for r.FinishCurrentFrame
|
|
}
|
|
|
|
FVulkanDynamicRHI::Get().DrawingViewport = nullptr;
|
|
}
|
|
|
|
#if WITH_RHI_BREADCRUMBS
|
|
void FVulkanCommandListContext::RHIBeginBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb)
|
|
{
|
|
const TCHAR* NameStr = nullptr;
|
|
FRHIBreadcrumb::FBuffer Buffer;
|
|
auto GetNameStr = [&]()
|
|
{
|
|
if (!NameStr)
|
|
{
|
|
NameStr = Breadcrumb->GetTCHAR(Buffer);
|
|
}
|
|
return NameStr;
|
|
};
|
|
|
|
const FColor Color = FColor::White;
|
|
|
|
if (ShouldEmitBreadcrumbs())
|
|
{
|
|
#if VULKAN_ENABLE_DRAW_MARKERS
|
|
if (auto CmdBeginLabel = Device.GetCmdBeginDebugLabel())
|
|
{
|
|
FTCHARToUTF8 Converter(GetNameStr());
|
|
VkDebugUtilsLabelEXT Label;
|
|
ZeroVulkanStruct(Label, VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT);
|
|
Label.pLabelName = Converter.Get();
|
|
FLinearColor LColor(Color);
|
|
Label.color[0] = LColor.R;
|
|
Label.color[1] = LColor.G;
|
|
Label.color[2] = LColor.B;
|
|
Label.color[3] = LColor.A;
|
|
CmdBeginLabel(GetCommandBuffer().GetHandle(), &Label);
|
|
}
|
|
#endif
|
|
|
|
#if VULKAN_ENABLE_DUMP_LAYER
|
|
// only valid on immediate context currently. needs to be fixed for parallel rhi execute
|
|
if (IsImmediate())
|
|
{
|
|
VulkanRHI::DumpLayerPushMarker(GetNameStr());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if RHI_NEW_GPU_PROFILER
|
|
if (bSupportsBreadcrumbs)
|
|
{
|
|
FlushProfilerStats();
|
|
|
|
auto& Event = GetCommandBuffer().EmplaceProfilerEvent<UE::RHI::GPUProfiler::FEvent::FBeginBreadcrumb>(Breadcrumb);
|
|
FVulkanQueryPool* CurrentPool = GetCurrentTimestampQueryPool();
|
|
const uint32 IndexInPool = CurrentPool->ReserveQuery(&Event.GPUTimestampTOP);
|
|
VulkanRHI::vkCmdWriteTimestamp(GetCommandBuffer().GetHandle(), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, CurrentPool->GetHandle(), IndexInPool);
|
|
}
|
|
#else
|
|
if (IsImmediate())
|
|
{
|
|
#if VULKAN_SUPPORTS_GPU_CRASH_DUMPS
|
|
if (GpuProfiler.bTrackingGPUCrashData)
|
|
{
|
|
GpuProfiler.PushMarkerForCrash(GetActiveCmdBuffer(), Device.GetCrashMarkerBuffer(), GetNameStr());
|
|
}
|
|
#endif
|
|
if (GpuProfiler.IsProfilingGPU())
|
|
{
|
|
GpuProfiler.PushEvent(GetNameStr(), Color);
|
|
}
|
|
}
|
|
#endif // RHI_NEW_GPU_PROFILER
|
|
}
|
|
|
|
void FVulkanCommandListContext::RHIEndBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb)
|
|
{
|
|
#if RHI_NEW_GPU_PROFILER
|
|
if (bSupportsBreadcrumbs)
|
|
{
|
|
FlushProfilerStats();
|
|
|
|
auto& Event = GetCommandBuffer().EmplaceProfilerEvent<UE::RHI::GPUProfiler::FEvent::FEndBreadcrumb>(Breadcrumb);
|
|
FVulkanQueryPool* CurrentPool = GetCurrentTimestampQueryPool();
|
|
const uint32 IndexInPool = CurrentPool->ReserveQuery(&Event.GPUTimestampBOP);
|
|
VulkanRHI::vkCmdWriteTimestamp(GetCommandBuffer().GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, CurrentPool->GetHandle(), IndexInPool);
|
|
}
|
|
#else
|
|
//only valid on immediate context currently. needs to be fixed for parallel rhi execute
|
|
if (IsImmediate())
|
|
{
|
|
if (GpuProfiler.IsProfilingGPU())
|
|
{
|
|
GpuProfiler.PopEvent();
|
|
}
|
|
|
|
#if VULKAN_SUPPORTS_GPU_CRASH_DUMPS
|
|
if (GpuProfiler.bTrackingGPUCrashData)
|
|
{
|
|
GpuProfiler.PopMarkerForCrash(GetActiveCmdBuffer(), Device.GetCrashMarkerBuffer());
|
|
}
|
|
#endif
|
|
}
|
|
#endif // RHI_NEW_GPU_PROFILER
|
|
|
|
if (ShouldEmitBreadcrumbs())
|
|
{
|
|
#if VULKAN_ENABLE_DUMP_LAYER
|
|
if (IsImmediate())
|
|
{
|
|
VulkanRHI::DumpLayerPopMarker();
|
|
}
|
|
#endif
|
|
|
|
#if VULKAN_ENABLE_DRAW_MARKERS
|
|
if (auto CmdEndLabel = Device.GetCmdEndDebugLabel())
|
|
{
|
|
CmdEndLabel(GetCommandBuffer().GetHandle());
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
#endif // WITH_RHI_BREADCRUMBS
|
|
|
|
void FVulkanDynamicRHI::RHIGetSupportedResolution( uint32 &Width, uint32 &Height )
|
|
{
|
|
}
|
|
|
|
bool FVulkanDynamicRHI::RHIGetAvailableResolutions(FScreenResolutionArray& Resolutions, bool bIgnoreRefreshRate)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIFlushResources()
|
|
{
|
|
FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext();
|
|
bool bTrimMemory = true;
|
|
ImmediateContext.FreeUnusedCmdBuffers(bTrimMemory);
|
|
}
|
|
|
|
// IVulkanDynamicRHI interface
|
|
|
|
uint32 FVulkanDynamicRHI::RHIGetVulkanVersion() const
|
|
{
|
|
return ApiVersion;
|
|
}
|
|
|
|
VkInstance FVulkanDynamicRHI::RHIGetVkInstance() const
|
|
{
|
|
return GetInstance();
|
|
}
|
|
|
|
VkDevice FVulkanDynamicRHI::RHIGetVkDevice() const
|
|
{
|
|
if (Device)
|
|
{
|
|
return Device->GetInstanceHandle();
|
|
}
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
const uint8* FVulkanDynamicRHI::RHIGetVulkanDeviceUUID() const
|
|
{
|
|
return GetDevice()->GetDeviceIdProperties().deviceUUID;
|
|
}
|
|
|
|
VkPhysicalDevice FVulkanDynamicRHI::RHIGetVkPhysicalDevice() const
|
|
{
|
|
return Device->GetPhysicalHandle();
|
|
}
|
|
|
|
const VkAllocationCallbacks* FVulkanDynamicRHI::RHIGetVkAllocationCallbacks()
|
|
{
|
|
return VULKAN_CPU_ALLOCATOR;
|
|
}
|
|
|
|
VkQueue FVulkanDynamicRHI::RHIGetGraphicsVkQueue() const
|
|
{
|
|
return GetDevice()->GetGraphicsQueue()->GetHandle();
|
|
}
|
|
|
|
uint32 FVulkanDynamicRHI::RHIGetGraphicsQueueIndex() const
|
|
{
|
|
return GetDevice()->GetGraphicsQueue()->GetQueueIndex();
|
|
}
|
|
|
|
uint32 FVulkanDynamicRHI::RHIGetGraphicsQueueFamilyIndex() const
|
|
{
|
|
return GetDevice()->GetGraphicsQueue()->GetFamilyIndex();
|
|
}
|
|
|
|
VkCommandBuffer FVulkanDynamicRHI::RHIGetActiveVkCommandBuffer()
|
|
{
|
|
FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext();
|
|
VkCommandBuffer VulkanCommandBuffer = ImmediateContext.GetActiveCmdBuffer()->GetHandle();
|
|
return VulkanCommandBuffer;
|
|
}
|
|
|
|
uint64 FVulkanDynamicRHI::RHIGetGraphicsAdapterLUID(VkPhysicalDevice InPhysicalDevice) const
|
|
{
|
|
uint64 AdapterLUID = 0;
|
|
#if VULKAN_SUPPORTS_DRIVER_PROPERTIES
|
|
|
|
VkPhysicalDeviceProperties2KHR GpuProps2;
|
|
ZeroVulkanStruct(GpuProps2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2);
|
|
|
|
VkPhysicalDeviceIDPropertiesKHR GpuIdProps;
|
|
ZeroVulkanStruct(GpuIdProps, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES);
|
|
|
|
GpuProps2.pNext = &GpuIdProps;
|
|
|
|
VulkanRHI::vkGetPhysicalDeviceProperties2(InPhysicalDevice, &GpuProps2);
|
|
check(GpuIdProps.deviceLUIDValid);
|
|
AdapterLUID = reinterpret_cast<const uint64&>(GpuIdProps.deviceLUID);
|
|
#endif
|
|
return AdapterLUID;
|
|
}
|
|
|
|
bool FVulkanDynamicRHI::RHIDoesAdapterMatchDevice(const void* InAdapterId) const
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
const VkPhysicalDeviceIDPropertiesKHR& vkPhysicalDeviceIDProperties = GetDevice()->GetDeviceIdProperties();
|
|
if (vkPhysicalDeviceIDProperties.deviceLUIDValid)
|
|
{
|
|
return FMemory::Memcmp(InAdapterId, &vkPhysicalDeviceIDProperties.deviceLUID, sizeof(LUID)) == 0;
|
|
}
|
|
#endif
|
|
|
|
// Not enough information. Assume the adapter matches
|
|
return true;
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetVkDeviceProcAddr(const char* InName) const
|
|
{
|
|
return (void*)VulkanRHI::vkGetDeviceProcAddr(Device->GetInstanceHandle(), InName);
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetVkInstanceProcAddr(const char* InName) const
|
|
{
|
|
return (void*)VulkanRHI::vkGetInstanceProcAddr(Instance, InName);
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetVkInstanceGlobalProcAddr(const char* InName) const
|
|
{
|
|
return (void*)VulkanRHI::vkGetInstanceProcAddr(nullptr, InName);
|
|
}
|
|
|
|
VkFormat FVulkanDynamicRHI::RHIGetSwapChainVkFormat(EPixelFormat InFormat) const
|
|
{
|
|
// UE renders a gamma-corrected image so we need to use an sRGB format if available
|
|
return UEToVkTextureFormat(GPixelFormats[InFormat].UnrealFormat, true);
|
|
}
|
|
|
|
bool FVulkanDynamicRHI::RHISupportsEXTFragmentDensityMap2() const
|
|
{
|
|
return GetDevice()->GetOptionalExtensions().HasEXTFragmentDensityMap2;
|
|
}
|
|
|
|
TArray<VkExtensionProperties> FVulkanDynamicRHI::RHIGetAllInstanceExtensions() const
|
|
{
|
|
uint32_t ExtensionCount = 0;
|
|
VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &ExtensionCount, nullptr);
|
|
|
|
TArray<VkExtensionProperties> Extensions;
|
|
Extensions.SetNumUninitialized(ExtensionCount);
|
|
|
|
VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &ExtensionCount, Extensions.GetData());
|
|
|
|
return Extensions;
|
|
}
|
|
|
|
TArray<VkExtensionProperties> FVulkanDynamicRHI::RHIGetAllDeviceExtensions(VkPhysicalDevice InPhysicalDevice) const
|
|
{
|
|
uint32_t ExtensionCount = 0;
|
|
VulkanRHI::vkEnumerateDeviceExtensionProperties(InPhysicalDevice, nullptr, &ExtensionCount, nullptr);
|
|
|
|
TArray<VkExtensionProperties> Extensions;
|
|
Extensions.SetNumUninitialized(ExtensionCount);
|
|
|
|
VulkanRHI::vkEnumerateDeviceExtensionProperties(InPhysicalDevice, nullptr, &ExtensionCount, Extensions.GetData());
|
|
|
|
return Extensions;
|
|
}
|
|
|
|
TArray<FAnsiString> FVulkanDynamicRHI::RHIGetLoadedDeviceExtensions() const
|
|
{
|
|
// Create copies to prevent issues
|
|
TArray<FAnsiString> OutExtensions;
|
|
const TArray<const ANSICHAR*>& DeviceExtensions = GetDevice()->DeviceExtensions;
|
|
for (const ANSICHAR* ExtensionName : DeviceExtensions)
|
|
{
|
|
OutExtensions.Emplace(ExtensionName);
|
|
}
|
|
return OutExtensions;
|
|
}
|
|
|
|
VkImage FVulkanDynamicRHI::RHIGetVkImage(FRHITexture* InTexture) const
|
|
{
|
|
FVulkanTexture* VulkanTexture = ResourceCast(InTexture);
|
|
return VulkanTexture->Image;
|
|
}
|
|
|
|
VkFormat FVulkanDynamicRHI::RHIGetViewVkFormat(FRHITexture* InTexture) const
|
|
{
|
|
FVulkanTexture* VulkanTexture = ResourceCast(InTexture);
|
|
return VulkanTexture->ViewFormat;
|
|
}
|
|
|
|
FVulkanRHIAllocationInfo FVulkanDynamicRHI::RHIGetAllocationInfo(FRHITexture* InTexture) const
|
|
{
|
|
FVulkanTexture* VulkanTexture = ResourceCast(InTexture);
|
|
|
|
FVulkanRHIAllocationInfo NewInfo{};
|
|
NewInfo.Handle = VulkanTexture->GetAllocationHandle();
|
|
NewInfo.Offset = VulkanTexture->GetAllocationOffset();
|
|
NewInfo.Size = VulkanTexture->GetMemorySize();
|
|
|
|
return NewInfo;
|
|
}
|
|
|
|
FVulkanRHIImageViewInfo FVulkanDynamicRHI::RHIGetImageViewInfo(FRHITexture* InTexture) const
|
|
{
|
|
FVulkanTexture* VulkanTexture = ResourceCast(InTexture);
|
|
|
|
const FRHITextureDesc& Desc = InTexture->GetDesc();
|
|
|
|
FVulkanRHIImageViewInfo Info{};
|
|
Info.ImageView = VulkanTexture->DefaultView->GetTextureView().View;
|
|
Info.Image = VulkanTexture->DefaultView->GetTextureView().Image;
|
|
Info.Format = VulkanTexture->ViewFormat;
|
|
Info.Width = Desc.Extent.X;
|
|
Info.Height = Desc.Extent.Y;
|
|
Info.Depth = Desc.Depth;
|
|
Info.UEFlags = Desc.Flags;
|
|
|
|
Info.SubresourceRange.aspectMask = VulkanTexture->GetFullAspectMask();
|
|
Info.SubresourceRange.layerCount = VulkanTexture->GetNumberOfArrayLevels();
|
|
Info.SubresourceRange.levelCount = Desc.NumMips;
|
|
|
|
// TODO: do we need these?
|
|
Info.SubresourceRange.baseMipLevel = 0;
|
|
Info.SubresourceRange.baseArrayLayer = 0;
|
|
|
|
return Info;
|
|
}
|
|
|
|
FVulkanRHIAllocationInfo FVulkanDynamicRHI::RHIGetAllocationInfo(FRHIBuffer* InBuffer) const
|
|
{
|
|
FVulkanBuffer* VulkanBuffer = ResourceCast(InBuffer);
|
|
const VulkanRHI::FVulkanAllocation& Allocation = VulkanBuffer->GetCurrentAllocation();
|
|
|
|
FVulkanRHIAllocationInfo NewInfo{};
|
|
NewInfo.Handle = Allocation.GetDeviceMemoryHandle(GetDevice());
|
|
NewInfo.Offset = Allocation.Offset;
|
|
NewInfo.Size = Allocation.Size;
|
|
|
|
return NewInfo;
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHISetImageLayout(VkImage Image, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresourceRange)
|
|
{
|
|
FVulkanCommandListContext& ImmediateContext = GetDevice()->GetImmediateContext();
|
|
FVulkanCommandBuffer& CommandBuffer = ImmediateContext.GetCommandBuffer();
|
|
VulkanSetImageLayout(CommandBuffer.GetHandle(), Image, OldLayout, NewLayout, SubresourceRange);
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHISetUploadImageLayout(VkImage Image, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresourceRange)
|
|
{
|
|
FVulkanCommandListContext& ImmediateContext = GetDevice()->GetImmediateContext();
|
|
FVulkanCommandBuffer& CommandBuffer = ImmediateContext.GetCommandBuffer();
|
|
VulkanSetImageLayout(CommandBuffer.GetHandle(), Image, OldLayout, NewLayout, SubresourceRange);
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIFinishExternalComputeWork(VkCommandBuffer InCommandBuffer)
|
|
{
|
|
FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext();
|
|
check(InCommandBuffer == ImmediateContext.GetActiveCmdBuffer()->GetHandle());
|
|
|
|
ImmediateContext.GetPendingComputeState()->Reset();
|
|
ImmediateContext.GetPendingGfxState()->Reset();
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIRegisterWork(uint32 NumPrimitives)
|
|
{
|
|
#if (RHI_NEW_GPU_PROFILER == 0)
|
|
FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext();
|
|
if (FVulkanPlatform::RegisterGPUWork() && ImmediateContext.IsImmediate())
|
|
{
|
|
ImmediateContext.RegisterGPUWork(NumPrimitives);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHISubmitUploadCommandBuffer()
|
|
{
|
|
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIVerifyResult(VkResult Result, const ANSICHAR* VkFuntion, const ANSICHAR* Filename, uint32 Line)
|
|
{
|
|
VulkanRHI::VerifyVulkanResult(Result, VkFuntion, Filename, Line);
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetNativeDevice()
|
|
{
|
|
return (void*)Device->GetInstanceHandle();
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetNativePhysicalDevice()
|
|
{
|
|
return (void*)Device->GetPhysicalHandle();
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetNativeGraphicsQueue()
|
|
{
|
|
return (void*)Device->GetGraphicsQueue()->GetHandle();
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetNativeComputeQueue()
|
|
{
|
|
return Device->HasAsyncComputeQueue() ?
|
|
(void*)Device->GetComputeQueue()->GetHandle() :
|
|
(void*)Device->GetGraphicsQueue()->GetHandle();
|
|
}
|
|
|
|
void* FVulkanDynamicRHI::RHIGetNativeInstance()
|
|
{
|
|
return (void*)GetInstance();
|
|
}
|
|
|
|
IRHICommandContext* FVulkanDynamicRHI::RHIGetDefaultContext()
|
|
{
|
|
return &Device->GetImmediateContext();
|
|
}
|
|
|
|
uint64 FVulkanDynamicRHI::RHIGetMinimumAlignmentForBufferBackedSRV(EPixelFormat Format)
|
|
{
|
|
const VkPhysicalDeviceLimits& Limits = Device->GetLimits();
|
|
return Limits.minTexelBufferOffsetAlignment;
|
|
}
|
|
|
|
FTextureRHIRef FVulkanDynamicRHI::RHICreateTexture2DFromResource(EPixelFormat Format, uint32 SizeX, uint32 SizeY, uint32 NumMips, uint32 NumSamples, VkImage Resource, ETextureCreateFlags Flags, const FClearValueBinding& ClearValueBinding, const FVulkanRHIExternalImageDeleteCallbackInfo& ExternalImageDeleteCallbackInfo)
|
|
{
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create2D(TEXT("VulkanTexture2DFromResource"), SizeX, SizeY, Format)
|
|
.SetClearValue(ClearValueBinding)
|
|
.SetFlags(Flags)
|
|
.SetNumMips(NumMips)
|
|
.SetNumSamples(NumSamples)
|
|
.DetermineInititialState();
|
|
|
|
return new FVulkanTexture(*Device, Desc, Resource, ExternalImageDeleteCallbackInfo);
|
|
}
|
|
|
|
#if PLATFORM_ANDROID
|
|
FTextureRHIRef FVulkanDynamicRHI::RHICreateTexture2DFromAndroidHardwareBuffer(AHardwareBuffer* HardwareBuffer)
|
|
{
|
|
check(HardwareBuffer);
|
|
|
|
AHardwareBuffer_Desc HardwareBufferDesc;
|
|
AHardwareBuffer_describe(HardwareBuffer, &HardwareBufferDesc);
|
|
check((HardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) != 0);
|
|
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create2D(TEXT("VulkanTexture2DFromAndroidHardwareBuffer"), HardwareBufferDesc.width, HardwareBufferDesc.height, PF_Unknown)
|
|
.SetFlags(ETextureCreateFlags::External)
|
|
.DetermineInititialState();
|
|
|
|
return new FVulkanTexture(*Device, Desc, HardwareBufferDesc, HardwareBuffer);
|
|
}
|
|
#endif
|
|
|
|
FTextureRHIRef FVulkanDynamicRHI::RHICreateTexture2DArrayFromResource(EPixelFormat Format, uint32 SizeX, uint32 SizeY, uint32 ArraySize, uint32 NumMips, uint32 NumSamples, VkImage Resource, ETextureCreateFlags Flags, const FClearValueBinding& ClearValueBinding)
|
|
{
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create2DArray(TEXT("VulkanTextureArrayFromResource"), SizeX, SizeY, ArraySize, Format)
|
|
.SetClearValue(ClearValueBinding)
|
|
.SetFlags(Flags)
|
|
.SetNumMips(NumMips)
|
|
.SetNumSamples(NumSamples)
|
|
.DetermineInititialState();
|
|
|
|
return new FVulkanTexture(*Device, Desc, Resource, {});
|
|
}
|
|
|
|
FTextureRHIRef FVulkanDynamicRHI::RHICreateTextureCubeFromResource(EPixelFormat Format, uint32 Size, bool bArray, uint32 ArraySize, uint32 NumMips, VkImage Resource, ETextureCreateFlags Flags, const FClearValueBinding& ClearValueBinding)
|
|
{
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create(TEXT("VulkanTextureCubeFromResource"), ArraySize > 1 ? ETextureDimension::TextureCubeArray : ETextureDimension::TextureCube)
|
|
.SetExtent(Size)
|
|
.SetArraySize(ArraySize)
|
|
.SetFormat(Format)
|
|
.SetClearValue(ClearValueBinding)
|
|
.SetFlags(Flags)
|
|
.SetNumMips(NumMips)
|
|
.DetermineInititialState();
|
|
|
|
return new FVulkanTexture(*Device, Desc, Resource, {});
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIAliasTextureResources(FTextureRHIRef& DestTextureRHI, FTextureRHIRef& SrcTextureRHI)
|
|
{
|
|
if (DestTextureRHI && SrcTextureRHI)
|
|
{
|
|
FVulkanTexture* DestTexture = ResourceCast(DestTextureRHI);
|
|
DestTexture->AliasTextureResources(SrcTextureRHI);
|
|
}
|
|
}
|
|
|
|
FTextureRHIRef FVulkanDynamicRHI::RHICreateAliasedTexture(FTextureRHIRef& SourceTextureRHI)
|
|
{
|
|
const FString Name = SourceTextureRHI->GetName().ToString() + TEXT("Alias");
|
|
FRHITextureCreateDesc Desc(SourceTextureRHI->GetDesc(), ERHIAccess::SRVMask, *Name);
|
|
return new FVulkanTexture(*Device, Desc, SourceTextureRHI);
|
|
}
|
|
|
|
FVulkanDescriptorSetsLayout::FVulkanDescriptorSetsLayout(FVulkanDevice* InDevice) :
|
|
Device(InDevice)
|
|
{
|
|
}
|
|
|
|
FVulkanDescriptorSetsLayout::~FVulkanDescriptorSetsLayout()
|
|
{
|
|
// Handles are owned by FVulkanPipelineStateCacheManager
|
|
LayoutHandles.Reset(0);
|
|
}
|
|
|
|
// Increments a value and asserts on overflow.
|
|
// FSetInfo uses narrow integer types for descriptor counts,
|
|
// which may feasibly overflow one day (for example if we add bindless resources).
|
|
template <typename T>
|
|
static void IncrementChecked(T& Value)
|
|
{
|
|
check(Value < TNumericLimits<T>::Max());
|
|
++Value;
|
|
}
|
|
|
|
void FVulkanDescriptorSetsLayoutInfo::AddDescriptor(int32 DescriptorSetIndex, const VkDescriptorSetLayoutBinding& Descriptor)
|
|
{
|
|
// Increment type usage
|
|
if (LayoutTypes.Contains(Descriptor.descriptorType))
|
|
{
|
|
LayoutTypes[Descriptor.descriptorType]++;
|
|
}
|
|
else
|
|
{
|
|
LayoutTypes.Add(Descriptor.descriptorType, 1);
|
|
}
|
|
|
|
if (DescriptorSetIndex >= SetLayouts.Num())
|
|
{
|
|
SetLayouts.SetNum(DescriptorSetIndex + 1, EAllowShrinking::No);
|
|
}
|
|
|
|
FSetLayout& DescSetLayout = SetLayouts[DescriptorSetIndex];
|
|
|
|
VkDescriptorSetLayoutBinding* Binding = new(DescSetLayout.LayoutBindings) VkDescriptorSetLayoutBinding;
|
|
*Binding = Descriptor;
|
|
|
|
const FStageInfo& SetInfo = StageInfos[DescriptorSetIndex];
|
|
check(SetInfo.Types[Descriptor.binding] == Descriptor.descriptorType);
|
|
switch (Descriptor.descriptorType)
|
|
{
|
|
case VK_DESCRIPTOR_TYPE_SAMPLER:
|
|
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
|
|
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
|
|
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
|
|
IncrementChecked(StageInfos[DescriptorSetIndex].NumImageInfos);
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
|
|
IncrementChecked(StageInfos[DescriptorSetIndex].NumBufferInfos);
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR:
|
|
IncrementChecked(StageInfos[DescriptorSetIndex].NumAccelerationStructures);
|
|
break;
|
|
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
|
|
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
|
|
break;
|
|
default:
|
|
checkf(0, TEXT("Unsupported descriptor type %d"), (int32)Descriptor.descriptorType);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FVulkanDescriptorSetsLayoutInfo::GenerateHash(const TArrayView<FRHISamplerState*>& InImmutableSamplers, VkPipelineBindPoint InBindPoint)
|
|
{
|
|
const int32 LayoutCount = SetLayouts.Num();
|
|
Hash = FCrc::MemCrc32(&TypesUsageID, sizeof(uint32), LayoutCount);
|
|
|
|
for (int32 layoutIndex = 0; layoutIndex < LayoutCount; ++layoutIndex)
|
|
{
|
|
SetLayouts[layoutIndex].GenerateHash();
|
|
Hash = FCrc::MemCrc32(&SetLayouts[layoutIndex].Hash, sizeof(uint32), Hash);
|
|
}
|
|
|
|
const uint32 NumStages = GetNumStagesForBindPoint(InBindPoint);
|
|
for (uint32 RemapingIndex = 0; RemapingIndex < NumStages; ++RemapingIndex)
|
|
{
|
|
const FStageInfo& StageInfo = StageInfos[RemapingIndex];
|
|
|
|
Hash = FCrc::TypeCrc32(StageInfo.PackedGlobalsSize, Hash);
|
|
Hash = FCrc::TypeCrc32(StageInfo.NumBoundUniformBuffers, Hash);
|
|
Hash = FCrc::TypeCrc32(StageInfo.NumImageInfos, Hash);
|
|
Hash = FCrc::TypeCrc32(StageInfo.NumBufferInfos, Hash);
|
|
Hash = FCrc::TypeCrc32(StageInfo.NumAccelerationStructures, Hash);
|
|
|
|
const TArray<VkDescriptorType>& Types = StageInfo.Types;
|
|
Hash = FCrc::MemCrc32(Types.GetData(), sizeof(VkDescriptorType) * Types.Num(), Hash);
|
|
}
|
|
|
|
// It would be better to store this when the object is created, but it's not available at that time, so we'll do it here.
|
|
BindPoint = InBindPoint;
|
|
|
|
// Include the bind point in the hash, because we can have graphics and compute PSOs with the same descriptor info, and we don't want them to collide.
|
|
Hash = FCrc::MemCrc32(&BindPoint, sizeof(BindPoint), Hash);
|
|
}
|
|
|
|
static FCriticalSection GTypesUsageCS;
|
|
void FVulkanDescriptorSetsLayoutInfo::CompileTypesUsageID()
|
|
{
|
|
FScopeLock ScopeLock(>ypesUsageCS);
|
|
|
|
static TMap<uint32, uint32> GTypesUsageHashMap;
|
|
static uint32 GUniqueID = 1;
|
|
|
|
LayoutTypes.KeySort([](VkDescriptorType A, VkDescriptorType B)
|
|
{
|
|
return static_cast<uint32>(A) < static_cast<uint32>(B);
|
|
});
|
|
|
|
uint32 TypesUsageHash = 0;
|
|
for (const auto& Elem : LayoutTypes)
|
|
{
|
|
TypesUsageHash = FCrc::MemCrc32(&Elem.Value, sizeof(uint32), TypesUsageHash);
|
|
}
|
|
|
|
uint32* UniqueID = GTypesUsageHashMap.Find(TypesUsageHash);
|
|
if (UniqueID == nullptr)
|
|
{
|
|
TypesUsageID = GTypesUsageHashMap.Add(TypesUsageHash, GUniqueID++);
|
|
}
|
|
else
|
|
{
|
|
TypesUsageID = *UniqueID;
|
|
}
|
|
}
|
|
|
|
void FVulkanDescriptorSetsLayout::Compile(FVulkanDescriptorSetLayoutMap& DSetLayoutMap)
|
|
{
|
|
check(LayoutHandles.Num() == 0);
|
|
|
|
// Check if we obey limits
|
|
const VkPhysicalDeviceLimits& Limits = Device->GetLimits();
|
|
|
|
// Check for maxDescriptorSetSamplers
|
|
check( LayoutTypes[VK_DESCRIPTOR_TYPE_SAMPLER]
|
|
+ LayoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER]
|
|
<= Limits.maxDescriptorSetSamplers);
|
|
|
|
// Check for maxDescriptorSetUniformBuffers
|
|
check( LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]
|
|
+ LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC]
|
|
<= Limits.maxDescriptorSetUniformBuffers);
|
|
|
|
// Check for maxDescriptorSetUniformBuffersDynamic
|
|
check(Device->GetVendorId() == EGpuVendorId::Amd ||
|
|
LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC]
|
|
<= Limits.maxDescriptorSetUniformBuffersDynamic);
|
|
|
|
// Check for maxDescriptorSetStorageBuffers
|
|
check( LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER]
|
|
+ LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC]
|
|
<= Limits.maxDescriptorSetStorageBuffers);
|
|
|
|
// Check for maxDescriptorSetStorageBuffersDynamic
|
|
check( LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC]
|
|
<= Limits.maxDescriptorSetStorageBuffersDynamic);
|
|
|
|
// Check for maxDescriptorSetSampledImages
|
|
check( LayoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER]
|
|
+ LayoutTypes[VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE]
|
|
+ LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]
|
|
<= Limits.maxDescriptorSetSampledImages);
|
|
|
|
// Check for maxDescriptorSetStorageImages
|
|
check( LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_IMAGE]
|
|
+ LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER]
|
|
<= Limits.maxDescriptorSetStorageImages);
|
|
|
|
check(LayoutTypes[VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT] <= Limits.maxDescriptorSetInputAttachments);
|
|
|
|
if (GRHISupportsRayTracing)
|
|
{
|
|
check(LayoutTypes[VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR] < Device->GetOptionalExtensionProperties().AccelerationStructureProps.maxDescriptorSetAccelerationStructures);
|
|
}
|
|
|
|
LayoutHandles.Empty(SetLayouts.Num());
|
|
|
|
if (UseVulkanDescriptorCache())
|
|
{
|
|
LayoutHandleIds.Empty(SetLayouts.Num());
|
|
}
|
|
|
|
for (FSetLayout& Layout : SetLayouts)
|
|
{
|
|
VkDescriptorSetLayout* LayoutHandle = new(LayoutHandles) VkDescriptorSetLayout;
|
|
|
|
uint32* LayoutHandleId = nullptr;
|
|
if (UseVulkanDescriptorCache())
|
|
{
|
|
LayoutHandleId = new(LayoutHandleIds) uint32;
|
|
}
|
|
|
|
if (FVulkanDescriptorSetLayoutEntry* Found = DSetLayoutMap.Find(Layout))
|
|
{
|
|
*LayoutHandle = Found->Handle;
|
|
if (LayoutHandleId)
|
|
{
|
|
*LayoutHandleId = Found->HandleId;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
VkDescriptorSetLayoutCreateInfo DescriptorLayoutInfo;
|
|
ZeroVulkanStruct(DescriptorLayoutInfo, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO);
|
|
DescriptorLayoutInfo.bindingCount = Layout.LayoutBindings.Num();
|
|
DescriptorLayoutInfo.pBindings = Layout.LayoutBindings.GetData();
|
|
|
|
VERIFYVULKANRESULT(VulkanRHI::vkCreateDescriptorSetLayout(Device->GetInstanceHandle(), &DescriptorLayoutInfo, VULKAN_CPU_ALLOCATOR, LayoutHandle));
|
|
|
|
if (LayoutHandleId)
|
|
{
|
|
*LayoutHandleId = ++GVulkanDSetLayoutHandleIdCounter;
|
|
}
|
|
|
|
FVulkanDescriptorSetLayoutEntry DescriptorSetLayoutEntry;
|
|
DescriptorSetLayoutEntry.Handle = *LayoutHandle;
|
|
DescriptorSetLayoutEntry.HandleId = LayoutHandleId ? *LayoutHandleId : 0;
|
|
|
|
DSetLayoutMap.Add(Layout, DescriptorSetLayoutEntry);
|
|
}
|
|
|
|
if (TypesUsageID == ~0)
|
|
{
|
|
CompileTypesUsageID();
|
|
}
|
|
|
|
ZeroVulkanStruct(DescriptorSetAllocateInfo, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO);
|
|
DescriptorSetAllocateInfo.descriptorSetCount = LayoutHandles.Num();
|
|
DescriptorSetAllocateInfo.pSetLayouts = LayoutHandles.GetData();
|
|
}
|
|
|
|
FVulkanRenderPass::FVulkanRenderPass(FVulkanDevice& InDevice, const FVulkanRenderTargetLayout& InRTLayout) :
|
|
Layout(InRTLayout),
|
|
RenderPass(VK_NULL_HANDLE),
|
|
NumUsedClearValues(InRTLayout.GetNumUsedClearValues()),
|
|
Device(InDevice)
|
|
{
|
|
INC_DWORD_STAT(STAT_VulkanNumRenderPasses);
|
|
RenderPass = CreateVulkanRenderPass(InDevice, InRTLayout);
|
|
}
|
|
|
|
FVulkanRenderPass::~FVulkanRenderPass()
|
|
{
|
|
DEC_DWORD_STAT(STAT_VulkanNumRenderPasses);
|
|
|
|
Device.GetDeferredDeletionQueue().EnqueueResource(VulkanRHI::FDeferredDeletionQueue2::EType::RenderPass, RenderPass);
|
|
RenderPass = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void FVulkanDynamicRHI::SavePipelineCache()
|
|
{
|
|
FString CacheFile = VulkanRHI::GetPipelineCacheFilename();
|
|
|
|
GVulkanRHI->Device->PipelineStateCache->Save(CacheFile);
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RebuildPipelineCache()
|
|
{
|
|
GVulkanRHI->Device->PipelineStateCache->RebuildCache();
|
|
}
|
|
|
|
#if VULKAN_SUPPORTS_VALIDATION_CACHE
|
|
void FVulkanDynamicRHI::SaveValidationCache()
|
|
{
|
|
VkValidationCacheEXT ValidationCache = GVulkanRHI->Device->GetValidationCache();
|
|
if (ValidationCache != VK_NULL_HANDLE)
|
|
{
|
|
VkDevice Device = GVulkanRHI->Device->GetInstanceHandle();
|
|
PFN_vkGetValidationCacheDataEXT vkGetValidationCacheData = (PFN_vkGetValidationCacheDataEXT)(void*)VulkanRHI::vkGetDeviceProcAddr(Device, "vkGetValidationCacheDataEXT");
|
|
check(vkGetValidationCacheData);
|
|
size_t CacheSize = 0;
|
|
VkResult Result = vkGetValidationCacheData(Device, ValidationCache, &CacheSize, nullptr);
|
|
if (Result == VK_SUCCESS)
|
|
{
|
|
if (CacheSize > 0)
|
|
{
|
|
TArray<uint8> Data;
|
|
Data.AddUninitialized(CacheSize);
|
|
Result = vkGetValidationCacheData(Device, ValidationCache, &CacheSize, Data.GetData());
|
|
if (Result == VK_SUCCESS)
|
|
{
|
|
FString CacheFilename = VulkanRHI::GetValidationCacheFilename();
|
|
if (FFileHelper::SaveArrayToFile(Data, *CacheFilename))
|
|
{
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Saved validation cache file '%s', %d bytes"), *CacheFilename, Data.Num());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Failed to query Vulkan validation cache data, VkResult=%d"), Result);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Failed to query Vulkan validation cache size, VkResult=%d"), Result);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
|
|
void FVulkanDynamicRHI::DumpMemory()
|
|
{
|
|
GVulkanRHI->Device->GetMemoryManager().DumpMemory(false);
|
|
}
|
|
void FVulkanDynamicRHI::DumpMemoryFull()
|
|
{
|
|
GVulkanRHI->Device->GetMemoryManager().DumpMemory(true);
|
|
}
|
|
void FVulkanDynamicRHI::DumpStagingMemory()
|
|
{
|
|
GVulkanRHI->Device->GetStagingManager().DumpMemory();
|
|
}
|
|
void FVulkanDynamicRHI::DumpLRU()
|
|
{
|
|
GVulkanRHI->Device->PipelineStateCache->LRUDump();
|
|
}
|
|
void FVulkanDynamicRHI::TrimLRU()
|
|
{
|
|
GVulkanRHI->Device->PipelineStateCache->LRUDebugEvictAll();
|
|
}
|
|
#endif
|
|
|
|
void FVulkanDynamicRHI::VulkanSetImageLayout(VkCommandBuffer CmdBuffer, VkImage Image, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresourceRange)
|
|
{
|
|
FVulkanPipelineBarrier Barrier;
|
|
Barrier.AddImageLayoutTransition(Image, OldLayout, NewLayout, SubresourceRange);
|
|
Barrier.Execute(CmdBuffer);
|
|
}
|
|
|
|
IRHITransientResourceAllocator* FVulkanDynamicRHI::RHICreateTransientResourceAllocator()
|
|
{
|
|
#if VULKAN_SUPPORTS_TRANSIENT_RESOURCE_ALLOCATOR
|
|
// Only use transient heap on desktop platforms for now
|
|
// Not compatible with VulkanDescriptorCache for now because it hashes using the 32bit BufferId instead of the VulkanHandle.
|
|
if (GVulkanEnableTransientResourceAllocator && IsPCPlatform(GMaxRHIShaderPlatform) && !UseVulkanDescriptorCache())
|
|
{
|
|
return new FVulkanTransientResourceAllocator(Device->GetOrCreateTransientHeapCache());
|
|
}
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
uint32 FVulkanDynamicRHI::GetPrecachePSOHashVersion()
|
|
{
|
|
static const uint32 PrecacheHashVersion = 3;
|
|
return PrecacheHashVersion;
|
|
}
|
|
|
|
// If you modify this function bump then GetPrecachePSOHashVersion, this will invalidate any previous uses of the hash.
|
|
// i.e. pre-existing PSO caches must be rebuilt.
|
|
uint64 FVulkanDynamicRHI::RHIComputeStatePrecachePSOHash(const FGraphicsPipelineStateInitializer& Initializer)
|
|
{
|
|
struct FHashKey
|
|
{
|
|
uint32 VertexDeclaration;
|
|
uint32 VertexShader;
|
|
uint32 PixelShader;
|
|
#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
|
|
uint32 GeometryShader;
|
|
#endif // PLATFORM_SUPPORTS_GEOMETRY_SHADERS
|
|
#if PLATFORM_SUPPORTS_MESH_SHADERS
|
|
uint32 MeshShader;
|
|
uint32 TaskShader;
|
|
#endif // PLATFORM_SUPPORTS_MESH_SHADERS
|
|
uint32 BlendState;
|
|
uint32 RasterizerState;
|
|
uint32 DepthStencilState;
|
|
uint32 ImmutableSamplerState;
|
|
|
|
uint32 DrawShadingRate : 8;
|
|
uint32 PrimitiveType : 8;
|
|
uint32 bDepthBounds : 1;
|
|
uint32 bAllowVariableRateShading : 1;
|
|
uint32 Unused : 14;
|
|
} HashKey;
|
|
|
|
FMemory::Memzero(&HashKey, sizeof(FHashKey));
|
|
|
|
// We know for sure that on ARM MALI GPUs vertex decl does not affect PSO
|
|
const bool bVertexDeclAffectsPSO = (GRHIVendorId != (uint32)EGpuVendorId::Arm);
|
|
if (bVertexDeclAffectsPSO)
|
|
{
|
|
HashKey.VertexDeclaration = Initializer.BoundShaderState.VertexDeclarationRHI ? Initializer.BoundShaderState.VertexDeclarationRHI->GetPrecachePSOHash() : 0;
|
|
}
|
|
HashKey.VertexShader = Initializer.BoundShaderState.GetVertexShader() ? GetTypeHash(Initializer.BoundShaderState.GetVertexShader()->GetHash()) : 0;
|
|
HashKey.PixelShader = Initializer.BoundShaderState.GetPixelShader() ? GetTypeHash(Initializer.BoundShaderState.GetPixelShader()->GetHash()) : 0;
|
|
#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
|
|
HashKey.GeometryShader = Initializer.BoundShaderState.GetGeometryShader() ? GetTypeHash(Initializer.BoundShaderState.GetGeometryShader()->GetHash()) : 0;
|
|
#endif
|
|
#if PLATFORM_SUPPORTS_MESH_SHADERS
|
|
HashKey.MeshShader = Initializer.BoundShaderState.GetMeshShader() ? GetTypeHash(Initializer.BoundShaderState.GetMeshShader()->GetHash()) : 0;
|
|
HashKey.TaskShader = Initializer.BoundShaderState.GetAmplificationShader() ? GetTypeHash(Initializer.BoundShaderState.GetAmplificationShader()->GetHash()) : 0;
|
|
#endif
|
|
|
|
FBlendStateInitializerRHI BlendStateInitializerRHI;
|
|
if (Initializer.BlendState && Initializer.BlendState->GetInitializer(BlendStateInitializerRHI))
|
|
{
|
|
HashKey.BlendState = GetTypeHash(BlendStateInitializerRHI);
|
|
}
|
|
FRasterizerStateInitializerRHI RasterizerStateInitializerRHI;
|
|
if (Initializer.RasterizerState && Initializer.RasterizerState->GetInitializer(RasterizerStateInitializerRHI))
|
|
{
|
|
HashKey.RasterizerState = GetTypeHash(RasterizerStateInitializerRHI);
|
|
}
|
|
FDepthStencilStateInitializerRHI DepthStencilStateInitializerRHI;
|
|
if (Initializer.DepthStencilState && Initializer.DepthStencilState->GetInitializer(DepthStencilStateInitializerRHI))
|
|
{
|
|
HashKey.DepthStencilState = GetTypeHash(DepthStencilStateInitializerRHI);
|
|
}
|
|
|
|
// Ignore immutable samplers for now
|
|
//HashKey.ImmutableSamplerState = GetTypeHash(ImmutableSamplerState);
|
|
|
|
HashKey.DrawShadingRate = Initializer.ShadingRate;
|
|
HashKey.PrimitiveType = Initializer.PrimitiveType;
|
|
HashKey.bDepthBounds = Initializer.bDepthBounds;
|
|
HashKey.bAllowVariableRateShading = Initializer.bAllowVariableRateShading;
|
|
|
|
uint64 PrecachePSOHash = CityHash64((const char*)&HashKey, sizeof(FHashKey));
|
|
|
|
return PrecachePSOHash;
|
|
}
|
|
|
|
// If you modify this function bump then GetPrecachePSOHashVersion, this will invalidate any previous uses of the hash.
|
|
// i.e. pre-existing PSO caches must be rebuilt.
|
|
uint64 FVulkanDynamicRHI::RHIComputePrecachePSOHash(const FGraphicsPipelineStateInitializer& Initializer)
|
|
{
|
|
|
|
// When compute precache PSO hash we assume a valid state precache PSO hash is already provided
|
|
uint64 StatePrecachePSOHash = Initializer.StatePrecachePSOHash;
|
|
if (StatePrecachePSOHash == 0)
|
|
{
|
|
StatePrecachePSOHash = RHIComputeStatePrecachePSOHash(Initializer);
|
|
}
|
|
|
|
// All members which are not part of the state objects
|
|
struct FNonStateHashKey
|
|
{
|
|
uint64 StatePrecachePSOHash;
|
|
|
|
uint32 RenderTargetsEnabled;
|
|
FGraphicsPipelineStateInitializer::TRenderTargetFormats RenderTargetFormats;
|
|
FGraphicsPipelineStateInitializer::TRenderTargetFlags RenderTargetFlags;
|
|
// AJB: temporarily disabling depth stencil properties as they do not appear to be required and it causes us to miss some permutations.
|
|
// EPixelFormat DepthStencilTargetFormat;
|
|
// ETextureCreateFlags DepthStencilTargetFlag;
|
|
uint16 NumSamples;
|
|
ESubpassHint SubpassHint;
|
|
uint8 SubpassIndex;
|
|
uint8 MultiViewCount;
|
|
bool bHasFragmentDensityAttachment;
|
|
EConservativeRasterization ConservativeRasterization;
|
|
} HashKey;
|
|
|
|
FMemory::Memzero(&HashKey, sizeof(FNonStateHashKey));
|
|
|
|
HashKey.StatePrecachePSOHash = StatePrecachePSOHash;
|
|
|
|
HashKey.RenderTargetsEnabled = Initializer.RenderTargetsEnabled;
|
|
HashKey.RenderTargetFormats = Initializer.RenderTargetFormats;
|
|
HashKey.RenderTargetFlags = Initializer.RenderTargetFlags;
|
|
// HashKey.DepthStencilTargetFormat = Initializer.DepthStencilTargetFormat;
|
|
// HashKey.DepthStencilTargetFlag = Initializer.DepthStencilTargetFlag;
|
|
HashKey.NumSamples = Initializer.NumSamples;
|
|
HashKey.SubpassHint = Initializer.SubpassHint;
|
|
HashKey.SubpassIndex = Initializer.SubpassIndex;
|
|
HashKey.MultiViewCount = Initializer.MultiViewCount;
|
|
HashKey.bHasFragmentDensityAttachment = Initializer.bHasFragmentDensityAttachment;
|
|
HashKey.ConservativeRasterization = Initializer.ConservativeRasterization;
|
|
|
|
// TODO: check if any RT flags actually affect PSO in VK
|
|
for (ETextureCreateFlags& Flags : HashKey.RenderTargetFlags)
|
|
{
|
|
Flags = Flags & FGraphicsPipelineStateInitializer::RelevantRenderTargetFlagMask;
|
|
}
|
|
// HashKey.DepthStencilTargetFlag = (HashKey.DepthStencilTargetFlag & FGraphicsPipelineStateInitializer::RelevantDepthStencilFlagMask);
|
|
|
|
return CityHash64((const char*)&HashKey, sizeof(FNonStateHashKey));
|
|
}
|
|
|
|
bool FVulkanDynamicRHI::RHIMatchPrecachePSOInitializers(const FGraphicsPipelineStateInitializer& LHS, const FGraphicsPipelineStateInitializer& RHS)
|
|
{
|
|
// first check non pointer objects
|
|
if (LHS.ImmutableSamplerState != RHS.ImmutableSamplerState ||
|
|
LHS.PrimitiveType != RHS.PrimitiveType ||
|
|
LHS.bDepthBounds != RHS.bDepthBounds ||
|
|
LHS.MultiViewCount != RHS.MultiViewCount ||
|
|
LHS.ShadingRate != RHS.ShadingRate ||
|
|
LHS.bHasFragmentDensityAttachment != RHS.bHasFragmentDensityAttachment ||
|
|
LHS.bAllowVariableRateShading != RHS.bAllowVariableRateShading ||
|
|
LHS.RenderTargetsEnabled != RHS.RenderTargetsEnabled ||
|
|
LHS.RenderTargetFormats != RHS.RenderTargetFormats ||
|
|
!FGraphicsPipelineStateInitializer::RelevantRenderTargetFlagsEqual(LHS.RenderTargetFlags, RHS.RenderTargetFlags) ||
|
|
LHS.DepthStencilTargetFormat != RHS.DepthStencilTargetFormat ||
|
|
!FGraphicsPipelineStateInitializer::RelevantDepthStencilFlagsEqual(LHS.DepthStencilTargetFlag, RHS.DepthStencilTargetFlag) ||
|
|
LHS.NumSamples != RHS.NumSamples ||
|
|
LHS.SubpassHint != RHS.SubpassHint ||
|
|
LHS.SubpassIndex != RHS.SubpassIndex ||
|
|
LHS.StatePrecachePSOHash != RHS.StatePrecachePSOHash ||
|
|
LHS.ConservativeRasterization != RHS.ConservativeRasterization)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check the RHI shaders (pointer check for shaders should be fine)
|
|
if (LHS.BoundShaderState.VertexShaderRHI != RHS.BoundShaderState.VertexShaderRHI ||
|
|
LHS.BoundShaderState.PixelShaderRHI != RHS.BoundShaderState.PixelShaderRHI ||
|
|
LHS.BoundShaderState.GetMeshShader() != RHS.BoundShaderState.GetMeshShader() ||
|
|
LHS.BoundShaderState.GetAmplificationShader() != RHS.BoundShaderState.GetAmplificationShader() ||
|
|
LHS.BoundShaderState.GetGeometryShader() != RHS.BoundShaderState.GetGeometryShader())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Full compare the of the vertex declaration
|
|
if (!MatchRHIState<FRHIVertexDeclaration, FVertexDeclarationElementList>(LHS.BoundShaderState.VertexDeclarationRHI, RHS.BoundShaderState.VertexDeclarationRHI))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check actual state content (each initializer can have it's own state and not going through a factory)
|
|
if (!MatchRHIState<FRHIBlendState, FBlendStateInitializerRHI>(LHS.BlendState, RHS.BlendState)
|
|
|| !MatchRHIState<FRHIRasterizerState, FRasterizerStateInitializerRHI>(LHS.RasterizerState, RHS.RasterizerState)
|
|
|| !MatchRHIState<FRHIDepthStencilState, FDepthStencilStateInitializerRHI>(LHS.DepthStencilState, RHS.DepthStencilState)
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FVulkanDynamicRHI::RHIReplaceResources(FRHICommandListBase& RHICmdList, TArray<FRHIResourceReplaceInfo>&& ReplaceInfos)
|
|
{
|
|
RHICmdList.EnqueueLambda(TEXT("FVulkanDynamicRHI::RHIReplaceResources"),
|
|
[ReplaceInfos = MoveTemp(ReplaceInfos)](FRHICommandListBase& ExecutingCmdList)
|
|
{
|
|
for (FRHIResourceReplaceInfo const& Info : ReplaceInfos)
|
|
{
|
|
switch (Info.GetType())
|
|
{
|
|
default:
|
|
checkNoEntry();
|
|
break;
|
|
|
|
case FRHIResourceReplaceInfo::EType::Buffer:
|
|
{
|
|
FVulkanBuffer* Dst = ResourceCast(Info.GetBuffer().Dst);
|
|
FVulkanBuffer* Src = ResourceCast(Info.GetBuffer().Src);
|
|
|
|
if (Src)
|
|
{
|
|
// The source buffer should not have any associated views.
|
|
check(!Src->HasLinkedViews());
|
|
|
|
Dst->TakeOwnership(*Src);
|
|
}
|
|
else
|
|
{
|
|
Dst->ReleaseOwnership();
|
|
}
|
|
|
|
Dst->UpdateLinkedViews();
|
|
}
|
|
break;
|
|
|
|
case FRHIResourceReplaceInfo::EType::RTGeometry:
|
|
{
|
|
FVulkanRayTracingGeometry* Src = ResourceCast(Info.GetRTGeometry().Src);
|
|
FVulkanRayTracingGeometry* Dst = ResourceCast(Info.GetRTGeometry().Dst);
|
|
|
|
if (!Src)
|
|
{
|
|
TRefCountPtr<FVulkanRayTracingGeometry> DeletionProxy = new FVulkanRayTracingGeometry(NoInit);
|
|
Dst->RemoveCompactionRequest();
|
|
Dst->Swap(*DeletionProxy);
|
|
}
|
|
else
|
|
{
|
|
Dst->Swap(*Src);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
RHICmdList.RHIThreadFence(true);
|
|
}
|
|
|
|
#if PLATFORM_SUPPORTS_BINDLESS_RENDERING
|
|
FRHIResourceCollectionRef FVulkanDynamicRHI::RHICreateResourceCollection(FRHICommandListBase& RHICmdList, TConstArrayView<FRHIResourceCollectionMember> InMembers)
|
|
{
|
|
return new FVulkanResourceCollection(RHICmdList, InMembers);
|
|
}
|
|
#endif
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|