5#include <SDL3/SDL_oldnames.h>
6#include <SDL3/SDL_video.h>
7#include <SDL3/SDL_vulkan.h>
32#ifndef MXVK_SPRITE_SHADER_DIR
33#define MXVK_SPRITE_SHADER_DIR "."
36#ifndef MXVK_TEXT_SHADER_DIR
37#define MXVK_TEXT_SHADER_DIR "."
40#ifndef MXVK_DEFAULT_FONT_DIR
41#define MXVK_DEFAULT_FONT_DIR "."
47 const bool negative = (value & 0x8000U) != 0U;
48 const uint32_t exponent = (value >> 10U) & 0x1FU;
49 const uint32_t mantissa = value & 0x3FFU;
52 result = std::ldexp(
static_cast<float>(mantissa), -24);
53 }
else if (exponent == 31U) {
54 result = mantissa == 0U ? std::numeric_limits<float>::infinity() : std::numeric_limits<float>::quiet_NaN();
56 result = std::ldexp(1.0F +
static_cast<float>(mantissa) / 1024.0F,
static_cast<int>(exponent) - 15);
58 return negative ? -result : result;
62 const char *quiet_missing_validation = std::getenv(
"MXVK_QUIET_MISSING_VALIDATION");
63 return quiet_missing_validation ==
nullptr || std::strcmp(quiet_missing_validation,
"1") != 0;
67 VKAPI_ATTR VkBool32 VKAPI_CALL
VK_Window::debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT *callback_data, [[maybe_unused]]
void *user_data) {
68 const char *message = (callback_data !=
nullptr && callback_data->pMessage !=
nullptr) ? callback_data->pMessage :
"Unknown Vulkan validation message";
70 if ((severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0U) {
71 std::cerr << std::format(
"vk validation error: {}\n", message);
72 }
else if ((severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0U) {
73 std::cerr << std::format(
"vk validation warning: {}\n", message);
75 std::cout << std::format(
"vk validation: {}\n", message);
89 VK_Window::SwapchainSupport VK_Window::querySwapchainSupport(VkPhysicalDevice device, VkSurfaceKHR
surface) {
90 std::cout <<
"vk: querying swapchain support details\n";
91 SwapchainSupport support{};
93 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
device,
surface, &support.capabilities);
95 uint32_t format_count = 0;
96 vkGetPhysicalDeviceSurfaceFormatsKHR(
device,
surface, &format_count,
nullptr);
97 if (format_count > 0U) {
98 support.formats.resize(format_count);
99 vkGetPhysicalDeviceSurfaceFormatsKHR(
device,
surface, &format_count, support.formats.data());
102 uint32_t present_mode_count = 0;
103 vkGetPhysicalDeviceSurfacePresentModesKHR(
device,
surface, &present_mode_count,
nullptr);
104 if (present_mode_count > 0U) {
105 support.present_modes.resize(present_mode_count);
106 vkGetPhysicalDeviceSurfacePresentModesKHR(
device,
surface, &present_mode_count, support.present_modes.data());
112 VkSurfaceFormatKHR VK_Window::chooseSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &available_formats) {
113 for (
const VkSurfaceFormatKHR &format : available_formats) {
114 if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
119 return available_formats.front();
122 VkPresentModeKHR VK_Window::choosePresentMode(
const std::vector<VkPresentModeKHR> &available_present_modes)
const {
124 return VK_PRESENT_MODE_FIFO_KHR;
127 for (
const VkPresentModeKHR present_mode : available_present_modes) {
128 if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) {
133 return VK_PRESENT_MODE_FIFO_KHR;
136 VkExtent2D VK_Window::chooseExtent(
const VkSurfaceCapabilitiesKHR &capabilities, SDL_Window *window) {
137 if (capabilities.currentExtent.width != UINT32_MAX) {
138 return capabilities.currentExtent;
143 SDL_GetWindowSizeInPixels(
window, &width, &height);
145 VkExtent2D actual_extent{};
146 actual_extent.width = std::clamp(
static_cast<uint32_t
>(std::max(width, 1)), capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
147 actual_extent.height = std::clamp(
static_cast<uint32_t
>(std::max(height, 1)), capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
149 return actual_extent;
156 std::string VK_Window::resolveRuntimeShaderPath(
const std::string &shaderFileName,
const char *fallbackDir)
const {
157 if (shaderFileName.empty()) {
161 std::vector<std::filesystem::path> candidates{};
167 const auto add_executable_candidates = [&candidates, &shaderFileName](
const std::filesystem::path &executableDir) {
168 candidates.push_back(executableDir /
"data" / shaderFileName);
169 candidates.push_back(executableDir / shaderFileName);
170 candidates.push_back(executableDir.parent_path() /
"share" /
"mxvk" /
"shaders" / shaderFileName);
173 if (
const char *basePath = SDL_GetBasePath(); basePath !=
nullptr) {
174 add_executable_candidates(std::filesystem::path(basePath));
178 std::vector<wchar_t> executableName(MAX_PATH);
179 const DWORD executableLength = GetModuleFileNameW(
nullptr, executableName.data(),
static_cast<DWORD
>(executableName.size()));
180 if (executableLength > 0U && executableLength < executableName.size() - 1U) {
181 add_executable_candidates(std::filesystem::path(std::wstring(executableName.data(), executableLength)).parent_path());
185 candidates.push_back(std::filesystem::path(
"data") / shaderFileName);
188 const std::filesystem::path fontPath(
font_path);
189 if (fontPath.has_parent_path()) {
190 candidates.push_back(fontPath.parent_path() / shaderFileName);
194 if (fallbackDir !=
nullptr && fallbackDir[0] !=
'\0') {
195 candidates.push_back(std::filesystem::path(fallbackDir) / shaderFileName);
198 std::error_code existsError{};
199 for (
const std::filesystem::path &candidate : candidates) {
200 if (std::filesystem::exists(candidate, existsError)) {
201 return candidate.string();
206 throw mxvk::Exception(std::format(
"Failed to locate shader file '{}'", shaderFileName));
210 if (width <= 0 || height <= 0) {
215 std::cout << std::format(
"mxvk: starting VK_Window construction (title='{}', width={}, height={}, fullscreen={}, validation={}, mode={})\n", title, width, height, full, validiation,
headless() ?
"headless" :
"windowed");
216 SDL_WindowFlags flags =
static_cast<SDL_WindowFlags
>(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
218 std::cout <<
"SDL3: enabling fullscreen window flag\n";
219 flags =
static_cast<SDL_WindowFlags
>(flags | SDL_WINDOW_FULLSCREEN);
223 std::cout <<
"mxvk: initializing window subsystem and creating SDL window\n";
224 if (!
initWindow(title, width, height, flags)) {
229 std::cout <<
"mxvk: surface-free headless mode; SDL video and window creation skipped\n";
232 std::cout <<
"mxvk: initializing Vulkan runtime and rendering resources\n";
237 std::cout <<
"mxvk: VK_Window construction complete\n";
240 VK_Window::VK_Window(
const std::string &title,
int width,
int height,
bool full,
bool validiation,
bool enableVsync) :
VK_Window(title, width, height, full, validiation, enableVsync ?
PresentModePreference::Vsync :
PresentModePreference::LowLatency) {}
243 std::cout <<
"mxvk: destructor invoked, releasing resources\n";
248 std::cout <<
"mxvk: starting resource teardown\n";
249 stopScreenshotWorker();
251 if (
device != VK_NULL_HANDLE) {
252 std::cout <<
"vk: waiting for device idle before teardown\n";
270 std::cout << std::format(
"vk: releasing {} sprite(s)\n",
sprites.size());
272 std::cout <<
"vk: sprite resources released\n";
275 std::cout << std::format(
"vk: releasing {} 3D sprite batch(es)\n",
sprites3d.size());
279 destroySpritePipeline();
293 destroyTextPipeline();
299 cleanupSyncObjects();
303 vkDestroySwapchainKHR(
device, retired,
nullptr);
307 std::cout <<
"vk: tearing down swapchain-dependent resources\n";
308 cleanupSwapchain(
true);
311 std::cout <<
"vk: destroying command pool\n";
316 if (
device != VK_NULL_HANDLE) {
318 destroyPipelineCache();
319 std::cout <<
"vk: destroying logical device\n";
320 vkDestroyDevice(
device,
nullptr);
331 std::cout <<
"vk: destroying presentation surface\n";
336 cleanupDebugMessenger();
339 std::cout <<
"vk: destroying Vulkan instance\n";
340 vkDestroyInstance(
instance,
nullptr);
345 std::cout <<
"SDL3: destroying SDL window handle\n";
350 std::cout <<
"SDL3: unloading Vulkan loader library\n";
351 SDL_Vulkan_UnloadLibrary();
356 std::cout <<
"SDL3: shutting down SDL video/gamepad subsystems\n";
357 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
362 std::cout <<
"mxvk: resource teardown complete\n";
366 std::cout << std::format(
"mxvk: entering initVulkan (validation={})\n", validiation);
370 std::cerr <<
"mxvk: Cannot initialize Vulkan without an SDL window\n";
375 std::cout <<
"vk: instance already initialized; skipping initVulkan\n";
379 std::vector<const char *> enabled_extensions{};
381 std::cout <<
"vk: initializing volk directly from the Vulkan loader\n";
382 if (volkInitialize() != VK_SUCCESS) {
383 std::cerr <<
"mxvk: failed to initialize the Vulkan loader\n";
387 std::cout <<
"vk: initializing volk from SDL's Vulkan loader\n";
388 const auto get_instance_proc_addr =
reinterpret_cast<PFN_vkGetInstanceProcAddr
>(SDL_Vulkan_GetVkGetInstanceProcAddr());
389 if (get_instance_proc_addr ==
nullptr) {
390 std::cerr << std::format(
"mxvk: SDL did not provide vkGetInstanceProcAddr: {}\n", SDL_GetError());
393 volkInitializeCustom(get_instance_proc_addr);
395 unsigned int extension_count = 0;
396 std::cout <<
"SDL3: querying Vulkan instance extensions required by SDL\n";
397 const char *
const *extensions = SDL_Vulkan_GetInstanceExtensions(&extension_count);
398 if (extensions ==
nullptr || extension_count == 0U) {
399 std::cerr << std::format(
"mxvk: Failed to get Vulkan instance extensions: {}\n", SDL_GetError());
402 std::cout << std::format(
"vk: SDL provided {} required instance extension(s)\n", extension_count);
403 enabled_extensions.assign(extensions, extensions + extension_count);
406#if defined(MXVK_USE_MOLTENVK)
407 const auto append_instance_extension_if_missing = [&enabled_extensions](
const char *extension_name) {
408 const bool exists = std::ranges::any_of(enabled_extensions, [extension_name](
const char *existing) {
return std::strcmp(existing, extension_name) == 0; });
410 enabled_extensions.push_back(extension_name);
413 append_instance_extension_if_missing(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
414 append_instance_extension_if_missing(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
417 std::vector<const char *> enabled_layers{};
418 VkDebugUtilsMessengerCreateInfoEXT debug_create_info{};
420 if (!hasValidationLayerSupport()) {
421 if (shouldLogMissingValidationLayer()) {
422 std::cerr << std::format(
"mxvk: validation layer '{}' is not available; continuing without validation\n", validation_layer_name);
426 enabled_layers.push_back(validation_layer_name);
427 enabled_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
428 const std::optional<VkDebugUtilsMessengerCreateInfoEXT> maybe_debug_create_info = makeDebugMessengerCreateInfo();
429 if (!maybe_debug_create_info.has_value()) {
430 std::cerr <<
"mxvk: failed to construct debug messenger create info\n";
432 enabled_layers.clear();
434 debug_create_info = maybe_debug_create_info.value();
439 VkApplicationInfo app_info{};
440 app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
441 app_info.pApplicationName =
"mxvk";
442 app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
443 app_info.pEngineName =
"mxvk";
444 app_info.engineVersion = VK_MAKE_VERSION(MXVK_VERSION_CODE_MAJOR, MXVK_VERSION_CODE_MINOR, MXVK_VERSION_CODE_PATCH);
445 app_info.apiVersion = VK_API_VERSION_1_4;
447 VkInstanceCreateInfo create_info{};
448 create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
449 create_info.pApplicationInfo = &app_info;
450 create_info.enabledExtensionCount =
static_cast<uint32_t
>(enabled_extensions.size());
451 create_info.ppEnabledExtensionNames = enabled_extensions.data();
452 create_info.enabledLayerCount =
static_cast<uint32_t
>(enabled_layers.size());
453 create_info.ppEnabledLayerNames = enabled_layers.empty() ? nullptr : enabled_layers.data();
456 create_info.enabledExtensionCount =
static_cast<uint32_t
>(enabled_extensions.size());
457#if defined(MXVK_USE_MOLTENVK)
458 create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
461 std::cout <<
"vk: creating Vulkan instance\n";
462 if (vkCreateInstance(&create_info,
nullptr, &
instance) != VK_SUCCESS) {
463 std::cerr <<
"mxvk: Failed to create Vulkan instance\n";
468 std::cout <<
"vk: loading Vulkan instance function pointers via volk\n";
471 setupDebugMessenger();
473 uint32_t instanceVersion = 0;
474 if (vkEnumerateInstanceVersion !=
nullptr) {
475 vkEnumerateInstanceVersion(&instanceVersion);
477 std::cout <<
"vk: Vulkan instance version: " << VK_VERSION_MAJOR(instanceVersion) <<
"." << VK_VERSION_MINOR(instanceVersion) <<
"." << VK_VERSION_PATCH(instanceVersion) <<
"\n";
478 std::cout <<
"mxvk: engine version: " << MXVK_VERSION_CODE_MAJOR <<
"." << MXVK_VERSION_CODE_MINOR <<
"." << MXVK_VERSION_CODE_PATCH <<
"\n";
481 std::cout <<
"SDL3: creating Vulkan presentation surface from SDL window\n";
483 std::cerr << std::format(
"mxvk: Failed to create Vulkan surface: {}\n", SDL_GetError());
484 cleanupDebugMessenger();
485 vkDestroyInstance(
instance,
nullptr);
492 std::cout <<
"mxvk: selecting suitable physical device\n";
495 std::cerr <<
"mxvk: Failed to find a suitable Vulkan physical device\n";
499 std::cout <<
"mxvk: creating logical device and queues\n";
501 if (
device == VK_NULL_HANDLE) {
502 std::cerr <<
"mxvk: Failed to create Vulkan logical device\n";
505 createPipelineCache();
507 std::cout <<
"mxvk: deferring swapchain/render/sync resource creation until first frame\n";
509 std::cout <<
"mxvk: initVulkan complete\n";
513 std::string VK_Window::pipelineCachePath()
const {
518 VkPhysicalDeviceProperties properties{};
521 char *pref_path = SDL_GetPrefPath(
"mxvk",
"MXVK");
522 std::filesystem::path base_path;
523 if (pref_path !=
nullptr) {
524 base_path = pref_path;
527 base_path = std::filesystem::current_path();
531 std::filesystem::create_directories(base_path, ec);
536 std::ostringstream filename;
537 filename <<
"pipeline_cache_" << std::hex << std::setfill(
'0') << properties.vendorID <<
'_' << properties.deviceID <<
'_' << properties.driverVersion <<
'_';
538 for (uint8_t
byte : properties.pipelineCacheUUID) {
539 filename << std::setw(2) << static_cast<unsigned>(
byte);
543 return (base_path / filename.str()).string();
546 void VK_Window::createPipelineCache() {
551 const std::string cache_path = pipelineCachePath();
552 std::vector<char> initial_data{};
553 if (!cache_path.empty() && std::filesystem::exists(cache_path)) {
554 std::ifstream file(cache_path, std::ios::binary);
556 initial_data.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
560 VkPipelineCacheCreateInfo create_info{};
561 create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
562 create_info.initialDataSize = initial_data.size();
563 create_info.pInitialData = initial_data.empty() ? nullptr : initial_data.data();
566 if (result != VK_SUCCESS && !initial_data.empty()) {
567 std::cerr <<
"vk: cached pipeline data rejected; creating an empty pipeline cache\n";
568 create_info.initialDataSize = 0;
569 create_info.pInitialData =
nullptr;
573 if (result != VK_SUCCESS) {
575 std::cerr << std::format(
"vk: failed to create pipeline cache ({})\n",
static_cast<int>(result));
579 if (!initial_data.empty()) {
580 std::cout << std::format(
"vk: loaded pipeline cache: {} bytes\n", initial_data.size());
584 void VK_Window::savePipelineCache()
const {
589 size_t data_size = 0;
591 if (result != VK_SUCCESS || data_size == 0) {
595 std::vector<char> data(data_size);
597 if (result != VK_SUCCESS || data_size == 0) {
600 data.resize(data_size);
602 const std::string cache_path = pipelineCachePath();
603 if (cache_path.empty()) {
607 std::ofstream file(cache_path, std::ios::binary | std::ios::trunc);
609 std::cerr << std::format(
"vk: failed to open pipeline cache for writing: {}\n", cache_path);
613 file.write(data.data(),
static_cast<std::streamsize
>(data.size()));
615 std::cout << std::format(
"vk: saved pipeline cache: {} bytes\n", data.size());
619 void VK_Window::destroyPipelineCache() {
628 case SDL_EVENT_KEY_DOWN:
638 clear_color.float32[0] = std::clamp(r, 0.0f, 1.0f);
639 clear_color.float32[1] = std::clamp(g, 0.0f, 1.0f);
640 clear_color.float32[2] = std::clamp(b, 0.0f, 1.0f);
641 clear_color.float32[3] = std::clamp(a, 0.0f, 1.0f);
666 while (SDL_PollEvent(&e)) {
671 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
678 case SDL_EVENT_KEY_DOWN:
679 if ((e.key.key == SDLK_F12 || e.key.scancode == SDL_SCANCODE_F12) && !e.key.repeat) {
682 if (
screenshot_enabled && (e.key.key == SDLK_F10 || e.key.scancode == SDL_SCANCODE_F10) && !e.key.repeat) {
686 std::cerr << std::format(
"mxvk: screenshot failed: {}\n", ex.
text());
687 }
catch (
const std::exception &ex) {
688 std::cerr << std::format(
"mxvk: screenshot failed: {}\n", ex.what());
699 const uint64_t now_ms = SDL_GetTicks();
714 void VK_Window::saveScreenshot() {
715 const std::string path = makeScreenshotPath();
716 std::vector<std::uint8_t> rgba{};
720 enqueueScreenshotSave(path, std::move(rgba), width, height);
721 std::cout << std::format(
"mxvk: screenshot queued: {}\n", path);
724 void VK_Window::enqueueScreenshotSave(std::string path, std::vector<std::uint8_t> rgba, uint32_t width, uint32_t height) {
726 throw mxvk::Exception(
"enqueueScreenshotSave requires a non-empty output path");
728 if (rgba.empty() || width == 0U || height == 0U) {
729 throw mxvk::Exception(
"enqueueScreenshotSave requires non-empty pixel data");
732 startScreenshotWorker();
737 .path = std::move(path),
738 .rgba = std::move(rgba),
746 void VK_Window::startScreenshotWorker() {
756 void VK_Window::stopScreenshotWorker() {
770 void VK_Window::screenshotWorkerLoop() {
785 if (!
mxvk::SavePNG_RGBA(task.path.c_str(), task.rgba.data(),
static_cast<int>(task.width),
static_cast<int>(task.height))) {
786 std::cerr << std::format(
"mxvk: screenshot failed to write PNG: {}\n", task.path);
789 std::cout << std::format(
"mxvk: screenshot saved: {}\n", task.path);
793 std::string VK_Window::makeScreenshotPath() {
794 const char *home = std::getenv(
"HOME");
795 std::filesystem::path pictures_dir = (home !=
nullptr && home[0] !=
'\0') ? std::filesystem::path(home) /
"Pictures" : std::filesystem::path(
"Pictures");
796 std::filesystem::create_directories(pictures_dir);
798 const std::time_t now = std::time(
nullptr);
799 std::tm local_time{};
801 localtime_s(&local_time, &now);
803 localtime_r(&now, &local_time);
806 std::ostringstream date_stream;
807 date_stream << std::put_time(&local_time,
"%Y.%m.%d");
808 std::ostringstream time_stream;
809 time_stream << std::put_time(&local_time,
"%H.%M.%S");
813 for (uint32_t attempt = 0; attempt < 10000U; ++attempt) {
815 std::filesystem::path path = pictures_dir / std::format(
"{}{}.png", prefix, index);
816 if (!std::filesystem::exists(path)) {
817 return path.string();
821 return (pictures_dir / std::format(
"{}{}.png", prefix,
screenshot_index++)).string();
828 throw mxvk::Exception(
"saveSnapshot requires a non-empty output path");
831 std::vector<std::uint8_t> rgba{};
836 if (!
mxvk::SavePNG_RGBA(path.c_str(), rgba.data(),
static_cast<int>(width),
static_cast<int>(height))) {
856 throw mxvk::Exception(
"captureSnapshotPixels called before Vulkan render resources are ready");
859 throw mxvk::Exception(
"captureSnapshotPixels called before a frame has been presented");
863 throw mxvk::Exception(
"captureSnapshotPixels requires swapchain transfer-source support");
867 const VkImageLayout source_layout = capture_offscreen ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL :
headless() ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
868 if (source_extent.width == 0U || source_extent.height == 0U) {
869 throw mxvk::Exception(
"captureSnapshotPixels cannot capture an empty swapchain extent");
874 if (!format_is_bgra && !format_is_rgba) {
878 const VkDeviceSize row_bytes =
static_cast<VkDeviceSize
>(source_extent.width) * 4U;
879 const VkDeviceSize image_bytes = row_bytes *
static_cast<VkDeviceSize
>(source_extent.height);
881 VkBuffer readback_buffer = VK_NULL_HANDLE;
882 VkDeviceMemory readback_memory = VK_NULL_HANDLE;
883 VkCommandBuffer command_buffer = VK_NULL_HANDLE;
884 VkFence copy_fence = VK_NULL_HANDLE;
886 auto cleanup = [&]() {
887 if (copy_fence != VK_NULL_HANDLE) {
888 vkDestroyFence(
device, copy_fence,
nullptr);
890 if (command_buffer != VK_NULL_HANDLE) {
893 if (readback_buffer != VK_NULL_HANDLE) {
894 vkDestroyBuffer(
device, readback_buffer,
nullptr);
896 if (readback_memory != VK_NULL_HANDLE) {
897 vkFreeMemory(
device, readback_memory,
nullptr);
902 VkBufferCreateInfo buffer_info{};
903 buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
904 buffer_info.size = image_bytes;
905 buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
906 buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
907 if (vkCreateBuffer(
device, &buffer_info,
nullptr, &readback_buffer) != VK_SUCCESS) {
908 throw mxvk::Exception(
"captureSnapshotPixels failed to create readback buffer");
911 VkMemoryRequirements memory_requirements{};
912 vkGetBufferMemoryRequirements(
device, readback_buffer, &memory_requirements);
914 VkPhysicalDeviceMemoryProperties memory_properties{};
915 vkGetPhysicalDeviceMemoryProperties(
physical_device, &memory_properties);
916 uint32_t memory_type_index = invalid_queue_index;
917 constexpr VkMemoryPropertyFlags REQUIRED_PROPERTIES = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
918 constexpr VkMemoryPropertyFlags PREFERRED_PROPERTIES = REQUIRED_PROPERTIES | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
919 for (VkMemoryPropertyFlags properties : {PREFERRED_PROPERTIES, REQUIRED_PROPERTIES}) {
920 for (uint32_t index = 0; index < memory_properties.memoryTypeCount; ++index) {
921 const bool type_matches = (memory_requirements.memoryTypeBits & (1U << index)) != 0U;
922 const bool properties_match = (memory_properties.memoryTypes[index].propertyFlags & properties) == properties;
923 if (type_matches && properties_match) {
924 memory_type_index = index;
928 if (memory_type_index != invalid_queue_index) {
932 if (memory_type_index == invalid_queue_index) {
933 throw mxvk::Exception(
"captureSnapshotPixels failed to find host-visible coherent memory");
936 VkMemoryAllocateInfo allocation_info{};
937 allocation_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
938 allocation_info.allocationSize = memory_requirements.size;
939 allocation_info.memoryTypeIndex = memory_type_index;
940 if (vkAllocateMemory(
device, &allocation_info,
nullptr, &readback_memory) != VK_SUCCESS) {
941 throw mxvk::Exception(
"captureSnapshotPixels failed to allocate readback memory");
943 if (vkBindBufferMemory(
device, readback_buffer, readback_memory, 0) != VK_SUCCESS) {
944 throw mxvk::Exception(
"captureSnapshotPixels failed to bind readback memory");
947 VkCommandBufferAllocateInfo command_buffer_info{};
948 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
950 command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
951 command_buffer_info.commandBufferCount = 1;
952 if (vkAllocateCommandBuffers(
device, &command_buffer_info, &command_buffer) != VK_SUCCESS) {
953 throw mxvk::Exception(
"captureSnapshotPixels failed to allocate command buffer");
956 VkFenceCreateInfo fence_info{};
957 fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
958 if (vkCreateFence(
device, &fence_info,
nullptr, ©_fence) != VK_SUCCESS) {
959 throw mxvk::Exception(
"captureSnapshotPixels failed to create copy fence");
963 if (source_fence != VK_NULL_HANDLE) {
964 const VkResult wait_result = vkWaitForFences(
device, 1, &source_fence, VK_TRUE, UINT64_MAX);
965 if (wait_result != VK_SUCCESS) {
966 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed waiting for rendered frame: {}",
static_cast<int>(wait_result)));
970 const VkResult idle_result = vkDeviceWaitIdle(
device);
971 if (idle_result != VK_SUCCESS) {
972 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed waiting for device idle: {}",
static_cast<int>(idle_result)));
975 VkCommandBufferBeginInfo begin_info{};
976 begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
977 begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
978 if (vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) {
979 throw mxvk::Exception(
"captureSnapshotPixels failed to begin copy command buffer");
982 VkImageMemoryBarrier2 to_transfer_barrier{};
983 to_transfer_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
984 to_transfer_barrier.srcStageMask = capture_offscreen ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT :
headless() ? VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT : VK_PIPELINE_STAGE_2_NONE;
985 to_transfer_barrier.srcAccessMask = capture_offscreen ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT :
headless() ? VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_2_NONE;
986 to_transfer_barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
987 to_transfer_barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
988 to_transfer_barrier.oldLayout = source_layout;
989 to_transfer_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
990 to_transfer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
991 to_transfer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
992 to_transfer_barrier.image = source_image;
993 to_transfer_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
994 to_transfer_barrier.subresourceRange.baseMipLevel = 0;
995 to_transfer_barrier.subresourceRange.levelCount = 1;
996 to_transfer_barrier.subresourceRange.baseArrayLayer = 0;
997 to_transfer_barrier.subresourceRange.layerCount = 1;
999 VkDependencyInfo to_transfer_dependency{};
1000 to_transfer_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
1001 to_transfer_dependency.imageMemoryBarrierCount = 1;
1002 to_transfer_dependency.pImageMemoryBarriers = &to_transfer_barrier;
1003 vkCmdPipelineBarrier2(command_buffer, &to_transfer_dependency);
1005 VkBufferImageCopy copy_region{};
1006 copy_region.bufferOffset = 0;
1007 copy_region.bufferRowLength = 0;
1008 copy_region.bufferImageHeight = 0;
1009 copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1010 copy_region.imageSubresource.mipLevel = 0;
1011 copy_region.imageSubresource.baseArrayLayer = 0;
1012 copy_region.imageSubresource.layerCount = 1;
1013 copy_region.imageOffset = {0, 0, 0};
1014 copy_region.imageExtent = {source_extent.width, source_extent.height, 1};
1015 vkCmdCopyImageToBuffer(command_buffer, source_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback_buffer, 1, ©_region);
1017 VkImageMemoryBarrier2 to_present_barrier{};
1018 to_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
1019 to_present_barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
1020 to_present_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
1021 to_present_barrier.dstStageMask = capture_offscreen ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT :
headless() ? VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT : VK_PIPELINE_STAGE_2_NONE;
1022 to_present_barrier.dstAccessMask = capture_offscreen ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT :
headless() ? VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_2_NONE;
1023 to_present_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
1024 to_present_barrier.newLayout = source_layout;
1025 to_present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1026 to_present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1027 to_present_barrier.image = source_image;
1028 to_present_barrier.subresourceRange = to_transfer_barrier.subresourceRange;
1030 VkDependencyInfo to_present_dependency{};
1031 to_present_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
1032 to_present_dependency.imageMemoryBarrierCount = 1;
1033 to_present_dependency.pImageMemoryBarriers = &to_present_barrier;
1034 vkCmdPipelineBarrier2(command_buffer, &to_present_dependency);
1036 if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
1037 throw mxvk::Exception(
"captureSnapshotPixels failed to end copy command buffer");
1040 VkCommandBufferSubmitInfo command_submit_info{};
1041 command_submit_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
1042 command_submit_info.commandBuffer = command_buffer;
1044 VkSubmitInfo2 submit_info{};
1045 submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
1046 submit_info.commandBufferInfoCount = 1;
1047 submit_info.pCommandBufferInfos = &command_submit_info;
1049 const VkResult submit_result = vkQueueSubmit2(
graphics_queue, 1, &submit_info, copy_fence);
1050 if (submit_result != VK_SUCCESS) {
1051 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed to submit copy command: {}",
static_cast<int>(submit_result)));
1053 const VkResult copy_wait_result = vkWaitForFences(
device, 1, ©_fence, VK_TRUE, UINT64_MAX);
1054 if (copy_wait_result != VK_SUCCESS) {
1055 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed waiting for copy: {}",
static_cast<int>(copy_wait_result)));
1058 void *mapped =
nullptr;
1059 if (vkMapMemory(
device, readback_memory, 0, image_bytes, 0, &mapped) != VK_SUCCESS) {
1060 throw mxvk::Exception(
"captureSnapshotPixels failed to map readback memory");
1063 const auto *src =
static_cast<const std::uint8_t *
>(mapped);
1064 rgba_pixels.resize(
static_cast<std::size_t
>(image_bytes));
1065 for (std::size_t i = 0; i < rgba_pixels.size(); i += 4U) {
1066 if (format_is_bgra) {
1067 rgba_pixels[i + 0U] = src[i + 2U];
1068 rgba_pixels[i + 1U] = src[i + 1U];
1069 rgba_pixels[i + 2U] = src[i + 0U];
1070 rgba_pixels[i + 3U] = src[i + 3U];
1072 rgba_pixels[i + 0U] = src[i + 0U];
1073 rgba_pixels[i + 1U] = src[i + 1U];
1074 rgba_pixels[i + 2U] = src[i + 2U];
1075 rgba_pixels[i + 3U] = src[i + 3U];
1078 vkUnmapMemory(
device, readback_memory);
1079 width = source_extent.width;
1080 height = source_extent.height;
1089 void VK_Window::onFrameReadback([[maybe_unused]] std::vector<std::uint8_t> &rgba_pixels, [[maybe_unused]] uint32_t width, [[maybe_unused]] uint32_t height) {}
1096 if (!slot.pending) {
1099 if (slot.mapped ==
nullptr || slot.width == 0U || slot.height == 0U) {
1100 slot.pending =
false;
1104 if (slot.format == VK_FORMAT_R16G16B16A16_SFLOAT) {
1105 const auto *source =
static_cast<const std::uint16_t *
>(slot.mapped);
1106 const std::size_t component_count =
static_cast<std::size_t
>(slot.width) * slot.height * 4U;
1108 for (std::size_t component = 0; component < component_count; ++component) {
1109 const float normalized = std::clamp(half_to_float(source[component]), 0.0F, 1.0F);
1114 slot.pending =
false;
1119 const auto *source =
static_cast<const std::uint8_t *
>(slot.mapped);
1120 const std::size_t pixel_bytes =
static_cast<std::size_t
>(slot.width) * slot.height * 4U;
1122 const bool format_is_bgra = slot.format == VK_FORMAT_B8G8R8A8_UNORM || slot.format == VK_FORMAT_B8G8R8A8_SRGB;
1123 for (std::size_t offset = 0; offset < pixel_bytes; offset += 4U) {
1124 if (format_is_bgra) {
1139 slot.pending =
false;
1144 if (
device == VK_NULL_HANDLE) {
1147 const bool has_pending = std::ranges::any_of(
frame_readback_slots, [](
const FrameReadbackSlot &slot) {
return slot.pending; });
1152 const VkResult wait_result = vkDeviceWaitIdle(
device);
1153 if (wait_result != VK_SUCCESS) {
1154 throw mxvk::Exception(std::format(
"frame readback flush failed waiting for the device (VkResult={})",
static_cast<int>(wait_result)));
1156 for (uint32_t offset = 0; offset < max_frames_in_flight; ++offset) {
1157 const uint32_t slot_index = (
current_frame + offset) % max_frames_in_flight;
1167 throw mxvk::Exception(
"frame readback requires swapchain transfer-source support");
1171 const VkDeviceSize required_size =
static_cast<VkDeviceSize
>(readback_extent.width) * readback_extent.height * (
frame_readback_rgba16_enabled ? 8U : 4U);
1172 if (required_size == 0U) {
1173 throw mxvk::Exception(
"frame readback cannot use an empty swapchain extent");
1175 const bool resources_ready = std::ranges::all_of(
frame_readback_slots, [&](
const FrameReadbackSlot &slot) {
return slot.buffer != VK_NULL_HANDLE && slot.mapped !=
nullptr && slot.size >= required_size; });
1176 if (resources_ready) {
1180 if (
device != VK_NULL_HANDLE) {
1182 vkDeviceWaitIdle(
device);
1186 VkBufferCreateInfo buffer_info{};
1187 buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1188 buffer_info.size = required_size;
1189 buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1190 buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1191 VkPhysicalDeviceMemoryProperties memory_properties{};
1192 vkGetPhysicalDeviceMemoryProperties(
physical_device, &memory_properties);
1193 constexpr VkMemoryPropertyFlags REQUIRED_PROPERTIES = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
1194 constexpr VkMemoryPropertyFlags PREFERRED_PROPERTIES = REQUIRED_PROPERTIES | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
1195 bool memory_type_logged =
false;
1198 if (vkCreateBuffer(
device, &buffer_info,
nullptr, &slot.buffer) != VK_SUCCESS) {
1199 throw mxvk::Exception(
"failed to create pipelined frame-readback buffer");
1202 VkMemoryRequirements memory_requirements{};
1203 vkGetBufferMemoryRequirements(
device, slot.buffer, &memory_requirements);
1204 uint32_t memory_type_index = invalid_queue_index;
1205 for (VkMemoryPropertyFlags properties : {PREFERRED_PROPERTIES, REQUIRED_PROPERTIES}) {
1206 for (uint32_t index = 0; index < memory_properties.memoryTypeCount; ++index) {
1207 const bool type_matches = (memory_requirements.memoryTypeBits & (1U << index)) != 0U;
1208 const bool properties_match = (memory_properties.memoryTypes[index].propertyFlags & properties) == properties;
1209 if (type_matches && properties_match) {
1210 memory_type_index = index;
1214 if (memory_type_index != invalid_queue_index) {
1218 if (memory_type_index == invalid_queue_index) {
1219 throw mxvk::Exception(
"failed to find host-visible frame-readback memory");
1221 if (!memory_type_logged) {
1222 const VkMemoryPropertyFlags selected_properties = memory_properties.memoryTypes[memory_type_index].propertyFlags;
1223 std::cout << std::format(
"mxvk: frame readback memory type {} ({})\n", memory_type_index, (selected_properties & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0U ?
"host cached" :
"host coherent fallback");
1224 memory_type_logged =
true;
1227 VkMemoryAllocateInfo allocation_info{};
1228 allocation_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1229 allocation_info.allocationSize = memory_requirements.size;
1230 allocation_info.memoryTypeIndex = memory_type_index;
1231 if (vkAllocateMemory(
device, &allocation_info,
nullptr, &slot.memory) != VK_SUCCESS) {
1234 if (vkBindBufferMemory(
device, slot.buffer, slot.memory, 0) != VK_SUCCESS) {
1237 if (vkMapMemory(
device, slot.memory, 0, required_size, 0, &slot.mapped) != VK_SUCCESS) {
1240 slot.size = required_size;
1254 if (
device != VK_NULL_HANDLE && slot.mapped !=
nullptr && slot.memory != VK_NULL_HANDLE) {
1255 vkUnmapMemory(
device, slot.memory);
1257 slot.mapped =
nullptr;
1258 if (
device != VK_NULL_HANDLE && slot.buffer != VK_NULL_HANDLE) {
1259 vkDestroyBuffer(
device, slot.buffer,
nullptr);
1261 slot.buffer = VK_NULL_HANDLE;
1262 if (
device != VK_NULL_HANDLE && slot.memory != VK_NULL_HANDLE) {
1263 vkFreeMemory(
device, slot.memory,
nullptr);
1265 slot.memory = VK_NULL_HANDLE;
1269 slot.format = VK_FORMAT_UNDEFINED;
1270 slot.pending =
false;
1281 if (
device == VK_NULL_HANDLE ||
command_pool == VK_NULL_HANDLE || vkTrimCommandPool ==
nullptr) {
1288 void VK_Window::maybeTrimMemory() {
1289 const auto now = std::chrono::steady_clock::now();
1299 std::cout << std::format(
"mxvk: entering initWindow (title='{}', width={}, height={})\n", title, width, height);
1300 if (width <= 0 || height <= 0) {
1301 std::cerr <<
"mxvk: Window dimensions must be positive\n";
1306 constexpr SDL_InitFlags sdlInitFlags = SDL_INIT_VIDEO | SDL_INIT_GAMEPAD;
1307 std::cout <<
"SDL3: initializing video/gamepad subsystems\n";
1308 if (!SDL_Init(sdlInitFlags)) {
1309 std::cerr <<
"mxvk: Error on SDL init: " << SDL_GetError() <<
"\n";
1313 SDL_SetGamepadEventsEnabled(
true);
1314 SDL_SetJoystickEventsEnabled(
true);
1315 std::cout <<
"SDL3: video/gamepad subsystems initialized\n";
1319 std::cout <<
"SDL3: loading the Vulkan loader library\n";
1320 if (!SDL_Vulkan_LoadLibrary(
nullptr)) {
1321 std::cerr << std::format(
"mxvk: SDL_Vulkan_LoadLibrary failed: {}\n", SDL_GetError());
1322 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
1329 const bool fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0;
1330 const SDL_DisplayMode *fullscreen_mode =
nullptr;
1332 const SDL_DisplayID display = SDL_GetPrimaryDisplay();
1333 fullscreen_mode = display != 0 ? SDL_GetCurrentDisplayMode(display) :
nullptr;
1334 if (fullscreen_mode !=
nullptr && fullscreen_mode->w > 0 && fullscreen_mode->h > 0) {
1335 width = fullscreen_mode->w;
1336 height = fullscreen_mode->h;
1337 std::cout << std::format(
"SDL3: using current display mode for fullscreen window: {}x{}\n", width, height);
1341 std::cout <<
"SDL3: creating SDL window with Vulkan capability\n";
1342 SDL_Window *raw_window = SDL_CreateWindow(title.c_str(), width, height, flags);
1343 if (raw_window ==
nullptr) {
1344 std::cerr << std::format(
"Error creating window: {}\n", SDL_GetError());
1345 SDL_Vulkan_UnloadLibrary();
1347 std::cout <<
"SDL3: rolling back SDL video/gamepad subsystems after window creation failure\n";
1348 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
1353 window.reset(raw_window);
1354 if (fullscreen && fullscreen_mode !=
nullptr && !SDL_SetWindowFullscreenMode(
window.get(), fullscreen_mode)) {
1355 std::cerr << std::format(
"mxvk: SDL_SetWindowFullscreenMode failed: {}\n", SDL_GetError());
1357 std::cout <<
"SDL3: showing created window\n";
1358 SDL_ShowWindow(
window.get());
1359 if (fullscreen && !SDL_SyncWindow(
window.get())) {
1360 std::cerr << std::format(
"mxvk: SDL_SyncWindow failed after fullscreen show: {}\n", SDL_GetError());
1363 for (
int attempt = 0; attempt < 50; ++attempt) {
1364 int pixel_width = 0;
1365 int pixel_height = 0;
1366 SDL_GetWindowSizeInPixels(
window.get(), &pixel_width, &pixel_height);
1367 if (pixel_width == width && pixel_height == height) {
1374 std::cout <<
"mxvk: initWindow complete\n";
1395 if (
device == VK_NULL_HANDLE || extent.width == 0U || extent.height == 0U) {
1402 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1407 if ((width == 0U) != (height == 0U)) {
1408 throw mxvk::Exception(
"render extent requires two positive dimensions or two zeros");
1414 if (width == 0U && height == 0U) {
1420 if (
device != VK_NULL_HANDLE) {
1421 vkDeviceWaitIdle(
device);
1424 cleanupSyncObjects();
1425 cleanupSwapchain(
false);
1431 if (
device != VK_NULL_HANDLE) {
1435 std::cout << std::format(
"mxvk: surface-free render extent {}x{}\n", width, height);
1439 vkDeviceWaitIdle(
device);
1441 destroyPostProcessTargets();
1446 createPostProcessTargets();
1448 std::cout << std::format(
"mxvk: fixed render extent {}\n", width > 0U ? std::format(
"{}x{}", width, height) :
"disabled");
1457 if (
device != VK_NULL_HANDLE) {
1458 vkDeviceWaitIdle(
device);
1459 destroyPostProcessTargets();
1464 createPostProcessTargets();
1466 std::cout <<
"mxvk: HDR render intermediates " << (enabled ?
"enabled (RGBA16F)\n" :
"disabled (RGBA8)\n");
1477 std::vector<VK_Sprite *> attachedSprites;
1478 attachedSprites.reserve(effects.size());
1479 for (
size_t effect_index = 0; effect_index < effects.size(); ++effect_index) {
1482 throw mxvk::Exception(
"Cannot attach a post-processing pass with an empty shader path");
1488 throw mxvk::Exception(
"Post-processing shaders must contain a fragment or compute entry point");
1491 throw mxvk::Exception(
"Post-processing shader stage does not match its SPIR-V entry point");
1508 throw mxvk::Exception(std::format(
"Compute shader '{}' must declare layout({}) for the "
1509 "active {} render path",
1516 const uint32_t black_pixel = 0xFF000000u;
1520 attachedSprites.push_back(sprite);
1531 const uint32_t black_pixel = 0xFF000000u;
1549 createPostProcessTargets();
1552 return attachedSprites;
1568 vkDeviceWaitIdle(
device);
1572 destroyPostProcessTargets();
1579 sprites.erase(std::remove_if(
sprites.begin(),
sprites.end(), [&sprites_to_remove](
const std::unique_ptr<VK_Sprite> &sprite) { return std::ranges::find(sprites_to_remove, sprite.get()) != sprites_to_remove.end(); }),
sprites.end());
1638 if (sprites_to_remove.empty()) {
1641 sprites.erase(std::remove_if(
sprites.begin(),
sprites.end(), [&sprites_to_remove](
const std::unique_ptr<VK_Sprite> &existing_sprite) { return std::ranges::find(sprites_to_remove, existing_sprite.get()) != sprites_to_remove.end(); }),
sprites.end());
1647 post_process_sprites = sprite ==
nullptr ? std::vector<VK_Sprite *>{} : std::vector<VK_Sprite *>{sprite};
1652 createPostProcessTargets();
1658 void VK_Window::destroyPostProcessTargets() {
1660 if (sprite !=
nullptr) {
1671 for (VkImageView view : views) {
1672 if (view != VK_NULL_HANDLE) {
1673 vkDestroyImageView(
device, view,
nullptr);
1678 for (VkImage image : images) {
1679 if (image != VK_NULL_HANDLE) {
1680 vkDestroyImage(
device, image,
nullptr);
1685 for (VkDeviceMemory memory : memories) {
1686 if (memory != VK_NULL_HANDLE) {
1687 vkFreeMemory(
device, memory,
nullptr);
1697 void VK_Window::createPostProcessTargets() {
1698 destroyPostProcessTargets();
1704 size_t target_count = 1U;
1712 VkFormatProperties format_properties{};
1714 VkFormatFeatureFlags required_features = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
1716 required_features |= VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
1718 if ((format_properties.optimalTilingFeatures & required_features) != required_features) {
1719 throw mxvk::Exception(
"The selected compute post-processing format is unsupported on this device");
1726 VkPhysicalDeviceMemoryProperties memory_properties{};
1727 vkGetPhysicalDeviceMemoryProperties(
physical_device, &memory_properties);
1729 for (
size_t target = 0; target < target_count; ++target) {
1731 VkImageCreateInfo image_info{};
1732 image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1733 image_info.imageType = VK_IMAGE_TYPE_2D;
1734 image_info.extent = {render_extent.width, render_extent.height, 1};
1735 image_info.mipLevels = 1;
1736 image_info.arrayLayers = 1;
1738 image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
1739 image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1740 image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
1741 if (target != 0U && has_compute) {
1742 image_info.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
1744 image_info.samples = VK_SAMPLE_COUNT_1_BIT;
1745 image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1747 throw mxvk::Exception(
"Failed to create post-process image");
1749 VkMemoryRequirements requirements{};
1751 uint32_t memory_type = UINT32_MAX;
1752 for (uint32_t type = 0; type < memory_properties.memoryTypeCount; ++type) {
1753 if ((requirements.memoryTypeBits & (1U << type)) != 0U && (memory_properties.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0U) {
1758 if (memory_type == UINT32_MAX) {
1759 throw mxvk::Exception(
"Failed to find post-process image memory type");
1761 VkMemoryAllocateInfo allocation{};
1762 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1763 allocation.allocationSize = requirements.size;
1764 allocation.memoryTypeIndex = memory_type;
1766 throw mxvk::Exception(
"Failed to allocate post-process image memory");
1768 VkImageViewCreateInfo view_info{};
1769 view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1771 view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
1773 view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1774 view_info.subresourceRange.levelCount = 1;
1775 view_info.subresourceRange.layerCount = 1;
1777 throw mxvk::Exception(
"Failed to create post-process image view");
1782 destroyPostProcessTargets();
1790 if (
device == VK_NULL_HANDLE) {
1801 bool VK_Window::renderTargetsReady() const noexcept {
1809 std::cout <<
"mxvk: entering pickDevice\n";
1811 std::cout <<
"vk: cannot pick device because required instance state is missing\n";
1815 uint32_t device_count = 0;
1816 std::cout <<
"vk: enumerating physical devices\n";
1817 vkEnumeratePhysicalDevices(
instance, &device_count,
nullptr);
1818 if (device_count == 0U) {
1819 std::cout <<
"vk: no physical devices found\n";
1822 std::cout << std::format(
"vk: found {} physical device(s)\n", device_count);
1824 std::vector<VkPhysicalDevice> devices(device_count);
1825 vkEnumeratePhysicalDevices(
instance, &device_count, devices.data());
1827 for (
const VkPhysicalDevice candidate : devices) {
1828 std::cout <<
"vk: evaluating candidate physical device\n";
1829 uint32_t queue_family_count = 0;
1830 vkGetPhysicalDeviceQueueFamilyProperties(candidate, &queue_family_count,
nullptr);
1831 std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
1832 vkGetPhysicalDeviceQueueFamilyProperties(candidate, &queue_family_count, queue_families.data());
1834 uint32_t candidate_graphics = invalid_queue_index;
1835 uint32_t candidate_present = invalid_queue_index;
1837 for (uint32_t i = 0; i < queue_family_count; ++i) {
1838 if ((queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0U) {
1839 candidate_graphics = i;
1843 VkBool32 present_support = VK_FALSE;
1844 vkGetPhysicalDeviceSurfaceSupportKHR(candidate, i,
surface, &present_support);
1845 if (present_support == VK_TRUE) {
1846 candidate_present = i;
1852 candidate_present = candidate_graphics;
1855 if (candidate_graphics == invalid_queue_index || candidate_present == invalid_queue_index) {
1856 std::cout <<
"vk: candidate rejected due to missing graphics/present queue support\n";
1861 const SwapchainSupport swapchain_support = querySwapchainSupport(candidate,
surface);
1862 if (swapchain_support.formats.empty() || swapchain_support.present_modes.empty()) {
1863 std::cout <<
"vk: candidate rejected due to incomplete swapchain support\n";
1868 VkPhysicalDeviceProperties properties{};
1869 vkGetPhysicalDeviceProperties(candidate, &properties);
1871 const char *device_type =
"unknown";
1872 switch (properties.deviceType) {
1873 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
1874 device_type =
"integrated";
1876 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
1877 device_type =
"discrete";
1879 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
1880 device_type =
"virtual";
1882 case VK_PHYSICAL_DEVICE_TYPE_CPU:
1883 device_type =
"cpu";
1892 std::cout << std::format(
"vk: selected GPU='{}' type={} vendor=0x{:04x} device=0x{:04x}\n", properties.deviceName, device_type, properties.vendorID, properties.deviceID);
1896 std::cout <<
"vk: no suitable physical device selected\n";
1899 std::cout <<
"mxvk: entering createLogicalDevice\n";
1901 std::cout <<
"vk: cannot create logical device without a selected physical device\n";
1905 std::vector<uint32_t> queue_families{};
1911 constexpr float queue_priority = 1.0f;
1912 std::vector<VkDeviceQueueCreateInfo> queue_create_infos{};
1913 queue_create_infos.reserve(queue_families.size());
1914 for (
const uint32_t family : queue_families) {
1915 VkDeviceQueueCreateInfo queue_create_info{};
1916 queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1917 queue_create_info.queueFamilyIndex = family;
1918 queue_create_info.queueCount = 1;
1919 queue_create_info.pQueuePriorities = &queue_priority;
1920 queue_create_infos.push_back(queue_create_info);
1922 uint32_t device_extension_count = 0;
1923 vkEnumerateDeviceExtensionProperties(
physical_device,
nullptr, &device_extension_count,
nullptr);
1924 std::vector<VkExtensionProperties> device_extensions(device_extension_count);
1925 if (device_extension_count > 0U) {
1926 vkEnumerateDeviceExtensionProperties(
physical_device,
nullptr, &device_extension_count, device_extensions.data());
1929 [[maybe_unused]]
const auto has_device_extension = [&device_extensions](
const char *extension_name) {
return std::ranges::any_of(device_extensions, [extension_name](
const VkExtensionProperties &ext) {
return std::strcmp(ext.extensionName, extension_name) == 0; }); };
1931 std::vector<const char *> required_device_extensions{};
1933 required_device_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
1935#if defined(MXVK_CUDA) && defined(__linux__)
1936 if (has_device_extension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME)) {
1937 if (has_device_extension(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME)) {
1938 required_device_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
1940 required_device_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
1941 std::cout <<
"vk: enabling external memory FD support for CUDA interop\n";
1943 std::cout <<
"vk: external memory FD support unavailable; CUDA texture interop will fall back\n";
1947#if defined(MXVK_USE_MOLTENVK)
1948 const bool has_portability_subset = std::ranges::any_of(device_extensions, [](
const VkExtensionProperties &ext) {
return std::strcmp(ext.extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0; });
1950 if (has_portability_subset) {
1951 required_device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
1954 VkPhysicalDeviceFeatures2 features2{};
1955 features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
1956 VkPhysicalDeviceVulkan13Features supported_vulkan13_features{};
1957 supported_vulkan13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
1958 features2.pNext = &supported_vulkan13_features;
1961 std::cout << std::format(
"vk: feature support - synchronization2={}, dynamicRendering={}, shaderFloat64={}, fillModeNonSolid={}, samplerAnisotropy={}\n", supported_vulkan13_features.synchronization2 == VK_TRUE ?
"true" :
"false", supported_vulkan13_features.dynamicRendering == VK_TRUE ?
"true" :
"false", features2.features.shaderFloat64 == VK_TRUE ?
"true" :
"false", features2.features.fillModeNonSolid == VK_TRUE ?
"true" :
"false", features2.features.samplerAnisotropy == VK_TRUE ?
"true" :
"false");
1963 if (supported_vulkan13_features.synchronization2 != VK_TRUE) {
1964 std::cout <<
"vk: synchronization2 is unsupported on selected physical device\n";
1967 if (supported_vulkan13_features.dynamicRendering != VK_TRUE) {
1968 std::cout <<
"vk: dynamic rendering is unsupported on selected physical device\n";
1971 VkPhysicalDeviceVulkan13Features vulkan13_features{};
1972 vulkan13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
1973 vulkan13_features.synchronization2 = VK_TRUE;
1974 vulkan13_features.dynamicRendering = VK_TRUE;
1976 VkPhysicalDeviceFeatures2 enabled_features2{};
1977 enabled_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
1978 enabled_features2.features.shaderFloat64 = features2.features.shaderFloat64;
1979 enabled_features2.features.fillModeNonSolid = features2.features.fillModeNonSolid;
1980 enabled_features2.features.samplerAnisotropy = features2.features.samplerAnisotropy;
1982 enabled_features2.pNext = &vulkan13_features;
1984 VkDeviceCreateInfo create_info{};
1985 create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1986 create_info.pNext = &enabled_features2;
1987 create_info.queueCreateInfoCount =
static_cast<uint32_t
>(queue_create_infos.size());
1988 create_info.pQueueCreateInfos = queue_create_infos.data();
1989 create_info.enabledExtensionCount =
static_cast<uint32_t
>(required_device_extensions.size());
1990 create_info.ppEnabledExtensionNames = required_device_extensions.data();
1991 create_info.enabledLayerCount = 0;
1992 create_info.ppEnabledLayerNames =
nullptr;
1993 create_info.pEnabledFeatures =
nullptr;
1995 std::cout <<
"vk: creating logical device\n";
1996 const VkResult create_device_result = vkCreateDevice(
physical_device, &create_info,
nullptr, &
device);
1997 if (create_device_result != VK_SUCCESS) {
1998 std::cerr << std::format(
"vk: logical device creation failed (VkResult={})",
static_cast<int>(create_device_result));
1999 if (!required_device_extensions.empty()) {
2000 std::cerr <<
"; requested extensions:";
2001 for (
const char *extension_name : required_device_extensions) {
2002 std::cerr <<
' ' << extension_name;
2010 std::cout <<
"vk: loading device-level function pointers via volk\n";
2012 if (vkQueueSubmit2 ==
nullptr || vkCmdBeginRendering ==
nullptr || vkCmdEndRendering ==
nullptr || vkCmdPipelineBarrier2 ==
nullptr) {
2013 std::cout <<
"vk: required dynamic rendering/synchronization function pointers are unavailable\n";
2014 vkDestroyDevice(
device,
nullptr);
2018 std::cout << (
headless() ?
"vk: retrieving graphics queue\n" :
"vk: retrieving graphics and present queues\n");
2025 std::cout <<
"mxvk: createLogicalDevice complete\n";
2029 std::cout <<
"mxvk: entering createDevice\n";
2030 if (
device == VK_NULL_HANDLE) {
2031 std::cout <<
"vk: skipping createDevice because logical device is null\n";
2038 std::cout <<
"vk: createDevice skipped because render resources are already initialized\n";
2043 std::cout <<
"vk: creating surface-free color targets\n";
2044 if (!createHeadlessTargets()) {
2045 std::cout <<
"vk: createHeadlessTargets failed\n";
2049 std::cout <<
"vk: creating swapchain\n";
2050 if (!createSwapchain(VK_NULL_HANDLE)) {
2051 std::cout <<
"vk: createSwapchain failed\n";
2056 std::cout <<
"vk: creating render resources\n";
2057 if (!createRenderResources()) {
2058 std::cout <<
"vk: createRenderResources failed\n";
2062 std::cout <<
"vk: creating synchronization objects\n";
2063 if (!createSyncObjects()) {
2064 std::cout <<
"vk: createSyncObjects failed\n";
2068 std::cout <<
"mxvk: createDevice complete\n";
2071 bool VK_Window::createSwapchain(VkSwapchainKHR old_swapchain) {
2072 std::cout <<
"vk: entering createSwapchain\n";
2074 if (support.formats.empty() || support.present_modes.empty()) {
2075 std::cout <<
"vk: cannot create swapchain because support is incomplete\n";
2079 const VkSurfaceFormatKHR surface_format = chooseSurfaceFormat(support.formats);
2080 const VkPresentModeKHR present_mode = choosePresentMode(support.present_modes);
2081 auto clampedWindowPixelExtent = [
this, &support]() -> std::optional<VkExtent2D> {
2083 return std::nullopt;
2087 SDL_GetWindowSizeInPixels(
window.get(), &pixel_w, &pixel_h);
2088 if (pixel_w <= 0 || pixel_h <= 0) {
2089 return std::nullopt;
2092 .width = std::clamp(
static_cast<uint32_t
>(pixel_w), support.capabilities.minImageExtent.width, support.capabilities.maxImageExtent.width),
2093 .height = std::clamp(
static_cast<uint32_t
>(pixel_h), support.capabilities.minImageExtent.height, support.capabilities.maxImageExtent.height),
2096 auto extentMatchesWindowPixels = [&clampedWindowPixelExtent](
const VkExtent2D extent) {
2097 const std::optional<VkExtent2D> target_extent = clampedWindowPixelExtent();
2098 return !target_extent.has_value() || (extent.width == target_extent->width && extent.height == target_extent->height);
2101 VkExtent2D extent = chooseExtent(support.capabilities,
window.get());
2102 for (
int attempt = 0; attempt < 50 && !extentMatchesWindowPixels(extent); ++attempt) {
2106 extent = chooseExtent(support.capabilities,
window.get());
2108 if (!extentMatchesWindowPixels(extent)) {
2109 const std::optional<VkExtent2D> target_extent = clampedWindowPixelExtent();
2110 if (target_extent.has_value()) {
2111 std::cout << std::format(
"vk: delaying swapchain creation until surface extent catches up to window pixels (surface={}x{}, window={}x{})\n", extent.width, extent.height, target_extent->width, target_extent->height);
2116 uint32_t image_count = support.capabilities.minImageCount + 1;
2117 if (support.capabilities.maxImageCount > 0U && image_count > support.capabilities.maxImageCount) {
2118 image_count = support.capabilities.maxImageCount;
2121 VkSwapchainCreateInfoKHR create_info{};
2122 create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
2123 create_info.surface =
surface;
2124 create_info.minImageCount = image_count;
2125 create_info.imageFormat = surface_format.format;
2126 create_info.imageColorSpace = surface_format.colorSpace;
2127 create_info.imageExtent = extent;
2128 create_info.imageArrayLayers = 1;
2129 create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
2132 create_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
2134 std::cerr <<
"mxvk: swapchain does not support transfer-source usage; saveSnapshot is unavailable\n";
2139 create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
2140 create_info.queueFamilyIndexCount = 2;
2141 create_info.pQueueFamilyIndices = queue_family_indices;
2143 create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
2144 create_info.queueFamilyIndexCount = 0;
2145 create_info.pQueueFamilyIndices =
nullptr;
2148 create_info.preTransform = support.capabilities.currentTransform;
2149 create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
2150 create_info.presentMode = present_mode;
2151 create_info.clipped = VK_TRUE;
2152 create_info.oldSwapchain = old_swapchain;
2154 std::cout <<
"vk: creating swapchain object\n";
2155 if (vkCreateSwapchainKHR(
device, &create_info,
nullptr, &
swapchain) != VK_SUCCESS) {
2160 std::cout <<
"vk: querying swapchain images\n";
2172 VkImageViewCreateInfo view_info{};
2173 view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2175 view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
2177 view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
2178 view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
2179 view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
2180 view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
2181 view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2182 view_info.subresourceRange.baseMipLevel = 0;
2183 view_info.subresourceRange.levelCount = 1;
2184 view_info.subresourceRange.baseArrayLayer = 0;
2185 view_info.subresourceRange.layerCount = 1;
2187 std::cout << std::format(
"vk: creating image view for swapchain image {}\n", i);
2190 if (image_view != VK_NULL_HANDLE) {
2191 vkDestroyImageView(
device, image_view,
nullptr);
2207 std::cout <<
"vk: createSwapchain complete\n";
2211 bool VK_Window::createHeadlessTargets() {
2215 if (renderTargetsReady()) {
2222 constexpr size_t HEADLESS_TARGET_COUNT = max_frames_in_flight;
2228 VkPhysicalDeviceMemoryProperties memory_properties{};
2229 vkGetPhysicalDeviceMemoryProperties(
physical_device, &memory_properties);
2231 for (
size_t index = 0; index < HEADLESS_TARGET_COUNT; ++index) {
2232 VkImageCreateInfo image_info{};
2233 image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2234 image_info.imageType = VK_IMAGE_TYPE_2D;
2236 image_info.mipLevels = 1;
2237 image_info.arrayLayers = 1;
2239 image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
2240 image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2241 image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
2242 image_info.samples = VK_SAMPLE_COUNT_1_BIT;
2243 image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2245 throw mxvk::Exception(
"Failed to create a headless color image");
2248 VkMemoryRequirements requirements{};
2250 uint32_t memory_type = invalid_queue_index;
2251 for (uint32_t type = 0; type < memory_properties.memoryTypeCount; ++type) {
2252 const bool type_matches = (requirements.memoryTypeBits & (1U << type)) != 0U;
2253 const bool device_local = (memory_properties.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0U;
2254 if (type_matches && device_local) {
2259 if (memory_type == invalid_queue_index) {
2260 throw mxvk::Exception(
"Failed to find headless image memory");
2263 VkMemoryAllocateInfo allocation_info{};
2264 allocation_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2265 allocation_info.allocationSize = requirements.size;
2266 allocation_info.memoryTypeIndex = memory_type;
2268 throw mxvk::Exception(
"Failed to allocate headless image memory");
2271 throw mxvk::Exception(
"Failed to bind headless image memory");
2274 VkImageViewCreateInfo view_info{};
2275 view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2277 view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
2279 view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2280 view_info.subresourceRange.levelCount = 1;
2281 view_info.subresourceRange.layerCount = 1;
2283 throw mxvk::Exception(
"Failed to create a headless image view");
2286 }
catch (
const mxvk::Exception &ex) {
2287 std::cerr << std::format(
"mxvk: {}\n", ex.
text());
2288 cleanupSwapchain(
false);
2294 std::cout << std::format(
"vk: created {} surface-free color targets ({}x{}, RGBA8)\n", HEADLESS_TARGET_COUNT,
swapchain_extent.width,
swapchain_extent.height);
2298 bool VK_Window::createRenderResources() {
2299 std::cout <<
"vk: entering createRenderResources\n";
2300 const auto cleanup_render_resource_failure = [
this]() {
2307 if (view != VK_NULL_HANDLE) {
2308 vkDestroyImageView(
device, view,
nullptr);
2309 view = VK_NULL_HANDLE;
2315 if (image != VK_NULL_HANDLE) {
2316 vkDestroyImage(
device, image,
nullptr);
2317 image = VK_NULL_HANDLE;
2323 if (memory != VK_NULL_HANDLE) {
2324 vkFreeMemory(
device, memory,
nullptr);
2325 memory = VK_NULL_HANDLE;
2334 destroyPostProcessTargets();
2338 VkCommandPoolCreateInfo pool_info{};
2339 pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
2340 pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
2343 std::cout <<
"vk: creating command pool\n";
2350 std::cout <<
"vk: freeing stale command buffers before reallocation\n";
2356 VkCommandBufferAllocateInfo alloc_info{};
2357 alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
2359 alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
2360 alloc_info.commandBufferCount =
static_cast<uint32_t
>(
command_buffers.size());
2362 std::cout <<
"vk: allocating command buffers\n";
2368 auto findDepthFormat = [&](VkPhysicalDevice gpu) -> VkFormat {
2369 const std::array<VkFormat, 2> candidates = {
2370 VK_FORMAT_D32_SFLOAT,
2371 VK_FORMAT_D16_UNORM,
2374 for (
const VkFormat format : candidates) {
2375 VkFormatProperties props{};
2376 vkGetPhysicalDeviceFormatProperties(gpu, format, &props);
2377 if ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0U) {
2382 return VK_FORMAT_UNDEFINED;
2387 std::cerr <<
"mxvk: failed to find a supported depth format\n";
2388 cleanup_render_resource_failure();
2392 depth_images.resize(max_frames_in_flight, VK_NULL_HANDLE);
2399 std::cout << std::format(
"vk: creating depth image for frame {}\n", i);
2400 VkImageCreateInfo imageInfo{};
2401 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2402 imageInfo.imageType = VK_IMAGE_TYPE_2D;
2403 imageInfo.extent.width = render_extent.width;
2404 imageInfo.extent.height = render_extent.height;
2405 imageInfo.extent.depth = 1;
2406 imageInfo.mipLevels = 1;
2407 imageInfo.arrayLayers = 1;
2409 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
2410 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2411 imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
2412 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
2413 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2416 std::cerr << std::format(
"mxvk: failed to create depth image {}\n", i);
2417 cleanup_render_resource_failure();
2421 VkMemoryRequirements memReq{};
2424 VkPhysicalDeviceMemoryProperties memProps{};
2426 uint32_t memoryTypeIndex = UINT32_MAX;
2427 for (uint32_t t = 0; t < memProps.memoryTypeCount; ++t) {
2428 if (((memReq.memoryTypeBits & (1u << t)) != 0U) && ((memProps.memoryTypes[t].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0U)) {
2429 memoryTypeIndex = t;
2433 if (memoryTypeIndex == UINT32_MAX) {
2434 std::cerr <<
"mxvk: failed to find depth image memory type\n";
2435 cleanup_render_resource_failure();
2439 std::cout << std::format(
"vk: allocating depth image memory for frame {}\n", i);
2440 VkMemoryAllocateInfo allocInfo{};
2441 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
2442 allocInfo.allocationSize = memReq.size;
2443 allocInfo.memoryTypeIndex = memoryTypeIndex;
2446 std::cerr << std::format(
"mxvk: failed to allocate depth image memory {}\n", i);
2447 cleanup_render_resource_failure();
2452 std::cerr << std::format(
"mxvk: failed to bind depth image memory {}\n", i);
2453 cleanup_render_resource_failure();
2457 std::cout << std::format(
"vk: creating depth image view for frame {}\n", i);
2458 VkImageViewCreateInfo viewInfo{};
2459 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2461 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
2463 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
2464 viewInfo.subresourceRange.baseMipLevel = 0;
2465 viewInfo.subresourceRange.levelCount = 1;
2466 viewInfo.subresourceRange.baseArrayLayer = 0;
2467 viewInfo.subresourceRange.layerCount = 1;
2470 std::cerr << std::format(
"mxvk: failed to create depth image view {}\n", i);
2471 cleanup_render_resource_failure();
2478 createPostProcessTargets();
2479 }
catch (
const mxvk::Exception &ex) {
2480 std::cerr << std::format(
"mxvk: {}\n", ex.
text());
2481 cleanup_render_resource_failure();
2486 std::cout <<
"vk: createRenderResources complete\n";
2490 bool VK_Window::createSyncObjects() {
2491 std::cout <<
"vk: entering createSyncObjects\n";
2493 std::cerr <<
"mxvk: cannot create sync objects without swapchain images\n";
2497 cleanupSyncObjects();
2499 VkSemaphoreCreateInfo semaphore_info{};
2500 semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
2502 VkFenceCreateInfo fence_info{};
2503 fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
2504 fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
2508 for (uint32_t frame = 0; frame < max_frames_in_flight; ++frame) {
2509 std::cout << std::format(
"vk: creating image-available semaphore for frame {}\n", frame);
2511 cleanupSyncObjects();
2515 std::cout << std::format(
"vk: creating in-flight fence for frame {}\n", frame);
2517 cleanupSyncObjects();
2522 for (
size_t image_index = 0; image_index <
render_finished.size(); ++image_index) {
2523 std::cout << std::format(
"vk: creating render-finished semaphore for swapchain image {}\n", image_index);
2524 if (vkCreateSemaphore(
device, &semaphore_info,
nullptr, &
render_finished[image_index]) != VK_SUCCESS) {
2525 cleanupSyncObjects();
2533 std::cout <<
"vk: createSyncObjects complete\n";
2537 void VK_Window::cleanupSyncObjects() {
2538 if (
device == VK_NULL_HANDLE) {
2542 const bool has_in_flight_fences = std::ranges::any_of(
in_flight_fences.begin(),
in_flight_fences.end(), [](VkFence fence) { return fence != VK_NULL_HANDLE; });
2543 const bool has_render_finished = std::ranges::any_of(
render_finished.begin(),
render_finished.end(), [](VkSemaphore semaphore) { return semaphore != VK_NULL_HANDLE; });
2544 const bool has_image_available = std::ranges::any_of(
image_available.begin(),
image_available.end(), [](VkSemaphore semaphore) { return semaphore != VK_NULL_HANDLE; });
2546 if (!has_in_flight_fences && !has_render_finished && !has_image_available) {
2552 if (has_in_flight_fences) {
2553 std::cout <<
"vk: destroying in-flight fences\n";
2555 if (fence != VK_NULL_HANDLE) {
2556 vkDestroyFence(
device, fence,
nullptr);
2557 fence = VK_NULL_HANDLE;
2562 if (has_render_finished) {
2563 std::cout <<
"vk: destroying render-finished semaphores\n";
2565 if (semaphore != VK_NULL_HANDLE) {
2566 vkDestroySemaphore(
device, semaphore,
nullptr);
2567 semaphore = VK_NULL_HANDLE;
2572 if (has_image_available) {
2573 std::cout <<
"vk: destroying image-available semaphores\n";
2575 if (semaphore != VK_NULL_HANDLE) {
2576 vkDestroySemaphore(
device, semaphore,
nullptr);
2577 semaphore = VK_NULL_HANDLE;
2587 void VK_Window::recreateSwapchain() {
2593 SDL_GetWindowSizeInPixels(
window.get(), &pixel_w, &pixel_h);
2594 VkSurfaceCapabilitiesKHR surface_capabilities{};
2597 VkExtent2D new_extent{};
2599 if (surface_capabilities.currentExtent.width != 0xFFFFFFFFU) {
2600 new_extent = surface_capabilities.currentExtent;
2602 new_extent.width = std::clamp(
static_cast<uint32_t
>(pixel_w), surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
2604 new_extent.height = std::clamp(
static_cast<uint32_t
>(pixel_h), surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
2607 if (new_extent.width == 0 || new_extent.height == 0) {
2611 const uint32_t target_w = std::clamp(
static_cast<uint32_t
>(pixel_w), surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
2612 const uint32_t target_h = std::clamp(
static_cast<uint32_t
>(pixel_h), surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
2614 if (new_extent.width != target_w || new_extent.height != target_h) {
2626 std::cout << std::format(
"mxvk: recreating swapchain for {}x{} window\n", new_extent.width, new_extent.height);
2627 vkDeviceWaitIdle(
device);
2630 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2638 destroySpritePipeline();
2639 destroyTextPipeline();
2640 cleanupSyncObjects();
2641 VkSwapchainKHR old_swapchain =
swapchain;
2642 cleanupSwapchain(
false);
2643 if (!createSwapchain(old_swapchain) || !createRenderResources() || !createSyncObjects()) {
2644 std::cerr <<
"mxvk: failed to recreate swapchain after resize\n";
2645 if (old_swapchain != VK_NULL_HANDLE) {
2651 if (old_swapchain != VK_NULL_HANDLE) {
2656 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2661 for (
const std::unique_ptr<VK_Sprite3D> &sprite :
sprites3d) {
2663 sprite->resize(
this);
2670 std::cout <<
"mxvk: swapchain recreation complete\n";
2673 void VK_Window::cleanupSwapchain(
bool destroy_swapchain_handle) {
2674 std::cout <<
"vk: entering cleanupSwapchain\n";
2675 if (
device == VK_NULL_HANDLE) {
2679 destroyPostProcessTargets();
2682 std::cout <<
"vk: destroying swapchain image views\n";
2684 if (image_view != VK_NULL_HANDLE) {
2685 vkDestroyImageView(
device, image_view,
nullptr);
2692 std::cout <<
"vk: destroying surface-free color images\n";
2694 if (image != VK_NULL_HANDLE) {
2695 vkDestroyImage(
device, image,
nullptr);
2698 std::cout <<
"vk: freeing surface-free color image memory\n";
2700 if (memory != VK_NULL_HANDLE) {
2701 vkFreeMemory(
device, memory,
nullptr);
2708 std::cout <<
"vk: destroying depth image views\n";
2710 if (view != VK_NULL_HANDLE) {
2711 vkDestroyImageView(
device, view,
nullptr);
2718 std::cout <<
"vk: destroying depth images\n";
2720 if (image != VK_NULL_HANDLE) {
2721 vkDestroyImage(
device, image,
nullptr);
2728 std::cout <<
"vk: freeing depth image memory\n";
2730 if (memory != VK_NULL_HANDLE) {
2731 vkFreeMemory(
device, memory,
nullptr);
2746 if (destroy_swapchain_handle &&
swapchain != VK_NULL_HANDLE) {
2747 std::cout <<
"vk: destroying swapchain\n";
2751 std::cout <<
"vk: cleanupSwapchain complete\n";
2754 void VK_Window::drawFrame() {
2755 if (
device == VK_NULL_HANDLE) {
2763 SDL_GetWindowSizeInPixels(
window.get(), &pixel_w, &pixel_h);
2765 if (pixel_w <= 0 || pixel_h <= 0) {
2773 std::cout << std::format(
"mxvk: requesting swapchain recreation because window pixels changed from {}x{} to {}x{}\n",
swapchain_extent.width,
swapchain_extent.height, pixel_w, pixel_h);
2781 std::cout <<
"mxvk: creating deferred swapchain/render/sync resources\n";
2783 std::cerr <<
"mxvk: deferred resource creation failed; skipping frame\n";
2794 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2801 const size_t effect_index =
static_cast<size_t>(std::distance(
post_process_sprites.begin(), post_process_match));
2815 createSpritePipeline();
2816 }
catch (
const std::exception &ex) {
2817 std::cerr << std::format(
"mxvk: sprite pipeline build skipped: {}\n", ex.what());
2834 createTextPipeline();
2835 }
catch (
const std::exception &ex) {
2836 std::cerr << std::format(
"mxvk: text pipeline build skipped: {}\n", ex.what());
2843 const size_t depth_slot =
static_cast<size_t>(
current_frame);
2845 const VkResult wait_result = vkWaitForFences(
device, 1, &frame_fence, VK_TRUE, UINT64_MAX);
2846 if (wait_result == VK_ERROR_DEVICE_LOST) {
2847 std::cerr <<
"mxvk: device lost while waiting for frame fence; stopping render loop\n";
2851 if (wait_result != VK_SUCCESS) {
2852 std::cerr << std::format(
"mxvk: Failed waiting for frame fence (VkResult={})\n",
static_cast<int>(wait_result));
2863 uint32_t image_index = 0;
2864 VkResult acquire_result = VK_SUCCESS;
2869 const uint64_t acquire_timeout_ns = 100000000ULL;
2870 acquire_result = vkAcquireNextImageKHR(
device,
swapchain, acquire_timeout_ns, acquire_semaphore, VK_NULL_HANDLE, &image_index);
2873 static VkResult last_acquire_error = VK_SUCCESS;
2874 static uint32_t repeated_acquire_errors = 0;
2876 if (acquire_result == VK_ERROR_OUT_OF_DATE_KHR) {
2877 last_acquire_error = VK_SUCCESS;
2878 repeated_acquire_errors = 0;
2879 std::cout <<
"mxvk: requesting swapchain recreation because acquire returned VK_ERROR_OUT_OF_DATE_KHR\n";
2885 if (acquire_result == VK_ERROR_SURFACE_LOST_KHR) {
2886 if (last_acquire_error != acquire_result) {
2887 std::cerr <<
"mxvk: swapchain surface lost during acquire; requesting swapchain recreation\n";
2888 last_acquire_error = acquire_result;
2889 repeated_acquire_errors = 0;
2891 std::cout <<
"mxvk: requesting swapchain recreation because acquire returned VK_ERROR_SURFACE_LOST_KHR\n";
2897 if (acquire_result == VK_TIMEOUT || acquire_result == VK_NOT_READY) {
2902 if (acquire_result != VK_SUCCESS && acquire_result != VK_SUBOPTIMAL_KHR) {
2903 if (last_acquire_error == acquire_result) {
2904 ++repeated_acquire_errors;
2905 if ((repeated_acquire_errors % 120U) == 0U) {
2906 std::cerr << std::format(
"mxvk: repeated swapchain acquire failures continue (VkResult={})\n",
static_cast<int>(acquire_result));
2909 last_acquire_error = acquire_result;
2910 repeated_acquire_errors = 0;
2911 std::cerr << std::format(
"mxvk: Failed to acquire swapchain image (VkResult={})\n",
static_cast<int>(acquire_result));
2914 if (acquire_result == VK_ERROR_DEVICE_LOST) {
2915 std::cerr <<
"mxvk: device lost; stopping render loop\n";
2921 last_acquire_error = VK_SUCCESS;
2922 repeated_acquire_errors = 0;
2929 std::cerr <<
"mxvk: acquired image index exceeds tracked in-flight image count\n";
2934 std::cerr <<
"mxvk: acquired image index exceeds render-finished semaphore count\n";
2941 const VkResult image_wait_result = vkWaitForFences(
device, 1, &
image_fences[image_index], VK_TRUE, UINT64_MAX);
2942 if (image_wait_result == VK_ERROR_DEVICE_LOST) {
2943 std::cerr <<
"mxvk: device lost while waiting on acquired image fence; stopping render loop\n";
2947 if (image_wait_result != VK_SUCCESS) {
2948 std::cerr << std::format(
"mxvk: Failed waiting for acquired image fence (VkResult={})\n",
static_cast<int>(image_wait_result));
2955 vkResetCommandBuffer(cmd, 0);
2957 VkCommandBufferBeginInfo begin_info{};
2958 begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2959 if (vkBeginCommandBuffer(cmd, &begin_info) != VK_SUCCESS) {
2960 std::cerr <<
"mxvk: Failed to begin command buffer\n";
2965 VkClearValue clear_value{};
2970 const bool use_offscreen_target = use_post_process || detached_render;
2973 VkImage rgba16_readback_image = VK_NULL_HANDLE;
2975 VkImageMemoryBarrier2 to_color_barrier{};
2976 to_color_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2977 to_color_barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
2978 to_color_barrier.srcAccessMask = VK_ACCESS_2_NONE;
2979 to_color_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2980 to_color_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
2981 to_color_barrier.oldLayout =
swapchain_image_initialized[image_index] ? (
headless() ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) : VK_IMAGE_LAYOUT_UNDEFINED;
2982 to_color_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2983 to_color_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2984 to_color_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2986 to_color_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2987 to_color_barrier.subresourceRange.baseMipLevel = 0;
2988 to_color_barrier.subresourceRange.levelCount = 1;
2989 to_color_barrier.subresourceRange.baseArrayLayer = 0;
2990 to_color_barrier.subresourceRange.layerCount = 1;
2992 VkDependencyInfo pre_render_dependency{};
2993 pre_render_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2994 pre_render_dependency.imageMemoryBarrierCount = 1;
2995 pre_render_dependency.pImageMemoryBarriers = &to_color_barrier;
2996 vkCmdPipelineBarrier2(cmd, &pre_render_dependency);
2998 if (use_offscreen_target) {
2999 VkImageMemoryBarrier2 post_target_barrier{};
3000 post_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3001 post_target_barrier.srcStageMask =
post_process_initialized[scene_target][image_index] ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE;
3002 post_target_barrier.srcAccessMask =
post_process_initialized[scene_target][image_index] ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT : VK_ACCESS_2_NONE;
3003 post_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3004 post_target_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3005 post_target_barrier.oldLayout =
post_process_initialized[scene_target][image_index] ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
3006 post_target_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3007 post_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3008 post_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3010 post_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3011 post_target_barrier.subresourceRange.levelCount = 1;
3012 post_target_barrier.subresourceRange.layerCount = 1;
3013 VkDependencyInfo post_target_dependency{};
3014 post_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3015 post_target_dependency.imageMemoryBarrierCount = 1;
3016 post_target_dependency.pImageMemoryBarriers = &post_target_barrier;
3017 vkCmdPipelineBarrier2(cmd, &post_target_dependency);
3020 VkImageMemoryBarrier2 to_depth_barrier{};
3021 to_depth_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3022 to_depth_barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
3023 to_depth_barrier.srcAccessMask = VK_ACCESS_2_NONE;
3024 to_depth_barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT;
3025 to_depth_barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
3027 to_depth_barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
3028 to_depth_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3029 to_depth_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3033 to_depth_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
3034 to_depth_barrier.subresourceRange.baseMipLevel = 0;
3035 to_depth_barrier.subresourceRange.levelCount = 1;
3036 to_depth_barrier.subresourceRange.baseArrayLayer = 0;
3037 to_depth_barrier.subresourceRange.layerCount = 1;
3039 if (to_depth_barrier.image != VK_NULL_HANDLE) {
3040 VkDependencyInfo pre_depth_dependency{};
3041 pre_depth_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3042 pre_depth_dependency.imageMemoryBarrierCount = 1;
3043 pre_depth_dependency.pImageMemoryBarriers = &to_depth_barrier;
3044 vkCmdPipelineBarrier2(cmd, &pre_depth_dependency);
3048 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
3054 VkRenderingAttachmentInfo color_attachment{};
3055 color_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3057 color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3058 color_attachment.resolveMode = VK_RESOLVE_MODE_NONE;
3059 color_attachment.resolveImageView = VK_NULL_HANDLE;
3060 color_attachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
3061 color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
3062 color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3063 color_attachment.clearValue = clear_value;
3065 VkClearValue depth_clear_value{};
3066 depth_clear_value.depthStencil = {1.0f, 0};
3068 VkRenderingAttachmentInfo depth_attachment{};
3069 depth_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3073 depth_attachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
3074 depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
3075 depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
3076 depth_attachment.clearValue = depth_clear_value;
3078 VkRenderingAttachmentInfo stencil_attachment{};
3079 stencil_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3082 VkRenderingInfo rendering_info{};
3083 rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3084 rendering_info.renderArea.offset = {0, 0};
3085 rendering_info.renderArea.extent = use_offscreen_target ? render_extent :
swapchain_extent;
3086 rendering_info.layerCount = 1;
3087 rendering_info.viewMask = 0;
3088 rendering_info.colorAttachmentCount = 1;
3089 rendering_info.pColorAttachments = &color_attachment;
3090 rendering_info.pDepthAttachment = (depth_attachment.imageView != VK_NULL_HANDLE) ? &depth_attachment : nullptr;
3091 rendering_info.pStencilAttachment = (stencil_attachment.imageView != VK_NULL_HANDLE) ? &stencil_attachment : nullptr;
3093 vkCmdBeginRendering(cmd, &rendering_info);
3095 VkViewport viewport{};
3098 viewport.width =
static_cast<float>(rendering_info.renderArea.extent.width);
3099 viewport.height =
static_cast<float>(rendering_info.renderArea.extent.height);
3100 viewport.minDepth = 0.0F;
3101 viewport.maxDepth = 1.0F;
3102 vkCmdSetViewport(cmd, 0, 1, &viewport);
3105 scissor.offset = {0, 0};
3106 scissor.extent = rendering_info.renderArea.extent;
3107 vkCmdSetScissor(cmd, 0, 1, &scissor);
3113 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
3114 if (!sprite || isPostProcessSprite(sprite.get())) {
3118 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
3125 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
text_pipeline);
3129 vkCmdEndRendering(cmd);
3134 if (use_post_process) {
3135 VkImageMemoryBarrier2 post_target_barrier{};
3136 post_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3137 post_target_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3138 post_target_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3139 post_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3140 post_target_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
3141 post_target_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3142 post_target_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3143 post_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3144 post_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3146 post_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3147 post_target_barrier.subresourceRange.levelCount = 1;
3148 post_target_barrier.subresourceRange.layerCount = 1;
3149 VkDependencyInfo post_target_dependency{};
3150 post_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3151 post_target_dependency.imageMemoryBarrierCount = 1;
3152 post_target_dependency.pImageMemoryBarriers = &post_target_barrier;
3153 vkCmdPipelineBarrier2(cmd, &post_target_dependency);
3156 size_t source_target = scene_target;
3159 if (effect_sprite ==
nullptr) {
3168 effect_sprite->setShaderParams(params[0], params[1], params[2], params[3]);
3175 if (compute_effect) {
3176 VkImageMemoryBarrier2 storage_target_barrier{};
3177 storage_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3178 storage_target_barrier.srcStageMask =
post_process_initialized[destination_target][image_index] ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE;
3179 storage_target_barrier.srcAccessMask =
post_process_initialized[destination_target][image_index] ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT : VK_ACCESS_2_NONE;
3180 storage_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3181 storage_target_barrier.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
3182 storage_target_barrier.oldLayout =
post_process_initialized[destination_target][image_index] ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
3183 storage_target_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
3184 storage_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3185 storage_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3187 storage_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3188 storage_target_barrier.subresourceRange.levelCount = 1;
3189 storage_target_barrier.subresourceRange.layerCount = 1;
3190 VkDependencyInfo storage_target_dependency{};
3191 storage_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3192 storage_target_dependency.imageMemoryBarrierCount = 1;
3193 storage_target_dependency.pImageMemoryBarriers = &storage_target_barrier;
3194 vkCmdPipelineBarrier2(cmd, &storage_target_dependency);
3196 effect_sprite->dispatchCompute(cmd,
post_process_views[source_target][image_index],
post_process_views[destination_target][image_index], render_extent.width, render_extent.height);
3198 VkImageMemoryBarrier2 sampled_target_barrier{};
3199 sampled_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3200 sampled_target_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3201 sampled_target_barrier.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT;
3202 sampled_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3203 sampled_target_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
3204 sampled_target_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL;
3205 sampled_target_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3206 sampled_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3207 sampled_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3209 sampled_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3210 sampled_target_barrier.subresourceRange.levelCount = 1;
3211 sampled_target_barrier.subresourceRange.layerCount = 1;
3212 VkDependencyInfo sampled_target_dependency{};
3213 sampled_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3214 sampled_target_dependency.imageMemoryBarrierCount = 1;
3215 sampled_target_dependency.pImageMemoryBarriers = &sampled_target_barrier;
3216 vkCmdPipelineBarrier2(cmd, &sampled_target_dependency);
3218 source_target = destination_target;
3221 VkRenderingAttachmentInfo post_attachment{};
3222 post_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3224 post_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3225 post_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3226 post_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3227 VkRenderingInfo post_info{};
3228 post_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3230 post_info.layerCount = 1;
3231 post_info.colorAttachmentCount = 1;
3232 post_info.pColorAttachments = &post_attachment;
3233 vkCmdBeginRendering(cmd, &post_info);
3237 vkCmdEndRendering(cmd);
3244 VkImageMemoryBarrier2 next_target_barrier{};
3245 next_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3246 next_target_barrier.srcStageMask =
post_process_initialized[destination_target][image_index] ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE;
3247 next_target_barrier.srcAccessMask =
post_process_initialized[destination_target][image_index] ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT : VK_ACCESS_2_NONE;
3248 next_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3249 next_target_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3250 next_target_barrier.oldLayout =
post_process_initialized[destination_target][image_index] ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
3251 next_target_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3252 next_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3253 next_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3255 next_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3256 next_target_barrier.subresourceRange.levelCount = 1;
3257 next_target_barrier.subresourceRange.layerCount = 1;
3258 VkDependencyInfo next_target_dependency{};
3259 next_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3260 next_target_dependency.imageMemoryBarrierCount = 1;
3261 next_target_dependency.pImageMemoryBarriers = &next_target_barrier;
3262 vkCmdPipelineBarrier2(cmd, &next_target_dependency);
3265 VkRenderingAttachmentInfo post_attachment{};
3266 post_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3267 post_attachment.imageView = destination_view;
3268 post_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3269 post_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3270 post_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3271 VkRenderingInfo post_info{};
3272 post_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3273 post_info.renderArea.extent = detached_render ? render_extent :
swapchain_extent;
3274 post_info.layerCount = 1;
3275 post_info.colorAttachmentCount = 1;
3276 post_info.pColorAttachments = &post_attachment;
3277 vkCmdBeginRendering(cmd, &post_info);
3279 effect_sprite->setExternalTexture(
post_process_views[source_target][image_index],
static_cast<int>(render_extent.width),
static_cast<int>(render_extent.height));
3280 effect_sprite->drawSpriteRect(0, 0,
static_cast<int>(render_extent.width),
static_cast<int>(render_extent.height));
3282 vkCmdEndRendering(cmd);
3285 VkImageMemoryBarrier2 sampled_target_barrier{};
3286 sampled_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3287 sampled_target_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3288 sampled_target_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3289 sampled_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3290 sampled_target_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
3291 sampled_target_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3292 sampled_target_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3293 sampled_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3294 sampled_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3296 sampled_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3297 sampled_target_barrier.subresourceRange.levelCount = 1;
3298 sampled_target_barrier.subresourceRange.layerCount = 1;
3299 VkDependencyInfo sampled_target_dependency{};
3300 sampled_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3301 sampled_target_dependency.imageMemoryBarrierCount = 1;
3302 sampled_target_dependency.pImageMemoryBarriers = &sampled_target_barrier;
3303 vkCmdPipelineBarrier2(cmd, &sampled_target_dependency);
3305 source_target = destination_target;
3309 bool hdr_consumer_rendered =
false;
3311 const size_t consumer_target = source_target == 2U ? 3U : 2U;
3312 VkImageMemoryBarrier2 consumer_target_barrier{};
3313 consumer_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3314 consumer_target_barrier.srcStageMask =
post_process_initialized[consumer_target][image_index] ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE;
3315 consumer_target_barrier.srcAccessMask =
post_process_initialized[consumer_target][image_index] ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT : VK_ACCESS_2_NONE;
3316 consumer_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3317 consumer_target_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3318 consumer_target_barrier.oldLayout =
post_process_initialized[consumer_target][image_index] ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
3319 consumer_target_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3320 consumer_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3321 consumer_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3323 consumer_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3324 consumer_target_barrier.subresourceRange.levelCount = 1;
3325 consumer_target_barrier.subresourceRange.layerCount = 1;
3326 VkDependencyInfo consumer_target_dependency{};
3327 consumer_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3328 consumer_target_dependency.imageMemoryBarrierCount = 1;
3329 consumer_target_dependency.pImageMemoryBarriers = &consumer_target_barrier;
3330 vkCmdPipelineBarrier2(cmd, &consumer_target_dependency);
3332 VkRenderingAttachmentInfo consumer_attachment{};
3333 consumer_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3335 consumer_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3336 consumer_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3337 consumer_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3338 VkRenderingInfo consumer_info{};
3339 consumer_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3340 consumer_info.renderArea.extent = render_extent;
3341 consumer_info.layerCount = 1;
3342 consumer_info.colorAttachmentCount = 1;
3343 consumer_info.pColorAttachments = &consumer_attachment;
3344 consumer_info.pDepthAttachment = depth_attachment.imageView != VK_NULL_HANDLE ? &depth_attachment :
nullptr;
3345 vkCmdBeginRendering(cmd, &consumer_info);
3346 vkCmdSetViewport(cmd, 0, 1, &viewport);
3347 vkCmdSetScissor(cmd, 0, 1, &scissor);
3349 vkCmdEndRendering(cmd);
3351 VkImageMemoryBarrier2 sampled_consumer_barrier = consumer_target_barrier;
3352 sampled_consumer_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3353 sampled_consumer_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3354 sampled_consumer_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3355 sampled_consumer_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
3356 sampled_consumer_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3357 sampled_consumer_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3358 VkDependencyInfo sampled_consumer_dependency{};
3359 sampled_consumer_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3360 sampled_consumer_dependency.imageMemoryBarrierCount = 1;
3361 sampled_consumer_dependency.pImageMemoryBarriers = &sampled_consumer_barrier;
3362 vkCmdPipelineBarrier2(cmd, &sampled_consumer_dependency);
3364 source_target = consumer_target;
3365 hdr_consumer_rendered =
true;
3373 VkRenderingAttachmentInfo present_attachment{};
3374 present_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3376 present_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3377 present_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3378 present_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3379 VkRenderingInfo present_info{};
3380 present_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3382 present_info.layerCount = 1;
3383 present_info.colorAttachmentCount = 1;
3384 present_info.pColorAttachments = &present_attachment;
3385 vkCmdBeginRendering(cmd, &present_info);
3390 vkCmdEndRendering(cmd);
3394 VkImageMemoryBarrier2 composite_target_barrier{};
3395 composite_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3396 composite_target_barrier.srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3397 composite_target_barrier.srcAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
3398 composite_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3399 composite_target_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3400 composite_target_barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3401 composite_target_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3402 composite_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3403 composite_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3405 composite_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3406 composite_target_barrier.subresourceRange.levelCount = 1;
3407 composite_target_barrier.subresourceRange.layerCount = 1;
3408 VkDependencyInfo composite_dependency{};
3409 composite_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3410 composite_dependency.imageMemoryBarrierCount = 1;
3411 composite_dependency.pImageMemoryBarriers = &composite_target_barrier;
3412 vkCmdPipelineBarrier2(cmd, &composite_dependency);
3414 VkRenderingAttachmentInfo composite_attachment{};
3415 composite_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3417 composite_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3418 composite_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3419 composite_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3420 VkRenderingInfo composite_info{};
3421 composite_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3422 composite_info.renderArea.extent = render_extent;
3423 composite_info.layerCount = 1;
3424 composite_info.colorAttachmentCount = 1;
3425 composite_info.pColorAttachments = &composite_attachment;
3426 composite_info.pDepthAttachment = consume_post_process && !hdr_consumer_rendered && depth_attachment.imageView != VK_NULL_HANDLE ? &depth_attachment :
nullptr;
3427 vkCmdBeginRendering(cmd, &composite_info);
3428 if (consume_post_process && !hdr_consumer_rendered) {
3429 vkCmdSetViewport(cmd, 0, 1, &viewport);
3430 vkCmdSetScissor(cmd, 0, 1, &scissor);
3438 vkCmdEndRendering(cmd);
3439 }
else if (consume_post_process && !hdr_consumer_rendered) {
3440 VkRenderingAttachmentInfo consumer_attachment{};
3441 consumer_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3443 consumer_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3444 consumer_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3445 consumer_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3446 VkRenderingInfo consumer_info{};
3447 consumer_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3449 consumer_info.layerCount = 1;
3450 consumer_info.colorAttachmentCount = 1;
3451 consumer_info.pColorAttachments = &consumer_attachment;
3452 consumer_info.pDepthAttachment = depth_attachment.imageView != VK_NULL_HANDLE ? &depth_attachment :
nullptr;
3453 vkCmdBeginRendering(cmd, &consumer_info);
3454 vkCmdSetViewport(cmd, 0, 1, &viewport);
3455 vkCmdSetScissor(cmd, 0, 1, &scissor);
3457 vkCmdEndRendering(cmd);
3466 VkImageMemoryBarrier2 to_text_barrier{};
3467 to_text_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3468 to_text_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3469 to_text_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3470 to_text_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3471 to_text_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3472 to_text_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3473 to_text_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3474 to_text_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3475 to_text_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3477 to_text_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3478 to_text_barrier.subresourceRange.levelCount = 1;
3479 to_text_barrier.subresourceRange.layerCount = 1;
3480 VkDependencyInfo to_text_dependency{};
3481 to_text_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3482 to_text_dependency.imageMemoryBarrierCount = 1;
3483 to_text_dependency.pImageMemoryBarriers = &to_text_barrier;
3484 vkCmdPipelineBarrier2(cmd, &to_text_dependency);
3486 VkRenderingAttachmentInfo text_attachment{};
3487 text_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3489 text_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3490 text_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
3491 text_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3492 VkRenderingInfo text_info{};
3493 text_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3494 text_info.renderArea.extent = detached_render ? render_extent :
swapchain_extent;
3495 text_info.layerCount = 1;
3496 text_info.colorAttachmentCount = 1;
3497 text_info.pColorAttachments = &text_attachment;
3498 vkCmdBeginRendering(cmd, &text_info);
3499 vkCmdSetViewport(cmd, 0, 1, &viewport);
3500 vkCmdSetScissor(cmd, 0, 1, &scissor);
3501 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
text_pipeline);
3503 vkCmdEndRendering(cmd);
3510 VkImageMemoryBarrier2 to_readback_barrier{};
3511 to_readback_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3512 to_readback_barrier.srcStageMask = use_rgba16_readback ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3513 to_readback_barrier.srcAccessMask = use_rgba16_readback ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT : VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3514 to_readback_barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
3515 to_readback_barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
3516 to_readback_barrier.oldLayout = use_rgba16_readback ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3517 to_readback_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
3518 to_readback_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3519 to_readback_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3520 to_readback_barrier.image = readback_image;
3521 to_readback_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3522 to_readback_barrier.subresourceRange.baseMipLevel = 0;
3523 to_readback_barrier.subresourceRange.levelCount = 1;
3524 to_readback_barrier.subresourceRange.baseArrayLayer = 0;
3525 to_readback_barrier.subresourceRange.layerCount = 1;
3527 VkDependencyInfo to_readback_dependency{};
3528 to_readback_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3529 to_readback_dependency.imageMemoryBarrierCount = 1;
3530 to_readback_dependency.pImageMemoryBarriers = &to_readback_barrier;
3531 vkCmdPipelineBarrier2(cmd, &to_readback_dependency);
3533 VkBufferImageCopy readback_region{};
3534 readback_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3535 readback_region.imageSubresource.mipLevel = 0;
3536 readback_region.imageSubresource.baseArrayLayer = 0;
3537 readback_region.imageSubresource.layerCount = 1;
3538 readback_region.imageExtent = {use_rgba16_readback || detached_render ? render_extent.width :
swapchain_extent.width, use_rgba16_readback || detached_render ? render_extent.height :
swapchain_extent.height, 1};
3541 if (use_rgba16_readback) {
3542 VkImageMemoryBarrier2 restore_readback_barrier = to_readback_barrier;
3543 restore_readback_barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
3544 restore_readback_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
3545 restore_readback_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
3546 restore_readback_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
3547 restore_readback_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
3548 restore_readback_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3549 VkDependencyInfo restore_readback_dependency{};
3550 restore_readback_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3551 restore_readback_dependency.imageMemoryBarrierCount = 1;
3552 restore_readback_dependency.pImageMemoryBarriers = &restore_readback_barrier;
3553 vkCmdPipelineBarrier2(cmd, &restore_readback_dependency);
3558 VkImageMemoryBarrier2 to_sample_barrier{};
3559 to_sample_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3560 to_sample_barrier.srcStageMask = use_output_readback ? VK_PIPELINE_STAGE_2_TRANSFER_BIT : VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3561 to_sample_barrier.srcAccessMask = use_output_readback ? VK_ACCESS_2_TRANSFER_READ_BIT : VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3562 to_sample_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
3563 to_sample_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
3564 to_sample_barrier.oldLayout = use_output_readback ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3565 to_sample_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3566 to_sample_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3567 to_sample_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3569 to_sample_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3570 to_sample_barrier.subresourceRange.levelCount = 1;
3571 to_sample_barrier.subresourceRange.layerCount = 1;
3572 VkDependencyInfo to_sample_dependency{};
3573 to_sample_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3574 to_sample_dependency.imageMemoryBarrierCount = 1;
3575 to_sample_dependency.pImageMemoryBarriers = &to_sample_barrier;
3576 vkCmdPipelineBarrier2(cmd, &to_sample_dependency);
3578 VkRenderingAttachmentInfo present_attachment{};
3579 present_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3581 present_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3582 present_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
3583 present_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3584 present_attachment.clearValue = clear_value;
3585 VkRenderingInfo present_info{};
3586 present_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3588 present_info.layerCount = 1;
3589 present_info.colorAttachmentCount = 1;
3590 present_info.pColorAttachments = &present_attachment;
3591 vkCmdBeginRendering(cmd, &present_info);
3593 VkViewport present_viewport{};
3596 present_viewport.maxDepth = 1.0F;
3597 vkCmdSetViewport(cmd, 0, 1, &present_viewport);
3598 VkRect2D present_scissor{};
3600 vkCmdSetScissor(cmd, 0, 1, &present_scissor);
3602 const double preview_scale = std::min(
static_cast<double>(
swapchain_extent.width) / render_extent.width,
static_cast<double>(
swapchain_extent.height) / render_extent.height);
3603 const int preview_width = std::max(1,
static_cast<int>(std::lround(render_extent.width * preview_scale)));
3604 const int preview_height = std::max(1,
static_cast<int>(std::lround(render_extent.height * preview_scale)));
3605 const int preview_x = (
static_cast<int>(
swapchain_extent.width) - preview_width) / 2;
3606 const int preview_y = (
static_cast<int>(
swapchain_extent.height) - preview_height) / 2;
3611 vkCmdEndRendering(cmd);
3615 if (render_preview_text) {
3616 VkImageMemoryBarrier2 to_preview_barrier{};
3617 to_preview_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3618 to_preview_barrier.srcStageMask = use_output_readback && !detached_render ? VK_PIPELINE_STAGE_2_TRANSFER_BIT : VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3619 to_preview_barrier.srcAccessMask = use_output_readback && !detached_render ? VK_ACCESS_2_TRANSFER_READ_BIT : VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3620 to_preview_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3621 to_preview_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3622 to_preview_barrier.oldLayout = use_output_readback && !detached_render ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3623 to_preview_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3624 to_preview_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3625 to_preview_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3627 to_preview_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3628 to_preview_barrier.subresourceRange.levelCount = 1;
3629 to_preview_barrier.subresourceRange.layerCount = 1;
3630 VkDependencyInfo to_preview_dependency{};
3631 to_preview_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3632 to_preview_dependency.imageMemoryBarrierCount = 1;
3633 to_preview_dependency.pImageMemoryBarriers = &to_preview_barrier;
3634 vkCmdPipelineBarrier2(cmd, &to_preview_dependency);
3636 VkRenderingAttachmentInfo preview_attachment{};
3637 preview_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
3639 preview_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3640 preview_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
3641 preview_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
3642 VkRenderingInfo preview_info{};
3643 preview_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
3645 preview_info.layerCount = 1;
3646 preview_info.colorAttachmentCount = 1;
3647 preview_info.pColorAttachments = &preview_attachment;
3648 vkCmdBeginRendering(cmd, &preview_info);
3652 vkCmdSetViewport(cmd, 0, 1, &viewport);
3653 vkCmdSetScissor(cmd, 0, 1, &scissor);
3654 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
text_pipeline);
3656 vkCmdEndRendering(cmd);
3659 VkImageMemoryBarrier2 to_present_barrier{};
3660 to_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
3661 to_present_barrier.srcStageMask = render_preview_text ? VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT : use_output_readback && !detached_render ? VK_PIPELINE_STAGE_2_TRANSFER_BIT : VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3662 to_present_barrier.srcAccessMask = render_preview_text ? VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT : use_output_readback && !detached_render ? VK_ACCESS_2_TRANSFER_READ_BIT : VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
3663 to_present_barrier.dstStageMask = VK_PIPELINE_STAGE_2_NONE;
3664 to_present_barrier.dstAccessMask = VK_ACCESS_2_NONE;
3665 to_present_barrier.oldLayout = render_preview_text ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : use_output_readback && !detached_render ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
3666 to_present_barrier.newLayout =
headless() ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
3667 to_present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3668 to_present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
3670 to_present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
3671 to_present_barrier.subresourceRange.baseMipLevel = 0;
3672 to_present_barrier.subresourceRange.levelCount = 1;
3673 to_present_barrier.subresourceRange.baseArrayLayer = 0;
3674 to_present_barrier.subresourceRange.layerCount = 1;
3676 VkDependencyInfo post_render_dependency{};
3677 post_render_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
3678 post_render_dependency.imageMemoryBarrierCount = 1;
3679 post_render_dependency.pImageMemoryBarriers = &to_present_barrier;
3680 vkCmdPipelineBarrier2(cmd, &post_render_dependency);
3682 if (vkEndCommandBuffer(cmd) != VK_SUCCESS) {
3683 std::cerr <<
"mxvk: Failed to end command buffer\n";
3688 VkSemaphoreSubmitInfo wait_semaphore_info{};
3689 wait_semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
3690 wait_semaphore_info.semaphore = acquire_semaphore;
3691 wait_semaphore_info.value = 0;
3692 wait_semaphore_info.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
3693 wait_semaphore_info.deviceIndex = 0;
3695 VkCommandBufferSubmitInfo command_buffer_info{};
3696 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
3697 command_buffer_info.commandBuffer = cmd;
3698 command_buffer_info.deviceMask = 0;
3700 VkSemaphoreSubmitInfo signal_semaphore_info{};
3701 signal_semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
3702 signal_semaphore_info.semaphore = present_semaphore;
3703 signal_semaphore_info.value = 0;
3704 signal_semaphore_info.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
3705 signal_semaphore_info.deviceIndex = 0;
3707 VkSubmitInfo2 submit_info{};
3708 submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
3709 submit_info.waitSemaphoreInfoCount =
headless() ? 0U : 1U;
3710 submit_info.pWaitSemaphoreInfos =
headless() ? nullptr : &wait_semaphore_info;
3711 submit_info.commandBufferInfoCount = 1;
3712 submit_info.pCommandBufferInfos = &command_buffer_info;
3713 submit_info.signalSemaphoreInfoCount =
headless() ? 0U : 1U;
3714 submit_info.pSignalSemaphoreInfos =
headless() ? nullptr : &signal_semaphore_info;
3716 const VkResult fence_reset_result = vkResetFences(
device, 1, &frame_fence);
3717 if (fence_reset_result == VK_ERROR_DEVICE_LOST) {
3718 std::cerr <<
"mxvk: device lost while resetting frame fence; stopping render loop\n";
3722 if (fence_reset_result != VK_SUCCESS) {
3723 std::cerr << std::format(
"mxvk: Failed to reset frame fence (VkResult={})\n",
static_cast<int>(fence_reset_result));
3728 const VkResult submit_result = vkQueueSubmit2(
graphics_queue, 1, &submit_info, frame_fence);
3729 if (submit_result == VK_ERROR_DEVICE_LOST) {
3730 std::cerr <<
"mxvk: device lost during queue submit; stopping render loop\n";
3734 if (submit_result != VK_SUCCESS) {
3735 std::cerr << std::format(
"mxvk: Failed to submit draw command (VkResult={})\n",
static_cast<int>(submit_result));
3744 VkResult present_result = VK_SUCCESS;
3746 VkPresentInfoKHR present_info{};
3747 present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
3748 present_info.waitSemaphoreCount = 1;
3749 present_info.pWaitSemaphores = &present_semaphore;
3750 present_info.swapchainCount = 1;
3752 present_info.pImageIndices = &image_index;
3753 present_result = vkQueuePresentKHR(
present_queue, &present_info);
3755 if (present_result == VK_SUCCESS || present_result == VK_SUBOPTIMAL_KHR) {
3759 slot.width = use_rgba16_readback || detached_render ? render_extent.width :
swapchain_extent.width;
3760 slot.height = use_rgba16_readback || detached_render ? render_extent.height :
swapchain_extent.height;
3761 slot.format = use_rgba16_readback ? VK_FORMAT_R16G16B16A16_SFLOAT :
swapchain_format;
3762 slot.pending =
true;
3767 if (present_result == VK_ERROR_OUT_OF_DATE_KHR) {
3768 std::cout <<
"mxvk: requesting swapchain recreation because present returned VK_ERROR_OUT_OF_DATE_KHR\n";
3772 }
else if (present_result == VK_SUBOPTIMAL_KHR) {
3773 int present_pixel_w = 0;
3774 int present_pixel_h = 0;
3776 SDL_GetWindowSizeInPixels(
window.get(), &present_pixel_w, &present_pixel_h);
3778 const bool known_present_extent = present_pixel_w > 0 && present_pixel_h > 0;
3779 const bool present_extent_changed = known_present_extent && (
swapchain_extent.width !=
static_cast<uint32_t
>(present_pixel_w) ||
swapchain_extent.height !=
static_cast<uint32_t
>(present_pixel_h));
3780 if (!known_present_extent || present_extent_changed) {
3781 std::cout <<
"mxvk: requesting swapchain recreation because present returned VK_SUBOPTIMAL_KHR with changed window extent\n";
3786 }
else if (present_result != VK_SUCCESS) {
3787 std::cerr <<
"mxvk: Failed to present swapchain image\n";
3790 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
3804 if (!
retired_swapchains.empty() && (present_result == VK_SUCCESS || present_result == VK_SUBOPTIMAL_KHR)) {
3806 vkDestroySwapchainKHR(
device, retired,
nullptr);
3814 bool VK_Window::hasValidationLayerSupport() {
3815 uint32_t layer_count = 0;
3816 const VkResult count_result = vkEnumerateInstanceLayerProperties(&layer_count,
nullptr);
3817 if (count_result != VK_SUCCESS) {
3818 std::cerr << std::format(
"mxvk: Failed to query validation layer count (VkResult={})\n",
static_cast<int>(count_result));
3822 if (layer_count == 0U) {
3826 std::vector<VkLayerProperties> available_layers(layer_count);
3827 const VkResult layers_result = vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data());
3828 if (layers_result != VK_SUCCESS) {
3829 std::cerr << std::format(
"mxvk: Failed to enumerate validation layers (VkResult={})\n",
static_cast<int>(layers_result));
3833 return std::ranges::any_of(available_layers, [](
const VkLayerProperties &layer) {
return std::strcmp(layer.layerName, validation_layer_name) == 0; });
3836 std::optional<VkDebugUtilsMessengerCreateInfoEXT> VK_Window::makeDebugMessengerCreateInfo() {
3837 VkDebugUtilsMessengerCreateInfoEXT create_info{};
3838 create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
3839 create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
3840 create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
3842 create_info.pUserData =
nullptr;
3846 void VK_Window::setupDebugMessenger() {
3851 const std::optional<VkDebugUtilsMessengerCreateInfoEXT> maybe_create_info = makeDebugMessengerCreateInfo();
3852 if (!maybe_create_info.has_value()) {
3853 std::cerr <<
"mxvk: unable to build debug messenger create info\n";
3857 const VkResult result = vkCreateDebugUtilsMessengerEXT(
instance, &maybe_create_info.value(),
nullptr, &
debug_messenger);
3858 if (result != VK_SUCCESS) {
3859 std::cerr <<
"mxvk: failed to create Vulkan debug messenger\n";
3864 void VK_Window::cleanupDebugMessenger() {
3866 std::cout <<
"vk: destroying debug messenger\n";
3873 if (fontPath.empty() || fontSize <= 0) {
3874 throw mxvk::Exception(
"setFont requires a non-empty path and positive font size");
3881 if (
device == VK_NULL_HANDLE) {
3882 throw mxvk::Exception(
"Cannot set font before Vulkan device initialization");
3884 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
3887 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
3888 throw mxvk::Exception(
"Cannot initialize text renderer before swapchain and command resources are available");
3892 ensureTextRenderer();
3903 if (fontPath.empty() || fontSize <= 0) {
3904 throw mxvk::Exception(
"setPreviewFont requires a non-empty path and positive font size");
3906 if (
device == VK_NULL_HANDLE) {
3907 throw mxvk::Exception(
"Cannot set preview font before Vulkan device initialization");
3910 ensurePreviewTextRenderer(fontPath, fontSize);
3925 throw mxvk::Exception(
"printText requires setFont() to be called first");
3928 ensureTextRenderer();
3937 if (font ==
nullptr) {
3941 ensureTextRenderer(resolveDefaultFontPath(),
font_size);
3943 throw mxvk::Exception(
"printText with an explicit font could not initialize the text renderer");
3957 ensureTextRenderer(font.
path(), font.
size());
3959 throw mxvk::Exception(
"printText with an explicit font could not initialize the text renderer");
3979 throw mxvk::Exception(
"printPreviewText requires setFont() to be called first");
3981 ensurePreviewTextRenderer();
3983 throw mxvk::Exception(
"printPreviewText could not initialize the text renderer");
3994 throw mxvk::Exception(
"printPreviewText requires a valid mxvk::Font");
3996 ensurePreviewTextRenderer(font.
path(), font.
size());
3998 throw mxvk::Exception(
"printPreviewText could not initialize the text renderer");
4006 throw mxvk::Exception(
"getTextDimensions requires setFont() to be called first");
4010 ensureTextRenderer();
4017 return text_renderer->getTextDimensions(text, width, height);
4021 if (font ==
nullptr) {
4027 ensureTextRenderer(resolveDefaultFontPath(),
font_size);
4033 return text_renderer->getTextDimensions(text, width, height, font);
4043 ensureTextRenderer(font.
path(), font.
size());
4049 return text_renderer->getTextDimensions(text, width, height, font);
4052 void VK_Window::ensureTextRenderer() {
4061 if (
device == VK_NULL_HANDLE) {
4064 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4067 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4072 createTextDescriptorSetLayout();
4080 void VK_Window::ensurePreviewTextRenderer() {
4087 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4090 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4094 createTextDescriptorSetLayout();
4101 void VK_Window::ensurePreviewTextRenderer(
const std::string &fallbackFontPath,
int fallbackFontSize) {
4105 if (
device == VK_NULL_HANDLE) {
4108 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4111 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4116 if (renderer_font_path.empty() || renderer_font_size <= 0) {
4120 createTextDescriptorSetLayout();
4127 void VK_Window::ensureTextRenderer(
const std::string &fallbackFontPath,
int fallbackFontSize) {
4132 if (
device == VK_NULL_HANDLE) {
4135 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4138 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4144 if (renderer_font_path.empty() || renderer_font_size <= 0) {
4149 createTextDescriptorSetLayout();
4157 std::string VK_Window::resolveDefaultFontPath()
const {
4158 std::vector<std::filesystem::path> candidates{};
4160 if (
const char *basePath = SDL_GetBasePath(); basePath !=
nullptr) {
4161 const std::filesystem::path executableDir(basePath);
4162 candidates.push_back(executableDir /
"data" /
"default.ttf");
4163 candidates.push_back(executableDir /
"default.ttf");
4166 candidates.push_back(std::filesystem::path(
"data") /
"default.ttf");
4169 const std::filesystem::path configured_font_path(
font_path);
4170 if (configured_font_path.has_parent_path()) {
4171 candidates.push_back(configured_font_path.parent_path() /
"default.ttf");
4177 std::error_code exists_error{};
4178 for (
const std::filesystem::path &candidate : candidates) {
4179 if (std::filesystem::exists(candidate, exists_error)) {
4180 return candidate.string();
4182 exists_error.clear();
4188 void VK_Window::toggleFpsCounter() {
4193 std::cout << std::format(
"mxvk: FPS counter {}\n",
fps_counter_enabled ?
"enabled" :
"disabled");
4200 const std::string default_font_path = resolveDefaultFontPath();
4201 if (default_font_path.empty()) {
4202 std::cerr <<
"mxvk: F12 FPS counter could not locate data/default.ttf\n";
4211 void VK_Window::updateFpsCounter() {
4217 const std::string default_font_path = resolveDefaultFontPath();
4218 if (default_font_path.empty()) {
4219 std::cerr <<
"mxvk: F12 FPS counter could not locate data/default.ttf\n";
4228 const auto now = std::chrono::steady_clock::now();
4230 if (elapsed >= 0.5) {
4240 void VK_Window::createTextDescriptorSetLayout() {
4245 VkDescriptorSetLayoutBinding sampler_binding{};
4246 sampler_binding.binding = 0;
4247 sampler_binding.descriptorCount = 1;
4248 sampler_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
4249 sampler_binding.pImmutableSamplers =
nullptr;
4250 sampler_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
4252 VkDescriptorSetLayoutCreateInfo layout_info{};
4253 layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
4254 layout_info.bindingCount = 1;
4255 layout_info.pBindings = &sampler_binding;
4258 throw mxvk::Exception(
"Failed to create text descriptor set layout");
4262 void VK_Window::destroyTextPipeline() {
4263 if (
device == VK_NULL_HANDLE) {
4270 std::cout <<
"vk: destroying text pipeline\n";
4275 std::cout <<
"vk: destroying text pipeline layout\n";
4281 void VK_Window::createTextPipeline() {
4286 destroyTextPipeline();
4292 VkShaderModule frag_module = VK_NULL_HANDLE;
4297 VkPipelineShaderStageCreateInfo vert_stage{};
4298 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
4299 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
4300 vert_stage.module = vert_module;
4301 vert_stage.pName =
"main";
4303 VkPipelineShaderStageCreateInfo frag_stage{};
4304 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
4305 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
4306 frag_stage.module = frag_module;
4307 frag_stage.pName =
"main";
4309 const VkPipelineShaderStageCreateInfo shader_stages[] = {vert_stage, frag_stage};
4311 VkVertexInputBindingDescription binding_description{};
4312 binding_description.binding = 0;
4313 binding_description.stride =
sizeof(float) * 4;
4314 binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
4316 std::array<VkVertexInputAttributeDescription, 2> attributes{};
4317 attributes[0].binding = 0;
4318 attributes[0].location = 0;
4319 attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
4320 attributes[0].offset = 0;
4321 attributes[1].binding = 0;
4322 attributes[1].location = 1;
4323 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
4324 attributes[1].offset =
sizeof(float) * 2;
4326 VkPipelineVertexInputStateCreateInfo vertex_input{};
4327 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
4328 vertex_input.vertexBindingDescriptionCount = 1;
4329 vertex_input.pVertexBindingDescriptions = &binding_description;
4330 vertex_input.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
4331 vertex_input.pVertexAttributeDescriptions = attributes.data();
4333 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
4334 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
4335 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
4336 input_assembly.primitiveRestartEnable = VK_FALSE;
4338 const std::array<VkDynamicState, 2> dynamic_states = {
4339 VK_DYNAMIC_STATE_VIEWPORT,
4340 VK_DYNAMIC_STATE_SCISSOR,
4342 VkPipelineDynamicStateCreateInfo dynamic_state{};
4343 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
4344 dynamic_state.dynamicStateCount =
static_cast<uint32_t
>(dynamic_states.size());
4345 dynamic_state.pDynamicStates = dynamic_states.data();
4347 VkPipelineViewportStateCreateInfo viewport_state{};
4348 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
4349 viewport_state.viewportCount = 1;
4350 viewport_state.scissorCount = 1;
4352 VkPipelineRasterizationStateCreateInfo rasterizer{};
4353 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
4354 rasterizer.depthClampEnable = VK_FALSE;
4355 rasterizer.rasterizerDiscardEnable = VK_FALSE;
4356 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
4357 rasterizer.lineWidth = 1.0F;
4358 rasterizer.cullMode = VK_CULL_MODE_NONE;
4359 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
4360 rasterizer.depthBiasEnable = VK_FALSE;
4362 VkPipelineMultisampleStateCreateInfo multisampling{};
4363 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
4364 multisampling.sampleShadingEnable = VK_FALSE;
4365 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
4367 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
4368 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
4369 depth_stencil.depthTestEnable = VK_FALSE;
4370 depth_stencil.depthWriteEnable = VK_FALSE;
4372 VkPipelineColorBlendAttachmentState color_attachment{};
4373 color_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
4374 color_attachment.blendEnable = VK_TRUE;
4375 color_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
4376 color_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
4377 color_attachment.colorBlendOp = VK_BLEND_OP_ADD;
4378 color_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
4379 color_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
4380 color_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
4382 VkPipelineColorBlendStateCreateInfo color_blending{};
4383 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
4384 color_blending.logicOpEnable = VK_FALSE;
4385 color_blending.attachmentCount = 1;
4386 color_blending.pAttachments = &color_attachment;
4388 VkPushConstantRange push_constant_range{};
4389 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
4390 push_constant_range.offset = 0;
4391 push_constant_range.size =
sizeof(float) * 4;
4393 VkPipelineLayoutCreateInfo pipeline_layout_info{};
4394 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
4395 pipeline_layout_info.setLayoutCount = 1;
4397 pipeline_layout_info.pushConstantRangeCount = 1;
4398 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
4401 throw mxvk::Exception(
"Failed to create text pipeline layout");
4404 VkPipelineRenderingCreateInfo rendering_info{};
4405 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
4406 rendering_info.viewMask = 0;
4407 rendering_info.colorAttachmentCount = 1;
4413 VkGraphicsPipelineCreateInfo pipeline_info{};
4414 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
4415 pipeline_info.pNext = &rendering_info;
4416 pipeline_info.stageCount = 2;
4417 pipeline_info.pStages = shader_stages;
4418 pipeline_info.pVertexInputState = &vertex_input;
4419 pipeline_info.pInputAssemblyState = &input_assembly;
4420 pipeline_info.pViewportState = &viewport_state;
4421 pipeline_info.pRasterizationState = &rasterizer;
4422 pipeline_info.pMultisampleState = &multisampling;
4423 pipeline_info.pDepthStencilState = &depth_stencil;
4424 pipeline_info.pColorBlendState = &color_blending;
4425 pipeline_info.pDynamicState = &dynamic_state;
4427 pipeline_info.renderPass = VK_NULL_HANDLE;
4428 pipeline_info.subpass = 0;
4429 pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
4430 pipeline_info.basePipelineIndex = -1;
4433 throw mxvk::Exception(
"Failed to create text graphics pipeline");
4444 if (frag_module != VK_NULL_HANDLE) {
4445 vkDestroyShaderModule(
device, frag_module,
nullptr);
4447 vkDestroyShaderModule(
device, vert_module,
nullptr);
4451 vkDestroyShaderModule(
device, frag_module,
nullptr);
4452 vkDestroyShaderModule(
device, vert_module,
nullptr);
4456 if (
device == VK_NULL_HANDLE) {
4457 throw mxvk::Exception(
"Cannot create sprite before Vulkan device initialization");
4459 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4462 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4463 throw mxvk::Exception(
"Cannot create sprite before swapchain and command resources are available");
4467 createSpriteDescriptorSetLayout();
4476 if (!vertexShaderPath.empty()) {
4480 sprite->
loadSprite(pngPath, fragmentShaderPath);
4482 VK_Sprite *
const sprite_ptr = sprite.get();
4483 sprites.push_back(std::move(sprite));
4490 throw mxvk::Exception(
"Cannot create sprite from a null SDL surface");
4492 if (
device == VK_NULL_HANDLE) {
4493 throw mxvk::Exception(
"Cannot create sprite before Vulkan device initialization");
4495 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4498 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4499 throw mxvk::Exception(
"Cannot create sprite before swapchain and command resources are available");
4503 createSpriteDescriptorSetLayout();
4512 if (!vertexShaderPath.empty()) {
4515 if (!fragmentShaderPath.empty()) {
4521 VK_Sprite *
const sprite_ptr = sprite.get();
4522 sprites.push_back(std::move(sprite));
4527 VK_Sprite *
VK_Window::createSprite(
int width,
int height,
const std::string &vertexShaderPath,
const std::string &fragmentShaderPath, uint32_t spectrumBinCount) {
return createSprite(width, height, vertexShaderPath, fragmentShaderPath, spectrumBinCount, 0); }
4529 VK_Sprite *
VK_Window::createSprite(
int width,
int height,
const std::string &vertexShaderPath,
const std::string &fragmentShaderPath, uint32_t spectrumBinCount, uint32_t spectrumHistoryLayerCount) {
4530 if (width <= 0 || height <= 0) {
4533 if (
device == VK_NULL_HANDLE) {
4534 throw mxvk::Exception(
"Cannot create sprite before Vulkan device initialization");
4536 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4539 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4540 throw mxvk::Exception(
"Cannot create sprite before swapchain and command resources are available");
4544 createSpriteDescriptorSetLayout();
4552 if (!fragmentShaderPath.empty()) {
4555 if (spectrumBinCount > 0) {
4558 if (spectrumHistoryLayerCount > 0) {
4559 if (spectrumBinCount == 0) {
4560 throw mxvk::Exception(
"Spectrum history requires a positive spectrum bin count");
4567 VK_Sprite *
const sprite_ptr = sprite.get();
4568 sprites.push_back(std::move(sprite));
4574 if (
device == VK_NULL_HANDLE) {
4575 throw mxvk::Exception(
"Cannot create 3D sprite before Vulkan device initialization");
4577 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4580 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4581 throw mxvk::Exception(
"Cannot create 3D sprite before swapchain and command resources are available");
4584 const std::string vertPath = vertexShaderPath.empty() ? resolveRuntimeShaderPath(
"sprite3d.vert.spv", MXVK_SPRITE3D_SHADER_DIR) : vertexShaderPath;
4585 const std::string fragPath = fragmentShaderPath.empty() ? resolveRuntimeShaderPath(
"sprite3d.frag.spv", MXVK_SPRITE3D_SHADER_DIR) : fragmentShaderPath;
4587 auto sprite = std::make_unique<VK_Sprite3D>();
4588 sprite->load(
this, pngPath, vertPath, fragPath);
4597 throw mxvk::Exception(
"Cannot create 3D sprite from a null SDL surface");
4599 if (
device == VK_NULL_HANDLE) {
4600 throw mxvk::Exception(
"Cannot create 3D sprite before Vulkan device initialization");
4602 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4605 if (!renderTargetsReady() ||
command_pool == VK_NULL_HANDLE) {
4606 throw mxvk::Exception(
"Cannot create 3D sprite before swapchain and command resources are available");
4609 const std::string vertPath = vertexShaderPath.empty() ? resolveRuntimeShaderPath(
"sprite3d.vert.spv", MXVK_SPRITE3D_SHADER_DIR) : vertexShaderPath;
4610 const std::string fragPath = fragmentShaderPath.empty() ? resolveRuntimeShaderPath(
"sprite3d.frag.spv", MXVK_SPRITE3D_SHADER_DIR) : fragmentShaderPath;
4612 auto sprite = std::make_unique<VK_Sprite3D>();
4613 sprite->load(
this,
surface, vertPath, fragPath);
4627 void VK_Window::createSpriteDescriptorSetLayout() {
4632 VkDescriptorSetLayoutBinding sampler_binding{};
4633 sampler_binding.binding = 0;
4634 sampler_binding.descriptorCount = 1;
4635 sampler_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
4636 sampler_binding.pImmutableSamplers =
nullptr;
4637 sampler_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
4639 VkDescriptorSetLayoutCreateInfo layout_info{};
4640 layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
4641 layout_info.bindingCount = 1;
4642 layout_info.pBindings = &sampler_binding;
4645 throw mxvk::Exception(
"Failed to create sprite descriptor set layout");
4649 void VK_Window::destroySpritePipeline() {
4650 if (
device == VK_NULL_HANDLE) {
4657 std::cout <<
"vk: destroying sprite pipeline\n";
4662 std::cout <<
"vk: destroying sprite pipeline layout\n";
4668 void VK_Window::createSpritePipeline() {
4673 createSpriteDescriptorSetLayout();
4676 destroySpritePipeline();
4682 VkShaderModule frag_module = VK_NULL_HANDLE;
4687 VkPipelineShaderStageCreateInfo vert_stage{};
4688 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
4689 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
4690 vert_stage.module = vert_module;
4691 vert_stage.pName =
"main";
4693 VkPipelineShaderStageCreateInfo frag_stage{};
4694 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
4695 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
4696 frag_stage.module = frag_module;
4697 frag_stage.pName =
"main";
4699 const VkPipelineShaderStageCreateInfo shader_stages[] = {vert_stage, frag_stage};
4701 VkVertexInputBindingDescription binding_description{};
4702 binding_description.binding = 0;
4703 binding_description.stride =
sizeof(float) * 4;
4704 binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
4706 std::array<VkVertexInputAttributeDescription, 2> attributes{};
4707 attributes[0].binding = 0;
4708 attributes[0].location = 0;
4709 attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
4710 attributes[0].offset = 0;
4711 attributes[1].binding = 0;
4712 attributes[1].location = 1;
4713 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
4714 attributes[1].offset =
sizeof(float) * 2;
4716 VkPipelineVertexInputStateCreateInfo vertex_input{};
4717 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
4718 vertex_input.vertexBindingDescriptionCount = 1;
4719 vertex_input.pVertexBindingDescriptions = &binding_description;
4720 vertex_input.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
4721 vertex_input.pVertexAttributeDescriptions = attributes.data();
4723 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
4724 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
4725 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
4726 input_assembly.primitiveRestartEnable = VK_FALSE;
4728 const std::array<VkDynamicState, 2> dynamic_states = {
4729 VK_DYNAMIC_STATE_VIEWPORT,
4730 VK_DYNAMIC_STATE_SCISSOR,
4732 VkPipelineDynamicStateCreateInfo dynamic_state{};
4733 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
4734 dynamic_state.dynamicStateCount =
static_cast<uint32_t
>(dynamic_states.size());
4735 dynamic_state.pDynamicStates = dynamic_states.data();
4737 VkPipelineViewportStateCreateInfo viewport_state{};
4738 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
4739 viewport_state.viewportCount = 1;
4740 viewport_state.scissorCount = 1;
4742 VkPipelineRasterizationStateCreateInfo rasterizer{};
4743 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
4744 rasterizer.depthClampEnable = VK_FALSE;
4745 rasterizer.rasterizerDiscardEnable = VK_FALSE;
4746 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
4747 rasterizer.lineWidth = 1.0F;
4748 rasterizer.cullMode = VK_CULL_MODE_NONE;
4749 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
4750 rasterizer.depthBiasEnable = VK_FALSE;
4752 VkPipelineMultisampleStateCreateInfo multisampling{};
4753 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
4754 multisampling.sampleShadingEnable = VK_FALSE;
4755 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
4757 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
4758 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
4759 depth_stencil.depthTestEnable = VK_FALSE;
4760 depth_stencil.depthWriteEnable = VK_FALSE;
4762 VkPipelineColorBlendAttachmentState color_attachment{};
4763 color_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
4764 color_attachment.blendEnable = VK_TRUE;
4765 color_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
4766 color_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
4767 color_attachment.colorBlendOp = VK_BLEND_OP_ADD;
4768 color_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
4769 color_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
4770 color_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
4772 VkPipelineColorBlendStateCreateInfo color_blending{};
4773 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
4774 color_blending.logicOpEnable = VK_FALSE;
4775 color_blending.attachmentCount = 1;
4776 color_blending.pAttachments = &color_attachment;
4778 VkPushConstantRange push_constant_range{};
4779 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
4780 push_constant_range.offset = 0;
4781 push_constant_range.size =
sizeof(float) * 12;
4783 VkPipelineLayoutCreateInfo pipeline_layout_info{};
4784 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
4785 pipeline_layout_info.setLayoutCount = 1;
4787 pipeline_layout_info.pushConstantRangeCount = 1;
4788 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
4791 throw mxvk::Exception(
"Failed to create sprite pipeline layout");
4794 VkPipelineRenderingCreateInfo rendering_info{};
4795 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
4796 rendering_info.viewMask = 0;
4797 rendering_info.colorAttachmentCount = 1;
4803 VkGraphicsPipelineCreateInfo pipeline_info{};
4804 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
4805 pipeline_info.pNext = &rendering_info;
4806 pipeline_info.stageCount = 2;
4807 pipeline_info.pStages = shader_stages;
4808 pipeline_info.pVertexInputState = &vertex_input;
4809 pipeline_info.pInputAssemblyState = &input_assembly;
4810 pipeline_info.pViewportState = &viewport_state;
4811 pipeline_info.pRasterizationState = &rasterizer;
4812 pipeline_info.pMultisampleState = &multisampling;
4813 pipeline_info.pDepthStencilState = &depth_stencil;
4814 pipeline_info.pColorBlendState = &color_blending;
4815 pipeline_info.pDynamicState = &dynamic_state;
4817 pipeline_info.renderPass = VK_NULL_HANDLE;
4818 pipeline_info.subpass = 0;
4819 pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
4820 pipeline_info.basePipelineIndex = -1;
4823 throw mxvk::Exception(
"Failed to create sprite graphics pipeline");
4834 if (frag_module != VK_NULL_HANDLE) {
4835 vkDestroyShaderModule(
device, frag_module,
nullptr);
4837 vkDestroyShaderModule(
device, vert_module,
nullptr);
4841 vkDestroyShaderModule(
device, frag_module,
nullptr);
4842 vkDestroyShaderModule(
device, vert_module,
nullptr);
Small RAII wrapper for an SDL_ttf font handle.
int size() const noexcept
const std::string & path() const noexcept
Depth-tested 3D billboard sprite batch.
void renderSprites(VkCommandBuffer cmdBuffer, VkPipelineLayout pipelineLayout, uint32_t screenWidth, uint32_t screenHeight)
Record all queued draw commands into the given command buffer.
void setVertexShaderPath(const std::string &path)
Override the vertex shader path (used when rebuilding the pipeline).
void releaseUploadResources()
Release upload/staging resources tied to the current command pool.
void shareHistoryTexture(const VK_Sprite &source)
Bind another sprite's history array without taking ownership.
void setDepthAttachmentFormat(VkFormat format)
Assign dynamic-rendering depth attachment format used to build pipelines.
void setCommandPool(VkCommandPool pool)
Rebind the command pool used for upload/staging operations.
void enableComputeShader(const std::string &path, uint32_t localSizeX, uint32_t localSizeY, uint32_t localSizeZ=1)
Build a compute image-effect pipeline for this sprite.
void setShaderParams(float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Set up to four custom shader float parameters.
void setDescriptorSetLayout(VkDescriptorSetLayout layout)
Assign an external descriptor-set layout.
void enableExtendedUBO()
Allocate and initialise the extended uniform buffer object.
void prepareForRendering(VkCommandBuffer cmdBuffer)
Record texture barriers that must happen before dynamic rendering begins.
void updateTexture(SDL_Surface *surface)
Replace the sprite texture from an SDL_Surface.
void rebuildInstancedPipeline()
Destroy and recreate the instanced graphics pipeline.
void setColorAttachmentFormat(VkFormat format)
Assign dynamic-rendering color attachment format used to build pipelines.
void loadSprite(const std::string &pngPath, const std::string &fragmentShaderPath="")
Load sprite texture from a PNG file.
void clearExternalTextureDescriptors()
void createEmptySprite(int width, int height, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a blank (un-initialised) sprite texture.
uint32_t enableSpectrumHistoryTexture(uint32_t bins, uint32_t layers)
Allocate a shader-readable FFT spectrum-history array.
void setExternalTexture(VkImageView image_view, int width, int height)
void setPipelineCache(VkPipelineCache cache)
Use the shared pipeline cache for custom/instanced pipeline creation.
void setFragmentShaderPath(const std::string &path)
Replace the fragment shader path and rebuild the custom pipeline.
void clearQueue()
Discard all pending draw commands without rendering.
void enableSpectrumTexture(uint32_t bins)
Allocate a shader-readable 1-D floating-point spectrum texture.
void rebuildPipeline()
Destroy and recreate the custom graphics pipeline.
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.
std::vector< ShaderStage > post_process_effect_stages
VkPipelineLayout sprite_pipeline_layout
bool post_process_enabled
bool force_swapchain_recreate
std::mutex screenshot_queue_mutex
void captureSnapshotPixels(std::vector< std::uint8_t > &rgba_pixels, uint32_t &width, uint32_t &height)
Capture the most recently presented swapchain image as tightly packed RGBA8 pixels.
virtual void event(SDL_Event &e)
Handle one SDL event.
virtual ~VK_Window()
Destroy owned Vulkan and SDL resources.
void pickDevice()
Pick a suitable Vulkan physical device.
void dispatchFrameReadback(FrameReadbackSlot &slot)
static constexpr std::chrono::seconds MEMORY_TRIM_INTERVAL
VkDescriptorSetLayout sprite_descriptor_set_layout
std::vector< std::vector< bool > > post_process_initialized
void loop()
Run the main event/render loop.
VK_Sprite3D * createSprite3D(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a world-space billboard sprite from a PNG file.
std::condition_variable screenshot_queue_cv
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
VkDebugUtilsMessengerEXT debug_messenger
static VkBool32 debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data)
std::chrono::steady_clock::time_point fps_counter_sample_time
uint32_t screenshot_index
void createLogicalDevice()
Create a logical Vulkan device from the selected physical device.
uint32_t fps_counter_frame_count
virtual bool initVulkan(bool validiation)
Initialize Vulkan state.
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
VkClearColorValue clear_color
VkQueue getGraphicsQueue() const noexcept
Get the graphics queue handle.
std::string screenshot_prefix
virtual void onSwapchainRecreated()
Called after swapchain and render resources are recreated.
VkFormat swapchain_format
VkCommandPool getCommandPool() const noexcept
Get the command pool used for graphics/upload work.
VkExtent2D swapchain_extent
virtual void onSwapchainAboutToRecreate()
Called right before swapchain-dependent resources are recreated.
std::vector< std::uint16_t > latest_frame_readback_rgba16
std::vector< bool > post_process_effect_time_enabled
VulkanContext context() const
bool vulkan_library_loaded
VkExtent2D requested_headless_extent
bool screenshot_worker_stop
std::vector< std::array< float, 4 > > post_process_effect_params
std::vector< std::vector< VkDeviceMemory > > post_process_memories
VkDescriptorSetLayout text_descriptor_set_layout
static constexpr uint64_t resize_settle_delay_ms
std::vector< std::vector< VkImageView > > post_process_views
std::unique_ptr< VK_Text > preview_text_renderer
std::chrono::steady_clock::time_point post_process_start_time
uint32_t present_queue_family
void enablePostProcessing(VK_Sprite *sprite)
uint32_t last_presented_image_index
std::vector< VkImageView > swapchain_image_views
std::vector< VK_Sprite * > owned_post_process_sprites
static VkShaderModule createShaderModule(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
std::chrono::steady_clock::time_point last_memory_trim_time
void setRenderExtent(uint32_t width, uint32_t height)
Render post-processing at a fixed extent independent of the preview window.
void ensureFrameReadbackResources()
void createDevice()
Create final device resources.
bool getTextDimensions(const std::string &text, int &width, int &height)
Measure text dimensions in pixels.
std::vector< bool > swapchain_image_initialized
std::vector< VkImage > swapchain_images
std::vector< VkFence > image_fences
void clearTextQueue()
Clear all queued text draw calls for the current frame.
void flushFrameReadbacks()
Wait for and deliver all pending pipelined frame readbacks.
std::vector< VkSwapchainKHR > retired_swapchains
std::vector< VkImageView > depth_image_views
virtual void onConfigureDepthStencilAttachments(VkRenderingAttachmentInfo &depth_attachment, VkRenderingAttachmentInfo &stencil_attachment, uint32_t image_index)
Allow derived classes to customize depth/stencil attachments for the main dynamic rendering pass.
bool post_process_time_enabled
std::unique_ptr< VK_Text > text_renderer
void saveSnapshot(const std::string &path)
Save the most recently rendered window contents as a PNG file.
uint32_t graphics_queue_family
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
std::vector< VK_Sprite * > attachPostProcessingShaders(const std::vector< PostProcessingEffect > &effects)
virtual void onPrepareFrameRendering(VkCommandBuffer cmd, uint32_t image_index)
Record resource transitions that must happen before dynamic rendering begins.
std::vector< VkCommandBuffer > command_buffers
void renderStandaloneSprite(VK_Sprite &sprite, VkCommandBuffer cmd)
Render one standalone sprite using the window's shared sprite pipeline.
std::thread screenshot_worker
std::vector< VkSemaphore > render_finished
void setHdrRenderIntermediatesEnabled(bool enabled)
Select RGBA16F scene/effect intermediates while retaining the presentation format.
VkPipelineCache pipeline_cache
std::unique_ptr< SDL_Window, SDLWindowDeleter > window
VK_Sprite * post_process_composite_sprite
std::vector< std::chrono::steady_clock::time_point > post_process_effect_start_times
VkPipeline sprite_pipeline
void exit()
Request loop termination.
virtual void onRecordPostProcessingTexture(VkCommandBuffer cmd, uint32_t image_index, VkImageView texture_view, VkExtent2D texture_extent)
Render a completed post-processing chain as custom scene input.
VkCommandPool command_pool
VK_Sprite * attachPostProcessingShader(const std::string &fragmentShaderPath, float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Attach a full-screen post-processing fragment shader.
uint32_t latest_frame_readback_width
bool frame_readback_enabled
void printPreviewText(const std::string &text, int x, int y, const SDL_Color &col)
Queue preview-only text after frame readback.
bool hdr_render_intermediates_enabled
bool frame_readback_rgba16_enabled
bool ensureRenderResources()
Ensure deferred render resources are initialized.
VK_Window()=default
Construct an empty window object.
VkPhysicalDevice physical_device
VK_Sprite * post_process_present_sprite
VkPipelineLayout text_pipeline_layout
virtual void onRecordCustomRendering(VkCommandBuffer cmd, uint32_t image_index)
Optional hook for derived classes to record extra draw commands.
std::array< float, 4 > post_process_params
void detachPostProcessingShader()
Detach the current post-processing shader and return to direct swapchain rendering.
std::vector< bool > depth_image_initialized
bool fps_counter_font_ready
uint32_t next_headless_image
std::deque< ScreenshotSaveTask > screenshot_save_queue
void release()
Release Vulkan and SDL resources.
std::array< FrameReadbackSlot, max_frames_in_flight > frame_readback_slots
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
std::vector< VkDeviceMemory > depth_image_memories
std::vector< std::uint8_t > latest_frame_readback_rgba
PresentModePreference present_mode_preference
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
static std::vector< char > loadSpv(const std::string &path)
Load a SPIR-V file from disk.
std::vector< std::unique_ptr< VK_Sprite3D > > sprites3d
VkExtent2D render_extent_override
virtual void onFrameReadbackRgba16(std::vector< std::uint16_t > &rgba_pixels, uint32_t width, uint32_t height)
Receive normalized RGBA16 pixels from an RGBA16F HDR target.
std::vector< VK_Sprite * > post_process_sprites
void trimMemory()
Return transient Vulkan command-pool allocations to the driver when supported.
virtual void onFrameReadback(std::vector< std::uint8_t > &rgba_pixels, uint32_t width, uint32_t height)
Receive one tightly packed RGBA8 copy of the rendered frame.
VK_Sprite * post_process_sprite
uint32_t latest_frame_readback_height
void setPostProcessingShaderParams(float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Set the post-processing shader params passed as a vec4 push constant.
void destroyFrameReadbackResources()
std::string fps_counter_text
virtual void render()
Render one frame.
bool initWindow(const std::string &title, int width, int height, SDL_WindowFlags flags)
Initialize SDL window resources.
bool headless() const noexcept
Return true when rendering without SDL video, a surface, or presentation.
std::vector< VkImage > depth_images
std::vector< VkDeviceMemory > headless_image_memories
bool post_process_texture_consumer_enabled
void setPostProcessingPresentFragmentShader(const std::string &path)
Set an optional fragment shader used only when presenting the final post-processing texture to a wind...
virtual void proc()
Execute one processing/update step.
bool validationEnabled() const
Check whether Vulkan validation layers are currently enabled.
virtual void onFrameReadbackScheduled()
Called after a presented frame has scheduled a readback.
std::vector< std::unique_ptr< VK_Sprite > > sprites
uint64_t last_resize_event_ms
VkExtent2D getRenderExtent() const noexcept
Get the active scene/post-processing extent.
VkPhysicalDevice getPhysicalDevice() const noexcept
Get the Vulkan physical device handle.
void setPostProcessingShaderTimeEnabled(bool enabled)
Keep shader param 1 updated with elapsed render time in seconds.
std::vector< std::vector< VkImage > > post_process_images
void setEnableScreenshot(bool enabled) noexcept
Enable or disable F10 screenshot capture.
std::string post_process_present_shader_path
void setPreviewFont(const std::string &fontPath, int fontSize=24)
Set the preview-only HUD font independently of output text.
VK_Sprite * owned_post_process_sprite
std::array< VkSemaphore, max_frames_in_flight > image_available
bool swapchain_supports_transfer_src
std::array< VkFence, max_frames_in_flight > in_flight_fences
VkFormat postProcessIntermediateFormat() const noexcept
#define MXVK_TEXT_SHADER_DIR
#define MXVK_SPRITE_SHADER_DIR
#define MXVK_DEFAULT_FONT_DIR
PNG image loading and saving utilities via SDL3.
float half_to_float(uint16_t value) noexcept
bool shouldLogMissingValidationLayer()
Utilities for loading and saving PNG images.
bool SavePNG_RGBA(const char *filename, void *buffer, int w, int h, int compression_level=6)
Save a raw RGBA pixel buffer to a PNG file.
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
const std::string & defaultShaderDirectory()
bool defaultEnableScreenshot()
ShaderModuleInfo inspect_spirv(const std::vector< char > &spv_bytes)
Inspect a SPIR-V entry point and compute local workgroup size.
const std::string & defaultExecutableName()
std::vector< char > load_spv(const std::string &path)
Load a SPIR-V file from disk.
StorageImageFormat storageImageFormat
std::array< float, 4 > params
uint32_t spectrumHistoryLayerCount
VK_Sprite * historySource
uint32_t spectrumBinCount
std::string fragmentShaderPath
Minimal Vulkan handles required by MXVK resource helpers.