diff --git a/parallel-rdp-standalone/CMakeLists.txt b/parallel-rdp-standalone/CMakeLists.txt index 9004123..96f3b7c 100644 --- a/parallel-rdp-standalone/CMakeLists.txt +++ b/parallel-rdp-standalone/CMakeLists.txt @@ -5,7 +5,7 @@ project(parallel-rdp LANGUAGES CXX C) set(NAME_PLUGIN_SIMPLE64 "simple64-video-parallel") include_directories(../mupen64plus-core/src/api) -add_definitions(-DM64P_PLUGIN_API -DGRANITE_VULKAN_MT) +add_definitions(-DM64P_PLUGIN_API) include(CheckIPOSupported) check_ipo_supported(RESULT ENABLE_IPO) diff --git a/parallel-rdp-standalone/COMMIT b/parallel-rdp-standalone/COMMIT index d399f5b..7b6caef 100644 --- a/parallel-rdp-standalone/COMMIT +++ b/parallel-rdp-standalone/COMMIT @@ -1 +1 @@ -65f4f4da2ec11cf3485318dbb4ccd41d35d72a36 +7d75aa31e42da166a00604fdfcc29fa26bd3543c diff --git a/parallel-rdp-standalone/config.mk b/parallel-rdp-standalone/config.mk index 6c68e29..5ae8749 100644 --- a/parallel-rdp-standalone/config.mk +++ b/parallel-rdp-standalone/config.mk @@ -1,7 +1,7 @@ # For use in standalone implementations. PARALLEL_RDP_CFLAGS := -PARALLEL_RDP_CXXFLAGS := -DGRANITE_VULKAN_MT +PARALLEL_RDP_CXXFLAGS := PARALLEL_RDP_SOURCES_CXX := \ $(wildcard $(PARALLEL_RDP_IMPLEMENTATION)/parallel-rdp/*.cpp) \ diff --git a/parallel-rdp-standalone/parallel-rdp/rdp_device.hpp b/parallel-rdp-standalone/parallel-rdp/rdp_device.hpp index 2ad7640..5be857a 100644 --- a/parallel-rdp-standalone/parallel-rdp/rdp_device.hpp +++ b/parallel-rdp-standalone/parallel-rdp/rdp_device.hpp @@ -33,10 +33,6 @@ #include "worker_thread.hpp" #include "rdp_dump_write.hpp" -#ifndef GRANITE_VULKAN_MT -#error "Granite Vulkan backend must be built with multithreading support." -#endif - namespace RDP { struct RGBA diff --git a/parallel-rdp-standalone/parallel-rdp/rdp_renderer.cpp b/parallel-rdp-standalone/parallel-rdp/rdp_renderer.cpp index fc4651c..bc3c7b0 100644 --- a/parallel-rdp-standalone/parallel-rdp/rdp_renderer.cpp +++ b/parallel-rdp-standalone/parallel-rdp/rdp_renderer.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "rdp_renderer.hpp" #include "rdp_device.hpp" #include "logging.hpp" @@ -27,6 +28,7 @@ #include "luts.hpp" #include "timer.hpp" #include +#include #ifdef PARALLEL_RDP_SHADER_DIR #include "global_managers.hpp" #include "os_filesystem.hpp" @@ -156,9 +158,9 @@ bool Renderer::init_caps() bool allow_small_types = true; bool forces_small_types = false; - if (const char *small = getenv("PARALLEL_RDP_SMALL_TYPES")) + if (const char *small_type = getenv("PARALLEL_RDP_SMALL_TYPES")) { - allow_small_types = strtol(small, nullptr, 0) > 0; + allow_small_types = strtol(small_type, nullptr, 0) > 0; forces_small_types = true; LOGI("Allow small types = %d.\n", int(allow_small_types)); } diff --git a/parallel-rdp-standalone/parallel-rdp/video_interface.cpp b/parallel-rdp-standalone/parallel-rdp/video_interface.cpp index 6d5e061..c46be7b 100644 --- a/parallel-rdp-standalone/parallel-rdp/video_interface.cpp +++ b/parallel-rdp-standalone/parallel-rdp/video_interface.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "video_interface.hpp" #include "rdp_renderer.hpp" #include "luts.hpp" diff --git a/parallel-rdp-standalone/util/logging.cpp b/parallel-rdp-standalone/util/logging.cpp index f4a584e..5412478 100644 --- a/parallel-rdp-standalone/util/logging.cpp +++ b/parallel-rdp-standalone/util/logging.cpp @@ -22,6 +22,11 @@ #include "logging.hpp" +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + namespace Util { static thread_local LoggingInterface *logging_iface; @@ -42,4 +47,26 @@ void set_thread_logging_interface(LoggingInterface *iface) { logging_iface = iface; } + +#ifdef _WIN32 +void debug_output_log(const char *tag, const char *fmt, ...) +{ + if (!IsDebuggerPresent()) + return; + + va_list va; + va_start(va, fmt); + auto len = vsnprintf(nullptr, 0, fmt, va); + if (len > 0) + { + size_t tag_len = strlen(tag); + char *buf = new char[len + tag_len + 1]; + memcpy(buf, tag, tag_len); + vsnprintf(buf + tag_len, len + 1, fmt, va); + OutputDebugStringA(buf); + delete[] buf; + } + va_end(va); +} +#endif } \ No newline at end of file diff --git a/parallel-rdp-standalone/util/logging.hpp b/parallel-rdp-standalone/util/logging.hpp index 5715e2f..7069d70 100644 --- a/parallel-rdp-standalone/util/logging.hpp +++ b/parallel-rdp-standalone/util/logging.hpp @@ -39,31 +39,28 @@ bool interface_log(const char *tag, const char *fmt, ...); void set_thread_logging_interface(LoggingInterface *iface); } -#if defined(_MSC_VER) -#define WIN32_LEAN_AND_MEAN -#include +#if defined(_WIN32) +namespace Util +{ +void debug_output_log(const char *tag, const char *fmt, ...); +} + #define LOGE_FALLBACK(...) do { \ - fprintf(stderr, "[ERROR]: " __VA_ARGS__); \ - fflush(stderr); \ - char buffer[16 * 1024]; \ - snprintf(buffer, sizeof(buffer), "[ERROR]: " __VA_ARGS__); \ - OutputDebugStringA(buffer); \ + fprintf(stderr, "[ERROR]: " __VA_ARGS__); \ + fflush(stderr); \ + ::Util::debug_output_log("[ERROR]: ", __VA_ARGS__); \ } while(false) #define LOGW_FALLBACK(...) do { \ - fprintf(stderr, "[WARN]: " __VA_ARGS__); \ - fflush(stderr); \ - char buffer[16 * 1024]; \ - snprintf(buffer, sizeof(buffer), "[WARN]: " __VA_ARGS__); \ - OutputDebugStringA(buffer); \ + fprintf(stderr, "[WARN]: " __VA_ARGS__); \ + fflush(stderr); \ + ::Util::debug_output_log("[WARN]: ", __VA_ARGS__); \ } while(false) #define LOGI_FALLBACK(...) do { \ - fprintf(stderr, "[INFO]: " __VA_ARGS__); \ - fflush(stderr); \ - char buffer[16 * 1024]; \ - snprintf(buffer, sizeof(buffer), "[INFO]: " __VA_ARGS__); \ - OutputDebugStringA(buffer); \ + fprintf(stderr, "[INFO]: " __VA_ARGS__); \ + fflush(stderr); \ + ::Util::debug_output_log("[INFO]: ", __VA_ARGS__); \ } while(false) #elif defined(ANDROID) #include diff --git a/parallel-rdp-standalone/util/object_pool.hpp b/parallel-rdp-standalone/util/object_pool.hpp index c7bb75d..2dcc547 100644 --- a/parallel-rdp-standalone/util/object_pool.hpp +++ b/parallel-rdp-standalone/util/object_pool.hpp @@ -44,7 +44,7 @@ public: if (vacants.empty()) { unsigned num_objects = 64u << memory.size(); - T *ptr = static_cast(memalign_alloc(std::max(size_t(64), alignof(T)), + T *ptr = static_cast(memalign_alloc(std::max(64, alignof(T)), num_objects * sizeof(T))); if (!ptr) return nullptr; diff --git a/parallel-rdp-standalone/util/read_write_lock.hpp b/parallel-rdp-standalone/util/read_write_lock.hpp index 6c365cc..f63dff7 100644 --- a/parallel-rdp-standalone/util/read_write_lock.hpp +++ b/parallel-rdp-standalone/util/read_write_lock.hpp @@ -51,6 +51,18 @@ public: } } + inline bool try_lock_read() + { + unsigned v = counter.fetch_add(Reader, std::memory_order_acquire); + if ((v & Writer) != 0) + { + unlock_read(); + return false; + } + + return true; + } + inline void unlock_read() { counter.fetch_sub(Reader, std::memory_order_release); @@ -70,6 +82,14 @@ public: } } + inline bool try_lock_write() + { + uint32_t expected = 0; + return counter.compare_exchange_strong(expected, Writer, + std::memory_order_acquire, + std::memory_order_relaxed); + } + inline void unlock_write() { counter.fetch_and(~Writer, std::memory_order_release); @@ -90,4 +110,40 @@ public: private: std::atomic_uint32_t counter; }; + +class RWSpinLockReadHolder +{ +public: + explicit RWSpinLockReadHolder(RWSpinLock &lock_) + : lock(lock_) + { + lock.lock_read(); + } + + ~RWSpinLockReadHolder() + { + lock.unlock_read(); + } + +private: + RWSpinLock &lock; +}; + +class RWSpinLockWriteHolder +{ +public: + explicit RWSpinLockWriteHolder(RWSpinLock &lock_) + : lock(lock_) + { + lock.lock_write(); + } + + ~RWSpinLockWriteHolder() + { + lock.unlock_write(); + } + +private: + RWSpinLock &lock; +}; } diff --git a/parallel-rdp-standalone/util/thread_name.cpp b/parallel-rdp-standalone/util/thread_name.cpp index 214cdac..726d44f 100644 --- a/parallel-rdp-standalone/util/thread_name.cpp +++ b/parallel-rdp-standalone/util/thread_name.cpp @@ -1,3 +1,25 @@ +/* Copyright (c) 2017-2022 Hans-Kristian Arntzen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + #include "thread_name.hpp" #ifdef __linux__ diff --git a/parallel-rdp-standalone/util/timeline_trace_file.cpp b/parallel-rdp-standalone/util/timeline_trace_file.cpp index 3844bb7..692a1f7 100644 --- a/parallel-rdp-standalone/util/timeline_trace_file.cpp +++ b/parallel-rdp-standalone/util/timeline_trace_file.cpp @@ -149,4 +149,17 @@ TimelineTraceFile::~TimelineTraceFile() if (thr.joinable()) thr.join(); } + +TimelineTraceFile::ScopedEvent::ScopedEvent(TimelineTraceFile *file_, const char *tag) + : file(file_) +{ + if (file && tag && *tag != '\0') + event = file->begin_event(tag); +} + +TimelineTraceFile::ScopedEvent::~ScopedEvent() +{ + if (event) + file->end_event(event); +} } diff --git a/parallel-rdp-standalone/util/timeline_trace_file.hpp b/parallel-rdp-standalone/util/timeline_trace_file.hpp index 8fe6dc9..bd1ced7 100644 --- a/parallel-rdp-standalone/util/timeline_trace_file.hpp +++ b/parallel-rdp-standalone/util/timeline_trace_file.hpp @@ -58,6 +58,16 @@ public: Event *allocate_event(); void submit_event(Event *e); + struct ScopedEvent + { + ScopedEvent(TimelineTraceFile *file, const char *tag); + ~ScopedEvent(); + void operator=(const ScopedEvent &) = delete; + ScopedEvent(const ScopedEvent &) = delete; + TimelineTraceFile *file = nullptr; + Event *event = nullptr; + }; + private: void looper(std::string path); std::thread thr; @@ -67,4 +77,14 @@ private: ThreadSafeObjectPool event_pool; std::queue queued_events; }; + +#ifndef GRANITE_SHIPPING +#define GRANITE_SCOPED_TIMELINE_EVENT(str) \ + ::Util::TimelineTraceFile::ScopedEvent _timeline_scoped_count_##__COUNTER__{GRANITE_THREAD_GROUP()->get_timeline_trace_file(), str} +#define GRANITE_SCOPED_TIMELINE_EVENT_FILE(file, str) \ + ::Util::TimelineTraceFile::ScopedEvent _timeline_scoped_count_##__COUNTER__{file, str} +#else +#define GRANITE_SCOPED_TIMELINE_EVENT(...) ((void)0) +#define GRANITE_SCOPED_TIMELINE_EVENT_FILE(...) ((void)0) +#endif } diff --git a/parallel-rdp-standalone/volk/volk.c b/parallel-rdp-standalone/volk/volk.c index db3baab..d72e38c 100644 --- a/parallel-rdp-standalone/volk/volk.c +++ b/parallel-rdp-standalone/volk/volk.c @@ -734,6 +734,9 @@ static void volkGenLoadDevice(void* context, PFN_vkVoidFunction (*load)(void*, c vkGetShaderModuleCreateInfoIdentifierEXT = (PFN_vkGetShaderModuleCreateInfoIdentifierEXT)load(context, "vkGetShaderModuleCreateInfoIdentifierEXT"); vkGetShaderModuleIdentifierEXT = (PFN_vkGetShaderModuleIdentifierEXT)load(context, "vkGetShaderModuleIdentifierEXT"); #endif /* defined(VK_EXT_shader_module_identifier) */ +#if defined(VK_EXT_swapchain_maintenance1) + vkReleaseSwapchainImagesEXT = (PFN_vkReleaseSwapchainImagesEXT)load(context, "vkReleaseSwapchainImagesEXT"); +#endif /* defined(VK_EXT_swapchain_maintenance1) */ #if defined(VK_EXT_transform_feedback) vkCmdBeginQueryIndexedEXT = (PFN_vkCmdBeginQueryIndexedEXT)load(context, "vkCmdBeginQueryIndexedEXT"); vkCmdBeginTransformFeedbackEXT = (PFN_vkCmdBeginTransformFeedbackEXT)load(context, "vkCmdBeginTransformFeedbackEXT"); @@ -1461,6 +1464,9 @@ static void volkGenLoadDeviceTable(struct VolkDeviceTable* table, void* context, table->vkGetShaderModuleCreateInfoIdentifierEXT = (PFN_vkGetShaderModuleCreateInfoIdentifierEXT)load(context, "vkGetShaderModuleCreateInfoIdentifierEXT"); table->vkGetShaderModuleIdentifierEXT = (PFN_vkGetShaderModuleIdentifierEXT)load(context, "vkGetShaderModuleIdentifierEXT"); #endif /* defined(VK_EXT_shader_module_identifier) */ +#if defined(VK_EXT_swapchain_maintenance1) + table->vkReleaseSwapchainImagesEXT = (PFN_vkReleaseSwapchainImagesEXT)load(context, "vkReleaseSwapchainImagesEXT"); +#endif /* defined(VK_EXT_swapchain_maintenance1) */ #if defined(VK_EXT_transform_feedback) table->vkCmdBeginQueryIndexedEXT = (PFN_vkCmdBeginQueryIndexedEXT)load(context, "vkCmdBeginQueryIndexedEXT"); table->vkCmdBeginTransformFeedbackEXT = (PFN_vkCmdBeginTransformFeedbackEXT)load(context, "vkCmdBeginTransformFeedbackEXT"); @@ -2269,6 +2275,9 @@ PFN_vkGetPhysicalDeviceMultisamplePropertiesEXT vkGetPhysicalDeviceMultisamplePr PFN_vkGetShaderModuleCreateInfoIdentifierEXT vkGetShaderModuleCreateInfoIdentifierEXT; PFN_vkGetShaderModuleIdentifierEXT vkGetShaderModuleIdentifierEXT; #endif /* defined(VK_EXT_shader_module_identifier) */ +#if defined(VK_EXT_swapchain_maintenance1) +PFN_vkReleaseSwapchainImagesEXT vkReleaseSwapchainImagesEXT; +#endif /* defined(VK_EXT_swapchain_maintenance1) */ #if defined(VK_EXT_tooling_info) PFN_vkGetPhysicalDeviceToolPropertiesEXT vkGetPhysicalDeviceToolPropertiesEXT; #endif /* defined(VK_EXT_tooling_info) */ diff --git a/parallel-rdp-standalone/volk/volk.h b/parallel-rdp-standalone/volk/volk.h index eafbec0..c1feea5 100644 --- a/parallel-rdp-standalone/volk/volk.h +++ b/parallel-rdp-standalone/volk/volk.h @@ -15,7 +15,7 @@ #endif /* VOLK_GENERATE_VERSION_DEFINE */ -#define VOLK_HEADER_VERSION 235 +#define VOLK_HEADER_VERSION 237 /* VOLK_GENERATE_VERSION_DEFINE */ #ifndef VK_NO_PROTOTYPES @@ -510,6 +510,9 @@ struct VolkDeviceTable PFN_vkGetShaderModuleCreateInfoIdentifierEXT vkGetShaderModuleCreateInfoIdentifierEXT; PFN_vkGetShaderModuleIdentifierEXT vkGetShaderModuleIdentifierEXT; #endif /* defined(VK_EXT_shader_module_identifier) */ +#if defined(VK_EXT_swapchain_maintenance1) + PFN_vkReleaseSwapchainImagesEXT vkReleaseSwapchainImagesEXT; +#endif /* defined(VK_EXT_swapchain_maintenance1) */ #if defined(VK_EXT_transform_feedback) PFN_vkCmdBeginQueryIndexedEXT vkCmdBeginQueryIndexedEXT; PFN_vkCmdBeginTransformFeedbackEXT vkCmdBeginTransformFeedbackEXT; @@ -1310,6 +1313,9 @@ extern PFN_vkGetPhysicalDeviceMultisamplePropertiesEXT vkGetPhysicalDeviceMultis extern PFN_vkGetShaderModuleCreateInfoIdentifierEXT vkGetShaderModuleCreateInfoIdentifierEXT; extern PFN_vkGetShaderModuleIdentifierEXT vkGetShaderModuleIdentifierEXT; #endif /* defined(VK_EXT_shader_module_identifier) */ +#if defined(VK_EXT_swapchain_maintenance1) +extern PFN_vkReleaseSwapchainImagesEXT vkReleaseSwapchainImagesEXT; +#endif /* defined(VK_EXT_swapchain_maintenance1) */ #if defined(VK_EXT_tooling_info) extern PFN_vkGetPhysicalDeviceToolPropertiesEXT vkGetPhysicalDeviceToolPropertiesEXT; #endif /* defined(VK_EXT_tooling_info) */ diff --git a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vk_icd.h b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vk_icd.h index 44b1527..fa90fcf 100644 --- a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vk_icd.h +++ b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vk_icd.h @@ -2,9 +2,9 @@ // File: vk_icd.h // /* - * Copyright (c) 2015-2016 The Khronos Group Inc. - * Copyright (c) 2015-2016 Valve Corporation - * Copyright (c) 2015-2016 LunarG, Inc. + * Copyright (c) 2015-2016, 2022 The Khronos Group Inc. + * Copyright (c) 2015-2016, 2022 Valve Corporation + * Copyright (c) 2015-2016, 2022 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,17 @@ // call for any API version > 1.0. Otherwise, the loader will // manually determine if it can support the expected version. // Version 6 - Add support for vk_icdEnumerateAdapterPhysicalDevices. -#define CURRENT_LOADER_ICD_INTERFACE_VERSION 6 +// Version 7 - If an ICD supports any of the following functions, they must be +// queryable with vk_icdGetInstanceProcAddr: +// vk_icdNegotiateLoaderICDInterfaceVersion +// vk_icdGetPhysicalDeviceProcAddr +// vk_icdEnumerateAdapterPhysicalDevices (Windows only) +// In addition, these functions no longer need to be exported directly. +// This version allows drivers provided through the extension +// VK_LUNARG_direct_driver_loading be able to support the entire +// Driver-Loader interface. + +#define CURRENT_LOADER_ICD_INTERFACE_VERSION 7 #define MIN_SUPPORTED_LOADER_ICD_INTERFACE_VERSION 0 #define MIN_PHYS_DEV_EXTENSION_ICD_INTERFACE_VERSION 4 diff --git a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_beta.h b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_beta.h index db51102..b6c8e99 100644 --- a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_beta.h +++ b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_beta.h @@ -958,7 +958,7 @@ typedef struct VkVideoDecodeH264DpbSlotInfoEXT { #define VK_EXT_video_decode_h265 1 #include "vk_video/vulkan_video_codec_h265std_decode.h" -#define VK_EXT_VIDEO_DECODE_H265_SPEC_VERSION 5 +#define VK_EXT_VIDEO_DECODE_H265_SPEC_VERSION 6 #define VK_EXT_VIDEO_DECODE_H265_EXTENSION_NAME "VK_EXT_video_decode_h265" typedef struct VkVideoDecodeH265ProfileInfoEXT { VkStructureType sType; @@ -996,8 +996,8 @@ typedef struct VkVideoDecodeH265PictureInfoEXT { VkStructureType sType; const void* pNext; StdVideoDecodeH265PictureInfo* pStdPictureInfo; - uint32_t sliceCount; - const uint32_t* pSliceOffsets; + uint32_t sliceSegmentCount; + const uint32_t* pSliceSegmentOffsets; } VkVideoDecodeH265PictureInfoEXT; typedef struct VkVideoDecodeH265DpbSlotInfoEXT { diff --git a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_core.h b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_core.h index b9c5e25..248f6ab 100644 --- a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_core.h +++ b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_core.h @@ -72,7 +72,7 @@ extern "C" { #define VK_API_VERSION_1_0 VK_MAKE_API_VERSION(0, 1, 0, 0)// Patch version should always be set to 0 // Version of this file -#define VK_HEADER_VERSION 235 +#define VK_HEADER_VERSION 237 // Complete version of this file #define VK_HEADER_VERSION_COMPLETE VK_MAKE_API_VERSION(0, 1, 3, VK_HEADER_VERSION) @@ -854,6 +854,15 @@ typedef enum VkStructureType { VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_STATISTIC_KHR = 1000269004, VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INTERNAL_REPRESENTATION_KHR = 1000269005, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_FLOAT_2_FEATURES_EXT = 1000273000, + VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT = 1000274000, + VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT = 1000274001, + VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT = 1000274002, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT = 1000275000, + VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT = 1000275001, + VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODES_CREATE_INFO_EXT = 1000275002, + VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT = 1000275003, + VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_SCALING_CREATE_INFO_EXT = 1000275004, + VK_STRUCTURE_TYPE_RELEASE_SWAPCHAIN_IMAGES_INFO_EXT = 1000275005, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEVICE_GENERATED_COMMANDS_PROPERTIES_NV = 1000277000, VK_STRUCTURE_TYPE_GRAPHICS_SHADER_GROUP_CREATE_INFO_NV = 1000277001, VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_SHADER_GROUPS_CREATE_INFO_NV = 1000277002, @@ -1039,6 +1048,8 @@ typedef enum VkStructureType { VK_STRUCTURE_TYPE_RENDER_PASS_CREATION_CONTROL_EXT = 1000458001, VK_STRUCTURE_TYPE_RENDER_PASS_CREATION_FEEDBACK_CREATE_INFO_EXT = 1000458002, VK_STRUCTURE_TYPE_RENDER_PASS_SUBPASS_FEEDBACK_CREATE_INFO_EXT = 1000458003, + VK_STRUCTURE_TYPE_DIRECT_DRIVER_LOADING_INFO_LUNARG = 1000459000, + VK_STRUCTURE_TYPE_DIRECT_DRIVER_LOADING_LIST_LUNARG = 1000459001, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_MODULE_IDENTIFIER_FEATURES_EXT = 1000462000, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_MODULE_IDENTIFIER_PROPERTIES_EXT = 1000462001, VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_MODULE_IDENTIFIER_CREATE_INFO_EXT = 1000462002, @@ -1057,6 +1068,7 @@ typedef enum VkStructureType { VK_STRUCTURE_TYPE_TILE_PROPERTIES_QCOM = 1000484001, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_AMIGO_PROFILING_FEATURES_SEC = 1000485000, VK_STRUCTURE_TYPE_AMIGO_PROFILING_SUBMIT_INFO_SEC = 1000485001, + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PER_VIEW_VIEWPORTS_FEATURES_QCOM = 1000488000, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_INVOCATION_REORDER_FEATURES_NV = 1000490000, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_INVOCATION_REORDER_PROPERTIES_NV = 1000490001, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MUTABLE_DESCRIPTOR_TYPE_FEATURES_EXT = 1000351000, @@ -7629,6 +7641,7 @@ typedef enum VkSwapchainCreateFlagBitsKHR { VK_SWAPCHAIN_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT_KHR = 0x00000001, VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR = 0x00000002, VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR = 0x00000004, + VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT = 0x00000008, VK_SWAPCHAIN_CREATE_FLAG_BITS_MAX_ENUM_KHR = 0x7FFFFFFF } VkSwapchainCreateFlagBitsKHR; typedef VkFlags VkSwapchainCreateFlagsKHR; @@ -13109,6 +13122,105 @@ typedef struct VkPhysicalDeviceShaderAtomicFloat2FeaturesEXT { +#define VK_EXT_surface_maintenance1 1 +#define VK_EXT_SURFACE_MAINTENANCE_1_SPEC_VERSION 1 +#define VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME "VK_EXT_surface_maintenance1" + +typedef enum VkPresentScalingFlagBitsEXT { + VK_PRESENT_SCALING_ONE_TO_ONE_BIT_EXT = 0x00000001, + VK_PRESENT_SCALING_ASPECT_RATIO_STRETCH_BIT_EXT = 0x00000002, + VK_PRESENT_SCALING_STRETCH_BIT_EXT = 0x00000004, + VK_PRESENT_SCALING_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF +} VkPresentScalingFlagBitsEXT; +typedef VkFlags VkPresentScalingFlagsEXT; + +typedef enum VkPresentGravityFlagBitsEXT { + VK_PRESENT_GRAVITY_MIN_BIT_EXT = 0x00000001, + VK_PRESENT_GRAVITY_MAX_BIT_EXT = 0x00000002, + VK_PRESENT_GRAVITY_CENTERED_BIT_EXT = 0x00000004, + VK_PRESENT_GRAVITY_FLAG_BITS_MAX_ENUM_EXT = 0x7FFFFFFF +} VkPresentGravityFlagBitsEXT; +typedef VkFlags VkPresentGravityFlagsEXT; +typedef struct VkSurfacePresentModeEXT { + VkStructureType sType; + void* pNext; + VkPresentModeKHR presentMode; +} VkSurfacePresentModeEXT; + +typedef struct VkSurfacePresentScalingCapabilitiesEXT { + VkStructureType sType; + void* pNext; + VkPresentScalingFlagsEXT supportedPresentScaling; + VkPresentGravityFlagsEXT supportedPresentGravityX; + VkPresentGravityFlagsEXT supportedPresentGravityY; + VkExtent2D minScaledImageExtent; + VkExtent2D maxScaledImageExtent; +} VkSurfacePresentScalingCapabilitiesEXT; + +typedef struct VkSurfacePresentModeCompatibilityEXT { + VkStructureType sType; + void* pNext; + uint32_t presentModeCount; + VkPresentModeKHR* pPresentModes; +} VkSurfacePresentModeCompatibilityEXT; + + + +#define VK_EXT_swapchain_maintenance1 1 +#define VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION 1 +#define VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME "VK_EXT_swapchain_maintenance1" +typedef struct VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT { + VkStructureType sType; + void* pNext; + VkBool32 swapchainMaintenance1; +} VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT; + +typedef struct VkSwapchainPresentFenceInfoEXT { + VkStructureType sType; + void* pNext; + uint32_t swapchainCount; + const VkFence* pFences; +} VkSwapchainPresentFenceInfoEXT; + +typedef struct VkSwapchainPresentModesCreateInfoEXT { + VkStructureType sType; + void* pNext; + uint32_t presentModeCount; + const VkPresentModeKHR* pPresentModes; +} VkSwapchainPresentModesCreateInfoEXT; + +typedef struct VkSwapchainPresentModeInfoEXT { + VkStructureType sType; + void* pNext; + uint32_t swapchainCount; + const VkPresentModeKHR* pPresentModes; +} VkSwapchainPresentModeInfoEXT; + +typedef struct VkSwapchainPresentScalingCreateInfoEXT { + VkStructureType sType; + const void* pNext; + VkPresentScalingFlagsEXT scalingBehavior; + VkPresentGravityFlagsEXT presentGravityX; + VkPresentGravityFlagsEXT presentGravityY; +} VkSwapchainPresentScalingCreateInfoEXT; + +typedef struct VkReleaseSwapchainImagesInfoEXT { + VkStructureType sType; + const void* pNext; + VkSwapchainKHR swapchain; + uint32_t imageIndexCount; + const uint32_t* pImageIndices; +} VkReleaseSwapchainImagesInfoEXT; + +typedef VkResult (VKAPI_PTR *PFN_vkReleaseSwapchainImagesEXT)(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkReleaseSwapchainImagesEXT( + VkDevice device, + const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo); +#endif + + #define VK_EXT_shader_demote_to_helper_invocation 1 #define VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_SPEC_VERSION 1 #define VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME "VK_EXT_shader_demote_to_helper_invocation" @@ -13667,7 +13779,7 @@ typedef union VkDescriptorDataEXT { const VkDescriptorAddressInfoEXT* pStorageTexelBuffer; const VkDescriptorAddressInfoEXT* pUniformBuffer; const VkDescriptorAddressInfoEXT* pStorageBuffer; - VkDeviceAddress accelerationStructure; + VkDeviceAddress accelerationStructure; } VkDescriptorDataEXT; typedef struct VkDescriptorGetInfoEXT { @@ -14243,24 +14355,6 @@ typedef struct VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT { -#define VK_NV_acquire_winrt_display 1 -#define VK_NV_ACQUIRE_WINRT_DISPLAY_SPEC_VERSION 1 -#define VK_NV_ACQUIRE_WINRT_DISPLAY_EXTENSION_NAME "VK_NV_acquire_winrt_display" -typedef VkResult (VKAPI_PTR *PFN_vkAcquireWinrtDisplayNV)(VkPhysicalDevice physicalDevice, VkDisplayKHR display); -typedef VkResult (VKAPI_PTR *PFN_vkGetWinrtDisplayNV)(VkPhysicalDevice physicalDevice, uint32_t deviceRelativeId, VkDisplayKHR* pDisplay); - -#ifndef VK_NO_PROTOTYPES -VKAPI_ATTR VkResult VKAPI_CALL vkAcquireWinrtDisplayNV( - VkPhysicalDevice physicalDevice, - VkDisplayKHR display); - -VKAPI_ATTR VkResult VKAPI_CALL vkGetWinrtDisplayNV( - VkPhysicalDevice physicalDevice, - uint32_t deviceRelativeId, - VkDisplayKHR* pDisplay); -#endif - - #define VK_VALVE_mutable_descriptor_type 1 #define VK_VALVE_MUTABLE_DESCRIPTOR_TYPE_SPEC_VERSION 1 #define VK_VALVE_MUTABLE_DESCRIPTOR_TYPE_EXTENSION_NAME "VK_VALVE_mutable_descriptor_type" @@ -15547,6 +15641,36 @@ typedef struct VkRenderPassSubpassFeedbackCreateInfoEXT { +#define VK_LUNARG_direct_driver_loading 1 +#define VK_LUNARG_DIRECT_DRIVER_LOADING_SPEC_VERSION 1 +#define VK_LUNARG_DIRECT_DRIVER_LOADING_EXTENSION_NAME "VK_LUNARG_direct_driver_loading" + +typedef enum VkDirectDriverLoadingModeLUNARG { + VK_DIRECT_DRIVER_LOADING_MODE_EXCLUSIVE_LUNARG = 0, + VK_DIRECT_DRIVER_LOADING_MODE_INCLUSIVE_LUNARG = 1, + VK_DIRECT_DRIVER_LOADING_MODE_MAX_ENUM_LUNARG = 0x7FFFFFFF +} VkDirectDriverLoadingModeLUNARG; +typedef VkFlags VkDirectDriverLoadingFlagsLUNARG; +typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetInstanceProcAddrLUNARG)( + VkInstance instance, const char* pName); + +typedef struct VkDirectDriverLoadingInfoLUNARG { + VkStructureType sType; + void* pNext; + VkDirectDriverLoadingFlagsLUNARG flags; + PFN_vkGetInstanceProcAddrLUNARG pfnGetInstanceProcAddr; +} VkDirectDriverLoadingInfoLUNARG; + +typedef struct VkDirectDriverLoadingListLUNARG { + VkStructureType sType; + void* pNext; + VkDirectDriverLoadingModeLUNARG mode; + uint32_t driverCount; + const VkDirectDriverLoadingInfoLUNARG* pDrivers; +} VkDirectDriverLoadingListLUNARG; + + + #define VK_EXT_shader_module_identifier 1 #define VK_MAX_SHADER_MODULE_IDENTIFIER_SIZE_EXT 32U #define VK_EXT_SHADER_MODULE_IDENTIFIER_SPEC_VERSION 1 @@ -15836,6 +15960,17 @@ typedef struct VkAmigoProfilingSubmitInfoSEC { +#define VK_QCOM_multiview_per_view_viewports 1 +#define VK_QCOM_MULTIVIEW_PER_VIEW_VIEWPORTS_SPEC_VERSION 1 +#define VK_QCOM_MULTIVIEW_PER_VIEW_VIEWPORTS_EXTENSION_NAME "VK_QCOM_multiview_per_view_viewports" +typedef struct VkPhysicalDeviceMultiviewPerViewViewportsFeaturesQCOM { + VkStructureType sType; + void* pNext; + VkBool32 multiviewPerViewViewports; +} VkPhysicalDeviceMultiviewPerViewViewportsFeaturesQCOM; + + + #define VK_NV_ray_tracing_invocation_reorder 1 #define VK_NV_RAY_TRACING_INVOCATION_REORDER_SPEC_VERSION 1 #define VK_NV_RAY_TRACING_INVOCATION_REORDER_EXTENSION_NAME "VK_NV_ray_tracing_invocation_reorder" diff --git a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_win32.h b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_win32.h index affe0c0..a8e46c8 100644 --- a/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_win32.h +++ b/parallel-rdp-standalone/vulkan-headers/include/vulkan/vulkan_win32.h @@ -308,6 +308,24 @@ VKAPI_ATTR VkResult VKAPI_CALL vkGetDeviceGroupSurfacePresentModes2EXT( VkDeviceGroupPresentModeFlagsKHR* pModes); #endif + +#define VK_NV_acquire_winrt_display 1 +#define VK_NV_ACQUIRE_WINRT_DISPLAY_SPEC_VERSION 1 +#define VK_NV_ACQUIRE_WINRT_DISPLAY_EXTENSION_NAME "VK_NV_acquire_winrt_display" +typedef VkResult (VKAPI_PTR *PFN_vkAcquireWinrtDisplayNV)(VkPhysicalDevice physicalDevice, VkDisplayKHR display); +typedef VkResult (VKAPI_PTR *PFN_vkGetWinrtDisplayNV)(VkPhysicalDevice physicalDevice, uint32_t deviceRelativeId, VkDisplayKHR* pDisplay); + +#ifndef VK_NO_PROTOTYPES +VKAPI_ATTR VkResult VKAPI_CALL vkAcquireWinrtDisplayNV( + VkPhysicalDevice physicalDevice, + VkDisplayKHR display); + +VKAPI_ATTR VkResult VKAPI_CALL vkGetWinrtDisplayNV( + VkPhysicalDevice physicalDevice, + uint32_t deviceRelativeId, + VkDisplayKHR* pDisplay); +#endif + #ifdef __cplusplus } #endif diff --git a/parallel-rdp-standalone/vulkan/buffer_pool.cpp b/parallel-rdp-standalone/vulkan/buffer_pool.cpp index 7bae132..f9849c3 100644 --- a/parallel-rdp-standalone/vulkan/buffer_pool.cpp +++ b/parallel-rdp-standalone/vulkan/buffer_pool.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "buffer_pool.hpp" #include "device.hpp" #include diff --git a/parallel-rdp-standalone/vulkan/buffer_pool.hpp b/parallel-rdp-standalone/vulkan/buffer_pool.hpp index 6b8f79c..6f26005 100644 --- a/parallel-rdp-standalone/vulkan/buffer_pool.hpp +++ b/parallel-rdp-standalone/vulkan/buffer_pool.hpp @@ -58,8 +58,8 @@ struct BufferBlock auto *ret = mapped + aligned_offset; offset = aligned_offset + allocate_size; - VkDeviceSize padded_size = std::max(allocate_size, spill_size); - padded_size = std::min(padded_size, size - aligned_offset); + VkDeviceSize padded_size = std::max(allocate_size, spill_size); + padded_size = std::min(padded_size, size - aligned_offset); return { ret, aligned_offset, padded_size }; } diff --git a/parallel-rdp-standalone/vulkan/command_buffer.cpp b/parallel-rdp-standalone/vulkan/command_buffer.cpp index 70cf77f..6b52bf1 100644 --- a/parallel-rdp-standalone/vulkan/command_buffer.cpp +++ b/parallel-rdp-standalone/vulkan/command_buffer.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "command_buffer.hpp" #include "device.hpp" #include "format.hpp" @@ -64,6 +65,8 @@ CommandBuffer::CommandBuffer(Device *device_, VkCommandBuffer cmd_, VkPipelineCa pipeline_state.subgroup_size_tag = (features.subgroup_size_control_properties.minSubgroupSize << 0) | (features.subgroup_size_control_properties.maxSubgroupSize << 8); + + device->lock.read_only_cache.lock_read(); } CommandBuffer::~CommandBuffer() @@ -72,6 +75,7 @@ CommandBuffer::~CommandBuffer() VK_ASSERT(ibo_block.mapped == nullptr); VK_ASSERT(ubo_block.mapped == nullptr); VK_ASSERT(staging_block.mapped == nullptr); + device->lock.read_only_cache.unlock_read(); } void CommandBuffer::fill_buffer(const Buffer &dst, uint32_t value) diff --git a/parallel-rdp-standalone/vulkan/context.cpp b/parallel-rdp-standalone/vulkan/context.cpp index e16a421..13fd842 100644 --- a/parallel-rdp-standalone/vulkan/context.cpp +++ b/parallel-rdp-standalone/vulkan/context.cpp @@ -363,6 +363,12 @@ bool Context::create_instance(const char **instance_ext, uint32_t instance_ext_c ext.supports_surface_capabilities2 = true; } + if (ext.supports_surface_capabilities2 && has_extension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME)) + { + instance_exts.push_back(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME); + ext.supports_surface_maintenance1 = true; + } + if ((flags & CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT) != 0 && has_surface_extension && has_extension(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)) @@ -722,7 +728,8 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c requires_swapchain = true; else if (strcmp(required_device_extensions[i], VK_KHR_PRESENT_ID_EXTENSION_NAME) == 0 || strcmp(required_device_extensions[i], VK_KHR_PRESENT_WAIT_EXTENSION_NAME) == 0 || - strcmp(required_device_extensions[i], VK_EXT_HDR_METADATA_EXTENSION_NAME) == 0) + strcmp(required_device_extensions[i], VK_EXT_HDR_METADATA_EXTENSION_NAME) == 0 || + strcmp(required_device_extensions[i], VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME) == 0) { flags |= CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT; } @@ -864,7 +871,7 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c } #endif - VkPhysicalDeviceFeatures2 features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; + pdf2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; ext.multiview_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES }; ext.sampler_ycbcr_conversion_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES }; @@ -879,6 +886,7 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c ext.present_id_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR }; ext.present_wait_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR }; ext.performance_query_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR }; + ext.swapchain_maintenance1_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT }; ext.subgroup_size_control_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT }; ext.host_query_reset_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT }; @@ -892,7 +900,7 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c ext.compute_shader_derivative_features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COMPUTE_SHADER_DERIVATIVES_FEATURES_NV }; - void **ppNext = &features.pNext; + void **ppNext = &pdf2.pNext; *ppNext = &ext.multiview_features; ppNext = &ext.multiview_features.pNext; @@ -1037,18 +1045,35 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c if ((flags & CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT) != 0 && requires_swapchain) { - if (has_extension(VK_KHR_PRESENT_ID_EXTENSION_NAME)) + bool broken_present_wait = ext.driver_properties.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY && + VK_VERSION_MAJOR(gpu_props.driverVersion) == 525; + + if (broken_present_wait) { - enabled_extensions.push_back(VK_KHR_PRESENT_ID_EXTENSION_NAME); - *ppNext = &ext.present_id_features; - ppNext = &ext.present_id_features.pNext; + LOGW("Disabling present_wait due to broken driver.\n"); + } + else + { + if (has_extension(VK_KHR_PRESENT_ID_EXTENSION_NAME)) + { + enabled_extensions.push_back(VK_KHR_PRESENT_ID_EXTENSION_NAME); + *ppNext = &ext.present_id_features; + ppNext = &ext.present_id_features.pNext; + } + + if (has_extension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME)) + { + enabled_extensions.push_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME); + *ppNext = &ext.present_wait_features; + ppNext = &ext.present_wait_features.pNext; + } } - if (has_extension(VK_KHR_PRESENT_WAIT_EXTENSION_NAME)) + if (ext.supports_surface_maintenance1 && has_extension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME)) { - enabled_extensions.push_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME); - *ppNext = &ext.present_wait_features; - ppNext = &ext.present_wait_features.pNext; + enabled_extensions.push_back(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME); + *ppNext = &ext.swapchain_maintenance1_features; + ppNext = &ext.swapchain_maintenance1_features.pNext; } if (ext.supports_swapchain_colorspace && has_extension(VK_EXT_HDR_METADATA_EXTENSION_NAME)) @@ -1058,63 +1083,63 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c } } - vkGetPhysicalDeviceFeatures2(gpu, &features); + vkGetPhysicalDeviceFeatures2(gpu, &pdf2); // Enable device features we might care about. { VkPhysicalDeviceFeatures enabled_features = *required_features; - if (features.features.textureCompressionETC2) + if (pdf2.features.textureCompressionETC2) enabled_features.textureCompressionETC2 = VK_TRUE; - if (features.features.textureCompressionBC) + if (pdf2.features.textureCompressionBC) enabled_features.textureCompressionBC = VK_TRUE; - if (features.features.textureCompressionASTC_LDR) + if (pdf2.features.textureCompressionASTC_LDR) enabled_features.textureCompressionASTC_LDR = VK_TRUE; - if (features.features.fullDrawIndexUint32) + if (pdf2.features.fullDrawIndexUint32) enabled_features.fullDrawIndexUint32 = VK_TRUE; - if (features.features.imageCubeArray) + if (pdf2.features.imageCubeArray) enabled_features.imageCubeArray = VK_TRUE; - if (features.features.fillModeNonSolid) + if (pdf2.features.fillModeNonSolid) enabled_features.fillModeNonSolid = VK_TRUE; - if (features.features.independentBlend) + if (pdf2.features.independentBlend) enabled_features.independentBlend = VK_TRUE; - if (features.features.sampleRateShading) + if (pdf2.features.sampleRateShading) enabled_features.sampleRateShading = VK_TRUE; - if (features.features.fragmentStoresAndAtomics) + if (pdf2.features.fragmentStoresAndAtomics) enabled_features.fragmentStoresAndAtomics = VK_TRUE; - if (features.features.shaderStorageImageExtendedFormats) + if (pdf2.features.shaderStorageImageExtendedFormats) enabled_features.shaderStorageImageExtendedFormats = VK_TRUE; - if (features.features.shaderStorageImageMultisample) + if (pdf2.features.shaderStorageImageMultisample) enabled_features.shaderStorageImageMultisample = VK_TRUE; - if (features.features.largePoints) + if (pdf2.features.largePoints) enabled_features.largePoints = VK_TRUE; - if (features.features.shaderInt16) + if (pdf2.features.shaderInt16) enabled_features.shaderInt16 = VK_TRUE; - if (features.features.shaderInt64) + if (pdf2.features.shaderInt64) enabled_features.shaderInt64 = VK_TRUE; - if (features.features.shaderStorageImageWriteWithoutFormat) + if (pdf2.features.shaderStorageImageWriteWithoutFormat) enabled_features.shaderStorageImageWriteWithoutFormat = VK_TRUE; - if (features.features.shaderStorageImageReadWithoutFormat) + if (pdf2.features.shaderStorageImageReadWithoutFormat) enabled_features.shaderStorageImageReadWithoutFormat = VK_TRUE; - if (features.features.shaderSampledImageArrayDynamicIndexing) + if (pdf2.features.shaderSampledImageArrayDynamicIndexing) enabled_features.shaderSampledImageArrayDynamicIndexing = VK_TRUE; - if (features.features.shaderUniformBufferArrayDynamicIndexing) + if (pdf2.features.shaderUniformBufferArrayDynamicIndexing) enabled_features.shaderUniformBufferArrayDynamicIndexing = VK_TRUE; - if (features.features.shaderStorageBufferArrayDynamicIndexing) + if (pdf2.features.shaderStorageBufferArrayDynamicIndexing) enabled_features.shaderStorageBufferArrayDynamicIndexing = VK_TRUE; - if (features.features.shaderStorageImageArrayDynamicIndexing) + if (pdf2.features.shaderStorageImageArrayDynamicIndexing) enabled_features.shaderStorageImageArrayDynamicIndexing = VK_TRUE; - if (features.features.shaderImageGatherExtended) + if (pdf2.features.shaderImageGatherExtended) enabled_features.shaderImageGatherExtended = VK_TRUE; - if (features.features.samplerAnisotropy) + if (pdf2.features.samplerAnisotropy) enabled_features.samplerAnisotropy = VK_TRUE; - features.features = enabled_features; + pdf2.features = enabled_features; ext.enabled_features = enabled_features; } - device_info.pNext = &features; + device_info.pNext = &pdf2; if (ext.supports_external && has_extension(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME)) { @@ -1190,8 +1215,8 @@ bool Context::create_device(VkPhysicalDevice gpu_, VkSurfaceKHR surface, const c #ifdef GRANITE_VULKAN_FOSSILIZE feature_filter.init(user_application_info ? user_application_info->apiVersion : VK_API_VERSION_1_1, - enabled_extensions.data(), device_info.enabledExtensionCount, - &features, &props); + enabled_extensions.data(), device_info.enabledExtensionCount, + &pdf2, &props); feature_filter.set_device_query_interface(this); #endif diff --git a/parallel-rdp-standalone/vulkan/context.hpp b/parallel-rdp-standalone/vulkan/context.hpp index 4aeb197..3f9ffb5 100644 --- a/parallel-rdp-standalone/vulkan/context.hpp +++ b/parallel-rdp-standalone/vulkan/context.hpp @@ -72,6 +72,7 @@ struct DeviceFeatures bool supports_tooling_info = false; bool supports_hdr_metadata = false; bool supports_swapchain_colorspace = false; + bool supports_surface_maintenance1 = false; // Vulkan 1.1 core VkPhysicalDeviceFeatures enabled_features = {}; @@ -109,6 +110,7 @@ struct DeviceFeatures VkPhysicalDeviceASTCDecodeFeaturesEXT astc_decode_features = {}; VkPhysicalDeviceTextureCompressionASTCHDRFeaturesEXT astc_hdr_features = {}; VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT pipeline_creation_cache_control_features = {}; + VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_features = {}; // Vendor VkPhysicalDeviceComputeShaderDerivativesFeaturesNV compute_shader_derivative_features = {}; @@ -254,6 +256,11 @@ public: } #endif + const VkPhysicalDeviceFeatures2 &get_physical_device_features() const + { + return pdf2; + } + private: VkDevice device = VK_NULL_HANDLE; VkInstance instance = VK_NULL_HANDLE; @@ -276,6 +283,7 @@ private: bool owned_instance = false; bool owned_device = false; DeviceFeatures ext; + VkPhysicalDeviceFeatures2 pdf2; #ifdef VULKAN_DEBUG VkDebugUtilsMessengerEXT debug_messenger = VK_NULL_HANDLE; diff --git a/parallel-rdp-standalone/vulkan/descriptor_set.cpp b/parallel-rdp-standalone/vulkan/descriptor_set.cpp index 8398dba..a942ce6 100644 --- a/parallel-rdp-standalone/vulkan/descriptor_set.cpp +++ b/parallel-rdp-standalone/vulkan/descriptor_set.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "descriptor_set.hpp" #include "device.hpp" #include diff --git a/parallel-rdp-standalone/vulkan/device.cpp b/parallel-rdp-standalone/vulkan/device.cpp index b56bc2b..f320601 100644 --- a/parallel-rdp-standalone/vulkan/device.cpp +++ b/parallel-rdp-standalone/vulkan/device.cpp @@ -20,7 +20,11 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "device.hpp" +#ifdef GRANITE_VULKAN_FOSSILIZE +#include "device_fossilize.hpp" +#endif #include "format.hpp" #include "timeline_trace_file.hpp" #include "type_to_string.hpp" @@ -39,28 +43,19 @@ #include "string_helpers.hpp" #endif -#ifdef GRANITE_VULKAN_MT #include "thread_id.hpp" static unsigned get_thread_index() { return Util::get_current_thread_index(); } -#define LOCK() std::lock_guard holder__{lock.lock} -#define LOCK_MEMORY() std::lock_guard holder__{lock.memory_lock} +#define LOCK() std::lock_guard _holder_##__COUNTER__{lock.lock} +#define LOCK_MEMORY() std::lock_guard _holder_##__COUNTER__{lock.memory_lock} +#define LOCK_CACHE() ::Util::RWSpinLockReadHolder _holder_##__COUNTER__{lock.read_only_cache} #define DRAIN_FRAME_LOCK() \ - std::unique_lock holder__{lock.lock}; \ - lock.cond.wait(holder__, [&]() { \ + std::unique_lock _holder{lock.lock}; \ + lock.cond.wait(_holder, [&]() { \ return lock.counter == 0; \ }) -#else -#define LOCK() ((void)0) -#define LOCK_MEMORY() ((void)0) -#define DRAIN_FRAME_LOCK() VK_ASSERT(lock.counter == 0) -static unsigned get_thread_index() -{ - return 0; -} -#endif using namespace Util; @@ -96,9 +91,7 @@ Device::Device() , texture_manager(this) #endif { -#ifdef GRANITE_VULKAN_MT cookie.store(0); -#endif } Semaphore Device::request_semaphore(VkSemaphoreTypeKHR type, VkSemaphore vk_semaphore, bool transfer_ownership) @@ -386,6 +379,7 @@ Shader *Device::request_shader(const uint32_t *data, size_t size, const ImmutableSamplerBank *sampler_bank) { auto hash = Shader::hash(data, size, sampler_bank); + LOCK_CACHE(); auto *ret = shaders.find(hash); if (!ret) ret = shaders.emplace_yield(hash, hash, this, data, size, layout, sampler_bank); @@ -394,6 +388,7 @@ Shader *Device::request_shader(const uint32_t *data, size_t size, Shader *Device::request_shader_by_hash(Hash hash) { + LOCK_CACHE(); return shaders.find(hash); } @@ -405,6 +400,7 @@ Program *Device::request_program(Vulkan::Shader *compute_shader) Util::Hasher hasher; hasher.u64(compute_shader->get_hash()); + LOCK_CACHE(); auto hash = hasher.get(); auto *ret = programs.find(hash); if (!ret) @@ -433,6 +429,7 @@ Program *Device::request_program(Shader *vertex, Shader *fragment) hasher.u64(fragment->get_hash()); auto hash = hasher.get(); + LOCK_CACHE(); auto *ret = programs.find(hash); if (!ret) @@ -490,6 +487,7 @@ DescriptorSetAllocator *Device::request_descriptor_set_allocator(const Descripto }); auto hash = h.get(); + LOCK_CACHE(); auto *ret = descriptor_set_allocators.find(hash); if (!ret) ret = descriptor_set_allocators.emplace_yield(hash, hash, this, layout, stages_for_bindings, immutable_samplers_); @@ -827,11 +825,10 @@ void Device::init_workarounds() void Device::set_context(const Context &context) { + ctx = &context; table = &context.get_device_table(); -#ifdef GRANITE_VULKAN_MT register_thread_index(0); -#endif instance = context.get_instance(); gpu = context.get_gpu(); device = context.get_device(); @@ -850,7 +847,6 @@ void Device::set_context(const Context &context) init_pipeline_cache(); init_timeline_semaphores(); - init_bindless(); init_frame_contexts(2); // By default, regular double buffer between CPU and GPU. @@ -894,36 +890,39 @@ void Device::set_context(const Context &context) queue_data[i].performance_query_pool.init_device(this, queue_info.family_indices[i]); } -#ifdef GRANITE_VULKAN_FILESYSTEM - init_shader_manager_cache(); -#endif - -#ifdef GRANITE_VULKAN_FOSSILIZE - init_pipeline_state(context.get_feature_filter()); -#endif - if (system_handles.timeline_trace_file) init_calibrated_timestamps(); } -void Device::init_bindless() +void Device::begin_shader_caches() { - if (!ext.supports_descriptor_indexing) + if (!ctx) + { + LOGE("No context. Forgot Device::set_context()?\n"); return; + } - DescriptorSetLayout layout; - - layout.array_size[0] = DescriptorSetLayout::UNSIZED_ARRAY; - for (unsigned i = 1; i < VULKAN_NUM_BINDINGS; i++) - layout.array_size[i] = 1; - - layout.separate_image_mask = 1; - uint32_t stages_for_sets[VULKAN_NUM_BINDINGS] = { VK_SHADER_STAGE_ALL }; - bindless_sampled_image_allocator_integer = request_descriptor_set_allocator(layout, stages_for_sets, nullptr); - layout.fp_mask = 1; - bindless_sampled_image_allocator_fp = request_descriptor_set_allocator(layout, stages_for_sets, nullptr); +#ifdef GRANITE_VULKAN_FOSSILIZE + init_pipeline_state(ctx->get_feature_filter(), ctx->get_physical_device_features(), + ctx->get_application_info()); +#elif defined(GRANITE_VULKAN_FILESYSTEM) + // Fossilize init will deal with init_shader_manager_cache() + init_shader_manager_cache(); +#endif } +#ifndef GRANITE_VULKAN_FOSSILIZE +unsigned Device::query_initialization_progress(InitializationStage) const +{ + // If we don't have Fossilize, everything is considered done up front. + return 100; +} + +void Device::wait_shader_caches() +{ +} +#endif + void Device::init_timeline_semaphores() { if (!ext.timeline_semaphore_features.timelineSemaphore) @@ -1271,6 +1270,13 @@ void Device::submit_empty_inner(QueueIndices physical_type, InternalFence *fence collect_wait_semaphores(data, wait_semaphores); composer.add_wait_submissions(wait_semaphores); + for (auto consume : frame().consumed_semaphores) + { + composer.add_wait_semaphore(consume, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); + frame().recycled_semaphores.push_back(consume); + } + frame().consumed_semaphores.clear(); + emit_queue_signals(composer, external_semaphore, timeline_semaphore, timeline_value, fence, semaphore_count, semaphores); @@ -1590,6 +1596,19 @@ void Helper::BatchComposer::add_wait_semaphore(SemaphoreHolder &sem, VkPipelineS wait_counts[submit_index].push_back(is_timeline ? sem.get_timeline_value() : 0); } +void Helper::BatchComposer::add_wait_semaphore(VkSemaphore sem, VkPipelineStageFlags stage) +{ + if (!cmds[submit_index].empty() || !signals[submit_index].empty()) + begin_batch(); + + if (split_binary_timeline_semaphores && has_timeline_semaphore_in_batch(submit_index)) + begin_batch(); + + waits[submit_index].push_back(sem); + wait_stages[submit_index].push_back(stage); + wait_counts[submit_index].push_back(0); +} + void Device::emit_queue_signals(Helper::BatchComposer &composer, SemaphoreHolder *external_semaphore, VkSemaphore sem, uint64_t timeline, InternalFence *fence, @@ -1758,6 +1777,13 @@ void Device::submit_queue(QueueIndices physical_type, InternalFence *fence, if (fence) fence->fence = cleared_fence; + for (auto consume : frame().consumed_semaphores) + { + composer.add_wait_semaphore(consume, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); + frame().recycled_semaphores.push_back(consume); + } + frame().consumed_semaphores.clear(); + emit_queue_signals(composer, external_semaphore, timeline_semaphore, timeline_value, fence, semaphore_count, semaphores); @@ -1850,7 +1876,9 @@ void Device::end_frame_nolock() for (auto &i : queue_flush_order) { - if (queue_data[i].need_fence || !frame().submissions[i].empty()) + if (queue_data[i].need_fence || + !frame().submissions[i].empty() || + !frame().consumed_semaphores.empty()) { submit_queue(i, &fence); if (fence.fence != VK_NULL_HANDLE) @@ -1908,9 +1936,7 @@ CommandBufferHandle Device::request_profiled_command_buffer_for_thread(unsigned CommandBufferHandle Device::request_command_buffer_nolock(unsigned thread_index, CommandBuffer::Type type, bool profiled) { -#ifndef GRANITE_VULKAN_MT VK_ASSERT(thread_index == 0); -#endif auto physical_type = get_physical_queue_type(type); auto &pool = frame().cmd_pools[physical_type][thread_index]; auto cmd = pool.request_command_buffer(); @@ -2019,14 +2045,14 @@ bool Device::swapchain_touched() const Device::~Device() { - wait_idle(); - - managers.timestamps.log_simple(); - wsi.acquire.reset(); wsi.release.reset(); wsi.swapchain.clear(); + wait_idle(); + + managers.timestamps.log_simple(); + if (pipeline_cache != VK_NULL_HANDLE) { flush_pipeline_cache(); @@ -2126,7 +2152,6 @@ void Device::init_swapchain(const std::vector &swapchain_images, unsign { DRAIN_FRAME_LOCK(); wsi.swapchain.clear(); - wait_idle_nolock(); auto info = ImageCreateInfo::render_target(width, height, format); info.usage = usage; @@ -2256,6 +2281,12 @@ void Device::destroy_semaphore(VkSemaphore semaphore) destroy_semaphore_nolock(semaphore); } +void Device::consume_semaphore(VkSemaphore semaphore) +{ + LOCK(); + consume_semaphore_nolock(semaphore); +} + void Device::recycle_semaphore(VkSemaphore semaphore) { LOCK(); @@ -2304,6 +2335,12 @@ void Device::destroy_semaphore_nolock(VkSemaphore semaphore) frame().destroyed_semaphores.push_back(semaphore); } +void Device::consume_semaphore_nolock(VkSemaphore semaphore) +{ + VK_ASSERT(!exists(frame().consumed_semaphores, semaphore)); + frame().consumed_semaphores.push_back(semaphore); +} + void Device::recycle_semaphore_nolock(VkSemaphore semaphore) { VK_ASSERT(!exists(frame().recycled_semaphores, semaphore)); @@ -2415,15 +2452,10 @@ void Device::wait_idle_nolock() framebuffer_allocator.clear(); transient_allocator.clear(); -#ifdef GRANITE_VULKAN_MT for (auto &allocator : descriptor_set_allocators.get_read_only()) allocator.clear(); for (auto &allocator : descriptor_set_allocators.get_read_write()) allocator.clear(); -#else - for (auto &allocator : descriptor_set_allocators) - allocator.clear(); -#endif for (auto &frame : per_frame) { @@ -2441,20 +2473,25 @@ void Device::wait_idle_nolock() void Device::promote_read_write_caches_to_read_only() { -#ifdef GRANITE_VULKAN_MT - pipeline_layouts.move_to_read_only(); - descriptor_set_allocators.move_to_read_only(); - shaders.move_to_read_only(); - programs.move_to_read_only(); - for (auto &program : programs.get_read_only()) - program.promote_read_write_to_read_only(); - render_passes.move_to_read_only(); - immutable_samplers.move_to_read_only(); - immutable_ycbcr_conversions.move_to_read_only(); + // Components which could potentially call into these must hold global reader locks. + // - A CommandBuffer holds a read lock for its lifetime. + // - Fossilize replay in the background also holds lock. + if (lock.read_only_cache.try_lock_write()) + { + pipeline_layouts.move_to_read_only(); + descriptor_set_allocators.move_to_read_only(); + shaders.move_to_read_only(); + programs.move_to_read_only(); + for (auto &program : programs.get_read_only()) + program.promote_read_write_to_read_only(); + render_passes.move_to_read_only(); + immutable_samplers.move_to_read_only(); + immutable_ycbcr_conversions.move_to_read_only(); #ifdef GRANITE_VULKAN_FILESYSTEM - shader_manager.promote_read_write_caches_to_read_only(); -#endif + shader_manager.promote_read_write_caches_to_read_only(); #endif + lock.read_only_cache.unlock_write(); + } } void Device::next_frame_context() @@ -2474,21 +2511,18 @@ void Device::next_frame_context() framebuffer_allocator.begin_frame(); transient_allocator.begin_frame(); -#ifdef GRANITE_VULKAN_MT for (auto &allocator : descriptor_set_allocators.get_read_only()) allocator.begin_frame(); for (auto &allocator : descriptor_set_allocators.get_read_write()) allocator.begin_frame(); -#else - for (auto &allocator : descriptor_set_allocators) - allocator.begin_frame(); -#endif VK_ASSERT(!per_frame.empty()); frame_context_index++; if (frame_context_index >= per_frame.size()) frame_context_index = 0; + promote_read_write_caches_to_read_only(); + frame().begin(); recalibrate_timestamps(); frame_context_begin_ts = write_calibrated_timestamp_nolock(); @@ -2678,9 +2712,7 @@ void Device::decrement_frame_counter_nolock() { VK_ASSERT(lock.counter > 0); lock.counter--; -#ifdef GRANITE_VULKAN_MT lock.cond.notify_all(); -#endif } void Device::PerFrame::trim_command_pools() @@ -2794,12 +2826,11 @@ void Device::PerFrame::begin() managers.semaphore.recycle(semaphore); for (auto &event : recycled_events) managers.event.recycle(event); + VK_ASSERT(consumed_semaphores.empty()); if (!allocations.empty()) { -#ifdef GRANITE_VULKAN_MT std::lock_guard holder{device.lock.memory_lock}; -#endif for (auto &alloc : allocations) alloc.free_immediate(managers.memory); } @@ -3417,11 +3448,17 @@ InitialImageBuffer Device::create_image_staging_buffer(const TextureFormatLayout buffer_info.domain = BufferDomain::Host; buffer_info.size = layout.get_required_size(); buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - result.buffer = create_buffer(buffer_info, nullptr); + { + GRANITE_SCOPED_TIMELINE_EVENT_FILE(system_handles.timeline_trace_file, "allocate-image-staging-buffer"); + result.buffer = create_buffer(buffer_info, nullptr); + } set_name(*result.buffer, "image-upload-staging-buffer"); auto *mapped = static_cast(map_host_buffer(*result.buffer, MEMORY_ACCESS_WRITE_BIT)); - memcpy(mapped, layout.data(), layout.get_required_size()); + { + GRANITE_SCOPED_TIMELINE_EVENT_FILE(system_handles.timeline_trace_file, "copy-image-staging-buffer"); + memcpy(mapped, layout.data(), layout.get_required_size()); + } unmap_host_buffer(*result.buffer, MEMORY_ACCESS_WRITE_BIT); layout.build_buffer_image_copies(result.blits); @@ -3462,7 +3499,10 @@ InitialImageBuffer Device::create_image_staging_buffer(const ImageCreateInfo &in buffer_info.domain = BufferDomain::Host; buffer_info.size = layout.get_required_size(); buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - result.buffer = create_buffer(buffer_info, nullptr); + { + GRANITE_SCOPED_TIMELINE_EVENT_FILE(system_handles.timeline_trace_file, "allocate-image-staging-buffer"); + result.buffer = create_buffer(buffer_info, nullptr); + } set_name(*result.buffer, "image-upload-staging-buffer"); // And now, do the actual copy. @@ -3471,6 +3511,7 @@ InitialImageBuffer Device::create_image_staging_buffer(const ImageCreateInfo &in layout.set_buffer(mapped, layout.get_required_size()); + GRANITE_SCOPED_TIMELINE_EVENT_FILE(system_handles.timeline_trace_file, "copy-image-staging-buffer"); for (unsigned level = 0; level < copy_levels; level++) { const auto &mip_info = layout.get_mip_info(level); @@ -4158,6 +4199,7 @@ const ImmutableSampler *Device::request_immutable_sampler(const SamplerCreateInf else h.u32(0); + LOCK_CACHE(); auto *sampler = immutable_samplers.find(h.get()); if (!sampler) sampler = immutable_samplers.emplace_yield(h.get(), h.get(), this, sampler_info, ycbcr); @@ -4180,6 +4222,7 @@ const ImmutableYcbcrConversion *Device::request_immutable_ycbcr_conversion( h.u32(info.ycbcrModel); h.u32(info.ycbcrRange); + LOCK_CACHE(); auto *sampler = immutable_ycbcr_conversions.find(h.get()); if (!sampler) sampler = immutable_ycbcr_conversions.emplace_yield(h.get(), h.get(), this, info); @@ -4201,22 +4244,29 @@ BindlessDescriptorPoolHandle Device::create_bindless_descriptor_pool(BindlessRes if (!ext.supports_descriptor_indexing) return BindlessDescriptorPoolHandle{nullptr}; - DescriptorSetAllocator *allocator = nullptr; + DescriptorSetLayout layout; + const uint32_t stages_for_sets[VULKAN_NUM_BINDINGS] = { VK_SHADER_STAGE_ALL }; + layout.array_size[0] = DescriptorSetLayout::UNSIZED_ARRAY; + for (unsigned i = 1; i < VULKAN_NUM_BINDINGS; i++) + layout.array_size[i] = 1; switch (type) { case BindlessResourceType::ImageFP: - allocator = bindless_sampled_image_allocator_fp; + layout.separate_image_mask = 1; + layout.fp_mask = 1; break; case BindlessResourceType::ImageInt: - allocator = bindless_sampled_image_allocator_integer; + layout.separate_image_mask = 1; break; default: - break; + return BindlessDescriptorPoolHandle{nullptr}; } + auto *allocator = request_descriptor_set_allocator(layout, stages_for_sets, nullptr); + VkDescriptorPool pool = VK_NULL_HANDLE; if (allocator) pool = allocator->allocate_bindless_pool(num_sets, num_descriptors); @@ -4670,12 +4720,7 @@ VkFormat Device::get_default_depth_format() const uint64_t Device::allocate_cookie() { // Reserve lower bits for "special purposes". -#ifdef GRANITE_VULKAN_MT return cookie.fetch_add(16, std::memory_order_relaxed) + 16; -#else - cookie += 16; - return cookie; -#endif } const RenderPass &Device::request_render_pass(const RenderPassInfo &info, bool compatible) @@ -5048,6 +5093,15 @@ TextureManager &Device::get_texture_manager() ShaderManager &Device::get_shader_manager() { +#ifdef GRANITE_VULKAN_FOSSILIZE + if (query_initialization_progress(InitializationStage::ShaderModules) < 100) + { + LOGW("Querying shader manager before completion of module initialization.\n" + "Application should not hit this case.\n" + "Blocking until completion ... Try using DeviceShaderModuleReadyEvent or PipelineReadyEvent instead.\n"); + block_until_shader_module_ready(); + } +#endif return shader_manager; } #endif diff --git a/parallel-rdp-standalone/vulkan/device.hpp b/parallel-rdp-standalone/vulkan/device.hpp index bc87b45..7d7d5f9 100644 --- a/parallel-rdp-standalone/vulkan/device.hpp +++ b/parallel-rdp-standalone/vulkan/device.hpp @@ -49,11 +49,9 @@ #include "texture_manager.hpp" #endif -#ifdef GRANITE_VULKAN_MT #include #include #include -#endif #ifdef GRANITE_VULKAN_FOSSILIZE #include "fossilize.hpp" @@ -136,6 +134,7 @@ public: explicit BatchComposer(bool split_binary_timeline_semaphores); void add_wait_submissions(WaitSemaphores &sem); void add_wait_semaphore(SemaphoreHolder &sem, VkPipelineStageFlags stage); + void add_wait_semaphore(VkSemaphore sem, VkPipelineStageFlags stage); void add_signal_semaphore(VkSemaphore sem, uint64_t count); void add_command_buffer(VkCommandBuffer cmd); @@ -218,6 +217,13 @@ public: // Only called by main thread, during setup phase. void set_context(const Context &context); + + // This is asynchronous in nature. See query_initialization_progress(). + // Kicks off Fossilize and shader manager caching. + void begin_shader_caches(); + // For debug or trivial applications, blocks until all shader cache work is done. + void wait_shader_caches(); + void init_swapchain(const std::vector &swapchain_images, unsigned width, unsigned height, VkFormat format, VkSurfaceTransformFlagBitsKHR transform, VkImageUsageFlags usage); void set_swapchain_queue_family_support(uint32_t queue_family_support); @@ -277,7 +283,6 @@ public: Fence *fence = nullptr, SemaphoreHolder *semaphore = nullptr); void submit_discard(CommandBufferHandle &cmd); - void add_wait_semaphore(CommandBuffer::Type type, Semaphore semaphore, VkPipelineStageFlags stages, bool flush); QueueIndices get_physical_queue_type(CommandBuffer::Type queue_type) const; void register_time_interval(std::string tid, QueryPoolHandle start_ts, QueryPoolHandle end_ts, std::string tag, std::string extra = {}); @@ -360,6 +365,8 @@ public: // For timelines, we need to know which handle type to use (OPAQUE or ID3D12Fence). // Binary external semaphore is always opaque with TEMPORARY semantics. + void add_wait_semaphore(CommandBuffer::Type type, Semaphore semaphore, VkPipelineStageFlags stages, bool flush); + // If transfer_ownership is set, Semaphore owns the VkSemaphore. Otherwise, application must // free the semaphore when GPU usage of it is complete. Semaphore request_semaphore(VkSemaphoreTypeKHR type, VkSemaphore handle = VK_NULL_HANDLE, bool transfer_ownership = false); @@ -369,7 +376,10 @@ public: // See request_timeline_semaphore_as_binary() for how to use timelines. Semaphore request_semaphore_external(VkSemaphoreTypeKHR type, VkExternalSemaphoreHandleTypeFlagBits handle_type); + // The created semaphore does not hold ownership of the VkSemaphore object. + // This is used when we want to wait on or signal an external timeline semaphore at a specific timeline value. + // We must collapse the timeline to a "binary" semaphore before we can call submit_empty or add_wait_semaphore(). Semaphore request_timeline_semaphore_as_binary(const SemaphoreHolder &holder, uint64_t value); // A proxy semaphore which lets us grab a semaphore handle before we signal it. @@ -406,12 +416,31 @@ public: const Sampler &get_stock_sampler(StockSampler sampler) const; #ifdef GRANITE_VULKAN_FILESYSTEM + // To obtain ShaderManager, ShaderModules must be observed to be complete + // in query_initialization_progress(). ShaderManager &get_shader_manager(); TextureManager &get_texture_manager(); - void init_shader_manager_cache(); - void flush_shader_manager_cache(); #endif + // Useful for loading screens or otherwise figuring out + // when we can start rendering in a stable state. + enum class InitializationStage + { + CacheMaintenance, + // When this is done, shader modules and the shader manager have been populated. + // At this stage it is safe to use shaders in a configuration where we + // don't have SPIRV-Cross and/or shaderc to do on the fly compilation. + // For shipping configurations. We can still compile pipelines, but it may stutter. + ShaderModules, + // When this is done, pipelines should never stutter if Fossilize knows about the pipeline. + Pipelines + }; + + // 0 -> not started + // [1, 99] rough percentage of completion + // >= 100 done + unsigned query_initialization_progress(InitializationStage status) const; + // For some platforms, the device and queue might be shared, possibly across threads, so need some mechanism to // lock the global device and queue. void set_queue_lock(std::function lock_callback, @@ -436,16 +465,6 @@ public: // A split version of VkEvent handling which lets us record a wait command before signal is recorded. PipelineEvent begin_signal_event(VkPipelineStageFlags stages); - // Promotes any read-write cached state to read-only, - // which eliminates need to read/write lock. - // Can be called at any time as long as there is no - // racing access to: - // - Command buffer recording which uses pipelines (texture manager is fine). - // - request_shader() - // - request_program() - // Generally, this should be called before you call next_frame_context(). - void promote_read_write_caches_to_read_only(); - const Context::SystemHandles &get_system_handles() const { return system_handles; @@ -467,14 +486,11 @@ private: VkPhysicalDevice gpu = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE; const VolkDeviceTable *table = nullptr; + const Context *ctx = nullptr; QueueInfo queue_info; unsigned num_thread_indices = 1; -#ifdef GRANITE_VULKAN_MT std::atomic_uint64_t cookie; -#else - uint64_t cookie = 0; -#endif uint64_t allocate_cookie(); void bake_program(Program &program); @@ -505,7 +521,6 @@ private: void init_stock_samplers(); void init_stock_sampler(StockSampler sampler, float max_aniso, float lod_bias); void init_timeline_semaphores(); - void init_bindless(); void deinit_timeline_semaphores(); uint64_t update_wrapped_device_timestamp(uint64_t ts); @@ -544,11 +559,10 @@ private: struct { -#ifdef GRANITE_VULKAN_MT std::mutex memory_lock; std::mutex lock; std::condition_variable cond; -#endif + Util::RWSpinLock read_only_cache; unsigned counter = 0; } lock; @@ -594,6 +608,7 @@ private: std::vector recycled_semaphores; std::vector recycled_events; std::vector destroyed_semaphores; + std::vector consumed_semaphores; std::vector keep_alive_images; struct DebugChannel @@ -696,9 +711,6 @@ private: VulkanCache immutable_samplers; VulkanCache immutable_ycbcr_conversions; - DescriptorSetAllocator *bindless_sampled_image_allocator_fp = nullptr; - DescriptorSetAllocator *bindless_sampled_image_allocator_integer = nullptr; - FramebufferAllocator framebuffer_allocator; TransientAttachmentAllocator transient_allocator; VkPipelineCache pipeline_cache = VK_NULL_HANDLE; @@ -736,6 +748,7 @@ private: void destroy_sampler(VkSampler sampler); void destroy_framebuffer(VkFramebuffer framebuffer); void destroy_semaphore(VkSemaphore semaphore); + void consume_semaphore(VkSemaphore semaphore); void recycle_semaphore(VkSemaphore semaphore); void destroy_event(VkEvent event); void free_memory(const DeviceAllocation &alloc); @@ -751,6 +764,7 @@ private: void destroy_sampler_nolock(VkSampler sampler); void destroy_framebuffer_nolock(VkFramebuffer framebuffer); void destroy_semaphore_nolock(VkSemaphore semaphore); + void consume_semaphore_nolock(VkSemaphore semaphore); void recycle_semaphore_nolock(VkSemaphore semaphore); void destroy_event_nolock(VkEvent event); void free_memory_nolock(const DeviceAllocation &alloc); @@ -790,10 +804,11 @@ private: #ifdef GRANITE_VULKAN_FILESYSTEM ShaderManager shader_manager; TextureManager texture_manager; + void init_shader_manager_cache(); + void flush_shader_manager_cache(); #endif #ifdef GRANITE_VULKAN_FOSSILIZE - Fossilize::StateRecorder state_recorder; bool enqueue_create_sampler(Fossilize::Hash hash, const VkSamplerCreateInfo *create_info, VkSampler *sampler) override; bool enqueue_create_descriptor_set_layout(Fossilize::Hash hash, const VkDescriptorSetLayoutCreateInfo *create_info, VkDescriptorSetLayout *layout) override; bool enqueue_create_pipeline_layout(Fossilize::Hash hash, const VkPipelineLayoutCreateInfo *create_info, VkPipelineLayout *layout) override; @@ -803,9 +818,10 @@ private: bool enqueue_create_compute_pipeline(Fossilize::Hash hash, const VkComputePipelineCreateInfo *create_info, VkPipeline *pipeline) override; bool enqueue_create_graphics_pipeline(Fossilize::Hash hash, const VkGraphicsPipelineCreateInfo *create_info, VkPipeline *pipeline) override; bool enqueue_create_raytracing_pipeline(Fossilize::Hash hash, const VkRayTracingPipelineCreateInfoKHR *create_info, VkPipeline *pipeline) override; - void notify_replayed_resources_for_type() override; - VkPipeline fossilize_create_graphics_pipeline(Fossilize::Hash hash, VkGraphicsPipelineCreateInfo &info); - VkPipeline fossilize_create_compute_pipeline(Fossilize::Hash hash, VkComputePipelineCreateInfo &info); + bool fossilize_replay_graphics_pipeline(Fossilize::Hash hash, VkGraphicsPipelineCreateInfo &info); + bool fossilize_replay_compute_pipeline(Fossilize::Hash hash, VkComputePipelineCreateInfo &info); + + void replay_tag_simple(Fossilize::ResourceTag tag); void register_graphics_pipeline(Fossilize::Hash hash, const VkGraphicsPipelineCreateInfo &info); void register_compute_pipeline(Fossilize::Hash hash, const VkComputePipelineCreateInfo &info); @@ -815,19 +831,21 @@ private: void register_shader_module(VkShaderModule module, Fossilize::Hash hash, const VkShaderModuleCreateInfo &info); //void register_sampler(VkSampler sampler, Fossilize::Hash hash, const VkSamplerCreateInfo &info); - struct - { - std::unordered_map shader_map; - std::unordered_map render_pass_map; - const Fossilize::FeatureFilter *feature_filter = nullptr; -#ifdef GRANITE_VULKAN_MT - // Need to forward-declare the type, avoid the ref-counted wrapper. - Granite::TaskGroup *pipeline_group = nullptr; -#endif - } replayer_state; + struct RecorderState; + std::unique_ptr recorder_state; - void init_pipeline_state(const Fossilize::FeatureFilter &filter); + struct ReplayerState; + std::unique_ptr replayer_state; + + void promote_write_cache_to_readonly() const; + void promote_readonly_db_from_assets() const; + + void init_pipeline_state(const Fossilize::FeatureFilter &filter, + const VkPhysicalDeviceFeatures2 &pdf2, + const VkApplicationInfo &application_info); void flush_pipeline_state(); + void block_until_shader_module_ready(); + void block_until_pipeline_ready(); #endif ImplementationWorkarounds workarounds; @@ -838,6 +856,8 @@ private: bool allocate_image_memory(DeviceAllocation *allocation, const ImageCreateInfo &info, VkImage image, VkImageTiling tiling); + + void promote_read_write_caches_to_read_only(); }; // A fairly complex helper used for async queue readbacks. diff --git a/parallel-rdp-standalone/vulkan/device_fossilize.cpp b/parallel-rdp-standalone/vulkan/device_fossilize.cpp index 9f68000..3c91fd7 100644 --- a/parallel-rdp-standalone/vulkan/device_fossilize.cpp +++ b/parallel-rdp-standalone/vulkan/device_fossilize.cpp @@ -20,12 +20,34 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "device.hpp" +#include "device_fossilize.hpp" #include "timer.hpp" #include "thread_group.hpp" +#include "fossilize_db.hpp" +#include "dynamic_array.hpp" namespace Vulkan { +Device::RecorderState::RecorderState() +{ + recorder_ready.store(false, std::memory_order_relaxed); +} + +Device::RecorderState::~RecorderState() +{ +} + +Device::ReplayerState::ReplayerState() +{ + progress.prepare.store(0, std::memory_order_relaxed); + progress.modules.store(0, std::memory_order_relaxed); + progress.pipelines.store(0, std::memory_order_relaxed); +} + +Device::ReplayerState::~ReplayerState() +{ +} + #if 0 void Device::register_sampler(VkSampler sampler, Fossilize::Hash hash, const VkSamplerCreateInfo &info) { @@ -36,100 +58,145 @@ void Device::register_sampler(VkSampler sampler, Fossilize::Hash hash, const VkS void Device::register_descriptor_set_layout(VkDescriptorSetLayout layout, Fossilize::Hash hash, const VkDescriptorSetLayoutCreateInfo &info) { - if (!state_recorder.record_descriptor_set_layout(layout, info, hash)) + if (!recorder_state) + return; + + if (!recorder_state->recorder_ready.load(std::memory_order_acquire)) + { + LOGW("Attempting to register descriptor set layout before recorder is ready.\n"); + return; + } + + if (recorder_state && !recorder_state->recorder.record_descriptor_set_layout(layout, info, hash)) LOGW("Failed to register descriptor set layout.\n"); } void Device::register_pipeline_layout(VkPipelineLayout layout, Fossilize::Hash hash, const VkPipelineLayoutCreateInfo &info) { - if (!state_recorder.record_pipeline_layout(layout, info, hash)) + if (!recorder_state) + return; + + if (!recorder_state->recorder_ready.load(std::memory_order_acquire)) + { + LOGW("Attempting to register pipeline layout before recorder is ready.\n"); + return; + } + + if (recorder_state && !recorder_state->recorder.record_pipeline_layout(layout, info, hash)) LOGW("Failed to register pipeline layout.\n"); } void Device::register_shader_module(VkShaderModule module, Fossilize::Hash hash, const VkShaderModuleCreateInfo &info) { - if (!state_recorder.record_shader_module(module, info, hash)) + if (!recorder_state) + return; + + if (!recorder_state->recorder_ready.load(std::memory_order_acquire)) + { + LOGW("Attempting to register shader module before recorder is ready.\n"); + return; + } + + if (!recorder_state->recorder.record_shader_module(module, info, hash)) LOGW("Failed to register shader module.\n"); } void Device::register_compute_pipeline(Fossilize::Hash hash, const VkComputePipelineCreateInfo &info) { - if (!state_recorder.record_compute_pipeline(VK_NULL_HANDLE, info, nullptr, 0, hash)) + if (!recorder_state) + return; + + if (!recorder_state->recorder_ready.load(std::memory_order_acquire)) + { + LOGW("Attempting to register compute pipeline before recorder is ready.\n"); + return; + } + + if (!recorder_state->recorder.record_compute_pipeline(VK_NULL_HANDLE, info, nullptr, 0, hash)) LOGW("Failed to register compute pipeline.\n"); } void Device::register_graphics_pipeline(Fossilize::Hash hash, const VkGraphicsPipelineCreateInfo &info) { - if (!state_recorder.record_graphics_pipeline(VK_NULL_HANDLE, info, nullptr, 0, hash)) + if (!recorder_state) + return; + + if (!recorder_state->recorder_ready.load(std::memory_order_acquire)) + { + LOGW("Attempting to register graphics pipeline before recorder is ready.\n"); + return; + } + + if (!recorder_state->recorder.record_graphics_pipeline(VK_NULL_HANDLE, info, nullptr, 0, hash)) LOGW("Failed to register graphics pipeline.\n"); } void Device::register_render_pass(VkRenderPass render_pass, Fossilize::Hash hash, const VkRenderPassCreateInfo &info) { - if (!state_recorder.record_render_pass(render_pass, info, hash)) + if (!recorder_state) + return; + + if (!recorder_state->recorder_ready.load(std::memory_order_acquire)) + { + LOGW("Attempting to register render pass before recorder is ready.\n"); + return; + } + + if (!recorder_state->recorder.record_render_pass(render_pass, info, hash)) LOGW("Failed to register render pass.\n"); } bool Device::enqueue_create_shader_module(Fossilize::Hash hash, const VkShaderModuleCreateInfo *create_info, VkShaderModule *module) { - if (!replayer_state.feature_filter->shader_module_is_supported(create_info)) + if (!replayer_state->feature_filter->shader_module_is_supported(create_info)) { *module = VK_NULL_HANDLE; + replayer_state->progress.modules.fetch_add(1, std::memory_order_release); return true; } ResourceLayout layout; - Shader *ret; // If we know the resource layout already, just reuse that. Avoids spinning up SPIRV-Cross reflection // and allows us to not even build it for release builds. if (shader_manager.get_resource_layout_by_shader_hash(hash, layout)) - ret = shaders.emplace_yield(hash, hash, this, create_info->pCode, create_info->codeSize, &layout); + shaders.emplace_yield(hash, hash, this, create_info->pCode, create_info->codeSize, &layout); else - ret = shaders.emplace_yield(hash, hash, this, create_info->pCode, create_info->codeSize); + shaders.emplace_yield(hash, hash, this, create_info->pCode, create_info->codeSize); - *module = ret->get_module(); - replayer_state.shader_map[*module] = ret; + // Resolve the handles later. + *module = (VkShaderModule)hash; + replayer_state->progress.modules.fetch_add(1, std::memory_order_release); return true; } -void Device::notify_replayed_resources_for_type() +bool Device::fossilize_replay_graphics_pipeline(Fossilize::Hash hash, VkGraphicsPipelineCreateInfo &info) { -#ifdef GRANITE_VULKAN_MT - if (replayer_state.pipeline_group) + if (info.stageCount != 2 || + info.pStages[0].stage != VK_SHADER_STAGE_VERTEX_BIT || + info.pStages[1].stage != VK_SHADER_STAGE_FRAGMENT_BIT) { - replayer_state.pipeline_group->wait(); - replayer_state.pipeline_group->release_reference(); - replayer_state.pipeline_group = nullptr; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); + return false; } -#endif -} -VkPipeline Device::fossilize_create_graphics_pipeline(Fossilize::Hash hash, VkGraphicsPipelineCreateInfo &info) -{ - if (info.stageCount != 2) - return VK_NULL_HANDLE; - if (info.pStages[0].stage != VK_SHADER_STAGE_VERTEX_BIT) - return VK_NULL_HANDLE; - if (info.pStages[1].stage != VK_SHADER_STAGE_FRAGMENT_BIT) - return VK_NULL_HANDLE; + auto *vert_shader = shaders.find((Fossilize::Hash)info.pStages[0].module); + auto *frag_shader = shaders.find((Fossilize::Hash)info.pStages[1].module); - // Find the Shader* associated with this VkShaderModule and just use that. - auto vertex_itr = replayer_state.shader_map.find(info.pStages[0].module); - if (vertex_itr == end(replayer_state.shader_map)) - return VK_NULL_HANDLE; + if (!vert_shader || !frag_shader) + { + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); + return false; + } - // Find the Shader* associated with this VkShaderModule and just use that. - auto fragment_itr = replayer_state.shader_map.find(info.pStages[1].module); - if (fragment_itr == end(replayer_state.shader_map)) - return VK_NULL_HANDLE; - - auto *ret = request_program(vertex_itr->second, fragment_itr->second); + auto *ret = request_program(vert_shader, frag_shader); // The layout is dummy, resolve it here. info.layout = ret->get_pipeline_layout()->get_layout(); - register_graphics_pipeline(hash, info); + // Resolve shader modules. + const_cast(info.pStages)[0].module = vert_shader->get_module(); + const_cast(info.pStages)[1].module = frag_shader->get_module(); #ifdef VULKAN_DEBUG LOGI("Replaying graphics pipeline.\n"); @@ -169,23 +236,37 @@ VkPipeline Device::fossilize_create_graphics_pipeline(Fossilize::Hash hash, VkGr VkPipeline pipeline = VK_NULL_HANDLE; VkResult res = table->vkCreateGraphicsPipelines(device, pipeline_cache, 1, &info, nullptr, &pipeline); if (res != VK_SUCCESS) + { LOGE("Failed to create graphics pipeline!\n"); - return ret->add_pipeline(hash, { pipeline, dynamic_state }).pipeline; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); + return false; + } + + auto actual_pipe = ret->add_pipeline(hash, { pipeline, dynamic_state }).pipeline; + if (actual_pipe != pipeline) + table->vkDestroyPipeline(device, pipeline, nullptr); + + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); + return actual_pipe != VK_NULL_HANDLE; } -VkPipeline Device::fossilize_create_compute_pipeline(Fossilize::Hash hash, VkComputePipelineCreateInfo &info) +bool Device::fossilize_replay_compute_pipeline(Fossilize::Hash hash, VkComputePipelineCreateInfo &info) { // Find the Shader* associated with this VkShaderModule and just use that. - auto itr = replayer_state.shader_map.find(info.stage.module); - if (itr == end(replayer_state.shader_map)) - return VK_NULL_HANDLE; + auto *shader = shaders.find((Fossilize::Hash)info.stage.module); + if (!shader) + { + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); + return false; + } - auto *ret = request_program(itr->second); + auto *ret = request_program(shader); // The layout is dummy, resolve it here. info.layout = ret->get_pipeline_layout()->get_layout(); - register_compute_pipeline(hash, info); + // Resolve shader module. + info.stage.module = shader->get_module(); #ifdef VULKAN_DEBUG LOGI("Replaying compute pipeline.\n"); @@ -193,8 +274,18 @@ VkPipeline Device::fossilize_create_compute_pipeline(Fossilize::Hash hash, VkCom VkPipeline pipeline = VK_NULL_HANDLE; VkResult res = table->vkCreateComputePipelines(device, pipeline_cache, 1, &info, nullptr, &pipeline); if (res != VK_SUCCESS) + { LOGE("Failed to create compute pipeline!\n"); - return ret->add_pipeline(hash, { pipeline, 0 }).pipeline; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); + return false; + } + + auto actual_pipe = ret->add_pipeline(hash, { pipeline, 0 }).pipeline; + if (actual_pipe != pipeline) + table->vkDestroyPipeline(device, pipeline, nullptr); + + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); + return actual_pipe != VK_NULL_HANDLE; } bool Device::enqueue_create_graphics_pipeline(Fossilize::Hash hash, @@ -206,103 +297,65 @@ bool Device::enqueue_create_graphics_pipeline(Fossilize::Hash hash, if (create_info->pStages[i].module == VK_NULL_HANDLE) { *pipeline = VK_NULL_HANDLE; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); return true; } } - if (create_info->renderPass == VK_NULL_HANDLE) + if (create_info->renderPass == VK_NULL_HANDLE || create_info->layout == VK_NULL_HANDLE) { *pipeline = VK_NULL_HANDLE; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); return true; } - if (!replayer_state.feature_filter->graphics_pipeline_is_supported(create_info)) + if (!replayer_state->feature_filter->graphics_pipeline_is_supported(create_info)) { *pipeline = VK_NULL_HANDLE; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); return true; } -#ifdef GRANITE_VULKAN_MT - if (!replayer_state.pipeline_group && get_system_handles().thread_group) - replayer_state.pipeline_group = get_system_handles().thread_group->create_task().release(); - - if (replayer_state.pipeline_group) - { - replayer_state.pipeline_group->enqueue_task([this, create_info, hash, pipeline]() { - // The lifetime of create_info is tied to the replayer itself. - auto tmp_create_info = *create_info; - *pipeline = fossilize_create_graphics_pipeline(hash, tmp_create_info); - }); - return true; - } - else - { - auto info = *create_info; - *pipeline = fossilize_create_graphics_pipeline(hash, info); - return *pipeline != VK_NULL_HANDLE; - } -#else - auto info = *create_info; - *pipeline = fossilize_create_graphics_pipeline(hash, info); - return *pipeline != VK_NULL_HANDLE; -#endif + // The lifetime of create_info is tied to the replayer itself. + replayer_state->graphics_pipelines.emplace_back(hash, const_cast(create_info)); + return true; } bool Device::enqueue_create_compute_pipeline(Fossilize::Hash hash, const VkComputePipelineCreateInfo *create_info, VkPipeline *pipeline) { - if (create_info->stage.module == VK_NULL_HANDLE) + if (create_info->stage.module == VK_NULL_HANDLE || create_info->layout == VK_NULL_HANDLE) { *pipeline = VK_NULL_HANDLE; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); return true; } - if (!replayer_state.feature_filter->compute_pipeline_is_supported(create_info)) + if (!replayer_state->feature_filter->compute_pipeline_is_supported(create_info)) { *pipeline = VK_NULL_HANDLE; + replayer_state->progress.pipelines.fetch_add(1, std::memory_order_release); return true; } -#ifdef GRANITE_VULKAN_MT - if (!replayer_state.pipeline_group && get_system_handles().thread_group) - replayer_state.pipeline_group = get_system_handles().thread_group->create_task().release(); - - if (replayer_state.pipeline_group) - { - replayer_state.pipeline_group->enqueue_task([this, create_info, hash, pipeline]() { - // The lifetime of create_info is tied to the replayer itself. - auto tmp_create_info = *create_info; - *pipeline = fossilize_create_compute_pipeline(hash, tmp_create_info); - }); - return true; - } - else - { - auto info = *create_info; - *pipeline = fossilize_create_compute_pipeline(hash, info); - return *pipeline != VK_NULL_HANDLE; - } -#else - auto info = *create_info; - *pipeline = fossilize_create_compute_pipeline(hash, info); - return *pipeline != VK_NULL_HANDLE; -#endif + // The lifetime of create_info is tied to the replayer itself. + replayer_state->compute_pipelines.emplace_back(hash, const_cast(create_info)); + return true; } bool Device::enqueue_create_render_pass(Fossilize::Hash hash, const VkRenderPassCreateInfo *create_info, VkRenderPass *render_pass) { - if (!replayer_state.feature_filter->render_pass_is_supported(create_info)) + if (!replayer_state->feature_filter->render_pass_is_supported(create_info)) { - *render_pass = VK_NULL_HANDLE; + render_pass = VK_NULL_HANDLE; return true; } - auto *ret = render_passes.emplace_yield(hash, hash, this, *create_info); - *render_pass = ret->get_render_pass(); - replayer_state.render_pass_map[*render_pass] = ret; + auto *pass = render_passes.emplace_yield(hash, hash, this, *create_info); + *render_pass = pass->get_render_pass(); return true; } @@ -324,83 +377,543 @@ bool Device::enqueue_create_sampler(Fossilize::Hash, const VkSamplerCreateInfo * return false; } -bool Device::enqueue_create_descriptor_set_layout(Fossilize::Hash, const VkDescriptorSetLayoutCreateInfo *, VkDescriptorSetLayout *layout) +bool Device::enqueue_create_descriptor_set_layout(Fossilize::Hash, const VkDescriptorSetLayoutCreateInfo *info, VkDescriptorSetLayout *layout) { + if (!replayer_state->feature_filter->descriptor_set_layout_is_supported(info)) + { + *layout = VK_NULL_HANDLE; + return true; + } + // We will create this naturally when building pipelines, can just emit dummy handles. *layout = (VkDescriptorSetLayout) uint64_t(-1); return true; } -bool Device::enqueue_create_pipeline_layout(Fossilize::Hash, const VkPipelineLayoutCreateInfo *, VkPipelineLayout *layout) +bool Device::enqueue_create_pipeline_layout(Fossilize::Hash, const VkPipelineLayoutCreateInfo *info, VkPipelineLayout *layout) { + if (!replayer_state->feature_filter->pipeline_layout_is_supported(info)) + { + *layout = VK_NULL_HANDLE; + return true; + } + // We will create this naturally when building pipelines, can just emit dummy handles. *layout = (VkPipelineLayout) uint64_t(-1); return true; } -void Device::init_pipeline_state(const Fossilize::FeatureFilter &filter) +void Device::promote_readonly_db_from_assets() const +{ + auto *fs = get_system_handles().filesystem; + + // We might want to be able to ship a Fossilize database so that we can prime all PSOs up front. + Granite::FileStat s_cache = {}; + Granite::FileStat s_assets = {}; + bool cache_exists = fs->stat("cache://fossilize/db.foz", s_cache) && s_cache.type == Granite::PathType::File; + bool assets_exists = fs->stat("assets://fossilize/db.foz", s_assets) && s_assets.type == Granite::PathType::File; + + bool overwrite = false; + if (assets_exists) + { + if (!cache_exists) + { + overwrite = true; + } + else + { + // If an application updates the assets Foz DB for shipping updates, throw the old one away. + std::string cache_iter, asset_iter; + if (!fs->read_file_to_string("cache://fossilize/iteration", cache_iter) || + !fs->read_file_to_string("assets://fossilize/iteration", asset_iter) || + cache_iter != asset_iter) + { + overwrite = true; + } + } + } + + if (overwrite) + { + // The Fossilize DB needs to work with a proper file system. The assets folder is highly virtual by nature. + auto ro = fs->open_readonly_mapping("assets://fossilize/db.foz"); + if (!ro) + { + LOGE("Failed to open readonly Fossilize archive.\n"); + return; + } + + if (!fs->write_buffer_to_file("cache://fossilize/db.foz", ro->data(), ro->get_size())) + { + LOGE("Failed to write to cache://fossilize/db.foz"); + return; + } + + std::string asset_iter; + if (fs->read_file_to_string("assets://fossilize/iteration", asset_iter)) + fs->write_string_to_file("cache://fossilize/iteration", asset_iter); + } +} + +void Device::replay_tag_simple(Fossilize::ResourceTag tag) +{ + size_t count = 0; + replayer_state->db->get_hash_list_for_resource_tag(tag, &count, nullptr); + std::vector hashes(count); + replayer_state->db->get_hash_list_for_resource_tag(tag, &count, hashes.data()); + + Util::DynamicArray buffer; + auto &db = *replayer_state->db; + size_t size = 0; + + for (auto hash : hashes) + { + if (!db.read_entry(tag, hash, &size, nullptr, 0)) + continue; + buffer.reserve(size); + if (!db.read_entry(tag, hash, &size, buffer.data(), 0)) + continue; + if (!replayer_state->base_replayer.parse(*this, &db, buffer.data(), size)) + LOGW("Failed to replay object.\n"); + } +} + +void Device::promote_write_cache_to_readonly() const +{ + auto *fs = get_system_handles().filesystem; + auto list = fs->list("cache://fossilize"); + std::vector merge_paths_str; + std::vector del_paths_str; + std::vector merge_paths; + merge_paths_str.reserve(list.size()); + merge_paths.reserve(list.size()); + bool have_read_only = false; + + for (auto &l : list) + { + if (l.type != Granite::PathType::File || l.path == "fossilize/iteration" || l.path == "fossilize/TOUCH") + continue; + else if (l.path == "fossilize/db.foz") + { + have_read_only = true; + LOGI("Fossilize: Found read-only cache.\n"); + continue; + } + else if (l.path == "fossilize/merge.foz") + { + del_paths_str.emplace_back("cache://fossilize/merge.foz"); + continue; + } + + auto p = "cache://" + l.path; + merge_paths_str.push_back(p); + del_paths_str.push_back(p); + LOGI("Fossilize: Found write cache: %s.\n", merge_paths_str.back().c_str()); + } + + if (!have_read_only && merge_paths_str.size() == 1) + { + LOGI("Fossilize: No read-cache and one write cache. Replacing directly.\n"); + if (fs->move_replace("cache://fossilize/db.foz", merge_paths_str.front())) + LOGI("Fossilize: Promoted write-only cache.\n"); + else + LOGW("Fossilize: Failed to promote write-only cache.\n"); + } + else if (!merge_paths_str.empty()) + { + auto append_path = fs->get_filesystem_path("cache://fossilize/merge.foz"); + bool should_merge; + + // Ensure that we have taken exclusive write access to this file. + // Only one process will be able to pass this test until the file is removed. + if (have_read_only) + { + LOGI("Fossilize: Attempting to merge caches.\n"); + should_merge = fs->move_yield("cache://fossilize/merge.foz", "cache://fossilize/db.foz"); + } + else + { + auto db = std::unique_ptr( + Fossilize::create_stream_archive_database(append_path.c_str(), Fossilize::DatabaseMode::ExclusiveOverWrite)); + should_merge = db && db->prepare(); + } + + if (should_merge) + { + for (auto &str : merge_paths_str) + { + str = fs->get_filesystem_path(str); + merge_paths.push_back(str.c_str()); + } + + if (Fossilize::merge_concurrent_databases(append_path.c_str(), merge_paths.data(), merge_paths.size())) + { + if (fs->move_replace("cache://fossilize/db.foz", "cache://fossilize/merge.foz")) + LOGI("Fossilize: Successfully merged caches.\n"); + else + LOGW("Fossilize: Failed to replace existing read-only database.\n"); + } + else + LOGW("Fossilize: Failed to merge databases.\n"); + } + else + LOGW("Fossilize: Skipping merge due to unexpected error.\n"); + } + else + LOGI("Fossilize: No write only files, nothing to do.\n"); + + // Cleanup any stale write-only files. + // This can easily race against concurrent processes, so the cache will likely be destroyed by accident, + // but that's ok. Running multiple Granite processes concurrently like this is questionable at best. + for (auto &str : del_paths_str) + fs->remove(str); +} + +void Device::init_pipeline_state(const Fossilize::FeatureFilter &filter, + const VkPhysicalDeviceFeatures2 &pdf2, + const VkApplicationInfo &application_info) { - replayer_state.feature_filter = &filter; - state_recorder.init_recording_thread(nullptr); if (!get_system_handles().filesystem) - return; - - auto file = get_system_handles().filesystem->open_readonly_mapping("assets://pipelines.json"); - if (!file) - file = get_system_handles().filesystem->open_readonly_mapping("cache://pipelines.json"); - - if (!file) - return; - - const void *mapped = file->data(); - if (!mapped) { - LOGE("Failed to map pipelines.json.\n"); + LOGW("Filesystem system handle must be provided to use Fossilize.\n"); return; } - LOGI("Replaying cached state.\n"); - Fossilize::StateReplayer replayer; - auto start = Util::get_current_time_nsecs(); - if (!replayer.parse(*this, nullptr, static_cast(mapped), file->get_size())) - LOGE("Failed to parse Fossilize archive.\n"); - auto end = Util::get_current_time_nsecs(); - LOGI("Completed replaying cached state in %.3f ms.\n", (end - start) * 1e-6); - - if (replayer_state.pipeline_group) + if (!get_system_handles().thread_group) { - replayer_state.pipeline_group->wait(); - replayer_state.pipeline_group->release_reference(); + LOGW("Thread group system handle must be provided to use Fossilize.\n"); + return; } - replayer_state = {}; - promote_read_write_caches_to_read_only(); + + replayer_state.reset(new ReplayerState); + recorder_state.reset(new RecorderState); + + if (!recorder_state->recorder.record_application_info(application_info)) + LOGW("Failed to record application info.\n"); + if (!recorder_state->recorder.record_physical_device_features(&pdf2)) + LOGW("Failed to record PDF2.\n"); + + lock.read_only_cache.lock_read(); + + replayer_state->feature_filter = &filter; + auto *group = get_system_handles().thread_group; + + auto shader_manager_task = group->create_task([this]() { + init_shader_manager_cache(); + }); + shader_manager_task->set_desc("shader-manager-init"); + + auto cache_maintenance_task = group->create_task([this]() { + // Ensure we create the Fossilize cache folder. + // Also creates a timestamp. + get_system_handles().filesystem->open("cache://fossilize/TOUCH", Granite::FileMode::WriteOnly); + replayer_state->progress.prepare.fetch_add(20, std::memory_order_release); + promote_write_cache_to_readonly(); + replayer_state->progress.prepare.fetch_add(50, std::memory_order_release); + promote_readonly_db_from_assets(); + replayer_state->progress.prepare.fetch_add(20, std::memory_order_release); + }); + cache_maintenance_task->set_desc("foz-cache-maintenance"); + + auto recorder_kick_task = group->create_task([this]() { + // Kick off recorder thread. + auto write_real_path = get_system_handles().filesystem->get_filesystem_path("cache://fossilize/db"); + if (!write_real_path.empty()) + { + recorder_state->db.reset(Fossilize::create_concurrent_database( + write_real_path.c_str(), Fossilize::DatabaseMode::Append, nullptr, 0)); + recorder_state->recorder.set_database_enable_application_feature_links(false); + recorder_state->recorder.init_recording_thread(recorder_state->db.get()); + } + recorder_state->recorder_ready.store(true, std::memory_order_release); + replayer_state->progress.prepare.fetch_add(10, std::memory_order_release); + }); + recorder_kick_task->set_desc("foz-recorder-kick"); + + group->add_dependency(*recorder_kick_task, *cache_maintenance_task); + + auto prepare_task = group->create_task([this]() { + auto *fs = get_system_handles().filesystem; + auto read_real_path = fs->get_filesystem_path("cache://fossilize/db.foz"); + if (read_real_path.empty()) + { + replayer_state->progress.modules.store(~0u, std::memory_order_release); + replayer_state->progress.pipelines.store(~0u, std::memory_order_release); + return; + } + + replayer_state->db.reset( + Fossilize::create_stream_archive_database(read_real_path.c_str(), Fossilize::DatabaseMode::ReadOnly)); + + if (replayer_state->db && !replayer_state->db->prepare()) + { + LOGW("Failed to prepare read-only cache.\n"); + replayer_state->db.reset(); + } + + if (replayer_state->db) + { + replay_tag_simple(Fossilize::RESOURCE_DESCRIPTOR_SET_LAYOUT); + replay_tag_simple(Fossilize::RESOURCE_PIPELINE_LAYOUT); + replay_tag_simple(Fossilize::RESOURCE_RENDER_PASS); + + size_t count = 0; + + replayer_state->db->get_hash_list_for_resource_tag(Fossilize::RESOURCE_SHADER_MODULE, &count, nullptr); + replayer_state->module_hashes.resize(count); + replayer_state->db->get_hash_list_for_resource_tag(Fossilize::RESOURCE_SHADER_MODULE, &count, + replayer_state->module_hashes.data()); + + replayer_state->db->get_hash_list_for_resource_tag(Fossilize::RESOURCE_GRAPHICS_PIPELINE, &count, nullptr); + replayer_state->graphics_hashes.resize(count); + replayer_state->db->get_hash_list_for_resource_tag(Fossilize::RESOURCE_GRAPHICS_PIPELINE, &count, + replayer_state->graphics_hashes.data()); + + replayer_state->db->get_hash_list_for_resource_tag(Fossilize::RESOURCE_COMPUTE_PIPELINE, &count, nullptr); + replayer_state->compute_hashes.resize(count); + replayer_state->db->get_hash_list_for_resource_tag(Fossilize::RESOURCE_COMPUTE_PIPELINE, &count, + replayer_state->compute_hashes.data()); + + replayer_state->progress.num_modules = replayer_state->module_hashes.size(); + replayer_state->progress.num_pipelines = + replayer_state->graphics_hashes.size() + replayer_state->compute_hashes.size(); + } + + if (replayer_state->progress.num_modules == 0) + replayer_state->progress.modules.store(~0u, std::memory_order_release); + if (replayer_state->progress.num_pipelines == 0) + replayer_state->progress.pipelines.store(~0u, std::memory_order_release); + }); + prepare_task->set_desc("foz-prepare"); + + group->add_dependency(*prepare_task, *cache_maintenance_task); + + auto parse_modules_task = group->create_task(); + parse_modules_task->set_desc("foz-parse-modules"); + group->add_dependency(*parse_modules_task, *prepare_task); + group->add_dependency(*parse_modules_task, *shader_manager_task); + + for (unsigned i = 0; i < NumTasks; i++) + { + parse_modules_task->enqueue_task([this, i]() { + if (!replayer_state->db) + return; + + Fossilize::StateReplayer module_replayer; + Util::DynamicArray buffer; + auto &db = *replayer_state->db; + + size_t start = (i * replayer_state->module_hashes.size()) / NumTasks; + size_t end = ((i + 1) * replayer_state->module_hashes.size()) / NumTasks; + size_t size = 0; + + for (; start < end; start++) + { + auto hash = replayer_state->module_hashes[start]; + if (!db.read_entry(Fossilize::RESOURCE_SHADER_MODULE, hash, &size, nullptr, Fossilize::PAYLOAD_READ_CONCURRENT_BIT)) + continue; + buffer.reserve(size); + if (!db.read_entry(Fossilize::RESOURCE_SHADER_MODULE, hash, &size, buffer.data(), Fossilize::PAYLOAD_READ_CONCURRENT_BIT)) + continue; + + if (!module_replayer.parse(*this, &db, buffer.data(), size)) + LOGW("Failed to parse module.\n"); + } + }); + } + + auto parse_graphics_task = group->create_task([this]() { + if (!replayer_state->db) + return; + + auto &replayer = replayer_state->graphics_replayer; + replayer.copy_handle_references(replayer_state->base_replayer); + replayer.set_resolve_shader_module_handles(false); + + size_t size = 0; + auto &db = *replayer_state->db; + Util::DynamicArray buffer; + + for (auto hash : replayer_state->graphics_hashes) + { + if (!db.read_entry(Fossilize::RESOURCE_GRAPHICS_PIPELINE, hash, &size, nullptr, Fossilize::PAYLOAD_READ_CONCURRENT_BIT)) + continue; + buffer.reserve(size); + if (!db.read_entry(Fossilize::RESOURCE_GRAPHICS_PIPELINE, hash, &size, buffer.data(), Fossilize::PAYLOAD_READ_CONCURRENT_BIT)) + continue; + + if (!replayer.parse(*this, &db, buffer.data(), size)) + LOGW("Failed to parse graphics pipeline.\n"); + } + }); + parse_graphics_task->set_desc("foz-parse-graphics"); + group->add_dependency(*parse_graphics_task, *prepare_task); + + auto parse_compute_task = group->create_task([this]() { + if (!replayer_state->db) + return; + + auto &replayer = replayer_state->compute_replayer; + replayer.copy_handle_references(replayer_state->base_replayer); + replayer.set_resolve_shader_module_handles(false); + + size_t size = 0; + auto &db = *replayer_state->db; + Util::DynamicArray buffer; + for (auto hash : replayer_state->compute_hashes) + { + if (!db.read_entry(Fossilize::RESOURCE_COMPUTE_PIPELINE, hash, &size, nullptr, Fossilize::PAYLOAD_READ_CONCURRENT_BIT)) + continue; + buffer.reserve(size); + if (!db.read_entry(Fossilize::RESOURCE_COMPUTE_PIPELINE, hash, &size, buffer.data(), Fossilize::PAYLOAD_READ_CONCURRENT_BIT)) + continue; + + if (!replayer.parse(*this, &db, buffer.data(), size)) + LOGW("Failed to parse compute pipeline.\n"); + } + }); + parse_compute_task->set_desc("foz-parse-compute"); + group->add_dependency(*parse_compute_task, *prepare_task); + + auto compile_graphics_task = group->create_task(); + auto compile_compute_task = group->create_task(); + compile_graphics_task->set_desc("foz-compile-graphics"); + compile_compute_task->set_desc("foz-compile-compute"); + group->add_dependency(*compile_graphics_task, *parse_modules_task); + group->add_dependency(*compile_graphics_task, *parse_graphics_task); + group->add_dependency(*compile_compute_task, *parse_modules_task); + group->add_dependency(*compile_compute_task, *parse_compute_task); + for (unsigned i = 0; i < NumTasks; i++) + { + compile_graphics_task->enqueue_task([this, i]() { + size_t start = (i * replayer_state->graphics_pipelines.size()) / NumTasks; + size_t end = ((i + 1) * replayer_state->graphics_pipelines.size()) / NumTasks; + for (; start < end; start++) + { + auto &pipe = replayer_state->graphics_pipelines[start]; + fossilize_replay_graphics_pipeline(pipe.first, *pipe.second); + } + }); + + compile_compute_task->enqueue_task([this, i]() { + size_t start = (i * replayer_state->compute_pipelines.size()) / NumTasks; + size_t end = ((i + 1) * replayer_state->compute_pipelines.size()) / NumTasks; + for (; start < end; start++) + { + auto &pipe = replayer_state->compute_pipelines[start]; + fossilize_replay_compute_pipeline(pipe.first, *pipe.second); + } + }); + } + + replayer_state->complete = get_system_handles().thread_group->create_task([this]() { + LOGI("Fossilize replay completed!\n Modules: %zu\n Graphics: %zu\n Compute: %zu\n", + replayer_state->module_hashes.size(), + replayer_state->graphics_hashes.size(), + replayer_state->compute_hashes.size()); + lock.read_only_cache.unlock_read(); + const auto cleanup = [](Fossilize::StateReplayer &r) { + r.forget_handle_references(); + r.forget_pipeline_handle_references(); + r.get_allocator().reset(); + }; + cleanup(replayer_state->base_replayer); + cleanup(replayer_state->graphics_replayer); + cleanup(replayer_state->compute_replayer); + replayer_state->graphics_pipelines.clear(); + replayer_state->compute_pipelines.clear(); + replayer_state->module_hashes.clear(); + replayer_state->graphics_hashes.clear(); + replayer_state->compute_hashes.clear(); + replayer_state->db.reset(); + }); + replayer_state->complete->set_desc("foz-replay-complete"); + group->add_dependency(*replayer_state->complete, *compile_graphics_task); + group->add_dependency(*replayer_state->complete, *compile_compute_task); + group->add_dependency(*replayer_state->complete, *recorder_kick_task); + replayer_state->complete->flush(); + + replayer_state->module_ready = std::move(parse_modules_task); + replayer_state->module_ready->flush(); + + auto compile_task = group->create_task(); + group->add_dependency(*compile_task, *compile_graphics_task); + group->add_dependency(*compile_task, *compile_compute_task); + replayer_state->pipeline_ready = std::move(compile_task); + replayer_state->pipeline_ready->flush(); } void Device::flush_pipeline_state() { - if (!get_system_handles().filesystem) - return; - - uint8_t *serialized = nullptr; - size_t serialized_size = 0; - if (!state_recorder.serialize(&serialized, &serialized_size)) + if (replayer_state) { - LOGE("Failed to serialize Fossilize state.\n"); - return; + if (replayer_state->complete) + replayer_state->complete->wait(); + replayer_state.reset(); } - auto file = get_system_handles().filesystem->open("cache://pipelines.json", - Granite::FileMode::WriteOnlyTransactional); - if (file) + if (recorder_state) { - auto mapping = file->map_write(serialized_size); - auto *data = mapping ? mapping->mutable_data() : nullptr; - if (data) - memcpy(data, serialized, serialized_size); - else - LOGE("Failed to serialize pipeline data.\n"); - + recorder_state->recorder.tear_down_recording_thread(); + recorder_state.reset(); } - Fossilize::StateRecorder::free_serialized(serialized); +} + +unsigned Device::query_initialization_progress(InitializationStage status) const +{ + if (!replayer_state) + return 100; + + switch (status) + { + case InitializationStage::CacheMaintenance: + return replayer_state->progress.prepare.load(std::memory_order_acquire); + + case InitializationStage::ShaderModules: + { + unsigned done = replayer_state->progress.modules.load(std::memory_order_acquire); + // Avoid 0/0. + if (!done) + return 0; + else if (done == ~0u) + return 100; + return (100u * done) / replayer_state->progress.num_modules; + } + + case InitializationStage::Pipelines: + { + unsigned done = replayer_state->progress.pipelines.load(std::memory_order_acquire); + // Avoid 0/0. + if (!done) + return 0; + else if (done == ~0u) + return 100; + return (100u * done) / replayer_state->progress.num_pipelines; + } + + default: + break; + } + + return 0; +} + +void Device::block_until_shader_module_ready() +{ + if (!replayer_state || !replayer_state->module_ready) + return; + replayer_state->module_ready->wait(); +} + +void Device::block_until_pipeline_ready() +{ + if (!replayer_state || !replayer_state->pipeline_ready) + return; + replayer_state->pipeline_ready->wait(); +} + +void Device::wait_shader_caches() +{ + block_until_pipeline_ready(); } } diff --git a/parallel-rdp-standalone/vulkan/device_fossilize.hpp b/parallel-rdp-standalone/vulkan/device_fossilize.hpp new file mode 100644 index 0000000..a058301 --- /dev/null +++ b/parallel-rdp-standalone/vulkan/device_fossilize.hpp @@ -0,0 +1,70 @@ +/* Copyright (c) 2017-2022 Hans-Kristian Arntzen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include "device.hpp" +#include "thread_group.hpp" + +namespace Vulkan +{ +struct Device::RecorderState +{ + RecorderState(); + ~RecorderState(); + + std::unique_ptr db; + Fossilize::StateRecorder recorder; + std::atomic_bool recorder_ready; +}; + +static constexpr unsigned NumTasks = 4; +struct Device::ReplayerState +{ + ReplayerState(); + ~ReplayerState(); + + std::vector module_hashes; + std::vector graphics_hashes; + std::vector compute_hashes; + + Fossilize::StateReplayer base_replayer; + Fossilize::StateReplayer graphics_replayer; + Fossilize::StateReplayer compute_replayer; + const Fossilize::FeatureFilter *feature_filter = nullptr; + std::unique_ptr db; + Granite::TaskGroupHandle complete; + Granite::TaskGroupHandle module_ready; + Granite::TaskGroupHandle pipeline_ready; + std::vector> graphics_pipelines; + std::vector> compute_pipelines; + + struct + { + std::atomic_uint32_t pipelines; + std::atomic_uint32_t modules; + std::atomic_uint32_t prepare; + uint32_t num_pipelines = 0; + uint32_t num_modules = 0; + } progress; +}; +} diff --git a/parallel-rdp-standalone/vulkan/fence.cpp b/parallel-rdp-standalone/vulkan/fence.cpp index 3baaaf8..d9e9c63 100644 --- a/parallel-rdp-standalone/vulkan/fence.cpp +++ b/parallel-rdp-standalone/vulkan/fence.cpp @@ -36,7 +36,7 @@ FenceHolder::~FenceHolder() } } -VkFence FenceHolder::get_fence() const +const VkFence &FenceHolder::get_fence() const { return fence; } @@ -45,11 +45,10 @@ void FenceHolder::wait() { auto &table = device->get_device_table(); -#ifdef GRANITE_VULKAN_MT // Waiting for the same VkFence in parallel is not allowed, and there seems to be some shenanigans on Intel // when waiting for a timeline semaphore in parallel with same value as well. std::lock_guard holder{lock}; -#endif + if (observed_wait) return; diff --git a/parallel-rdp-standalone/vulkan/fence.hpp b/parallel-rdp-standalone/vulkan/fence.hpp index c6901b2..45ece5a 100644 --- a/parallel-rdp-standalone/vulkan/fence.hpp +++ b/parallel-rdp-standalone/vulkan/fence.hpp @@ -26,9 +26,7 @@ #include "vulkan_headers.hpp" #include "object_pool.hpp" #include "cookie.hpp" -#ifdef GRANITE_VULKAN_MT #include -#endif namespace Vulkan { @@ -69,16 +67,14 @@ private: VK_ASSERT(value > 0); } - VkFence get_fence() const; + const VkFence &get_fence() const; Device *device; VkFence fence; VkSemaphore timeline_semaphore; uint64_t timeline_value; bool observed_wait = false; -#ifdef GRANITE_VULKAN_MT std::mutex lock; -#endif }; using Fence = Util::IntrusivePtr; diff --git a/parallel-rdp-standalone/vulkan/format.hpp b/parallel-rdp-standalone/vulkan/format.hpp index d02ebba..c0c1b47 100644 --- a/parallel-rdp-standalone/vulkan/format.hpp +++ b/parallel-rdp-standalone/vulkan/format.hpp @@ -251,7 +251,7 @@ static inline VkDeviceSize format_get_layer_size(VkFormat format, VkImageAspectF format_num_blocks(format, blocks_x, blocks_y); format_align_dim(format, width, height); - VkDeviceSize size = TextureFormatLayout::format_block_size(format, aspect) * depth * blocks_x * blocks_y; + VkDeviceSize size = VkDeviceSize(TextureFormatLayout::format_block_size(format, aspect)) * depth * blocks_x * blocks_y; return size; } diff --git a/parallel-rdp-standalone/vulkan/image.hpp b/parallel-rdp-standalone/vulkan/image.hpp index 8496902..644c413 100644 --- a/parallel-rdp-standalone/vulkan/image.hpp +++ b/parallel-rdp-standalone/vulkan/image.hpp @@ -117,7 +117,7 @@ static inline VkAccessFlags image_usage_to_possible_access(VkImageUsageFlags usa static inline uint32_t image_num_miplevels(const VkExtent3D &extent) { - uint32_t size = std::max(std::max(extent.width, extent.height), extent.depth); + uint32_t size = std::max(std::max(extent.width, extent.height), extent.depth); return Util::floor_log2(size) + 1; } @@ -495,17 +495,17 @@ public: uint32_t get_width(uint32_t lod = 0) const { - return std::max(1u, create_info.width >> lod); + return std::max(1u, create_info.width >> lod); } uint32_t get_height(uint32_t lod = 0) const { - return std::max(1u, create_info.height >> lod); + return std::max(1u, create_info.height >> lod); } uint32_t get_depth(uint32_t lod = 0) const { - return std::max(1u, create_info.depth >> lod); + return std::max(1u, create_info.depth >> lod); } const ImageCreateInfo &get_create_info() const diff --git a/parallel-rdp-standalone/vulkan/memory_allocator.cpp b/parallel-rdp-standalone/vulkan/memory_allocator.cpp index bbd1416..d0e2c1a 100644 --- a/parallel-rdp-standalone/vulkan/memory_allocator.cpp +++ b/parallel-rdp-standalone/vulkan/memory_allocator.cpp @@ -21,6 +21,7 @@ */ #include "memory_allocator.hpp" +#include "timeline_trace_file.hpp" #include "device.hpp" #include @@ -697,7 +698,11 @@ bool DeviceAllocator::internal_allocate( } VkDeviceMemory device_memory; - VkResult res = table->vkAllocateMemory(device->get_device(), &info, nullptr, &device_memory); + VkResult res; + { + GRANITE_SCOPED_TIMELINE_EVENT_FILE(device->get_system_handles().timeline_trace_file, "vkAllocateMemory"); + res = table->vkAllocateMemory(device->get_device(), &info, nullptr, &device_memory); + } // If we're importing, make sure we consume the native handle. if (external && bool(*external) && @@ -736,7 +741,11 @@ bool DeviceAllocator::internal_allocate( { table->vkFreeMemory(device->get_device(), block_itr->memory, nullptr); heap.size -= block_itr->size; - res = table->vkAllocateMemory(device->get_device(), &info, nullptr, &device_memory); + { + GRANITE_SCOPED_TIMELINE_EVENT_FILE(device->get_system_handles().timeline_trace_file, + "vkAllocateMemory"); + res = table->vkAllocateMemory(device->get_device(), &info, nullptr, &device_memory); + } ++block_itr; } diff --git a/parallel-rdp-standalone/vulkan/render_pass.cpp b/parallel-rdp-standalone/vulkan/render_pass.cpp index dd137c5..a192ebf 100644 --- a/parallel-rdp-standalone/vulkan/render_pass.cpp +++ b/parallel-rdp-standalone/vulkan/render_pass.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "render_pass.hpp" #include "stack_allocator.hpp" #include "device.hpp" @@ -29,11 +30,7 @@ using namespace Util; -#ifdef GRANITE_VULKAN_MT #define LOCK() std::lock_guard holder__{lock} -#else -#define LOCK() ((void)0) -#endif namespace Vulkan { diff --git a/parallel-rdp-standalone/vulkan/render_pass.hpp b/parallel-rdp-standalone/vulkan/render_pass.hpp index 10600a1..de2b4b0 100644 --- a/parallel-rdp-standalone/vulkan/render_pass.hpp +++ b/parallel-rdp-standalone/vulkan/render_pass.hpp @@ -233,9 +233,7 @@ private: Device *device; Util::TemporaryHashmap framebuffers; -#ifdef GRANITE_VULKAN_MT std::mutex lock; -#endif }; class TransientAttachmentAllocator @@ -265,9 +263,7 @@ private: Device *device; Util::TemporaryHashmap attachments; -#ifdef GRANITE_VULKAN_MT std::mutex lock; -#endif }; } diff --git a/parallel-rdp-standalone/vulkan/semaphore.cpp b/parallel-rdp-standalone/vulkan/semaphore.cpp index 182b5f7..aa53930 100644 --- a/parallel-rdp-standalone/vulkan/semaphore.cpp +++ b/parallel-rdp-standalone/vulkan/semaphore.cpp @@ -46,15 +46,37 @@ void SemaphoreHolder::recycle_semaphore() if (internal_sync) { - if (semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE_KHR || external_compatible_features || is_signalled()) + if (semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE_KHR || external_compatible_features) + { device->destroy_semaphore_nolock(semaphore); + } + else if (is_signalled()) + { + // We can't just destroy a semaphore if we don't know who signals it (e.g. WSI). + // Have to consume it by waiting then recycle. + if (signal_is_foreign_queue) + device->consume_semaphore_nolock(semaphore); + else + device->destroy_semaphore_nolock(semaphore); + } else device->recycle_semaphore_nolock(semaphore); } else { - if (semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE_KHR || external_compatible_features || is_signalled()) + if (semaphore_type == VK_SEMAPHORE_TYPE_TIMELINE_KHR || external_compatible_features) + { device->destroy_semaphore(semaphore); + } + else if (is_signalled()) + { + // We can't just destroy a semaphore if we don't know who signals it (e.g. WSI). + // Have to consume it by waiting then recycle. + if (signal_is_foreign_queue) + device->consume_semaphore(semaphore); + else + device->destroy_semaphore(semaphore); + } else device->recycle_semaphore(semaphore); } diff --git a/parallel-rdp-standalone/vulkan/semaphore.hpp b/parallel-rdp-standalone/vulkan/semaphore.hpp index 95d4ff2..8c682d6 100644 --- a/parallel-rdp-standalone/vulkan/semaphore.hpp +++ b/parallel-rdp-standalone/vulkan/semaphore.hpp @@ -95,6 +95,12 @@ public: signalled = true; } + void set_signal_is_foreign_queue() + { + VK_ASSERT(signalled); + signal_is_foreign_queue = true; + } + void set_pending_wait() { pending_wait = true; @@ -189,6 +195,7 @@ private: bool pending_wait = false; bool owned = false; bool proxy_timeline = false; + bool signal_is_foreign_queue = false; VkExternalSemaphoreHandleTypeFlagBits external_compatible_handle_type = {}; VkExternalSemaphoreFeatureFlags external_compatible_features = 0; }; diff --git a/parallel-rdp-standalone/vulkan/shader.cpp b/parallel-rdp-standalone/vulkan/shader.cpp index 396680e..0f23e69 100644 --- a/parallel-rdp-standalone/vulkan/shader.cpp +++ b/parallel-rdp-standalone/vulkan/shader.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "shader.hpp" #include "device.hpp" #ifdef GRANITE_VULKAN_SPIRV_CROSS @@ -620,21 +621,14 @@ void Program::destroy_pipeline(const Pipeline &pipeline) void Program::promote_read_write_to_read_only() { -#ifdef GRANITE_VULKAN_MT pipelines.move_to_read_only(); -#endif } Program::~Program() { -#ifdef GRANITE_VULKAN_MT for (auto &pipe : pipelines.get_read_only()) destroy_pipeline(pipe.get()); for (auto &pipe : pipelines.get_read_write()) destroy_pipeline(pipe.get()); -#else - for (auto &pipe : pipelines) - destroy_pipeline(pipe.get()); -#endif } } diff --git a/parallel-rdp-standalone/vulkan/texture_format.cpp b/parallel-rdp-standalone/vulkan/texture_format.cpp index f5e39e1..050e2b3 100644 --- a/parallel-rdp-standalone/vulkan/texture_format.cpp +++ b/parallel-rdp-standalone/vulkan/texture_format.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "texture_format.hpp" #include "format.hpp" #include diff --git a/parallel-rdp-standalone/vulkan/texture_format.hpp b/parallel-rdp-standalone/vulkan/texture_format.hpp index cf97d97..46c96b1 100644 --- a/parallel-rdp-standalone/vulkan/texture_format.hpp +++ b/parallel-rdp-standalone/vulkan/texture_format.hpp @@ -65,12 +65,12 @@ public: inline size_t get_row_size(uint32_t mip) const { - return mips[mip].block_row_length * block_stride; + return size_t(mips[mip].block_row_length) * block_stride; } inline size_t get_layer_size(uint32_t mip) const { - return mips[mip].block_image_height * get_row_size(mip); + return size_t(mips[mip].block_image_height) * get_row_size(mip); } struct MipInfo diff --git a/parallel-rdp-standalone/vulkan/vulkan_common.hpp b/parallel-rdp-standalone/vulkan/vulkan_common.hpp index 18fc552..d940de7 100644 --- a/parallel-rdp-standalone/vulkan/vulkan_common.hpp +++ b/parallel-rdp-standalone/vulkan/vulkan_common.hpp @@ -29,27 +29,14 @@ namespace Vulkan { -#ifdef GRANITE_VULKAN_MT using HandleCounter = Util::MultiThreadCounter; -#else -using HandleCounter = Util::SingleThreadCounter; -#endif -#ifdef GRANITE_VULKAN_MT template using VulkanObjectPool = Util::ThreadSafeObjectPool; template using VulkanCache = Util::ThreadSafeIntrusiveHashMapReadCached; template using VulkanCacheReadWrite = Util::ThreadSafeIntrusiveHashMap; -#else -template -using VulkanObjectPool = Util::ObjectPool; -template -using VulkanCache = Util::IntrusiveHashMap; -template -using VulkanCacheReadWrite = Util::IntrusiveHashMap; -#endif enum QueueIndices { diff --git a/parallel-rdp-standalone/vulkan/vulkan_headers.hpp b/parallel-rdp-standalone/vulkan/vulkan_headers.hpp index 25a6c40..1415a1c 100644 --- a/parallel-rdp-standalone/vulkan/vulkan_headers.hpp +++ b/parallel-rdp-standalone/vulkan/vulkan_headers.hpp @@ -30,6 +30,10 @@ #define VK_ENABLE_BETA_EXTENSIONS #endif +#if defined(VULKAN_H_) || defined(VULKAN_CORE_H_) +#error "Must include vulkan_headers.hpp before Vulkan headers" +#endif + #include "volk.h" #include #include "logging.hpp" diff --git a/parallel-rdp-standalone/vulkan/vulkan_prerotate.hpp b/parallel-rdp-standalone/vulkan/vulkan_prerotate.hpp index 33f6d28..81904dc 100644 --- a/parallel-rdp-standalone/vulkan/vulkan_prerotate.hpp +++ b/parallel-rdp-standalone/vulkan/vulkan_prerotate.hpp @@ -91,8 +91,8 @@ static inline void rect2d_clip(VkRect2D &rect) rect.offset.y = 0; } - rect.extent.width = std::min(rect.extent.width, 0x7fffffffu - rect.offset.x); - rect.extent.height = std::min(rect.extent.height, 0x7fffffffu - rect.offset.y); + rect.extent.width = std::min(rect.extent.width, 0x7fffffffu - rect.offset.x); + rect.extent.height = std::min(rect.extent.height, 0x7fffffffu - rect.offset.y); } static inline void rect2d_transform_xy(VkRect2D &rect, VkSurfaceTransformFlagBitsKHR transform, diff --git a/parallel-rdp-standalone/vulkan/wsi.cpp b/parallel-rdp-standalone/vulkan/wsi.cpp index 3d6fe6a..9abd3b4 100644 --- a/parallel-rdp-standalone/vulkan/wsi.cpp +++ b/parallel-rdp-standalone/vulkan/wsi.cpp @@ -20,6 +20,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#define NOMINMAX #include "wsi.hpp" #include "quirks.hpp" @@ -268,25 +269,81 @@ void WSI::reinit_surface_and_swapchain(VkSurfaceKHR new_surface) update_framebuffer(swapchain_width, swapchain_height); } -void WSI::drain_swapchain() +void WSI::nonblock_delete_swapchains() +{ + if (swapchain != VK_NULL_HANDLE && device->get_device_features().present_wait_features.presentWait) + { + // If we can help it, don't try to destroy swapchains until we know the new swapchain has presented at least one frame on screen. + if (table->vkWaitForPresentKHR(context->get_device(), swapchain, 1, 0) != VK_SUCCESS) + return; + } + + Util::SmallVector keep; + size_t pending = deferred_swapchains.size(); + for (auto &swap : deferred_swapchains) + { + if (!swap.fence || swap.fence->wait_timeout(0)) + { + table->vkDestroySwapchainKHR(device->get_device(), swap.swapchain, nullptr); + } + else if (pending >= 2) + { + swap.fence->wait(); + table->vkDestroySwapchainKHR(device->get_device(), swap.swapchain, nullptr); + } + else + keep.push_back(std::move(swap)); + + pending--; + } + + deferred_swapchains = std::move(keep); +} + +void WSI::drain_swapchain(bool in_tear_down) { release_semaphores.clear(); device->set_acquire_semaphore(0, Semaphore{}); device->consume_release_semaphore(); - device->wait_idle(); + + if (device->get_device_features().swapchain_maintenance1_features.swapchainMaintenance1) + { + // If we're just resizing, there's no need to block, defer deletions for later. + if (in_tear_down) + { + if (last_present_fence) + { + last_present_fence->wait(); + last_present_fence.reset(); + } + + for (auto &old_swap : deferred_swapchains) + { + if (old_swap.fence) + old_swap.fence->wait(); + table->vkDestroySwapchainKHR(context->get_device(), old_swap.swapchain, nullptr); + } + + deferred_swapchains.clear(); + } + } + else if (swapchain != VK_NULL_HANDLE && device->get_device_features().present_wait_features.presentWait && present_last_id) + { + table->vkWaitForPresentKHR(context->get_device(), swapchain, present_last_id, UINT64_MAX); + // If the last present was not successful, + // it's not clear that the present ID will be signalled, so wait idle as a fallback. + if (present_id != present_last_id) + device->wait_idle(); + } + else + device->wait_idle(); } void WSI::tear_down_swapchain() { - drain_swapchain(); + drain_swapchain(true); platform->event_swapchain_destroyed(); - - if (swapchain != VK_NULL_HANDLE) - { - if (device->get_device_features().present_wait_features.presentWait && present_last_id) - table->vkWaitForPresentKHR(context->get_device(), swapchain, present_last_id, UINT64_MAX); - table->vkDestroySwapchainKHR(context->get_device(), swapchain, nullptr); - } + table->vkDestroySwapchainKHR(context->get_device(), swapchain, nullptr); swapchain = VK_NULL_HANDLE; has_acquired_swapchain_index = false; present_id = 0; @@ -451,6 +508,9 @@ bool WSI::begin_frame() has_acquired_swapchain_index = true; acquire->signal_external(); + // WSI signals this, which exists outside the domain of our Vulkan queues. + acquire->set_signal_is_foreign_queue(); + wait_swapchain_latency(); auto frame_time = platform->get_frame_timer().frame(); @@ -510,6 +570,7 @@ bool WSI::end_frame() auto release = device->consume_release_semaphore(); VK_ASSERT(release); VK_ASSERT(release->is_signalled()); + VK_ASSERT(!release->is_pending_wait()); auto release_semaphore = release->get_semaphore(); VK_ASSERT(release_semaphore != VK_NULL_HANDLE); @@ -522,7 +583,10 @@ bool WSI::end_frame() info.pImageIndices = &swapchain_index; info.pResults = &result; + VkSwapchainPresentFenceInfoEXT present_fence = { VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT }; + VkSwapchainPresentModeInfoEXT present_mode_info = { VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT }; VkPresentIdKHR present_id_info = { VK_STRUCTURE_TYPE_PRESENT_ID_KHR }; + if (device->get_device_features().present_id_features.presentId) { present_id_info.swapchainCount = 1; @@ -532,6 +596,23 @@ bool WSI::end_frame() info.pNext = &present_id_info; } + // If we can, just promote the new presentation mode right away. + update_active_presentation_mode(present_mode); + + if (device->get_device_features().swapchain_maintenance1_features.swapchainMaintenance1) + { + last_present_fence = device->request_legacy_fence(); + present_fence.swapchainCount = 1; + present_fence.pFences = &last_present_fence->get_fence(); + present_fence.pNext = const_cast(info.pNext); + info.pNext = &present_fence; + + present_mode_info.swapchainCount = 1; + present_mode_info.pPresentModes = &active_present_mode; + present_mode_info.pNext = const_cast(info.pNext); + info.pNext = &present_mode_info; + } + #ifdef VULKAN_WSI_TIMING_DEBUG auto present_start = Util::get_current_time_nsecs(); #endif @@ -583,18 +664,22 @@ bool WSI::end_frame() swapchain_is_suboptimal = true; } + // The present semaphore is consumed even on OUT_OF_DATE, etc. + release->wait_external(); + if (overall < 0 || result < 0) { LOGE("vkQueuePresentKHR failed.\n"); + release.reset(); tear_down_swapchain(); return false; } else { - release->wait_external(); // Cannot release the WSI wait semaphore until we observe that the image has been // waited on again. - release_semaphores[swapchain_index] = release; + // Could make this a bit tighter with swapchain_maintenance1, but not that important here. + release_semaphores[swapchain_index] = std::move(release); } // Re-init swapchain. @@ -608,6 +693,7 @@ bool WSI::end_frame() } } + nonblock_delete_swapchains(); return true; } @@ -615,7 +701,7 @@ void WSI::update_framebuffer(unsigned width, unsigned height) { if (context && device) { - drain_swapchain(); + drain_swapchain(false); if (blocking_init_swapchain(width, height)) { device->init_swapchain(swapchain_images, swapchain_width, swapchain_height, swapchain_surface_format.format, @@ -628,13 +714,55 @@ void WSI::update_framebuffer(unsigned width, unsigned height) platform->notify_current_swapchain_dimensions(swapchain_width, swapchain_height); } +bool WSI::update_active_presentation_mode(PresentMode mode) +{ + if (current_present_mode == mode) + return true; + + for (auto m : present_mode_compat_group) + { + bool match = false; + switch (m) + { + case VK_PRESENT_MODE_FIFO_KHR: + match = mode == PresentMode::SyncToVBlank; + break; + + case VK_PRESENT_MODE_IMMEDIATE_KHR: + match = mode == PresentMode::UnlockedMaybeTear || + mode == PresentMode::UnlockedForceTearing; + break; + + case VK_PRESENT_MODE_MAILBOX_KHR: + match = mode == PresentMode::UnlockedNoTearing || + mode == PresentMode::UnlockedMaybeTear; + break; + + default: + break; + } + + if (match) + { + active_present_mode = m; + current_present_mode = mode; + return true; + } + } + + return false; +} + void WSI::set_present_mode(PresentMode mode) { present_mode = mode; if (!has_acquired_swapchain_index && present_mode != current_present_mode) { - current_present_mode = present_mode; - update_framebuffer(swapchain_width, swapchain_height); + if (!update_active_presentation_mode(present_mode)) + { + current_present_mode = present_mode; + update_framebuffer(swapchain_width, swapchain_height); + } } } @@ -781,119 +909,244 @@ VkSurfaceFormatKHR WSI::find_suitable_present_format(const std::vector formats; + VkSwapchainPresentModesCreateInfoEXT present_modes_info; + std::vector present_mode_compat_group; + const void *swapchain_pnext; +#ifdef _WIN32 + VkSurfaceFullScreenExclusiveInfoEXT exclusive_info; + VkSurfaceFullScreenExclusiveWin32InfoEXT exclusive_info_win32; +#endif +}; + +static bool init_surface_info(Device &device, WSIPlatform &platform, + VkSurfaceKHR surface, BackbufferFormat format, + PresentMode present_mode, SurfaceInfo &info) { if (surface == VK_NULL_HANDLE) { LOGE("Cannot create swapchain with surface == VK_NULL_HANDLE.\n"); - return SwapchainError::Error; + return false; } - VkSurfaceCapabilitiesKHR surface_properties; - VkPhysicalDeviceSurfaceInfo2KHR surface_info = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR }; - surface_info.surface = surface; - bool use_surface_info = device->get_device_features().supports_surface_capabilities2; - bool use_application_controlled_exclusive_fullscreen = false; + info.surface_info = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR }; + info.surface_info.surface = surface; + info.swapchain_pnext = nullptr; + + auto &ext = device.get_device_features(); #ifdef _WIN32 - VkSurfaceFullScreenExclusiveInfoEXT exclusive_info = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT }; - VkSurfaceFullScreenExclusiveWin32InfoEXT exclusive_info_win32 = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT }; - - HMONITOR monitor = reinterpret_cast(platform->get_fullscreen_monitor()); - if (!device->get_device_features().supports_full_screen_exclusive) - monitor = nullptr; - - surface_info.pNext = &exclusive_info; - if (monitor != nullptr) + if (ext.supports_full_screen_exclusive) { - exclusive_info.pNext = &exclusive_info_win32; - exclusive_info_win32.hmonitor = monitor; - LOGI("Win32: Got a full-screen monitor.\n"); - } - else - LOGI("Win32: Not running full-screen.\n"); + info.exclusive_info = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT }; + auto monitor = reinterpret_cast(platform.get_fullscreen_monitor()); + info.swapchain_pnext = &info.exclusive_info; + info.surface_info.pNext = &info.exclusive_info; - const char *exclusive = getenv("GRANITE_EXCLUSIVE_FULL_SCREEN"); - bool prefer_exclusive = exclusive && strtoul(exclusive, nullptr, 0) != 0; - - if (device->get_device_features().driver_properties.driverID == VK_DRIVER_ID_AMD_PROPRIETARY_KHR && - current_backbuffer_format == BackbufferFormat::HDR10) - { - LOGI("HDR requested on AMD Windows. Forcing exclusive fullscreen, or HDR will not work properly.\n"); - prefer_exclusive = true; - } - - if (prefer_exclusive) - { - LOGI("Win32: Opting in to exclusive full-screen!\n"); - exclusive_info.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT; - } - else - { - LOGI("Win32: Opting out of exclusive full-screen!\n"); - exclusive_info.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT; - } -#endif - - auto gpu = context->get_gpu(); - if (use_surface_info) - { - VkSurfaceCapabilities2KHR surface_capabilities2 = { VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR }; - -#ifdef _WIN32 - VkSurfaceCapabilitiesFullScreenExclusiveEXT capability_full_screen_exclusive = { VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT }; - if (device->get_device_features().supports_full_screen_exclusive && exclusive_info_win32.hmonitor) + if (monitor != nullptr) { - surface_capabilities2.pNext = &capability_full_screen_exclusive; - capability_full_screen_exclusive.pNext = &exclusive_info_win32; + info.exclusive_info_win32 = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT }; + info.exclusive_info.pNext = &info.exclusive_info_win32; + info.exclusive_info_win32.hmonitor = monitor; + LOGI("Win32: Got a full-screen monitor.\n"); } -#endif + else + LOGI("Win32: Not running full-screen.\n"); - if (vkGetPhysicalDeviceSurfaceCapabilities2KHR(gpu, &surface_info, &surface_capabilities2) != VK_SUCCESS) - return SwapchainError::Error; + const char *exclusive = getenv("GRANITE_EXCLUSIVE_FULL_SCREEN"); + bool prefer_exclusive = exclusive && strtoul(exclusive, nullptr, 0) != 0; - surface_properties = surface_capabilities2.surfaceCapabilities; - -#ifdef _WIN32 - if (capability_full_screen_exclusive.fullScreenExclusiveSupported) - LOGI("Surface could support app-controlled exclusive fullscreen.\n"); - - use_application_controlled_exclusive_fullscreen = exclusive_info.fullScreenExclusive == VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT && - capability_full_screen_exclusive.fullScreenExclusiveSupported == VK_TRUE; - if (monitor == nullptr) - use_application_controlled_exclusive_fullscreen = false; -#endif - - if (use_application_controlled_exclusive_fullscreen) + if (ext.driver_properties.driverID == VK_DRIVER_ID_AMD_PROPRIETARY_KHR && format == BackbufferFormat::HDR10) { - LOGI("Using app-controlled exclusive fullscreen.\n"); -#ifdef _WIN32 - exclusive_info.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT; - exclusive_info.pNext = &exclusive_info_win32; -#endif + LOGI("Win32: HDR requested on AMD Windows. Forcing exclusive fullscreen, or HDR will not work properly.\n"); + prefer_exclusive = true; + } + + if (prefer_exclusive && monitor != nullptr) + { + LOGI("Win32: Opting in to exclusive full-screen!\n"); + info.exclusive_info.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT; + + // Try to promote this to application controlled exclusive. + VkSurfaceCapabilities2KHR surface_capabilities2 = { VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR }; + VkSurfaceCapabilitiesFullScreenExclusiveEXT capability_full_screen_exclusive = { + VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_FULL_SCREEN_EXCLUSIVE_EXT + }; + surface_capabilities2.pNext = &capability_full_screen_exclusive; + + if (vkGetPhysicalDeviceSurfaceCapabilities2KHR(device.get_physical_device(), &info.surface_info, + &surface_capabilities2) != VK_SUCCESS) + return false; + + if (capability_full_screen_exclusive.fullScreenExclusiveSupported) + { + LOGI("Win32: Opting for exclusive fullscreen access.\n"); + info.exclusive_info.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT; + } } else { - LOGI("Not using app-controlled exclusive fullscreen.\n"); + LOGI("Win32: Opting out of exclusive full-screen!\n"); + info.exclusive_info.fullScreenExclusive = VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT; + } + } +#else + (void)platform; + (void)format; +#endif + + std::vector present_modes; + uint32_t num_present_modes = 0; + auto gpu = device.get_physical_device(); + +#ifdef _WIN32 + if (ext.supports_surface_capabilities2 && ext.supports_full_screen_exclusive) + { + if (vkGetPhysicalDeviceSurfacePresentModes2EXT(gpu, &info.surface_info, &num_present_modes, nullptr) != + VK_SUCCESS) + { + return false; + } + present_modes.resize(num_present_modes); + if (vkGetPhysicalDeviceSurfacePresentModes2EXT(gpu, &info.surface_info, &num_present_modes, + present_modes.data()) != VK_SUCCESS) + { + return false; } } else +#endif { - if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(gpu, surface, &surface_properties) != VK_SUCCESS) - return SwapchainError::Error; + if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &num_present_modes, nullptr) != VK_SUCCESS) + return false; + present_modes.resize(num_present_modes); + if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &num_present_modes, present_modes.data()) != VK_SUCCESS) + return false; } - // Happens on Windows when you minimize a window. - if (surface_properties.maxImageExtent.width == 0 && surface_properties.maxImageExtent.height == 0) - return SwapchainError::NoSurface; - - uint32_t format_count; - std::vector formats; - - if (use_surface_info) + auto swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR; + bool use_vsync = present_mode == PresentMode::SyncToVBlank; + if (!use_vsync) { - if (vkGetPhysicalDeviceSurfaceFormats2KHR(gpu, &surface_info, &format_count, nullptr) != VK_SUCCESS) - return SwapchainError::Error; + bool allow_mailbox = present_mode != PresentMode::UnlockedForceTearing; + bool allow_immediate = present_mode != PresentMode::UnlockedNoTearing; + +#ifdef _WIN32 + // If we're trying to go exclusive full-screen, + // we need to ban certain types of present modes which apparently do not work as we expect. + if (info.exclusive_info.fullScreenExclusive == VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT) + allow_mailbox = false; + else + allow_immediate = false; +#endif + + for (auto &mode : present_modes) + { + if ((allow_immediate && mode == VK_PRESENT_MODE_IMMEDIATE_KHR) || + (allow_mailbox && mode == VK_PRESENT_MODE_MAILBOX_KHR)) + { + swapchain_present_mode = mode; + break; + } + } + } + + // First, query minImageCount without any present mode. + // Avoid opting for present mode compat that is pathological in nature, + // e.g. Xorg MAILBOX where minImageCount shoots up to 5 for stupid reasons. + uint32_t baseline_image_count; + if (ext.supports_surface_capabilities2) + { + VkSurfaceCapabilities2KHR surface_capabilities2 = { VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR }; + if (vkGetPhysicalDeviceSurfaceCapabilities2KHR(gpu, &info.surface_info, &surface_capabilities2) != VK_SUCCESS) + return false; + info.surface_capabilities = surface_capabilities2.surfaceCapabilities; + baseline_image_count = surface_capabilities2.surfaceCapabilities.minImageCount; + } + else + { + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(gpu, surface, &info.surface_capabilities) != VK_SUCCESS) + return false; + baseline_image_count = info.surface_capabilities.minImageCount; + } + + // Do not exceed this limit when selecting compatible present modes. + baseline_image_count = std::max(3u, baseline_image_count); + + // Make sure we query surface caps tied to the present mode for correct results. + if (ext.supports_surface_maintenance1 && ext.supports_surface_capabilities2) + { + VkSurfaceCapabilities2KHR surface_capabilities2 = { VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR }; + VkSurfacePresentModeCompatibilityEXT present_mode_caps = + { VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT }; + std::vector present_mode_compat_group; + + if (ext.supports_surface_maintenance1) + { + present_mode_compat_group.resize(32); + present_mode_caps.presentModeCount = present_mode_compat_group.size(); + present_mode_caps.pPresentModes = present_mode_compat_group.data(); + surface_capabilities2.pNext = &present_mode_caps; + } + + info.present_mode.pNext = const_cast(info.surface_info.pNext); + info.surface_info.pNext = &info.present_mode; + info.present_mode = { VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT }; + info.present_mode.presentMode = swapchain_present_mode; + + if (vkGetPhysicalDeviceSurfaceCapabilities2KHR(gpu, &info.surface_info, &surface_capabilities2) != VK_SUCCESS) + return false; + + info.surface_capabilities.minImageCount = surface_capabilities2.surfaceCapabilities.minImageCount; + present_mode_compat_group.resize(present_mode_caps.presentModeCount); + info.present_mode_compat_group.reserve(present_mode_caps.presentModeCount); + info.present_mode_compat_group.push_back(swapchain_present_mode); + + for (auto mode : present_mode_compat_group) + { + if (mode == swapchain_present_mode) + continue; + + // Only allow sensible present modes that we know of. + if (mode != VK_PRESENT_MODE_FIFO_KHR && + mode != VK_PRESENT_MODE_IMMEDIATE_KHR && + mode != VK_PRESENT_MODE_MAILBOX_KHR) + { + continue; + } + + info.present_mode.presentMode = mode; + if (vkGetPhysicalDeviceSurfaceCapabilities2KHR(gpu, &info.surface_info, &surface_capabilities2) != VK_SUCCESS) + return false; + + // Accept the present mode if it does not increment minImageCount beyond our expectation. + // When we use present groups, minImageCount == max(presentMode's minImageCount). + // Accept a bump to 3 images, since that's what we expect to use either way. + if (surface_capabilities2.surfaceCapabilities.minImageCount <= + std::max(baseline_image_count, info.surface_capabilities.minImageCount)) + { + info.present_mode_compat_group.push_back(mode); + info.surface_capabilities.minImageCount = + std::max(surface_capabilities2.surfaceCapabilities.minImageCount, + info.surface_capabilities.minImageCount); + } + } + } + + uint32_t format_count = 0; + if (ext.supports_surface_capabilities2) + { + if (vkGetPhysicalDeviceSurfaceFormats2KHR(device.get_physical_device(), + &info.surface_info, &format_count, + nullptr) != VK_SUCCESS) + { + return false; + } std::vector formats2(format_count); @@ -903,43 +1156,69 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) f.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR; } - if (vkGetPhysicalDeviceSurfaceFormats2KHR(gpu, &surface_info, &format_count, formats2.data()) != VK_SUCCESS) - return SwapchainError::Error; + if (vkGetPhysicalDeviceSurfaceFormats2KHR(gpu, &info.surface_info, &format_count, formats2.data()) != VK_SUCCESS) + return false; - formats.reserve(format_count); + info.formats.reserve(format_count); for (auto &f : formats2) - formats.push_back(f.surfaceFormat); + info.formats.push_back(f.surfaceFormat); } else { if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &format_count, nullptr) != VK_SUCCESS) - return SwapchainError::Error; - formats.resize(format_count); - if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &format_count, formats.data()) != VK_SUCCESS) - return SwapchainError::Error; + return false; + info.formats.resize(format_count); + if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &format_count, info.formats.data()) != VK_SUCCESS) + return false; } + // Allow for seamless toggle between presentation modes. + if (ext.swapchain_maintenance1_features.swapchainMaintenance1) + { + info.present_modes_info = { VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODES_CREATE_INFO_EXT }; + info.present_modes_info.pNext = const_cast(info.swapchain_pnext); + info.present_modes_info.presentModeCount = info.present_mode_compat_group.size(); + info.present_modes_info.pPresentModes = info.present_mode_compat_group.data(); + info.swapchain_pnext = &info.present_modes_info; + } + + info.present_mode.presentMode = swapchain_present_mode; + + return true; +} + +WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) +{ + SurfaceInfo surface_info = {}; + if (!init_surface_info(*device, *platform, surface, current_backbuffer_format, current_present_mode, surface_info)) + return SwapchainError::Error; + const auto &caps = surface_info.surface_capabilities; + + // Happens on Windows when you minimize a window. + if (caps.maxImageExtent.width == 0 && caps.maxImageExtent.height == 0) + return SwapchainError::NoSurface; + if (current_extra_usage && support_prerotate) { LOGW("Disabling prerotate support due to extra usage flags in swapchain.\n"); support_prerotate = false; } - if (current_extra_usage & ~surface_properties.supportedUsageFlags) + if (current_extra_usage & ~caps.supportedUsageFlags) { LOGW("Attempting to use unsupported usage flags 0x%x for swapchain.\n", current_extra_usage); - current_extra_usage &= surface_properties.supportedUsageFlags; + current_extra_usage &= caps.supportedUsageFlags; extra_usage = current_extra_usage; } auto attempt_backbuffer_format = current_backbuffer_format; - auto surface_format = find_suitable_present_format(formats, attempt_backbuffer_format); + auto surface_format = find_suitable_present_format(surface_info.formats, attempt_backbuffer_format); if (surface_format.format == VK_FORMAT_UNDEFINED && attempt_backbuffer_format == BackbufferFormat::HDR10) { LOGW("Could not find suitable present format for HDR10. Attempting fallback to sRGB.\n"); attempt_backbuffer_format = BackbufferFormat::sRGB; - surface_format = find_suitable_present_format(formats, attempt_backbuffer_format); + surface_format = find_suitable_present_format(surface_info.formats, attempt_backbuffer_format); } if (surface_format.format == VK_FORMAT_UNDEFINED) @@ -947,7 +1226,13 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) LOGW("Could not find supported format for swapchain usage flags 0x%x.\n", current_extra_usage); current_extra_usage = 0; extra_usage = 0; - surface_format = find_suitable_present_format(formats, attempt_backbuffer_format); + surface_format = find_suitable_present_format(surface_info.formats, attempt_backbuffer_format); + } + + if (surface_format.format == VK_FORMAT_UNDEFINED) + { + LOGE("Failed to find any suitable format for swapchain.\n"); + return SwapchainError::Error; } static const char *transform_names[] = { @@ -962,47 +1247,47 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) "INHERIT_BIT_KHR", }; - LOGI("Current transform is enum 0x%x.\n", unsigned(surface_properties.currentTransform)); + LOGI("Current transform is enum 0x%x.\n", unsigned(caps.currentTransform)); for (unsigned i = 0; i <= 8; i++) { - if (surface_properties.supportedTransforms & (1u << i)) + if (caps.supportedTransforms & (1u << i)) LOGI("Supported transform 0x%x: %s.\n", 1u << i, transform_names[i]); } VkSurfaceTransformFlagBitsKHR pre_transform; - if (!support_prerotate && (surface_properties.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) != 0) + if (!support_prerotate && (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) != 0) pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; else { // Only attempt to use prerotate if we can deal with it purely using a XY clip fixup. // For horizontal flip we need to start flipping front-face as well ... - if ((surface_properties.currentTransform & ( + if ((caps.currentTransform & ( VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR)) != 0) - pre_transform = surface_properties.currentTransform; + pre_transform = caps.currentTransform; else pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; } - if (pre_transform != surface_properties.currentTransform) + if (pre_transform != caps.currentTransform) { LOGW("surfaceTransform (0x%x) != currentTransform (0x%u). Might get performance penalty.\n", - unsigned(pre_transform), unsigned(surface_properties.currentTransform)); + unsigned(pre_transform), unsigned(caps.currentTransform)); } swapchain_current_prerotate = pre_transform; VkExtent2D swapchain_size; LOGI("Swapchain current extent: %d x %d\n", - int(surface_properties.currentExtent.width), - int(surface_properties.currentExtent.height)); + int(caps.currentExtent.width), + int(caps.currentExtent.height)); if (width == 0) { - if (surface_properties.currentExtent.width != ~0u) - width = surface_properties.currentExtent.width; + if (caps.currentExtent.width != ~0u) + width = caps.currentExtent.width; else width = 1280; LOGI("Auto selected width = %u.\n", width); @@ -1010,8 +1295,8 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) if (height == 0) { - if (surface_properties.currentExtent.height != ~0u) - height = surface_properties.currentExtent.height; + if (caps.currentExtent.height != ~0u) + height = caps.currentExtent.height; else height = 720; LOGI("Auto selected height = %u.\n", height); @@ -1036,63 +1321,9 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) // Clamp the target width, height to boundaries. swapchain_size.width = - std::max(std::min(width, surface_properties.maxImageExtent.width), surface_properties.minImageExtent.width); + std::max(std::min(width, caps.maxImageExtent.width), caps.minImageExtent.width); swapchain_size.height = - std::max(std::min(height, surface_properties.maxImageExtent.height), surface_properties.minImageExtent.height); - - uint32_t num_present_modes; - - std::vector present_modes; - -#ifdef _WIN32 - if (use_surface_info && device->get_device_features().supports_full_screen_exclusive) - { - if (vkGetPhysicalDeviceSurfacePresentModes2EXT(gpu, &surface_info, &num_present_modes, nullptr) != VK_SUCCESS) - return SwapchainError::Error; - present_modes.resize(num_present_modes); - if (vkGetPhysicalDeviceSurfacePresentModes2EXT(gpu, &surface_info, &num_present_modes, present_modes.data()) != - VK_SUCCESS) - return SwapchainError::Error; - } - else -#endif - { - if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &num_present_modes, nullptr) != VK_SUCCESS) - return SwapchainError::Error; - present_modes.resize(num_present_modes); - if (vkGetPhysicalDeviceSurfacePresentModesKHR(gpu, surface, &num_present_modes, present_modes.data()) != VK_SUCCESS) - return SwapchainError::Error; - } - - VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR; - bool use_vsync = current_present_mode == PresentMode::SyncToVBlank; - if (!use_vsync) - { - bool allow_mailbox = current_present_mode != PresentMode::UnlockedForceTearing; - bool allow_immediate = current_present_mode != PresentMode::UnlockedNoTearing; - -#ifdef _WIN32 - if (device->get_gpu_properties().vendorID == VENDOR_ID_NVIDIA) - { - // If we're trying to go exclusive full-screen, - // we need to ban certain types of present modes which apparently do not work as we expect. - if (use_application_controlled_exclusive_fullscreen) - allow_mailbox = false; - else - allow_immediate = false; - } -#endif - - for (uint32_t i = 0; i < num_present_modes; i++) - { - if ((allow_immediate && present_modes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) || - (allow_mailbox && present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR)) - { - swapchain_present_mode = present_modes[i]; - break; - } - } - } + std::max(std::min(height, caps.maxImageExtent.height), caps.minImageExtent.height); uint32_t desired_swapchain_images = 3; { @@ -1103,20 +1334,20 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) LOGI("Targeting %u swapchain images.\n", desired_swapchain_images); - if (desired_swapchain_images < surface_properties.minImageCount) - desired_swapchain_images = surface_properties.minImageCount; + if (desired_swapchain_images < caps.minImageCount) + desired_swapchain_images = caps.minImageCount; - if ((surface_properties.maxImageCount > 0) && (desired_swapchain_images > surface_properties.maxImageCount)) - desired_swapchain_images = surface_properties.maxImageCount; + if ((caps.maxImageCount > 0) && (desired_swapchain_images > caps.maxImageCount)) + desired_swapchain_images = caps.maxImageCount; VkCompositeAlphaFlagBitsKHR composite_mode = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + if (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) composite_mode = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) + else if (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) composite_mode = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; - else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) + else if (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) composite_mode = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; - else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) + else if (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) composite_mode = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; else LOGW("No sensible composite mode supported?\n"); @@ -1125,6 +1356,7 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) VkSwapchainCreateInfoKHR info = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR }; info.surface = surface; + info.pNext = surface_info.swapchain_pnext; info.minImageCount = desired_swapchain_images; info.imageFormat = surface_format.format; info.imageColorSpace = surface_format.colorSpace; @@ -1135,31 +1367,30 @@ WSI::SwapchainError WSI::init_swapchain(unsigned width, unsigned height) info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; info.preTransform = pre_transform; info.compositeAlpha = composite_mode; - info.presentMode = swapchain_present_mode; + info.presentMode = surface_info.present_mode.presentMode; info.clipped = VK_TRUE; info.oldSwapchain = old_swapchain; - if (device->get_device_features().present_wait_features.presentWait && - old_swapchain != VK_NULL_HANDLE && present_last_id) + // Defer the deletion instead. + if (device->get_device_features().swapchain_maintenance1_features.swapchainMaintenance1 && + old_swapchain != VK_NULL_HANDLE) { - table->vkWaitForPresentKHR(context->get_device(), old_swapchain, present_last_id, UINT64_MAX); + deferred_swapchains.push_back({ old_swapchain, last_present_fence }); + old_swapchain = VK_NULL_HANDLE; } -#ifdef _WIN32 - if (device->get_device_features().supports_full_screen_exclusive) - info.pNext = &exclusive_info; -#endif - platform->event_swapchain_destroyed(); auto res = table->vkCreateSwapchainKHR(context->get_device(), &info, nullptr, &swapchain); - if (old_swapchain != VK_NULL_HANDLE) - table->vkDestroySwapchainKHR(context->get_device(), old_swapchain, nullptr); + table->vkDestroySwapchainKHR(context->get_device(), old_swapchain, nullptr); has_acquired_swapchain_index = false; present_id = 0; present_last_id = 0; + active_present_mode = info.presentMode; + present_mode_compat_group = std::move(surface_info.present_mode_compat_group); + #ifdef _WIN32 - if (use_application_controlled_exclusive_fullscreen) + if (surface_info.exclusive_info.fullScreenExclusive == VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT) { bool success = vkAcquireFullScreenExclusiveModeEXT(context->get_device(), swapchain) == VK_SUCCESS; if (success) diff --git a/parallel-rdp-standalone/vulkan/wsi.hpp b/parallel-rdp-standalone/vulkan/wsi.hpp index 7dcff31..98b9f40 100644 --- a/parallel-rdp-standalone/vulkan/wsi.hpp +++ b/parallel-rdp-standalone/vulkan/wsi.hpp @@ -280,6 +280,11 @@ private: VkSurfaceFormatKHR swapchain_surface_format = { VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; PresentMode current_present_mode = PresentMode::SyncToVBlank; PresentMode present_mode = PresentMode::SyncToVBlank; + + VkPresentModeKHR active_present_mode = VK_PRESENT_MODE_FIFO_KHR; + std::vector present_mode_compat_group; + bool update_active_presentation_mode(PresentMode mode); + VkImageUsageFlags current_extra_usage = 0; VkImageUsageFlags extra_usage = 0; bool swapchain_is_suboptimal = false; @@ -322,11 +327,20 @@ private: unsigned present_frame_latency = 0; void tear_down_swapchain(); - void drain_swapchain(); + void drain_swapchain(bool in_tear_down); void wait_swapchain_latency(); VkHdrMetadataEXT hdr_metadata = { VK_STRUCTURE_TYPE_HDR_METADATA_EXT }; + struct DeferredDeletion + { + VkSwapchainKHR swapchain; + Fence fence; + }; + Util::SmallVector deferred_swapchains; + Vulkan::Fence last_present_fence; + void nonblock_delete_swapchains(); + VkSurfaceFormatKHR find_suitable_present_format(const std::vector &formats, BackbufferFormat desired_format) const; }; }