331 lines
13 KiB
C++
331 lines
13 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "RHI.h"
|
|
#include "Android/AndroidWindow.h"
|
|
|
|
#define VK_USE_PLATFORM_ANDROID_KHR 1
|
|
|
|
#define VULKAN_ENABLE_DUMP_LAYER 0
|
|
#define VULKAN_DYNAMICALLYLOADED 1
|
|
#define VULKAN_SHOULD_ENABLE_DRAW_MARKERS (UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG)
|
|
#define VULKAN_USE_IMAGE_ACQUIRE_FENCES 0
|
|
#define VULKAN_USE_CREATE_ANDROID_SURFACE 1
|
|
#define VULKAN_SHOULD_USE_LLM (UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT) // If enabled Vulkan will report detailed allocation statistics, overrides some tags with custom ones
|
|
#define VULKAN_SHOULD_USE_COMMANDWRAPPERS VULKAN_SHOULD_USE_LLM //LLM on Vulkan needs command wrappers to account for vkallocs
|
|
#define VULKAN_ENABLE_LRU_CACHE 1
|
|
#define VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING 1
|
|
#define VULKAN_PURGE_SHADER_MODULES 0
|
|
#define VULKAN_SUPPORTS_DEDICATED_ALLOCATION 0
|
|
#define VULKAN_SUPPORTS_ASTC_DECODE_MODE 1
|
|
#define VULKAN_SUPPORTS_NV_DIAGNOSTIC_CHECKPOINT 0
|
|
#define VULKAN_SUPPORTS_SCALAR_BLOCK_LAYOUT 1
|
|
#define VULKAN_SUPPORTS_TRANSIENT_RESOURCE_ALLOCATOR 0
|
|
#define VULKAN_SUPPORTS_DRIVER_PROPERTIES 0
|
|
#define VULKAN_SUPPORTS_DESCRIPTOR_INDEXING 1
|
|
#define VULKAN_SUPPORTS_GPU_CRASH_DUMPS 1
|
|
#define VULKAN_SUPPORTS_RAY_TRACING_POSITION_FETCH 0
|
|
|
|
#define UE_VK_API_VERSION VK_API_VERSION_1_1
|
|
|
|
#define ENUM_VK_ENTRYPOINTS_PLATFORM_BASE(EnumMacro)
|
|
|
|
#define ENUM_VK_ENTRYPOINTS_PLATFORM_INSTANCE(EnumMacro) \
|
|
EnumMacro(PFN_vkCreateAndroidSurfaceKHR, vkCreateAndroidSurfaceKHR) \
|
|
EnumMacro(PFN_vkGetAndroidHardwareBufferPropertiesANDROID, vkGetAndroidHardwareBufferPropertiesANDROID)
|
|
|
|
#define ENUM_VK_ENTRYPOINTS_OPTIONAL_PLATFORM_INSTANCE(EnumMacro) \
|
|
EnumMacro(PFN_vkGetRefreshCycleDurationGOOGLE, vkGetRefreshCycleDurationGOOGLE) \
|
|
EnumMacro(PFN_vkGetPastPresentationTimingGOOGLE, vkGetPastPresentationTimingGOOGLE)
|
|
|
|
// and now, include the GenericPlatform class
|
|
#include "VulkanGenericPlatform.h"
|
|
|
|
extern bool IsInAndroidEventThread();
|
|
|
|
class FVulkanAndroidPlatformWindowContext
|
|
{
|
|
public:
|
|
FVulkanAndroidPlatformWindowContext(void* InWindowHandle)
|
|
{
|
|
AndroidWindow = (FAndroidWindow*)InWindowHandle;
|
|
// in this case FVulkanAndroidPlatformWindowContext owns the FNativeAccessor.
|
|
if (InWindowHandle)
|
|
{
|
|
if(FAndroidMisc::UseNewWindowBehavior())
|
|
{
|
|
// the context should be locked at the beginning of the update process, this overload is used for GT initiated events.
|
|
check(IsInGameThread());
|
|
WindowContainer = AndroidWindow->GetANativeAccessor(false);
|
|
NativeWindow = WindowContainer.IsSet() ? WindowContainer->GetANativeWindow() : nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
FVulkanAndroidPlatformWindowContext(TOptional<FAndroidWindow::FNativeAccessor> WindowContainerIn)
|
|
{
|
|
// in this case the FNativeAccessor has come from the event thread
|
|
if(WindowContainerIn.IsSet())
|
|
{
|
|
check(FAndroidMisc::UseNewWindowBehavior());
|
|
// the context should be locked at the beginning of the update process, this overload is used for ET initiated events.
|
|
check(IsInAndroidEventThread());
|
|
WindowContainer = MoveTemp(WindowContainerIn);
|
|
NativeWindow = WindowContainer->GetANativeWindow();
|
|
AndroidWindow = WindowContainer.GetValue().Get();
|
|
}
|
|
}
|
|
|
|
// we dont have a locked window to create swapchains during present, android will use the
|
|
// SetRHIOnReleaseWindowCallback / SetRHIOnReInitWindowCallback to create as required.
|
|
static bool CanCreateSwapchainOnDemand() { return false; }
|
|
bool IsValid() const
|
|
{
|
|
return FAndroidMisc::UseNewWindowBehavior() ? GetANativeWindow() != nullptr : true;
|
|
}
|
|
|
|
ANativeWindow* GetANativeWindow() const
|
|
{
|
|
return NativeWindow;
|
|
}
|
|
|
|
void* GetWindowHandle() const
|
|
{
|
|
return AndroidWindow;
|
|
}
|
|
|
|
private:
|
|
ANativeWindow* NativeWindow = nullptr;
|
|
FAndroidWindow* AndroidWindow = nullptr;
|
|
TOptional<FAndroidWindow::FNativeAccessor> WindowContainer;
|
|
};
|
|
|
|
using FVulkanPlatformWindowContext = FVulkanAndroidPlatformWindowContext;
|
|
|
|
class FVulkanAndroidPlatform : public FVulkanGenericPlatform
|
|
{
|
|
public:
|
|
static bool LoadVulkanLibrary();
|
|
static bool LoadVulkanInstanceFunctions(VkInstance inInstance);
|
|
static void FreeVulkanLibrary();
|
|
|
|
static void InitDevice(FVulkanDevice* InDevice);
|
|
static void PostInitGPU(const FVulkanDevice& InDevice);
|
|
|
|
static void GetInstanceExtensions(FVulkanInstanceExtensionArray& OutExtensions);
|
|
static void GetInstanceLayers(TArray<const ANSICHAR*>& OutLayers);
|
|
static void GetDeviceExtensions(FVulkanDevice* Device, FVulkanDeviceExtensionArray& OutExtensions);
|
|
static void GetDeviceLayers(TArray<const ANSICHAR*>& OutLayers);
|
|
static void NotifyFoundDeviceLayersAndExtensions(VkPhysicalDevice PhysicalDevice, const TArray<const ANSICHAR*>& Layers, const TArray<const ANSICHAR*>& Extensions);
|
|
|
|
static void CreateSurface(FVulkanPlatformWindowContext& WindowContext, VkInstance Instance, VkSurfaceKHR* OutSurface);
|
|
|
|
static void* GetHardwareWindowHandle();
|
|
|
|
static bool SupportsBCTextureFormats();
|
|
static bool SupportsASTCTextureFormats() { return true; }
|
|
static bool SupportsETC2TextureFormats() { return true; }
|
|
// GLES does not support R16Unorm, so all Android has to fallback to R16F instead
|
|
static bool SupportsR16UnormTextureFormat() { return false; }
|
|
|
|
static bool SupportsQuerySurfaceProperties() { return false; }
|
|
|
|
static void SetupFeatureLevels(TArrayView<EShaderPlatform> ShaderPlatformForFeatureLevel)
|
|
{
|
|
if (RequiresMobileRenderer())
|
|
{
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES2_REMOVED] = SP_NumPlatforms;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1] = SP_VULKAN_ES3_1_ANDROID;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM4_REMOVED] = SP_NumPlatforms;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM5] = SP_NumPlatforms;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM6] = SP_NumPlatforms;
|
|
}
|
|
else
|
|
{
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES2_REMOVED] = SP_NumPlatforms;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1] = SP_VULKAN_ES3_1_ANDROID;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM4_REMOVED] = SP_VULKAN_SM5_ANDROID;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM5] = SP_VULKAN_SM5_ANDROID;
|
|
ShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM6] = SP_NumPlatforms;
|
|
}
|
|
}
|
|
|
|
static bool SupportsTimestampRenderQueries();
|
|
|
|
static bool SupportsDynamicResolution();
|
|
|
|
static bool RequiresMobileRenderer()
|
|
{
|
|
#if USE_STATIC_FEATURE_LEVEL_ENUMS
|
|
return (UE_ANDROID_STATIC_FEATURE_LEVEL == ERHIFeatureLevel::ES3_1);
|
|
#else
|
|
return !FAndroidMisc::ShouldUseDesktopVulkan();
|
|
#endif
|
|
}
|
|
|
|
static ERHIFeatureLevel::Type GetFeatureLevel(ERHIFeatureLevel::Type RequestedFeatureLevel)
|
|
{
|
|
#if USE_STATIC_FEATURE_LEVEL_ENUMS
|
|
return UE_ANDROID_STATIC_FEATURE_LEVEL;
|
|
#else
|
|
return FVulkanGenericPlatform::GetFeatureLevel(RequestedFeatureLevel);
|
|
#endif
|
|
}
|
|
|
|
static bool HasCustomFrameTiming();
|
|
|
|
static bool SupportsVolumeTextureRendering() { return false; }
|
|
|
|
static void OverridePlatformHandlers(bool bInit);
|
|
|
|
//#todo-rco: Detect Mali?
|
|
static bool RequiresPresentLayoutFix() { return true; }
|
|
|
|
#if PLATFORM_ANDROID_X64
|
|
static bool HasUnifiedMemory();
|
|
#else
|
|
static bool HasUnifiedMemory() { return true; }
|
|
#endif
|
|
|
|
static bool RegisterGPUWork() { return false; }
|
|
|
|
// Assume most devices can't use the extra cores for running parallel tasks
|
|
static bool SupportParallelRenderingTasks() { return false; }
|
|
|
|
//#todo-rco: Detect Mali? Doing a clear on ColorAtt layout on empty cmd buffer causes issues
|
|
static bool RequiresSwapchainGeneralInitialLayout() { return true; }
|
|
|
|
static bool RequiresWaitingForFrameCompletionEvent() { return false; }
|
|
|
|
// Does the platform allow a nullptr Pixelshader on the pipeline
|
|
static bool SupportsNullPixelShader() { return false; }
|
|
|
|
// Does the platform require depth to be written on stencil clear
|
|
static bool RequiresDepthStencilFullWrite() { return bRequiresDepthStencilFullWrite; }
|
|
static void SetupRequiresDepthStencilFullWriteWorkaround(const FVulkanDevice& Device);
|
|
|
|
static bool FramePace(FVulkanDevice& Device, void* WindowHandle, VkSwapchainKHR Swapchain, uint32 PresentID, VkPresentInfoKHR& Info);
|
|
|
|
static VkResult Present(VkQueue Queue, VkPresentInfoKHR& PresentInfo);
|
|
|
|
static VkResult CreateSwapchainKHR(FVulkanPlatformWindowContext& WindowContext, VkPhysicalDevice PhysicalDevice, VkDevice Device, const VkSwapchainCreateInfoKHR* CreateInfo, const VkAllocationCallbacks* Allocator, VkSwapchainKHR* Swapchain);
|
|
|
|
static void DestroySwapchainKHR(VkDevice Device, VkSwapchainKHR Swapchain, const VkAllocationCallbacks* Allocator);
|
|
|
|
// handle precompile of PSOs, send to an android specific precompile external process.
|
|
static VkPipelineCache 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 = nullptr);
|
|
|
|
static bool AreRemoteCompileServicesActive();
|
|
static bool StartRemoteCompileServices(int NumServices);
|
|
static void StopRemoteCompileServices();
|
|
|
|
// Do not attempt to immediately recreate swapchain
|
|
static bool RecreateSwapchainOnFail() { return false; }
|
|
|
|
// original window behavior swapchain functions.
|
|
// ignored with the new window method.
|
|
static void RecreateSwapChain(void* NewNativeWindow);
|
|
static void DestroySwapChain();
|
|
|
|
static VkFormat GetPlatform5551FormatWithFallback(VkFormat& OutFallbackFormat0, VkFormat& OutFallbackFormat1)
|
|
{
|
|
OutFallbackFormat0 = VK_FORMAT_A1R5G5B5_UNORM_PACK16;
|
|
OutFallbackFormat1 = VK_FORMAT_B8G8R8A8_UNORM;
|
|
return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
|
|
};
|
|
|
|
// Setup platform to use a workaround to reduce textures memory requirements
|
|
static void SetupImageMemoryRequirementWorkaround(const FVulkanDevice& InDevice);
|
|
static void SetImageMemoryRequirementWorkaround(VkImageCreateInfo& ImageCreateInfo);
|
|
|
|
// Returns the profile name to look up for a given feature level on a platform
|
|
static FString GetVulkanProfileNameForFeatureLevel(ERHIFeatureLevel::Type FeatureLevel, bool bRaytracing);
|
|
|
|
static VkShaderStageFlags RequiredWaveOpsShaderStageFlags(VkShaderStageFlags VulkanDeviceShaderStageFlags)
|
|
{
|
|
// Many Android Vulkan implementations do not support wave ops in vertex and geometry shaders and we don't need them there.
|
|
return VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT;
|
|
}
|
|
|
|
static void WriteCrashMarker(const FOptionalVulkanDeviceExtensions& OptionalExtensions, FVulkanCommandBuffer* CmdBuffer, VkBuffer DestBuffer, const TArrayView<uint32>& Entries, bool bAdding);
|
|
|
|
static VkTimeDomainKHR GetTimeDomain() { return VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR; }
|
|
|
|
#if USE_ANDROID_VULKAN_SWAPPY
|
|
static bool bSwappyEnabledAtRHIInit;
|
|
#endif
|
|
|
|
protected:
|
|
static void* VulkanLib;
|
|
static bool bAttemptedLoad;
|
|
|
|
#if VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
static bool bHasGoogleDisplayTiming;
|
|
static TUniquePtr<class FGDTimingFramePacer> GDTimingFramePacer;
|
|
#endif
|
|
|
|
static TUniquePtr<struct FAndroidVulkanFramePacer> FramePacer;
|
|
static int32 CachedFramePace;
|
|
static int32 CachedRefreshRate;
|
|
static int32 CachedSyncInterval;
|
|
static int32 SuccessfulRefreshRateFrames;
|
|
static int32 UnsuccessfulRefreshRateFrames;
|
|
static TArray<TArray<ANSICHAR>> DebugVulkanDeviceLayers;
|
|
static TArray<TArray<ANSICHAR>> DebugVulkanInstanceLayers;
|
|
static TArray<TArray<ANSICHAR>> SwappyRequiredExtensions;
|
|
|
|
static int32 AFBCWorkaroundOption;
|
|
static int32 ASTCWorkaroundOption;
|
|
|
|
static bool bRequiresDepthStencilFullWrite;
|
|
};
|
|
|
|
#if VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
class FGDTimingFramePacer : FNoncopyable
|
|
{
|
|
public:
|
|
FGDTimingFramePacer(VkDevice InDevice, VkSwapchainKHR InSwapChain);
|
|
|
|
const VkPresentTimesInfoGOOGLE* GetPresentTimesInfo() const
|
|
{
|
|
return ((SyncDuration > 0) ? &PresentTimesInfo : nullptr);
|
|
}
|
|
|
|
void ScheduleNextFrame(uint32 InPresentID, int32 FramePace, int32 RefreshRate); // Call right before present
|
|
|
|
private:
|
|
void UpdateSyncDuration(int32 FramePace, int32 RefreshRate);
|
|
|
|
uint64 PredictLastScheduledFramePresentTime(uint32 CurrentPresentID) const;
|
|
uint64 CalculateMinPresentTime(uint64 CpuPresentTime) const;
|
|
uint64 CalculateMaxPresentTime(uint64 CpuPresentTime) const;
|
|
uint64 CalculateNearestVsTime(uint64 ActualPresentTime, uint64 TargetTime) const;
|
|
void PollPastFrameInfo();
|
|
|
|
private:
|
|
struct FKnownFrameInfo
|
|
{
|
|
bool bValid = false;
|
|
uint32 PresentID = 0;
|
|
uint64 ActualPresentTime = 0;
|
|
};
|
|
|
|
private:
|
|
VkDevice Device;
|
|
VkSwapchainKHR SwapChain;
|
|
|
|
VkPresentTimesInfoGOOGLE PresentTimesInfo;
|
|
VkPresentTimeGOOGLE PresentTime;
|
|
uint64 RefreshDuration = 0;
|
|
uint64 HalfRefreshDuration = 0;
|
|
|
|
FKnownFrameInfo LastKnownFrameInfo;
|
|
uint64 LastScheduledPresentTime = 0;
|
|
uint64 SyncDuration = 0;
|
|
int32 FramePace = 0;
|
|
};
|
|
#endif //VULKAN_SUPPORTS_GOOGLE_DISPLAY_TIMING
|
|
|
|
typedef FVulkanAndroidPlatform FVulkanPlatform;
|