rexglue-skate3/src/ui/vulkan/vulkan_presenter.cpp

2810 lines
140 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
*/
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cmath>
#include <cstring>
#include <memory>
#include <utility>
#include <vector>
#include <rex/assert.h>
#include <rex/cvar.h>
#include <rex/logging.h>
#include <rex/math.h>
#include <rex/platform.h>
#include <rex/ui/vulkan/presenter.h>
#include <rex/ui/vulkan/util.h>
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
#include <ffx_api/ffx_api.h>
#include <ffx_api/ffx_upscale.h>
#include <ffx_api/vk/ffx_api_vk.h>
#endif
#if REX_PLATFORM_ANDROID
#include <rex/ui/surface_android.h>
#endif
#if REX_PLATFORM_GNU_LINUX
#include <rex/ui/surface_gnulinux.h>
#endif
#if REX_PLATFORM_MAC
#include <rex/ui/surface_sdl.h>
#endif
#if REX_PLATFORM_WIN32
#include <rex/ui/surface_win.h>
#endif
REXCVAR_DEFINE_BOOL(present_render_pass_clear, true, "UI/Presenter",
"Clear render pass during presentation");
REXCVAR_DEFINE_BOOL(vulkan_allow_present_mode_immediate, true, "UI/Vulkan",
"Allow immediate present mode (no vsync)");
REXCVAR_DEFINE_BOOL(vulkan_allow_present_mode_mailbox, true, "UI/Vulkan",
"Allow mailbox present mode (triple buffering)");
REXCVAR_DEFINE_BOOL(vulkan_allow_present_mode_fifo_relaxed, true, "UI/Vulkan",
"Allow FIFO relaxed present mode");
REXCVAR_DEFINE_BOOL(vulkan_present_timing_log, false, "UI/Vulkan",
"Log averaged Vulkan host presentation timing")
.lifecycle(rex::cvar::Lifecycle::kHotReload)
.debug_only();
REXCVAR_DEFINE_UINT32(vulkan_present_timing_interval, 120, "UI/Vulkan",
"Number of presented frames per Vulkan timing log entry")
.range(1, 10000)
.lifecycle(rex::cvar::Lifecycle::kHotReload)
.debug_only();
namespace rex {
namespace ui {
namespace vulkan {
namespace {
struct VulkanPresentTimingStats {
uint64_t frames = 0;
uint64_t ui_thread_frames = 0;
uint64_t acquire_us = 0;
uint64_t consume_and_record_us = 0;
uint64_t end_command_buffer_us = 0;
uint64_t submit_us = 0;
uint64_t present_us = 0;
uint64_t total_us = 0;
};
uint64_t ElapsedUs(std::chrono::steady_clock::time_point start,
std::chrono::steady_clock::time_point end) {
return uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count());
}
void AccumulateAndMaybeLogPresentTiming(bool execute_ui_drawers, uint64_t acquire_us,
uint64_t consume_and_record_us,
uint64_t end_command_buffer_us, uint64_t submit_us,
uint64_t present_us, uint64_t total_us) {
static VulkanPresentTimingStats stats;
if (!REXCVAR_GET(vulkan_present_timing_log)) {
stats = {};
return;
}
++stats.frames;
if (execute_ui_drawers) {
++stats.ui_thread_frames;
}
stats.acquire_us += acquire_us;
stats.consume_and_record_us += consume_and_record_us;
stats.end_command_buffer_us += end_command_buffer_us;
stats.submit_us += submit_us;
stats.present_us += present_us;
stats.total_us += total_us;
const uint32_t interval = std::max(uint32_t(1), REXCVAR_GET(vulkan_present_timing_interval));
if (stats.frames < interval) {
return;
}
const uint64_t frames = stats.frames;
REXLOG_INFO(
"VulkanPresenter timing: frames={}, ui_thread={}, avg_total_us={}, avg_acquire_us={}, "
"avg_record_us={}, avg_end_cmd_us={}, avg_submit_us={}, avg_present_us={}",
frames, stats.ui_thread_frames, stats.total_us / frames, stats.acquire_us / frames,
stats.consume_and_record_us / frames, stats.end_command_buffer_us / frames,
stats.submit_us / frames, stats.present_us / frames);
stats = {};
}
} // namespace
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
namespace {
std::atomic<PFN_vkGetDeviceProcAddr> g_ffx_vk_get_device_proc_addr{nullptr};
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL FfxVkGetDeviceProcAddrCompat(VkDevice device,
const char* p_name) {
if (!p_name) {
return nullptr;
}
PFN_vkGetDeviceProcAddr get_device_proc_addr =
g_ffx_vk_get_device_proc_addr.load(std::memory_order_relaxed);
if (!get_device_proc_addr) {
return nullptr;
}
PFN_vkVoidFunction proc = get_device_proc_addr(device, p_name);
if (proc) {
return proc;
}
// Some drivers expose Vulkan 1.1+ core entry points, but not the legacy KHR
// aliases expected by parts of FidelityFX VK backend initialization.
if (!std::strcmp(p_name, "vkGetBufferMemoryRequirements2KHR")) {
return get_device_proc_addr(device, "vkGetBufferMemoryRequirements2");
}
if (!std::strcmp(p_name, "vkGetImageMemoryRequirements2KHR")) {
return get_device_proc_addr(device, "vkGetImageMemoryRequirements2");
}
if (!std::strcmp(p_name, "vkBindBufferMemory2KHR")) {
return get_device_proc_addr(device, "vkBindBufferMemory2");
}
if (!std::strcmp(p_name, "vkBindImageMemory2KHR")) {
return get_device_proc_addr(device, "vkBindImageMemory2");
}
return nullptr;
}
} // namespace
#endif // defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
// Generated with `xb buildshaders`.
namespace shaders {
#include "../shaders/vulkan_spirv/guest_output_bilinear_dither_ps.h"
#include "../shaders/vulkan_spirv/guest_output_bilinear_ps.h"
#if defined(REX_HAS_FIDELITYFX_SDK)
#include "../shaders/vulkan_spirv/guest_output_ffx_cas_resample_dither_ps.h"
#include "../shaders/vulkan_spirv/guest_output_ffx_cas_resample_ps.h"
#include "../shaders/vulkan_spirv/guest_output_ffx_cas_sharpen_dither_ps.h"
#include "../shaders/vulkan_spirv/guest_output_ffx_cas_sharpen_ps.h"
#include "../shaders/vulkan_spirv/guest_output_ffx_fsr_easu_ps.h"
#include "../shaders/vulkan_spirv/guest_output_ffx_fsr_rcas_dither_ps.h"
#include "../shaders/vulkan_spirv/guest_output_ffx_fsr_rcas_ps.h"
#endif
#include "../shaders/vulkan_spirv/guest_output_triangle_strip_rect_vs.h"
} // namespace shaders
VulkanPresenter::PaintContext::Submission::~Submission() {
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
if (draw_command_pool_ != VK_NULL_HANDLE) {
dfn.vkDestroyCommandPool(device, draw_command_pool_, nullptr);
}
if (acquire_semaphore_ != VK_NULL_HANDLE) {
dfn.vkDestroySemaphore(device, acquire_semaphore_, nullptr);
}
}
bool VulkanPresenter::PaintContext::Submission::Initialize() {
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkSemaphoreCreateInfo semaphore_create_info;
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphore_create_info.pNext = nullptr;
semaphore_create_info.flags = 0;
if (dfn.vkCreateSemaphore(device, &semaphore_create_info, nullptr, &acquire_semaphore_) !=
VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create a swapchain image acquisition "
"semaphore");
return false;
}
// Note: the presentation-wait semaphores are per-swapchain-image, not
// per-submission - see PaintContext::swapchain_present_semaphores.
VkCommandPoolCreateInfo command_pool_create_info;
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
command_pool_create_info.pNext = nullptr;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
command_pool_create_info.queueFamilyIndex = vulkan_device_->queue_family_graphics_compute();
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr, &draw_command_pool_) !=
VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create a command pool for drawing to a "
"swapchain");
return false;
}
VkCommandBufferAllocateInfo command_buffer_allocate_info;
command_buffer_allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_allocate_info.pNext = nullptr;
command_buffer_allocate_info.commandPool = draw_command_pool_;
command_buffer_allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
command_buffer_allocate_info.commandBufferCount = 1;
if (dfn.vkAllocateCommandBuffers(device, &command_buffer_allocate_info, &draw_command_buffer_) !=
VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to allocate a command buffer for drawing to a "
"swapchain");
return false;
}
return true;
}
VulkanPresenter::~VulkanPresenter() {
// Destroy the swapchain after its images are not used for drawing anymore.
// This is a confusing part in Vulkan, as vkQueuePresentKHR doesn't signal a
// fence clearly indicating when it's safe to destroy a swapchain, so we
// assume that its lifetime is tracked internally in the WSI. This is also
// done before destroying the semaphore awaited by vkQueuePresentKHR, hoping
// that it will prevent the destruction during the semaphore wait in
// vkQueuePresentKHR execution (or between the vkQueueSubmit semaphore signal
// and the vkQueuePresentKHR semaphore wait).
// This will await completion of all paint submissions also.
paint_context_.DestroySwapchainAndVulkanSurface();
// Await completion of the usage of everything before destroying anything
// (paint submission completion already awaited).
// From most likely the latest to most likely the earliest to be signaled, so
// just one sleep will likely be needed.
ui_submission_tracker_.Shutdown();
guest_output_image_refresher_submission_tracker_.Shutdown();
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
DestroyTemporalUpscalerContext();
#endif
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
// All paint submissions have completed and the swapchains (whose pending
// present operations' semaphore waits are assumed to be tracked internally
// by the WSI, as described above) have been destroyed - the present-wait
// semaphores of the current and all retired swapchains
// (PrepareForSwapchainRetirement moves the current ones into the retired
// list) can now be destroyed.
for (VkSemaphore retired_present_semaphore : paint_context_.retired_present_semaphores) {
dfn.vkDestroySemaphore(device, retired_present_semaphore, nullptr);
}
paint_context_.retired_present_semaphores.clear();
if (paint_context_.swapchain_render_pass != VK_NULL_HANDLE) {
dfn.vkDestroyRenderPass(device, paint_context_.swapchain_render_pass, nullptr);
}
for (const PaintContext::UISetupCommandBuffer& ui_setup_command_buffer :
paint_context_.ui_setup_command_buffers) {
dfn.vkDestroyCommandPool(device, ui_setup_command_buffer.command_pool, nullptr);
}
for (VkFramebuffer& framebuffer : paint_context_.guest_output_intermediate_framebuffers) {
util::DestroyAndNullHandle(dfn.vkDestroyFramebuffer, device, framebuffer);
}
util::DestroyAndNullHandle(dfn.vkDestroyDescriptorPool, device,
paint_context_.guest_output_descriptor_pool);
for (PaintContext::GuestOutputPaintPipeline& guest_output_paint_pipeline :
paint_context_.guest_output_paint_pipelines) {
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
guest_output_paint_pipeline.swapchain_pipeline);
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
guest_output_paint_pipeline.intermediate_pipeline);
}
util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device,
guest_output_intermediate_render_pass_);
for (VkShaderModule& shader_module : guest_output_paint_fs_) {
util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, shader_module);
}
util::DestroyAndNullHandle(dfn.vkDestroyShaderModule, device, guest_output_paint_vs_);
for (VkPipelineLayout& pipeline_layout : guest_output_paint_pipeline_layouts_) {
util::DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, device, pipeline_layout);
}
util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, device,
guest_output_paint_image_descriptor_set_layout_);
}
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
void VulkanPresenter::DestroyTemporalUpscalerContext() {
if (!temporal_upscaler_context_) {
return;
}
ffxContext* context = reinterpret_cast<ffxContext*>(&temporal_upscaler_context_);
ffxDestroyContext(context, nullptr);
temporal_upscaler_context_ = nullptr;
temporal_upscaler_max_render_width_ = 0;
temporal_upscaler_max_render_height_ = 0;
temporal_upscaler_max_output_width_ = 0;
temporal_upscaler_max_output_height_ = 0;
}
bool VulkanPresenter::EnsureTemporalUpscalerContext(uint32_t render_width, uint32_t render_height,
uint32_t output_width, uint32_t output_height) {
if (!temporal_upscaler_context_ || temporal_upscaler_max_render_width_ != render_width ||
temporal_upscaler_max_render_height_ != render_height ||
temporal_upscaler_max_output_width_ != output_width ||
temporal_upscaler_max_output_height_ != output_height) {
DestroyTemporalUpscalerContext();
ffxCreateContextDescUpscale create_desc = {};
create_desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
create_desc.header.pNext = nullptr;
create_desc.flags = FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE | FFX_UPSCALE_ENABLE_AUTO_EXPOSURE;
create_desc.maxRenderSize.width = render_width;
create_desc.maxRenderSize.height = render_height;
create_desc.maxUpscaleSize.width = output_width;
create_desc.maxUpscaleSize.height = output_height;
create_desc.fpMessage = nullptr;
ffxCreateBackendVKDesc backend_desc = {};
backend_desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
backend_desc.header.pNext = nullptr;
backend_desc.vkDevice = vulkan_device_->device();
backend_desc.vkPhysicalDevice = vulkan_device_->physical_device();
PFN_vkGetDeviceProcAddr vk_device_proc_addr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(
vulkan_device_->vulkan_instance()->functions().vkGetInstanceProcAddr(
vulkan_device_->vulkan_instance()->instance(), "vkGetDeviceProcAddr"));
if (!vk_device_proc_addr) {
REXLOG_WARN("VulkanPresenter: vkGetDeviceProcAddr is unavailable for FidelityFX");
return false;
}
g_ffx_vk_get_device_proc_addr.store(vk_device_proc_addr, std::memory_order_relaxed);
backend_desc.vkDeviceProcAddr = FfxVkGetDeviceProcAddrCompat;
create_desc.header.pNext = &backend_desc.header;
ffxContext* context = reinterpret_cast<ffxContext*>(&temporal_upscaler_context_);
if (ffxCreateContext(context, &create_desc.header, nullptr) != FFX_API_RETURN_OK) {
REXLOG_WARN(
"VulkanPresenter: Failed to create FidelityFX temporal upscaler "
"context");
temporal_upscaler_context_ = nullptr;
return false;
}
temporal_upscaler_max_render_width_ = render_width;
temporal_upscaler_max_render_height_ = render_height;
temporal_upscaler_max_output_width_ = output_width;
temporal_upscaler_max_output_height_ = output_height;
temporal_upscaler_provider_logged_ = false;
}
if (!temporal_upscaler_provider_logged_) {
ffxQueryGetProviderVersion provider_version = {};
provider_version.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
provider_version.header.pNext = nullptr;
ffxContext* context = reinterpret_cast<ffxContext*>(&temporal_upscaler_context_);
if (ffxQuery(context, &provider_version.header) == FFX_API_RETURN_OK &&
provider_version.versionName) {
REXLOG_INFO("VulkanPresenter: FidelityFX upscaler provider {}", provider_version.versionName);
}
temporal_upscaler_provider_logged_ = true;
}
return temporal_upscaler_context_ != nullptr;
}
bool VulkanPresenter::DispatchTemporalUpscaler(VkCommandBuffer command_buffer, VkImage input_image,
uint32_t input_width, uint32_t input_height,
VkImage output_image, uint32_t output_width,
uint32_t output_height,
const GuestOutputPaintConfig& config) {
if (command_buffer == VK_NULL_HANDLE || input_image == VK_NULL_HANDLE ||
output_image == VK_NULL_HANDLE || !input_width || !input_height || !output_width ||
!output_height) {
return false;
}
if (!EnsureTemporalUpscalerContext(input_width, input_height, output_width, output_height)) {
return false;
}
VkImageCreateInfo source_image_create_info = {};
source_image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
source_image_create_info.imageType = VK_IMAGE_TYPE_2D;
source_image_create_info.format = kGuestOutputFormat;
source_image_create_info.extent.width = input_width;
source_image_create_info.extent.height = input_height;
source_image_create_info.extent.depth = 1;
source_image_create_info.mipLevels = 1;
source_image_create_info.arrayLayers = 1;
source_image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
source_image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
source_image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
source_image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
source_image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
FfxApiResourceDescription source_desc = ffxApiGetImageResourceDescriptionVK(
input_image, source_image_create_info, FFX_API_RESOURCE_USAGE_READ_ONLY);
FfxApiResource source = ffxApiGetResourceVK(reinterpret_cast<void*>(input_image), source_desc,
FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
FfxApiResource depth =
ffxApiGetResourceVK(reinterpret_cast<void*>(input_image),
ffxApiGetImageResourceDescriptionVK(input_image, source_image_create_info,
FFX_API_RESOURCE_USAGE_DEPTHTARGET),
FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ);
FfxApiResource motion_vectors = source;
VkImageCreateInfo output_image_create_info = source_image_create_info;
output_image_create_info.extent.width = output_width;
output_image_create_info.extent.height = output_height;
FfxApiResourceDescription output_desc = ffxApiGetImageResourceDescriptionVK(
output_image, output_image_create_info,
FFX_API_RESOURCE_USAGE_UAV | FFX_API_RESOURCE_USAGE_READ_ONLY);
FfxApiResource output = ffxApiGetResourceVK(reinterpret_cast<void*>(output_image), output_desc,
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
ffxDispatchDescUpscale dispatch_desc = {};
dispatch_desc.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE;
dispatch_desc.header.pNext = nullptr;
dispatch_desc.commandList = command_buffer;
dispatch_desc.color = source;
dispatch_desc.depth = depth;
dispatch_desc.motionVectors = motion_vectors;
dispatch_desc.exposure = {};
dispatch_desc.reactive = {};
dispatch_desc.transparencyAndComposition = {};
dispatch_desc.output = output;
dispatch_desc.jitterOffset.x = 0.0f;
dispatch_desc.jitterOffset.y = 0.0f;
dispatch_desc.motionVectorScale.x = float(input_width);
dispatch_desc.motionVectorScale.y = float(input_height);
dispatch_desc.renderSize.width = input_width;
dispatch_desc.renderSize.height = input_height;
dispatch_desc.upscaleSize.width = output_width;
dispatch_desc.upscaleSize.height = output_height;
dispatch_desc.enableSharpening = true;
dispatch_desc.sharpness = std::clamp(1.0f - config.GetFsrSharpnessReduction() * 0.5f, 0.0f, 1.0f);
dispatch_desc.reset = true;
dispatch_desc.frameTimeDelta = 16.666f;
dispatch_desc.preExposure = 1.0f;
dispatch_desc.cameraNear = 0.1f;
dispatch_desc.cameraFar = 1000.0f;
dispatch_desc.cameraFovAngleVertical = 1.0472f;
dispatch_desc.viewSpaceToMetersFactor = 1.0f;
dispatch_desc.flags = 0;
ffxContext* context = reinterpret_cast<ffxContext*>(&temporal_upscaler_context_);
if (ffxDispatch(context, &dispatch_desc.header) != FFX_API_RETURN_OK) {
return false;
}
return true;
}
#endif
Surface::TypeFlags VulkanPresenter::GetSurfaceTypesSupportedByInstance(
const VulkanInstance::Extensions& instance_extensions) {
if (!instance_extensions.ext_KHR_surface) {
return 0;
}
Surface::TypeFlags type_flags = 0;
#if REX_PLATFORM_ANDROID
if (instance_extensions.ext_KHR_android_surface) {
type_flags |= Surface::kTypeFlag_AndroidNativeWindow;
}
#endif
#if REX_PLATFORM_GNU_LINUX
if (instance_extensions.ext_KHR_xcb_surface) {
type_flags |= Surface::kTypeFlag_XcbWindow;
}
#endif
#if REX_PLATFORM_MAC
if (instance_extensions.ext_EXT_metal_surface) {
type_flags |= Surface::kTypeFlag_SDLMetalView;
}
#endif
#if REX_PLATFORM_WIN32
if (instance_extensions.ext_KHR_win32_surface) {
type_flags |= Surface::kTypeFlag_Win32Hwnd;
}
#endif
return type_flags;
}
Surface::TypeFlags VulkanPresenter::GetSupportedSurfaceTypes() const {
if (!vulkan_device_->extensions().ext_KHR_swapchain) {
return 0;
}
return GetSurfaceTypesSupportedByInstance(vulkan_device_->vulkan_instance()->extensions());
}
bool VulkanPresenter::CaptureGuestOutput(RawImage& image_out) {
std::shared_ptr<GuestOutputImage> guest_output_image;
{
uint32_t guest_output_mailbox_index;
std::unique_lock<std::mutex> guest_output_consumer_lock(
ConsumeGuestOutput(guest_output_mailbox_index, nullptr, nullptr));
if (guest_output_mailbox_index != UINT32_MAX) {
assert_true(guest_output_images_[guest_output_mailbox_index].ever_successfully_refreshed);
guest_output_image = guest_output_images_[guest_output_mailbox_index].image;
}
// Incremented the reference count of the guest output image - safe to leave
// the consumer critical section now.
}
if (!guest_output_image) {
return false;
}
VkExtent2D image_extent = guest_output_image->extent();
size_t pixel_count = size_t(image_extent.width) * image_extent.height;
VkDeviceSize buffer_size = VkDeviceSize(sizeof(uint32_t) * pixel_count);
VkBuffer buffer;
VkDeviceMemory buffer_memory;
if (!util::CreateDedicatedAllocationBuffer(
vulkan_device_, buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT,
util::MemoryPurpose::kReadback, buffer, buffer_memory)) {
REXLOG_ERROR("VulkanPresenter: Failed to create the guest output capture buffer");
return false;
}
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
{
VkCommandPoolCreateInfo command_pool_create_info;
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
command_pool_create_info.pNext = nullptr;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
command_pool_create_info.queueFamilyIndex = vulkan_device_->queue_family_graphics_compute();
VkCommandPool command_pool;
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr, &command_pool) !=
VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the guest output capturing "
"command pool");
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
VkCommandBufferAllocateInfo command_buffer_allocate_info;
command_buffer_allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_allocate_info.pNext = nullptr;
command_buffer_allocate_info.commandPool = command_pool;
command_buffer_allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
command_buffer_allocate_info.commandBufferCount = 1;
VkCommandBuffer command_buffer;
if (dfn.vkAllocateCommandBuffers(device, &command_buffer_allocate_info, &command_buffer) !=
VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to allocate the guest output capturing "
"command buffer");
dfn.vkDestroyCommandPool(device, command_pool, nullptr);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
VkCommandBufferBeginInfo command_buffer_begin_info;
command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
command_buffer_begin_info.pNext = nullptr;
command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
command_buffer_begin_info.pInheritanceInfo = nullptr;
if (dfn.vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to begin recording the guest output "
"capturing command buffer");
dfn.vkDestroyCommandPool(device, command_pool, nullptr);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
VkImageMemoryBarrier image_memory_barrier;
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.pNext = nullptr;
image_memory_barrier.srcAccessMask = kGuestOutputInternalAccessMask;
image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
image_memory_barrier.oldLayout = kGuestOutputInternalLayout;
image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = guest_output_image->image();
image_memory_barrier.subresourceRange = util::InitializeSubresourceRange();
dfn.vkCmdPipelineBarrier(command_buffer, kGuestOutputInternalStageMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1,
&image_memory_barrier);
VkBufferImageCopy buffer_image_copy = {};
buffer_image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
buffer_image_copy.imageSubresource.layerCount = 1;
buffer_image_copy.imageExtent.width = image_extent.width;
buffer_image_copy.imageExtent.height = image_extent.height;
buffer_image_copy.imageExtent.depth = 1;
dfn.vkCmdCopyImageToBuffer(command_buffer, guest_output_image->image(),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, 1, &buffer_image_copy);
// A fence doesn't guarantee host visibility and availability.
VkBufferMemoryBarrier buffer_memory_barrier;
buffer_memory_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
buffer_memory_barrier.pNext = nullptr;
buffer_memory_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
buffer_memory_barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
buffer_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
buffer_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
buffer_memory_barrier.buffer = buffer;
buffer_memory_barrier.offset = 0;
buffer_memory_barrier.size = VK_WHOLE_SIZE;
std::swap(image_memory_barrier.srcAccessMask, image_memory_barrier.dstAccessMask);
std::swap(image_memory_barrier.oldLayout, image_memory_barrier.newLayout);
dfn.vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_HOST_BIT | kGuestOutputInternalStageMask, 0, 0,
nullptr, 1, &buffer_memory_barrier, 1, &image_memory_barrier);
if (dfn.vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to end recording the guest output capturing "
"command buffer");
dfn.vkDestroyCommandPool(device, command_pool, nullptr);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
VulkanSubmissionTracker submission_tracker(vulkan_device_);
{
VulkanSubmissionTracker::FenceAcquisition fence_acqusition(
submission_tracker.AcquireFenceToAdvanceSubmission());
if (!fence_acqusition.fence()) {
REXLOG_ERROR(
"VulkanPresenter: Failed to acquire a fence for guest output "
"capturing");
fence_acqusition.SubmissionFailedOrDropped();
dfn.vkDestroyCommandPool(device, command_pool, nullptr);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
VkResult submit_result;
{
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(vulkan_device_->queue_family_graphics_compute(), 0);
submit_result =
dfn.vkQueueSubmit(queue_acquisition.queue(), 1, &submit_info, fence_acqusition.fence());
}
if (submit_result != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to submit the guest output capturing "
"command buffer ({})",
vk::to_string(vk::Result(submit_result)));
fence_acqusition.SubmissionFailedOrDropped();
dfn.vkDestroyCommandPool(device, command_pool, nullptr);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
}
if (!submission_tracker.AwaitAllSubmissionsCompletion()) {
REXLOG_ERROR("VulkanPresenter: Failed to await the guest output capturing fence");
dfn.vkDestroyCommandPool(device, command_pool, nullptr);
dfn.vkDestroyBuffer(device, buffer, nullptr);
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
dfn.vkDestroyCommandPool(device, command_pool, nullptr);
}
// Don't need the buffer anymore, just its memory.
dfn.vkDestroyBuffer(device, buffer, nullptr);
void* mapping;
if (dfn.vkMapMemory(device, buffer_memory, 0, VK_WHOLE_SIZE, 0, &mapping) != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to map the guest output capture memory");
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return false;
}
image_out.width = image_extent.width;
image_out.height = image_extent.height;
image_out.stride = sizeof(uint32_t) * image_extent.width;
image_out.data.resize(size_t(buffer_size));
uint32_t* image_out_pixels = reinterpret_cast<uint32_t*>(image_out.data.data());
for (size_t i = 0; i < pixel_count; ++i) {
image_out_pixels[i] = Packed10bpcRGBTo8bpcBytes(reinterpret_cast<const uint32_t*>(mapping)[i]);
}
// Unmapping will be done by freeing.
dfn.vkFreeMemory(device, buffer_memory, nullptr);
return true;
}
VkCommandBuffer VulkanPresenter::AcquireUISetupCommandBufferFromUIThread() {
if (paint_context_.ui_setup_command_buffer_current_index != SIZE_MAX) {
return paint_context_
.ui_setup_command_buffers[paint_context_.ui_setup_command_buffer_current_index]
.command_buffer;
}
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkCommandBufferBeginInfo command_buffer_begin_info;
command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
command_buffer_begin_info.pNext = nullptr;
command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
command_buffer_begin_info.pInheritanceInfo = nullptr;
// Try to reuse an existing command buffer.
if (!paint_context_.ui_setup_command_buffers.empty()) {
uint64_t submission_index_completed = ui_submission_tracker_.UpdateAndGetCompletedSubmission();
for (size_t i = 0; i < paint_context_.ui_setup_command_buffers.size(); ++i) {
PaintContext::UISetupCommandBuffer& ui_setup_command_buffer =
paint_context_.ui_setup_command_buffers[i];
if (ui_setup_command_buffer.last_usage_submission_index > submission_index_completed) {
continue;
}
if (dfn.vkResetCommandPool(device, ui_setup_command_buffer.command_pool, 0) != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to reset a UI setup command pool");
return VK_NULL_HANDLE;
}
if (dfn.vkBeginCommandBuffer(ui_setup_command_buffer.command_buffer,
&command_buffer_begin_info) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to begin UI setup command buffer "
"recording");
return VK_NULL_HANDLE;
}
paint_context_.ui_setup_command_buffer_current_index = i;
ui_setup_command_buffer.last_usage_submission_index =
ui_submission_tracker_.GetCurrentSubmission();
return ui_setup_command_buffer.command_buffer;
}
}
// Create a new command buffer.
VkCommandPoolCreateInfo command_pool_create_info;
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
command_pool_create_info.pNext = nullptr;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
command_pool_create_info.queueFamilyIndex = vulkan_device_->queue_family_graphics_compute();
VkCommandPool new_command_pool;
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr, &new_command_pool) !=
VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to create a UI setup command pool");
return VK_NULL_HANDLE;
}
VkCommandBufferAllocateInfo command_buffer_allocate_info;
command_buffer_allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_allocate_info.pNext = nullptr;
command_buffer_allocate_info.commandPool = new_command_pool;
command_buffer_allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
command_buffer_allocate_info.commandBufferCount = 1;
VkCommandBuffer new_command_buffer;
if (dfn.vkAllocateCommandBuffers(device, &command_buffer_allocate_info, &new_command_buffer) !=
VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to allocate a UI setup command buffer");
dfn.vkDestroyCommandPool(device, new_command_pool, nullptr);
return VK_NULL_HANDLE;
}
if (dfn.vkBeginCommandBuffer(new_command_buffer, &command_buffer_begin_info) != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to begin UI setup command buffer recording");
dfn.vkDestroyCommandPool(device, new_command_pool, nullptr);
return VK_NULL_HANDLE;
}
paint_context_.ui_setup_command_buffer_current_index =
paint_context_.ui_setup_command_buffers.size();
paint_context_.ui_setup_command_buffers.emplace_back(
new_command_pool, new_command_buffer, ui_submission_tracker_.GetCurrentSubmission());
return new_command_buffer;
}
Presenter::SurfacePaintConnectResult
VulkanPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread(Surface& new_surface,
uint32_t new_surface_width,
uint32_t new_surface_height,
bool was_paintable,
bool& is_vsync_implicit_out) {
const VulkanInstance* const vulkan_instance = vulkan_device_->vulkan_instance();
const VulkanInstance::Functions& ifn = vulkan_instance->functions();
const VkInstance instance = vulkan_instance->instance();
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkFormat new_swapchain_format;
// ConnectOrReconnectToSurfaceFromUIThread may be called only for the
// ui::Surface of the current swapchain or when the old swapchain and
// VkSurface have, if the ui::Surface is the same, try using the existing
// VkSurface and creating the swapchain smoothly from the existing one - if
// this doesn't succeed, start from scratch.
// The retirement or destruction of the swapchain here will also cause
// awaiting completion of the usage of the swapchain and the surface on the
// GPU.
#if REX_PLATFORM_MAC
// Native macOS fullscreen transitions can reconfigure the CAMetalLayer behind
// MoltenVK without making the existing VkSurface visibly recoverable. Rebind
// from the layer instead of preserving the old surface through the transition.
if (paint_context_.vulkan_surface != VK_NULL_HANDLE) {
paint_context_.DestroySwapchainAndVulkanSurface();
}
#endif
if (paint_context_.vulkan_surface != VK_NULL_HANDLE) {
VkSwapchainKHR old_swapchain = paint_context_.PrepareForSwapchainRetirement();
bool surface_unusable;
paint_context_.swapchain = PaintContext::CreateSwapchainForVulkanSurface(
vulkan_device_, paint_context_.vulkan_surface, new_surface_width, new_surface_height,
old_swapchain, paint_context_.present_queue_family, new_swapchain_format,
paint_context_.swapchain_extent, paint_context_.swapchain_is_fifo, surface_unusable);
// Destroy the old swapchain that may be retired now.
if (old_swapchain != VK_NULL_HANDLE) {
dfn.vkDestroySwapchainKHR(device, old_swapchain, nullptr);
}
if (paint_context_.swapchain == VK_NULL_HANDLE) {
// Couldn't create the swapchain for the existing surface - start over.
paint_context_.DestroySwapchainAndVulkanSurface();
}
}
// If failed to create the swapchain for the previous surface, recreate the
// surface and create the new swapchain.
if (paint_context_.swapchain == VK_NULL_HANDLE) {
// DestroySwapchainAndVulkanSurface should have been called previously.
assert_true(paint_context_.vulkan_surface == VK_NULL_HANDLE);
Surface::TypeIndex surface_type = new_surface.GetType();
// Check if the surface type is supported according to the Vulkan
// extensions.
if (!(GetSupportedSurfaceTypes() & (Surface::TypeFlags(1) << surface_type))) {
REXLOG_ERROR(
"VulkanPresenter: Tried to create a Vulkan surface for an "
"unsupported Xenia surface type");
return SurfacePaintConnectResult::kFailureSurfaceUnusable;
}
VkResult vulkan_surface_create_result = VK_ERROR_UNKNOWN;
switch (surface_type) {
#if REX_PLATFORM_ANDROID
case Surface::kTypeIndex_AndroidNativeWindow: {
auto& android_native_window_surface =
static_cast<const AndroidNativeWindowSurface&>(new_surface);
VkAndroidSurfaceCreateInfoKHR surface_create_info;
surface_create_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
surface_create_info.pNext = nullptr;
surface_create_info.flags = 0;
surface_create_info.window = android_native_window_surface.window();
vulkan_surface_create_result = ifn.vkCreateAndroidSurfaceKHR(
instance, &surface_create_info, nullptr, &paint_context_.vulkan_surface);
} break;
#endif
#if REX_PLATFORM_GNU_LINUX
case Surface::kTypeIndex_XcbWindow: {
auto& xcb_window_surface = static_cast<const XcbWindowSurface&>(new_surface);
VkXcbSurfaceCreateInfoKHR surface_create_info;
surface_create_info.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
surface_create_info.pNext = nullptr;
surface_create_info.flags = 0;
surface_create_info.connection = xcb_window_surface.connection();
surface_create_info.window = xcb_window_surface.window();
vulkan_surface_create_result = ifn.vkCreateXcbSurfaceKHR(
instance, &surface_create_info, nullptr, &paint_context_.vulkan_surface);
} break;
#endif
#if REX_PLATFORM_WIN32
case Surface::kTypeIndex_Win32Hwnd: {
auto& win32_hwnd_surface = static_cast<const Win32HwndSurface&>(new_surface);
VkWin32SurfaceCreateInfoKHR surface_create_info;
surface_create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
surface_create_info.pNext = nullptr;
surface_create_info.flags = 0;
surface_create_info.hinstance = win32_hwnd_surface.hinstance();
surface_create_info.hwnd = win32_hwnd_surface.hwnd();
vulkan_surface_create_result = ifn.vkCreateWin32SurfaceKHR(
instance, &surface_create_info, nullptr, &paint_context_.vulkan_surface);
} break;
#endif
#if REX_PLATFORM_MAC
case Surface::kTypeIndex_SDLMetalView: {
auto& sdl_metal_surface = static_cast<const SDLMetalViewSurface&>(new_surface);
VkMetalSurfaceCreateInfoEXT surface_create_info;
surface_create_info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
surface_create_info.pNext = nullptr;
surface_create_info.flags = 0;
surface_create_info.pLayer = sdl_metal_surface.metal_layer();
vulkan_surface_create_result = ifn.vkCreateMetalSurfaceEXT(
instance, &surface_create_info, nullptr, &paint_context_.vulkan_surface);
} break;
#endif
default:
assert_unhandled_case(surface_type);
REXLOG_ERROR(
"VulkanPresenter: Tried to create a Vulkan surface for an "
"unknown Xenia surface type");
return SurfacePaintConnectResult::kFailureSurfaceUnusable;
}
if (vulkan_surface_create_result != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to create a Vulkan surface");
return SurfacePaintConnectResult::kFailure;
}
bool surface_unusable;
paint_context_.swapchain = PaintContext::CreateSwapchainForVulkanSurface(
vulkan_device_, paint_context_.vulkan_surface, new_surface_width, new_surface_height,
VK_NULL_HANDLE, paint_context_.present_queue_family, new_swapchain_format,
paint_context_.swapchain_extent, paint_context_.swapchain_is_fifo, surface_unusable);
if (paint_context_.swapchain == VK_NULL_HANDLE) {
// Failed to create the swapchain for the new Vulkan surface - destroy the
// Vulkan surface.
ifn.vkDestroySurfaceKHR(instance, paint_context_.vulkan_surface, nullptr);
paint_context_.vulkan_surface = VK_NULL_HANDLE;
return surface_unusable ? SurfacePaintConnectResult::kFailureSurfaceUnusable
: SurfacePaintConnectResult::kFailure;
}
// Successfully attached (at least for now).
}
// From now on, in case of failure,
// paint_context_.DestroySwapchainAndVulkanSurface must be called before
// returning.
// Update the render pass to the new format.
if (paint_context_.swapchain_render_pass_format != new_swapchain_format) {
util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device,
paint_context_.swapchain_render_pass);
paint_context_.swapchain_render_pass_format = new_swapchain_format;
}
if (paint_context_.swapchain_render_pass == VK_NULL_HANDLE) {
VkAttachmentDescription render_pass_attachment;
render_pass_attachment.flags = 0;
render_pass_attachment.format = new_swapchain_format;
render_pass_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
render_pass_attachment.loadOp = REXCVAR_GET(present_render_pass_clear)
? VK_ATTACHMENT_LOAD_OP_CLEAR
: VK_ATTACHMENT_LOAD_OP_DONT_CARE;
render_pass_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
render_pass_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
render_pass_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
render_pass_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
render_pass_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference render_pass_color_attachment;
render_pass_color_attachment.attachment = 0;
render_pass_color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription render_pass_subpass = {};
render_pass_subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
render_pass_subpass.colorAttachmentCount = 1;
render_pass_subpass.pColorAttachments = &render_pass_color_attachment;
VkSubpassDependency render_pass_dependencies[2];
render_pass_dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
render_pass_dependencies[0].dstSubpass = 0;
// srcStageMask is the semaphore wait stage.
render_pass_dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
render_pass_dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
render_pass_dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
// The main target can be used for UI drawing at any moment, which requires
// blending, so VK_ACCESS_COLOR_ATTACHMENT_READ_BIT is also included.
render_pass_dependencies[0].dstAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
render_pass_dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
render_pass_dependencies[1].srcSubpass = 0;
render_pass_dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
render_pass_dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
// Semaphores are signaled at VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT.
render_pass_dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
render_pass_dependencies[1].srcAccessMask =
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
render_pass_dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
render_pass_dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
VkRenderPassCreateInfo render_pass_create_info;
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
render_pass_create_info.pNext = nullptr;
render_pass_create_info.flags = 0;
render_pass_create_info.attachmentCount = 1;
render_pass_create_info.pAttachments = &render_pass_attachment;
render_pass_create_info.subpassCount = 1;
render_pass_create_info.pSubpasses = &render_pass_subpass;
render_pass_create_info.dependencyCount = uint32_t(rex::countof(render_pass_dependencies));
render_pass_create_info.pDependencies = render_pass_dependencies;
VkRenderPass new_render_pass;
if (dfn.vkCreateRenderPass(device, &render_pass_create_info, nullptr, &new_render_pass) !=
VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the render pass for drawing to "
"swapchain images");
paint_context_.DestroySwapchainAndVulkanSurface();
return SurfacePaintConnectResult::kFailure;
}
paint_context_.swapchain_render_pass = new_render_pass;
paint_context_.swapchain_render_pass_format = new_swapchain_format;
paint_context_.swapchain_render_pass_clear_load_op =
render_pass_attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR;
}
// Get the swapchain images.
paint_context_.swapchain_images.clear();
VkResult swapchain_images_get_result;
for (;;) {
uint32_t swapchain_image_count = uint32_t(paint_context_.swapchain_images.size());
bool swapchain_images_were_empty = !swapchain_image_count;
swapchain_images_get_result = dfn.vkGetSwapchainImagesKHR(
device, paint_context_.swapchain, &swapchain_image_count,
swapchain_images_were_empty ? nullptr : paint_context_.swapchain_images.data());
// If the original swapchain image count was 0 (first call), SUCCESS is
// returned, not INCOMPLETE.
if (swapchain_images_get_result == VK_SUCCESS || swapchain_images_get_result == VK_INCOMPLETE) {
paint_context_.swapchain_images.resize(swapchain_image_count);
if (swapchain_images_get_result == VK_SUCCESS &&
(!swapchain_images_were_empty || !swapchain_image_count)) {
break;
}
} else {
break;
}
}
if (swapchain_images_get_result != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to get swapchain images");
paint_context_.DestroySwapchainAndVulkanSurface();
return SurfacePaintConnectResult::kFailure;
}
// Create the image views and the framebuffers.
assert_true(paint_context_.swapchain_framebuffers.empty());
paint_context_.swapchain_framebuffers.reserve(paint_context_.swapchain_images.size());
VkImageViewCreateInfo image_view_create_info;
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.pNext = nullptr;
image_view_create_info.flags = 0;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = new_swapchain_format;
image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.baseMipLevel = 0;
image_view_create_info.subresourceRange.levelCount = 1;
image_view_create_info.subresourceRange.baseArrayLayer = 0;
image_view_create_info.subresourceRange.layerCount = 1;
VkImageView image_view;
VkFramebufferCreateInfo framebuffer_create_info;
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebuffer_create_info.pNext = nullptr;
framebuffer_create_info.flags = 0;
framebuffer_create_info.renderPass = paint_context_.swapchain_render_pass;
framebuffer_create_info.attachmentCount = 1;
framebuffer_create_info.pAttachments = &image_view;
framebuffer_create_info.width = paint_context_.swapchain_extent.width;
framebuffer_create_info.height = paint_context_.swapchain_extent.height;
framebuffer_create_info.layers = 1;
for (VkImage image : paint_context_.swapchain_images) {
image_view_create_info.image = image;
if (dfn.vkCreateImageView(device, &image_view_create_info, nullptr, &image_view) !=
VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to create a swapchain image view");
paint_context_.DestroySwapchainAndVulkanSurface();
return SurfacePaintConnectResult::kFailure;
}
VkFramebuffer framebuffer;
if (dfn.vkCreateFramebuffer(device, &framebuffer_create_info, nullptr, &framebuffer) !=
VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to create a swapchain framebuffer");
dfn.vkDestroyImageView(device, image_view, nullptr);
paint_context_.DestroySwapchainAndVulkanSurface();
return SurfacePaintConnectResult::kFailure;
}
paint_context_.swapchain_framebuffers.emplace_back(image_view, framebuffer);
}
// Create one presentation-wait semaphore per swapchain image, always fresh
// for a new swapchain (the previous swapchain's semaphores were moved to
// retired_present_semaphores by PrepareForSwapchainRetirement). The paint
// submission signals the semaphore of the acquired image, and
// vkQueuePresentKHR waits on it - re-signaling is safe because the image
// index has just been re-acquired, proving its previous present (and thus
// the presentation engine's wait on this semaphore) has completed.
assert_true(paint_context_.swapchain_present_semaphores.empty());
paint_context_.swapchain_present_semaphores.reserve(paint_context_.swapchain_images.size());
VkSemaphoreCreateInfo present_semaphore_create_info;
present_semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
present_semaphore_create_info.pNext = nullptr;
present_semaphore_create_info.flags = 0;
for (size_t i = 0; i < paint_context_.swapchain_images.size(); ++i) {
VkSemaphore present_semaphore;
if (dfn.vkCreateSemaphore(device, &present_semaphore_create_info, nullptr,
&present_semaphore) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create a swapchain image presentation "
"semaphore");
paint_context_.DestroySwapchainAndVulkanSurface();
return SurfacePaintConnectResult::kFailure;
}
paint_context_.swapchain_present_semaphores.push_back(present_semaphore);
}
is_vsync_implicit_out = paint_context_.swapchain_is_fifo;
return SurfacePaintConnectResult::kSuccess;
}
void VulkanPresenter::DisconnectPaintingFromSurfaceFromUIThreadImpl() {
paint_context_.DestroySwapchainAndVulkanSurface();
}
bool VulkanPresenter::RefreshGuestOutputImpl(
uint32_t mailbox_index, uint32_t frontbuffer_width, uint32_t frontbuffer_height,
std::function<bool(GuestOutputRefreshContext& context)> refresher, bool& is_8bpc_out_ref) {
assert_not_zero(frontbuffer_width);
assert_not_zero(frontbuffer_height);
VkExtent2D max_framebuffer_extent = util::GetMax2DFramebufferExtent(vulkan_device_->properties());
if (frontbuffer_width > max_framebuffer_extent.width ||
frontbuffer_height > max_framebuffer_extent.height) {
// Writing the guest output isn't supposed to rescale, and a guest texture
// exceeding the maximum size won't be loadable anyway.
return false;
}
GuestOutputImageInstance& image_instance = guest_output_images_[mailbox_index];
if (image_instance.image && (image_instance.image->extent().width != frontbuffer_width ||
image_instance.image->extent().height != frontbuffer_height)) {
guest_output_image_refresher_submission_tracker_.AwaitSubmissionCompletion(
image_instance.last_refresher_submission);
image_instance.image.reset();
}
if (!image_instance.image) {
std::unique_ptr<GuestOutputImage> new_image =
GuestOutputImage::Create(vulkan_device_, frontbuffer_width, frontbuffer_height);
if (!new_image) {
return false;
}
image_instance.SetToNewImage(std::move(new_image), guest_output_image_next_version_++);
}
VulkanGuestOutputRefreshContext context(is_8bpc_out_ref, image_instance.image->image(),
image_instance.image->view(), image_instance.version,
image_instance.ever_successfully_refreshed);
bool refresher_succeeded = refresher(context);
if (refresher_succeeded) {
image_instance.ever_successfully_refreshed = true;
}
// Even if the refresher has returned false, it still might have submitted
// some commands referencing the image. It's better to put an excessive
// signal and wait slightly longer, for nothing important, while shutting down
// than to destroy the image while it's still in use.
image_instance.last_refresher_submission =
guest_output_image_refresher_submission_tracker_.GetCurrentSubmission();
// No need to make the refresher signal the fence by itself - signal it here
// instead to have more control:
// "Fence signal operations that are defined by vkQueueSubmit additionally
// include in the first synchronization scope all commands that occur earlier
// in submission order."
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
{
VulkanSubmissionTracker::FenceAcquisition fence_acqusition(
guest_output_image_refresher_submission_tracker_.AcquireFenceToAdvanceSubmission());
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(vulkan_device_->queue_family_graphics_compute(), 0);
if (dfn.vkQueueSubmit(queue_acquisition.queue(), 0, nullptr, fence_acqusition.fence()) !=
VK_SUCCESS) {
fence_acqusition.SubmissionSucceededSignalFailed();
}
}
return refresher_succeeded;
}
VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface(
const VulkanDevice* vulkan_device, VkSurfaceKHR surface, uint32_t width, uint32_t height,
VkSwapchainKHR old_swapchain, uint32_t& present_queue_family_out, VkFormat& image_format_out,
VkExtent2D& image_extent_out, bool& is_fifo_out, bool& ui_surface_unusable_out) {
ui_surface_unusable_out = false;
const VulkanInstance::Functions& ifn = vulkan_device->vulkan_instance()->functions();
const VkPhysicalDevice physical_device = vulkan_device->physical_device();
const VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
// Get surface capabilities.
VkSurfaceCapabilitiesKHR surface_capabilities;
if (ifn.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface,
&surface_capabilities) != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to get Vulkan surface capabilities");
// Some strange error, try again later.
return VK_NULL_HANDLE;
}
// First, check if the surface is not zero-area because in this case, the rest
// of the fields in theory may not be informative as the surface doesn't need
// to go to presentation anyway, thus there's no need to return real
// information, and ui_surface_unusable_out (from which it may not be possible
// to recover on certain platforms at all) may be set to true spuriously if
// any checks set it to true. VkSurfaceKHR may have zero-area bounds in some
// window state cases on Windows, for example. Also clamp the requested size
// to the maximum supported by the physical device as long as minImageExtent
// in the instance's WSI allows that (if not, there's no way to satisfy both
// requirements - the maximum 2D framebuffer size on the specific physical
// device, and the minimum swap chain size on the whole instance - fail to
// create until the surface becomes smaller).
VkExtent2D max_framebuffer_extent = util::GetMax2DFramebufferExtent(vulkan_device->properties());
VkExtent2D image_extent;
image_extent.width = std::min(std::max(std::min(width, max_framebuffer_extent.width),
surface_capabilities.minImageExtent.width),
surface_capabilities.maxImageExtent.width);
image_extent.height = std::min(std::max(std::min(height, max_framebuffer_extent.height),
surface_capabilities.minImageExtent.height),
surface_capabilities.maxImageExtent.height);
if (!image_extent.width || !image_extent.height ||
image_extent.width > max_framebuffer_extent.width ||
image_extent.height > max_framebuffer_extent.height) {
return VK_NULL_HANDLE;
}
// Get the queue family for presentation.
uint32_t queue_family_index_present = UINT32_MAX;
const std::vector<VulkanDevice::QueueFamily>& queue_families = vulkan_device->queue_families();
VkBool32 queue_family_present_supported;
// First try the graphics and compute queue, prefer it to avoid the concurrent
// image sharing mode.
uint32_t queue_family_index_graphics_compute = vulkan_device->queue_family_graphics_compute();
const VulkanDevice::QueueFamily& queue_family_graphics_compute =
queue_families[queue_family_index_graphics_compute];
if (queue_family_graphics_compute.may_support_presentation &&
!queue_family_graphics_compute.queues.empty() &&
ifn.vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, queue_family_index_graphics_compute,
surface,
&queue_family_present_supported) == VK_SUCCESS &&
queue_family_present_supported) {
queue_family_index_present = queue_family_index_graphics_compute;
} else {
for (uint32_t i = 0; i < uint32_t(queue_families.size()); ++i) {
const VulkanDevice::QueueFamily& queue_family = queue_families[i];
if (!queue_family.queues.empty() && queue_family.may_support_presentation &&
ifn.vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, i, surface,
&queue_family_present_supported) == VK_SUCCESS &&
queue_family_present_supported) {
queue_family_index_present = i;
break;
}
}
}
if (queue_family_index_present == UINT32_MAX) {
// Not unusable though - may become presentable if the window (with the same
// surface) moved to a different display, for instance.
return VK_NULL_HANDLE;
}
// TODO(Triang3l): Support transforms.
if (!(surface_capabilities.supportedTransforms &
(VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR | VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR))) {
REXLOG_ERROR(
"VulkanPresenter: The surface doesn't support identity or "
"window-system-controlled transform");
return VK_NULL_HANDLE;
}
// Get the surface format.
std::vector<VkSurfaceFormatKHR> surface_formats;
VkResult surface_formats_get_result;
for (;;) {
uint32_t surface_format_count = uint32_t(surface_formats.size());
bool surface_formats_were_empty = !surface_format_count;
surface_formats_get_result = ifn.vkGetPhysicalDeviceSurfaceFormatsKHR(
physical_device, surface, &surface_format_count,
surface_formats_were_empty ? nullptr : surface_formats.data());
// If the original presentation mode count was 0 (first call), SUCCESS is
// returned, not INCOMPLETE.
if (surface_formats_get_result == VK_SUCCESS || surface_formats_get_result == VK_INCOMPLETE) {
surface_formats.resize(surface_format_count);
if (surface_formats_get_result == VK_SUCCESS &&
(!surface_formats_were_empty || !surface_format_count)) {
break;
}
} else {
break;
}
}
if (surface_formats_get_result != VK_SUCCESS) {
// Assuming any format in case of an error (or as some fallback in case of
// specification violation).
surface_formats.clear();
}
#if REX_PLATFORM_ANDROID
// Android uses R8G8B8A8.
static const VkFormat kFormat8888Primary = VK_FORMAT_R8G8B8A8_UNORM;
static const VkFormat kFormat8888Secondary = VK_FORMAT_B8G8R8A8_UNORM;
#else
// GNU/Linux X11 and Windows DWM use B8G8R8A8.
static const VkFormat kFormat8888Primary = VK_FORMAT_B8G8R8A8_UNORM;
static const VkFormat kFormat8888Secondary = VK_FORMAT_R8G8B8A8_UNORM;
#endif
VkSurfaceFormatKHR image_format;
if (surface_formats.empty() ||
(surface_formats.size() == 1 || surface_formats[0].format == VK_FORMAT_UNDEFINED)) {
// Can choose any format if the implementation specifies only UNDEFINED.
image_format.format = kFormat8888Primary;
image_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
} else {
// Pick the sRGB 8888 format preferred by the OS, fall back to any sRGB
// 8888, then to 8888 with an unknown color space, and then to the first
// sRGB available, and then to the first.
auto format_8888_primary_it = surface_formats.cend();
auto format_8888_secondary_it = surface_formats.cend();
auto any_non_8888_srgb_it = surface_formats.cend();
for (auto it = surface_formats.cbegin(); it != surface_formats.cend(); ++it) {
if (it->format != kFormat8888Primary && it->format != kFormat8888Secondary) {
if (it->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR &&
any_non_8888_srgb_it == surface_formats.cend()) {
any_non_8888_srgb_it = it;
}
continue;
}
auto& preferred_8888_it =
it->format == kFormat8888Primary ? format_8888_primary_it : format_8888_secondary_it;
if (preferred_8888_it == surface_formats.cend()) {
// First primary or secondary 8888 encounter.
preferred_8888_it = it;
continue;
}
// Is this a better primary or secondary 8888, that is, this is sRGB,
// while the previous encounter was not?
if (it->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR &&
preferred_8888_it->colorSpace != VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
preferred_8888_it = it;
}
}
if (format_8888_primary_it != surface_formats.cend() &&
format_8888_secondary_it != surface_formats.cend()) {
// Both the primary and the secondary 8888 formats are available - prefer
// sRGB, if both are sRGB or not, prefer the primary format.
if (format_8888_primary_it->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR ||
format_8888_secondary_it->colorSpace != VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
image_format = *format_8888_primary_it;
} else {
image_format = *format_8888_secondary_it;
}
} else if (format_8888_primary_it != surface_formats.cend()) {
// Only primary 8888.
image_format = *format_8888_primary_it;
} else if (format_8888_secondary_it != surface_formats.cend()) {
// Only secondary 8888.
image_format = *format_8888_secondary_it;
} else if (any_non_8888_srgb_it != surface_formats.cend()) {
// No 8888, but some sRGB format is available.
image_format = *any_non_8888_srgb_it;
} else {
// Just pick any format.
image_format = surface_formats.front();
}
}
// Get presentation modes.
std::vector<VkPresentModeKHR> present_modes;
VkResult present_modes_get_result;
for (;;) {
uint32_t present_mode_count = uint32_t(present_modes.size());
bool present_modes_were_empty = !present_mode_count;
present_modes_get_result = ifn.vkGetPhysicalDeviceSurfacePresentModesKHR(
physical_device, surface, &present_mode_count,
present_modes_were_empty ? nullptr : present_modes.data());
// If the original presentation mode count was 0 (first call), SUCCESS is
// returned, not INCOMPLETE.
if (present_modes_get_result == VK_SUCCESS || present_modes_get_result == VK_INCOMPLETE) {
present_modes.resize(present_mode_count);
if (present_modes_get_result == VK_SUCCESS &&
(!present_modes_were_empty || !present_mode_count)) {
break;
}
} else {
break;
}
}
if (present_modes_get_result != VK_SUCCESS) {
// Assuming FIFO only (required everywhere) in case of an error.
present_modes.clear();
}
// Create the swapchain.
VkSwapchainCreateInfoKHR swapchain_create_info;
swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_create_info.pNext = nullptr;
swapchain_create_info.flags = 0;
swapchain_create_info.surface = surface;
swapchain_create_info.minImageCount =
std::max(kSubmissionCount, surface_capabilities.minImageCount);
if (surface_capabilities.maxImageCount) {
swapchain_create_info.minImageCount =
std::min(swapchain_create_info.minImageCount, surface_capabilities.maxImageCount);
}
swapchain_create_info.imageFormat = image_format.format;
swapchain_create_info.imageColorSpace = image_format.colorSpace;
swapchain_create_info.imageExtent = image_extent;
swapchain_create_info.imageArrayLayers = 1;
swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
uint32_t swapchain_queue_family_indices[2];
if (queue_family_index_graphics_compute != queue_family_index_present) {
// Using concurrent sharing mode to avoid an explicit ownership transfer
// before presenting, which would require an additional command buffer
// submission with the acquire barrier and a semaphore between the graphics
// queue and the present queue. Different queues are an extremely rare case,
// and Xenia only uses the swapchain for the final guest output and the
// internal UI, so keeping framebuffer compression is not worth the
// additional submission complexity and possibly latency.
swapchain_queue_family_indices[0] = queue_family_index_graphics_compute;
swapchain_queue_family_indices[1] = queue_family_index_present;
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_create_info.queueFamilyIndexCount = 2;
swapchain_create_info.pQueueFamilyIndices = swapchain_queue_family_indices;
} else {
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_create_info.queueFamilyIndexCount = 0;
swapchain_create_info.pQueueFamilyIndices = nullptr;
}
swapchain_create_info.preTransform =
(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
: VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR;
// Prefer opaque to avoid blending in the window system, or let that be
// specified via the window system if it can't be forced. As a last resort,
// just pick any - guest output will write alpha of 1 anyway.
if (surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) {
swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
} else if (surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) {
swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
} else {
uint32_t composite_alpha_shift;
if (!rex::bit_scan_forward(uint32_t(surface_capabilities.supportedCompositeAlpha),
&composite_alpha_shift)) {
// Against the Vulkan specification, but breaks the logic here.
REXLOG_ERROR(
"VulkanPresenter: The surface doesn't support any composite alpha "
"mode");
return VK_NULL_HANDLE;
}
swapchain_create_info.compositeAlpha =
VkCompositeAlphaFlagBitsKHR(uint32_t(1) << composite_alpha_shift);
}
// As presentation is usually controlled by the GPU command processor, it's
// better to use modes that allow as quick acquisition as possible to avoid
// interfering with GPU command processing, and also to allow tearing so
// variable refresh rate may be used where it's available.
// Note: If the priorities here are changes, update the cvar descriptions.
constexpr bool allow_immediate_present_mode = true;
if (allow_immediate_present_mode && REXCVAR_GET(vulkan_allow_present_mode_immediate) &&
std::find(present_modes.cbegin(), present_modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) !=
present_modes.cend()) {
// Allowing tearing to reduce latency, and possibly variable refresh rate
// (though on Windows with borderless fullscreen, GDI copying is used
// instead of independent flip, so it's not supported there).
swapchain_create_info.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
} else if (REXCVAR_GET(vulkan_allow_present_mode_mailbox) &&
std::find(present_modes.cbegin(), present_modes.cend(), VK_PRESENT_MODE_MAILBOX_KHR) !=
present_modes.cend()) {
// Allowing dropping frames to reduce latency, but no tearing.
swapchain_create_info.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
} else if (REXCVAR_GET(vulkan_allow_present_mode_fifo_relaxed) &&
std::find(present_modes.cbegin(), present_modes.cend(),
VK_PRESENT_MODE_FIFO_RELAXED_KHR) != present_modes.cend()) {
// Limiting the frame rate, but lets too long frames cause tearing not to
// make the latency even worse.
swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
} else {
// Highest latency (but always guaranteed to be available).
swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
}
swapchain_create_info.clipped = VK_TRUE;
swapchain_create_info.oldSwapchain = old_swapchain;
VkSwapchainKHR swapchain;
if (dfn.vkCreateSwapchainKHR(device, &swapchain_create_info, nullptr, &swapchain) != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to create a swapchain");
return VK_NULL_HANDLE;
}
REXLOG_INFO(
"VulkanPresenter: Created {}x{} swapchain with format {}, color space "
"{}, presentation mode {}",
swapchain_create_info.imageExtent.width, swapchain_create_info.imageExtent.height,
uint32_t(swapchain_create_info.imageFormat), uint32_t(swapchain_create_info.imageColorSpace),
uint32_t(swapchain_create_info.presentMode));
present_queue_family_out = queue_family_index_present;
image_format_out = swapchain_create_info.imageFormat;
image_extent_out = swapchain_create_info.imageExtent;
is_fifo_out = swapchain_create_info.presentMode == VK_PRESENT_MODE_FIFO_KHR ||
swapchain_create_info.presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR;
return swapchain;
}
VkSwapchainKHR VulkanPresenter::PaintContext::PrepareForSwapchainRetirement() {
if (swapchain != VK_NULL_HANDLE) {
submission_tracker.AwaitAllSubmissionsCompletion();
}
const VulkanDevice::Functions& dfn = vulkan_device->functions();
const VkDevice device = vulkan_device->device();
for (const SwapchainFramebuffer& framebuffer : swapchain_framebuffers) {
dfn.vkDestroyFramebuffer(device, framebuffer.framebuffer, nullptr);
dfn.vkDestroyImageView(device, framebuffer.image_view, nullptr);
}
swapchain_framebuffers.clear();
// The retired swapchain may still have presents in flight, and
// vkQueuePresentKHR provides no host-visible signal for the completion of
// its semaphore wait operations (the submission fences awaited above only
// cover the vkQueueSubmit that signaled them). Retire the per-image
// present-wait semaphores instead of destroying or reusing them - a new set
// is created for the new swapchain, so no stale pending signal can ever be
// carried over into it (that carry-over caused
// SYNC-HAZARD-PRESENT-AFTER-WRITE: a present pairing with an old signal
// leaves the new frame's swapchain image writes unordered against
// presentation). Semaphores are tiny and swapchain recreations are rare, so
// the retired list stays small; it's destroyed in ~VulkanPresenter once all
// submissions have completed and the swapchains have been destroyed.
retired_present_semaphores.insert(retired_present_semaphores.end(),
swapchain_present_semaphores.begin(),
swapchain_present_semaphores.end());
swapchain_present_semaphores.clear();
swapchain_images.clear();
swapchain_extent.width = 0;
swapchain_extent.height = 0;
// The old swapchain must be destroyed externally.
VkSwapchainKHR old_swapchain = swapchain;
swapchain = nullptr;
return old_swapchain;
}
void VulkanPresenter::PaintContext::DestroySwapchainAndVulkanSurface() {
VkSwapchainKHR old_swapchain = PrepareForSwapchainRetirement();
if (old_swapchain != VK_NULL_HANDLE) {
vulkan_device->functions().vkDestroySwapchainKHR(vulkan_device->device(), old_swapchain,
nullptr);
}
present_queue_family = UINT32_MAX;
if (vulkan_surface != VK_NULL_HANDLE) {
const VulkanInstance* vulkan_instance = vulkan_device->vulkan_instance();
vulkan_instance->functions().vkDestroySurfaceKHR(vulkan_instance->instance(), vulkan_surface,
nullptr);
vulkan_surface = VK_NULL_HANDLE;
}
}
VulkanPresenter::GuestOutputImage::~GuestOutputImage() {
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
if (view_ != VK_NULL_HANDLE) {
dfn.vkDestroyImageView(device, view_, nullptr);
}
if (image_ != VK_NULL_HANDLE) {
dfn.vkDestroyImage(device, image_, nullptr);
}
if (memory_ != VK_NULL_HANDLE) {
dfn.vkFreeMemory(device, memory_, nullptr);
}
}
bool VulkanPresenter::GuestOutputImage::Initialize() {
VkImageCreateInfo image_create_info;
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_create_info.pNext = nullptr;
image_create_info.flags = 0;
image_create_info.imageType = VK_IMAGE_TYPE_2D;
image_create_info.format = kGuestOutputFormat;
image_create_info.extent.width = extent_.width;
image_create_info.extent.height = extent_.height;
image_create_info.extent.depth = 1;
image_create_info.mipLevels = 1;
image_create_info.arrayLayers = 1;
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_create_info.queueFamilyIndexCount = 0;
image_create_info.pQueueFamilyIndices = nullptr;
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (!ui::vulkan::util::CreateDedicatedAllocationImage(
vulkan_device_, image_create_info, ui::vulkan::util::MemoryPurpose::kDeviceLocal, image_,
memory_)) {
REXLOG_ERROR("VulkanPresenter: Failed to create a guest output image");
return false;
}
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkImageViewCreateInfo image_view_create_info;
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
image_view_create_info.pNext = nullptr;
image_view_create_info.flags = 0;
image_view_create_info.image = image_;
image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
image_view_create_info.format = kGuestOutputFormat;
image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_view_create_info.subresourceRange.baseMipLevel = 0;
image_view_create_info.subresourceRange.levelCount = 1;
image_view_create_info.subresourceRange.baseArrayLayer = 0;
image_view_create_info.subresourceRange.layerCount = 1;
if (dfn.vkCreateImageView(device, &image_view_create_info, nullptr, &view_) != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to create a guest output image view");
return false;
}
return true;
}
Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_drawers) {
const auto timing_start = std::chrono::steady_clock::now();
// Begin the submission in place of the one not currently potentially used on
// the GPU.
uint64_t current_paint_submission_index =
paint_context_.submission_tracker.GetCurrentSubmission();
uint64_t paint_submission_count = uint64_t(paint_context_.submissions.size());
if (current_paint_submission_index >= paint_submission_count) {
paint_context_.submission_tracker.AwaitSubmissionCompletion(current_paint_submission_index -
paint_submission_count);
}
const PaintContext::Submission& paint_submission =
*paint_context_.submissions[current_paint_submission_index % paint_submission_count];
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkCommandPool draw_command_pool = paint_submission.draw_command_pool();
if (dfn.vkResetCommandPool(device, draw_command_pool, 0) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to reset a command buffer for drawing to the "
"swapchain");
return PaintResult::kNotPresented;
}
VkCommandBuffer draw_command_buffer = paint_submission.draw_command_buffer();
VkCommandBufferBeginInfo command_buffer_begin_info;
command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
command_buffer_begin_info.pNext = nullptr;
command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
command_buffer_begin_info.pInheritanceInfo = nullptr;
if (dfn.vkBeginCommandBuffer(draw_command_buffer, &command_buffer_begin_info) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to being recording the command buffer for "
"drawing to the swapchain");
return PaintResult::kNotPresented;
}
// vkResetCommandPool resets from both initial and recording states, still
// safe to return early from this function in case of an error.
VkSemaphore acquire_semaphore = paint_submission.acquire_semaphore();
uint32_t swapchain_image_index;
const auto timing_acquire_start = std::chrono::steady_clock::now();
VkResult acquire_result =
dfn.vkAcquireNextImageKHR(device, paint_context_.swapchain, UINT64_MAX, acquire_semaphore,
VK_NULL_HANDLE, &swapchain_image_index);
const auto timing_acquire_end = std::chrono::steady_clock::now();
switch (acquire_result) {
case VK_SUCCESS:
case VK_SUBOPTIMAL_KHR:
break;
case VK_ERROR_DEVICE_LOST:
REXLOG_ERROR(
"VulkanPresenter: Failed to acquire the swapchain image as the "
"device has been lost ({})",
vk::to_string(vk::Result(acquire_result)));
return PaintResult::kGpuLostResponsible;
case VK_ERROR_OUT_OF_DATE_KHR:
case VK_ERROR_SURFACE_LOST_KHR:
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
// Not an error, reporting just as info (may normally occur while resizing
// on some platforms).
REXLOG_INFO(
"VulkanPresenter: Presentation to the swapchain image has been "
"dropped as the swapchain or the surface has become outdated");
return PaintResult::kNotPresentedConnectionOutdated;
default:
REXLOG_ERROR("VulkanPresenter: Failed to acquire the swapchain image ({})",
vk::to_string(vk::Result(acquire_result)));
return PaintResult::kNotPresented;
}
// Non-zero extents needed for both the viewport (width must not be zero) and
// the guest output rectangle.
assert_not_zero(paint_context_.swapchain_extent.width);
assert_not_zero(paint_context_.swapchain_extent.height);
bool swapchain_image_clear_needed = true;
VkClearAttachment swapchain_image_clear_attachment;
swapchain_image_clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
swapchain_image_clear_attachment.colorAttachment = 0;
swapchain_image_clear_attachment.clearValue.color.float32[0] = 0.0f;
swapchain_image_clear_attachment.clearValue.color.float32[1] = 0.0f;
swapchain_image_clear_attachment.clearValue.color.float32[2] = 0.0f;
swapchain_image_clear_attachment.clearValue.color.float32[3] = 1.0f;
VkRenderPassBeginInfo swapchain_render_pass_begin_info;
swapchain_render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
swapchain_render_pass_begin_info.pNext = nullptr;
swapchain_render_pass_begin_info.renderPass = paint_context_.swapchain_render_pass;
swapchain_render_pass_begin_info.framebuffer =
paint_context_.swapchain_framebuffers[swapchain_image_index].framebuffer;
swapchain_render_pass_begin_info.renderArea.offset.x = 0;
swapchain_render_pass_begin_info.renderArea.offset.y = 0;
swapchain_render_pass_begin_info.renderArea.extent = paint_context_.swapchain_extent;
swapchain_render_pass_begin_info.clearValueCount = 0;
swapchain_render_pass_begin_info.pClearValues = nullptr;
if (paint_context_.swapchain_render_pass_clear_load_op) {
swapchain_render_pass_begin_info.clearValueCount = 1;
swapchain_render_pass_begin_info.pClearValues = &swapchain_image_clear_attachment.clearValue;
swapchain_image_clear_needed = false;
}
bool swapchain_image_pass_begun = false;
GuestOutputProperties guest_output_properties;
GuestOutputPaintConfig guest_output_paint_config;
std::shared_ptr<GuestOutputImage> guest_output_image;
{
uint32_t guest_output_mailbox_index;
std::unique_lock<std::mutex> guest_output_consumer_lock(ConsumeGuestOutput(
guest_output_mailbox_index, &guest_output_properties, &guest_output_paint_config));
if (guest_output_mailbox_index != UINT32_MAX) {
assert_true(guest_output_images_[guest_output_mailbox_index].ever_successfully_refreshed);
guest_output_image = guest_output_images_[guest_output_mailbox_index].image;
}
// Incremented the reference count of the guest output image - safe to leave
// the consumer critical section now as everything here either will be using
// the new reference or is exclusively owned by main target painting (and
// multiple threads can't paint the main target at the same time).
}
if (guest_output_image) {
VkExtent2D max_framebuffer_extent =
util::GetMax2DFramebufferExtent(vulkan_device_->properties());
GuestOutputPaintFlow guest_output_flow = GetGuestOutputPaintFlow(
guest_output_properties, paint_context_.swapchain_extent.width,
paint_context_.swapchain_extent.height, max_framebuffer_extent.width,
max_framebuffer_extent.height, guest_output_paint_config);
if (guest_output_flow.effect_count) {
// Store the main target reference to the guest output image so it's not
// destroyed while it's still potentially in use by main target painting
// queued on the GPU.
size_t guest_output_image_paint_ref_index = SIZE_MAX;
size_t guest_output_image_paint_ref_new_index = SIZE_MAX;
// Try to find the existing reference to the same image, or an already
// released (or a taken, but never actually used) slot.
for (size_t i = 0; i < paint_context_.guest_output_image_paint_refs.size(); ++i) {
const std::pair<uint64_t, std::shared_ptr<GuestOutputImage>>& guest_output_image_paint_ref =
paint_context_.guest_output_image_paint_refs[i];
if (guest_output_image_paint_ref.second == guest_output_image) {
guest_output_image_paint_ref_index = i;
break;
}
if (guest_output_image_paint_ref_new_index == SIZE_MAX &&
(!guest_output_image_paint_ref.second || !guest_output_image_paint_ref.first)) {
guest_output_image_paint_ref_new_index = i;
}
}
if (guest_output_image_paint_ref_index == SIZE_MAX) {
// New image - store the reference and create the descriptors.
if (guest_output_image_paint_ref_new_index == SIZE_MAX) {
// Replace the earliest used reference.
guest_output_image_paint_ref_new_index = 0;
for (size_t i = 1; i < paint_context_.guest_output_image_paint_refs.size(); ++i) {
if (paint_context_.guest_output_image_paint_refs[i].first <
paint_context_.guest_output_image_paint_refs[guest_output_image_paint_ref_new_index]
.first) {
guest_output_image_paint_ref_new_index = i;
}
}
// Await the completion of the usage of the old guest output image and
// its descriptors.
paint_context_.submission_tracker.AwaitSubmissionCompletion(
paint_context_.guest_output_image_paint_refs[guest_output_image_paint_ref_new_index]
.first);
}
guest_output_image_paint_ref_index = guest_output_image_paint_ref_new_index;
// The actual submission index will be set if the image is actually
// used, not dropped due to some error.
paint_context_.guest_output_image_paint_refs[guest_output_image_paint_ref_index] =
std::make_pair(uint64_t(0), guest_output_image);
// Create the descriptors of the new image.
VkDescriptorImageInfo guest_output_image_descriptor_image_info;
guest_output_image_descriptor_image_info.sampler = VK_NULL_HANDLE;
guest_output_image_descriptor_image_info.imageView = guest_output_image->view();
guest_output_image_descriptor_image_info.imageLayout =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkWriteDescriptorSet guest_output_image_descriptor_write;
guest_output_image_descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
guest_output_image_descriptor_write.pNext = nullptr;
guest_output_image_descriptor_write.dstSet =
paint_context_.guest_output_descriptor_sets
[PaintContext::kGuestOutputDescriptorSetGuestOutput0Sampled +
guest_output_image_paint_ref_index];
guest_output_image_descriptor_write.dstBinding = 0;
guest_output_image_descriptor_write.dstArrayElement = 0;
guest_output_image_descriptor_write.descriptorCount = 1;
guest_output_image_descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
guest_output_image_descriptor_write.pImageInfo = &guest_output_image_descriptor_image_info;
guest_output_image_descriptor_write.pBufferInfo = nullptr;
guest_output_image_descriptor_write.pTexelBufferView = nullptr;
dfn.vkUpdateDescriptorSets(device, 1, &guest_output_image_descriptor_write, 0, nullptr);
}
// Make sure intermediate textures of the needed size are available, and
// unneeded intermediate textures are destroyed.
for (size_t i = 0; i < kMaxGuestOutputPaintEffects - 1; ++i) {
std::pair<uint32_t, uint32_t> intermediate_needed_size(0, 0);
if (i + 1 < guest_output_flow.effect_count) {
intermediate_needed_size = guest_output_flow.effect_output_sizes[i];
}
std::unique_ptr<GuestOutputImage>& intermediate_image_ptr_ref =
paint_context_.guest_output_intermediate_images[i];
VkExtent2D intermediate_current_extent(
intermediate_image_ptr_ref ? intermediate_image_ptr_ref->extent() : VkExtent2D{});
if (intermediate_current_extent.width != intermediate_needed_size.first ||
intermediate_current_extent.height != intermediate_needed_size.second) {
if (intermediate_needed_size.first && intermediate_needed_size.second) {
// Need to replace immediately as a new image with the requested
// size is needed.
if (intermediate_image_ptr_ref) {
paint_context_.submission_tracker.AwaitSubmissionCompletion(
paint_context_.guest_output_intermediate_image_last_submission);
intermediate_image_ptr_ref.reset();
util::DestroyAndNullHandle(dfn.vkDestroyFramebuffer, device,
paint_context_.guest_output_intermediate_framebuffers[i]);
}
// Image.
intermediate_image_ptr_ref = GuestOutputImage::Create(
vulkan_device_, intermediate_needed_size.first, intermediate_needed_size.second);
if (!intermediate_image_ptr_ref) {
// Don't display the guest output, and don't try to create more
// intermediate textures (only destroy them).
guest_output_flow.effect_count = 0;
continue;
}
// Framebuffer.
VkImageView intermediate_framebuffer_attachment = intermediate_image_ptr_ref->view();
VkFramebufferCreateInfo intermediate_framebuffer_create_info;
intermediate_framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
intermediate_framebuffer_create_info.pNext = nullptr;
intermediate_framebuffer_create_info.flags = 0;
intermediate_framebuffer_create_info.renderPass =
guest_output_intermediate_render_pass_;
intermediate_framebuffer_create_info.attachmentCount = 1;
intermediate_framebuffer_create_info.pAttachments =
&intermediate_framebuffer_attachment;
intermediate_framebuffer_create_info.width = intermediate_needed_size.first;
intermediate_framebuffer_create_info.height = intermediate_needed_size.second;
intermediate_framebuffer_create_info.layers = 1;
if (dfn.vkCreateFramebuffer(
device, &intermediate_framebuffer_create_info, nullptr,
&paint_context_.guest_output_intermediate_framebuffers[i]) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create a guest output "
"intermediate framebuffer");
// Don't display the guest output, and don't try to create more
// intermediate textures (only destroy them).
guest_output_flow.effect_count = 0;
continue;
}
// Descriptors.
VkDescriptorImageInfo intermediate_descriptor_image_info;
intermediate_descriptor_image_info.sampler = VK_NULL_HANDLE;
intermediate_descriptor_image_info.imageView = intermediate_image_ptr_ref->view();
intermediate_descriptor_image_info.imageLayout =
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkWriteDescriptorSet intermediate_descriptor_write;
intermediate_descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
intermediate_descriptor_write.pNext = nullptr;
intermediate_descriptor_write.dstSet =
paint_context_.guest_output_descriptor_sets
[PaintContext ::kGuestOutputDescriptorSetIntermediate0Sampled + i];
intermediate_descriptor_write.dstBinding = 0;
intermediate_descriptor_write.dstArrayElement = 0;
intermediate_descriptor_write.descriptorCount = 1;
intermediate_descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
intermediate_descriptor_write.pImageInfo = &intermediate_descriptor_image_info;
intermediate_descriptor_write.pBufferInfo = nullptr;
intermediate_descriptor_write.pTexelBufferView = nullptr;
dfn.vkUpdateDescriptorSets(device, 1, &intermediate_descriptor_write, 0, nullptr);
} else {
// Was previously needed, but not anymore - destroy when possible.
if (intermediate_image_ptr_ref &&
paint_context_.submission_tracker.UpdateAndGetCompletedSubmission() >=
paint_context_.guest_output_intermediate_image_last_submission) {
intermediate_image_ptr_ref.reset();
util::DestroyAndNullHandle(dfn.vkDestroyFramebuffer, device,
paint_context_.guest_output_intermediate_framebuffers[i]);
}
}
}
}
if (guest_output_flow.effect_count) {
// Check if all the intermediate effects are supported by the
// implementation.
for (size_t i = 0; i + 1 < guest_output_flow.effect_count; ++i) {
if (paint_context_.guest_output_paint_pipelines[size_t(guest_output_flow.effects[i])]
.intermediate_pipeline == VK_NULL_HANDLE) {
guest_output_flow.effect_count = 0;
break;
}
}
if (guest_output_flow.effect_count) {
// Ensure the pipeline exists for the final effect drawing to the
// swapchain, for the render pass with the up-to-date image format.
GuestOutputPaintEffect swapchain_effect =
guest_output_flow.effects[guest_output_flow.effect_count - 1];
PaintContext::GuestOutputPaintPipeline& swapchain_effect_pipeline =
paint_context_.guest_output_paint_pipelines[size_t(swapchain_effect)];
if (swapchain_effect_pipeline.swapchain_pipeline != VK_NULL_HANDLE &&
swapchain_effect_pipeline.swapchain_format !=
paint_context_.swapchain_render_pass_format) {
paint_context_.submission_tracker.AwaitSubmissionCompletion(
paint_context_.guest_output_image_paint_last_submission);
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
swapchain_effect_pipeline.swapchain_pipeline);
swapchain_effect_pipeline.swapchain_format = VK_FORMAT_UNDEFINED;
}
if (swapchain_effect_pipeline.swapchain_pipeline == VK_NULL_HANDLE) {
assert_true(CanGuestOutputPaintEffectBeFinal(swapchain_effect));
assert_true(guest_output_paint_fs_[size_t(swapchain_effect)] != VK_NULL_HANDLE);
swapchain_effect_pipeline.swapchain_pipeline = CreateGuestOutputPaintPipeline(
swapchain_effect, paint_context_.swapchain_render_pass);
if (swapchain_effect_pipeline.swapchain_pipeline == VK_NULL_HANDLE) {
guest_output_flow.effect_count = 0;
} else {
swapchain_effect_pipeline.swapchain_format =
paint_context_.swapchain_render_pass_format;
}
}
}
}
if (guest_output_flow.effect_count) {
// Actually draw the guest output.
paint_context_.guest_output_image_paint_refs[guest_output_image_paint_ref_index].first =
current_paint_submission_index;
paint_context_.guest_output_image_paint_last_submission = current_paint_submission_index;
VkViewport guest_output_viewport;
guest_output_viewport.x = 0.0f;
guest_output_viewport.y = 0.0f;
guest_output_viewport.minDepth = 0.0f;
guest_output_viewport.maxDepth = 1.0f;
VkRect2D guest_output_scissor;
guest_output_scissor.offset.x = 0;
guest_output_scissor.offset.y = 0;
if (guest_output_flow.effect_count > 1) {
paint_context_.guest_output_intermediate_image_last_submission =
current_paint_submission_index;
}
[[maybe_unused]] bool temporal_effect_selected = false;
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
temporal_effect_selected =
guest_output_paint_config.GetEffect() == GuestOutputPaintConfig::Effect::kFsr2 ||
guest_output_paint_config.GetEffect() == GuestOutputPaintConfig::Effect::kFsr3;
#endif
for (size_t i = 0; i < guest_output_flow.effect_count; ++i) {
bool is_final_effect = i + 1 >= guest_output_flow.effect_count;
GuestOutputPaintEffect effect = guest_output_flow.effects[i];
std::pair<uint32_t, uint32_t> effect_rect_size = guest_output_flow.effect_output_sizes[i];
if (is_final_effect && guest_output_flow.output_x == 0 &&
guest_output_flow.output_y == 0 &&
effect_rect_size.first < paint_context_.swapchain_extent.width &&
effect_rect_size.second < paint_context_.swapchain_extent.height) {
static std::atomic<bool> logged_swapchain_expand = false;
if (!logged_swapchain_expand.exchange(true)) {
REXLOG_WARN(
"VulkanPresenter: expanding final guest output rect from "
"{}x{} to swapchain {}x{} to avoid top-left-only output",
effect_rect_size.first, effect_rect_size.second,
paint_context_.swapchain_extent.width, paint_context_.swapchain_extent.height);
}
effect_rect_size = std::make_pair(paint_context_.swapchain_extent.width,
paint_context_.swapchain_extent.height);
// Keep all final-pass data coherent - shader constants and
// letterbox clears are derived from guest_output_flow.
guest_output_flow.effect_output_sizes[i] = effect_rect_size;
guest_output_flow.output_x = 0;
guest_output_flow.output_y = 0;
guest_output_flow.letterbox_clear_rectangle_count = 0;
}
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
bool use_temporal_upscaler =
temporal_effect_selected && effect == GuestOutputPaintEffect::kFsrEasu;
if (use_temporal_upscaler && !is_final_effect) {
VkImage source_image =
i ? paint_context_.guest_output_intermediate_images[i - 1]->image()
: guest_output_image->image();
VkImage output_image = paint_context_.guest_output_intermediate_images[i]->image();
VkImageMemoryBarrier barrier_to_uav = {};
barrier_to_uav.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier_to_uav.srcAccessMask = 0;
barrier_to_uav.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
barrier_to_uav.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier_to_uav.newLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier_to_uav.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier_to_uav.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier_to_uav.image = output_image;
barrier_to_uav.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier_to_uav.subresourceRange.baseMipLevel = 0;
barrier_to_uav.subresourceRange.levelCount = 1;
barrier_to_uav.subresourceRange.baseArrayLayer = 0;
barrier_to_uav.subresourceRange.layerCount = 1;
dfn.vkCmdPipelineBarrier(draw_command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0,
nullptr, 1, &barrier_to_uav);
uint32_t effect_input_width = 0, effect_input_height = 0;
guest_output_flow.GetEffectInputSize(i, effect_input_width, effect_input_height);
if (DispatchTemporalUpscaler(draw_command_buffer, source_image, effect_input_width,
effect_input_height, output_image, effect_rect_size.first,
effect_rect_size.second, guest_output_paint_config)) {
VkImageMemoryBarrier barrier_to_shader_read = {};
barrier_to_shader_read.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier_to_shader_read.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
barrier_to_shader_read.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier_to_shader_read.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
barrier_to_shader_read.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier_to_shader_read.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier_to_shader_read.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier_to_shader_read.image = output_image;
barrier_to_shader_read.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier_to_shader_read.subresourceRange.baseMipLevel = 0;
barrier_to_shader_read.subresourceRange.levelCount = 1;
barrier_to_shader_read.subresourceRange.baseArrayLayer = 0;
barrier_to_shader_read.subresourceRange.layerCount = 1;
dfn.vkCmdPipelineBarrier(
draw_command_buffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0,
0, nullptr, 0, nullptr, 1, &barrier_to_shader_read);
continue;
}
}
#endif
int32_t effect_rect_x, effect_rect_y;
if (is_final_effect) {
effect_rect_x = guest_output_flow.output_x;
effect_rect_y = guest_output_flow.output_y;
dfn.vkCmdBeginRenderPass(draw_command_buffer, &swapchain_render_pass_begin_info,
VK_SUBPASS_CONTENTS_INLINE);
swapchain_image_pass_begun = true;
guest_output_viewport.width = float(paint_context_.swapchain_extent.width);
guest_output_viewport.height = float(paint_context_.swapchain_extent.height);
guest_output_scissor.extent.width = paint_context_.swapchain_extent.width;
guest_output_scissor.extent.height = paint_context_.swapchain_extent.height;
} else {
effect_rect_x = 0;
effect_rect_y = 0;
VkRenderPassBeginInfo intermediate_render_pass_begin_info;
intermediate_render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
intermediate_render_pass_begin_info.pNext = nullptr;
intermediate_render_pass_begin_info.renderPass = guest_output_intermediate_render_pass_;
intermediate_render_pass_begin_info.framebuffer =
paint_context_.guest_output_intermediate_framebuffers[i];
intermediate_render_pass_begin_info.renderArea.offset.x = 0;
intermediate_render_pass_begin_info.renderArea.offset.y = 0;
intermediate_render_pass_begin_info.renderArea.extent.width = effect_rect_size.first;
intermediate_render_pass_begin_info.renderArea.extent.height = effect_rect_size.second;
intermediate_render_pass_begin_info.clearValueCount = 0;
intermediate_render_pass_begin_info.pClearValues = nullptr;
dfn.vkCmdBeginRenderPass(draw_command_buffer, &intermediate_render_pass_begin_info,
VK_SUBPASS_CONTENTS_INLINE);
guest_output_viewport.width = float(effect_rect_size.first);
guest_output_viewport.height = float(effect_rect_size.second);
guest_output_scissor.extent.width = effect_rect_size.first;
guest_output_scissor.extent.height = effect_rect_size.second;
}
dfn.vkCmdSetViewport(draw_command_buffer, 0, 1, &guest_output_viewport);
dfn.vkCmdSetScissor(draw_command_buffer, 0, 1, &guest_output_scissor);
const PaintContext::GuestOutputPaintPipeline& effect_pipeline =
paint_context_.guest_output_paint_pipelines[size_t(effect)];
VkPipeline effect_vulkan_pipeline = is_final_effect
? effect_pipeline.swapchain_pipeline
: effect_pipeline.intermediate_pipeline;
assert_true(effect_vulkan_pipeline != VK_NULL_HANDLE);
dfn.vkCmdBindPipeline(draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
effect_vulkan_pipeline);
GuestOutputPaintPipelineLayoutIndex guest_output_paint_pipeline_layout_index =
GetGuestOutputPaintPipelineLayoutIndex(effect);
VkPipelineLayout guest_output_paint_pipeline_layout =
guest_output_paint_pipeline_layouts_[guest_output_paint_pipeline_layout_index];
PaintContext::GuestOutputDescriptorSet effect_src_descriptor_set;
if (i) {
effect_src_descriptor_set = PaintContext::GuestOutputDescriptorSet(
PaintContext::kGuestOutputDescriptorSetIntermediate0Sampled + (i - 1));
} else {
effect_src_descriptor_set = PaintContext::GuestOutputDescriptorSet(
PaintContext::kGuestOutputDescriptorSetGuestOutput0Sampled +
guest_output_image_paint_ref_index);
}
dfn.vkCmdBindDescriptorSets(
draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
guest_output_paint_pipeline_layout, 0, 1,
&paint_context_.guest_output_descriptor_sets[effect_src_descriptor_set], 0, nullptr);
GuestOutputPaintRectangleConstants effect_rect_constants;
float effect_x_to_ndc = 2.0f / guest_output_viewport.width;
float effect_y_to_ndc = 2.0f / guest_output_viewport.height;
effect_rect_constants.x = -1.0f + float(effect_rect_x) * effect_x_to_ndc;
effect_rect_constants.y = -1.0f + float(effect_rect_y) * effect_y_to_ndc;
effect_rect_constants.width = float(effect_rect_size.first) * effect_x_to_ndc;
effect_rect_constants.height = float(effect_rect_size.second) * effect_y_to_ndc;
dfn.vkCmdPushConstants(draw_command_buffer, guest_output_paint_pipeline_layout,
VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(effect_rect_constants),
&effect_rect_constants);
uint32_t effect_constants_size = 0;
union {
BilinearConstants bilinear;
#if defined(REX_HAS_FIDELITYFX_SDK)
CasSharpenConstants cas_sharpen;
CasResampleConstants cas_resample;
FsrEasuConstants fsr_easu;
FsrRcasConstants fsr_rcas;
#endif
} effect_constants;
switch (guest_output_paint_pipeline_layout_index) {
case kGuestOutputPaintPipelineLayoutIndexBilinear: {
effect_constants_size = sizeof(effect_constants.bilinear);
effect_constants.bilinear.Initialize(guest_output_flow, i);
} break;
#if defined(REX_HAS_FIDELITYFX_SDK)
case kGuestOutputPaintPipelineLayoutIndexCasSharpen: {
effect_constants_size = sizeof(effect_constants.cas_sharpen);
effect_constants.cas_sharpen.Initialize(guest_output_flow, i,
guest_output_paint_config);
} break;
case kGuestOutputPaintPipelineLayoutIndexCasResample: {
effect_constants_size = sizeof(effect_constants.cas_resample);
effect_constants.cas_resample.Initialize(guest_output_flow, i,
guest_output_paint_config);
} break;
case kGuestOutputPaintPipelineLayoutIndexFsrEasu: {
effect_constants_size = sizeof(effect_constants.fsr_easu);
effect_constants.fsr_easu.Initialize(guest_output_flow, i);
} break;
case kGuestOutputPaintPipelineLayoutIndexFsrRcas: {
effect_constants_size = sizeof(effect_constants.fsr_rcas);
effect_constants.fsr_rcas.Initialize(guest_output_flow, i, guest_output_paint_config);
} break;
#endif
default:
break;
}
if (effect_constants_size) {
dfn.vkCmdPushConstants(draw_command_buffer, guest_output_paint_pipeline_layout,
VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(effect_rect_constants),
effect_constants_size, &effect_constants);
}
dfn.vkCmdDraw(draw_command_buffer, 4, 1, 0, 0);
if (is_final_effect) {
// Clear the letterbox around the guest output if the guest output
// doesn't cover the entire swapchain image.
if (swapchain_image_clear_needed && guest_output_flow.letterbox_clear_rectangle_count) {
VkClearRect
letterbox_clear_vulkan_rectangles[GuestOutputPaintFlow::kMaxClearRectangles];
for (size_t i = 0; i < guest_output_flow.letterbox_clear_rectangle_count; ++i) {
VkClearRect& letterbox_clear_vulkan_rectangle =
letterbox_clear_vulkan_rectangles[i];
const GuestOutputPaintFlow::ClearRectangle& letterbox_clear_rectangle =
guest_output_flow.letterbox_clear_rectangles[i];
letterbox_clear_vulkan_rectangle.rect.offset.x =
int32_t(letterbox_clear_rectangle.x);
letterbox_clear_vulkan_rectangle.rect.offset.y =
int32_t(letterbox_clear_rectangle.y);
letterbox_clear_vulkan_rectangle.rect.extent.width =
letterbox_clear_rectangle.width;
letterbox_clear_vulkan_rectangle.rect.extent.height =
letterbox_clear_rectangle.height;
letterbox_clear_vulkan_rectangle.baseArrayLayer = 0;
letterbox_clear_vulkan_rectangle.layerCount = 1;
}
dfn.vkCmdClearAttachments(draw_command_buffer, 1, &swapchain_image_clear_attachment,
uint32_t(guest_output_flow.letterbox_clear_rectangle_count),
letterbox_clear_vulkan_rectangles);
}
swapchain_image_clear_needed = false;
} else {
// Still need the swapchain pass to be open for UI drawing.
dfn.vkCmdEndRenderPass(draw_command_buffer);
}
}
}
}
}
// Release main target guest output image references that aren't needed
// anymore (this is done after various potential guest-output-related main
// target submission tracker waits so the completed submission value is the
// most actual).
uint64_t completed_paint_submission =
paint_context_.submission_tracker.UpdateAndGetCompletedSubmission();
for (std::pair<uint64_t, std::shared_ptr<GuestOutputImage>>& guest_output_image_paint_ref :
paint_context_.guest_output_image_paint_refs) {
if (!guest_output_image_paint_ref.second ||
guest_output_image_paint_ref.second == guest_output_image) {
continue;
}
if (completed_paint_submission >= guest_output_image_paint_ref.first) {
guest_output_image_paint_ref.second.reset();
}
}
// If hasn't presented the guest output, begin the pass to clear and, if
// needed, to draw the UI.
if (!swapchain_image_pass_begun) {
dfn.vkCmdBeginRenderPass(draw_command_buffer, &swapchain_render_pass_begin_info,
VK_SUBPASS_CONTENTS_INLINE);
}
if (swapchain_image_clear_needed) {
VkClearRect swapchain_image_clear_rectangle;
swapchain_image_clear_rectangle.rect.offset.x = 0;
swapchain_image_clear_rectangle.rect.offset.y = 0;
swapchain_image_clear_rectangle.rect.extent = paint_context_.swapchain_extent;
swapchain_image_clear_rectangle.baseArrayLayer = 0;
swapchain_image_clear_rectangle.layerCount = 1;
dfn.vkCmdClearAttachments(draw_command_buffer, 1, &swapchain_image_clear_attachment, 1,
&swapchain_image_clear_rectangle);
swapchain_image_clear_needed = false;
}
if (execute_ui_drawers) {
// Draw the UI.
VulkanUIDrawContext ui_draw_context(
*this, paint_context_.swapchain_extent.width, paint_context_.swapchain_extent.height,
draw_command_buffer, ui_submission_tracker_.GetCurrentSubmission(),
ui_submission_tracker_.UpdateAndGetCompletedSubmission(),
paint_context_.swapchain_render_pass, paint_context_.swapchain_render_pass_format);
ExecuteUIDrawersFromUIThread(ui_draw_context);
}
dfn.vkCmdEndRenderPass(draw_command_buffer);
const auto timing_end_command_buffer_start = std::chrono::steady_clock::now();
dfn.vkEndCommandBuffer(draw_command_buffer);
const auto timing_end_command_buffer_end = std::chrono::steady_clock::now();
VkPipelineStageFlags acquire_semaphore_wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkCommandBuffer command_buffers[2];
uint32_t command_buffer_count = 0;
// UI setup command buffers must be accessed only if execute_ui_drawers is not
// null, to identify the UI thread.
size_t ui_setup_command_buffer_index =
execute_ui_drawers ? paint_context_.ui_setup_command_buffer_current_index : SIZE_MAX;
if (ui_setup_command_buffer_index != SIZE_MAX) {
PaintContext::UISetupCommandBuffer& ui_setup_command_buffer =
paint_context_.ui_setup_command_buffers[ui_setup_command_buffer_index];
dfn.vkEndCommandBuffer(ui_setup_command_buffer.command_buffer);
command_buffers[command_buffer_count++] = ui_setup_command_buffer.command_buffer;
// Release the current UI setup command buffer regardless of submission
// result. Failed submissions (if the UI submission index wasn't incremented
// since the previous draw) should be handled by UI drawers themselves by
// retrying all the failed work if needed.
paint_context_.ui_setup_command_buffer_current_index = SIZE_MAX;
}
command_buffers[command_buffer_count++] = draw_command_buffer;
// Signal the presentation-wait semaphore belonging to the acquired swapchain
// image (not a per-submission one - the submission ring's fence can't prove
// the presentation engine has finished waiting on a present semaphore, only
// re-acquisition of the same image can, so per-image semaphores are the only
// safe pooling; see PaintContext::swapchain_present_semaphores).
assert_true(size_t(swapchain_image_index) < paint_context_.swapchain_present_semaphores.size());
VkSemaphore present_semaphore =
paint_context_.swapchain_present_semaphores[swapchain_image_index];
VkSubmitInfo submit_info;
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.pNext = nullptr;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &acquire_semaphore;
submit_info.pWaitDstStageMask = &acquire_semaphore_wait_stage;
submit_info.commandBufferCount = command_buffer_count;
submit_info.pCommandBuffers = command_buffers;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &present_semaphore;
const auto timing_submit_start = std::chrono::steady_clock::now();
{
VulkanSubmissionTracker::FenceAcquisition fence_acqusition(
paint_context_.submission_tracker.AcquireFenceToAdvanceSubmission());
// Also update the submission tracker giving submission indices to UI draw
// callbacks if submission is successful.
VulkanSubmissionTracker::FenceAcquisition ui_fence_acquisition;
if (execute_ui_drawers) {
ui_fence_acquisition = ui_submission_tracker_.AcquireFenceToAdvanceSubmission();
}
VkResult submit_result;
{
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(vulkan_device_->queue_family_graphics_compute(), 0);
submit_result =
dfn.vkQueueSubmit(queue_acquisition.queue(), 1, &submit_info, fence_acqusition.fence());
if (ui_fence_acquisition.fence() != VK_NULL_HANDLE && submit_result == VK_SUCCESS) {
if (dfn.vkQueueSubmit(queue_acquisition.queue(), 0, nullptr,
ui_fence_acquisition.fence()) != VK_SUCCESS) {
ui_fence_acquisition.SubmissionSucceededSignalFailed();
}
}
}
if (submit_result != VK_SUCCESS) {
REXLOG_ERROR("VulkanPresenter: Failed to submit command buffers ({})",
vk::to_string(vk::Result(submit_result)));
fence_acqusition.SubmissionFailedOrDropped();
ui_fence_acquisition.SubmissionFailedOrDropped();
if (ui_setup_command_buffer_index != SIZE_MAX) {
// If failed to submit, make the UI setup command buffer available for
// immediate reuse, as the completed submission index won't be updated
// to the current index, and failing submissions with setup command
// buffer over and over will result in never reusing the setup command
// buffers.
paint_context_.ui_setup_command_buffers[ui_setup_command_buffer_index]
.last_usage_submission_index = 0;
}
// The image is in an acquired state - but now, it will be in it forever.
// To avoid that, recreate the swapchain - don't return just
// kNotPresented.
return PaintResult::kNotPresentedConnectionOutdated;
}
}
const auto timing_submit_end = std::chrono::steady_clock::now();
VkPresentInfoKHR present_info;
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.pNext = nullptr;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &present_semaphore;
present_info.swapchainCount = 1;
present_info.pSwapchains = &paint_context_.swapchain;
present_info.pImageIndices = &swapchain_image_index;
present_info.pResults = nullptr;
VkResult present_result;
const auto timing_present_start = std::chrono::steady_clock::now();
{
const VulkanDevice::Queue::Acquisition queue_acquisition =
vulkan_device_->AcquireQueue(paint_context_.present_queue_family, 0);
present_result = dfn.vkQueuePresentKHR(queue_acquisition.queue(), &present_info);
}
const auto timing_present_end = std::chrono::steady_clock::now();
AccumulateAndMaybeLogPresentTiming(
execute_ui_drawers, ElapsedUs(timing_acquire_start, timing_acquire_end),
ElapsedUs(timing_acquire_end, timing_submit_start),
ElapsedUs(timing_end_command_buffer_start, timing_end_command_buffer_end),
ElapsedUs(timing_submit_start, timing_submit_end),
ElapsedUs(timing_present_start, timing_present_end),
ElapsedUs(timing_start, timing_present_end));
switch (present_result) {
case VK_SUCCESS:
return PaintResult::kPresented;
case VK_SUBOPTIMAL_KHR:
return PaintResult::kPresentedSuboptimal;
case VK_ERROR_DEVICE_LOST:
REXLOG_ERROR(
"VulkanPresenter: Failed to present the swapchain image as the "
"device has been lost ({})",
vk::to_string(vk::Result(present_result)));
return PaintResult::kGpuLostResponsible;
case VK_ERROR_OUT_OF_DATE_KHR:
case VK_ERROR_SURFACE_LOST_KHR:
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
// Not an error, reporting just as info (may normally occur while resizing
// on some platforms).
REXLOG_INFO(
"VulkanPresenter: Presentation to the swapchain image has been "
"dropped as the swapchain or the surface has become outdated");
// Note that the semaphore wait (followed by reset) has been enqueued,
// however, this should have no effect on anything here likely.
return PaintResult::kNotPresentedConnectionOutdated;
default:
REXLOG_ERROR("VulkanPresenter: Failed to present the swapchain image ({})",
vk::to_string(vk::Result(present_result)));
// The image is in an acquired state - but now, it will be in it forever.
// To avoid that, recreate the swapchain - don't return just
// kNotPresented.
return PaintResult::kNotPresentedConnectionOutdated;
}
}
bool VulkanPresenter::InitializeSurfaceIndependent() {
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkDescriptorSetLayoutBinding guest_output_image_sampler_bindings[2];
guest_output_image_sampler_bindings[0].binding = 0;
guest_output_image_sampler_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
guest_output_image_sampler_bindings[0].descriptorCount = 1;
guest_output_image_sampler_bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
guest_output_image_sampler_bindings[0].pImmutableSamplers = nullptr;
const VkSampler sampler_linear_clamp =
ui_samplers_->samplers()[UISamplers::kSamplerIndexLinearClampToEdge];
guest_output_image_sampler_bindings[1].binding = 1;
guest_output_image_sampler_bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
guest_output_image_sampler_bindings[1].descriptorCount = 1;
guest_output_image_sampler_bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
guest_output_image_sampler_bindings[1].pImmutableSamplers = &sampler_linear_clamp;
VkDescriptorSetLayoutCreateInfo guest_output_paint_image_descriptor_set_layout_create_info;
guest_output_paint_image_descriptor_set_layout_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
guest_output_paint_image_descriptor_set_layout_create_info.pNext = nullptr;
guest_output_paint_image_descriptor_set_layout_create_info.flags = 0;
guest_output_paint_image_descriptor_set_layout_create_info.bindingCount =
uint32_t(rex::countof(guest_output_image_sampler_bindings));
guest_output_paint_image_descriptor_set_layout_create_info.pBindings =
guest_output_image_sampler_bindings;
if (dfn.vkCreateDescriptorSetLayout(
device, &guest_output_paint_image_descriptor_set_layout_create_info, nullptr,
&guest_output_paint_image_descriptor_set_layout_) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the guest output image descriptor "
"set layout");
return false;
}
VkPushConstantRange guest_output_paint_push_constant_ranges[2];
VkPushConstantRange& guest_output_paint_push_constant_range_rect =
guest_output_paint_push_constant_ranges[0];
guest_output_paint_push_constant_range_rect.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
guest_output_paint_push_constant_range_rect.offset = 0;
guest_output_paint_push_constant_range_rect.size = sizeof(GuestOutputPaintRectangleConstants);
VkPushConstantRange& guest_output_paint_push_constant_range_ffx =
guest_output_paint_push_constant_ranges[1];
guest_output_paint_push_constant_range_ffx.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
guest_output_paint_push_constant_range_ffx.offset =
guest_output_paint_push_constant_ranges[0].offset +
guest_output_paint_push_constant_ranges[0].size;
VkPipelineLayoutCreateInfo guest_output_paint_pipeline_layout_create_info;
guest_output_paint_pipeline_layout_create_info.sType =
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
guest_output_paint_pipeline_layout_create_info.pNext = nullptr;
guest_output_paint_pipeline_layout_create_info.flags = 0;
guest_output_paint_pipeline_layout_create_info.setLayoutCount = 1;
guest_output_paint_pipeline_layout_create_info.pSetLayouts =
&guest_output_paint_image_descriptor_set_layout_;
guest_output_paint_pipeline_layout_create_info.pPushConstantRanges =
guest_output_paint_push_constant_ranges;
for (size_t i = 0; i < size_t(kGuestOutputPaintPipelineLayoutCount); ++i) {
switch (GuestOutputPaintPipelineLayoutIndex(i)) {
case kGuestOutputPaintPipelineLayoutIndexBilinear:
guest_output_paint_push_constant_range_ffx.size = sizeof(BilinearConstants);
break;
#if defined(REX_HAS_FIDELITYFX_SDK)
case kGuestOutputPaintPipelineLayoutIndexCasSharpen:
guest_output_paint_push_constant_range_ffx.size = sizeof(CasSharpenConstants);
break;
case kGuestOutputPaintPipelineLayoutIndexCasResample:
guest_output_paint_push_constant_range_ffx.size = sizeof(CasResampleConstants);
break;
case kGuestOutputPaintPipelineLayoutIndexFsrEasu:
guest_output_paint_push_constant_range_ffx.size = sizeof(FsrEasuConstants);
break;
case kGuestOutputPaintPipelineLayoutIndexFsrRcas:
guest_output_paint_push_constant_range_ffx.size = sizeof(FsrRcasConstants);
break;
#endif
default:
assert_unhandled_case(GuestOutputPaintPipelineLayoutIndex(i));
continue;
}
guest_output_paint_pipeline_layout_create_info.pushConstantRangeCount =
1 + uint32_t(guest_output_paint_push_constant_range_ffx.size != 0);
if (dfn.vkCreatePipelineLayout(device, &guest_output_paint_pipeline_layout_create_info, nullptr,
&guest_output_paint_pipeline_layouts_[i]) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create a guest output presentation "
"pipeline layout with {} bytes of push constants",
guest_output_paint_push_constant_range_rect.size +
guest_output_paint_push_constant_range_ffx.size);
return false;
}
}
VkShaderModuleCreateInfo shader_module_create_info;
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_module_create_info.pNext = nullptr;
shader_module_create_info.flags = 0;
shader_module_create_info.codeSize = sizeof(shaders::guest_output_triangle_strip_rect_vs);
shader_module_create_info.pCode = shaders::guest_output_triangle_strip_rect_vs;
if (dfn.vkCreateShaderModule(device, &shader_module_create_info, nullptr,
&guest_output_paint_vs_) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the guest output presentation "
"vertex shader module");
return false;
}
for (size_t i = 0; i < size_t(GuestOutputPaintEffect::kCount); ++i) {
GuestOutputPaintEffect guest_output_paint_effect = GuestOutputPaintEffect(i);
switch (guest_output_paint_effect) {
case GuestOutputPaintEffect::kBilinear:
shader_module_create_info.codeSize = sizeof(shaders::guest_output_bilinear_ps);
shader_module_create_info.pCode = shaders::guest_output_bilinear_ps;
break;
case GuestOutputPaintEffect::kBilinearDither:
shader_module_create_info.codeSize = sizeof(shaders::guest_output_bilinear_dither_ps);
shader_module_create_info.pCode = shaders::guest_output_bilinear_dither_ps;
break;
#if defined(REX_HAS_FIDELITYFX_SDK)
case GuestOutputPaintEffect::kCasSharpen:
shader_module_create_info.codeSize = sizeof(shaders::guest_output_ffx_cas_sharpen_ps);
shader_module_create_info.pCode = shaders::guest_output_ffx_cas_sharpen_ps;
break;
case GuestOutputPaintEffect::kCasSharpenDither:
shader_module_create_info.codeSize =
sizeof(shaders::guest_output_ffx_cas_sharpen_dither_ps);
shader_module_create_info.pCode = shaders::guest_output_ffx_cas_sharpen_dither_ps;
break;
case GuestOutputPaintEffect::kCasResample:
shader_module_create_info.codeSize = sizeof(shaders::guest_output_ffx_cas_resample_ps);
shader_module_create_info.pCode = shaders::guest_output_ffx_cas_resample_ps;
break;
case GuestOutputPaintEffect::kCasResampleDither:
shader_module_create_info.codeSize =
sizeof(shaders::guest_output_ffx_cas_resample_dither_ps);
shader_module_create_info.pCode = shaders::guest_output_ffx_cas_resample_dither_ps;
break;
case GuestOutputPaintEffect::kFsrEasu:
shader_module_create_info.codeSize = sizeof(shaders::guest_output_ffx_fsr_easu_ps);
shader_module_create_info.pCode = shaders::guest_output_ffx_fsr_easu_ps;
break;
case GuestOutputPaintEffect::kFsrRcas:
shader_module_create_info.codeSize = sizeof(shaders::guest_output_ffx_fsr_rcas_ps);
shader_module_create_info.pCode = shaders::guest_output_ffx_fsr_rcas_ps;
break;
case GuestOutputPaintEffect::kFsrRcasDither:
shader_module_create_info.codeSize = sizeof(shaders::guest_output_ffx_fsr_rcas_dither_ps);
shader_module_create_info.pCode = shaders::guest_output_ffx_fsr_rcas_dither_ps;
break;
#endif
default:
// Not supported by this implementation.
continue;
}
if (dfn.vkCreateShaderModule(device, &shader_module_create_info, nullptr,
&guest_output_paint_fs_[i]) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the guest output painting shader "
"module for effect {}",
i);
return false;
}
}
VkAttachmentDescription intermediate_render_pass_attachment;
intermediate_render_pass_attachment.flags = 0;
intermediate_render_pass_attachment.format = kGuestOutputFormat;
intermediate_render_pass_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
intermediate_render_pass_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
intermediate_render_pass_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
intermediate_render_pass_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
intermediate_render_pass_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
intermediate_render_pass_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
intermediate_render_pass_attachment.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkAttachmentReference intermediate_render_pass_color_attachment;
intermediate_render_pass_color_attachment.attachment = 0;
intermediate_render_pass_color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription intermediate_render_pass_subpass = {};
intermediate_render_pass_subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
intermediate_render_pass_subpass.colorAttachmentCount = 1;
intermediate_render_pass_subpass.pColorAttachments = &intermediate_render_pass_color_attachment;
VkSubpassDependency intermediate_render_pass_dependencies[2];
intermediate_render_pass_dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
intermediate_render_pass_dependencies[0].dstSubpass = 0;
intermediate_render_pass_dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
intermediate_render_pass_dependencies[0].dstStageMask =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
intermediate_render_pass_dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
intermediate_render_pass_dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
intermediate_render_pass_dependencies[0].dependencyFlags = 0;
intermediate_render_pass_dependencies[1].srcSubpass = 0;
intermediate_render_pass_dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
intermediate_render_pass_dependencies[1].srcStageMask =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
intermediate_render_pass_dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
intermediate_render_pass_dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
intermediate_render_pass_dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
intermediate_render_pass_dependencies[1].dependencyFlags = 0;
VkRenderPassCreateInfo intermediate_render_pass_create_info;
intermediate_render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
intermediate_render_pass_create_info.pNext = nullptr;
intermediate_render_pass_create_info.flags = 0;
intermediate_render_pass_create_info.attachmentCount = 1;
intermediate_render_pass_create_info.pAttachments = &intermediate_render_pass_attachment;
intermediate_render_pass_create_info.subpassCount = 1;
intermediate_render_pass_create_info.pSubpasses = &intermediate_render_pass_subpass;
intermediate_render_pass_create_info.dependencyCount =
uint32_t(rex::countof(intermediate_render_pass_dependencies));
intermediate_render_pass_create_info.pDependencies = intermediate_render_pass_dependencies;
if (dfn.vkCreateRenderPass(device, &intermediate_render_pass_create_info, nullptr,
&guest_output_intermediate_render_pass_) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the guest output intermediate image "
"render pass");
return false;
}
// Initialize connection-independent parts of the painting context.
for (size_t i = 0; i < paint_context_.submissions.size(); ++i) {
paint_context_.submissions[i] = PaintContext::Submission::Create(vulkan_device_);
if (!paint_context_.submissions[i]) {
return false;
}
}
// Guest output paint pipelines drawing to intermediate images, not depending
// on runtime state unlike ones drawing to the swapchain images as those
// depend on the swapchain format.
for (size_t i = 0; i < size_t(GuestOutputPaintEffect::kCount); ++i) {
if (!CanGuestOutputPaintEffectBeIntermediate(GuestOutputPaintEffect(i)) ||
guest_output_paint_fs_[i] == VK_NULL_HANDLE) {
continue;
}
VkPipeline guest_output_paint_intermediate_pipeline = CreateGuestOutputPaintPipeline(
GuestOutputPaintEffect(i), guest_output_intermediate_render_pass_);
if (guest_output_paint_intermediate_pipeline == VK_NULL_HANDLE) {
return false;
}
paint_context_.guest_output_paint_pipelines[i].intermediate_pipeline =
guest_output_paint_intermediate_pipeline;
}
// Guest output painting descriptor sets.
VkDescriptorPoolSize guest_output_paint_descriptor_pool_sizes[2];
guest_output_paint_descriptor_pool_sizes[0].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
guest_output_paint_descriptor_pool_sizes[0].descriptorCount =
PaintContext::kGuestOutputDescriptorSetCount;
// Required even when using immutable samplers, otherwise failing to allocate
// descriptor sets (tested on AMD Software: Adrenalin Edition 22.3.2 on
// Windows 10 on AMD Radeon RX Vega 10 with Vulkan validation enabled).
guest_output_paint_descriptor_pool_sizes[1].type = VK_DESCRIPTOR_TYPE_SAMPLER;
guest_output_paint_descriptor_pool_sizes[1].descriptorCount =
PaintContext::kGuestOutputDescriptorSetCount;
VkDescriptorPoolCreateInfo guest_output_paint_descriptor_pool_create_info;
guest_output_paint_descriptor_pool_create_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
guest_output_paint_descriptor_pool_create_info.pNext = nullptr;
guest_output_paint_descriptor_pool_create_info.flags = 0;
guest_output_paint_descriptor_pool_create_info.maxSets =
PaintContext::kGuestOutputDescriptorSetCount;
guest_output_paint_descriptor_pool_create_info.poolSizeCount =
uint32_t(rex::countof(guest_output_paint_descriptor_pool_sizes));
guest_output_paint_descriptor_pool_create_info.pPoolSizes =
guest_output_paint_descriptor_pool_sizes;
if (dfn.vkCreateDescriptorPool(device, &guest_output_paint_descriptor_pool_create_info, nullptr,
&paint_context_.guest_output_descriptor_pool) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the guest output painting "
"descriptor pool");
return false;
}
VkDescriptorSetLayout
guest_output_paint_descriptor_set_layouts[PaintContext::kGuestOutputDescriptorSetCount];
std::fill(guest_output_paint_descriptor_set_layouts,
guest_output_paint_descriptor_set_layouts +
rex::countof(guest_output_paint_descriptor_set_layouts),
guest_output_paint_image_descriptor_set_layout_);
VkDescriptorSetAllocateInfo guest_output_paint_descriptor_set_allocate_info;
guest_output_paint_descriptor_set_allocate_info.sType =
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
guest_output_paint_descriptor_set_allocate_info.pNext = nullptr;
guest_output_paint_descriptor_set_allocate_info.descriptorPool =
paint_context_.guest_output_descriptor_pool;
guest_output_paint_descriptor_set_allocate_info.descriptorSetCount =
PaintContext::kGuestOutputDescriptorSetCount;
guest_output_paint_descriptor_set_allocate_info.pSetLayouts =
guest_output_paint_descriptor_set_layouts;
if (dfn.vkAllocateDescriptorSets(device, &guest_output_paint_descriptor_set_allocate_info,
paint_context_.guest_output_descriptor_sets) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to allocate the guest output painting "
"descriptor sets");
return false;
}
return InitializeCommonSurfaceIndependent();
}
VkPipeline VulkanPresenter::CreateGuestOutputPaintPipeline(GuestOutputPaintEffect effect,
VkRenderPass render_pass) {
VkPipelineShaderStageCreateInfo stages[2] = {};
for (uint32_t i = 0; i < 2; ++i) {
VkPipelineShaderStageCreateInfo& stage = stages[i];
stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stage.stage = i ? VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_VERTEX_BIT;
stage.pName = "main";
}
stages[0].module = guest_output_paint_vs_;
stages[1].module = guest_output_paint_fs_[size_t(effect)];
assert_true(stages[1].module != VK_NULL_HANDLE);
VkPipelineVertexInputStateCreateInfo vertex_input_state = {};
vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {};
input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
#if REX_PLATFORM_MAC
input_assembly_state.primitiveRestartEnable = VK_TRUE;
#endif
VkPipelineViewportStateCreateInfo viewport_state = {};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport_state.viewportCount = 1;
viewport_state.scissorCount = 1;
VkPipelineRasterizationStateCreateInfo rasterization_state = {};
rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization_state.polygonMode = VK_POLYGON_MODE_FILL;
rasterization_state.cullMode = VK_CULL_MODE_NONE;
rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterization_state.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo multisample_state = {};
multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisample_state.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendAttachmentState color_blend_attachment_state = {};
color_blend_attachment_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo color_blend_state = {};
color_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
color_blend_state.attachmentCount = 1;
color_blend_state.pAttachments = &color_blend_attachment_state;
static const VkDynamicState kPipelineDynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
VkPipelineDynamicStateCreateInfo dynamic_state = {};
dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic_state.dynamicStateCount = uint32_t(rex::countof(kPipelineDynamicStates));
dynamic_state.pDynamicStates = kPipelineDynamicStates;
VkGraphicsPipelineCreateInfo pipeline_create_info;
pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_create_info.pNext = nullptr;
pipeline_create_info.flags = 0;
pipeline_create_info.stageCount = uint32_t(rex::countof(stages));
pipeline_create_info.pStages = stages;
pipeline_create_info.pVertexInputState = &vertex_input_state;
pipeline_create_info.pInputAssemblyState = &input_assembly_state;
pipeline_create_info.pTessellationState = nullptr;
pipeline_create_info.pViewportState = &viewport_state;
pipeline_create_info.pRasterizationState = &rasterization_state;
pipeline_create_info.pMultisampleState = &multisample_state;
pipeline_create_info.pDepthStencilState = nullptr;
pipeline_create_info.pColorBlendState = &color_blend_state;
pipeline_create_info.pDynamicState = &dynamic_state;
pipeline_create_info.layout =
guest_output_paint_pipeline_layouts_[GetGuestOutputPaintPipelineLayoutIndex(effect)];
pipeline_create_info.renderPass = render_pass;
pipeline_create_info.subpass = 0;
pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
pipeline_create_info.basePipelineIndex = -1;
const VulkanDevice::Functions& dfn = vulkan_device_->functions();
const VkDevice device = vulkan_device_->device();
VkPipeline pipeline;
if (dfn.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_create_info, nullptr,
&pipeline) != VK_SUCCESS) {
REXLOG_ERROR(
"VulkanPresenter: Failed to create the guest output painting pipeline "
"for effect {}",
size_t(effect));
return VK_NULL_HANDLE;
}
return pipeline;
}
} // namespace vulkan
} // namespace ui
} // namespace rex