918 lines
32 KiB
C++
918 lines
32 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
VulkanSwapChain.cpp: Vulkan viewport RHI definitions.
|
|
=============================================================================*/
|
|
|
|
#include "VulkanSwapChain.h"
|
|
#include "VulkanCommandWrappers.h"
|
|
#include "VulkanDevice.h"
|
|
#include "VulkanPlatform.h"
|
|
#include "VulkanRHIPrivate.h"
|
|
#include "VulkanSubmission.h"
|
|
#include "Engine/RendererSettings.h"
|
|
#include "HAL/PlatformFramePacer.h"
|
|
#include "IHeadMountedDisplayModule.h"
|
|
#include "IHeadMountedDisplayVulkanExtensions.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "RHIUtilities.h"
|
|
|
|
#if PLATFORM_ANDROID
|
|
// this path crashes within libvulkan during vkDestroySwapchainKHR on some versions of Android. See FORT-250079
|
|
int32 GVulkanKeepSwapChain = 0;
|
|
#else
|
|
int32 GVulkanKeepSwapChain = 1;
|
|
#endif
|
|
static FAutoConsoleVariableRef CVarVulkanKeepSwapChain(
|
|
TEXT("r.Vulkan.KeepSwapChain"),
|
|
GVulkanKeepSwapChain,
|
|
TEXT("Whether to keep old swap chain to pass through when creating the next one"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GVulkanSwapChainIgnoreExtraImages = 0;
|
|
static FAutoConsoleVariableRef CVarVulkanSwapChainIgnoreExtraImages(
|
|
TEXT("r.Vulkan.SwapChainIgnoreExtraImages"),
|
|
GVulkanSwapChainIgnoreExtraImages,
|
|
TEXT("Whether to ignore extra images created in swapchain and stick with a requested number of images"),
|
|
ECVF_ReadOnly
|
|
);
|
|
|
|
int32 GShouldCpuWaitForFence = 1;
|
|
static FAutoConsoleVariableRef CVarCpuWaitForFence(
|
|
TEXT("r.Vulkan.CpuWaitForFence"),
|
|
GShouldCpuWaitForFence,
|
|
TEXT("Whether to have the Cpu wait for the fence in AcquireImageIndex"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
//disabled by default in swapchain creation if the extension frame pacer is available
|
|
int32 GVulkanCPURenderThreadFramePacer = 1;
|
|
static FAutoConsoleVariableRef CVarVulkanCPURenderThreadFramePacer(
|
|
TEXT("r.Vulkan.CPURenderthreadFramePacer"),
|
|
GVulkanCPURenderThreadFramePacer,
|
|
TEXT("Whether to enable the simple Render thread CPU Framepacer for Vulkan"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GVulkanCPURHIFramePacer = 1;
|
|
static FAutoConsoleVariableRef CVarVulkanCPURHIFramePacer(
|
|
TEXT("r.Vulkan.CPURHIThreadFramePacer"),
|
|
GVulkanCPURHIFramePacer,
|
|
TEXT("Whether to enable the simple RHI thread CPU Framepacer for Vulkan"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GVulkanForcePacingWithoutVSync = 0;
|
|
static FAutoConsoleVariableRef CVarVulkanForcePacingWithoutVSync(
|
|
TEXT("r.Vulkan.ForcePacingWithoutVSync"),
|
|
GVulkanForcePacingWithoutVSync,
|
|
TEXT("Whether to CPU pacers remain enabled even if VSync is off"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarVulkanPreTransform(
|
|
TEXT("r.Vulkan.PreTransform"),
|
|
1,
|
|
TEXT("0: Surface transformed in Vulkan Presentation Engine")
|
|
TEXT("1: Surface transformed in App to save bandwidth (Default)"),
|
|
ECVF_ReadOnly);
|
|
|
|
int32 GPrintVulkanVsyncDebug = 0;
|
|
#if !(UE_BUILD_SHIPPING)
|
|
static FAutoConsoleVariableRef CVarVulkanDebugVsync(
|
|
TEXT("r.Vulkan.DebugVsync"),
|
|
GPrintVulkanVsyncDebug,
|
|
TEXT("Whether to print vulkan vsync data"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
#endif
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
|
|
bool GSimulateLostSurfaceInNextTick = false;
|
|
bool GSimulateSuboptimalSurfaceInNextTick = false;
|
|
|
|
// A self registering exec helper to check for the VULKAN_* commands.
|
|
class FVulkanCommandsHelper : public FSelfRegisteringExec
|
|
{
|
|
virtual bool Exec_Dev(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
|
|
{
|
|
if (FParse::Command(&Cmd, TEXT("VULKAN_SIMULATE_LOST_SURFACE")))
|
|
{
|
|
GSimulateLostSurfaceInNextTick = true;
|
|
Ar.Log(FString::Printf(TEXT("Vulkan: simulating lost surface next frame")));
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Cmd, TEXT("VULKAN_SIMULATE_SUBOPTIMAL_SURFACE")))
|
|
{
|
|
GSimulateSuboptimalSurfaceInNextTick = true;
|
|
Ar.Log(FString::Printf(TEXT("Vulkan: simulating suboptimal surface next frame")));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
static FVulkanCommandsHelper GVulkanCommandsHelper;
|
|
|
|
VkResult SimulateErrors(VkResult Result)
|
|
{
|
|
if (GSimulateLostSurfaceInNextTick)
|
|
{
|
|
GSimulateLostSurfaceInNextTick = false;
|
|
return VK_ERROR_SURFACE_LOST_KHR;
|
|
}
|
|
|
|
if (GSimulateSuboptimalSurfaceInNextTick)
|
|
{
|
|
GSimulateSuboptimalSurfaceInNextTick = false;
|
|
return VK_SUBOPTIMAL_KHR;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
#endif
|
|
|
|
extern TAutoConsoleVariable<int32> GAllowPresentOnComputeQueue;
|
|
static TSet<EPixelFormat> GPixelFormatNotSupportedWarning;
|
|
|
|
bool IsVulkanPreTransformEnabled(const FStaticShaderPlatform Platform)
|
|
{
|
|
return (IsVulkanMobilePlatform(Platform) || IsVulkanMobileSM5Platform(Platform))
|
|
// Request an intermediate image as the BackBuffer
|
|
&& GVulkanDelayAcquireImage == EDelayAcquireImageType::DelayAcquire
|
|
&& CVarVulkanPreTransform.GetValueOnAnyThread() > 0;
|
|
}
|
|
|
|
FVulkanSwapChain::FVulkanSwapChain(VkInstance InInstance, FVulkanDevice& InDevice, EPixelFormat& InOutPixelFormat, uint32 Width, uint32 Height, bool bIsFullScreen,
|
|
uint32* InOutDesiredNumBackBuffers, TArray<VkImage>& OutImages, int8 InLockToVsync, FVulkanPlatformWindowContext& WindowContext, FVulkanSwapChainRecreateInfo* RecreateInfo)
|
|
: SwapChain(VK_NULL_HANDLE)
|
|
, Device(InDevice)
|
|
, Surface(VK_NULL_HANDLE)
|
|
, WindowHandle(WindowContext.GetWindowHandle())
|
|
, CurrentImageIndex(-1)
|
|
, SemaphoreIndex(0)
|
|
, NumPresentCalls(0)
|
|
, NumAcquireCalls(0)
|
|
, Instance(InInstance)
|
|
, LockToVsync(InLockToVsync)
|
|
{
|
|
NextPresentTargetTime = (FPlatformTime::Seconds() - GStartTime);
|
|
|
|
if (RecreateInfo != nullptr && RecreateInfo->SwapChain != VK_NULL_HANDLE)
|
|
{
|
|
check(RecreateInfo->Surface != VK_NULL_HANDLE);
|
|
Surface = RecreateInfo->Surface;
|
|
RecreateInfo->Surface = VK_NULL_HANDLE;
|
|
}
|
|
else
|
|
{
|
|
// let the platform create the surface
|
|
FVulkanPlatform::CreateSurface(WindowContext, Instance, &Surface);
|
|
}
|
|
|
|
uint32 NumFormats;
|
|
VERIFYVULKANRESULT_EXPANDED(VulkanRHI::vkGetPhysicalDeviceSurfaceFormatsKHR(Device.GetPhysicalHandle(), Surface, &NumFormats, nullptr));
|
|
check(NumFormats > 0);
|
|
|
|
TArray<VkSurfaceFormatKHR> Formats;
|
|
Formats.AddZeroed(NumFormats);
|
|
VERIFYVULKANRESULT_EXPANDED(VulkanRHI::vkGetPhysicalDeviceSurfaceFormatsKHR(Device.GetPhysicalHandle(), Surface, &NumFormats, Formats.GetData()));
|
|
|
|
VkColorSpaceKHR RequestedColorSpace = Formats[0].colorSpace;
|
|
|
|
// If multiple colorspaces are possible, then use CVarHDROutputDevice to narrow it down
|
|
for (int32 Index = 1; Index < Formats.Num(); ++Index)
|
|
{
|
|
if (Formats[Index].colorSpace != RequestedColorSpace)
|
|
{
|
|
static const auto CVarHDROutputDevice = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.HDR.Display.OutputDevice"));
|
|
EDisplayOutputFormat OutputDevice = CVarHDROutputDevice ? (EDisplayOutputFormat)CVarHDROutputDevice->GetValueOnAnyThread() : EDisplayOutputFormat::SDR_sRGB;
|
|
switch (OutputDevice)
|
|
{
|
|
case EDisplayOutputFormat::SDR_sRGB:
|
|
RequestedColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
|
break;
|
|
case EDisplayOutputFormat::SDR_Rec709:
|
|
RequestedColorSpace = VK_COLOR_SPACE_BT709_NONLINEAR_EXT;
|
|
break;
|
|
case EDisplayOutputFormat::HDR_ACES_1000nit_ST2084:
|
|
case EDisplayOutputFormat::HDR_ACES_2000nit_ST2084:
|
|
RequestedColorSpace = VK_COLOR_SPACE_HDR10_ST2084_EXT;
|
|
break;
|
|
default:
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Requested color format %d not supported in Vulkan, falling back to sRGB. Please check the value of r.HDR.Display.OutputDevice."), int(OutputDevice));
|
|
RequestedColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find Pixel format for presentable images
|
|
VkSurfaceFormatKHR CurrFormat;
|
|
FMemory::Memzero(CurrFormat);
|
|
{
|
|
if (InOutPixelFormat == PF_Unknown)
|
|
{
|
|
static const auto* CVarDefaultBackBufferPixelFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DefaultBackBufferPixelFormat"));
|
|
InOutPixelFormat = CVarDefaultBackBufferPixelFormat ? EDefaultBackBufferPixelFormat::Convert2PixelFormat(EDefaultBackBufferPixelFormat::FromInt(CVarDefaultBackBufferPixelFormat->GetValueOnGameThread())) : PF_Unknown;
|
|
}
|
|
|
|
if (InOutPixelFormat != PF_Unknown)
|
|
{
|
|
bool bFound = false;
|
|
if (GPixelFormats[InOutPixelFormat].Supported)
|
|
{
|
|
VkFormat Requested = (VkFormat)GPixelFormats[InOutPixelFormat].PlatformFormat;
|
|
for (int32 Index = 0; Index < Formats.Num(); ++Index)
|
|
{
|
|
if (Formats[Index].format == Requested)
|
|
{
|
|
CurrFormat = Formats[Index];
|
|
bFound = true;
|
|
|
|
// We stop the search if both the pixel format and color space are a match. However, if we can't find a matching color space, we'll still use one of the
|
|
// formats that at least matches the pixel format.
|
|
if (CurrFormat.colorSpace == RequestedColorSpace)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
if (!GPixelFormatNotSupportedWarning.Contains(InOutPixelFormat))
|
|
{
|
|
GPixelFormatNotSupportedWarning.Add(InOutPixelFormat);
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Requested PixelFormat %d not supported by this swapchain! Falling back to supported swapchain format..."), (uint32)InOutPixelFormat);
|
|
}
|
|
InOutPixelFormat = PF_Unknown;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Requested PixelFormat %d not supported by this Vulkan implementation!"), (uint32)InOutPixelFormat);
|
|
InOutPixelFormat = PF_Unknown;
|
|
}
|
|
}
|
|
|
|
if (InOutPixelFormat == PF_Unknown)
|
|
{
|
|
for (int32 Index = 0; Index < Formats.Num(); ++Index)
|
|
{
|
|
// Reverse lookup
|
|
check(Formats[Index].format != VK_FORMAT_UNDEFINED);
|
|
for (int32 PFIndex = 0; PFIndex < PF_MAX; ++PFIndex)
|
|
{
|
|
if (Formats[Index].format == GPixelFormats[PFIndex].PlatformFormat && Formats[Index].colorSpace == RequestedColorSpace)
|
|
{
|
|
InOutPixelFormat = (EPixelFormat)PFIndex;
|
|
CurrFormat = Formats[Index];
|
|
UE_LOG(LogVulkanRHI, Verbose, TEXT("No swapchain format requested, picking up VulkanFormat %s"), VK_TYPE_TO_STRING(VkFormat, CurrFormat.format));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (InOutPixelFormat != PF_Unknown)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (InOutPixelFormat == PF_Unknown)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Can't find a proper pixel format for the swapchain, trying to pick up the first available"));
|
|
VkFormat PlatformFormat = UEToVkTextureFormat(InOutPixelFormat, false);
|
|
bool bSupported = false;
|
|
for (int32 Index = 0; Index < Formats.Num(); ++Index)
|
|
{
|
|
if (Formats[Index].format == PlatformFormat && Formats[Index].colorSpace == RequestedColorSpace)
|
|
{
|
|
bSupported = true;
|
|
CurrFormat = Formats[Index];
|
|
break;
|
|
}
|
|
}
|
|
|
|
check(bSupported);
|
|
}
|
|
|
|
if (InOutPixelFormat == PF_Unknown)
|
|
{
|
|
FString Msg;
|
|
for (int32 Index = 0; Index < Formats.Num(); ++Index)
|
|
{
|
|
if (Index == 0)
|
|
{
|
|
Msg += TEXT("(");
|
|
}
|
|
else
|
|
{
|
|
Msg += TEXT(", ");
|
|
}
|
|
Msg += FString::Printf(TEXT("%d/%d"), (int32)Formats[Index].format, (int32)Formats[Index].colorSpace);
|
|
}
|
|
if (Formats.Num())
|
|
{
|
|
Msg += TEXT(")");
|
|
}
|
|
UE_LOG(LogVulkanRHI, Fatal, TEXT("Unable to find a pixel format for the swapchain; swapchain returned %d Vulkan formats %s"), Formats.Num(), *Msg);
|
|
}
|
|
}
|
|
|
|
if (CurrFormat.colorSpace != RequestedColorSpace)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Requested color format %d not supported by this Vulkan implementation, falling back to %d. Please check the value of r.HDR.Display.OutputDevice."), (int32)RequestedColorSpace, (int32)CurrFormat.colorSpace);
|
|
}
|
|
|
|
VkFormat PlatformFormat = UEToVkTextureFormat(InOutPixelFormat, false);
|
|
|
|
Device.SetupPresentQueue(Surface);
|
|
|
|
// Fetch present mode
|
|
VkPresentModeKHR PresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
|
if (FVulkanPlatform::SupportsQuerySurfaceProperties())
|
|
{
|
|
// Only dump the present modes the very first time they are queried
|
|
static bool bFirstTimeLog = !!VULKAN_HAS_DEBUGGING_ENABLED;
|
|
|
|
uint32 NumFoundPresentModes = 0;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkGetPhysicalDeviceSurfacePresentModesKHR(Device.GetPhysicalHandle(), Surface, &NumFoundPresentModes, nullptr));
|
|
check(NumFoundPresentModes > 0);
|
|
|
|
TArray<VkPresentModeKHR> FoundPresentModes;
|
|
FoundPresentModes.AddZeroed(NumFoundPresentModes);
|
|
VERIFYVULKANRESULT(VulkanRHI::vkGetPhysicalDeviceSurfacePresentModesKHR(Device.GetPhysicalHandle(), Surface, &NumFoundPresentModes, FoundPresentModes.GetData()));
|
|
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Display, TEXT("Found %d Surface present modes:"), NumFoundPresentModes);
|
|
|
|
bool bFoundPresentModeMailbox = false;
|
|
bool bFoundPresentModeImmediate = false;
|
|
bool bFoundPresentModeFIFO = false;
|
|
|
|
for (size_t i = 0; i < NumFoundPresentModes; i++)
|
|
{
|
|
switch (FoundPresentModes[i])
|
|
{
|
|
case VK_PRESENT_MODE_MAILBOX_KHR:
|
|
bFoundPresentModeMailbox = true;
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Display, TEXT("- VK_PRESENT_MODE_MAILBOX_KHR (%d)"), (int32)VK_PRESENT_MODE_MAILBOX_KHR);
|
|
break;
|
|
case VK_PRESENT_MODE_IMMEDIATE_KHR:
|
|
bFoundPresentModeImmediate = true;
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Display, TEXT("- VK_PRESENT_MODE_IMMEDIATE_KHR (%d)"), (int32)VK_PRESENT_MODE_IMMEDIATE_KHR);
|
|
break;
|
|
case VK_PRESENT_MODE_FIFO_KHR:
|
|
bFoundPresentModeFIFO = true;
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Display, TEXT("- VK_PRESENT_MODE_FIFO_KHR (%d)"), (int32)VK_PRESENT_MODE_FIFO_KHR);
|
|
break;
|
|
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Display, TEXT("- VK_PRESENT_MODE_FIFO_RELAXED_KHR (%d)"), (int32)VK_PRESENT_MODE_FIFO_RELAXED_KHR);
|
|
break;
|
|
default:
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Display, TEXT("- VkPresentModeKHR %d"), (int32)FoundPresentModes[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
int32 RequestedPresentMode = -1;
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("vulkanpresentmode="), RequestedPresentMode))
|
|
{
|
|
bool bRequestSuccessful = false;
|
|
switch (RequestedPresentMode)
|
|
{
|
|
case VK_PRESENT_MODE_MAILBOX_KHR:
|
|
if (bFoundPresentModeMailbox)
|
|
{
|
|
PresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
|
bRequestSuccessful = true;
|
|
}
|
|
break;
|
|
case VK_PRESENT_MODE_IMMEDIATE_KHR:
|
|
if (bFoundPresentModeImmediate)
|
|
{
|
|
PresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
|
bRequestSuccessful = true;
|
|
}
|
|
break;
|
|
case VK_PRESENT_MODE_FIFO_KHR:
|
|
if (bFoundPresentModeFIFO)
|
|
{
|
|
PresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
|
bRequestSuccessful = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!bRequestSuccessful)
|
|
{
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Warning, TEXT("Requested PresentMode (%d) is not handled or available, ignoring..."), RequestedPresentMode);
|
|
RequestedPresentMode = -1;
|
|
}
|
|
}
|
|
|
|
if (RequestedPresentMode == -1)
|
|
{
|
|
// Until FVulkanViewport::Present honors SyncInterval, we need to disable vsync for the spectator window if using an HMD.
|
|
const bool bDisableVsyncForHMD = (FVulkanDynamicRHI::HMDVulkanExtensions.IsValid()) ? FVulkanDynamicRHI::HMDVulkanExtensions->ShouldDisableVulkanVSync() : false;
|
|
|
|
if (bFoundPresentModeImmediate && (bDisableVsyncForHMD || !LockToVsync))
|
|
{
|
|
PresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
|
}
|
|
else if (bFoundPresentModeMailbox)
|
|
{
|
|
PresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
|
}
|
|
else if (bFoundPresentModeFIFO)
|
|
{
|
|
PresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Couldn't find desired PresentMode! Using %s"), VK_TYPE_TO_STRING(VkPresentModeKHR, FoundPresentModes[0]));
|
|
PresentMode = FoundPresentModes[0];
|
|
}
|
|
}
|
|
|
|
UE_CLOG(bFirstTimeLog, LogVulkanRHI, Display, TEXT("Selected VkPresentModeKHR mode %s"), VK_TYPE_TO_STRING(VkPresentModeKHR, PresentMode));
|
|
bFirstTimeLog = false;
|
|
}
|
|
|
|
// Check the surface properties and formats
|
|
|
|
VkSurfaceCapabilitiesKHR SurfProperties;
|
|
VERIFYVULKANRESULT_EXPANDED(VulkanRHI::vkGetPhysicalDeviceSurfaceCapabilitiesKHR(Device.GetPhysicalHandle(),
|
|
Surface,
|
|
&SurfProperties));
|
|
|
|
if (!IsVulkanPreTransformEnabled(GMaxRHIShaderPlatform))
|
|
{
|
|
PreTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
|
}
|
|
else
|
|
{
|
|
PreTransform = SurfProperties.currentTransform;
|
|
}
|
|
|
|
VkCompositeAlphaFlagBitsKHR CompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
|
|
if (SurfProperties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
|
|
{
|
|
CompositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
}
|
|
|
|
// 0 means no limit, so use the requested number
|
|
uint32 DesiredNumBuffers = SurfProperties.maxImageCount > 0 ? FMath::Clamp(*InOutDesiredNumBackBuffers, SurfProperties.minImageCount, SurfProperties.maxImageCount) : *InOutDesiredNumBackBuffers;
|
|
|
|
uint32 SizeX = FVulkanPlatform::SupportsQuerySurfaceProperties() ? (SurfProperties.currentExtent.width == 0xFFFFFFFF ? Width : SurfProperties.currentExtent.width) : Width;
|
|
uint32 SizeY = FVulkanPlatform::SupportsQuerySurfaceProperties() ? (SurfProperties.currentExtent.height == 0xFFFFFFFF ? Height : SurfProperties.currentExtent.height) : Height;
|
|
|
|
VkSwapchainCreateInfoKHR SwapChainInfo;
|
|
ZeroVulkanStruct(SwapChainInfo, VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR);
|
|
SwapChainInfo.surface = Surface;
|
|
SwapChainInfo.minImageCount = DesiredNumBuffers;
|
|
SwapChainInfo.imageFormat = CurrFormat.format;
|
|
SwapChainInfo.imageColorSpace = CurrFormat.colorSpace;
|
|
SwapChainInfo.imageExtent.width = SizeX;
|
|
SwapChainInfo.imageExtent.height = SizeY;
|
|
SwapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
SwapChainInfo.preTransform = PreTransform;
|
|
SwapChainInfo.imageArrayLayers = 1;
|
|
SwapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
SwapChainInfo.presentMode = PresentMode;
|
|
SwapChainInfo.oldSwapchain = VK_NULL_HANDLE;
|
|
if(RecreateInfo != nullptr)
|
|
{
|
|
SwapChainInfo.oldSwapchain = RecreateInfo->SwapChain;
|
|
}
|
|
|
|
SwapChainInfo.clipped = VK_TRUE;
|
|
SwapChainInfo.compositeAlpha = CompositeAlpha;
|
|
|
|
{
|
|
//#todo-rco: Crappy workaround
|
|
if (SwapChainInfo.imageExtent.width == 0)
|
|
{
|
|
SwapChainInfo.imageExtent.width = Width;
|
|
}
|
|
if (SwapChainInfo.imageExtent.height == 0)
|
|
{
|
|
SwapChainInfo.imageExtent.height = Height;
|
|
}
|
|
}
|
|
|
|
VkBool32 bSupportsPresent;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkGetPhysicalDeviceSurfaceSupportKHR(Device.GetPhysicalHandle(), Device.GetPresentQueue()->GetFamilyIndex(), Surface, &bSupportsPresent));
|
|
ensure(bSupportsPresent);
|
|
|
|
//ensure(SwapChainInfo.imageExtent.width >= SurfProperties.minImageExtent.width && SwapChainInfo.imageExtent.width <= SurfProperties.maxImageExtent.width);
|
|
//ensure(SwapChainInfo.imageExtent.height >= SurfProperties.minImageExtent.height && SwapChainInfo.imageExtent.height <= SurfProperties.maxImageExtent.height);
|
|
static bool bPrintSwapchainCreationInfo = true;
|
|
if (bPrintSwapchainCreationInfo)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Creating new VK swapchain with %s, %s, %s, num images %d"),
|
|
VK_TYPE_TO_STRING(VkPresentModeKHR, SwapChainInfo.presentMode), VK_TYPE_TO_STRING(VkFormat, SwapChainInfo.imageFormat),
|
|
VK_TYPE_TO_STRING(VkColorSpaceKHR, SwapChainInfo.imageColorSpace), static_cast<uint32>(SwapChainInfo.minImageCount));
|
|
#if WITH_EDITOR
|
|
bPrintSwapchainCreationInfo = false;
|
|
#endif
|
|
}
|
|
|
|
#if VULKAN_SUPPORTS_FULLSCREEN_EXCLUSIVE
|
|
VkSurfaceFullScreenExclusiveInfoEXT FullScreenInfo;
|
|
ZeroVulkanStruct(FullScreenInfo, VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT);
|
|
if (Device.GetOptionalExtensions().HasEXTFullscreenExclusive)
|
|
{
|
|
FullScreenInfo.fullScreenExclusive = bIsFullScreen ? VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT : VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT;
|
|
FullScreenInfo.pNext = (void*)SwapChainInfo.pNext;
|
|
SwapChainInfo.pNext = &FullScreenInfo;
|
|
}
|
|
#endif
|
|
|
|
const bool bSwapChainNeedsTransform = PreTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || PreTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
|
|
if (bSwapChainNeedsTransform)
|
|
{
|
|
Swap(SwapChainInfo.imageExtent.width, SwapChainInfo.imageExtent.height);
|
|
}
|
|
|
|
VkResult Result = FVulkanPlatform::CreateSwapchainKHR(WindowContext, Device.GetPhysicalHandle(), Device.GetInstanceHandle(), &SwapChainInfo, VULKAN_CPU_ALLOCATOR, &SwapChain);
|
|
|
|
// Swap the aspect ratio back to make it transparent to high level
|
|
if (bSwapChainNeedsTransform)
|
|
{
|
|
Swap(SwapChainInfo.imageExtent.width, SwapChainInfo.imageExtent.height);
|
|
}
|
|
|
|
#if VULKAN_SUPPORTS_FULLSCREEN_EXCLUSIVE
|
|
if (Device.GetOptionalExtensions().HasEXTFullscreenExclusive && Result == VK_ERROR_INITIALIZATION_FAILED)
|
|
{
|
|
// Unlink fullscreen
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Create swapchain failed with Initialization error; removing FullScreen extension..."));
|
|
SwapChainInfo.pNext = FullScreenInfo.pNext;
|
|
Result = FVulkanPlatform::CreateSwapchainKHR(WindowContext, Device.GetPhysicalHandle(), Device.GetInstanceHandle(), &SwapChainInfo, VULKAN_CPU_ALLOCATOR, &SwapChain);
|
|
}
|
|
#endif
|
|
VERIFYVULKANRESULT_EXPANDED(Result);
|
|
|
|
if (RecreateInfo != nullptr)
|
|
{
|
|
if (RecreateInfo->SwapChain != VK_NULL_HANDLE)
|
|
{
|
|
FVulkanPlatform::DestroySwapchainKHR(Device.GetInstanceHandle(), RecreateInfo->SwapChain, VULKAN_CPU_ALLOCATOR);
|
|
RecreateInfo->SwapChain = VK_NULL_HANDLE;
|
|
}
|
|
if (RecreateInfo->Surface != VK_NULL_HANDLE)
|
|
{
|
|
VulkanRHI::vkDestroySurfaceKHR(Instance, RecreateInfo->Surface, VULKAN_CPU_ALLOCATOR);
|
|
RecreateInfo->Surface = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
|
|
InternalWidth = FMath::Min(Width, SwapChainInfo.imageExtent.width);
|
|
InternalHeight = FMath::Min(Height, SwapChainInfo.imageExtent.height);
|
|
bInternalFullScreen = bIsFullScreen;
|
|
|
|
uint32 NumSwapChainImages;
|
|
VERIFYVULKANRESULT_EXPANDED(VulkanRHI::vkGetSwapchainImagesKHR(Device.GetInstanceHandle(), SwapChain, &NumSwapChainImages, nullptr));
|
|
|
|
if (GVulkanSwapChainIgnoreExtraImages != 0)
|
|
{
|
|
NumSwapChainImages = DesiredNumBuffers;
|
|
}
|
|
|
|
OutImages.AddUninitialized(NumSwapChainImages);
|
|
VERIFYVULKANRESULT_EXPANDED(VulkanRHI::vkGetSwapchainImagesKHR(Device.GetInstanceHandle(), SwapChain, &NumSwapChainImages, OutImages.GetData()));
|
|
|
|
#if VULKAN_USE_IMAGE_ACQUIRE_FENCES
|
|
ImageAcquiredFences.AddUninitialized(NumSwapChainImages);
|
|
VulkanRHI::FFenceManager& FenceMgr = Device.GetFenceManager();
|
|
for (uint32 BufferIndex = 0; BufferIndex < NumSwapChainImages; ++BufferIndex)
|
|
{
|
|
ImageAcquiredFences[BufferIndex] = Device.GetFenceManager().AllocateFence(true);
|
|
}
|
|
#endif
|
|
ImageAcquiredSemaphore.AddUninitialized(NumSwapChainImages);
|
|
for (uint32 BufferIndex = 0; BufferIndex < NumSwapChainImages; ++BufferIndex)
|
|
{
|
|
ImageAcquiredSemaphore[BufferIndex] = new VulkanRHI::FSemaphore(Device, VulkanRHI::EVulkanSemaphoreFlags::ExternallySignaled);
|
|
ImageAcquiredSemaphore[BufferIndex]->AddRef();
|
|
}
|
|
|
|
*InOutDesiredNumBackBuffers = NumSwapChainImages;
|
|
|
|
PresentID = 0;
|
|
}
|
|
|
|
void FVulkanSwapChain::Destroy(FVulkanSwapChainRecreateInfo* RecreateInfo)
|
|
{
|
|
// We could be responding to an OUT_OF_DATE event and the GPU might not be done with swapchain image, so wait for idle.
|
|
// Alternatively could also check on the fence(s) for the image(s) from the swapchain but then timing out/waiting could become an issue.
|
|
Device.WaitUntilIdle();
|
|
|
|
bool bRecreate = RecreateInfo && GVulkanKeepSwapChain;
|
|
if (bRecreate)
|
|
{
|
|
RecreateInfo->SwapChain = SwapChain;
|
|
RecreateInfo->Surface = Surface;
|
|
}
|
|
else
|
|
{
|
|
FVulkanPlatform::DestroySwapchainKHR(Device.GetInstanceHandle(), SwapChain, VULKAN_CPU_ALLOCATOR);
|
|
}
|
|
SwapChain = VK_NULL_HANDLE;
|
|
|
|
#if VULKAN_USE_IMAGE_ACQUIRE_FENCES
|
|
VulkanRHI::FFenceManager& FenceMgr = Device.GetFenceManager();
|
|
for (int32 Index = 0; Index < ImageAcquiredFences.Num(); ++Index)
|
|
{
|
|
FenceMgr.ReleaseFence(ImageAcquiredFences[Index]);
|
|
}
|
|
#endif
|
|
|
|
//#todo-rco: Enqueue for deletion as we first need to destroy the cmd buffers and queues otherwise validation fails
|
|
for (int BufferIndex = 0; BufferIndex < ImageAcquiredSemaphore.Num(); ++BufferIndex)
|
|
{
|
|
ImageAcquiredSemaphore[BufferIndex]->Release();
|
|
}
|
|
|
|
if(!bRecreate)
|
|
{
|
|
VulkanRHI::vkDestroySurfaceKHR(Instance, Surface, VULKAN_CPU_ALLOCATOR);
|
|
}
|
|
|
|
Surface = VK_NULL_HANDLE;
|
|
}
|
|
|
|
int32 FVulkanSwapChain::AcquireImageIndex(VulkanRHI::FSemaphore** OutSemaphore)
|
|
{
|
|
check(CurrentImageIndex == -1);
|
|
|
|
// Get the index of the next swapchain image we should render to.
|
|
// We'll wait with an "infinite" timeout, the function will block until an image is ready.
|
|
// The ImageAcquiredSemaphore[ImageAcquiredSemaphoreIndex] will get signaled when the image is ready (upon function return).
|
|
uint32 ImageIndex = 0;
|
|
const int32 PrevSemaphoreIndex = SemaphoreIndex;
|
|
SemaphoreIndex = (SemaphoreIndex + 1) % ImageAcquiredSemaphore.Num();
|
|
|
|
// If we have not called present for any of the swapchain images, it will cause a crash/hang
|
|
checkf(!(NumAcquireCalls == ImageAcquiredSemaphore.Num() - 1 && NumPresentCalls == 0), TEXT("vkAcquireNextImageKHR will fail as no images have been presented before acquiring all of them"));
|
|
#if VULKAN_USE_IMAGE_ACQUIRE_FENCES
|
|
VulkanRHI::FFenceManager& FenceMgr = Device.GetFenceManager();
|
|
FenceMgr.ResetFence(ImageAcquiredFences[SemaphoreIndex]);
|
|
const VkFence AcquiredFence = ImageAcquiredFences[SemaphoreIndex]->GetHandle();
|
|
#else
|
|
const VkFence AcquiredFence = VK_NULL_HANDLE;
|
|
#endif
|
|
VkResult Result;
|
|
{
|
|
const uint32 MaxImageIndex = ImageAcquiredSemaphore.Num() - 1;
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_VulkanAcquireBackBuffer);
|
|
FRenderThreadIdleScope IdleScope(ERenderThreadIdleTypes::WaitingForGPUPresent);
|
|
|
|
Result = VulkanRHI::vkAcquireNextImageKHR(
|
|
Device.GetInstanceHandle(),
|
|
SwapChain,
|
|
UINT64_MAX,
|
|
ImageAcquiredSemaphore[SemaphoreIndex]->GetHandle(),
|
|
AcquiredFence,
|
|
&ImageIndex);
|
|
|
|
// The swapchain may have more images than we have requested on creating it. Ignore all extra images
|
|
while (ImageIndex > MaxImageIndex && (Result == VK_SUCCESS || Result == VK_SUBOPTIMAL_KHR))
|
|
{
|
|
Result = VulkanRHI::vkAcquireNextImageKHR(
|
|
Device.GetInstanceHandle(),
|
|
SwapChain,
|
|
UINT64_MAX,
|
|
ImageAcquiredSemaphore[SemaphoreIndex]->GetHandle(),
|
|
AcquiredFence,
|
|
&ImageIndex);
|
|
}
|
|
}
|
|
|
|
if (Result == VK_ERROR_OUT_OF_DATE_KHR)
|
|
{
|
|
SemaphoreIndex = PrevSemaphoreIndex;
|
|
return (int32)EStatus::OutOfDate;
|
|
}
|
|
|
|
if (Result == VK_ERROR_SURFACE_LOST_KHR)
|
|
{
|
|
SemaphoreIndex = PrevSemaphoreIndex;
|
|
return (int32)EStatus::SurfaceLost;
|
|
}
|
|
|
|
++NumAcquireCalls;
|
|
*OutSemaphore = ImageAcquiredSemaphore[SemaphoreIndex];
|
|
|
|
#if VULKAN_HAS_DEBUGGING_ENABLED
|
|
if (Result == VK_ERROR_VALIDATION_FAILED_EXT)
|
|
{
|
|
extern TAutoConsoleVariable<int32> GValidationCvar;
|
|
if (GValidationCvar.GetValueOnRenderThread() == 0)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Fatal, TEXT("vkAcquireNextImageKHR failed with Validation error. Try running with r.Vulkan.EnableValidation=1 to get information from the driver"));
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
checkf(Result == VK_SUCCESS || Result == VK_SUBOPTIMAL_KHR, TEXT("vkAcquireNextImageKHR failed Result = %d"), int32(Result));
|
|
}
|
|
CurrentImageIndex = (int32)ImageIndex;
|
|
|
|
#if VULKAN_USE_IMAGE_ACQUIRE_FENCES
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_VulkanWaitSwapchain);
|
|
bool bResult = FenceMgr.WaitForFence(ImageAcquiredFences[SemaphoreIndex], UINT64_MAX);
|
|
ensure(bResult);
|
|
}
|
|
#endif
|
|
return CurrentImageIndex;
|
|
}
|
|
|
|
void FVulkanSwapChain::RenderThreadPacing()
|
|
{
|
|
check(IsInRenderingThread());
|
|
const int32 SyncInterval = (LockToVsync || GVulkanForcePacingWithoutVSync) ? RHIGetSyncInterval() : 0;
|
|
|
|
//very naive CPU side frame pacer.
|
|
if (GVulkanCPURenderThreadFramePacer && SyncInterval > 0)
|
|
{
|
|
double NowCPUTime = FPlatformTime::Seconds();
|
|
double DeltaCPUPresentTimeMS = (NowCPUTime - RTPacingPreviousFrameCPUTime) * 1000.0;
|
|
|
|
|
|
double TargetIntervalWithEpsilonMS = (double)SyncInterval * (1.0 / 60.0) * 1000.0;
|
|
const double IntervalThresholdMS = TargetIntervalWithEpsilonMS * 0.1;
|
|
|
|
RTPacingSampledDeltaTimeMS += DeltaCPUPresentTimeMS; RTPacingSampleCount++;
|
|
|
|
double SampledDeltaMS = (RTPacingSampledDeltaTimeMS / (double)RTPacingSampleCount) + IntervalThresholdMS;
|
|
|
|
if (RTPacingSampleCount > 1000)
|
|
{
|
|
RTPacingSampledDeltaTimeMS = SampledDeltaMS;
|
|
RTPacingSampleCount = 1;
|
|
}
|
|
|
|
if (SampledDeltaMS < (TargetIntervalWithEpsilonMS))
|
|
{
|
|
FRenderThreadIdleScope IdleScope(ERenderThreadIdleTypes::WaitingForGPUPresent);
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_StallForEmulatedSyncInterval);
|
|
|
|
FPlatformProcess::SleepNoStats((TargetIntervalWithEpsilonMS - SampledDeltaMS) * 0.001f);
|
|
if (GPrintVulkanVsyncDebug)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("CPU RT delta: %f, TargetWEps: %f, sleepTime: %f "), SampledDeltaMS, TargetIntervalWithEpsilonMS, TargetIntervalWithEpsilonMS - DeltaCPUPresentTimeMS);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GPrintVulkanVsyncDebug)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("CPU RT delta: %f"), DeltaCPUPresentTimeMS);
|
|
}
|
|
}
|
|
RTPacingPreviousFrameCPUTime = NowCPUTime;
|
|
}
|
|
}
|
|
|
|
FVulkanSwapChain::EStatus FVulkanSwapChain::Present(FVulkanQueue* PresentQueue, VulkanRHI::FSemaphore* BackBufferRenderingDoneSemaphore)
|
|
{
|
|
check(CurrentImageIndex != -1);
|
|
|
|
VkPresentInfoKHR Info;
|
|
ZeroVulkanStruct(Info, VK_STRUCTURE_TYPE_PRESENT_INFO_KHR);
|
|
VkSemaphore Semaphore = VK_NULL_HANDLE;
|
|
if (BackBufferRenderingDoneSemaphore)
|
|
{
|
|
Info.waitSemaphoreCount = 1;
|
|
Semaphore = BackBufferRenderingDoneSemaphore->GetHandle();
|
|
Info.pWaitSemaphores = &Semaphore;
|
|
}
|
|
Info.swapchainCount = 1;
|
|
Info.pSwapchains = &SwapChain;
|
|
Info.pImageIndices = (uint32*)&CurrentImageIndex;
|
|
|
|
bool bPlatformHandlesFramePacing = FVulkanPlatform::FramePace(Device, WindowHandle, SwapChain, PresentID, Info);
|
|
|
|
if (!bPlatformHandlesFramePacing)
|
|
{
|
|
const int32 FramePace = (LockToVsync || GVulkanForcePacingWithoutVSync) ? FPlatformRHIFramePacer::GetFramePace() : 0;
|
|
|
|
//very naive CPU side frame pacer.
|
|
if (GVulkanCPURHIFramePacer && FramePace > 0)
|
|
{
|
|
const double NowCPUTime = (FPlatformTime::Seconds() - GStartTime);
|
|
|
|
const double TimeToSleep = (NextPresentTargetTime - NowCPUTime);
|
|
const double TargetIntervalWithEpsilon = 1.0 / (double)FramePace;
|
|
|
|
if (TimeToSleep > 0.0)
|
|
{
|
|
FRenderThreadIdleScope IdleScope(ERenderThreadIdleTypes::WaitingForGPUPresent);
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_StallForEmulatedSyncInterval);
|
|
FPlatformProcess::SleepNoStats(static_cast<float>(TimeToSleep));
|
|
if (GPrintVulkanVsyncDebug)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("CurrentID: %i, CPU TimeToSleep: %f, TargetWEps: %f"), PresentID, TimeToSleep * 1000.0, TargetIntervalWithEpsilon * 1000.0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GPrintVulkanVsyncDebug)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("CurrentID: %i, CPU TimeToSleep: %f"), PresentID, TimeToSleep * 1000.0);
|
|
}
|
|
}
|
|
NextPresentTargetTime = FMath::Max(NextPresentTargetTime + TargetIntervalWithEpsilon, NowCPUTime);
|
|
}
|
|
}
|
|
PresentID++;
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_VulkanQueuePresent);
|
|
|
|
VkResult PresentResult;
|
|
{
|
|
FRenderThreadIdleScope IdleScope(ERenderThreadIdleTypes::WaitingForGPUPresent);
|
|
PresentResult = FVulkanPlatform::Present(PresentQueue->GetHandle(), Info);
|
|
}
|
|
|
|
CurrentImageIndex = -1;
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
PresentResult = SimulateErrors(PresentResult);
|
|
#endif
|
|
|
|
if (PresentResult == VK_ERROR_OUT_OF_DATE_KHR)
|
|
{
|
|
return EStatus::OutOfDate;
|
|
}
|
|
|
|
if (PresentResult == VK_ERROR_SURFACE_LOST_KHR)
|
|
{
|
|
return EStatus::SurfaceLost;
|
|
}
|
|
|
|
if (PresentResult != VK_SUCCESS && PresentResult != VK_SUBOPTIMAL_KHR)
|
|
{
|
|
VERIFYVULKANRESULT(PresentResult);
|
|
}
|
|
}
|
|
|
|
++NumPresentCalls;
|
|
|
|
return EStatus::Healthy;
|
|
}
|
|
|
|
void FVulkanDevice::SetupPresentQueue(VkSurfaceKHR Surface)
|
|
{
|
|
if (!PresentQueue)
|
|
{
|
|
const auto SupportsPresent = [Surface](VkPhysicalDevice PhysicalDevice, FVulkanQueue* Queue)
|
|
{
|
|
VkBool32 bSupportsPresent = VK_FALSE;
|
|
const uint32 FamilyIndex = Queue->GetFamilyIndex();
|
|
VERIFYVULKANRESULT(VulkanRHI::vkGetPhysicalDeviceSurfaceSupportKHR(PhysicalDevice, FamilyIndex, Surface, &bSupportsPresent));
|
|
if (bSupportsPresent)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Display, TEXT("Queue Family %d: Supports Present"), FamilyIndex);
|
|
}
|
|
return (bSupportsPresent == VK_TRUE);
|
|
};
|
|
|
|
const bool bGfx = SupportsPresent(Gpu, GetGraphicsQueue());
|
|
if (!bGfx)
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("Cannot find a compatible Vulkan device that supports surface presentation.\n\n"), TEXT("Vulkan device not available"));
|
|
FPlatformMisc::RequestExitWithStatus(true, 1);
|
|
}
|
|
PresentQueue = GetGraphicsQueue();
|
|
|
|
if (HasAsyncComputeQueue())
|
|
{
|
|
const bool bCompute = SupportsPresent(Gpu, GetComputeQueue());
|
|
if (bCompute && (GAllowPresentOnComputeQueue.GetValueOnAnyThread() != 0))
|
|
{
|
|
//#todo-rco: Do other IHVs have a fast path here?
|
|
bPresentOnComputeQueue = (VendorId == EGpuVendorId::Amd);
|
|
PresentQueue = GetComputeQueue();
|
|
}
|
|
}
|
|
}
|
|
}
|