2136 lines
83 KiB
C++
2136 lines
83 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "VulkanAndroidPlatform.h"
|
|
#include "../VulkanRayTracing.h"
|
|
#include "../VulkanPipeline.h"
|
|
#include "../VulkanRenderpass.h"
|
|
#include <dlfcn.h>
|
|
#include "Android/AndroidWindow.h"
|
|
#include "Android/AndroidPlatformFramePacer.h"
|
|
#include "Math/UnrealMathUtility.h"
|
|
#include "Android/AndroidApplication.h"
|
|
#include "Android/AndroidPlatformMisc.h"
|
|
#include "Android/AndroidJavaEnv.h"
|
|
#include "Android/AndroidJNI.h"
|
|
#include "Android/AndroidPlatformMisc.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "GenericPlatform/GenericPlatformCrashContext.h"
|
|
#include "../VulkanExtensions.h"
|
|
#include <android/sharedmem_jni.h>
|
|
#include <sys/mman.h>
|
|
#include "ProfilingDebugging/ScopedTimers.h"
|
|
#include "Android/AndroidDynamicRHI.h"
|
|
#include "PSOMetrics.h"
|
|
#include "../VulkanContext.h"
|
|
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
#undef VK_NO_PROTOTYPES
|
|
#include "Android/AndroidJNI.h"
|
|
#include "Android/AndroidApplication.h"
|
|
#include "swappy/swappyVk.h"
|
|
#include "EngineGlobals.h"
|
|
|
|
namespace AndroidVulkan
|
|
{
|
|
void VKSwappyPostWaitCallback(void*, int64_t cpu_time_ns, int64_t gpu_time_ns)
|
|
{
|
|
GRHIGPUFrameTimeHistory.PushFrameCycles(1'000'000'000.0, gpu_time_ns);
|
|
}
|
|
|
|
void SetSwappyPostWaitCallback()
|
|
{
|
|
SwappyTracer Tracer = { 0 };
|
|
Tracer.postWait = VKSwappyPostWaitCallback;
|
|
SwappyVk_injectTracer(&Tracer);
|
|
|
|
int32 FrameTimeFenceInMillis = FAndroidPlatformRHIFramePacer::CVarSwappyGPUFrameTimeFence.GetValueOnAnyThread();
|
|
|
|
SwappyVk_setFenceTimeoutNS(FrameTimeFenceInMillis * 1000000); // millis to ns (ms * 1000000)
|
|
}
|
|
};
|
|
|
|
#endif // #if USE_ANDROID_VULKAN_SWAPPY
|
|
|
|
// From VulklanSwapChain.cpp
|
|
extern int32 GVulkanCPURenderThreadFramePacer;
|
|
extern int32 GPrintVulkanVsyncDebug;
|
|
|
|
int32 GVulkanExtensionFramePacer = 1;
|
|
static FAutoConsoleVariableRef CVarVulkanExtensionFramePacer(
|
|
TEXT("r.Vulkan.ExtensionFramePacer"),
|
|
GVulkanExtensionFramePacer,
|
|
TEXT("Whether to enable the google extension Framepacer for Vulkan (when available on device)"),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static TAutoConsoleVariable<int32> CVarVulkanSupportsBCTextureFormats(
|
|
TEXT("r.Vulkan.SupportsBCTextureFormats"),
|
|
0,
|
|
TEXT("Whether or not BC Texture formats are supported\n")
|
|
TEXT(" 0 = unsupported\n")
|
|
TEXT(" 1 = supported."),
|
|
ECVF_SetByDeviceProfile
|
|
);
|
|
#if PLATFORM_ANDROID_X64
|
|
|
|
static TAutoConsoleVariable<int32> CVarVulkanHasUnifiedMemory(
|
|
TEXT("android.Vulkan.HasUnifiedMemory"),
|
|
1,
|
|
TEXT("Whether or not device has unified memory\n")
|
|
TEXT(" 0 = doesn't have\n")
|
|
TEXT(" 1 = has."),
|
|
ECVF_SetByDeviceProfile
|
|
);
|
|
#endif
|
|
|
|
// Vulkan function pointers
|
|
#define DEFINE_VK_ENTRYPOINTS(Type,Func) Type VulkanDynamicAPI::Func = NULL;
|
|
ENUM_VK_ENTRYPOINTS_ALL(DEFINE_VK_ENTRYPOINTS)
|
|
|
|
#define VULKAN_MALI_LAYER_NAME "VK_LAYER_ARM_AGA"
|
|
|
|
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
bool FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit = false;
|
|
#endif
|
|
void* FVulkanAndroidPlatform::VulkanLib = nullptr;
|
|
bool FVulkanAndroidPlatform::bAttemptedLoad = false;
|
|
|
|
#if VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
bool FVulkanAndroidPlatform::bHasGoogleDisplayTiming = false;
|
|
TUniquePtr<class FGDTimingFramePacer> FVulkanAndroidPlatform::GDTimingFramePacer;
|
|
#endif
|
|
|
|
TUniquePtr<struct FAndroidVulkanFramePacer> FVulkanAndroidPlatform::FramePacer;
|
|
int32 FVulkanAndroidPlatform::CachedFramePace = 0;
|
|
int32 FVulkanAndroidPlatform::CachedRefreshRate = 60;
|
|
int32 FVulkanAndroidPlatform::CachedSyncInterval = 1;
|
|
int32 FVulkanAndroidPlatform::SuccessfulRefreshRateFrames = 1;
|
|
int32 FVulkanAndroidPlatform::UnsuccessfulRefreshRateFrames = 0;
|
|
TArray<TArray<ANSICHAR>> FVulkanAndroidPlatform::DebugVulkanDeviceLayers;
|
|
TArray<TArray<ANSICHAR>> FVulkanAndroidPlatform::DebugVulkanInstanceLayers;
|
|
TArray<TArray<ANSICHAR>> FVulkanAndroidPlatform::SwappyRequiredExtensions;
|
|
int32 FVulkanAndroidPlatform::AFBCWorkaroundOption = 0;
|
|
int32 FVulkanAndroidPlatform::ASTCWorkaroundOption = 0;
|
|
bool FVulkanAndroidPlatform::bRequiresDepthStencilFullWrite = false;
|
|
|
|
#define CHECK_VK_ENTRYPOINTS(Type,Func) if (VulkanDynamicAPI::Func == NULL) { bFoundAllEntryPoints = false; UE_LOG(LogRHI, Warning, TEXT("Failed to find entry point for %s"), TEXT(#Func)); }
|
|
|
|
|
|
#if VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
FGDTimingFramePacer::FGDTimingFramePacer(VkDevice InDevice, VkSwapchainKHR InSwapChain)
|
|
: Device(InDevice)
|
|
, SwapChain(InSwapChain)
|
|
{
|
|
FMemory::Memzero(PresentTime);
|
|
|
|
ZeroVulkanStruct(PresentTimesInfo, VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE);
|
|
PresentTimesInfo.swapchainCount = 1;
|
|
PresentTimesInfo.pTimes = &PresentTime;
|
|
}
|
|
|
|
// Used as a safety measure to prevent scheduling too far ahead in case of an error
|
|
static constexpr uint64 GMaxAheadSchedulingTimeNanosec = 500000000llu; // 0.5 sec.
|
|
|
|
static uint64 TimeNanoseconds()
|
|
{
|
|
#if PLATFORM_ANDROID || PLATFORM_LINUX
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return ts.tv_sec * 1000000000ull + ts.tv_nsec;
|
|
#else
|
|
#error VK_GOOGLE_display_timing requires TimeNanoseconds() implementation for this platform
|
|
#endif
|
|
}
|
|
|
|
void FGDTimingFramePacer::ScheduleNextFrame(uint32 InPresentID, int32 InFramePace, int32 InRefreshRate)
|
|
{
|
|
UpdateSyncDuration(InFramePace, InRefreshRate);
|
|
if (SyncDuration == 0)
|
|
{
|
|
if (GPrintVulkanVsyncDebug != 0)
|
|
{
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT(" -- SyncDuration == 0"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
const uint64 CpuPresentTime = TimeNanoseconds();
|
|
|
|
PresentTime.presentID = InPresentID; // Still need to pass ID for proper history values
|
|
|
|
PollPastFrameInfo();
|
|
if (!LastKnownFrameInfo.bValid)
|
|
{
|
|
if (GPrintVulkanVsyncDebug != 0)
|
|
{
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT(" -- LastKnownFrameInfo not valid"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
const uint64 CpuTargetPresentTimeMin = CalculateMinPresentTime(CpuPresentTime);
|
|
const uint64 CpuTargetPresentTimeMax = CalculateMaxPresentTime(CpuPresentTime);
|
|
const uint64 GpuTargetPresentTime = (PredictLastScheduledFramePresentTime(InPresentID) + SyncDuration);
|
|
|
|
const uint64 TargetPresentTime = CalculateNearestVsTime(LastKnownFrameInfo.ActualPresentTime, FMath::Clamp(GpuTargetPresentTime, CpuTargetPresentTimeMin, CpuTargetPresentTimeMax));
|
|
LastScheduledPresentTime = TargetPresentTime;
|
|
|
|
PresentTime.desiredPresentTime = (TargetPresentTime - HalfRefreshDuration);
|
|
|
|
if (GPrintVulkanVsyncDebug != 0)
|
|
{
|
|
double cpuPMin = CpuTargetPresentTimeMin / 1000000000.0;
|
|
double cpuPMax = CpuTargetPresentTimeMax / 1000000000.0;
|
|
double gpuP = GpuTargetPresentTime / 1000000000.0;
|
|
double desP = PresentTime.desiredPresentTime / 1000000000.0;
|
|
double lastP = LastKnownFrameInfo.ActualPresentTime / 1000000000.0;
|
|
double cpuDelta = 0.0;
|
|
double cpuNow = CpuPresentTime / 1000000000.0;
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT(" -- ID: %u, desired %.3f, pred-gpu %.3f, pred-cpu-min %.3f, pred-cpu-max %.3f, last: %.3f, cpu-gpu-delta: %.3f, now-cpu %.3f"), PresentTime.presentID, desP, gpuP, cpuPMin, cpuPMax, lastP, cpuDelta, cpuNow);
|
|
}
|
|
}
|
|
|
|
void FGDTimingFramePacer::UpdateSyncDuration(int32 InFramePace, int32 InRefreshRate)
|
|
{
|
|
if (FramePace == InFramePace)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// It's possible we have requested a change in native refresh rate that has yet to take effect. However if we base the schedule for the next
|
|
// frame on our intend native refresh rate, the exact number of vsyncs the extension has to wait is irrelevant and should never present earler
|
|
// than intended.
|
|
RefreshDuration = InRefreshRate > 0 ? FMath::DivideAndRoundNearest(1000000000ull, (uint64)InRefreshRate) : 0;
|
|
ensure(RefreshDuration > 0);
|
|
if (RefreshDuration == 0)
|
|
{
|
|
RefreshDuration = 16666667;
|
|
}
|
|
HalfRefreshDuration = (RefreshDuration / 2);
|
|
|
|
|
|
FramePace = InFramePace;
|
|
SyncDuration = InFramePace > 0 ? FMath::DivideAndRoundNearest(1000000000ull, (uint64)FramePace) : 0;
|
|
|
|
if (SyncDuration > 0)
|
|
{
|
|
SyncDuration = (FMath::Max((SyncDuration + HalfRefreshDuration) / RefreshDuration, 1llu) * RefreshDuration);
|
|
}
|
|
}
|
|
|
|
uint64 FGDTimingFramePacer::PredictLastScheduledFramePresentTime(uint32 CurrentPresentID) const
|
|
{
|
|
const uint32 PredictFrameCount = (CurrentPresentID - LastKnownFrameInfo.PresentID - 1);
|
|
// Use RefreshDuration for predicted frames and not SyncDuration for most optimistic prediction of future frames after last known (possible hitchy) frame.
|
|
// Second parameter will be always >= than LastScheduledPresentTime if use SyncDuration.
|
|
// It is possible that GPU will recover after hitch without any changes to a normal schedule but pessimistic planning will prevent this from happening.
|
|
return FMath::Max(LastScheduledPresentTime, LastKnownFrameInfo.ActualPresentTime + (RefreshDuration * PredictFrameCount));
|
|
}
|
|
|
|
uint64 FGDTimingFramePacer::CalculateMinPresentTime(uint64 CpuPresentTime) const
|
|
{
|
|
// Do not use delta on Android because already using CLOCK_MONOTONIC for CPU time which is also used in the extension.
|
|
// Using delta will mostly work fine but there were problems in other projects. If GPU load changes quickly because
|
|
// of the delta filter lag its value may be too high for current frame and cause pessimistic planning and stuttering.
|
|
// Need additional time for testing to improve filtering.
|
|
// Adding HalfRefreshDuration to produce round-up (ceil) in the final CalculateNearestVsTime()
|
|
return (CpuPresentTime + HalfRefreshDuration);
|
|
}
|
|
|
|
uint64 FGDTimingFramePacer::CalculateMaxPresentTime(uint64 CpuPresentTime) const
|
|
{
|
|
return (CpuPresentTime + GMaxAheadSchedulingTimeNanosec);
|
|
}
|
|
|
|
uint64 FGDTimingFramePacer::CalculateNearestVsTime(uint64 ActualPresentTime, uint64 TargetTime) const
|
|
{
|
|
if (TargetTime > ActualPresentTime)
|
|
{
|
|
return (ActualPresentTime + ((TargetTime - ActualPresentTime) + HalfRefreshDuration) / RefreshDuration * RefreshDuration);
|
|
}
|
|
return ActualPresentTime;
|
|
}
|
|
|
|
void FGDTimingFramePacer::PollPastFrameInfo()
|
|
{
|
|
for (;;)
|
|
{
|
|
// MUST call once with nullptr to get the count, or the API won't return any results at all.
|
|
uint32 Count = 0;
|
|
VkResult Result = VulkanDynamicAPI::vkGetPastPresentationTimingGOOGLE(Device, SwapChain, &Count, nullptr);
|
|
checkf(Result == VK_SUCCESS, TEXT("vkGetPastPresentationTimingGOOGLE failed: %i"), Result);
|
|
|
|
if (Count == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Count = 1;
|
|
VkPastPresentationTimingGOOGLE PastPresentationTiming;
|
|
Result = VulkanDynamicAPI::vkGetPastPresentationTimingGOOGLE(Device, SwapChain, &Count, &PastPresentationTiming);
|
|
checkf(Result == VK_SUCCESS || Result == VK_INCOMPLETE, TEXT("vkGetPastPresentationTimingGOOGLE failed: %i"), Result);
|
|
|
|
// If desiredPresentTime was too large for some reason driver may ignore this value to prevent long wait
|
|
// Reset LastScheduledPresentTime in that case to be able to schedule on proper time
|
|
if (PastPresentationTiming.actualPresentTime < PastPresentationTiming.desiredPresentTime)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("PastPresentationTiming actualPresentTime is less than desiredPresentTime! Resetting LastScheduledPresentTime..."));
|
|
LastScheduledPresentTime = 0;
|
|
}
|
|
|
|
LastKnownFrameInfo.PresentID = PastPresentationTiming.presentID;
|
|
LastKnownFrameInfo.ActualPresentTime = PastPresentationTiming.actualPresentTime;
|
|
LastKnownFrameInfo.bValid = true;
|
|
}
|
|
}
|
|
#endif //VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
|
|
bool FVulkanAndroidPlatform::LoadVulkanLibrary()
|
|
{
|
|
if (bAttemptedLoad)
|
|
{
|
|
return (VulkanLib != nullptr);
|
|
}
|
|
bAttemptedLoad = true;
|
|
|
|
// try to load libvulkan.so
|
|
VulkanLib = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
|
|
|
if (VulkanLib == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bFoundAllEntryPoints = true;
|
|
|
|
#define GET_VK_ENTRYPOINTS(Type,Func) VulkanDynamicAPI::Func = (Type)dlsym(VulkanLib, #Func);
|
|
|
|
ENUM_VK_ENTRYPOINTS_BASE(GET_VK_ENTRYPOINTS);
|
|
ENUM_VK_ENTRYPOINTS_BASE(CHECK_VK_ENTRYPOINTS);
|
|
if (!bFoundAllEntryPoints)
|
|
{
|
|
dlclose(VulkanLib);
|
|
VulkanLib = nullptr;
|
|
return false;
|
|
}
|
|
|
|
ENUM_VK_ENTRYPOINTS_OPTIONAL_BASE(GET_VK_ENTRYPOINTS);
|
|
#if UE_BUILD_DEBUG
|
|
ENUM_VK_ENTRYPOINTS_OPTIONAL_BASE(CHECK_VK_ENTRYPOINTS);
|
|
#endif
|
|
|
|
#undef GET_VK_ENTRYPOINTS
|
|
|
|
// Init frame pacer
|
|
FramePacer = MakeUnique<FAndroidVulkanFramePacer>();
|
|
FPlatformRHIFramePacer::Init(FramePacer.Get());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FVulkanAndroidPlatform::LoadVulkanInstanceFunctions(VkInstance inInstance)
|
|
{
|
|
bool bFoundAllEntryPoints = true;
|
|
|
|
#define GETINSTANCE_VK_ENTRYPOINTS(Type, Func) VulkanDynamicAPI::Func = (Type)VulkanDynamicAPI::vkGetInstanceProcAddr(inInstance, #Func);
|
|
|
|
ENUM_VK_ENTRYPOINTS_INSTANCE(GETINSTANCE_VK_ENTRYPOINTS);
|
|
ENUM_VK_ENTRYPOINTS_INSTANCE(CHECK_VK_ENTRYPOINTS);
|
|
|
|
ENUM_VK_ENTRYPOINTS_SURFACE_INSTANCE(GETINSTANCE_VK_ENTRYPOINTS);
|
|
ENUM_VK_ENTRYPOINTS_SURFACE_INSTANCE(CHECK_VK_ENTRYPOINTS);
|
|
|
|
ENUM_VK_ENTRYPOINTS_PLATFORM_INSTANCE(GETINSTANCE_VK_ENTRYPOINTS);
|
|
ENUM_VK_ENTRYPOINTS_PLATFORM_INSTANCE(CHECK_VK_ENTRYPOINTS);
|
|
|
|
const bool bFoundRayTracingEntries = FVulkanRayTracingPlatform::CheckVulkanInstanceFunctions(inInstance);
|
|
if (!bFoundRayTracingEntries)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Warning, TEXT("Vulkan RHI ray tracing is enabled, but failed to load instance functions."));
|
|
}
|
|
|
|
if (!bFoundAllEntryPoints)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ENUM_VK_ENTRYPOINTS_OPTIONAL_INSTANCE(GETINSTANCE_VK_ENTRYPOINTS);
|
|
ENUM_VK_ENTRYPOINTS_OPTIONAL_PLATFORM_INSTANCE(GETINSTANCE_VK_ENTRYPOINTS);
|
|
#if UE_BUILD_DEBUG
|
|
ENUM_VK_ENTRYPOINTS_OPTIONAL_INSTANCE(CHECK_VK_ENTRYPOINTS);
|
|
ENUM_VK_ENTRYPOINTS_OPTIONAL_PLATFORM_INSTANCE(CHECK_VK_ENTRYPOINTS);
|
|
#endif
|
|
|
|
#undef GETINSTANCE_VK_ENTRYPOINTS
|
|
|
|
return true;
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::FreeVulkanLibrary()
|
|
{
|
|
if (VulkanLib != nullptr)
|
|
{
|
|
#define CLEAR_VK_ENTRYPOINTS(Type,Func) VulkanDynamicAPI::Func = nullptr;
|
|
ENUM_VK_ENTRYPOINTS_ALL(CLEAR_VK_ENTRYPOINTS);
|
|
|
|
dlclose(VulkanLib);
|
|
VulkanLib = nullptr;
|
|
}
|
|
bAttemptedLoad = false;
|
|
}
|
|
|
|
#undef CHECK_VK_ENTRYPOINTS
|
|
|
|
bool FVulkanAndroidPlatform::HasCustomFrameTiming()
|
|
{
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
return FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::InitDevice(FVulkanDevice* InDevice)
|
|
{
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit)
|
|
{
|
|
FVulkanQueue* GfxQueue = InDevice->GetGraphicsQueue();
|
|
check(GfxQueue);
|
|
SwappyVk_setQueueFamilyIndex(InDevice->GetInstanceHandle(), GfxQueue->GetHandle(), GfxQueue->GetFamilyIndex());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void* FVulkanAndroidPlatform::GetHardwareWindowHandle()
|
|
{
|
|
// don't use cached window handle coming from VulkanViewport, as it could be gone by now
|
|
void* WindowHandle = FAndroidWindow::GetHardwareWindow_EventThread();
|
|
if (WindowHandle == nullptr)
|
|
{
|
|
// Sleep if the hardware window isn't currently available.
|
|
// The Window may not exist if the activity is pausing/resuming, in which case we make this thread wait
|
|
FPlatformMisc::LowLevelOutputDebugString(TEXT("Waiting for Native window in FVulkanAndroidPlatform::CreateSurface"));
|
|
WindowHandle = FAndroidWindow::WaitForHardwareWindow();
|
|
|
|
if (WindowHandle == nullptr)
|
|
{
|
|
FPlatformMisc::LowLevelOutputDebugString(TEXT("Aborting FVulkanAndroidPlatform::CreateSurface, FAndroidWindow::WaitForHardwareWindow() returned null"));
|
|
}
|
|
}
|
|
|
|
return WindowHandle;
|
|
}
|
|
|
|
bool FVulkanAndroidPlatform::SupportsBCTextureFormats()
|
|
{
|
|
return (CVarVulkanSupportsBCTextureFormats.GetValueOnAnyThread() == 1);
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::CreateSurface(FVulkanPlatformWindowContext& WindowContext, VkInstance Instance, VkSurfaceKHR* OutSurface)
|
|
{
|
|
ANativeWindow* Handle = FAndroidMisc::UseNewWindowBehavior() ? WindowContext.GetANativeWindow() : (ANativeWindow*)GetHardwareWindowHandle();
|
|
check(Handle);
|
|
|
|
VkAndroidSurfaceCreateInfoKHR SurfaceCreateInfo;
|
|
ZeroVulkanStruct(SurfaceCreateInfo, VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR);
|
|
SurfaceCreateInfo.window = Handle;
|
|
VERIFYVULKANRESULT(VulkanDynamicAPI::vkCreateAndroidSurfaceKHR(Instance, &SurfaceCreateInfo, VULKAN_CPU_ALLOCATOR, OutSurface));
|
|
}
|
|
|
|
|
|
void FVulkanAndroidPlatform::GetInstanceExtensions(FVulkanInstanceExtensionArray& OutExtensions)
|
|
{
|
|
OutExtensions.Add(MakeUnique<FVulkanInstanceExtension>(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, VULKAN_EXTENSION_ENABLED, VULKAN_EXTENSION_NOT_PROMOTED));
|
|
|
|
// VK_GOOGLE_display_timing (as instance extension?)
|
|
if (FAndroidPlatformRHIFramePacer::CVarAllowFrameTimestamps.GetValueOnAnyThread() != 0)
|
|
{
|
|
OutExtensions.Add(
|
|
MakeUnique<FVulkanInstanceExtension>(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, VULKAN_EXTENSION_ENABLED, VULKAN_EXTENSION_NOT_PROMOTED));
|
|
}
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::GetInstanceLayers(TArray<const ANSICHAR*>& OutLayers)
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
if (DebugVulkanInstanceLayers.IsEmpty())
|
|
{
|
|
TArray<FString> LayerNames;
|
|
GConfig->GetArray(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("DebugVulkanInstanceLayers"), LayerNames, GEngineIni);
|
|
|
|
if (!LayerNames.IsEmpty())
|
|
{
|
|
uint32 Index = 0;
|
|
for (auto& LayerName : LayerNames)
|
|
{
|
|
TArray<ANSICHAR> LayerNameANSI{ TCHAR_TO_ANSI(*LayerName), LayerName.Len() + 1 };
|
|
DebugVulkanInstanceLayers.Add(LayerNameANSI);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const TArray<ANSICHAR>& LayerName : DebugVulkanInstanceLayers)
|
|
{
|
|
OutLayers.Add(LayerName.GetData());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int32 GVulkanUseASTCDecodeMode = 1;
|
|
static FAutoConsoleVariableRef CVarVulkanUseASTCDecodeMode(
|
|
TEXT("r.Vulkan.UseASTCDecodeMode"),
|
|
GVulkanUseASTCDecodeMode,
|
|
TEXT("Whether to use VK_EXT_astc_decode_mode extension\n"),
|
|
ECVF_ReadOnly
|
|
);
|
|
|
|
void FVulkanAndroidPlatform::GetDeviceExtensions(FVulkanDevice* Device, FVulkanDeviceExtensionArray& OutExtensions)
|
|
{
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(Device, VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, VULKAN_EXTENSION_ENABLED, VULKAN_EXTENSION_NOT_PROMOTED));
|
|
if (FAndroidPlatformRHIFramePacer::CVarAllowFrameTimestamps.GetValueOnAnyThread() != 0)
|
|
{
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(
|
|
Device, VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, VULKAN_EXTENSION_ENABLED, VULKAN_EXTENSION_NOT_PROMOTED));
|
|
}
|
|
if (GVulkanUseASTCDecodeMode)
|
|
{
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(Device, VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, VULKAN_SUPPORTS_ASTC_DECODE_MODE, VULKAN_EXTENSION_NOT_PROMOTED, DEVICE_EXT_FLAG_SETTER(HasEXTASTCDecodeMode)));
|
|
}
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(Device, VK_EXT_TEXTURE_COMPRESSION_ASTC_HDR_EXTENSION_NAME, VULKAN_SUPPORTS_TEXTURE_COMPRESSION_ASTC_HDR, VK_API_VERSION_1_3, DEVICE_EXT_FLAG_SETTER(HasEXTTextureCompressionASTCHDR)));
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(Device, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, VULKAN_EXTENSION_ENABLED));
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(Device, VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, VULKAN_SUPPORTS_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER, VULKAN_EXTENSION_NOT_PROMOTED, DEVICE_EXT_FLAG_SETTER(HasANDROIDExternalMemoryHardwareBuffer)));
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
// Layer name as extension
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(Device, VULKAN_MALI_LAYER_NAME, VULKAN_EXTENSION_ENABLED, VULKAN_EXTENSION_NOT_PROMOTED));
|
|
#endif
|
|
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit)
|
|
{
|
|
// make sure any extensions swappy requires are included
|
|
for (const TArray<ANSICHAR>& SwappyRequiredExtension : SwappyRequiredExtensions)
|
|
{
|
|
if (FVulkanExtensionBase::FindExtension(OutExtensions, SwappyRequiredExtension.GetData()) != INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
OutExtensions.Add(MakeUnique<FVulkanDeviceExtension>(Device, SwappyRequiredExtension.GetData(), VULKAN_EXTENSION_ENABLED, VULKAN_EXTENSION_NOT_PROMOTED));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::GetDeviceLayers(TArray<const ANSICHAR*>& OutLayers)
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
if (DebugVulkanDeviceLayers.IsEmpty())
|
|
{
|
|
TArray<FString> LayerNames;
|
|
GConfig->GetArray(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("DebugVulkanDeviceLayers"), LayerNames, GEngineIni);
|
|
|
|
if (!LayerNames.IsEmpty())
|
|
{
|
|
uint32 Index = 0;
|
|
for (auto& LayerName : LayerNames)
|
|
{
|
|
TArray<ANSICHAR> LayerNameANSI{ TCHAR_TO_ANSI(*LayerName), LayerName.Len() + 1 };
|
|
DebugVulkanDeviceLayers.Add(LayerNameANSI);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& LayerName : DebugVulkanDeviceLayers)
|
|
{
|
|
OutLayers.Add(LayerName.GetData());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::NotifyFoundDeviceLayersAndExtensions(VkPhysicalDevice PhysicalDevice, const TArray<const ANSICHAR*>& Layers, const TArray<const ANSICHAR*>& Extensions)
|
|
{
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit)
|
|
{
|
|
// Determine extensions required by Swappy
|
|
// We need to pass in vkEnumerateDeviceExtensionProperties directly so we cannot use the Extensions array as passed in.
|
|
uint32 PropertyCount = 0;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkEnumerateDeviceExtensionProperties(PhysicalDevice, nullptr, &PropertyCount, nullptr));
|
|
if (PropertyCount > 0)
|
|
{
|
|
TArray<VkExtensionProperties> Properties;
|
|
Properties.AddZeroed(PropertyCount);
|
|
VERIFYVULKANRESULT(VulkanRHI::vkEnumerateDeviceExtensionProperties(PhysicalDevice, nullptr, &PropertyCount, Properties.GetData()));
|
|
check(PropertyCount == Properties.Num());
|
|
|
|
uint32 SwappyRequiredExtensionCount = 0;
|
|
SwappyVk_determineDeviceExtensions(PhysicalDevice, PropertyCount, Properties.GetData(), &SwappyRequiredExtensionCount, nullptr);
|
|
|
|
if (SwappyRequiredExtensionCount > 0)
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Swappy requires %d extensions:"), SwappyRequiredExtensionCount);
|
|
|
|
SwappyRequiredExtensions.Empty(SwappyRequiredExtensionCount);
|
|
SwappyRequiredExtensions.AddDefaulted(SwappyRequiredExtensionCount);
|
|
// SwappyVk_determineDeviceExtensions API requires an array of pointers to char that it can fill in.
|
|
TArray<ANSICHAR*> SwappyRequiredExtensionPtrs;
|
|
SwappyRequiredExtensionPtrs.Empty(SwappyRequiredExtensionCount);
|
|
for (int32 i = 0; i < SwappyRequiredExtensionCount; i++)
|
|
{
|
|
SwappyRequiredExtensions[i].AddZeroed(VK_MAX_EXTENSION_NAME_SIZE + 1);
|
|
SwappyRequiredExtensionPtrs.Add(SwappyRequiredExtensions[i].GetData());
|
|
}
|
|
|
|
SwappyVk_determineDeviceExtensions(PhysicalDevice, PropertyCount, Properties.GetData(), &SwappyRequiredExtensionCount, SwappyRequiredExtensionPtrs.GetData());
|
|
|
|
check(SwappyRequiredExtensionCount == SwappyRequiredExtensions.Num());
|
|
|
|
for (int32 i = 0; i < SwappyRequiredExtensionCount; i++)
|
|
{
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT(" %s\n"), ANSI_TO_TCHAR(SwappyRequiredExtensions[i].GetData()));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Swappy didn't ask for any extensions"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("No extensions available for Swappy"));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#if VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
FVulkanAndroidPlatform::bHasGoogleDisplayTiming = Extensions.ContainsByPredicate([](const ANSICHAR* Key)
|
|
{
|
|
return Key && !FCStringAnsi::Strcmp(Key, VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME);
|
|
});
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("bHasGoogleDisplayTiming = %d"), FVulkanAndroidPlatform::bHasGoogleDisplayTiming);
|
|
#endif
|
|
}
|
|
|
|
bool FVulkanAndroidPlatform::SupportsTimestampRenderQueries()
|
|
{
|
|
static const auto CVarAndroidSupportsTimestampQueries = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Android.SupportsTimestampQueries"));
|
|
|
|
// standalone devices have newer drivers where timestamp render queries work.
|
|
return CVarAndroidSupportsTimestampQueries != nullptr &&
|
|
CVarAndroidSupportsTimestampQueries->GetBool();
|
|
}
|
|
|
|
bool FVulkanAndroidPlatform::SupportsDynamicResolution()
|
|
{
|
|
// separating render timestamp queries from dynres availability
|
|
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
|
|
static const auto CVarAndroidSupportsDynamicResolution = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Android.SupportsDynamicResolution"));
|
|
|
|
return FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit &&
|
|
CVarAndroidSupportsDynamicResolution != nullptr &&
|
|
CVarAndroidSupportsDynamicResolution->GetBool(); // is supported
|
|
|
|
#else // USE_ANDROID_VULKAN_SWAPPY
|
|
|
|
// defaulted to the previous code
|
|
return SupportsTimestampRenderQueries();
|
|
|
|
#endif
|
|
}
|
|
|
|
extern bool IsInAndroidEventThread();
|
|
|
|
namespace AndroidVulkan
|
|
{
|
|
static void OnLostWindow(TOptional<FAndroidWindow::FNativeAccessor> WindowContainer)
|
|
{
|
|
check(IsInAndroidEventThread());
|
|
check(FAndroidMisc::UseNewWindowBehavior());
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: Event thread"));
|
|
FVulkanPlatformWindowContext WindowContext(MoveTemp(WindowContainer));
|
|
FGraphEventRef OnComplete = FGraphEvent::CreateGraphEvent();
|
|
|
|
ENQUEUE_RENDER_COMMAND(OnAndroidLostWindow)([OnComplete, &WindowContext](FRHICommandListImmediate& RHICmdList) mutable
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: RT"));
|
|
RHICmdList.EnqueueLambda([&WindowContext](FRHICommandListImmediate& ExecutingCmdList) mutable
|
|
{
|
|
check(IsInRHIThread() || IsRunningRHIInSeparateThread());
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: RHI"));
|
|
|
|
// cannot just throw out this command buffer (needs to be submitted or other checks fail)
|
|
FVulkanCommandListContext& Context = FVulkanCommandListContext::Get(ExecutingCmdList);
|
|
Context.FlushCommands(EVulkanFlushFlags::WaitForSubmission);
|
|
|
|
bool bFound = false;
|
|
TArray<FVulkanViewport*> Viewports = GVulkanRHI->GetViewports();
|
|
for (FVulkanViewport* Viewport : Viewports)
|
|
{
|
|
if (Viewport->GetWindowHandle() == WindowContext.GetWindowHandle())
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: Destroying viewport swapchain (window %p -> null)"), WindowContext.GetANativeWindow() );
|
|
Viewport->DestroySwapchain(nullptr);
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: Destroying viewport swapchain done."));
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
check(bFound);
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: RHI done"));
|
|
});
|
|
|
|
// Swapchain creation pushes some commands - flush the command buffers now to begin with a fresh state
|
|
RHICmdList.RHIThreadFence(true);
|
|
RHICmdList.SubmitAndBlockUntilGPUIdle();
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow:RT done"));
|
|
OnComplete->DispatchSubsequents();
|
|
});
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: wait RT"));
|
|
OnComplete->Wait();
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnLostWindow: done"));
|
|
}
|
|
|
|
static void OnFoundWindow(TOptional<FAndroidWindow::FNativeAccessor> WindowContainer)
|
|
{
|
|
check(IsInAndroidEventThread());
|
|
check(FAndroidMisc::UseNewWindowBehavior());
|
|
|
|
FVulkanPlatformWindowContext WindowContext(MoveTemp(WindowContainer));
|
|
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow:"));
|
|
FGraphEventRef OnComplete = FGraphEvent::CreateGraphEvent();
|
|
ENQUEUE_RENDER_COMMAND(OnAndroidFoundWindow)([OnComplete, &WindowContext](FRHICommandListImmediate& RHICmdList) mutable
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: RT"));
|
|
RHICmdList.EnqueueLambda([&WindowContext](FRHICommandListImmediate& ExecutingCmdList) mutable
|
|
{
|
|
check(IsInRHIThread() || IsRunningRHIInSeparateThread());
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: RHI"));
|
|
|
|
FVulkanDevice* Device = FVulkanDynamicRHI::Get().GetDevice();
|
|
|
|
// cannot just throw out this command buffer (needs to be submitted or other checks fail)
|
|
FVulkanCommandListContext& Context = FVulkanCommandListContext::Get(ExecutingCmdList);
|
|
Context.FlushCommands(EVulkanFlushFlags::WaitForSubmission);
|
|
|
|
bool bFound = false;
|
|
TArray<FVulkanViewport*> Viewports = GVulkanRHI->GetViewports();
|
|
for (FVulkanViewport* Viewport : Viewports)
|
|
{
|
|
if (Viewport->GetWindowHandle() == WindowContext.GetWindowHandle())
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: RHI RecreateSwapchain (window %p)"), WindowContext.GetANativeWindow());
|
|
Viewport->RecreateSwapchain(Context, WindowContext);
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: RHI RecreateSwapchain done (window %p)"), WindowContext.GetANativeWindow());
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
check(bFound);
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: RHI done"));
|
|
});
|
|
// Swapchain creation pushes some commands - flush the command buffers now to begin with a fresh state
|
|
RHICmdList.RHIThreadFence(true);
|
|
RHICmdList.SubmitAndBlockUntilGPUIdle();
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: RT done"));
|
|
OnComplete->DispatchSubsequents();
|
|
});
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: wait RT"));
|
|
OnComplete->Wait();
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("OnFoundWindow: done"));
|
|
}
|
|
}
|
|
|
|
|
|
// Old window behavior window recreate function.
|
|
// Moved to platform specific code from VulkanRHI.cpp.
|
|
void FVulkanAndroidPlatform::RecreateSwapChain(void* NewNativeWindow)
|
|
{
|
|
check(!FAndroidMisc::UseNewWindowBehavior());
|
|
|
|
if (NewNativeWindow)
|
|
{
|
|
if (IsInGameThread())
|
|
{
|
|
FlushRenderingCommands();
|
|
}
|
|
|
|
TArray<FVulkanViewport*> Viewports = FVulkanDynamicRHI::Get().GetViewports();
|
|
ENQUEUE_RENDER_COMMAND(VulkanRecreateSwapChain)(
|
|
[Viewports = MoveTemp(Viewports), NewNativeWindow](FRHICommandListImmediate& RHICmdList) mutable
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Recreate swapchain ... "));
|
|
RHICmdList.EnqueueLambda(TEXT("RecreateSwapchain"), [Viewports = MoveTemp(Viewports), NewNativeWindow](FRHICommandListImmediate& ExecutingCmdList)
|
|
{
|
|
for (FVulkanViewport* Viewport : Viewports)
|
|
{
|
|
FVulkanPlatformWindowContext WindowContext(NewNativeWindow);
|
|
Viewport->RecreateSwapchain(FVulkanCommandListContext::Get(ExecutingCmdList), WindowContext);
|
|
}
|
|
});
|
|
|
|
RHICmdList.SubmitAndBlockUntilGPUIdle();
|
|
});
|
|
|
|
if (IsInGameThread())
|
|
{
|
|
FlushRenderingCommands();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Old window behavior window destroy function.
|
|
// Moved to platform specific code from VulkanRHI.cpp.
|
|
void FVulkanAndroidPlatform::DestroySwapChain()
|
|
{
|
|
check(!FAndroidMisc::UseNewWindowBehavior());
|
|
|
|
if (IsInGameThread())
|
|
{
|
|
FlushRenderingCommands();
|
|
}
|
|
|
|
TArray<FVulkanViewport*> Viewports = FVulkanDynamicRHI::Get().GetViewports();
|
|
ENQUEUE_RENDER_COMMAND(VulkanDestroySwapChain)(
|
|
[Viewports = MoveTemp(Viewports)](FRHICommandListImmediate& RHICmdList) mutable
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Destroy swapchain ... "));
|
|
|
|
RHICmdList.EnqueueLambda(TEXT("FVulkanDynamicRHI::DestroySwapChain"), [Viewports = MoveTemp(Viewports)](FRHICommandListBase& ExecutingCmdList)
|
|
{
|
|
for (FVulkanViewport* Viewport : Viewports)
|
|
{
|
|
Viewport->DestroySwapchain(nullptr);
|
|
}
|
|
});
|
|
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
|
|
});
|
|
|
|
if (IsInGameThread())
|
|
{
|
|
FlushRenderingCommands();
|
|
}
|
|
}
|
|
|
|
|
|
void FVulkanAndroidPlatform::OverridePlatformHandlers(bool bInit)
|
|
{
|
|
if (bInit)
|
|
{
|
|
if (FAndroidMisc::UseNewWindowBehavior())
|
|
{
|
|
UE::FAndroidPlatformDynamicRHI::SetRHIOnReleaseWindowCallback(AndroidVulkan::OnLostWindow);
|
|
UE::FAndroidPlatformDynamicRHI::SetRHIOnReInitWindowCallback(AndroidVulkan::OnFoundWindow);
|
|
}
|
|
else
|
|
{
|
|
FPlatformMisc::SetOnReInitWindowCallback(FVulkanAndroidPlatform::RecreateSwapChain);
|
|
FPlatformMisc::SetOnReleaseWindowCallback(FVulkanAndroidPlatform::DestroySwapChain);
|
|
}
|
|
FPlatformMisc::SetOnPauseCallback(FVulkanDynamicRHI::SavePipelineCache);
|
|
}
|
|
else
|
|
{
|
|
FPlatformMisc::SetOnReInitWindowCallback(nullptr);
|
|
FPlatformMisc::SetOnReleaseWindowCallback(nullptr);
|
|
UE::FAndroidPlatformDynamicRHI::SetRHIOnReleaseWindowCallback(nullptr);
|
|
UE::FAndroidPlatformDynamicRHI::SetRHIOnReInitWindowCallback(nullptr);
|
|
|
|
FPlatformMisc::SetOnPauseCallback(nullptr);
|
|
}
|
|
}
|
|
#if PLATFORM_ANDROID_X64
|
|
|
|
bool FVulkanAndroidPlatform::HasUnifiedMemory()
|
|
{
|
|
return CVarVulkanHasUnifiedMemory.GetValueOnAnyThread();
|
|
}
|
|
#endif
|
|
|
|
void FVulkanAndroidPlatform::SetupRequiresDepthStencilFullWriteWorkaround(const FVulkanDevice& Device)
|
|
{
|
|
bRequiresDepthStencilFullWrite = false;
|
|
if (Device.GetVendorId() == EGpuVendorId::Arm)
|
|
{
|
|
const uint32_t DriverVersion = Device.GetDeviceProperties().driverVersion;
|
|
if (DriverVersion >= VK_MAKE_API_VERSION(0, 16, 0, 0) && DriverVersion < VK_MAKE_API_VERSION(0, 24, 0, 0))
|
|
{
|
|
bRequiresDepthStencilFullWrite = true;
|
|
UE_LOG(LogRHI, Display, TEXT("Enabling workaround that changes partial depth/stencil attachment stores into full depth/stencil stores."));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FVulkanAndroidPlatform::FramePace(FVulkanDevice& Device, void* WindowHandle, VkSwapchainKHR Swapchain, uint32 PresentID, VkPresentInfoKHR& Info)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_VulkanAndroid_FramePace);
|
|
|
|
bool bVsyncMultiple = (CachedSyncInterval != 0);
|
|
int32 CurrentFramePace = FAndroidPlatformRHIFramePacer::GetFramePace();
|
|
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit && CurrentFramePace != 0)
|
|
{
|
|
// cache refresh rate and sync interval
|
|
if (CurrentFramePace != CachedFramePace)
|
|
{
|
|
CachedFramePace = CurrentFramePace;
|
|
FramePacer->SupportsFramePaceInternal(CurrentFramePace, CachedRefreshRate, CachedSyncInterval);
|
|
SwappyVk_resetFramePacing(Swapchain);
|
|
|
|
if (CachedSyncInterval != 0)
|
|
{
|
|
// Multiple of sync interval, use swappy directly
|
|
SwappyVk_setSwapIntervalNS(Device.GetInstanceHandle(), Swapchain, (1000000000L) / (int64)CurrentFramePace);
|
|
bVsyncMultiple = true;
|
|
}
|
|
else
|
|
{
|
|
// Unsupported frame rate. Set to higher refresh rate and use CPU frame pacer to limit to desired frame pace
|
|
SwappyVk_setSwapIntervalNS(Device.GetInstanceHandle(), Swapchain, (1000000000L) / (int64)CachedRefreshRate);
|
|
// indicate that the RHI should perform CPU frame pacing to handle the requested frame rate
|
|
bVsyncMultiple = false;
|
|
}
|
|
}
|
|
|
|
return bVsyncMultiple;
|
|
}
|
|
#endif
|
|
|
|
#if VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
if (GVulkanExtensionFramePacer && bHasGoogleDisplayTiming)
|
|
{
|
|
check(GDTimingFramePacer);
|
|
GDTimingFramePacer->ScheduleNextFrame(PresentID, CurrentFramePace, CachedRefreshRate);
|
|
Info.pNext = GDTimingFramePacer->GetPresentTimesInfo();
|
|
}
|
|
#else
|
|
{}
|
|
#endif
|
|
return bVsyncMultiple;
|
|
}
|
|
|
|
VkResult FVulkanAndroidPlatform::Present(VkQueue Queue, VkPresentInfoKHR& PresentInfo)
|
|
{
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit)
|
|
{
|
|
return SwappyVk_queuePresent(Queue, &PresentInfo);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
return VulkanRHI::vkQueuePresentKHR(Queue, &PresentInfo);
|
|
}
|
|
}
|
|
|
|
VkResult FVulkanAndroidPlatform::CreateSwapchainKHR(FVulkanPlatformWindowContext& WindowContext, VkPhysicalDevice PhysicalDevice, VkDevice Device, const VkSwapchainCreateInfoKHR* CreateInfo, const VkAllocationCallbacks* Allocator, VkSwapchainKHR* Swapchain)
|
|
{
|
|
VkResult Result = VulkanRHI::vkCreateSwapchainKHR(Device, CreateInfo, Allocator, Swapchain);
|
|
|
|
if (Result == VK_SUCCESS)
|
|
{
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit)
|
|
{
|
|
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
|
|
if (ensure(Env))
|
|
{
|
|
ANativeWindow* WindowHandle = FAndroidMisc::UseNewWindowBehavior() ? WindowContext.GetANativeWindow() : (ANativeWindow*)GetHardwareWindowHandle();
|
|
check(WindowHandle);
|
|
|
|
uint64_t RefreshDuration; // in nanoseconds
|
|
SwappyVk_initAndGetRefreshCycleDuration(Env, FJavaWrapper::GameActivityThis, PhysicalDevice, Device, *Swapchain, &RefreshDuration);
|
|
SwappyVk_setWindow(Device, *Swapchain, WindowHandle);
|
|
SwappyVk_setAutoSwapInterval(false);
|
|
AndroidVulkan::SetSwappyPostWaitCallback();
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("SwappyVk_initAndGetRefreshCycleDuration: %ull"), RefreshDuration);
|
|
}
|
|
|
|
GVulkanCPURenderThreadFramePacer = 0;
|
|
GVulkanExtensionFramePacer = 0;
|
|
}
|
|
else
|
|
#endif
|
|
#if VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
if (GVulkanExtensionFramePacer && FVulkanAndroidPlatform::bHasGoogleDisplayTiming)
|
|
{
|
|
GDTimingFramePacer = MakeUnique<FGDTimingFramePacer>(Device, *Swapchain);
|
|
GVulkanCPURenderThreadFramePacer = 0;
|
|
}
|
|
#else
|
|
{}
|
|
#endif
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::DestroySwapchainKHR(VkDevice Device, VkSwapchainKHR Swapchain, const VkAllocationCallbacks* Allocator)
|
|
{
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit)
|
|
{
|
|
SwappyVk_destroySwapchain(Device, Swapchain);
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("SwappyVk_destroySwapchain"));
|
|
}
|
|
#endif
|
|
|
|
VulkanRHI::vkDestroySwapchainKHR(Device, Swapchain, Allocator);
|
|
|
|
// reset frame pace, to force display refresh rate update after we create a new swapchain
|
|
// see FVulkanAndroidPlatform::FramePace
|
|
CachedFramePace = 0;
|
|
}
|
|
|
|
|
|
bool FAndroidVulkanFramePacer::SupportsFramePaceInternal(int32 QueryFramePace, int32& OutRefreshRate, int32& OutSyncInterval)
|
|
{
|
|
TArray<int32> RefreshRates = FAndroidMisc::GetSupportedNativeDisplayRefreshRates();
|
|
RefreshRates.Sort();
|
|
|
|
FString DebugString = TEXT("FAndroidVulkanFramePacer -> Supported Refresh Rates:");
|
|
for (int32 RefreshRate : RefreshRates)
|
|
{
|
|
DebugString += FString::Printf(TEXT(" %d"), RefreshRate);
|
|
}
|
|
UE_LOG(LogRHI, Log, TEXT("%s"), *DebugString);
|
|
|
|
for (int32 Rate : RefreshRates)
|
|
{
|
|
if ((Rate % QueryFramePace) == 0)
|
|
{
|
|
UE_LOG(LogRHI, Log, TEXT("Supports %d using refresh rate %d and sync interval %d"), QueryFramePace, Rate, Rate / QueryFramePace);
|
|
OutRefreshRate = Rate;
|
|
OutSyncInterval = Rate / QueryFramePace;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// check if we want to use CPU frame pacing at less than a multiple of supported refresh rate
|
|
if (FAndroidPlatformRHIFramePacer::CVarSupportNonVSyncMultipleFrameRates.GetValueOnAnyThread() == 1)
|
|
{
|
|
for (int32 Rate : RefreshRates)
|
|
{
|
|
if (Rate > QueryFramePace)
|
|
{
|
|
UE_LOG(LogRHI, Log, TEXT("Supports %d using refresh rate %d with CPU frame pacing"), QueryFramePace, Rate);
|
|
OutRefreshRate = Rate;
|
|
OutSyncInterval = 0;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
OutRefreshRate = QueryFramePace;
|
|
OutSyncInterval = 0;
|
|
return false;
|
|
}
|
|
|
|
struct GraphicsPipelineCreateInfo
|
|
{
|
|
VkPipelineCreateFlags PipelineCreateFlags;
|
|
uint32_t StageCount;
|
|
|
|
bool bHasVkPipelineVertexInputStateCreateInfo;
|
|
bool bHasVkPipelineInputAssemblyStateCreateInfo;
|
|
bool bHasVkPipelineTessellationStateCreateInfo;
|
|
bool bHasVkPipelineViewportStateCreateInfo;
|
|
bool bHasVkPipelineRasterizationStateCreateInfo;
|
|
bool bHasVkPipelineMultisampleStateCreateInfo;
|
|
bool bHasVkPipelineDepthStencilStateCreateInfo;
|
|
bool bHasVkPipelineColorBlendStateCreateInfo;
|
|
bool bHasVkPipelineDynamicStateCreateInfo;
|
|
|
|
uint32_t subpass;
|
|
};
|
|
|
|
#define COPY_TO_BUFFER(Dst, Src, Size) \
|
|
Dst.Append((const char*)Src, Size);
|
|
|
|
void CharArrayToBuffer(const TArray<const ANSICHAR*>& CharArray, TArray<char>& MemoryStream)
|
|
{
|
|
uint32_t Count = CharArray.Num();
|
|
COPY_TO_BUFFER(MemoryStream, &Count, sizeof(uint32_t));
|
|
for (uint32_t Idx = 0; Idx < Count; ++Idx)
|
|
{
|
|
uint32_t StrLength = strlen(CharArray[Idx])+1;
|
|
COPY_TO_BUFFER(MemoryStream, &StrLength, sizeof(uint32_t));
|
|
COPY_TO_BUFFER(MemoryStream, CharArray[Idx], StrLength);
|
|
}
|
|
}
|
|
|
|
void GetVKStructsFromPNext(const void* InNext, TMap<VkStructureType, const void*>& VkStructs, const TArray<VkStructureType>& ValidTypes)
|
|
{
|
|
const VkBaseInStructure* Next = reinterpret_cast<const VkBaseInStructure*>(InNext);
|
|
while (Next != nullptr)
|
|
{
|
|
if (!ValidTypes.Contains(Next->sType))
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("GetVKStructsFromPNext: Unexpected type found when reading pNext->sType %d, Valid Types: "), (uint32_t)Next->sType);
|
|
}
|
|
|
|
VkStructs.FindOrAdd(Next->sType) = Next;
|
|
Next = reinterpret_cast<const VkBaseInStructure*>(Next->pNext);
|
|
}
|
|
}
|
|
|
|
void HandleGraphicsPipelineCreatePNext(const VkGraphicsPipelineCreateInfo* PipelineCreateInfo, TArray<char>& MemoryStream)
|
|
{
|
|
TMap<VkStructureType, const void*> VkStructs;
|
|
GetVKStructsFromPNext(PipelineCreateInfo->pNext, VkStructs, { VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_STATE_CREATE_INFO_KHR });
|
|
|
|
int32_t HandledCount = 0;
|
|
|
|
// FSR Create Info
|
|
bool bHasFSRCreateInfo = false;
|
|
|
|
const void** Struct = VkStructs.Find(VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_STATE_CREATE_INFO_KHR);
|
|
bHasFSRCreateInfo = Struct != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasFSRCreateInfo, sizeof(bool));
|
|
|
|
if (bHasFSRCreateInfo)
|
|
{
|
|
VkPipelineFragmentShadingRateStateCreateInfoKHR* FSRCreateInfo = (VkPipelineFragmentShadingRateStateCreateInfoKHR*)*Struct;
|
|
check(FSRCreateInfo->pNext == nullptr);
|
|
FSRCreateInfo->pNext = nullptr;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, FSRCreateInfo, sizeof(VkPipelineFragmentShadingRateStateCreateInfoKHR));
|
|
HandledCount++;
|
|
}
|
|
|
|
check(HandledCount == VkStructs.Num());
|
|
}
|
|
|
|
void HandlePipelineShaderStagePNext(const VkPipelineShaderStageCreateInfo* CreateInfo, TArray<char>& MemoryStream)
|
|
{
|
|
TMap<VkStructureType, const void*> VkStructs;
|
|
GetVKStructsFromPNext(CreateInfo->pNext, VkStructs, { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO });
|
|
|
|
int32_t HandledCount = 0;
|
|
|
|
// Subgroup Size Info
|
|
bool bHasSubGroupSizeInfo = false;
|
|
|
|
const void** Struct = VkStructs.Find(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO);
|
|
bHasSubGroupSizeInfo = Struct != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasSubGroupSizeInfo, sizeof(bool));
|
|
|
|
if (bHasSubGroupSizeInfo)
|
|
{
|
|
VkPipelineShaderStageRequiredSubgroupSizeCreateInfo* SubgroupSizeCreateInfo = (VkPipelineShaderStageRequiredSubgroupSizeCreateInfo*)*Struct;
|
|
check(SubgroupSizeCreateInfo->pNext == nullptr);
|
|
|
|
COPY_TO_BUFFER(MemoryStream, SubgroupSizeCreateInfo, sizeof(VkPipelineShaderStageRequiredSubgroupSizeCreateInfo));
|
|
HandledCount++;
|
|
}
|
|
|
|
check(HandledCount == VkStructs.Num());
|
|
}
|
|
|
|
void HandleSubpassDescriptionPNext(VkSubpassDescription2* SubpassDescription, TArray<char>& MemoryStream)
|
|
{
|
|
TMap<VkStructureType, const void*> VkStructs;
|
|
GetVKStructsFromPNext(SubpassDescription->pNext, VkStructs, { VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR });
|
|
|
|
int32_t HandledCount = 0;
|
|
|
|
// FSR Create Info
|
|
bool bHasFSRAttachmentCreateInfo = false;
|
|
|
|
const void** Struct = VkStructs.Find(VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR);
|
|
bHasFSRAttachmentCreateInfo = Struct != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasFSRAttachmentCreateInfo, sizeof(bool));
|
|
|
|
if (bHasFSRAttachmentCreateInfo)
|
|
{
|
|
VkFragmentShadingRateAttachmentInfoKHR* FragmentShadingRateCreateInfo = (VkFragmentShadingRateAttachmentInfoKHR*)*Struct;
|
|
|
|
check(FragmentShadingRateCreateInfo->pFragmentShadingRateAttachment->pNext == nullptr);
|
|
COPY_TO_BUFFER(MemoryStream, FragmentShadingRateCreateInfo->pFragmentShadingRateAttachment, sizeof(VkAttachmentReference2));
|
|
COPY_TO_BUFFER(MemoryStream, &FragmentShadingRateCreateInfo->shadingRateAttachmentTexelSize, sizeof(VkExtent2D));
|
|
HandledCount++;
|
|
}
|
|
|
|
check(HandledCount == VkStructs.Num());
|
|
}
|
|
|
|
void HandleDepthStencilAttachmentPNext(const VkAttachmentReference2* Attachment, TArray<char>& MemoryStream)
|
|
{
|
|
TMap<VkStructureType, const void*> VkStructs;
|
|
GetVKStructsFromPNext(Attachment->pNext, VkStructs, { VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_STENCIL_LAYOUT });
|
|
|
|
int32_t HandledCount = 0;
|
|
|
|
bool bHasStencilLayout = false;
|
|
|
|
const void** Struct = VkStructs.Find(VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_STENCIL_LAYOUT);
|
|
bHasStencilLayout = Struct != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasStencilLayout, sizeof(bool));
|
|
|
|
if (bHasStencilLayout)
|
|
{
|
|
VkAttachmentReferenceStencilLayout* StencilLayout = (VkAttachmentReferenceStencilLayout*)*Struct;
|
|
check(StencilLayout->pNext == nullptr);
|
|
COPY_TO_BUFFER(MemoryStream, StencilLayout, sizeof(VkAttachmentReferenceStencilLayout));
|
|
HandledCount++;
|
|
}
|
|
|
|
check(HandledCount == VkStructs.Num());
|
|
}
|
|
|
|
void PipelineToBinary(FVulkanDevice* Device, const VkGraphicsPipelineCreateInfo* PipelineInfo, const FGfxPipelineDesc* GfxEntry, const FVulkanRenderTargetLayout* RTLayout, TArray<char>& MemoryStream)
|
|
{
|
|
static const unsigned int INITIAL_PSO_STREAM_SIZE = 64 * 1024;
|
|
MemoryStream.Reserve(INITIAL_PSO_STREAM_SIZE);
|
|
|
|
GraphicsPipelineCreateInfo pipelineCreateInfo;
|
|
|
|
pipelineCreateInfo.PipelineCreateFlags = PipelineInfo->flags;
|
|
pipelineCreateInfo.StageCount = PipelineInfo->stageCount;
|
|
|
|
pipelineCreateInfo.bHasVkPipelineVertexInputStateCreateInfo = PipelineInfo->pVertexInputState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineInputAssemblyStateCreateInfo = PipelineInfo->pInputAssemblyState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineTessellationStateCreateInfo = PipelineInfo->pTessellationState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineViewportStateCreateInfo = PipelineInfo->pViewportState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineRasterizationStateCreateInfo = PipelineInfo->pRasterizationState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineMultisampleStateCreateInfo = PipelineInfo->pMultisampleState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineDepthStencilStateCreateInfo = PipelineInfo->pDepthStencilState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineColorBlendStateCreateInfo = PipelineInfo->pColorBlendState != nullptr;
|
|
pipelineCreateInfo.bHasVkPipelineDynamicStateCreateInfo = PipelineInfo->pDynamicState != nullptr;
|
|
|
|
pipelineCreateInfo.subpass = PipelineInfo->subpass;
|
|
|
|
TArray<const ANSICHAR*> InstanceLayers = GVulkanRHI->GetInstanceLayers();
|
|
CharArrayToBuffer(InstanceLayers, MemoryStream);
|
|
TArray<const ANSICHAR*> InstanceExtensions = GVulkanRHI->GetInstanceExtensions();
|
|
CharArrayToBuffer(InstanceExtensions, MemoryStream);
|
|
TArray<const ANSICHAR*> DeviceExtensions = Device->GetDeviceExtensions();
|
|
CharArrayToBuffer(DeviceExtensions, MemoryStream);
|
|
COPY_TO_BUFFER(MemoryStream, &pipelineCreateInfo, sizeof(GraphicsPipelineCreateInfo));
|
|
|
|
HandleGraphicsPipelineCreatePNext(PipelineInfo, MemoryStream);
|
|
|
|
// VkPipelineShaderStageCreateInfo
|
|
for (int32_t Idx = 0; Idx < PipelineInfo->stageCount; ++Idx)
|
|
{
|
|
VkPipelineShaderStageCreateInfo ShaderStage;
|
|
FMemory::Memzero(ShaderStage);
|
|
|
|
ShaderStage.sType = PipelineInfo->pStages[Idx].sType;
|
|
ShaderStage.flags = PipelineInfo->pStages[Idx].flags;
|
|
ShaderStage.stage = PipelineInfo->pStages[Idx].stage;
|
|
|
|
HandlePipelineShaderStagePNext(&PipelineInfo->pStages[Idx], MemoryStream);
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &ShaderStage, sizeof(VkPipelineShaderStageCreateInfo));
|
|
|
|
uint32_t NameLength = static_cast<uint32_t>(strlen(PipelineInfo->pStages[Idx].pName)+1);
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &NameLength, sizeof(uint32_t));
|
|
COPY_TO_BUFFER(MemoryStream, PipelineInfo->pStages[Idx].pName, NameLength);
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineVertexInputStateCreateInfo)
|
|
{
|
|
const VkPipelineVertexInputStateCreateInfo* VertexInputState = PipelineInfo->pVertexInputState;
|
|
check(VertexInputState->pNext == nullptr);
|
|
|
|
VkPipelineVertexInputStateCreateInfo CopyVertexInputState;
|
|
FMemory::Memzero(CopyVertexInputState);
|
|
|
|
CopyVertexInputState.sType = VertexInputState->sType;
|
|
CopyVertexInputState.flags = VertexInputState->flags;
|
|
CopyVertexInputState.vertexBindingDescriptionCount = VertexInputState->vertexBindingDescriptionCount;
|
|
CopyVertexInputState.vertexAttributeDescriptionCount = VertexInputState->vertexAttributeDescriptionCount;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &CopyVertexInputState, sizeof(VkPipelineVertexInputStateCreateInfo));
|
|
|
|
if(VertexInputState->vertexBindingDescriptionCount > 0)
|
|
{
|
|
uint32_t Length = sizeof(VkVertexInputBindingDescription) * VertexInputState->vertexBindingDescriptionCount;
|
|
COPY_TO_BUFFER(MemoryStream, VertexInputState->pVertexBindingDescriptions, Length);
|
|
}
|
|
|
|
if(VertexInputState->vertexAttributeDescriptionCount > 0)
|
|
{
|
|
uint32_t Length = sizeof(VkVertexInputAttributeDescription) * VertexInputState->vertexAttributeDescriptionCount;
|
|
COPY_TO_BUFFER(MemoryStream, VertexInputState->pVertexAttributeDescriptions, Length);
|
|
}
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineInputAssemblyStateCreateInfo)
|
|
{
|
|
VkPipelineInputAssemblyStateCreateInfo InputAssemblyCreateInfo = *PipelineInfo->pInputAssemblyState;
|
|
check(PipelineInfo->pInputAssemblyState->pNext == nullptr);
|
|
|
|
InputAssemblyCreateInfo.pNext = nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &InputAssemblyCreateInfo, sizeof(VkPipelineInputAssemblyStateCreateInfo));
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineTessellationStateCreateInfo)
|
|
{
|
|
VkPipelineTessellationStateCreateInfo TesselationCreateInfo = *PipelineInfo->pTessellationState;
|
|
check(TesselationCreateInfo.pNext == nullptr);
|
|
|
|
TesselationCreateInfo.pNext = nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &TesselationCreateInfo, sizeof(VkPipelineTessellationStateCreateInfo));
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineViewportStateCreateInfo)
|
|
{
|
|
VkPipelineViewportStateCreateInfo ViewportState = *PipelineInfo->pViewportState;
|
|
check(ViewportState.pNext == nullptr);
|
|
|
|
ViewportState.pNext = nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &ViewportState, sizeof(VkPipelineViewportStateCreateInfo));
|
|
|
|
uint32_t ViewportCount = ViewportState.viewportCount && ViewportState.pViewports ? ViewportState.viewportCount : 0;
|
|
COPY_TO_BUFFER(MemoryStream, &ViewportCount, sizeof(uint32_t));
|
|
|
|
if (ViewportCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, ViewportState.pViewports, sizeof(VkViewport) * ViewportCount);
|
|
}
|
|
|
|
uint32_t ScissorCount = ViewportState.scissorCount && ViewportState.pScissors ? ViewportState.scissorCount : 0;
|
|
COPY_TO_BUFFER(MemoryStream, &ScissorCount, sizeof(uint32_t));
|
|
|
|
if (ScissorCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, ViewportState.pScissors, sizeof(VkRect2D) * ScissorCount);
|
|
}
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineRasterizationStateCreateInfo)
|
|
{
|
|
VkPipelineRasterizationStateCreateInfo RasterizationState = *PipelineInfo->pRasterizationState;
|
|
check(RasterizationState.pNext == nullptr);
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &RasterizationState, sizeof(VkPipelineRasterizationStateCreateInfo));
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineMultisampleStateCreateInfo)
|
|
{
|
|
VkPipelineMultisampleStateCreateInfo MultiSampleState = *PipelineInfo->pMultisampleState;
|
|
check(MultiSampleState.pNext == nullptr);
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &MultiSampleState, sizeof(VkPipelineMultisampleStateCreateInfo));
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineDepthStencilStateCreateInfo)
|
|
{
|
|
VkPipelineDepthStencilStateCreateInfo DepthStencilState = *PipelineInfo->pDepthStencilState;
|
|
check(DepthStencilState.pNext == nullptr);
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &DepthStencilState, sizeof(VkPipelineDepthStencilStateCreateInfo));
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineColorBlendStateCreateInfo)
|
|
{
|
|
VkPipelineColorBlendStateCreateInfo ColorBlendState = *PipelineInfo->pColorBlendState;
|
|
check(ColorBlendState.pNext == nullptr);
|
|
|
|
ColorBlendState.pAttachments = nullptr;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &ColorBlendState, sizeof(VkPipelineColorBlendStateCreateInfo));
|
|
|
|
if(ColorBlendState.attachmentCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, PipelineInfo->pColorBlendState->pAttachments, sizeof(VkPipelineColorBlendAttachmentState)* ColorBlendState.attachmentCount);
|
|
}
|
|
}
|
|
|
|
if (pipelineCreateInfo.bHasVkPipelineDynamicStateCreateInfo)
|
|
{
|
|
VkPipelineDynamicStateCreateInfo DynamicState = *PipelineInfo->pDynamicState;
|
|
check(DynamicState.pNext == nullptr);
|
|
|
|
DynamicState.pDynamicStates = nullptr;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &DynamicState, sizeof(VkPipelineDynamicStateCreateInfo));
|
|
|
|
if (DynamicState.dynamicStateCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, PipelineInfo->pDynamicState->pDynamicStates, sizeof(VkDynamicState) * DynamicState.dynamicStateCount);
|
|
}
|
|
}
|
|
|
|
VkPipelineLayoutCreateInfo PipelineLayout;
|
|
FMemory::Memzero(PipelineLayout);
|
|
|
|
PipelineLayout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
PipelineLayout.setLayoutCount = GfxEntry->DescriptorSetLayoutBindings.Num();
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &PipelineLayout, sizeof(VkPipelineLayoutCreateInfo));
|
|
|
|
for (uint32_t Idx = 0; Idx < GfxEntry->DescriptorSetLayoutBindings.Num(); ++Idx)
|
|
{
|
|
uint32_t SetBindingsCount = GfxEntry->DescriptorSetLayoutBindings[Idx].Num();
|
|
COPY_TO_BUFFER(MemoryStream, &SetBindingsCount, sizeof(uint32_t));
|
|
|
|
for (auto DescriptorSetBinding : GfxEntry->DescriptorSetLayoutBindings[Idx])
|
|
{
|
|
VkDescriptorSetLayoutBinding binding;
|
|
binding.descriptorType = (VkDescriptorType)DescriptorSetBinding.DescriptorType;
|
|
binding.binding = DescriptorSetBinding.Binding;
|
|
binding.stageFlags = DescriptorSetBinding.StageFlags;
|
|
binding.descriptorCount = 1;
|
|
binding.pImmutableSamplers = 0;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &binding, sizeof(VkDescriptorSetLayoutBinding));
|
|
}
|
|
}
|
|
|
|
// Render pass
|
|
bool bUseRenderPass2 = false;
|
|
|
|
bUseRenderPass2 = Device->GetOptionalExtensions().HasKHRRenderPass2;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &bUseRenderPass2, sizeof(bool));
|
|
|
|
#if VULKAN_SUPPORTS_RENDERPASS2
|
|
if (bUseRenderPass2)
|
|
{
|
|
FVulkanRenderPassBuilder<FVulkanSubpassDescription<VkSubpassDescription2>, FVulkanSubpassDependency<VkSubpassDependency2>, FVulkanAttachmentReference<VkAttachmentReference2>, FVulkanAttachmentDescription<VkAttachmentDescription2>, FVulkanRenderPassCreateInfo<VkRenderPassCreateInfo2>> Creator(*Device);
|
|
|
|
Creator.BuildCreateInfo(*RTLayout);
|
|
|
|
FVulkanRenderPassCreateInfo<VkRenderPassCreateInfo2>& CreateInfo = Creator.GetCreateInfo();
|
|
|
|
VkRenderPassCreateInfo2 RenderPassCreateInfo;
|
|
FMemory::Memzero(RenderPassCreateInfo);
|
|
|
|
RenderPassCreateInfo.sType = CreateInfo.sType;
|
|
RenderPassCreateInfo.flags = CreateInfo.flags;
|
|
RenderPassCreateInfo.attachmentCount = CreateInfo.attachmentCount;
|
|
RenderPassCreateInfo.subpassCount = CreateInfo.subpassCount;
|
|
RenderPassCreateInfo.dependencyCount = CreateInfo.dependencyCount;
|
|
RenderPassCreateInfo.correlatedViewMaskCount = CreateInfo.correlatedViewMaskCount;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &RenderPassCreateInfo, sizeof(VkRenderPassCreateInfo2));
|
|
|
|
// Check for VK_STRUCTURE_TYPE_RENDER_PASS_FRAGMENT_DENSITY_MAP_CREATE_INFO_EXT
|
|
bool bHasCreateInfoNext = RenderPassCreateInfo.pNext != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasCreateInfoNext, sizeof(bool));
|
|
|
|
if (bHasCreateInfoNext)
|
|
{
|
|
VkStructureType NextType = *(VkStructureType*)RenderPassCreateInfo.pNext;
|
|
check(NextType == VK_STRUCTURE_TYPE_RENDER_PASS_FRAGMENT_DENSITY_MAP_CREATE_INFO_EXT);
|
|
|
|
VkRenderPassFragmentDensityMapCreateInfoEXT* FragmentDensityMap = (VkRenderPassFragmentDensityMapCreateInfoEXT*)RenderPassCreateInfo.pNext;
|
|
COPY_TO_BUFFER(MemoryStream, FragmentDensityMap, sizeof(VkRenderPassFragmentDensityMapCreateInfoEXT));
|
|
|
|
check(FragmentDensityMap->pNext == nullptr);
|
|
}
|
|
|
|
if(RenderPassCreateInfo.attachmentCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pAttachments, sizeof(VkAttachmentDescription2) * RenderPassCreateInfo.attachmentCount);
|
|
}
|
|
|
|
if(RenderPassCreateInfo.dependencyCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pDependencies, sizeof(VkSubpassDependency2) * RenderPassCreateInfo.dependencyCount);
|
|
}
|
|
|
|
for (uint32_t Idx = 0; Idx < RenderPassCreateInfo.subpassCount; ++Idx)
|
|
{
|
|
VkSubpassDescription2 SubpassDescription = CreateInfo.pSubpasses[Idx];
|
|
SubpassDescription.pColorAttachments = nullptr;
|
|
SubpassDescription.pDepthStencilAttachment = nullptr;
|
|
SubpassDescription.pPreserveAttachments = nullptr;
|
|
SubpassDescription.pInputAttachments = nullptr;
|
|
SubpassDescription.pResolveAttachments = nullptr;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &SubpassDescription, sizeof(VkSubpassDescription2));
|
|
|
|
HandleSubpassDescriptionPNext(&SubpassDescription, MemoryStream);
|
|
|
|
if(SubpassDescription.colorAttachmentCount > 0)
|
|
{
|
|
for (uint32_t n = 0; n < SubpassDescription.colorAttachmentCount; ++n)
|
|
{
|
|
check(CreateInfo.pSubpasses[Idx].pColorAttachments[n].pNext == nullptr);
|
|
}
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pColorAttachments, sizeof(VkAttachmentReference2) * SubpassDescription.colorAttachmentCount);
|
|
}
|
|
|
|
if(SubpassDescription.inputAttachmentCount > 0)
|
|
{
|
|
for (uint32_t n = 0; n < SubpassDescription.inputAttachmentCount; ++n)
|
|
{
|
|
check(CreateInfo.pSubpasses[Idx].pInputAttachments[n].pNext == nullptr);
|
|
}
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pInputAttachments, sizeof(VkAttachmentReference2) * SubpassDescription.inputAttachmentCount);
|
|
}
|
|
|
|
bool bHasResolveAttachment = CreateInfo.pSubpasses[Idx].pResolveAttachments != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasResolveAttachment, sizeof(bool));
|
|
|
|
if (bHasResolveAttachment)
|
|
{
|
|
if(SubpassDescription.colorAttachmentCount > 0)
|
|
{
|
|
for (uint32_t n = 0; n < SubpassDescription.colorAttachmentCount; ++n)
|
|
{
|
|
check(CreateInfo.pSubpasses[Idx].pResolveAttachments[n].pNext == nullptr);
|
|
}
|
|
check(CreateInfo.pSubpasses[Idx].pResolveAttachments == nullptr);
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pResolveAttachments, sizeof(VkAttachmentReference2)* SubpassDescription.colorAttachmentCount);
|
|
}
|
|
}
|
|
|
|
bool bHasDepthStencilAttachment = CreateInfo.pSubpasses[Idx].pDepthStencilAttachment != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasDepthStencilAttachment, sizeof(bool));
|
|
|
|
if (bHasDepthStencilAttachment)
|
|
{
|
|
HandleDepthStencilAttachmentPNext(CreateInfo.pSubpasses[Idx].pDepthStencilAttachment, MemoryStream);
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pDepthStencilAttachment, sizeof(VkAttachmentReference2));
|
|
}
|
|
}
|
|
|
|
if(RenderPassCreateInfo.correlatedViewMaskCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pCorrelatedViewMasks, sizeof(uint32_t) * RenderPassCreateInfo.correlatedViewMaskCount);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
FVulkanRenderPassBuilder<FVulkanSubpassDescription<VkSubpassDescription>, FVulkanSubpassDependency<VkSubpassDependency>, FVulkanAttachmentReference<VkAttachmentReference>, FVulkanAttachmentDescription<VkAttachmentDescription>, FVulkanRenderPassCreateInfo<VkRenderPassCreateInfo>> Creator(*Device);
|
|
|
|
Creator.BuildCreateInfo(*RTLayout);
|
|
|
|
FVulkanRenderPassCreateInfo<VkRenderPassCreateInfo>& CreateInfo = Creator.GetCreateInfo();
|
|
|
|
VkRenderPassCreateInfo RenderPassCreateInfo;
|
|
FMemory::Memzero(RenderPassCreateInfo);
|
|
|
|
RenderPassCreateInfo.sType = CreateInfo.sType;
|
|
RenderPassCreateInfo.flags = CreateInfo.flags;
|
|
RenderPassCreateInfo.attachmentCount = CreateInfo.attachmentCount;
|
|
RenderPassCreateInfo.subpassCount = CreateInfo.subpassCount;
|
|
RenderPassCreateInfo.dependencyCount = CreateInfo.dependencyCount;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &RenderPassCreateInfo, sizeof(VkRenderPassCreateInfo));
|
|
|
|
bool bHasCreateInfoNext = RenderPassCreateInfo.pNext != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasCreateInfoNext, sizeof(bool));
|
|
|
|
if (bHasCreateInfoNext)
|
|
{
|
|
VkStructureType NextType = *(VkStructureType*)RenderPassCreateInfo.pNext;
|
|
check(NextType == VK_STRUCTURE_TYPE_RENDER_PASS_FRAGMENT_DENSITY_MAP_CREATE_INFO_EXT);
|
|
|
|
VkRenderPassFragmentDensityMapCreateInfoEXT* FragmentDensityMap = (VkRenderPassFragmentDensityMapCreateInfoEXT*)RenderPassCreateInfo.pNext;
|
|
COPY_TO_BUFFER(MemoryStream, FragmentDensityMap, sizeof(VkRenderPassFragmentDensityMapCreateInfoEXT));
|
|
|
|
check(FragmentDensityMap->pNext == nullptr);
|
|
// TODO: Support Multiview create info
|
|
}
|
|
|
|
if(RenderPassCreateInfo.attachmentCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pAttachments, sizeof(VkAttachmentDescription) * RenderPassCreateInfo.attachmentCount);
|
|
}
|
|
|
|
if(RenderPassCreateInfo.dependencyCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pDependencies, sizeof(VkSubpassDependency) * RenderPassCreateInfo.dependencyCount);
|
|
}
|
|
|
|
for (uint32_t Idx = 0; Idx < RenderPassCreateInfo.subpassCount; ++Idx)
|
|
{
|
|
VkSubpassDescription SubpassDescription = CreateInfo.pSubpasses[Idx];
|
|
SubpassDescription.pColorAttachments = nullptr;
|
|
SubpassDescription.pDepthStencilAttachment = nullptr;
|
|
SubpassDescription.pPreserveAttachments = nullptr;
|
|
SubpassDescription.pInputAttachments = nullptr;
|
|
SubpassDescription.pResolveAttachments = nullptr;
|
|
|
|
COPY_TO_BUFFER(MemoryStream, &SubpassDescription, sizeof(VkSubpassDescription));
|
|
|
|
if(SubpassDescription.colorAttachmentCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pColorAttachments, sizeof(VkAttachmentReference) * SubpassDescription.colorAttachmentCount);
|
|
}
|
|
|
|
if(SubpassDescription.inputAttachmentCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pInputAttachments, sizeof(VkAttachmentReference) * SubpassDescription.inputAttachmentCount);
|
|
}
|
|
|
|
bool bHasResolveAttachment = CreateInfo.pSubpasses[Idx].pResolveAttachments != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasResolveAttachment, sizeof(bool));
|
|
|
|
if (bHasResolveAttachment)
|
|
{
|
|
if(SubpassDescription.colorAttachmentCount > 0)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pResolveAttachments, sizeof(VkAttachmentReference)* SubpassDescription.colorAttachmentCount);
|
|
}
|
|
}
|
|
|
|
bool bHasDepthStencilAttachment = CreateInfo.pSubpasses[Idx].pDepthStencilAttachment != nullptr;
|
|
COPY_TO_BUFFER(MemoryStream, &bHasDepthStencilAttachment, sizeof(bool));
|
|
|
|
if (bHasDepthStencilAttachment)
|
|
{
|
|
COPY_TO_BUFFER(MemoryStream, CreateInfo.pSubpasses[Idx].pDepthStencilAttachment, sizeof(VkAttachmentReference));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if UE_BUILD_SHIPPING
|
|
#define CHECK_JNI_EXCEPTIONS(env) env->ExceptionClear();
|
|
#else
|
|
#define CHECK_JNI_EXCEPTIONS(env) if (env->ExceptionCheck()) {env->ExceptionDescribe();env->ExceptionClear();}
|
|
#endif
|
|
|
|
namespace AndroidVulkanService
|
|
{
|
|
std::atomic<bool> GRemoteCompileServicesStarted = false;
|
|
std::atomic<bool> GRemoteCompileServicesActive = false;
|
|
std::atomic<bool> bOneTimeErrorEncountered = false;
|
|
std::atomic<int> TotalErrors = 0;
|
|
}
|
|
|
|
struct FVKRemoteProgramCompileJNI
|
|
{
|
|
jclass PSOServiceAccessor = 0;
|
|
jmethodID DispatchPSOCompile = 0;
|
|
jmethodID DispatchPSOCompileShm = 0;
|
|
jmethodID StartRemoteProgramLink = 0;
|
|
jmethodID HaveServicesFailed = 0;
|
|
jmethodID AreProgramServicesReady = 0;
|
|
jmethodID StopRemoteProgramLink = 0;
|
|
jclass ProgramResponseClass = 0;
|
|
jfieldID ProgramResponse_SuccessField = 0;
|
|
jfieldID ProgramResponse_ErrorField = 0;
|
|
jfieldID ProgramResponse_SHMOutputHandleField = 0;
|
|
jfieldID ProgramResponse_CompiledBinaryField = 0;
|
|
jfieldID ProgramResponse_CompilationDurationField = 0;
|
|
bool bAllFound = false;
|
|
|
|
void Init(JNIEnv* Env)
|
|
{
|
|
// class JNIProgramLinkResponse
|
|
// {
|
|
// boolean bCompileSuccess;
|
|
// String ErrorMessage;
|
|
// byte[] CompiledProgram;
|
|
// };
|
|
// JNIProgramLinkResponse AndroidThunkJava_OGLRemoteProgramLink(...):
|
|
|
|
if (PSOServiceAccessor)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(PSOServiceAccessor == 0);
|
|
PSOServiceAccessor = AndroidJavaEnv::FindJavaClassGlobalRef("com/epicgames/unreal/psoservices/PSOProgramServiceAccessor");
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
if (PSOServiceAccessor)
|
|
{
|
|
DispatchPSOCompile = FJavaWrapper::FindStaticMethod(Env, PSOServiceAccessor, "AndroidThunkJava_VKPSOGFXCompile", "([BJ[B[B[B[BZ)Lcom/epicgames/unreal/psoservices/PSOProgramServiceAccessor$JNIProgramLinkResponse;", false);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
DispatchPSOCompileShm = FJavaWrapper::FindStaticMethod(Env, PSOServiceAccessor, "AndroidThunkJava_VKPSOGFXCompileShm", "([BJIJJJJZ)Lcom/epicgames/unreal/psoservices/PSOProgramServiceAccessor$JNIProgramLinkResponse;", false);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
StartRemoteProgramLink = FJavaWrapper::FindStaticMethod(Env, PSOServiceAccessor, "AndroidThunkJava_StartRemoteProgramLink", "(IZZ)Z", false);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
HaveServicesFailed = FJavaWrapper::FindStaticMethod(Env, PSOServiceAccessor, "AndroidThunkJava_HaveServicesFailed", "()Z", false);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
AreProgramServicesReady = FJavaWrapper::FindStaticMethod(Env, PSOServiceAccessor, "AndroidThunkJava_AreProgramServicesReady", "()Z", false);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
StopRemoteProgramLink = FJavaWrapper::FindStaticMethod(Env, PSOServiceAccessor, "AndroidThunkJava_StopRemoteProgramLink", "()V", false);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
ProgramResponseClass = AndroidJavaEnv::FindJavaClassGlobalRef("com/epicgames/unreal/psoservices/PSOProgramServiceAccessor$JNIProgramLinkResponse");
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
ProgramResponse_SuccessField = FJavaWrapper::FindField(Env, ProgramResponseClass, "bCompileSuccess", "Z", true);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
ProgramResponse_CompiledBinaryField = FJavaWrapper::FindField(Env, ProgramResponseClass, "CompiledProgram", "[B", true);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
ProgramResponse_ErrorField = FJavaWrapper::FindField(Env, ProgramResponseClass, "ErrorMessage", "Ljava/lang/String;", true);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
ProgramResponse_SHMOutputHandleField = FJavaWrapper::FindField(Env, ProgramResponseClass, "SHMOutputHandle", "I", true);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
ProgramResponse_CompilationDurationField = FJavaWrapper::FindField(Env, ProgramResponseClass, "CompilationDuration", "F", true);
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
}
|
|
|
|
bAllFound = PSOServiceAccessor && DispatchPSOCompile && DispatchPSOCompileShm && StartRemoteProgramLink && HaveServicesFailed && AreProgramServicesReady && StopRemoteProgramLink && ProgramResponseClass && ProgramResponse_SuccessField && ProgramResponse_CompiledBinaryField && ProgramResponse_ErrorField && ProgramResponse_SHMOutputHandleField && ProgramResponse_CompilationDurationField;
|
|
UE_CLOG(!bAllFound, LogRHI, Fatal, TEXT("Failed to find JNI Vulkan remote program compiler."));
|
|
}
|
|
}VKRemoteProgramCompileJNI;
|
|
|
|
static bool AreAndroidVulkanRemoteCompileServicesAvailable()
|
|
{
|
|
static int RemoteCompileService = -1;
|
|
if (RemoteCompileService == -1)
|
|
{
|
|
const FString* ConfigRulesDisableProgramCompileServices = FAndroidMisc::GetConfigRulesVariable(TEXT("DisableProgramCompileServices"));
|
|
bool bConfigRulesDisableProgramCompileServices = ConfigRulesDisableProgramCompileServices && ConfigRulesDisableProgramCompileServices->Equals("true", ESearchCase::IgnoreCase);
|
|
static const auto CVarProgramLRU = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Vulkan.EnablePipelineLRUCache"));
|
|
static const auto CVarNumRemoteProgramCompileServices = IConsoleManager::Get().FindConsoleVariable(TEXT("Android.Vulkan.NumRemoteProgramCompileServices"));
|
|
|
|
RemoteCompileService = !bConfigRulesDisableProgramCompileServices && VKRemoteProgramCompileJNI.bAllFound && (CVarProgramLRU->GetInt() != 0) && (CVarNumRemoteProgramCompileServices->GetInt() > 0);
|
|
FGenericCrashContext::SetEngineData(TEXT("Android.PSOService"), RemoteCompileService == 0 ? TEXT("disabled") : TEXT("enabled"));
|
|
|
|
UE_LOG(LogRHI, Log, TEXT("External PSO compilers = %s"), RemoteCompileService == 0 ? TEXT("disabled") : TEXT("enabled"));
|
|
}
|
|
return RemoteCompileService;
|
|
}
|
|
|
|
bool FVulkanAndroidPlatform::AreRemoteCompileServicesActive()
|
|
{
|
|
// The services could be stopped at any point elsewhere, the return value is not guaranteed to be correct.
|
|
// it does not need to be exact as the PSO service will reject any new requests after service stop has been encountered.
|
|
// any existing PSOservice jobs will complete as normal.
|
|
if (AndroidVulkanService::GRemoteCompileServicesStarted && AreAndroidVulkanRemoteCompileServicesAvailable())
|
|
{
|
|
if (!AndroidVulkanService::GRemoteCompileServicesActive)
|
|
{
|
|
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
|
|
AndroidVulkanService::GRemoteCompileServicesActive = (bool)Env->CallStaticBooleanMethod(VKRemoteProgramCompileJNI.PSOServiceAccessor, VKRemoteProgramCompileJNI.AreProgramServicesReady);
|
|
if (!AndroidVulkanService::GRemoteCompileServicesActive)
|
|
{
|
|
if ((bool)Env->CallStaticBooleanMethod(VKRemoteProgramCompileJNI.PSOServiceAccessor, VKRemoteProgramCompileJNI.HaveServicesFailed))
|
|
{
|
|
UE_LOG(LogRHI, Error, TEXT("Remote compile services failed to start."));
|
|
StopRemoteCompileServices();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogRHI, Log, TEXT("Remote compile services are active."));
|
|
}
|
|
}
|
|
return AndroidVulkanService::GRemoteCompileServicesActive;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FVulkanAndroidPlatform::StartRemoteCompileServices(int NumServices)
|
|
{
|
|
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
|
|
|
|
VKRemoteProgramCompileJNI.Init(Env);
|
|
|
|
if (Env && AreAndroidVulkanRemoteCompileServicesAvailable() && !AndroidVulkanService::GRemoteCompileServicesStarted)
|
|
{
|
|
AndroidVulkanService::GRemoteCompileServicesStarted = (bool)Env->CallStaticBooleanMethod(VKRemoteProgramCompileJNI.PSOServiceAccessor, VKRemoteProgramCompileJNI.StartRemoteProgramLink, (jint)NumServices, /*bUseRobustEGLContext*/(jboolean)false, /*bUseVulkan*/(jboolean)true);
|
|
}
|
|
|
|
return AndroidVulkanService::GRemoteCompileServicesStarted;
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::StopRemoteCompileServices()
|
|
{
|
|
bool bExpected = true;
|
|
if(AndroidVulkanService::GRemoteCompileServicesStarted.compare_exchange_strong(bExpected, false) )
|
|
{
|
|
UE_LOG(LogVulkanRHI, Log, TEXT("Stopping Remote Compile Services"));
|
|
AndroidVulkanService::GRemoteCompileServicesActive = false;
|
|
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
|
|
|
|
if (Env && ensure(AreAndroidVulkanRemoteCompileServicesAvailable()))
|
|
{
|
|
Env->CallStaticVoidMethod(VKRemoteProgramCompileJNI.PSOServiceAccessor, VKRemoteProgramCompileJNI.StopRemoteProgramLink);
|
|
}
|
|
}
|
|
}
|
|
|
|
VkPipelineCache FVulkanAndroidPlatform::PrecompilePSO(
|
|
FVulkanDevice* Device,
|
|
const TArrayView<uint8> OptionalPSOCacheData,
|
|
FGraphicsPipelineStateInitializer::EPSOPrecacheCompileType PSOCompileType,
|
|
const VkGraphicsPipelineCreateInfo* PipelineInfo,
|
|
const FGfxPipelineDesc* GfxEntry,
|
|
const FVulkanRenderTargetLayout* RTLayout,
|
|
TArrayView<uint32_t> VS,
|
|
TArrayView<uint32_t> PS,
|
|
size_t& AfterSize,
|
|
FString* FailureMessageOUT
|
|
)
|
|
{
|
|
if (!ensure(AreRemoteCompileServicesActive()))
|
|
{
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_VulkanAndroid_PrecompilePSO);
|
|
|
|
auto SetFailureMessage = [FailureMessageOUT](FString&& Msg) { if (FailureMessageOUT) { *FailureMessageOUT = Msg; }};
|
|
|
|
VkPipelineCache ReturnPipelineCache = VK_NULL_HANDLE;
|
|
FPlatformDynamicRHI::FPSOServicePriInfo PriorityInfo(PSOCompileType);
|
|
|
|
// FScopedDurationTimeLogger Timer(TEXT("FVulkanAndroidPlatform::PrecompilePSO"));
|
|
|
|
TArray<char> MemoryStream;
|
|
PipelineToBinary(Device, PipelineInfo, GfxEntry, RTLayout, MemoryStream);
|
|
|
|
bool bResult = false;
|
|
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
|
|
FString ErrorMessage;
|
|
|
|
if (Env && ensure(VKRemoteProgramCompileJNI.bAllFound))
|
|
{
|
|
// In this version we pass all data via shared buffer, offsets are still supplied via args
|
|
auto ArrayByteSize = [](const auto& ArrayToSize) {return ArrayToSize.GetTypeSize() * ArrayToSize.Num(); };
|
|
|
|
const uint64 VSSize = ArrayByteSize(VS);
|
|
const uint64 PSSize = ArrayByteSize(PS);
|
|
const uint64 PSOParamsSize = ArrayByteSize(MemoryStream);
|
|
const uint64 PreSuppliedCacheSize = ArrayByteSize(OptionalPSOCacheData);
|
|
const uint64 TotalOutputSize = VSSize + PSSize + PSOParamsSize + PreSuppliedCacheSize;
|
|
|
|
auto ProgramKeyBuffer = NewScopedJavaObject(Env, Env->NewByteArray(4));
|
|
Env->SetByteArrayRegion(*ProgramKeyBuffer, 0, 4, reinterpret_cast<const jbyte*>("Test"));
|
|
|
|
// create a shared mem region for external process to access.
|
|
FScopedJavaObject<_jobject*> ProgramResponseObj;
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_VulkanAndroid_PrecompilePSO1);
|
|
|
|
const uint64 TotalSHMSize = TotalOutputSize;
|
|
const uint64 TotalSHMSizeAligned = Align(TotalSHMSize, FPlatformMemory::GetConstants().PageSize);
|
|
int SharedMemFD = ASharedMemory_create("", TotalSHMSizeAligned);
|
|
if (ensure(SharedMemFD > -1))
|
|
{
|
|
// By default it has PROT_READ | PROT_WRITE | PROT_EXEC.
|
|
size_t memSize = ASharedMemory_getSize(SharedMemFD);
|
|
char* SharedBuffer = (char*)mmap(NULL, memSize, PROT_READ | PROT_WRITE, MAP_SHARED, SharedMemFD, 0);
|
|
if (ensure(SharedBuffer))
|
|
{
|
|
char* AppendPtr = SharedBuffer;
|
|
auto AppendBuffer = [&AppendPtr,&SharedBuffer,&TotalSHMSize](const auto& AppendMe)
|
|
{
|
|
const size_t NumBytes = AppendMe.Num() * AppendMe.GetTypeSize();
|
|
if( ensure(AppendPtr>=SharedBuffer && ((AppendPtr+NumBytes) <= (SharedBuffer+TotalSHMSize))) )
|
|
{
|
|
FMemory::Memcpy(AppendPtr, (const char*)AppendMe.GetData(), NumBytes);
|
|
AppendPtr += NumBytes;
|
|
}
|
|
};
|
|
|
|
AppendBuffer(VS);
|
|
AppendBuffer(PS);
|
|
AppendBuffer(MemoryStream);
|
|
AppendBuffer(OptionalPSOCacheData);
|
|
|
|
// limit access to read only
|
|
ASharedMemory_setProt(SharedMemFD, PROT_READ);
|
|
|
|
// dont time out if the debugger is attached.
|
|
bool bEnableTimeOuts = !FPlatformMisc::IsDebuggerPresent();
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_VulkanAndroid_PrecompilePSOJAVA);
|
|
ProgramResponseObj = NewScopedJavaObject(Env, Env->CallStaticObjectMethod(VKRemoteProgramCompileJNI.PSOServiceAccessor, VKRemoteProgramCompileJNI.DispatchPSOCompileShm, *ProgramKeyBuffer, PriorityInfo.GetPriorityInfo(), SharedMemFD, VSSize, PSSize, PSOParamsSize, PreSuppliedCacheSize, bEnableTimeOuts));
|
|
}
|
|
CHECK_JNI_EXCEPTIONS(Env);
|
|
munmap(SharedBuffer, memSize);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogRHI, Error, TEXT("Failed to alloc %d bytes for external PSO compile: %d"),
|
|
TotalSHMSizeAligned,
|
|
errno
|
|
);
|
|
}
|
|
close(SharedMemFD);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogRHI, Error, TEXT("Failed to alloc %d bytes for external PSO compile: %d (%s)"),
|
|
TotalSHMSizeAligned,
|
|
errno,
|
|
(errno == EMFILE) ? TEXT("too many open file descriptors") : TEXT("unknown")
|
|
);
|
|
}
|
|
}
|
|
|
|
if (ProgramResponseObj)
|
|
{
|
|
const bool bSucceeded = (bool)Env->GetBooleanField(*ProgramResponseObj, VKRemoteProgramCompileJNI.ProgramResponse_SuccessField);
|
|
if (bSucceeded)
|
|
{
|
|
const int ProgramResultSharedHandle = Env->GetIntField(*ProgramResponseObj, VKRemoteProgramCompileJNI.ProgramResponse_SHMOutputHandleField);
|
|
const float ProgramResultCompilationDuration = Env->GetFloatField(*ProgramResponseObj, VKRemoteProgramCompileJNI.ProgramResponse_CompilationDurationField);
|
|
AccumulatePSOMetrics(ProgramResultCompilationDuration);
|
|
if(ensure(ProgramResultSharedHandle > -1))
|
|
{
|
|
const uint32 ResultMemSize = (uint32)ASharedMemory_getSize(ProgramResultSharedHandle);
|
|
char* ResultSharedBuffer = (char*)mmap(NULL, ResultMemSize, PROT_READ, MAP_SHARED, ProgramResultSharedHandle, 0);
|
|
ON_SCOPE_EXIT{ if (ResultSharedBuffer) { munmap(ResultSharedBuffer, ResultMemSize); } close(ProgramResultSharedHandle); };
|
|
if (ensure(ResultSharedBuffer))
|
|
{
|
|
// Actual size of data is in the first 4 bytes.
|
|
const uint32 ResultSize = *(uint32*)ResultSharedBuffer;
|
|
|
|
if (ensure(ResultMemSize > 0))
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_VulkanAndroid_PrecompilePSOCreateCache);
|
|
VkPipelineCacheCreateInfo PipelineCacheCreateInfo;
|
|
memset(&PipelineCacheCreateInfo, 0, sizeof(VkPipelineCacheCreateInfo));
|
|
PipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
|
PipelineCacheCreateInfo.flags = 0;
|
|
PipelineCacheCreateInfo.pInitialData = ResultSharedBuffer + sizeof(ResultMemSize);
|
|
PipelineCacheCreateInfo.initialDataSize = ResultSize;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkCreatePipelineCache(Device->GetInstanceHandle(), &PipelineCacheCreateInfo, VULKAN_CPU_ALLOCATOR, &ReturnPipelineCache));
|
|
AfterSize = ResultSize - OptionalPSOCacheData.Num();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AndroidVulkanService::bOneTimeErrorEncountered.exchange(true) == false)
|
|
{
|
|
FGenericCrashContext::SetEngineData(TEXT("Android.PSOService"), TEXT("ec"));
|
|
}
|
|
|
|
SetFailureMessage( FJavaHelper::FStringFromLocalRef(Env, (jstring)Env->GetObjectField(*ProgramResponseObj, VKRemoteProgramCompileJNI.ProgramResponse_ErrorField)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AndroidVulkanService::bOneTimeErrorEncountered.exchange(true) == false)
|
|
{
|
|
FGenericCrashContext::SetEngineData(TEXT("Android.PSOService"), TEXT("es"));
|
|
}
|
|
SetFailureMessage(TEXT("Remote PSO compiler failed."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AndroidVulkanService::bOneTimeErrorEncountered.exchange(true) == false)
|
|
{
|
|
FGenericCrashContext::SetEngineData(TEXT("Android.PSOService"), TEXT("ejni"));
|
|
}
|
|
SetFailureMessage(TEXT("Remote PSO compiler JNI error."));
|
|
}
|
|
|
|
if (ReturnPipelineCache == VK_NULL_HANDLE)
|
|
{
|
|
if ((AndroidVulkanService::TotalErrors++) == FPlatformDynamicRHI::GetPSOServiceFailureThreshold())
|
|
{
|
|
FVulkanAndroidPlatform::StopRemoteCompileServices();
|
|
SetFailureMessage(TEXT("Remote PSO compiler failed, error count has passed threshold. Future compiles will be in-process."));
|
|
}
|
|
}
|
|
return ReturnPipelineCache;
|
|
}
|
|
|
|
void FAndroidVulkanFramePacer::Init()
|
|
{
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
if (FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.GetValueOnAnyThread() != 0)
|
|
{
|
|
FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit = true;
|
|
extern void LoadSwappy();
|
|
LoadSwappy();
|
|
}
|
|
#if !UE_BUILD_SHIPPING
|
|
FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.AsVariable()->SetOnChangedCallback(FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("In Vulkan mode, changing a.UseSwappyForFramePacing after the RHI is initialized has no effect. Swappy is %s."), FVulkanAndroidPlatform::bSwappyEnabledAtRHIInit ? TEXT("Enabledd") : TEXT("Disabled"));
|
|
}));
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
bool FAndroidVulkanFramePacer::SupportsFramePace(int32 QueryFramePace)
|
|
{
|
|
int32 TempRefreshRate, TempSyncInterval;
|
|
return SupportsFramePaceInternal(QueryFramePace, TempRefreshRate, TempSyncInterval);
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::PostInitGPU(const FVulkanDevice& InDevice)
|
|
{
|
|
SetupImageMemoryRequirementWorkaround(InDevice);
|
|
SetupRequiresDepthStencilFullWriteWorkaround(InDevice);
|
|
|
|
// start external compilers if precaching is specified.
|
|
static const auto CVarChunkedPSOCache = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Vulkan.UseChunkedPSOCache"));
|
|
static const auto CVarNumRemoteProgramCompileServices = IConsoleManager::Get().FindConsoleVariable(TEXT("Android.Vulkan.NumRemoteProgramCompileServices"));
|
|
static const auto CVarPSOPrecaching = IConsoleManager::Get().FindConsoleVariable(TEXT("r.PSOPrecaching"));
|
|
static const auto CVarVulkanPSOPrecaching = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Vulkan.AllowPSOPrecaching"));
|
|
if (CVarNumRemoteProgramCompileServices->GetInt() && CVarChunkedPSOCache->GetInt() && CVarPSOPrecaching->GetInt() && CVarVulkanPSOPrecaching->GetInt())
|
|
{
|
|
FVulkanAndroidPlatform::StartRemoteCompileServices(CVarNumRemoteProgramCompileServices->GetInt());
|
|
}
|
|
}
|
|
|
|
//
|
|
// Test whether we should enable workarounds for textures
|
|
// Arm GPUs use an optimization "Arm FrameBuffer Compression - AFBC" that can significanly inflate (~5x) uncompressed texture memory requirements
|
|
// For now AFBC and similar optimizations can be disabled by using VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT or VK_IMAGE_USAGE_STORAGE_BIT flags on a texture
|
|
// On Adreno GPUs ASTC textures with optimial tiling may require 8x more memory
|
|
//
|
|
void FVulkanAndroidPlatform::SetupImageMemoryRequirementWorkaround(const FVulkanDevice& InDevice)
|
|
{
|
|
AFBCWorkaroundOption = 0;
|
|
ASTCWorkaroundOption = 0;
|
|
|
|
VkImageCreateInfo ImageCreateInfo;
|
|
ZeroVulkanStruct(ImageCreateInfo, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);
|
|
ImageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
ImageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
ImageCreateInfo.format = VK_FORMAT_B8G8R8A8_UNORM;
|
|
ImageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
ImageCreateInfo.arrayLayers = 1;
|
|
ImageCreateInfo.extent = {128, 128, 1};
|
|
ImageCreateInfo.mipLevels = 8;
|
|
ImageCreateInfo.flags = 0;
|
|
ImageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
ImageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
ImageCreateInfo.queueFamilyIndexCount = 0;
|
|
ImageCreateInfo.pQueueFamilyIndices = nullptr;
|
|
ImageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
// AFBC workarounds
|
|
{
|
|
const VkFormatFeatureFlags FormatFlags = InDevice.GetFormatProperties(VK_FORMAT_B8G8R8A8_UNORM).optimalTilingFeatures;
|
|
|
|
VkImage Image0;
|
|
VkMemoryRequirements Image0Mem;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkCreateImage(InDevice.GetInstanceHandle(), &ImageCreateInfo, VULKAN_CPU_ALLOCATOR, &Image0));
|
|
VulkanRHI::vkGetImageMemoryRequirements(InDevice.GetInstanceHandle(), Image0, &Image0Mem);
|
|
VulkanRHI::vkDestroyImage(InDevice.GetInstanceHandle(), Image0, VULKAN_CPU_ALLOCATOR);
|
|
|
|
VkImage ImageMutable;
|
|
VkMemoryRequirements ImageMutableMem;
|
|
ImageCreateInfo.flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkCreateImage(InDevice.GetInstanceHandle(), &ImageCreateInfo, VULKAN_CPU_ALLOCATOR, &ImageMutable));
|
|
VulkanRHI::vkGetImageMemoryRequirements(InDevice.GetInstanceHandle(), ImageMutable, &ImageMutableMem);
|
|
VulkanRHI::vkDestroyImage(InDevice.GetInstanceHandle(), ImageMutable, VULKAN_CPU_ALLOCATOR);
|
|
|
|
VkImage ImageStorage;
|
|
VkMemoryRequirements ImageStorageMem;
|
|
if ((FormatFlags & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0)
|
|
{
|
|
ImageCreateInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
|
ImageCreateInfo.flags = 0;
|
|
}
|
|
VERIFYVULKANRESULT(VulkanRHI::vkCreateImage(InDevice.GetInstanceHandle(), &ImageCreateInfo, VULKAN_CPU_ALLOCATOR, &ImageStorage));
|
|
VulkanRHI::vkGetImageMemoryRequirements(InDevice.GetInstanceHandle(), ImageStorage, &ImageStorageMem);
|
|
VulkanRHI::vkDestroyImage(InDevice.GetInstanceHandle(), ImageStorage, VULKAN_CPU_ALLOCATOR);
|
|
|
|
const float MEM_SIZE_THRESHOLD = 1.5f;
|
|
const float IMAGE0_SIZE = (float)Image0Mem.size;
|
|
|
|
if (ImageMutableMem.size * MEM_SIZE_THRESHOLD < IMAGE0_SIZE)
|
|
{
|
|
AFBCWorkaroundOption = 1;
|
|
}
|
|
else if (ImageStorageMem.size * MEM_SIZE_THRESHOLD < IMAGE0_SIZE)
|
|
{
|
|
AFBCWorkaroundOption = 2;
|
|
}
|
|
|
|
if (AFBCWorkaroundOption != 0)
|
|
{
|
|
UE_LOG(LogRHI, Display, TEXT("Enabling workaround to reduce memory requirement for BGRA textures (%s flag). 128x128 - 8 Mips BGRA texture: %u KiB -> %u KiB"),
|
|
AFBCWorkaroundOption == 1 ? TEXT("MUTABLE") : TEXT("STORAGE"),
|
|
Image0Mem.size / 1024,
|
|
AFBCWorkaroundOption == 1 ? ImageMutableMem.size / 1024 : ImageStorageMem.size / 1024
|
|
);
|
|
}
|
|
}
|
|
|
|
// ASTC workarounds
|
|
VkFormatProperties formatProperties{};
|
|
VulkanRHI::vkGetPhysicalDeviceFormatProperties(InDevice.GetPhysicalHandle(), VK_FORMAT_ASTC_8x8_UNORM_BLOCK, &formatProperties);
|
|
if ((formatProperties.linearTilingFeatures & (VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) == (VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT))
|
|
{
|
|
ImageCreateInfo.flags = 0;
|
|
ImageCreateInfo.format = VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
|
|
ImageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
ImageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
|
|
VkImage ImageOptimal_ASTC;
|
|
VkMemoryRequirements ImageOptimalMem_ASTC;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkCreateImage(InDevice.GetInstanceHandle(), &ImageCreateInfo, VULKAN_CPU_ALLOCATOR, &ImageOptimal_ASTC));
|
|
VulkanRHI::vkGetImageMemoryRequirements(InDevice.GetInstanceHandle(), ImageOptimal_ASTC, &ImageOptimalMem_ASTC);
|
|
VulkanRHI::vkDestroyImage(InDevice.GetInstanceHandle(), ImageOptimal_ASTC, VULKAN_CPU_ALLOCATOR);
|
|
|
|
ImageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
|
|
|
|
VkImage ImageLinear_ASTC;
|
|
VkMemoryRequirements ImageLinearMem_ASTC;
|
|
VERIFYVULKANRESULT(VulkanRHI::vkCreateImage(InDevice.GetInstanceHandle(), &ImageCreateInfo, VULKAN_CPU_ALLOCATOR, &ImageLinear_ASTC));
|
|
VulkanRHI::vkGetImageMemoryRequirements(InDevice.GetInstanceHandle(), ImageLinear_ASTC, &ImageLinearMem_ASTC);
|
|
VulkanRHI::vkDestroyImage(InDevice.GetInstanceHandle(), ImageLinear_ASTC, VULKAN_CPU_ALLOCATOR);
|
|
|
|
|
|
const float MEM_SIZE_THRESHOLD = 2.0f;
|
|
const float ImageOptimal_SIZE = (float)ImageOptimalMem_ASTC.size;
|
|
|
|
if (ImageLinearMem_ASTC.size * MEM_SIZE_THRESHOLD <= ImageOptimal_SIZE)
|
|
{
|
|
ASTCWorkaroundOption = 1;
|
|
|
|
UE_LOG(LogRHI, Display, TEXT("Enabling workaround to reduce memory requirement for ASTC textures (VK_IMAGE_TILING_LINEAR). 128x128 - 8 Mips ASTC_8x8 texture: %u KiB -> %u KiB"),
|
|
ImageOptimalMem_ASTC.size / 1024,
|
|
ImageLinearMem_ASTC.size / 1024
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::SetImageMemoryRequirementWorkaround(VkImageCreateInfo& ImageCreateInfo)
|
|
{
|
|
if (AFBCWorkaroundOption != 0 &&
|
|
ImageCreateInfo.imageType == VK_IMAGE_TYPE_2D &&
|
|
ImageCreateInfo.format == VK_FORMAT_B8G8R8A8_UNORM &&
|
|
ImageCreateInfo.mipLevels >= 8) // its worth enabling for 128x128 and up
|
|
{
|
|
if (AFBCWorkaroundOption == 1)
|
|
{
|
|
ImageCreateInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
}
|
|
else if (AFBCWorkaroundOption == 2)
|
|
{
|
|
ImageCreateInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
|
|
}
|
|
}
|
|
|
|
// Use ASTC workaround for textures ASTC_6x6 and ASTC_8x8 with mips and size up to 128x128
|
|
if (ASTCWorkaroundOption != 0 &&
|
|
ImageCreateInfo.imageType == VK_IMAGE_TYPE_2D &&
|
|
(ImageCreateInfo.format >= VK_FORMAT_ASTC_6x6_UNORM_BLOCK && ImageCreateInfo.format <= VK_FORMAT_ASTC_8x8_SRGB_BLOCK) &&
|
|
(ImageCreateInfo.mipLevels > 1 && ImageCreateInfo.extent.width <= 128 && ImageCreateInfo.extent.height <= 128))
|
|
{
|
|
ImageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
|
|
}
|
|
}
|
|
|
|
FString FVulkanAndroidPlatform::GetVulkanProfileNameForFeatureLevel(ERHIFeatureLevel::Type FeatureLevel, bool bRaytracing)
|
|
{
|
|
// Use the generic name and add "_Android" at the end (the RT suffix get added after the platform)
|
|
FString ProfileName = FVulkanGenericPlatform::GetVulkanProfileNameForFeatureLevel(FeatureLevel, false) + TEXT("_Android");
|
|
if (bRaytracing)
|
|
{
|
|
ProfileName += TEXT("_RT");
|
|
}
|
|
return ProfileName;
|
|
}
|
|
|
|
void FVulkanAndroidPlatform::WriteCrashMarker(const FOptionalVulkanDeviceExtensions& OptionalExtensions, FVulkanCommandBuffer* CmdBuffer, VkBuffer DestBuffer, const TArrayView<uint32>& Entries, bool bAdding)
|
|
{
|
|
ensure(Entries.Num() <= GMaxCrashBufferEntries);
|
|
|
|
WriteCrashMarkerWithoutExtensions(CmdBuffer, DestBuffer, Entries, bAdding);
|
|
}
|