Files
kaizen/external/parallel-rdp/parallel-rdp-standalone/vulkan/wsi.cpp
T
iris 00cc9309cb Squashed 'external/ircolib/' changes from ce3cd726c..de6e324bd
de6e324bd separate emu thread
10d3daf86 Roms List improvements
95d202f37 Let's make the rom list process on a separate thread so the emulator doesnt take ages to load.
fc306967f Wow the ROM Header was just completely busted. Game list view works now
bad1691ee fuck this shit
2b59e5f46 game list in progress
d26417b83 remappable inputs in progress
ac4af8106 input
e72abc240 update readme
430139dc9 Qt6 frontend
3080d4d45 Fix this small bug too
08cd13b85 Cop0 unused functions do not actually pose a threat (as per manual). They don't do anything, so shall we.
61bb4fb44 make idle loop detection a little more specific with where the load goes
b037de4c3 SAZDFsdff
12e81e73e need to figure out why n64-systemtest loops indefinitely at some address that appears to be valid (i think it's me not invalidating the cache properly)
204f0e13b idle skipping seems to work!
cb8bb634a sdkfjlasdf
58e5c89c1 Fix compilation issue on my machine (no idea)
24fb2898e attempting more serious idle skipping
214719577 Place rsp.Step inside cached interpreter. Gains about 3 more fps
bb97dcc23 mmmmm
920b77d38 wjkhasdfjhkasdf
430ccdab4 it's a start...
4f42a673a Cached interpreter plays Mario 64. Start looking into RSP as well
c9a030787 idle skipping works!
5fbda03ce new idea
366637aba Idle skipping... maybe?
609fa2fb0 Cache instructions implemented but broken lmao. Commented out for now
e140a6d12 - Stop using inheritance for CPU, instead use composition. - Introduce KAIZEN_JIT_ENABLED optional define instead of relying on __aarch64__ and the like. - More cache work
68e613057 prep cache impl
811b4d809 fix clang format
fda755f7d idk
d5024ebbf small MI refactor in preparation of (eventually) implementing the RDRAM interface properly
694b45341 Merge commit '206dcdedf195fb320913584180edb12c7731e396' as 'external/SDL'
206dcdedf Squashed 'external/SDL/' content from commit 4d17b99d0a
4d16e1cb4 need to update sdl
848b19920 Fix compilation error
db61b5299 Merge commit 'e94a94559f28e49678fbcf72199a5258137b0fe9' as 'external/imgui'
e94a94559 Squashed 'external/imgui/' content from commit 02e9b8cac
52edb3757 need to update imgui
c1a705e86 Emulate weird JALR behaviour
4b4c32f4b Fix exception for "unusable COP1" in 4 instructions i missed accidentally (again)
df5828142 Bug putting 0s in the log everywhere
f8b580048 Make isviewer a sink to file
8241e9735 Fix exception for "unusable COP1" in 4 instructions i missed accidentally
b29715f20 small changes
d9a620bc1 make use of my new small utility library
0d1aa938e Add 'external/ircolib/' from commit 'ce3cd726c8df8388d554abf8bb55d55020eb4450'
e64eb40b3 Fuck git

git-subtree-dir: external/ircolib
git-subtree-split: de6e324bde
2026-06-15 11:56:38 +02:00

1770 lines
55 KiB
C++

/* Copyright (c) 2017-2023 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 "wsi.hpp"
#include "environment.hpp"
#include <algorithm>
#if defined(ANDROID) && defined(HAVE_SWAPPY)
#include "swappy/swappyVk.h"
#endif
namespace Vulkan
{
WSI::WSI()
{
// With frame latency of 1, we get the ideal latency where
// we present, and then wait for the previous present to complete.
// Once this unblocks, it means that the present we just queued up is scheduled to complete next vblank,
// and the next frame to be recorded will have to be ready in 2 frames.
// This is ideal, since worst case for full performance, we will have a pipeline of CPU -> GPU,
// where CPU can spend 1 frame's worth of time, and GPU can spend one frame's worth of time.
// For mobile, opt for 2 frames of latency, since TBDR likes deeper pipelines and we can absorb more
// surfaceflinger jank.
#ifdef ANDROID
present_frame_latency = 2;
#else
present_frame_latency = 1;
#endif
present_frame_latency = Util::get_environment_uint("GRANITE_VULKAN_PRESENT_WAIT_LATENCY", present_frame_latency);
LOGI("Targeting VK_KHR_present_wait latency to %u frames.\n", present_frame_latency);
// Primaries are ST.2020 with D65 whitepoint as specified.
hdr_metadata.displayPrimaryRed = { 0.708f, 0.292f };
hdr_metadata.displayPrimaryGreen = { 0.170f, 0.797f };
hdr_metadata.displayPrimaryBlue = { 0.131f, 0.046f };
hdr_metadata.whitePoint = { 0.3127f, 0.3290f };
// HDR10 range? Just arbitrary values, user can override later.
hdr_metadata.minLuminance = 0.01f;
hdr_metadata.maxLuminance = 1000.0f;
hdr_metadata.maxContentLightLevel = 1000.0f;
hdr_metadata.maxFrameAverageLightLevel = 200.0f;
}
void WSI::set_hdr_metadata(const VkHdrMetadataEXT &hdr)
{
hdr_metadata = hdr;
if (swapchain && swapchain_surface_format.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT &&
device->get_device_features().supports_hdr_metadata)
{
table->vkSetHdrMetadataEXT(device->get_device(), 1, &swapchain, &hdr_metadata);
}
}
void WSIPlatform::set_window_title(const std::string &)
{
}
void WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface)
{
vkDestroySurfaceKHR(instance, surface, nullptr);
}
uintptr_t WSIPlatform::get_fullscreen_monitor()
{
return 0;
}
uintptr_t WSIPlatform::get_native_window()
{
return 0;
}
const VkApplicationInfo *WSIPlatform::get_application_info()
{
return nullptr;
}
void WSI::set_window_title(const std::string &title)
{
if (platform)
platform->set_window_title(title);
}
double WSI::get_smooth_elapsed_time() const
{
return smooth_elapsed_time;
}
double WSI::get_smooth_frame_time() const
{
return smooth_frame_time;
}
bool WSI::init_from_existing_context(ContextHandle existing_context)
{
VK_ASSERT(platform);
if (platform && device)
platform->event_device_destroyed();
device.reset();
context = std::move(existing_context);
table = &context->get_device_table();
return true;
}
bool WSI::init_external_swapchain(std::vector<ImageHandle> swapchain_images_)
{
VK_ASSERT(context);
VK_ASSERT(device);
swapchain_width = platform->get_surface_width();
swapchain_height = platform->get_surface_height();
swapchain_aspect_ratio = platform->get_aspect_ratio();
external_swapchain_images = std::move(swapchain_images_);
swapchain_width = external_swapchain_images.front()->get_width();
swapchain_height = external_swapchain_images.front()->get_height();
swapchain_surface_format = { external_swapchain_images.front()->get_format(), VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
LOGI("Created swapchain %u x %u (fmt: %u).\n",
swapchain_width, swapchain_height, static_cast<unsigned>(swapchain_surface_format.format));
platform->event_swapchain_destroyed();
platform->event_swapchain_created(device.get(), VK_NULL_HANDLE, swapchain_width, swapchain_height,
swapchain_aspect_ratio,
external_swapchain_images.size(),
swapchain_surface_format.format, swapchain_surface_format.colorSpace,
swapchain_current_prerotate);
device->init_external_swapchain(this->external_swapchain_images);
platform->get_frame_timer().reset();
external_acquire.reset();
external_release.reset();
return true;
}
void WSI::set_platform(WSIPlatform *platform_)
{
platform = platform_;
}
bool WSI::init_device()
{
VK_ASSERT(context);
VK_ASSERT(!device);
device = Util::make_handle<Device>();
device->set_context(*context);
platform->event_device_created(device.get());
#ifdef HAVE_WSI_DXGI_INTEROP
dxgi.reset(new DXGIInteropSwapchain);
if (!dxgi->init_interop_device(*device))
dxgi.reset();
else
platform->get_frame_timer().reset();
#endif
return true;
}
bool WSI::init_device(DeviceHandle device_handle)
{
VK_ASSERT(context);
device = std::move(device_handle);
platform->event_device_created(device.get());
#ifdef HAVE_WSI_DXGI_INTEROP
dxgi.reset(new DXGIInteropSwapchain);
if (!dxgi->init_interop_device(*device))
dxgi.reset();
else
platform->get_frame_timer().reset();
#endif
return true;
}
#ifdef HAVE_WSI_DXGI_INTEROP
bool WSI::init_surface_swapchain_dxgi(unsigned width, unsigned height)
{
if (!dxgi)
return false;
// Anything fancy like compute present cannot use DXGI.
if (current_extra_usage)
return false;
HWND hwnd = reinterpret_cast<HWND>(platform->get_native_window());
if (!hwnd)
return false;
VkSurfaceFormatKHR format = {};
switch (current_backbuffer_format)
{
case BackbufferFormat::UNORM:
format = { VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
break;
case BackbufferFormat::sRGB:
format = { VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
break;
case BackbufferFormat::HDR10:
format = { VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_HDR10_ST2084_EXT };
break;
default:
return false;
}
constexpr unsigned num_images = 3;
if (!dxgi->init_swapchain(hwnd, format, width, height, num_images))
return false;
LOGI("Initialized DXGI interop swapchain!\n");
swapchain_width = width;
swapchain_height = height;
swapchain_aspect_ratio = platform->get_aspect_ratio();
swapchain_current_prerotate = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
swapchain_surface_format = dxgi->get_current_surface_format();
has_acquired_swapchain_index = false;
const uint32_t queue_present_support = 1u << context->get_queue_info().family_indices[QUEUE_INDEX_GRAPHICS];
device->set_swapchain_queue_family_support(queue_present_support);
swapchain_images.clear();
for (unsigned i = 0; i < num_images; i++)
swapchain_images.push_back(dxgi->get_vulkan_image(i));
device->init_swapchain(swapchain_images, swapchain_width, swapchain_height,
swapchain_surface_format.format,
swapchain_current_prerotate,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
platform->event_swapchain_destroyed();
platform->event_swapchain_created(device.get(), swapchain, swapchain_width, swapchain_height,
swapchain_aspect_ratio, num_images,
swapchain_surface_format.format,
swapchain_surface_format.colorSpace,
swapchain_current_prerotate);
return true;
}
#endif
bool WSI::init_surface_swapchain()
{
VK_ASSERT(surface == VK_NULL_HANDLE);
VK_ASSERT(context);
VK_ASSERT(device);
unsigned width = platform->get_surface_width();
unsigned height = platform->get_surface_height();
#ifdef HAVE_WSI_DXGI_INTEROP
if (init_surface_swapchain_dxgi(width, height))
return true;
else
dxgi.reset();
#endif
surface = platform->create_surface(context->get_instance(), context->get_gpu());
if (surface == VK_NULL_HANDLE)
{
LOGE("Failed to create VkSurfaceKHR.\n");
return false;
}
swapchain_aspect_ratio = platform->get_aspect_ratio();
VkBool32 supported = VK_FALSE;
uint32_t queue_present_support = 0;
// TODO: Ideally we need to create surface earlier and negotiate physical device based on that support.
for (auto &index : context->get_queue_info().family_indices)
{
if (index != VK_QUEUE_FAMILY_IGNORED)
{
if (vkGetPhysicalDeviceSurfaceSupportKHR(context->get_gpu(), index, surface, &supported) ==
VK_SUCCESS && supported)
{
queue_present_support |= 1u << index;
}
}
}
if ((queue_present_support & (1u << context->get_queue_info().family_indices[QUEUE_INDEX_GRAPHICS])) == 0)
{
LOGE("No presentation queue found for GPU. Is it connected to a display?\n");
return false;
}
device->set_swapchain_queue_family_support(queue_present_support);
if (!blocking_init_swapchain(width, height))
{
LOGE("Failed to create swapchain.\n");
return false;
}
device->init_swapchain(swapchain_images, swapchain_width, swapchain_height, swapchain_surface_format.format,
swapchain_current_prerotate,
current_extra_usage | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
platform->get_frame_timer().reset();
return true;
}
bool WSI::init_simple(unsigned num_thread_indices, const Context::SystemHandles &system_handles)
{
if (!init_context_from_platform(num_thread_indices, system_handles))
return false;
if (!init_device())
return false;
if (!init_surface_swapchain())
return false;
return true;
}
bool WSI::init_context_from_platform(unsigned num_thread_indices, const Context::SystemHandles &system_handles)
{
VK_ASSERT(platform);
auto instance_ext = platform->get_instance_extensions();
auto device_ext = platform->get_device_extensions();
auto new_context = Util::make_handle<Context>();
#ifdef HAVE_FFMPEG_VULKAN
constexpr ContextCreationFlags video_context_flags =
CONTEXT_CREATION_ENABLE_VIDEO_DECODE_BIT |
CONTEXT_CREATION_ENABLE_VIDEO_ENCODE_BIT |
CONTEXT_CREATION_ENABLE_VIDEO_H264_BIT |
CONTEXT_CREATION_ENABLE_VIDEO_H265_BIT;
#else
constexpr ContextCreationFlags video_context_flags = 0;
#endif
new_context->set_application_info(platform->get_application_info());
new_context->set_num_thread_indices(num_thread_indices);
new_context->set_system_handles(system_handles);
if (!new_context->init_instance(
instance_ext.data(), instance_ext.size(),
CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT | video_context_flags))
{
LOGE("Failed to create Vulkan instance.\n");
return false;
}
VkSurfaceKHR tmp_surface = platform->create_surface(new_context->get_instance(), VK_NULL_HANDLE);
bool ret = new_context->init_device(
VK_NULL_HANDLE, tmp_surface,
device_ext.data(), device_ext.size(),
CONTEXT_CREATION_ENABLE_ADVANCED_WSI_BIT | video_context_flags);
if (tmp_surface)
platform->destroy_surface(new_context->get_instance(), tmp_surface);
if (!ret)
{
LOGE("Failed to create Vulkan device.\n");
return false;
}
return init_from_existing_context(std::move(new_context));
}
void WSI::reinit_surface_and_swapchain(VkSurfaceKHR new_surface)
{
LOGI("init_surface_and_swapchain()\n");
if (new_surface != VK_NULL_HANDLE)
{
VK_ASSERT(surface == VK_NULL_HANDLE);
surface = new_surface;
}
swapchain_width = platform->get_surface_width();
swapchain_height = platform->get_surface_height();
update_framebuffer(swapchain_width, swapchain_height);
}
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<DeferredDeletion> keep;
size_t pending = deferred_swapchains.size();
for (auto &swap : deferred_swapchains)
{
if (!swap.fence || swap.fence->wait_timeout(0))
{
platform->destroy_swapchain_resources(swap.swapchain);
table->vkDestroySwapchainKHR(device->get_device(), swap.swapchain, nullptr);
}
else if (pending >= 2)
{
swap.fence->wait();
platform->destroy_swapchain_resources(swap.swapchain);
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();
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();
platform->destroy_swapchain_resources(old_swap.swapchain);
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()
{
#ifdef HAVE_WSI_DXGI_INTEROP
// We only do explicit teardown on exit.
dxgi.reset();
#endif
drain_swapchain(true);
platform->event_swapchain_destroyed();
platform->destroy_swapchain_resources(swapchain);
table->vkDestroySwapchainKHR(context->get_device(), swapchain, nullptr);
swapchain = VK_NULL_HANDLE;
has_acquired_swapchain_index = false;
present_id = 0;
present_last_id = 0;
}
void WSI::deinit_surface_and_swapchain()
{
LOGI("deinit_surface_and_swapchain()\n");
tear_down_swapchain();
if (surface != VK_NULL_HANDLE)
{
platform->destroy_surface(context->get_instance(), surface);
surface = VK_NULL_HANDLE;
}
}
void WSI::set_external_frame(unsigned index, Semaphore acquire_semaphore, double frame_time)
{
external_frame_index = index;
external_acquire = std::move(acquire_semaphore);
frame_is_external = true;
external_frame_time = frame_time;
}
bool WSI::begin_frame_external()
{
device->next_frame_context();
// Need to handle this stuff from outside.
if (has_acquired_swapchain_index)
return false;
auto frame_time = platform->get_frame_timer().frame(external_frame_time);
auto elapsed_time = platform->get_frame_timer().get_elapsed();
// Assume we have been given a smooth frame pacing.
smooth_frame_time = frame_time;
smooth_elapsed_time = elapsed_time;
// Poll after acquire as well for optimal latency.
platform->poll_input();
swapchain_index = external_frame_index;
platform->event_frame_tick(frame_time, elapsed_time);
platform->event_swapchain_index(device.get(), swapchain_index);
device->set_acquire_semaphore(swapchain_index, external_acquire);
external_acquire.reset();
return true;
}
Semaphore WSI::consume_external_release_semaphore()
{
Semaphore sem;
std::swap(external_release, sem);
return sem;
}
//#define VULKAN_WSI_TIMING_DEBUG
void WSI::wait_swapchain_latency()
{
unsigned effective_latency = low_latency_mode_enable ? 0 : present_frame_latency;
if (device->get_device_features().present_wait_features.presentWait &&
present_last_id > effective_latency &&
current_present_mode == PresentMode::SyncToVBlank)
{
// The effective latency is more like present_frame_latency + 1.
// If 0, we wait for vblank, and we must do CPU work and GPU work in one frame
// to hit next vblank.
uint64_t target = present_last_id - effective_latency;
#ifdef VULKAN_WSI_TIMING_DEBUG
auto begin_wait = Util::get_current_time_nsecs();
#endif
auto wait_ts = device->write_calibrated_timestamp();
VkResult wait_result = table->vkWaitForPresentKHR(context->get_device(), swapchain, target, UINT64_MAX);
device->register_time_interval("WSI", std::move(wait_ts),
device->write_calibrated_timestamp(), "wait_frame_latency");
if (wait_result != VK_SUCCESS)
LOGE("vkWaitForPresentKHR failed, vr %d.\n", wait_result);
#ifdef VULKAN_WSI_TIMING_DEBUG
auto end_wait = Util::get_current_time_nsecs();
LOGI("WaitForPresentKHR took %.3f ms.\n", 1e-6 * double(end_wait - begin_wait));
#endif
}
}
void WSI::set_low_latency_mode(bool enable)
{
low_latency_mode_enable = enable;
}
#ifdef HAVE_WSI_DXGI_INTEROP
bool WSI::begin_frame_dxgi()
{
Semaphore acquire;
while (!acquire)
{
if (!dxgi->acquire(acquire, swapchain_index))
return false;
acquire->signal_external();
has_acquired_swapchain_index = true;
// Poll after acquire as well for optimal latency.
platform->poll_input();
// Polling input may trigger a resize event. Trying to present in that situation without ResizeBuffers
// cause wonky issues on DXGI.
if (platform->should_resize())
update_framebuffer(platform->get_surface_width(), platform->get_surface_height());
// If update_framebuffer caused a resize, we won't have an acquire index anymore, reacquire.
if (!has_acquired_swapchain_index)
acquire.reset();
}
auto wait_ts = device->write_calibrated_timestamp();
if (!dxgi->wait_latency(present_frame_latency))
{
LOGE("Failed to wait on latency handle.\n");
return false;
}
device->register_time_interval("WSI", std::move(wait_ts), device->write_calibrated_timestamp(),
"DXGI wait latency");
auto frame_time = platform->get_frame_timer().frame();
auto elapsed_time = platform->get_frame_timer().get_elapsed();
smooth_frame_time = frame_time;
smooth_elapsed_time = elapsed_time;
platform->event_frame_tick(frame_time, elapsed_time);
platform->event_swapchain_index(device.get(), swapchain_index);
device->set_acquire_semaphore(swapchain_index, std::move(acquire));
return true;
}
#endif
bool WSI::begin_frame()
{
if (frame_is_external)
return begin_frame_external();
#ifdef VULKAN_WSI_TIMING_DEBUG
auto next_frame_start = Util::get_current_time_nsecs();
#endif
device->next_frame_context();
external_release.reset();
#ifdef VULKAN_WSI_TIMING_DEBUG
auto next_frame_end = Util::get_current_time_nsecs();
LOGI("Waited for vacant frame context for %.3f ms.\n", (next_frame_end - next_frame_start) * 1e-6);
#endif
#ifdef HAVE_WSI_DXGI_INTEROP
if (dxgi)
{
if (platform->should_resize())
update_framebuffer(platform->get_surface_width(), platform->get_surface_height());
if (has_acquired_swapchain_index)
return true;
return begin_frame_dxgi();
}
else
#endif
{
if (swapchain == VK_NULL_HANDLE || platform->should_resize() || swapchain_is_suboptimal)
update_framebuffer(platform->get_surface_width(), platform->get_surface_height());
if (has_acquired_swapchain_index)
return true;
}
if (swapchain == VK_NULL_HANDLE)
{
LOGE("Completely lost swapchain. Cannot continue.\n");
return false;
}
VkResult result;
do
{
auto acquire = device->request_semaphore(VK_SEMAPHORE_TYPE_BINARY);
#ifdef VULKAN_WSI_TIMING_DEBUG
auto acquire_start = Util::get_current_time_nsecs();
#endif
Fence fence;
// TODO: Improve this with fancier approaches as needed.
if (low_latency_mode_enable &&
!device->get_device_features().present_wait_features.presentWait &&
current_present_mode == PresentMode::SyncToVBlank)
{
fence = device->request_legacy_fence();
}
auto acquire_ts = device->write_calibrated_timestamp();
result = table->vkAcquireNextImageKHR(context->get_device(), swapchain, UINT64_MAX, acquire->get_semaphore(),
fence ? fence->get_fence() : VK_NULL_HANDLE, &swapchain_index);
device->register_time_interval("WSI", std::move(acquire_ts), device->write_calibrated_timestamp(), "acquire");
if (fence)
fence->wait();
#if defined(ANDROID)
// Android 10 can return suboptimal here, only because of pre-transform.
// We don't care about that, and treat this as success.
if (result == VK_SUBOPTIMAL_KHR && !support_prerotate)
result = VK_SUCCESS;
#endif
if (result == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
{
LOGE("Lost exclusive full-screen ...\n");
}
#ifdef VULKAN_WSI_TIMING_DEBUG
auto acquire_end = Util::get_current_time_nsecs();
LOGI("vkAcquireNextImageKHR took %.3f ms.\n", (acquire_end - acquire_start) * 1e-6);
#endif
if (result == VK_SUBOPTIMAL_KHR)
{
#ifdef VULKAN_DEBUG
LOGI("AcquireNextImageKHR is suboptimal, will recreate.\n");
#endif
#ifndef __APPLE__
swapchain_is_suboptimal = true;
LOGW("Swapchain suboptimal.\n");
#endif
}
if (result >= 0)
{
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();
auto elapsed_time = platform->get_frame_timer().get_elapsed();
smooth_frame_time = frame_time;
smooth_elapsed_time = elapsed_time;
// Poll after acquire as well for optimal latency.
platform->poll_input();
platform->event_frame_tick(frame_time, elapsed_time);
platform->event_swapchain_index(device.get(), swapchain_index);
device->set_acquire_semaphore(swapchain_index, acquire);
}
else if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
{
LOGW("Swapchain out of date.\n");
VK_ASSERT(swapchain_width != 0);
VK_ASSERT(swapchain_height != 0);
tear_down_swapchain();
if (!blocking_init_swapchain(swapchain_width, swapchain_height))
return false;
device->init_swapchain(swapchain_images, swapchain_width, swapchain_height,
swapchain_surface_format.format, swapchain_current_prerotate,
current_extra_usage | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
}
else
{
return false;
}
} while (result < 0);
return true;
}
#ifdef HAVE_WSI_DXGI_INTEROP
bool WSI::end_frame_dxgi()
{
auto release = device->consume_release_semaphore();
VK_ASSERT(release);
VK_ASSERT(release->is_signalled());
VK_ASSERT(!release->is_pending_wait());
return dxgi->present(std::move(release), current_present_mode == PresentMode::SyncToVBlank);
}
#endif
bool WSI::end_frame()
{
device->end_frame_context();
// Take ownership of the release semaphore so that the external user can use it.
if (frame_is_external)
{
// If we didn't render into the swapchain this frame, we will return a blank semaphore.
external_release = device->consume_release_semaphore();
VK_ASSERT(!external_release || external_release->is_signalled());
frame_is_external = false;
}
else
{
if (!device->swapchain_touched())
return true;
has_acquired_swapchain_index = false;
#ifdef HAVE_WSI_DXGI_INTEROP
if (dxgi)
return end_frame_dxgi();
#endif
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);
VkResult result = VK_SUCCESS;
VkPresentInfoKHR info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
info.waitSemaphoreCount = 1;
info.pWaitSemaphores = &release_semaphore;
info.swapchainCount = 1;
info.pSwapchains = &swapchain;
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;
present_id_info.pPresentIds = &present_id;
present_id++;
present_id_info.pNext = info.pNext;
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<void *>(info.pNext);
info.pNext = &present_fence;
present_mode_info.swapchainCount = 1;
present_mode_info.pPresentModes = &active_present_mode;
present_mode_info.pNext = const_cast<void *>(info.pNext);
info.pNext = &present_mode_info;
}
#ifdef VULKAN_WSI_TIMING_DEBUG
auto present_start = Util::get_current_time_nsecs();
#endif
auto present_ts = device->write_calibrated_timestamp();
device->external_queue_lock();
#if defined(ANDROID) && defined(HAVE_SWAPPY)
VkResult overall = SwappyVk_queuePresent(device->get_current_present_queue(), &info);
#else
VkResult overall = table->vkQueuePresentKHR(device->get_current_present_queue(), &info);
#endif
device->external_queue_unlock();
device->register_time_interval("WSI", std::move(present_ts), device->write_calibrated_timestamp(), "present");
#if defined(ANDROID)
// Android 10 can return suboptimal here, only because of pre-transform.
// We don't care about that, and treat this as success.
if (overall == VK_SUBOPTIMAL_KHR && !support_prerotate)
overall = VK_SUCCESS;
if (result == VK_SUBOPTIMAL_KHR && !support_prerotate)
result = VK_SUCCESS;
#endif
if (overall == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT ||
result == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
{
LOGE("Lost exclusive full-screen ...\n");
}
#ifdef VULKAN_WSI_TIMING_DEBUG
auto present_end = Util::get_current_time_nsecs();
LOGI("vkQueuePresentKHR took %.3f ms.\n", (present_end - present_start) * 1e-6);
#endif
// The presentID only seems to get updated if QueuePresent returns success.
// This makes sense I guess. Record the latest present ID which was successfully presented
// so we don't risk deadlock.
if ((result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) &&
device->get_device_features().present_id_features.presentId)
{
present_last_id = present_id;
}
if (overall == VK_SUBOPTIMAL_KHR || result == VK_SUBOPTIMAL_KHR)
{
#ifdef VULKAN_DEBUG
LOGI("QueuePresent is suboptimal, will recreate.\n");
#endif
#ifndef __APPLE__
swapchain_is_suboptimal = true;
#endif
}
// 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
{
// Cannot release the WSI wait semaphore until we observe that the image has been
// waited on again.
// Could make this a bit tighter with swapchain_maintenance1, but not that important here.
release_semaphores[swapchain_index] = std::move(release);
}
// Re-init swapchain.
if (present_mode != current_present_mode || backbuffer_format != current_backbuffer_format ||
extra_usage != current_extra_usage)
{
current_present_mode = present_mode;
current_backbuffer_format = backbuffer_format;
current_extra_usage = extra_usage;
update_framebuffer(swapchain_width, swapchain_height);
}
}
nonblock_delete_swapchains();
return true;
}
void WSI::update_framebuffer(unsigned width, unsigned height)
{
if (context && device)
{
#ifdef HAVE_WSI_DXGI_INTEROP
if (dxgi)
{
if (!init_surface_swapchain_dxgi(width, height))
LOGE("Failed to resize DXGI swapchain.\n");
}
else
#endif
{
drain_swapchain(false);
if (blocking_init_swapchain(width, height))
{
device->init_swapchain(swapchain_images, swapchain_width, swapchain_height,
swapchain_surface_format.format, swapchain_current_prerotate,
current_extra_usage | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
}
}
}
if (platform)
platform->notify_current_swapchain_dimensions(swapchain_width, swapchain_height);
}
bool WSI::update_active_presentation_mode(PresentMode mode)
{
if (current_present_mode == mode)
return true;
#ifdef HAVE_WSI_DXGI_INTEROP
// We set this on Present time.
if (dxgi)
{
current_present_mode = mode;
return true;
}
#endif
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)
{
if (!update_active_presentation_mode(present_mode))
{
current_present_mode = present_mode;
update_framebuffer(swapchain_width, swapchain_height);
}
}
}
void WSI::set_extra_usage_flags(VkImageUsageFlags usage)
{
extra_usage = usage;
if (!has_acquired_swapchain_index && extra_usage != current_extra_usage)
{
current_extra_usage = extra_usage;
update_framebuffer(swapchain_width, swapchain_height);
}
}
void WSI::set_backbuffer_format(BackbufferFormat format)
{
backbuffer_format = format;
if (!has_acquired_swapchain_index && backbuffer_format != current_backbuffer_format)
{
current_backbuffer_format = backbuffer_format;
update_framebuffer(swapchain_width, swapchain_height);
}
}
void WSI::set_backbuffer_srgb(bool enable)
{
set_backbuffer_format(enable ? BackbufferFormat::sRGB : BackbufferFormat::UNORM);
}
void WSI::teardown()
{
if (platform)
platform->release_resources();
if (context)
tear_down_swapchain();
if (surface != VK_NULL_HANDLE)
{
platform->destroy_surface(context->get_instance(), surface);
surface = VK_NULL_HANDLE;
}
if (platform)
platform->event_device_destroyed();
external_release.reset();
external_acquire.reset();
external_swapchain_images.clear();
device.reset();
context.reset();
}
bool WSI::blocking_init_swapchain(unsigned width, unsigned height)
{
SwapchainError err;
unsigned retry_counter = 0;
do
{
swapchain_aspect_ratio = platform->get_aspect_ratio();
err = init_swapchain(width, height);
if (err != SwapchainError::None)
platform->notify_current_swapchain_dimensions(0, 0);
if (err == SwapchainError::Error)
{
if (++retry_counter > 3)
return false;
// Try to not reuse the swapchain.
tear_down_swapchain();
}
else if (err == SwapchainError::NoSurface)
{
LOGW("WSI cannot make forward progress due to minimization, blocking ...\n");
device->set_enable_async_thread_frame_context(true);
platform->block_until_wsi_forward_progress(*this);
device->set_enable_async_thread_frame_context(false);
LOGW("Woke up!\n");
}
} while (err != SwapchainError::None);
return swapchain != VK_NULL_HANDLE;
}
VkSurfaceFormatKHR WSI::find_suitable_present_format(const std::vector<VkSurfaceFormatKHR> &formats, BackbufferFormat desired_format) const
{
size_t format_count = formats.size();
VkSurfaceFormatKHR format = { VK_FORMAT_UNDEFINED };
VkFormatFeatureFlags features = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT;
if ((current_extra_usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0)
features |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
if (format_count == 0)
{
LOGE("Surface has no formats?\n");
return format;
}
for (size_t i = 0; i < format_count; i++)
{
if (!device->image_format_is_supported(formats[i].format, features))
continue;
if (desired_format == BackbufferFormat::DisplayP3)
{
if (formats[i].colorSpace == VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT &&
(formats[i].format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 ||
formats[i].format == VK_FORMAT_A2R10G10B10_UNORM_PACK32))
{
format = formats[i];
break;
}
}
else if (desired_format == BackbufferFormat::UNORMPassthrough)
{
if (formats[i].colorSpace == VK_COLOR_SPACE_PASS_THROUGH_EXT &&
(formats[i].format == VK_FORMAT_R8G8B8A8_UNORM ||
formats[i].format == VK_FORMAT_B8G8R8A8_UNORM ||
formats[i].format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 ||
formats[i].format == VK_FORMAT_A2R10G10B10_UNORM_PACK32))
{
format = formats[i];
break;
}
}
else if (desired_format == BackbufferFormat::HDR10)
{
if (formats[i].colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT &&
(formats[i].format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 ||
formats[i].format == VK_FORMAT_A2R10G10B10_UNORM_PACK32))
{
format = formats[i];
break;
}
}
else if (desired_format == BackbufferFormat::sRGB)
{
if (formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR &&
(formats[i].format == VK_FORMAT_R8G8B8A8_SRGB ||
formats[i].format == VK_FORMAT_B8G8R8A8_SRGB ||
formats[i].format == VK_FORMAT_A8B8G8R8_SRGB_PACK32))
{
format = formats[i];
break;
}
}
else
{
if (formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR &&
(formats[i].format == VK_FORMAT_R8G8B8A8_UNORM ||
formats[i].format == VK_FORMAT_B8G8R8A8_UNORM ||
formats[i].format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 ||
formats[i].format == VK_FORMAT_A2R10G10B10_UNORM_PACK32 ||
formats[i].format == VK_FORMAT_A8B8G8R8_UNORM_PACK32))
{
format = formats[i];
break;
}
}
}
return format;
}
struct SurfaceInfo
{
VkPhysicalDeviceSurfaceInfo2KHR surface_info;
VkSurfacePresentModeEXT present_mode;
VkSurfaceCapabilitiesKHR surface_capabilities;
std::vector<VkSurfaceFormatKHR> formats;
VkSwapchainPresentModesCreateInfoEXT present_modes_info;
std::vector<VkPresentModeKHR> 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, bool low_latency_mode_enable)
{
if (surface == VK_NULL_HANDLE)
{
LOGE("Cannot create swapchain with surface == VK_NULL_HANDLE.\n");
return 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
if (ext.supports_full_screen_exclusive)
{
info.exclusive_info = { VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT };
auto monitor = reinterpret_cast<HMONITOR>(platform.get_fullscreen_monitor());
info.swapchain_pnext = &info.exclusive_info;
info.surface_info.pNext = &info.exclusive_info;
if (monitor != nullptr)
{
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");
}
else
LOGI("Win32: Not running full-screen.\n");
bool prefer_exclusive = Util::get_environment_bool("GRANITE_EXCLUSIVE_FULL_SCREEN", false) || low_latency_mode_enable;
if (ext.driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS)
prefer_exclusive = false; // Broken on Intel Windows
if (ext.driver_id == VK_DRIVER_ID_AMD_PROPRIETARY && format == BackbufferFormat::HDR10)
{
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("Win32: Opting out of exclusive full-screen!\n");
info.exclusive_info.fullScreenExclusive =
prefer_exclusive ? VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT : VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT;
}
}
#else
(void)platform;
(void)format;
#endif
std::vector<VkPresentModeKHR> 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 (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;
}
auto swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
bool use_vsync = present_mode == PresentMode::SyncToVBlank;
if (!use_vsync)
{
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;
#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;
}
}
}
if (swapchain_present_mode == VK_PRESENT_MODE_FIFO_KHR && low_latency_mode_enable)
for (auto mode : present_modes)
if (mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR)
swapchain_present_mode = mode;
LOGI("Using present mode: %u.\n", swapchain_present_mode);
// 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.
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;
}
else
{
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(gpu, surface, &info.surface_capabilities) != VK_SUCCESS)
return false;
}
// Make sure we query surface caps tied to the present mode for correct results.
if (ext.swapchain_maintenance1_features.swapchainMaintenance1 &&
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<VkPresentModeKHR> present_mode_compat_group;
present_mode_compat_group.resize(32);
present_mode_caps.presentModeCount = present_mode_compat_group.size();
present_mode_caps.pPresentModes = present_mode_compat_group.data();
info.present_mode.pNext = const_cast<void *>(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;
surface_capabilities2.pNext = &present_mode_caps;
if (vkGetPhysicalDeviceSurfaceCapabilities2KHR(gpu, &info.surface_info, &surface_capabilities2) != VK_SUCCESS)
return false;
surface_capabilities2.pNext = present_mode_caps.pNext;
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_FIFO_RELAXED_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 modify minImageCount.
// If image count changes, we should probably recreate the swapchain.
if (surface_capabilities2.surfaceCapabilities.minImageCount == info.surface_capabilities.minImageCount)
info.present_mode_compat_group.push_back(mode);
}
}
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<VkSurfaceFormat2KHR> formats2(format_count);
for (auto &f : formats2)
{
f = {};
f.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR;
}
if (vkGetPhysicalDeviceSurfaceFormats2KHR(gpu, &info.surface_info, &format_count, formats2.data()) != VK_SUCCESS)
return false;
info.formats.reserve(format_count);
for (auto &f : formats2)
info.formats.push_back(f.surfaceFormat);
}
else
{
if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &format_count, nullptr) != VK_SUCCESS)
return false;
info.formats.resize(format_count);
if (vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &format_count, info.formats.data()) != VK_SUCCESS)
return false;
}
// Ensure that 10-bit formats come before other formats.
std::sort(info.formats.begin(), info.formats.end(), [](const VkSurfaceFormatKHR &a, const VkSurfaceFormatKHR &b) {
const auto qual = [](VkFormat fmt) {
// Prefer a consistent ordering so Fossilize caches are more effective.
if (fmt == VK_FORMAT_A2B10G10R10_UNORM_PACK32)
return 3;
else if (fmt == VK_FORMAT_A2R10G10B10_UNORM_PACK32)
return 2;
else if (fmt == VK_FORMAT_B8G8R8A8_UNORM)
return 1;
else
return 0;
};
return qual(a.format) > qual(b.format);
});
// 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<void *>(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, low_latency_mode_enable))
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 & ~caps.supportedUsageFlags)
{
LOGW("Attempting to use unsupported usage flags 0x%x for swapchain.\n", current_extra_usage);
current_extra_usage &= caps.supportedUsageFlags;
extra_usage = current_extra_usage;
}
auto attempt_backbuffer_format = current_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 ||
attempt_backbuffer_format == BackbufferFormat::DisplayP3 ||
attempt_backbuffer_format == BackbufferFormat::UNORMPassthrough))
{
LOGW("Could not find suitable present format for HDR. Attempting fallback to UNORM.\n");
attempt_backbuffer_format = BackbufferFormat::UNORM;
surface_format = find_suitable_present_format(surface_info.formats, attempt_backbuffer_format);
}
if (surface_format.format == VK_FORMAT_UNDEFINED)
{
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(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[] = {
"IDENTITY_BIT_KHR",
"ROTATE_90_BIT_KHR",
"ROTATE_180_BIT_KHR",
"ROTATE_270_BIT_KHR",
"HORIZONTAL_MIRROR_BIT_KHR",
"HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR",
"HORIZONTAL_MIRROR_ROTATE_180_BIT_KHR",
"HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR",
"INHERIT_BIT_KHR",
};
LOGI("Current transform is enum 0x%x.\n", unsigned(caps.currentTransform));
for (unsigned i = 0; i <= 8; i++)
{
if (caps.supportedTransforms & (1u << i))
LOGI("Supported transform 0x%x: %s.\n", 1u << i, transform_names[i]);
}
VkSurfaceTransformFlagBitsKHR pre_transform;
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 ((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 = caps.currentTransform;
else
pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
}
if (pre_transform != caps.currentTransform)
{
LOGW("surfaceTransform (0x%x) != currentTransform (0x%u). Might get performance penalty.\n",
unsigned(pre_transform), unsigned(caps.currentTransform));
}
swapchain_current_prerotate = pre_transform;
VkExtent2D swapchain_size;
LOGI("Swapchain current extent: %d x %d\n",
int(caps.currentExtent.width),
int(caps.currentExtent.height));
if (width == 0)
{
if (caps.currentExtent.width != ~0u)
width = caps.currentExtent.width;
else
width = 1280;
LOGI("Auto selected width = %u.\n", width);
}
if (height == 0)
{
if (caps.currentExtent.height != ~0u)
height = caps.currentExtent.height;
else
height = 720;
LOGI("Auto selected height = %u.\n", height);
}
// Try to match the swapchain size up with what we expect w.r.t. aspect ratio.
float target_aspect_ratio = float(width) / float(height);
if ((swapchain_aspect_ratio > 1.0f && target_aspect_ratio < 1.0f) ||
(swapchain_aspect_ratio < 1.0f && target_aspect_ratio > 1.0f))
{
std::swap(width, height);
}
// If we are using pre-rotate of 90 or 270 degrees, we need to flip width and height again.
if (swapchain_current_prerotate &
(VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR | VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR |
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_90_BIT_KHR |
VK_SURFACE_TRANSFORM_HORIZONTAL_MIRROR_ROTATE_270_BIT_KHR))
{
std::swap(width, height);
}
// Clamp the target width, height to boundaries.
swapchain_size.width =
std::max(std::min(width, caps.maxImageExtent.width), caps.minImageExtent.width);
swapchain_size.height =
std::max(std::min(height, caps.maxImageExtent.height), caps.minImageExtent.height);
uint32_t desired_swapchain_images =
low_latency_mode_enable && current_present_mode == PresentMode::SyncToVBlank ? 2 : 3;
desired_swapchain_images = Util::get_environment_uint("GRANITE_VULKAN_SWAPCHAIN_IMAGES", desired_swapchain_images);
LOGI("Targeting %u swapchain images.\n", desired_swapchain_images);
if (desired_swapchain_images < caps.minImageCount)
desired_swapchain_images = caps.minImageCount;
if ((caps.maxImageCount > 0) && (desired_swapchain_images > caps.maxImageCount))
desired_swapchain_images = caps.maxImageCount;
VkCompositeAlphaFlagBitsKHR composite_mode = 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 (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
composite_mode = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
else if (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
composite_mode = VK_COMPOSITE_ALPHA_POST_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");
VkSwapchainKHR old_swapchain = swapchain;
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;
info.imageExtent.width = swapchain_size.width;
info.imageExtent.height = swapchain_size.height;
info.imageArrayLayers = 1;
info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | current_extra_usage;
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.preTransform = pre_transform;
info.compositeAlpha = composite_mode;
info.presentMode = surface_info.present_mode.presentMode;
info.clipped = VK_TRUE;
info.oldSwapchain = old_swapchain;
// Defer the deletion instead.
if (device->get_device_features().swapchain_maintenance1_features.swapchainMaintenance1 &&
old_swapchain != VK_NULL_HANDLE)
{
deferred_swapchains.push_back({ old_swapchain, last_present_fence });
old_swapchain = VK_NULL_HANDLE;
}
platform->event_swapchain_destroyed();
auto res = table->vkCreateSwapchainKHR(context->get_device(), &info, nullptr, &swapchain);
platform->destroy_swapchain_resources(old_swapchain);
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 (surface_info.exclusive_info.fullScreenExclusive == VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT)
{
bool success = vkAcquireFullScreenExclusiveModeEXT(context->get_device(), swapchain) == VK_SUCCESS;
if (success)
LOGI("Successfully acquired exclusive full-screen.\n");
else
LOGI("Failed to acquire exclusive full-screen. Using borderless windowed.\n");
}
#endif
if (res != VK_SUCCESS)
{
LOGE("Failed to create swapchain (code: %d)\n", int(res));
swapchain = VK_NULL_HANDLE;
return SwapchainError::Error;
}
swapchain_width = swapchain_size.width;
swapchain_height = swapchain_size.height;
swapchain_surface_format = surface_format;
swapchain_is_suboptimal = false;
LOGI("Created swapchain %u x %u (fmt: %u, transform: %u).\n", swapchain_width, swapchain_height,
unsigned(swapchain_surface_format.format), unsigned(swapchain_current_prerotate));
uint32_t image_count;
if (table->vkGetSwapchainImagesKHR(context->get_device(), swapchain, &image_count, nullptr) != VK_SUCCESS)
return SwapchainError::Error;
swapchain_images.resize(image_count);
release_semaphores.resize(image_count);
if (table->vkGetSwapchainImagesKHR(context->get_device(), swapchain, &image_count, swapchain_images.data()) != VK_SUCCESS)
return SwapchainError::Error;
LOGI("Got %u swapchain images.\n", image_count);
platform->event_swapchain_created(device.get(), swapchain, swapchain_width, swapchain_height,
swapchain_aspect_ratio, image_count,
swapchain_surface_format.format,
swapchain_surface_format.colorSpace,
swapchain_current_prerotate);
if (swapchain_surface_format.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT &&
device->get_device_features().supports_hdr_metadata)
{
table->vkSetHdrMetadataEXT(device->get_device(), 1, &swapchain, &hdr_metadata);
}
return SwapchainError::None;
}
void WSI::set_support_prerotate(bool enable)
{
support_prerotate = enable;
}
VkSurfaceTransformFlagBitsKHR WSI::get_current_prerotate() const
{
return swapchain_current_prerotate;
}
CommandBuffer::Type WSI::get_current_present_queue_type() const
{
return device->get_current_present_queue_type();
}
WSI::~WSI()
{
teardown();
}
void WSIPlatform::event_device_created(Device *) {}
void WSIPlatform::event_device_destroyed() {}
void WSIPlatform::event_swapchain_created(Device *, VkSwapchainKHR, unsigned, unsigned, float, size_t,
VkFormat, VkColorSpaceKHR,
VkSurfaceTransformFlagBitsKHR) {}
void WSIPlatform::event_swapchain_destroyed() {}
void WSIPlatform::destroy_swapchain_resources(VkSwapchainKHR) {}
void WSIPlatform::event_frame_tick(double, double) {}
void WSIPlatform::event_swapchain_index(Device *, unsigned) {}
void WSIPlatform::begin_drop_event() {}
void WSIPlatform::show_message_box(const std::string &, Vulkan::WSIPlatform::MessageType) {}
}