5#include <SDL3/SDL_oldnames.h>
6#include <SDL3/SDL_video.h>
7#include <SDL3/SDL_vulkan.h>
24#ifndef MXVK_SPRITE_SHADER_DIR
25#define MXVK_SPRITE_SHADER_DIR "."
28#ifndef MXVK_TEXT_SHADER_DIR
29#define MXVK_TEXT_SHADER_DIR "."
32#ifndef MXVK_DEFAULT_FONT_DIR
33#define MXVK_DEFAULT_FONT_DIR "."
39 const char *quiet_missing_validation = std::getenv(
"MXVK_QUIET_MISSING_VALIDATION");
40 return quiet_missing_validation ==
nullptr || std::strcmp(quiet_missing_validation,
"1") != 0;
44 VKAPI_ATTR VkBool32 VKAPI_CALL
VK_Window::debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT *callback_data, [[maybe_unused]]
void *user_data) {
45 const char *message = (callback_data !=
nullptr && callback_data->pMessage !=
nullptr)
46 ? callback_data->pMessage
47 :
"Unknown Vulkan validation message";
49 if ((severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0U) {
50 std::cerr << std::format(
"vk validation error: {}\n", message);
51 }
else if ((severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0U) {
52 std::cerr << std::format(
"vk validation warning: {}\n", message);
54 std::cout << std::format(
"vk validation: {}\n", message);
68 VK_Window::SwapchainSupport VK_Window::querySwapchainSupport(VkPhysicalDevice device, VkSurfaceKHR
surface) {
69 std::cout <<
"vk: querying swapchain support details\n";
70 SwapchainSupport support{};
72 vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
device,
surface, &support.capabilities);
74 uint32_t format_count = 0;
75 vkGetPhysicalDeviceSurfaceFormatsKHR(
device,
surface, &format_count,
nullptr);
76 if (format_count > 0U) {
77 support.formats.resize(format_count);
78 vkGetPhysicalDeviceSurfaceFormatsKHR(
device,
surface, &format_count, support.formats.data());
81 uint32_t present_mode_count = 0;
82 vkGetPhysicalDeviceSurfacePresentModesKHR(
device,
surface, &present_mode_count,
nullptr);
83 if (present_mode_count > 0U) {
84 support.present_modes.resize(present_mode_count);
85 vkGetPhysicalDeviceSurfacePresentModesKHR(
89 support.present_modes.data());
95 VkSurfaceFormatKHR VK_Window::chooseSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &available_formats) {
96 for (
const VkSurfaceFormatKHR &format : available_formats) {
97 if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
102 return available_formats.front();
105 VkPresentModeKHR VK_Window::choosePresentMode(
const std::vector<VkPresentModeKHR> &available_present_modes)
const {
107 return VK_PRESENT_MODE_FIFO_KHR;
110 for (
const VkPresentModeKHR present_mode : available_present_modes) {
111 if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) {
116 return VK_PRESENT_MODE_FIFO_KHR;
119 VkExtent2D VK_Window::chooseExtent(
const VkSurfaceCapabilitiesKHR &capabilities, SDL_Window *window) {
120 if (capabilities.currentExtent.width != UINT32_MAX) {
121 return capabilities.currentExtent;
126 SDL_GetWindowSizeInPixels(
window, &width, &height);
128 VkExtent2D actual_extent{};
129 actual_extent.width = std::clamp(
130 static_cast<uint32_t
>(std::max(width, 1)),
131 capabilities.minImageExtent.width,
132 capabilities.maxImageExtent.width);
133 actual_extent.height = std::clamp(
134 static_cast<uint32_t
>(std::max(height, 1)),
135 capabilities.minImageExtent.height,
136 capabilities.maxImageExtent.height);
138 return actual_extent;
149 std::string VK_Window::resolveRuntimeShaderPath(
const std::string &shaderFileName,
const char *fallbackDir)
const {
150 if (shaderFileName.empty()) {
154 std::vector<std::filesystem::path> candidates{};
156 if (
const char *basePath = SDL_GetBasePath(); basePath !=
nullptr) {
157 const std::filesystem::path executableDir(basePath);
158 candidates.push_back(executableDir /
"data" / shaderFileName);
159 candidates.push_back(executableDir / shaderFileName);
162 candidates.push_back(std::filesystem::path(
"data") / shaderFileName);
165 const std::filesystem::path fontPath(
font_path);
166 if (fontPath.has_parent_path()) {
167 candidates.push_back(fontPath.parent_path() / shaderFileName);
171 if (fallbackDir !=
nullptr && fallbackDir[0] !=
'\0') {
172 candidates.push_back(std::filesystem::path(fallbackDir) / shaderFileName);
175 std::error_code existsError{};
176 for (
const std::filesystem::path &candidate : candidates) {
177 if (std::filesystem::exists(candidate, existsError)) {
178 return candidate.string();
183 throw mxvk::Exception(std::format(
"Failed to locate shader file '{}'", shaderFileName));
189 std::cout << std::format(
"mxvk: starting VK_Window construction (title='{}', width={}, height={}, fullscreen={}, validation={})\n", title, width, height, full, validiation);
190 SDL_WindowFlags flags =
static_cast<SDL_WindowFlags
>(SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
192 std::cout <<
"SDL3: enabling fullscreen window flag\n";
193 flags =
static_cast<SDL_WindowFlags
>(flags | SDL_WINDOW_FULLSCREEN);
196 std::cout <<
"mxvk: initializing window subsystem and creating SDL window\n";
197 if (!
initWindow(title, width, height, flags)) {
202 std::cout <<
"mxvk: initializing Vulkan runtime and rendering resources\n";
207 std::cout <<
"mxvk: VK_Window construction complete\n";
210 VK_Window::VK_Window(
const std::string &title,
int width,
int height,
bool full,
bool validiation,
bool enableVsync)
214 std::cout <<
"mxvk: destructor invoked, releasing resources\n";
219 std::cout <<
"mxvk: starting resource teardown\n";
220 stopScreenshotWorker();
222 if (
device != VK_NULL_HANDLE) {
223 std::cout <<
"vk: waiting for device idle before teardown\n";
236 std::cout << std::format(
"vk: releasing {} sprite(s)\n",
sprites.size());
240 std::cout << std::format(
"vk: releasing {} 3D sprite batch(es)\n",
sprites3d.size());
244 destroySpritePipeline();
254 destroyTextPipeline();
260 cleanupSyncObjects();
264 vkDestroySwapchainKHR(
device, retired,
nullptr);
268 std::cout <<
"vk: tearing down swapchain-dependent resources\n";
269 cleanupSwapchain(
true);
272 std::cout <<
"vk: destroying command pool\n";
277 if (
device != VK_NULL_HANDLE) {
279 destroyPipelineCache();
280 std::cout <<
"vk: destroying logical device\n";
281 vkDestroyDevice(
device,
nullptr);
292 std::cout <<
"vk: destroying presentation surface\n";
297 cleanupDebugMessenger();
300 std::cout <<
"vk: destroying Vulkan instance\n";
301 vkDestroyInstance(
instance,
nullptr);
305 std::cout <<
"SDL3: destroying SDL window handle\n";
309 std::cout <<
"SDL3: shutting down SDL video/gamepad subsystems\n";
310 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
315 std::cout <<
"mxvk: resource teardown complete\n";
319 std::cout << std::format(
"mxvk: entering initVulkan (validation={})\n", validiation);
323 std::cerr <<
"mxvk: Cannot initialize Vulkan without an SDL window\n";
328 std::cout <<
"vk: instance already initialized; skipping initVulkan\n";
332 std::cout <<
"vk: initializing volk loader\n";
333 if (volkInitialize() != VK_SUCCESS) {
334 std::cerr <<
"mxvk: Failed to initialize volk\n";
338 unsigned int extension_count = 0;
339 std::cout <<
"SDL3: querying Vulkan instance extensions required by SDL\n";
340 const char *
const *extensions = SDL_Vulkan_GetInstanceExtensions(&extension_count);
341 if (extensions ==
nullptr || extension_count == 0U) {
342 std::cerr << std::format(
"mxvk: Failed to get Vulkan instance extensions: {}\n", SDL_GetError());
345 std::cout << std::format(
"vk: SDL provided {} required instance extension(s)\n", extension_count);
347 std::vector<const char *> enabled_extensions(extensions, extensions + extension_count);
349#if defined(MXVK_USE_MOLTENVK)
350 const auto append_instance_extension_if_missing = [&enabled_extensions](
const char *extension_name) {
351 const bool exists = std::ranges::any_of(
353 [extension_name](
const char *existing) {
return std::strcmp(existing, extension_name) == 0; });
355 enabled_extensions.push_back(extension_name);
358 append_instance_extension_if_missing(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
359 append_instance_extension_if_missing(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
362 std::vector<const char *> enabled_layers{};
363 VkDebugUtilsMessengerCreateInfoEXT debug_create_info{};
365 if (!hasValidationLayerSupport()) {
366 if (shouldLogMissingValidationLayer()) {
367 std::cerr << std::format(
368 "mxvk: validation layer '{}' is not available; continuing without validation\n",
369 validation_layer_name);
373 enabled_layers.push_back(validation_layer_name);
374 enabled_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
375 const std::optional<VkDebugUtilsMessengerCreateInfoEXT> maybe_debug_create_info =
376 makeDebugMessengerCreateInfo();
377 if (!maybe_debug_create_info.has_value()) {
378 std::cerr <<
"mxvk: failed to construct debug messenger create info\n";
380 enabled_layers.clear();
382 debug_create_info = maybe_debug_create_info.value();
387 VkApplicationInfo app_info{};
388 app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
389 app_info.pApplicationName =
"mxvk";
390 app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
391 app_info.pEngineName =
"mxvk";
392 app_info.engineVersion = VK_MAKE_VERSION(MXVK_VERSION_CODE_MAJOR, MXVK_VERSION_CODE_MINOR, MXVK_VERSION_CODE_PATCH);
393 app_info.apiVersion = VK_API_VERSION_1_4;
395 VkInstanceCreateInfo create_info{};
396 create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
397 create_info.pApplicationInfo = &app_info;
398 create_info.enabledExtensionCount = extension_count;
399 create_info.ppEnabledExtensionNames = enabled_extensions.data();
400 create_info.enabledLayerCount =
static_cast<uint32_t
>(enabled_layers.size());
401 create_info.ppEnabledLayerNames = enabled_layers.empty() ? nullptr : enabled_layers.data();
404 create_info.enabledExtensionCount =
static_cast<uint32_t
>(enabled_extensions.size());
405#if defined(MXVK_USE_MOLTENVK)
406 create_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
409 std::cout <<
"vk: creating Vulkan instance\n";
410 if (vkCreateInstance(&create_info,
nullptr, &
instance) != VK_SUCCESS) {
411 std::cerr <<
"mxvk: Failed to create Vulkan instance\n";
416 std::cout <<
"vk: loading Vulkan instance function pointers via volk\n";
419 setupDebugMessenger();
421 uint32_t instanceVersion = 0;
422 if (vkEnumerateInstanceVersion !=
nullptr) {
423 vkEnumerateInstanceVersion(&instanceVersion);
425 std::cout <<
"vk: Vulkan instance version: "
426 << VK_VERSION_MAJOR(instanceVersion) <<
"."
427 << VK_VERSION_MINOR(instanceVersion) <<
"."
428 << VK_VERSION_PATCH(instanceVersion) <<
"\n";
429 std::cout <<
"mxvk: engine version: "
430 << MXVK_VERSION_CODE_MAJOR <<
"."
431 << MXVK_VERSION_CODE_MINOR <<
"."
432 << MXVK_VERSION_CODE_PATCH <<
"\n";
434 std::cout <<
"SDL3: creating Vulkan presentation surface from SDL window\n";
436 std::cerr << std::format(
"mxvk: Failed to create Vulkan surface: {}\n", SDL_GetError());
437 cleanupDebugMessenger();
438 vkDestroyInstance(
instance,
nullptr);
444 std::cout <<
"mxvk: selecting suitable physical device\n";
447 std::cerr <<
"mxvk: Failed to find a Vulkan physical device with present support\n";
451 std::cout <<
"mxvk: creating logical device and queues\n";
453 if (
device == VK_NULL_HANDLE) {
454 std::cerr <<
"mxvk: Failed to create Vulkan logical device\n";
457 createPipelineCache();
459 std::cout <<
"mxvk: deferring swapchain/render/sync resource creation until first frame\n";
461 std::cout <<
"mxvk: initVulkan complete\n";
465 std::string VK_Window::pipelineCachePath()
const {
470 VkPhysicalDeviceProperties properties{};
473 char *pref_path = SDL_GetPrefPath(
"mxvk",
"MXVK");
474 std::filesystem::path base_path;
475 if (pref_path !=
nullptr) {
476 base_path = pref_path;
479 base_path = std::filesystem::current_path();
483 std::filesystem::create_directories(base_path, ec);
488 std::ostringstream filename;
489 filename <<
"pipeline_cache_"
490 << std::hex << std::setfill(
'0')
491 << properties.vendorID <<
'_'
492 << properties.deviceID <<
'_'
493 << properties.driverVersion <<
'_';
494 for (uint8_t
byte : properties.pipelineCacheUUID) {
495 filename << std::setw(2) << static_cast<unsigned>(
byte);
499 return (base_path / filename.str()).string();
502 void VK_Window::createPipelineCache() {
507 const std::string cache_path = pipelineCachePath();
508 std::vector<char> initial_data{};
509 if (!cache_path.empty() && std::filesystem::exists(cache_path)) {
510 std::ifstream file(cache_path, std::ios::binary);
512 initial_data.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
516 VkPipelineCacheCreateInfo create_info{};
517 create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
518 create_info.initialDataSize = initial_data.size();
519 create_info.pInitialData = initial_data.empty() ? nullptr : initial_data.data();
522 if (result != VK_SUCCESS && !initial_data.empty()) {
523 std::cerr <<
"vk: cached pipeline data rejected; creating an empty pipeline cache\n";
524 create_info.initialDataSize = 0;
525 create_info.pInitialData =
nullptr;
529 if (result != VK_SUCCESS) {
531 std::cerr << std::format(
"vk: failed to create pipeline cache ({})\n",
static_cast<int>(result));
535 if (!initial_data.empty()) {
536 std::cout << std::format(
"vk: loaded pipeline cache: {} bytes\n", initial_data.size());
540 void VK_Window::savePipelineCache()
const {
545 size_t data_size = 0;
547 if (result != VK_SUCCESS || data_size == 0) {
551 std::vector<char> data(data_size);
553 if (result != VK_SUCCESS || data_size == 0) {
556 data.resize(data_size);
558 const std::string cache_path = pipelineCachePath();
559 if (cache_path.empty()) {
563 std::ofstream file(cache_path, std::ios::binary | std::ios::trunc);
565 std::cerr << std::format(
"vk: failed to open pipeline cache for writing: {}\n", cache_path);
569 file.write(data.data(),
static_cast<std::streamsize
>(data.size()));
571 std::cout << std::format(
"vk: saved pipeline cache: {} bytes\n", data.size());
575 void VK_Window::destroyPipelineCache() {
584 case SDL_EVENT_KEY_DOWN:
594 clear_color.float32[0] = std::clamp(r, 0.0f, 1.0f);
595 clear_color.float32[1] = std::clamp(g, 0.0f, 1.0f);
596 clear_color.float32[2] = std::clamp(b, 0.0f, 1.0f);
597 clear_color.float32[3] = std::clamp(a, 0.0f, 1.0f);
604 while (SDL_PollEvent(&e)) {
609 case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
616 case SDL_EVENT_KEY_DOWN:
617 if ((e.key.key == SDLK_F12 || e.key.scancode == SDL_SCANCODE_F12) && !e.key.repeat) {
621 (e.key.key == SDLK_F10 || e.key.scancode == SDL_SCANCODE_F10) &&
626 std::cerr << std::format(
"mxvk: screenshot failed: {}\n", ex.
text());
627 }
catch (
const std::exception &ex) {
628 std::cerr << std::format(
"mxvk: screenshot failed: {}\n", ex.what());
639 const uint64_t now_ms = SDL_GetTicks();
656 void VK_Window::saveScreenshot() {
657 const std::string path = makeScreenshotPath();
658 std::vector<std::uint8_t> rgba{};
662 enqueueScreenshotSave(path, std::move(rgba), width, height);
663 std::cout << std::format(
"mxvk: screenshot queued: {}\n", path);
666 void VK_Window::enqueueScreenshotSave(std::string path, std::vector<std::uint8_t> rgba, uint32_t width, uint32_t height) {
668 throw mxvk::Exception(
"enqueueScreenshotSave requires a non-empty output path");
670 if (rgba.empty() || width == 0U || height == 0U) {
671 throw mxvk::Exception(
"enqueueScreenshotSave requires non-empty pixel data");
674 startScreenshotWorker();
679 .path = std::move(path),
680 .rgba = std::move(rgba),
688 void VK_Window::startScreenshotWorker() {
698 void VK_Window::stopScreenshotWorker() {
712 void VK_Window::screenshotWorkerLoop() {
731 static_cast<int>(task.width),
732 static_cast<int>(task.height))) {
733 std::cerr << std::format(
"mxvk: screenshot failed to write PNG: {}\n", task.path);
736 std::cout << std::format(
"mxvk: screenshot saved: {}\n", task.path);
740 std::string VK_Window::makeScreenshotPath() {
741 const char *home = std::getenv(
"HOME");
742 std::filesystem::path pictures_dir = (home !=
nullptr && home[0] !=
'\0')
743 ? std::filesystem::path(home) /
"Pictures"
744 : std::filesystem::path(
"Pictures");
745 std::filesystem::create_directories(pictures_dir);
747 const std::time_t now = std::time(
nullptr);
748 std::tm local_time{};
750 localtime_s(&local_time, &now);
752 localtime_r(&now, &local_time);
755 std::ostringstream date_stream;
756 date_stream << std::put_time(&local_time,
"%Y.%m.%d");
757 std::ostringstream time_stream;
758 time_stream << std::put_time(&local_time,
"%H.%M.%S");
760 const std::string prefix = std::format(
"{}.screenshot.{}.{}.{}x{}-",
767 for (uint32_t attempt = 0; attempt < 10000U; ++attempt) {
769 std::filesystem::path path = pictures_dir / std::format(
"{}{}.png", prefix, index);
770 if (!std::filesystem::exists(path)) {
771 return path.string();
775 return (pictures_dir / std::format(
"{}{}.png", prefix,
screenshot_index++)).string();
784 throw mxvk::Exception(
"saveSnapshot requires a non-empty output path");
787 std::vector<std::uint8_t> rgba{};
794 static_cast<int>(width),
795 static_cast<int>(height))) {
802 throw mxvk::Exception(
"captureSnapshotPixels called before Vulkan render resources are ready");
805 throw mxvk::Exception(
"captureSnapshotPixels requires swapchain transfer-source support");
810 throw mxvk::Exception(
"captureSnapshotPixels called before a frame has been presented");
813 throw mxvk::Exception(
"captureSnapshotPixels cannot capture an empty swapchain extent");
816 const bool format_is_bgra =
819 const bool format_is_rgba =
822 if (!format_is_bgra && !format_is_rgba) {
826 const VkDeviceSize row_bytes =
static_cast<VkDeviceSize
>(
swapchain_extent.width) * 4U;
827 const VkDeviceSize image_bytes = row_bytes *
static_cast<VkDeviceSize
>(
swapchain_extent.height);
829 VkBuffer readback_buffer = VK_NULL_HANDLE;
830 VkDeviceMemory readback_memory = VK_NULL_HANDLE;
831 VkCommandBuffer command_buffer = VK_NULL_HANDLE;
832 VkFence copy_fence = VK_NULL_HANDLE;
834 auto cleanup = [&]() {
835 if (copy_fence != VK_NULL_HANDLE) {
836 vkDestroyFence(
device, copy_fence,
nullptr);
838 if (command_buffer != VK_NULL_HANDLE) {
841 if (readback_buffer != VK_NULL_HANDLE) {
842 vkDestroyBuffer(
device, readback_buffer,
nullptr);
844 if (readback_memory != VK_NULL_HANDLE) {
845 vkFreeMemory(
device, readback_memory,
nullptr);
850 VkBufferCreateInfo buffer_info{};
851 buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
852 buffer_info.size = image_bytes;
853 buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
854 buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
855 if (vkCreateBuffer(
device, &buffer_info,
nullptr, &readback_buffer) != VK_SUCCESS) {
856 throw mxvk::Exception(
"captureSnapshotPixels failed to create readback buffer");
859 VkMemoryRequirements memory_requirements{};
860 vkGetBufferMemoryRequirements(
device, readback_buffer, &memory_requirements);
862 VkPhysicalDeviceMemoryProperties memory_properties{};
863 vkGetPhysicalDeviceMemoryProperties(
physical_device, &memory_properties);
864 uint32_t memory_type_index = invalid_queue_index;
865 constexpr VkMemoryPropertyFlags required_properties =
866 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
867 for (uint32_t i = 0; i < memory_properties.memoryTypeCount; ++i) {
868 const bool type_matches = (memory_requirements.memoryTypeBits & (1U << i)) != 0U;
869 const bool properties_match =
870 (memory_properties.memoryTypes[i].propertyFlags & required_properties) == required_properties;
871 if (type_matches && properties_match) {
872 memory_type_index = i;
876 if (memory_type_index == invalid_queue_index) {
877 throw mxvk::Exception(
"captureSnapshotPixels failed to find host-visible coherent memory");
880 VkMemoryAllocateInfo allocation_info{};
881 allocation_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
882 allocation_info.allocationSize = memory_requirements.size;
883 allocation_info.memoryTypeIndex = memory_type_index;
884 if (vkAllocateMemory(
device, &allocation_info,
nullptr, &readback_memory) != VK_SUCCESS) {
885 throw mxvk::Exception(
"captureSnapshotPixels failed to allocate readback memory");
887 if (vkBindBufferMemory(
device, readback_buffer, readback_memory, 0) != VK_SUCCESS) {
888 throw mxvk::Exception(
"captureSnapshotPixels failed to bind readback memory");
891 VkCommandBufferAllocateInfo command_buffer_info{};
892 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
894 command_buffer_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
895 command_buffer_info.commandBufferCount = 1;
896 if (vkAllocateCommandBuffers(
device, &command_buffer_info, &command_buffer) != VK_SUCCESS) {
897 throw mxvk::Exception(
"captureSnapshotPixels failed to allocate command buffer");
900 VkFenceCreateInfo fence_info{};
901 fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
902 if (vkCreateFence(
device, &fence_info,
nullptr, ©_fence) != VK_SUCCESS) {
903 throw mxvk::Exception(
"captureSnapshotPixels failed to create copy fence");
907 if (source_fence != VK_NULL_HANDLE) {
908 const VkResult wait_result = vkWaitForFences(
device, 1, &source_fence, VK_TRUE, UINT64_MAX);
909 if (wait_result != VK_SUCCESS) {
910 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed waiting for rendered frame: {}",
static_cast<int>(wait_result)));
914 const VkResult idle_result = vkDeviceWaitIdle(
device);
915 if (idle_result != VK_SUCCESS) {
916 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed waiting for device idle: {}",
static_cast<int>(idle_result)));
919 VkCommandBufferBeginInfo begin_info{};
920 begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
921 begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
922 if (vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) {
923 throw mxvk::Exception(
"captureSnapshotPixels failed to begin copy command buffer");
926 VkImageMemoryBarrier2 to_transfer_barrier{};
927 to_transfer_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
928 to_transfer_barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
929 to_transfer_barrier.srcAccessMask = VK_ACCESS_2_NONE;
930 to_transfer_barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
931 to_transfer_barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
932 to_transfer_barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
933 to_transfer_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
934 to_transfer_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
935 to_transfer_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
937 to_transfer_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
938 to_transfer_barrier.subresourceRange.baseMipLevel = 0;
939 to_transfer_barrier.subresourceRange.levelCount = 1;
940 to_transfer_barrier.subresourceRange.baseArrayLayer = 0;
941 to_transfer_barrier.subresourceRange.layerCount = 1;
943 VkDependencyInfo to_transfer_dependency{};
944 to_transfer_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
945 to_transfer_dependency.imageMemoryBarrierCount = 1;
946 to_transfer_dependency.pImageMemoryBarriers = &to_transfer_barrier;
947 vkCmdPipelineBarrier2(command_buffer, &to_transfer_dependency);
949 VkBufferImageCopy copy_region{};
950 copy_region.bufferOffset = 0;
951 copy_region.bufferRowLength = 0;
952 copy_region.bufferImageHeight = 0;
953 copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
954 copy_region.imageSubresource.mipLevel = 0;
955 copy_region.imageSubresource.baseArrayLayer = 0;
956 copy_region.imageSubresource.layerCount = 1;
957 copy_region.imageOffset = {0, 0, 0};
959 vkCmdCopyImageToBuffer(command_buffer,
961 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
966 VkImageMemoryBarrier2 to_present_barrier{};
967 to_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
968 to_present_barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
969 to_present_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT;
970 to_present_barrier.dstStageMask = VK_PIPELINE_STAGE_2_NONE;
971 to_present_barrier.dstAccessMask = VK_ACCESS_2_NONE;
972 to_present_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
973 to_present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
974 to_present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
975 to_present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
977 to_present_barrier.subresourceRange = to_transfer_barrier.subresourceRange;
979 VkDependencyInfo to_present_dependency{};
980 to_present_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
981 to_present_dependency.imageMemoryBarrierCount = 1;
982 to_present_dependency.pImageMemoryBarriers = &to_present_barrier;
983 vkCmdPipelineBarrier2(command_buffer, &to_present_dependency);
985 if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) {
986 throw mxvk::Exception(
"captureSnapshotPixels failed to end copy command buffer");
989 VkCommandBufferSubmitInfo command_submit_info{};
990 command_submit_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
991 command_submit_info.commandBuffer = command_buffer;
993 VkSubmitInfo2 submit_info{};
994 submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
995 submit_info.commandBufferInfoCount = 1;
996 submit_info.pCommandBufferInfos = &command_submit_info;
998 const VkResult submit_result = vkQueueSubmit2(
graphics_queue, 1, &submit_info, copy_fence);
999 if (submit_result != VK_SUCCESS) {
1000 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed to submit copy command: {}",
static_cast<int>(submit_result)));
1002 const VkResult copy_wait_result = vkWaitForFences(
device, 1, ©_fence, VK_TRUE, UINT64_MAX);
1003 if (copy_wait_result != VK_SUCCESS) {
1004 throw mxvk::Exception(std::format(
"captureSnapshotPixels failed waiting for copy: {}",
static_cast<int>(copy_wait_result)));
1007 void *mapped =
nullptr;
1008 if (vkMapMemory(
device, readback_memory, 0, image_bytes, 0, &mapped) != VK_SUCCESS) {
1009 throw mxvk::Exception(
"captureSnapshotPixels failed to map readback memory");
1012 const auto *src =
static_cast<const std::uint8_t *
>(mapped);
1013 rgba_pixels.resize(
static_cast<std::size_t
>(image_bytes));
1014 for (std::size_t i = 0; i < rgba_pixels.size(); i += 4U) {
1015 if (format_is_bgra) {
1016 rgba_pixels[i + 0U] = src[i + 2U];
1017 rgba_pixels[i + 1U] = src[i + 1U];
1018 rgba_pixels[i + 2U] = src[i + 0U];
1019 rgba_pixels[i + 3U] = src[i + 3U];
1021 rgba_pixels[i + 0U] = src[i + 0U];
1022 rgba_pixels[i + 1U] = src[i + 1U];
1023 rgba_pixels[i + 2U] = src[i + 2U];
1024 rgba_pixels[i + 3U] = src[i + 3U];
1027 vkUnmapMemory(
device, readback_memory);
1042 if (
device == VK_NULL_HANDLE ||
command_pool == VK_NULL_HANDLE || vkTrimCommandPool ==
nullptr) {
1049 void VK_Window::maybeTrimMemory() {
1050 const auto now = std::chrono::steady_clock::now();
1060 std::cout << std::format(
"mxvk: entering initWindow (title='{}', width={}, height={})\n", title, width, height);
1061 if (width <= 0 || height <= 0) {
1062 std::cerr <<
"mxvk: Window dimensions must be positive\n";
1067 constexpr SDL_InitFlags sdlInitFlags = SDL_INIT_VIDEO | SDL_INIT_GAMEPAD;
1068 std::cout <<
"SDL3: initializing video/gamepad subsystems\n";
1069 if (!SDL_Init(sdlInitFlags)) {
1070 std::cerr <<
"mxvk: Error on SDL init: " << SDL_GetError() <<
"\n";
1074 SDL_SetGamepadEventsEnabled(
true);
1075 SDL_SetJoystickEventsEnabled(
true);
1076 std::cout <<
"SDL3: video/gamepad subsystems initialized\n";
1079 const bool fullscreen = (flags & SDL_WINDOW_FULLSCREEN) != 0;
1080 const SDL_DisplayMode *fullscreen_mode =
nullptr;
1082 const SDL_DisplayID display = SDL_GetPrimaryDisplay();
1083 fullscreen_mode = display != 0 ? SDL_GetCurrentDisplayMode(display) :
nullptr;
1084 if (fullscreen_mode !=
nullptr && fullscreen_mode->w > 0 && fullscreen_mode->h > 0) {
1085 width = fullscreen_mode->w;
1086 height = fullscreen_mode->h;
1087 std::cout << std::format(
"SDL3: using current display mode for fullscreen window: {}x{}\n", width, height);
1091 std::cout <<
"SDL3: creating SDL window with Vulkan capability\n";
1092 SDL_Window *raw_window = SDL_CreateWindow(title.c_str(), width, height, flags);
1093 if (raw_window ==
nullptr) {
1094 std::cerr << std::format(
"Error creating window: {}\n", SDL_GetError());
1095 std::cout <<
"SDL3: rolling back SDL video/gamepad subsystems after window creation failure\n";
1096 SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD);
1101 window.reset(raw_window);
1102 if (fullscreen && fullscreen_mode !=
nullptr && !SDL_SetWindowFullscreenMode(
window.get(), fullscreen_mode)) {
1103 std::cerr << std::format(
"mxvk: SDL_SetWindowFullscreenMode failed: {}\n", SDL_GetError());
1105 std::cout <<
"SDL3: showing created window\n";
1106 SDL_ShowWindow(
window.get());
1107 if (fullscreen && !SDL_SyncWindow(
window.get())) {
1108 std::cerr << std::format(
"mxvk: SDL_SyncWindow failed after fullscreen show: {}\n", SDL_GetError());
1111 for (
int attempt = 0; attempt < 50; ++attempt) {
1112 int pixel_width = 0;
1113 int pixel_height = 0;
1114 SDL_GetWindowSizeInPixels(
window.get(), &pixel_width, &pixel_height);
1115 if (pixel_width == width && pixel_height == height) {
1122 std::cout <<
"mxvk: initWindow complete\n";
1139 [[maybe_unused]] VkRenderingAttachmentInfo &stencil_attachment,
1140 [[maybe_unused]] uint32_t image_index) {}
1150 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1162 std::vector<VK_Sprite *> attachedSprites;
1163 attachedSprites.reserve(effects.size());
1165 if (effect.fragmentShaderPath.empty()) {
1166 throw mxvk::Exception(
"Cannot attach post-processing shader with an empty fragment shader path");
1173 effect.fragmentShaderPath);
1174 const uint32_t black_pixel = 0xFF000000u;
1176 sprite->
setShaderParams(effect.params[0], effect.params[1], effect.params[2], effect.params[3]);
1178 attachedSprites.push_back(sprite);
1192 createPostProcessTargets();
1195 return attachedSprites;
1200 vkDeviceWaitIdle(
device);
1204 destroyPostProcessTargets();
1213 [&sprites_to_remove](
const std::unique_ptr<VK_Sprite> &sprite) {
1214 return std::ranges::find(sprites_to_remove, sprite.get()) != sprites_to_remove.end();
1274 if (sprites_to_remove.empty()) {
1281 [&sprites_to_remove](
const std::unique_ptr<VK_Sprite> &existing_sprite) {
1282 return std::ranges::find(sprites_to_remove, existing_sprite.get()) != sprites_to_remove.end();
1290 post_process_sprites = sprite ==
nullptr ? std::vector<VK_Sprite *>{} : std::vector<VK_Sprite *>{sprite};
1295 createPostProcessTargets();
1299 bool VK_Window::isPostProcessSprite(
const VK_Sprite *sprite)
const {
1303 void VK_Window::destroyPostProcessTargets() {
1305 if (sprite !=
nullptr) {
1310 for (VkImageView view : views) {
1311 if (view != VK_NULL_HANDLE) {
1312 vkDestroyImageView(
device, view,
nullptr);
1317 for (VkImage image : images) {
1318 if (image != VK_NULL_HANDLE) {
1319 vkDestroyImage(
device, image,
nullptr);
1324 for (VkDeviceMemory memory : memories) {
1325 if (memory != VK_NULL_HANDLE) {
1326 vkFreeMemory(
device, memory,
nullptr);
1336 void VK_Window::createPostProcessTargets() {
1337 destroyPostProcessTargets();
1346 VkPhysicalDeviceMemoryProperties memory_properties{};
1347 vkGetPhysicalDeviceMemoryProperties(
physical_device, &memory_properties);
1349 for (
size_t target = 0; target < target_count; ++target) {
1351 VkImageCreateInfo image_info{};
1352 image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1353 image_info.imageType = VK_IMAGE_TYPE_2D;
1355 image_info.mipLevels = 1;
1356 image_info.arrayLayers = 1;
1358 image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
1359 image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1360 image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
1361 image_info.samples = VK_SAMPLE_COUNT_1_BIT;
1362 image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1364 throw mxvk::Exception(
"Failed to create post-process image");
1366 VkMemoryRequirements requirements{};
1368 uint32_t memory_type = UINT32_MAX;
1369 for (uint32_t type = 0; type < memory_properties.memoryTypeCount; ++type) {
1370 if ((requirements.memoryTypeBits & (1U << type)) != 0U &&
1371 (memory_properties.memoryTypes[type].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0U) {
1376 if (memory_type == UINT32_MAX) {
1377 throw mxvk::Exception(
"Failed to find post-process image memory type");
1379 VkMemoryAllocateInfo allocation{};
1380 allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1381 allocation.allocationSize = requirements.size;
1382 allocation.memoryTypeIndex = memory_type;
1385 throw mxvk::Exception(
"Failed to allocate post-process image memory");
1387 VkImageViewCreateInfo view_info{};
1388 view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1390 view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
1392 view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1393 view_info.subresourceRange.levelCount = 1;
1394 view_info.subresourceRange.layerCount = 1;
1396 throw mxvk::Exception(
"Failed to create post-process image view");
1401 destroyPostProcessTargets();
1407 const auto sync_ready = [
this]() {
1414 if (
device == VK_NULL_HANDLE) {
1428 std::cout <<
"mxvk: entering pickDevice\n";
1430 std::cout <<
"vk: cannot pick device because instance or surface is missing\n";
1434 uint32_t device_count = 0;
1435 std::cout <<
"vk: enumerating physical devices\n";
1436 vkEnumeratePhysicalDevices(
instance, &device_count,
nullptr);
1437 if (device_count == 0U) {
1438 std::cout <<
"vk: no physical devices found\n";
1441 std::cout << std::format(
"vk: found {} physical device(s)\n", device_count);
1443 std::vector<VkPhysicalDevice> devices(device_count);
1444 vkEnumeratePhysicalDevices(
instance, &device_count, devices.data());
1446 for (
const VkPhysicalDevice candidate : devices) {
1447 std::cout <<
"vk: evaluating candidate physical device\n";
1448 uint32_t queue_family_count = 0;
1449 vkGetPhysicalDeviceQueueFamilyProperties(candidate, &queue_family_count,
nullptr);
1450 std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
1451 vkGetPhysicalDeviceQueueFamilyProperties(candidate, &queue_family_count, queue_families.data());
1453 uint32_t candidate_graphics = invalid_queue_index;
1454 uint32_t candidate_present = invalid_queue_index;
1456 for (uint32_t i = 0; i < queue_family_count; ++i) {
1457 if ((queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0U) {
1458 candidate_graphics = i;
1461 VkBool32 present_support = VK_FALSE;
1462 vkGetPhysicalDeviceSurfaceSupportKHR(candidate, i,
surface, &present_support);
1463 if (present_support == VK_TRUE) {
1464 candidate_present = i;
1468 if (candidate_graphics == invalid_queue_index || candidate_present == invalid_queue_index) {
1469 std::cout <<
"vk: candidate rejected due to missing graphics/present queue support\n";
1473 const SwapchainSupport swapchain_support = querySwapchainSupport(candidate,
surface);
1474 if (swapchain_support.formats.empty() || swapchain_support.present_modes.empty()) {
1475 std::cout <<
"vk: candidate rejected due to incomplete swapchain support\n";
1479 VkPhysicalDeviceProperties properties{};
1480 vkGetPhysicalDeviceProperties(candidate, &properties);
1482 const char *device_type =
"unknown";
1483 switch (properties.deviceType) {
1484 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
1485 device_type =
"integrated";
1487 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
1488 device_type =
"discrete";
1490 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
1491 device_type =
"virtual";
1493 case VK_PHYSICAL_DEVICE_TYPE_CPU:
1494 device_type =
"cpu";
1503 std::cout << std::format(
1504 "vk: selected GPU='{}' type={} vendor=0x{:04x} device=0x{:04x}\n",
1505 properties.deviceName,
1507 properties.vendorID,
1508 properties.deviceID);
1512 std::cout <<
"vk: no suitable physical device selected\n";
1515 std::cout <<
"mxvk: entering createLogicalDevice\n";
1517 std::cout <<
"vk: cannot create logical device without a selected physical device\n";
1521 std::vector<uint32_t> queue_families{};
1527 constexpr float queue_priority = 1.0f;
1528 std::vector<VkDeviceQueueCreateInfo> queue_create_infos{};
1529 queue_create_infos.reserve(queue_families.size());
1530 for (
const uint32_t family : queue_families) {
1531 VkDeviceQueueCreateInfo queue_create_info{};
1532 queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1533 queue_create_info.queueFamilyIndex = family;
1534 queue_create_info.queueCount = 1;
1535 queue_create_info.pQueuePriorities = &queue_priority;
1536 queue_create_infos.push_back(queue_create_info);
1538 uint32_t device_extension_count = 0;
1539 vkEnumerateDeviceExtensionProperties(
physical_device,
nullptr, &device_extension_count,
nullptr);
1540 std::vector<VkExtensionProperties> device_extensions(device_extension_count);
1541 if (device_extension_count > 0U) {
1542 vkEnumerateDeviceExtensionProperties(
physical_device,
nullptr, &device_extension_count, device_extensions.data());
1545 [[maybe_unused]]
const auto has_device_extension = [&device_extensions](
const char *extension_name) {
1546 return std::ranges::any_of(
1548 [extension_name](
const VkExtensionProperties &ext) {
1549 return std::strcmp(ext.extensionName, extension_name) == 0;
1553 std::vector<const char *> required_device_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
1554#if defined(MXVK_CUDA) && defined(__linux__)
1555 if (has_device_extension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME)) {
1556 if (has_device_extension(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME)) {
1557 required_device_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME);
1559 required_device_extensions.push_back(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME);
1560 std::cout <<
"vk: enabling external memory FD support for CUDA interop\n";
1562 std::cout <<
"vk: external memory FD support unavailable; CUDA texture interop will fall back\n";
1566#if defined(MXVK_USE_MOLTENVK)
1567 const bool has_portability_subset = std::ranges::any_of(
1569 [](
const VkExtensionProperties &ext) {
1570 return std::strcmp(ext.extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0;
1573 if (has_portability_subset) {
1574 required_device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
1577 VkPhysicalDeviceFeatures2 features2{};
1578 features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
1579 VkPhysicalDeviceVulkan13Features supported_vulkan13_features{};
1580 supported_vulkan13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
1581 features2.pNext = &supported_vulkan13_features;
1584 std::cout << std::format(
1585 "vk: feature support - synchronization2={}, dynamicRendering={}, shaderFloat64={}, fillModeNonSolid={}, samplerAnisotropy={}\n",
1586 supported_vulkan13_features.synchronization2 == VK_TRUE ?
"true" :
"false",
1587 supported_vulkan13_features.dynamicRendering == VK_TRUE ?
"true" :
"false",
1588 features2.features.shaderFloat64 == VK_TRUE ?
"true" :
"false",
1589 features2.features.fillModeNonSolid == VK_TRUE ?
"true" :
"false",
1590 features2.features.samplerAnisotropy == VK_TRUE ?
"true" :
"false");
1592 if (supported_vulkan13_features.synchronization2 != VK_TRUE) {
1593 std::cout <<
"vk: synchronization2 is unsupported on selected physical device\n";
1596 if (supported_vulkan13_features.dynamicRendering != VK_TRUE) {
1597 std::cout <<
"vk: dynamic rendering is unsupported on selected physical device\n";
1600 VkPhysicalDeviceVulkan13Features vulkan13_features{};
1601 vulkan13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
1602 vulkan13_features.synchronization2 = VK_TRUE;
1603 vulkan13_features.dynamicRendering = VK_TRUE;
1605 VkPhysicalDeviceFeatures2 enabled_features2{};
1606 enabled_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
1607 enabled_features2.features.shaderFloat64 = features2.features.shaderFloat64;
1608 enabled_features2.features.fillModeNonSolid = features2.features.fillModeNonSolid;
1609 enabled_features2.features.samplerAnisotropy = features2.features.samplerAnisotropy;
1611 enabled_features2.pNext = &vulkan13_features;
1613 VkDeviceCreateInfo create_info{};
1614 create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1615 create_info.pNext = &enabled_features2;
1616 create_info.queueCreateInfoCount =
static_cast<uint32_t
>(queue_create_infos.size());
1617 create_info.pQueueCreateInfos = queue_create_infos.data();
1618 create_info.enabledExtensionCount =
static_cast<uint32_t
>(required_device_extensions.size());
1619 create_info.ppEnabledExtensionNames = required_device_extensions.data();
1620 create_info.enabledLayerCount = 0;
1621 create_info.ppEnabledLayerNames =
nullptr;
1622 create_info.pEnabledFeatures =
nullptr;
1624 std::cout <<
"vk: creating logical device\n";
1626 std::cout <<
"vk: logical device creation failed\n";
1631 std::cout <<
"vk: loading device-level function pointers via volk\n";
1633 if (vkQueueSubmit2 ==
nullptr || vkCmdBeginRendering ==
nullptr || vkCmdEndRendering ==
nullptr || vkCmdPipelineBarrier2 ==
nullptr) {
1634 std::cout <<
"vk: required dynamic rendering/synchronization function pointers are unavailable\n";
1635 vkDestroyDevice(
device,
nullptr);
1639 std::cout <<
"vk: retrieving graphics and present queues\n";
1642 std::cout <<
"mxvk: createLogicalDevice complete\n";
1646 std::cout <<
"mxvk: entering createDevice\n";
1647 if (
device == VK_NULL_HANDLE) {
1648 std::cout <<
"vk: skipping createDevice because logical device is null\n";
1652 const bool sync_ready =
1660 std::cout <<
"vk: createDevice skipped because render resources are already initialized\n";
1664 std::cout <<
"vk: creating swapchain\n";
1665 if (!createSwapchain(VK_NULL_HANDLE)) {
1666 std::cout <<
"vk: createSwapchain failed\n";
1670 std::cout <<
"vk: creating render resources\n";
1671 if (!createRenderResources()) {
1672 std::cout <<
"vk: createRenderResources failed\n";
1676 std::cout <<
"vk: creating synchronization objects\n";
1677 if (!createSyncObjects()) {
1678 std::cout <<
"vk: createSyncObjects failed\n";
1682 std::cout <<
"mxvk: createDevice complete\n";
1685 bool VK_Window::createSwapchain(VkSwapchainKHR old_swapchain) {
1686 std::cout <<
"vk: entering createSwapchain\n";
1688 if (support.formats.empty() || support.present_modes.empty()) {
1689 std::cout <<
"vk: cannot create swapchain because support is incomplete\n";
1693 const VkSurfaceFormatKHR surface_format = chooseSurfaceFormat(support.formats);
1694 const VkPresentModeKHR present_mode = choosePresentMode(support.present_modes);
1695 auto clampedWindowPixelExtent = [
this, &support]() -> std::optional<VkExtent2D> {
1697 return std::nullopt;
1701 SDL_GetWindowSizeInPixels(
window.get(), &pixel_w, &pixel_h);
1702 if (pixel_w <= 0 || pixel_h <= 0) {
1703 return std::nullopt;
1706 .width = std::clamp(
static_cast<uint32_t
>(pixel_w),
1707 support.capabilities.minImageExtent.width,
1708 support.capabilities.maxImageExtent.width),
1709 .height = std::clamp(
static_cast<uint32_t
>(pixel_h),
1710 support.capabilities.minImageExtent.height,
1711 support.capabilities.maxImageExtent.height),
1714 auto extentMatchesWindowPixels = [&clampedWindowPixelExtent](
const VkExtent2D extent) {
1715 const std::optional<VkExtent2D> target_extent = clampedWindowPixelExtent();
1716 return !target_extent.has_value() ||
1717 (extent.width == target_extent->width && extent.height == target_extent->height);
1720 VkExtent2D extent = chooseExtent(support.capabilities,
window.get());
1721 for (
int attempt = 0; attempt < 50 && !extentMatchesWindowPixels(extent); ++attempt) {
1725 extent = chooseExtent(support.capabilities,
window.get());
1727 if (!extentMatchesWindowPixels(extent)) {
1728 const std::optional<VkExtent2D> target_extent = clampedWindowPixelExtent();
1729 if (target_extent.has_value()) {
1730 std::cout << std::format(
"vk: delaying swapchain creation until surface extent catches up to window pixels (surface={}x{}, window={}x{})\n",
1733 target_extent->width,
1734 target_extent->height);
1739 uint32_t image_count = support.capabilities.minImageCount + 1;
1740 if (support.capabilities.maxImageCount > 0U && image_count > support.capabilities.maxImageCount) {
1741 image_count = support.capabilities.maxImageCount;
1744 VkSwapchainCreateInfoKHR create_info{};
1745 create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
1746 create_info.surface =
surface;
1747 create_info.minImageCount = image_count;
1748 create_info.imageFormat = surface_format.format;
1749 create_info.imageColorSpace = surface_format.colorSpace;
1750 create_info.imageExtent = extent;
1751 create_info.imageArrayLayers = 1;
1752 create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1755 create_info.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
1757 std::cerr <<
"mxvk: swapchain does not support transfer-source usage; saveSnapshot is unavailable\n";
1762 create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
1763 create_info.queueFamilyIndexCount = 2;
1764 create_info.pQueueFamilyIndices = queue_family_indices;
1766 create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
1767 create_info.queueFamilyIndexCount = 0;
1768 create_info.pQueueFamilyIndices =
nullptr;
1771 create_info.preTransform = support.capabilities.currentTransform;
1772 create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
1773 create_info.presentMode = present_mode;
1774 create_info.clipped = VK_TRUE;
1775 create_info.oldSwapchain = old_swapchain;
1777 std::cout <<
"vk: creating swapchain object\n";
1778 if (vkCreateSwapchainKHR(
device, &create_info,
nullptr, &
swapchain) != VK_SUCCESS) {
1783 std::cout <<
"vk: querying swapchain images\n";
1795 VkImageViewCreateInfo view_info{};
1796 view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1798 view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
1800 view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
1801 view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
1802 view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
1803 view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
1804 view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1805 view_info.subresourceRange.baseMipLevel = 0;
1806 view_info.subresourceRange.levelCount = 1;
1807 view_info.subresourceRange.baseArrayLayer = 0;
1808 view_info.subresourceRange.layerCount = 1;
1810 std::cout << std::format(
"vk: creating image view for swapchain image {}\n", i);
1813 if (image_view != VK_NULL_HANDLE) {
1814 vkDestroyImageView(
device, image_view,
nullptr);
1830 std::cout <<
"vk: createSwapchain complete\n";
1834 bool VK_Window::createRenderResources() {
1835 std::cout <<
"vk: entering createRenderResources\n";
1836 const auto cleanup_render_resource_failure = [
this]() {
1843 if (view != VK_NULL_HANDLE) {
1844 vkDestroyImageView(
device, view,
nullptr);
1845 view = VK_NULL_HANDLE;
1851 if (image != VK_NULL_HANDLE) {
1852 vkDestroyImage(
device, image,
nullptr);
1853 image = VK_NULL_HANDLE;
1859 if (memory != VK_NULL_HANDLE) {
1860 vkFreeMemory(
device, memory,
nullptr);
1861 memory = VK_NULL_HANDLE;
1870 destroyPostProcessTargets();
1874 VkCommandPoolCreateInfo pool_info{};
1875 pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1876 pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
1879 std::cout <<
"vk: creating command pool\n";
1886 std::cout <<
"vk: freeing stale command buffers before reallocation\n";
1892 VkCommandBufferAllocateInfo alloc_info{};
1893 alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1895 alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1896 alloc_info.commandBufferCount =
static_cast<uint32_t
>(
command_buffers.size());
1898 std::cout <<
"vk: allocating command buffers\n";
1904 auto findDepthFormat = [&](VkPhysicalDevice gpu) -> VkFormat {
1905 const std::array<VkFormat, 2> candidates = {
1906 VK_FORMAT_D32_SFLOAT,
1907 VK_FORMAT_D16_UNORM,
1910 for (
const VkFormat format : candidates) {
1911 VkFormatProperties props{};
1912 vkGetPhysicalDeviceFormatProperties(gpu, format, &props);
1913 if ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0U) {
1918 return VK_FORMAT_UNDEFINED;
1923 std::cerr <<
"mxvk: failed to find a supported depth format\n";
1924 cleanup_render_resource_failure();
1928 depth_images.resize(max_frames_in_flight, VK_NULL_HANDLE);
1934 std::cout << std::format(
"vk: creating depth image for frame {}\n", i);
1935 VkImageCreateInfo imageInfo{};
1936 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1937 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1940 imageInfo.extent.depth = 1;
1941 imageInfo.mipLevels = 1;
1942 imageInfo.arrayLayers = 1;
1944 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1945 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1946 imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
1947 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1948 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1951 std::cerr << std::format(
"mxvk: failed to create depth image {}\n", i);
1952 cleanup_render_resource_failure();
1956 VkMemoryRequirements memReq{};
1959 VkPhysicalDeviceMemoryProperties memProps{};
1961 uint32_t memoryTypeIndex = UINT32_MAX;
1962 for (uint32_t t = 0; t < memProps.memoryTypeCount; ++t) {
1963 if (((memReq.memoryTypeBits & (1u << t)) != 0U) &&
1964 ((memProps.memoryTypes[t].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0U)) {
1965 memoryTypeIndex = t;
1969 if (memoryTypeIndex == UINT32_MAX) {
1970 std::cerr <<
"mxvk: failed to find depth image memory type\n";
1971 cleanup_render_resource_failure();
1975 std::cout << std::format(
"vk: allocating depth image memory for frame {}\n", i);
1976 VkMemoryAllocateInfo allocInfo{};
1977 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1978 allocInfo.allocationSize = memReq.size;
1979 allocInfo.memoryTypeIndex = memoryTypeIndex;
1982 std::cerr << std::format(
"mxvk: failed to allocate depth image memory {}\n", i);
1983 cleanup_render_resource_failure();
1988 std::cerr << std::format(
"mxvk: failed to bind depth image memory {}\n", i);
1989 cleanup_render_resource_failure();
1993 std::cout << std::format(
"vk: creating depth image view for frame {}\n", i);
1994 VkImageViewCreateInfo viewInfo{};
1995 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1997 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1999 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
2000 viewInfo.subresourceRange.baseMipLevel = 0;
2001 viewInfo.subresourceRange.levelCount = 1;
2002 viewInfo.subresourceRange.baseArrayLayer = 0;
2003 viewInfo.subresourceRange.layerCount = 1;
2006 std::cerr << std::format(
"mxvk: failed to create depth image view {}\n", i);
2007 cleanup_render_resource_failure();
2014 createPostProcessTargets();
2015 }
catch (
const mxvk::Exception &ex) {
2016 std::cerr << std::format(
"mxvk: {}\n", ex.
text());
2017 cleanup_render_resource_failure();
2022 std::cout <<
"vk: createRenderResources complete\n";
2026 bool VK_Window::createSyncObjects() {
2027 std::cout <<
"vk: entering createSyncObjects\n";
2029 std::cerr <<
"mxvk: cannot create sync objects without swapchain images\n";
2033 cleanupSyncObjects();
2035 VkSemaphoreCreateInfo semaphore_info{};
2036 semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
2038 VkFenceCreateInfo fence_info{};
2039 fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
2040 fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
2044 for (uint32_t frame = 0; frame < max_frames_in_flight; ++frame) {
2045 std::cout << std::format(
"vk: creating image-available semaphore for frame {}\n", frame);
2047 cleanupSyncObjects();
2051 std::cout << std::format(
"vk: creating in-flight fence for frame {}\n", frame);
2053 cleanupSyncObjects();
2058 for (
size_t image_index = 0; image_index <
render_finished.size(); ++image_index) {
2059 std::cout << std::format(
"vk: creating render-finished semaphore for swapchain image {}\n", image_index);
2060 if (vkCreateSemaphore(
device, &semaphore_info,
nullptr, &
render_finished[image_index]) != VK_SUCCESS) {
2061 cleanupSyncObjects();
2069 std::cout <<
"vk: createSyncObjects complete\n";
2073 void VK_Window::cleanupSyncObjects() {
2074 if (
device == VK_NULL_HANDLE) {
2078 const bool has_in_flight_fences = std::ranges::any_of(
2081 [](VkFence fence) { return fence != VK_NULL_HANDLE; });
2082 const bool has_render_finished = std::ranges::any_of(
2085 [](VkSemaphore semaphore) { return semaphore != VK_NULL_HANDLE; });
2086 const bool has_image_available = std::ranges::any_of(
2089 [](VkSemaphore semaphore) { return semaphore != VK_NULL_HANDLE; });
2091 if (!has_in_flight_fences && !has_render_finished && !has_image_available) {
2097 if (has_in_flight_fences) {
2098 std::cout <<
"vk: destroying in-flight fences\n";
2100 if (fence != VK_NULL_HANDLE) {
2101 vkDestroyFence(
device, fence,
nullptr);
2102 fence = VK_NULL_HANDLE;
2107 if (has_render_finished) {
2108 std::cout <<
"vk: destroying render-finished semaphores\n";
2110 if (semaphore != VK_NULL_HANDLE) {
2111 vkDestroySemaphore(
device, semaphore,
nullptr);
2112 semaphore = VK_NULL_HANDLE;
2117 if (has_image_available) {
2118 std::cout <<
"vk: destroying image-available semaphores\n";
2120 if (semaphore != VK_NULL_HANDLE) {
2121 vkDestroySemaphore(
device, semaphore,
nullptr);
2122 semaphore = VK_NULL_HANDLE;
2132 void VK_Window::recreateSwapchain() {
2138 SDL_GetWindowSizeInPixels(
window.get(), &pixel_w, &pixel_h);
2139 VkSurfaceCapabilitiesKHR surface_capabilities{};
2142 VkExtent2D new_extent{};
2144 if (surface_capabilities.currentExtent.width != 0xFFFFFFFFU) {
2145 new_extent = surface_capabilities.currentExtent;
2147 new_extent.width = std::clamp(
static_cast<uint32_t
>(pixel_w),
2148 surface_capabilities.minImageExtent.width,
2149 surface_capabilities.maxImageExtent.width);
2151 new_extent.height = std::clamp(
static_cast<uint32_t
>(pixel_h),
2152 surface_capabilities.minImageExtent.height,
2153 surface_capabilities.maxImageExtent.height);
2156 if (new_extent.width == 0 || new_extent.height == 0) {
2160 const uint32_t target_w = std::clamp(
static_cast<uint32_t
>(pixel_w),
2161 surface_capabilities.minImageExtent.width,
2162 surface_capabilities.maxImageExtent.width);
2163 const uint32_t target_h = std::clamp(
static_cast<uint32_t
>(pixel_h),
2164 surface_capabilities.minImageExtent.height,
2165 surface_capabilities.maxImageExtent.height);
2167 if (new_extent.width != target_w || new_extent.height != target_h) {
2181 std::cout << std::format(
"mxvk: recreating swapchain for {}x{} window\n", new_extent.width, new_extent.height);
2182 vkDeviceWaitIdle(
device);
2185 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2193 destroySpritePipeline();
2194 destroyTextPipeline();
2195 cleanupSyncObjects();
2196 VkSwapchainKHR old_swapchain =
swapchain;
2197 cleanupSwapchain(
false);
2198 if (!createSwapchain(old_swapchain) || !createRenderResources() || !createSyncObjects()) {
2199 std::cerr <<
"mxvk: failed to recreate swapchain after resize\n";
2200 if (old_swapchain != VK_NULL_HANDLE) {
2206 if (old_swapchain != VK_NULL_HANDLE) {
2211 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2216 for (
const std::unique_ptr<VK_Sprite3D> &sprite :
sprites3d) {
2218 sprite->resize(
this);
2225 std::cout <<
"mxvk: swapchain recreation complete\n";
2228 void VK_Window::cleanupSwapchain(
bool destroy_swapchain_handle) {
2229 std::cout <<
"vk: entering cleanupSwapchain\n";
2230 if (
device == VK_NULL_HANDLE) {
2234 destroyPostProcessTargets();
2237 std::cout <<
"vk: destroying swapchain image views\n";
2239 if (image_view != VK_NULL_HANDLE) {
2240 vkDestroyImageView(
device, image_view,
nullptr);
2247 std::cout <<
"vk: destroying depth image views\n";
2249 if (view != VK_NULL_HANDLE) {
2250 vkDestroyImageView(
device, view,
nullptr);
2257 std::cout <<
"vk: destroying depth images\n";
2259 if (image != VK_NULL_HANDLE) {
2260 vkDestroyImage(
device, image,
nullptr);
2267 std::cout <<
"vk: freeing depth image memory\n";
2269 if (memory != VK_NULL_HANDLE) {
2270 vkFreeMemory(
device, memory,
nullptr);
2284 if (destroy_swapchain_handle &&
swapchain != VK_NULL_HANDLE) {
2285 std::cout <<
"vk: destroying swapchain\n";
2289 std::cout <<
"vk: cleanupSwapchain complete\n";
2292 void VK_Window::drawFrame() {
2293 if (
device == VK_NULL_HANDLE) {
2300 SDL_GetWindowSizeInPixels(
window.get(), &pixel_w, &pixel_h);
2302 if (pixel_w <= 0 || pixel_h <= 0) {
2310 std::cout << std::format(
"mxvk: requesting swapchain recreation because window pixels changed from {}x{} to {}x{}\n",
2322 std::cout <<
"mxvk: creating deferred swapchain/render/sync resources\n";
2324 std::cerr <<
"mxvk: deferred resource creation failed; skipping frame\n";
2329 const bool sync_ready =
2338 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2349 createSpritePipeline();
2350 }
catch (
const std::exception &ex) {
2351 std::cerr << std::format(
"mxvk: sprite pipeline build skipped: {}\n", ex.what());
2363 createTextPipeline();
2364 }
catch (
const std::exception &ex) {
2365 std::cerr << std::format(
"mxvk: text pipeline build skipped: {}\n", ex.what());
2372 const size_t depth_slot =
static_cast<size_t>(
current_frame);
2374 const VkResult wait_result = vkWaitForFences(
device, 1, &frame_fence, VK_TRUE, UINT64_MAX);
2375 if (wait_result == VK_ERROR_DEVICE_LOST) {
2376 std::cerr <<
"mxvk: device lost while waiting for frame fence; stopping render loop\n";
2380 if (wait_result != VK_SUCCESS) {
2381 std::cerr << std::format(
"mxvk: Failed waiting for frame fence (VkResult={})\n",
static_cast<int>(wait_result));
2386 uint32_t image_index = 0;
2387 const uint64_t acquire_timeout_ns = 100000000ULL;
2388 const VkResult acquire_result =
2389 vkAcquireNextImageKHR(
device,
swapchain, acquire_timeout_ns, acquire_semaphore, VK_NULL_HANDLE, &image_index);
2391 static VkResult last_acquire_error = VK_SUCCESS;
2392 static uint32_t repeated_acquire_errors = 0;
2394 if (acquire_result == VK_ERROR_OUT_OF_DATE_KHR) {
2395 last_acquire_error = VK_SUCCESS;
2396 repeated_acquire_errors = 0;
2397 std::cout <<
"mxvk: requesting swapchain recreation because acquire returned VK_ERROR_OUT_OF_DATE_KHR\n";
2403 if (acquire_result == VK_ERROR_SURFACE_LOST_KHR) {
2404 if (last_acquire_error != acquire_result) {
2405 std::cerr <<
"mxvk: swapchain surface lost during acquire; requesting swapchain recreation\n";
2406 last_acquire_error = acquire_result;
2407 repeated_acquire_errors = 0;
2409 std::cout <<
"mxvk: requesting swapchain recreation because acquire returned VK_ERROR_SURFACE_LOST_KHR\n";
2415 if (acquire_result == VK_TIMEOUT || acquire_result == VK_NOT_READY) {
2420 if (acquire_result != VK_SUCCESS && acquire_result != VK_SUBOPTIMAL_KHR) {
2421 if (last_acquire_error == acquire_result) {
2422 ++repeated_acquire_errors;
2423 if ((repeated_acquire_errors % 120U) == 0U) {
2424 std::cerr << std::format(
2425 "mxvk: repeated swapchain acquire failures continue (VkResult={})\n",
2426 static_cast<int>(acquire_result));
2429 last_acquire_error = acquire_result;
2430 repeated_acquire_errors = 0;
2431 std::cerr << std::format(
2432 "mxvk: Failed to acquire swapchain image (VkResult={})\n",
2433 static_cast<int>(acquire_result));
2436 if (acquire_result == VK_ERROR_DEVICE_LOST) {
2437 std::cerr <<
"mxvk: device lost; stopping render loop\n";
2443 last_acquire_error = VK_SUCCESS;
2444 repeated_acquire_errors = 0;
2451 std::cerr <<
"mxvk: acquired image index exceeds tracked in-flight image count\n";
2456 std::cerr <<
"mxvk: acquired image index exceeds render-finished semaphore count\n";
2463 const VkResult image_wait_result = vkWaitForFences(
device, 1, &
image_fences[image_index], VK_TRUE, UINT64_MAX);
2464 if (image_wait_result == VK_ERROR_DEVICE_LOST) {
2465 std::cerr <<
"mxvk: device lost while waiting on acquired image fence; stopping render loop\n";
2469 if (image_wait_result != VK_SUCCESS) {
2470 std::cerr << std::format(
"mxvk: Failed waiting for acquired image fence (VkResult={})\n",
static_cast<int>(image_wait_result));
2477 vkResetCommandBuffer(cmd, 0);
2479 VkCommandBufferBeginInfo begin_info{};
2480 begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2481 if (vkBeginCommandBuffer(cmd, &begin_info) != VK_SUCCESS) {
2482 std::cerr <<
"mxvk: Failed to begin command buffer\n";
2487 VkClearValue clear_value{};
2498 VkImageMemoryBarrier2 to_color_barrier{};
2499 to_color_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2500 to_color_barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
2501 to_color_barrier.srcAccessMask = VK_ACCESS_2_NONE;
2502 to_color_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2503 to_color_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
2504 to_color_barrier.oldLayout =
2506 to_color_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2507 to_color_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2508 to_color_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2510 to_color_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2511 to_color_barrier.subresourceRange.baseMipLevel = 0;
2512 to_color_barrier.subresourceRange.levelCount = 1;
2513 to_color_barrier.subresourceRange.baseArrayLayer = 0;
2514 to_color_barrier.subresourceRange.layerCount = 1;
2516 VkDependencyInfo pre_render_dependency{};
2517 pre_render_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2518 pre_render_dependency.imageMemoryBarrierCount = 1;
2519 pre_render_dependency.pImageMemoryBarriers = &to_color_barrier;
2520 vkCmdPipelineBarrier2(cmd, &pre_render_dependency);
2522 if (use_post_process) {
2523 VkImageMemoryBarrier2 post_target_barrier{};
2524 post_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2525 post_target_barrier.srcStageMask =
post_process_initialized[0][image_index] ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE;
2526 post_target_barrier.srcAccessMask =
post_process_initialized[0][image_index] ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT : VK_ACCESS_2_NONE;
2527 post_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2528 post_target_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
2529 post_target_barrier.oldLayout =
post_process_initialized[0][image_index] ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
2530 post_target_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2531 post_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2532 post_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2534 post_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2535 post_target_barrier.subresourceRange.levelCount = 1;
2536 post_target_barrier.subresourceRange.layerCount = 1;
2537 VkDependencyInfo post_target_dependency{};
2538 post_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2539 post_target_dependency.imageMemoryBarrierCount = 1;
2540 post_target_dependency.pImageMemoryBarriers = &post_target_barrier;
2541 vkCmdPipelineBarrier2(cmd, &post_target_dependency);
2544 VkImageMemoryBarrier2 to_depth_barrier{};
2545 to_depth_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2546 to_depth_barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
2547 to_depth_barrier.srcAccessMask = VK_ACCESS_2_NONE;
2548 to_depth_barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT;
2549 to_depth_barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
2550 to_depth_barrier.oldLayout =
2552 ? VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL
2553 : VK_IMAGE_LAYOUT_UNDEFINED;
2554 to_depth_barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
2555 to_depth_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2556 to_depth_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2560 to_depth_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
2561 to_depth_barrier.subresourceRange.baseMipLevel = 0;
2562 to_depth_barrier.subresourceRange.levelCount = 1;
2563 to_depth_barrier.subresourceRange.baseArrayLayer = 0;
2564 to_depth_barrier.subresourceRange.layerCount = 1;
2566 if (to_depth_barrier.image != VK_NULL_HANDLE) {
2567 VkDependencyInfo pre_depth_dependency{};
2568 pre_depth_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2569 pre_depth_dependency.imageMemoryBarrierCount = 1;
2570 pre_depth_dependency.pImageMemoryBarriers = &to_depth_barrier;
2571 vkCmdPipelineBarrier2(cmd, &pre_depth_dependency);
2575 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2581 VkRenderingAttachmentInfo color_attachment{};
2582 color_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
2584 color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2585 color_attachment.resolveMode = VK_RESOLVE_MODE_NONE;
2586 color_attachment.resolveImageView = VK_NULL_HANDLE;
2587 color_attachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2588 color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
2589 color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
2590 color_attachment.clearValue = clear_value;
2592 VkClearValue depth_clear_value{};
2593 depth_clear_value.depthStencil = {1.0f, 0};
2595 VkRenderingAttachmentInfo depth_attachment{};
2596 depth_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
2600 depth_attachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
2601 depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
2602 depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
2603 depth_attachment.clearValue = depth_clear_value;
2605 VkRenderingAttachmentInfo stencil_attachment{};
2606 stencil_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
2609 VkRenderingInfo rendering_info{};
2610 rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
2611 rendering_info.renderArea.offset = {0, 0};
2613 rendering_info.layerCount = 1;
2614 rendering_info.viewMask = 0;
2615 rendering_info.colorAttachmentCount = 1;
2616 rendering_info.pColorAttachments = &color_attachment;
2617 rendering_info.pDepthAttachment = (depth_attachment.imageView != VK_NULL_HANDLE) ? &depth_attachment : nullptr;
2618 rendering_info.pStencilAttachment = (stencil_attachment.imageView != VK_NULL_HANDLE) ? &stencil_attachment : nullptr;
2620 vkCmdBeginRendering(cmd, &rendering_info);
2622 VkViewport viewport{};
2627 viewport.minDepth = 0.0F;
2628 viewport.maxDepth = 1.0F;
2629 vkCmdSetViewport(cmd, 0, 1, &viewport);
2632 scissor.offset = {0, 0};
2634 vkCmdSetScissor(cmd, 0, 1, &scissor);
2640 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2641 if (!sprite || isPostProcessSprite(sprite.get())) {
2645 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
2652 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
text_pipeline);
2656 vkCmdEndRendering(cmd);
2661 if (use_post_process) {
2662 VkImageMemoryBarrier2 post_target_barrier{};
2663 post_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2664 post_target_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2665 post_target_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
2666 post_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
2667 post_target_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
2668 post_target_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2669 post_target_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2670 post_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2671 post_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2673 post_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2674 post_target_barrier.subresourceRange.levelCount = 1;
2675 post_target_barrier.subresourceRange.layerCount = 1;
2676 VkDependencyInfo post_target_dependency{};
2677 post_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2678 post_target_dependency.imageMemoryBarrierCount = 1;
2679 post_target_dependency.pImageMemoryBarriers = &post_target_barrier;
2680 vkCmdPipelineBarrier2(cmd, &post_target_dependency);
2683 size_t source_target = 0;
2686 if (effect_sprite ==
nullptr) {
2691 const size_t destination_target = source_target == 0U ? 1U : 0U;
2694 if (!final_effect) {
2695 VkImageMemoryBarrier2 next_target_barrier{};
2696 next_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2697 next_target_barrier.srcStageMask =
post_process_initialized[destination_target][image_index] ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE;
2698 next_target_barrier.srcAccessMask =
post_process_initialized[destination_target][image_index] ? VK_ACCESS_2_SHADER_SAMPLED_READ_BIT : VK_ACCESS_2_NONE;
2699 next_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2700 next_target_barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
2701 next_target_barrier.oldLayout =
post_process_initialized[destination_target][image_index] ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
2702 next_target_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2703 next_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2704 next_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2706 next_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2707 next_target_barrier.subresourceRange.levelCount = 1;
2708 next_target_barrier.subresourceRange.layerCount = 1;
2709 VkDependencyInfo next_target_dependency{};
2710 next_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2711 next_target_dependency.imageMemoryBarrierCount = 1;
2712 next_target_dependency.pImageMemoryBarriers = &next_target_barrier;
2713 vkCmdPipelineBarrier2(cmd, &next_target_dependency);
2716 VkRenderingAttachmentInfo post_attachment{};
2717 post_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
2718 post_attachment.imageView = destination_view;
2719 post_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2720 post_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
2721 post_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
2722 VkRenderingInfo post_info{};
2723 post_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
2725 post_info.layerCount = 1;
2726 post_info.colorAttachmentCount = 1;
2727 post_info.pColorAttachments = &post_attachment;
2728 vkCmdBeginRendering(cmd, &post_info);
2735 effect_sprite->setShaderParams(params[0], params[1], params[2], params[3]);
2741 vkCmdEndRendering(cmd);
2743 if (!final_effect) {
2744 VkImageMemoryBarrier2 sampled_target_barrier{};
2745 sampled_target_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2746 sampled_target_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2747 sampled_target_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
2748 sampled_target_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT;
2749 sampled_target_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT;
2750 sampled_target_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2751 sampled_target_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2752 sampled_target_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2753 sampled_target_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2755 sampled_target_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2756 sampled_target_barrier.subresourceRange.levelCount = 1;
2757 sampled_target_barrier.subresourceRange.layerCount = 1;
2758 VkDependencyInfo sampled_target_dependency{};
2759 sampled_target_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2760 sampled_target_dependency.imageMemoryBarrierCount = 1;
2761 sampled_target_dependency.pImageMemoryBarriers = &sampled_target_barrier;
2762 vkCmdPipelineBarrier2(cmd, &sampled_target_dependency);
2764 source_target = destination_target;
2769 VkImageMemoryBarrier2 to_present_barrier{};
2770 to_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
2771 to_present_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2772 to_present_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
2773 to_present_barrier.dstStageMask = VK_PIPELINE_STAGE_2_NONE;
2774 to_present_barrier.dstAccessMask = VK_ACCESS_2_NONE;
2775 to_present_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
2776 to_present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
2777 to_present_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2778 to_present_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2780 to_present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2781 to_present_barrier.subresourceRange.baseMipLevel = 0;
2782 to_present_barrier.subresourceRange.levelCount = 1;
2783 to_present_barrier.subresourceRange.baseArrayLayer = 0;
2784 to_present_barrier.subresourceRange.layerCount = 1;
2786 VkDependencyInfo post_render_dependency{};
2787 post_render_dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
2788 post_render_dependency.imageMemoryBarrierCount = 1;
2789 post_render_dependency.pImageMemoryBarriers = &to_present_barrier;
2790 vkCmdPipelineBarrier2(cmd, &post_render_dependency);
2792 if (vkEndCommandBuffer(cmd) != VK_SUCCESS) {
2793 std::cerr <<
"mxvk: Failed to end command buffer\n";
2798 VkSemaphoreSubmitInfo wait_semaphore_info{};
2799 wait_semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
2800 wait_semaphore_info.semaphore = acquire_semaphore;
2801 wait_semaphore_info.value = 0;
2802 wait_semaphore_info.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
2803 wait_semaphore_info.deviceIndex = 0;
2805 VkCommandBufferSubmitInfo command_buffer_info{};
2806 command_buffer_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
2807 command_buffer_info.commandBuffer = cmd;
2808 command_buffer_info.deviceMask = 0;
2810 VkSemaphoreSubmitInfo signal_semaphore_info{};
2811 signal_semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
2812 signal_semaphore_info.semaphore = present_semaphore;
2813 signal_semaphore_info.value = 0;
2814 signal_semaphore_info.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT;
2815 signal_semaphore_info.deviceIndex = 0;
2817 VkSubmitInfo2 submit_info{};
2818 submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
2819 submit_info.waitSemaphoreInfoCount = 1;
2820 submit_info.pWaitSemaphoreInfos = &wait_semaphore_info;
2821 submit_info.commandBufferInfoCount = 1;
2822 submit_info.pCommandBufferInfos = &command_buffer_info;
2823 submit_info.signalSemaphoreInfoCount = 1;
2824 submit_info.pSignalSemaphoreInfos = &signal_semaphore_info;
2826 const VkResult fence_reset_result = vkResetFences(
device, 1, &frame_fence);
2827 if (fence_reset_result == VK_ERROR_DEVICE_LOST) {
2828 std::cerr <<
"mxvk: device lost while resetting frame fence; stopping render loop\n";
2832 if (fence_reset_result != VK_SUCCESS) {
2833 std::cerr << std::format(
"mxvk: Failed to reset frame fence (VkResult={})\n",
static_cast<int>(fence_reset_result));
2838 const VkResult submit_result = vkQueueSubmit2(
graphics_queue, 1, &submit_info, frame_fence);
2839 if (submit_result == VK_ERROR_DEVICE_LOST) {
2840 std::cerr <<
"mxvk: device lost during queue submit; stopping render loop\n";
2844 if (submit_result != VK_SUCCESS) {
2845 std::cerr << std::format(
"mxvk: Failed to submit draw command (VkResult={})\n",
static_cast<int>(submit_result));
2854 VkPresentInfoKHR present_info{};
2855 present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
2856 present_info.waitSemaphoreCount = 1;
2857 present_info.pWaitSemaphores = &present_semaphore;
2858 present_info.swapchainCount = 1;
2860 present_info.pImageIndices = &image_index;
2862 const VkResult present_result = vkQueuePresentKHR(
present_queue, &present_info);
2863 if (present_result == VK_SUCCESS || present_result == VK_SUBOPTIMAL_KHR) {
2867 if (present_result == VK_ERROR_OUT_OF_DATE_KHR) {
2868 std::cout <<
"mxvk: requesting swapchain recreation because present returned VK_ERROR_OUT_OF_DATE_KHR\n";
2872 }
else if (present_result == VK_SUBOPTIMAL_KHR) {
2873 int present_pixel_w = 0;
2874 int present_pixel_h = 0;
2876 SDL_GetWindowSizeInPixels(
window.get(), &present_pixel_w, &present_pixel_h);
2878 const bool known_present_extent = present_pixel_w > 0 && present_pixel_h > 0;
2879 const bool present_extent_changed =
2880 known_present_extent &&
2883 if (!known_present_extent || present_extent_changed) {
2884 std::cout <<
"mxvk: requesting swapchain recreation because present returned VK_SUBOPTIMAL_KHR with changed window extent\n";
2889 }
else if (present_result != VK_SUCCESS) {
2890 std::cerr <<
"mxvk: Failed to present swapchain image\n";
2893 for (
const std::unique_ptr<VK_Sprite> &sprite :
sprites) {
2903 if (!
retired_swapchains.empty() && (present_result == VK_SUCCESS || present_result == VK_SUBOPTIMAL_KHR)) {
2905 vkDestroySwapchainKHR(
device, retired,
nullptr);
2915 bool VK_Window::hasValidationLayerSupport() {
2916 uint32_t layer_count = 0;
2917 const VkResult count_result = vkEnumerateInstanceLayerProperties(&layer_count,
nullptr);
2918 if (count_result != VK_SUCCESS) {
2919 std::cerr << std::format(
2920 "mxvk: Failed to query validation layer count (VkResult={})\n",
2921 static_cast<int>(count_result));
2925 if (layer_count == 0U) {
2929 std::vector<VkLayerProperties> available_layers(layer_count);
2930 const VkResult layers_result = vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data());
2931 if (layers_result != VK_SUCCESS) {
2932 std::cerr << std::format(
2933 "mxvk: Failed to enumerate validation layers (VkResult={})\n",
2934 static_cast<int>(layers_result));
2938 return std::ranges::any_of(
2940 [](
const VkLayerProperties &layer) {
2941 return std::strcmp(layer.layerName, validation_layer_name) == 0;
2945 std::optional<VkDebugUtilsMessengerCreateInfoEXT> VK_Window::makeDebugMessengerCreateInfo() {
2946 VkDebugUtilsMessengerCreateInfoEXT create_info{};
2947 create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
2948 create_info.messageSeverity =
2949 VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
2950 VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
2951 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
2952 create_info.messageType =
2953 VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
2954 VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
2955 VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
2957 create_info.pUserData =
nullptr;
2961 void VK_Window::setupDebugMessenger() {
2966 const std::optional<VkDebugUtilsMessengerCreateInfoEXT> maybe_create_info = makeDebugMessengerCreateInfo();
2967 if (!maybe_create_info.has_value()) {
2968 std::cerr <<
"mxvk: unable to build debug messenger create info\n";
2972 const VkResult result = vkCreateDebugUtilsMessengerEXT(
2974 &maybe_create_info.value(),
2977 if (result != VK_SUCCESS) {
2978 std::cerr <<
"mxvk: failed to create Vulkan debug messenger\n";
2983 void VK_Window::cleanupDebugMessenger() {
2985 std::cout <<
"vk: destroying debug messenger\n";
2992 if (fontPath.empty() || fontSize <= 0) {
2993 throw mxvk::Exception(
"setFont requires a non-empty path and positive font size");
3000 if (
device == VK_NULL_HANDLE) {
3001 throw mxvk::Exception(
"Cannot set font before Vulkan device initialization");
3007 throw mxvk::Exception(
"Cannot initialize text renderer before swapchain and command resources are available");
3011 ensureTextRenderer();
3024 throw mxvk::Exception(
"printText requires setFont() to be called first");
3027 ensureTextRenderer();
3036 if (font ==
nullptr) {
3040 ensureTextRenderer(resolveDefaultFontPath(),
font_size);
3042 throw mxvk::Exception(
"printText with an explicit font could not initialize the text renderer");
3056 ensureTextRenderer(font.
path(), font.
size());
3058 throw mxvk::Exception(
"printText with an explicit font could not initialize the text renderer");
3071 throw mxvk::Exception(
"getTextDimensions requires setFont() to be called first");
3075 ensureTextRenderer();
3082 return text_renderer->getTextDimensions(text, width, height);
3086 if (font ==
nullptr) {
3092 ensureTextRenderer(resolveDefaultFontPath(),
font_size);
3098 return text_renderer->getTextDimensions(text, width, height, font);
3108 ensureTextRenderer(font.
path(), font.
size());
3114 return text_renderer->getTextDimensions(text, width, height, font);
3117 void VK_Window::ensureTextRenderer() {
3126 if (
device == VK_NULL_HANDLE) {
3137 createTextDescriptorSetLayout();
3145 void VK_Window::ensureTextRenderer(
const std::string &fallbackFontPath,
int fallbackFontSize) {
3150 if (
device == VK_NULL_HANDLE) {
3162 if (renderer_font_path.empty() || renderer_font_size <= 0) {
3167 createTextDescriptorSetLayout();
3175 std::string VK_Window::resolveDefaultFontPath()
const {
3176 std::vector<std::filesystem::path> candidates{};
3178 if (
const char *basePath = SDL_GetBasePath(); basePath !=
nullptr) {
3179 const std::filesystem::path executableDir(basePath);
3180 candidates.push_back(executableDir /
"data" /
"default.ttf");
3181 candidates.push_back(executableDir /
"default.ttf");
3184 candidates.push_back(std::filesystem::path(
"data") /
"default.ttf");
3187 const std::filesystem::path configured_font_path(
font_path);
3188 if (configured_font_path.has_parent_path()) {
3189 candidates.push_back(configured_font_path.parent_path() /
"default.ttf");
3195 std::error_code exists_error{};
3196 for (
const std::filesystem::path &candidate : candidates) {
3197 if (std::filesystem::exists(candidate, exists_error)) {
3198 return candidate.string();
3200 exists_error.clear();
3206 void VK_Window::toggleFpsCounter() {
3211 std::cout << std::format(
"mxvk: FPS counter {}\n",
fps_counter_enabled ?
"enabled" :
"disabled");
3218 const std::string default_font_path = resolveDefaultFontPath();
3219 if (default_font_path.empty()) {
3220 std::cerr <<
"mxvk: F12 FPS counter could not locate data/default.ttf\n";
3229 void VK_Window::updateFpsCounter() {
3235 const std::string default_font_path = resolveDefaultFontPath();
3236 if (default_font_path.empty()) {
3237 std::cerr <<
"mxvk: F12 FPS counter could not locate data/default.ttf\n";
3246 const auto now = std::chrono::steady_clock::now();
3248 if (elapsed >= 0.5) {
3258 void VK_Window::createTextDescriptorSetLayout() {
3263 VkDescriptorSetLayoutBinding sampler_binding{};
3264 sampler_binding.binding = 0;
3265 sampler_binding.descriptorCount = 1;
3266 sampler_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
3267 sampler_binding.pImmutableSamplers =
nullptr;
3268 sampler_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
3270 VkDescriptorSetLayoutCreateInfo layout_info{};
3271 layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
3272 layout_info.bindingCount = 1;
3273 layout_info.pBindings = &sampler_binding;
3276 throw mxvk::Exception(
"Failed to create text descriptor set layout");
3280 void VK_Window::destroyTextPipeline() {
3281 if (
device == VK_NULL_HANDLE) {
3288 std::cout <<
"vk: destroying text pipeline\n";
3293 std::cout <<
"vk: destroying text pipeline layout\n";
3299 void VK_Window::createTextPipeline() {
3304 destroyTextPipeline();
3310 VkShaderModule frag_module = VK_NULL_HANDLE;
3315 VkPipelineShaderStageCreateInfo vert_stage{};
3316 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3317 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
3318 vert_stage.module = vert_module;
3319 vert_stage.pName =
"main";
3321 VkPipelineShaderStageCreateInfo frag_stage{};
3322 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3323 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
3324 frag_stage.module = frag_module;
3325 frag_stage.pName =
"main";
3327 const VkPipelineShaderStageCreateInfo shader_stages[] = {vert_stage, frag_stage};
3329 VkVertexInputBindingDescription binding_description{};
3330 binding_description.binding = 0;
3331 binding_description.stride =
sizeof(float) * 4;
3332 binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
3334 std::array<VkVertexInputAttributeDescription, 2> attributes{};
3335 attributes[0].binding = 0;
3336 attributes[0].location = 0;
3337 attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
3338 attributes[0].offset = 0;
3339 attributes[1].binding = 0;
3340 attributes[1].location = 1;
3341 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
3342 attributes[1].offset =
sizeof(float) * 2;
3344 VkPipelineVertexInputStateCreateInfo vertex_input{};
3345 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
3346 vertex_input.vertexBindingDescriptionCount = 1;
3347 vertex_input.pVertexBindingDescriptions = &binding_description;
3348 vertex_input.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
3349 vertex_input.pVertexAttributeDescriptions = attributes.data();
3351 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
3352 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
3353 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
3354 input_assembly.primitiveRestartEnable = VK_FALSE;
3356 const std::array<VkDynamicState, 2> dynamic_states = {
3357 VK_DYNAMIC_STATE_VIEWPORT,
3358 VK_DYNAMIC_STATE_SCISSOR,
3360 VkPipelineDynamicStateCreateInfo dynamic_state{};
3361 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
3362 dynamic_state.dynamicStateCount =
static_cast<uint32_t
>(dynamic_states.size());
3363 dynamic_state.pDynamicStates = dynamic_states.data();
3365 VkPipelineViewportStateCreateInfo viewport_state{};
3366 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
3367 viewport_state.viewportCount = 1;
3368 viewport_state.scissorCount = 1;
3370 VkPipelineRasterizationStateCreateInfo rasterizer{};
3371 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
3372 rasterizer.depthClampEnable = VK_FALSE;
3373 rasterizer.rasterizerDiscardEnable = VK_FALSE;
3374 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
3375 rasterizer.lineWidth = 1.0F;
3376 rasterizer.cullMode = VK_CULL_MODE_NONE;
3377 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
3378 rasterizer.depthBiasEnable = VK_FALSE;
3380 VkPipelineMultisampleStateCreateInfo multisampling{};
3381 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
3382 multisampling.sampleShadingEnable = VK_FALSE;
3383 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
3385 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
3386 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
3387 depth_stencil.depthTestEnable = VK_FALSE;
3388 depth_stencil.depthWriteEnable = VK_FALSE;
3390 VkPipelineColorBlendAttachmentState color_attachment{};
3391 color_attachment.colorWriteMask =
3392 VK_COLOR_COMPONENT_R_BIT |
3393 VK_COLOR_COMPONENT_G_BIT |
3394 VK_COLOR_COMPONENT_B_BIT |
3395 VK_COLOR_COMPONENT_A_BIT;
3396 color_attachment.blendEnable = VK_TRUE;
3397 color_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
3398 color_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
3399 color_attachment.colorBlendOp = VK_BLEND_OP_ADD;
3400 color_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3401 color_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
3402 color_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
3404 VkPipelineColorBlendStateCreateInfo color_blending{};
3405 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
3406 color_blending.logicOpEnable = VK_FALSE;
3407 color_blending.attachmentCount = 1;
3408 color_blending.pAttachments = &color_attachment;
3410 VkPushConstantRange push_constant_range{};
3411 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
3412 push_constant_range.offset = 0;
3413 push_constant_range.size =
sizeof(float) * 4;
3415 VkPipelineLayoutCreateInfo pipeline_layout_info{};
3416 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
3417 pipeline_layout_info.setLayoutCount = 1;
3419 pipeline_layout_info.pushConstantRangeCount = 1;
3420 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
3423 throw mxvk::Exception(
"Failed to create text pipeline layout");
3426 VkPipelineRenderingCreateInfo rendering_info{};
3427 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
3428 rendering_info.viewMask = 0;
3429 rendering_info.colorAttachmentCount = 1;
3435 VkGraphicsPipelineCreateInfo pipeline_info{};
3436 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
3437 pipeline_info.pNext = &rendering_info;
3438 pipeline_info.stageCount = 2;
3439 pipeline_info.pStages = shader_stages;
3440 pipeline_info.pVertexInputState = &vertex_input;
3441 pipeline_info.pInputAssemblyState = &input_assembly;
3442 pipeline_info.pViewportState = &viewport_state;
3443 pipeline_info.pRasterizationState = &rasterizer;
3444 pipeline_info.pMultisampleState = &multisampling;
3445 pipeline_info.pDepthStencilState = &depth_stencil;
3446 pipeline_info.pColorBlendState = &color_blending;
3447 pipeline_info.pDynamicState = &dynamic_state;
3449 pipeline_info.renderPass = VK_NULL_HANDLE;
3450 pipeline_info.subpass = 0;
3451 pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
3452 pipeline_info.basePipelineIndex = -1;
3455 throw mxvk::Exception(
"Failed to create text graphics pipeline");
3466 if (frag_module != VK_NULL_HANDLE) {
3467 vkDestroyShaderModule(
device, frag_module,
nullptr);
3469 vkDestroyShaderModule(
device, vert_module,
nullptr);
3473 vkDestroyShaderModule(
device, frag_module,
nullptr);
3474 vkDestroyShaderModule(
device, vert_module,
nullptr);
3478 if (
device == VK_NULL_HANDLE) {
3479 throw mxvk::Exception(
"Cannot create sprite before Vulkan device initialization");
3485 throw mxvk::Exception(
"Cannot create sprite before swapchain and command resources are available");
3489 createSpriteDescriptorSetLayout();
3498 if (!vertexShaderPath.empty()) {
3502 sprite->
loadSprite(pngPath, fragmentShaderPath);
3504 VK_Sprite *
const sprite_ptr = sprite.get();
3505 sprites.push_back(std::move(sprite));
3512 throw mxvk::Exception(
"Cannot create sprite from a null SDL surface");
3514 if (
device == VK_NULL_HANDLE) {
3515 throw mxvk::Exception(
"Cannot create sprite before Vulkan device initialization");
3521 throw mxvk::Exception(
"Cannot create sprite before swapchain and command resources are available");
3525 createSpriteDescriptorSetLayout();
3534 if (!vertexShaderPath.empty()) {
3540 VK_Sprite *
const sprite_ptr = sprite.get();
3541 sprites.push_back(std::move(sprite));
3547 if (width <= 0 || height <= 0) {
3550 if (
device == VK_NULL_HANDLE) {
3551 throw mxvk::Exception(
"Cannot create sprite before Vulkan device initialization");
3557 throw mxvk::Exception(
"Cannot create sprite before swapchain and command resources are available");
3561 createSpriteDescriptorSetLayout();
3572 VK_Sprite *
const sprite_ptr = sprite.get();
3573 sprites.push_back(std::move(sprite));
3579 if (
device == VK_NULL_HANDLE) {
3580 throw mxvk::Exception(
"Cannot create 3D sprite before Vulkan device initialization");
3586 throw mxvk::Exception(
"Cannot create 3D sprite before swapchain and command resources are available");
3589 const std::string vertPath = vertexShaderPath.empty()
3590 ? resolveRuntimeShaderPath(
"sprite3d.vert.spv", MXVK_SPRITE3D_SHADER_DIR)
3592 const std::string fragPath = fragmentShaderPath.empty()
3593 ? resolveRuntimeShaderPath(
"sprite3d.frag.spv", MXVK_SPRITE3D_SHADER_DIR)
3594 : fragmentShaderPath;
3596 auto sprite = std::make_unique<VK_Sprite3D>();
3597 sprite->load(
this, pngPath, vertPath, fragPath);
3606 throw mxvk::Exception(
"Cannot create 3D sprite from a null SDL surface");
3608 if (
device == VK_NULL_HANDLE) {
3609 throw mxvk::Exception(
"Cannot create 3D sprite before Vulkan device initialization");
3615 throw mxvk::Exception(
"Cannot create 3D sprite before swapchain and command resources are available");
3618 const std::string vertPath = vertexShaderPath.empty()
3619 ? resolveRuntimeShaderPath(
"sprite3d.vert.spv", MXVK_SPRITE3D_SHADER_DIR)
3621 const std::string fragPath = fragmentShaderPath.empty()
3622 ? resolveRuntimeShaderPath(
"sprite3d.frag.spv", MXVK_SPRITE3D_SHADER_DIR)
3623 : fragmentShaderPath;
3625 auto sprite = std::make_unique<VK_Sprite3D>();
3626 sprite->load(
this,
surface, vertPath, fragPath);
3640 void VK_Window::createSpriteDescriptorSetLayout() {
3645 VkDescriptorSetLayoutBinding sampler_binding{};
3646 sampler_binding.binding = 0;
3647 sampler_binding.descriptorCount = 1;
3648 sampler_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
3649 sampler_binding.pImmutableSamplers =
nullptr;
3650 sampler_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
3652 VkDescriptorSetLayoutCreateInfo layout_info{};
3653 layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
3654 layout_info.bindingCount = 1;
3655 layout_info.pBindings = &sampler_binding;
3658 throw mxvk::Exception(
"Failed to create sprite descriptor set layout");
3662 void VK_Window::destroySpritePipeline() {
3663 if (
device == VK_NULL_HANDLE) {
3670 std::cout <<
"vk: destroying sprite pipeline\n";
3675 std::cout <<
"vk: destroying sprite pipeline layout\n";
3681 void VK_Window::createSpritePipeline() {
3686 createSpriteDescriptorSetLayout();
3689 destroySpritePipeline();
3695 VkShaderModule frag_module = VK_NULL_HANDLE;
3700 VkPipelineShaderStageCreateInfo vert_stage{};
3701 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3702 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
3703 vert_stage.module = vert_module;
3704 vert_stage.pName =
"main";
3706 VkPipelineShaderStageCreateInfo frag_stage{};
3707 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3708 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
3709 frag_stage.module = frag_module;
3710 frag_stage.pName =
"main";
3712 const VkPipelineShaderStageCreateInfo shader_stages[] = {vert_stage, frag_stage};
3714 VkVertexInputBindingDescription binding_description{};
3715 binding_description.binding = 0;
3716 binding_description.stride =
sizeof(float) * 4;
3717 binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
3719 std::array<VkVertexInputAttributeDescription, 2> attributes{};
3720 attributes[0].binding = 0;
3721 attributes[0].location = 0;
3722 attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
3723 attributes[0].offset = 0;
3724 attributes[1].binding = 0;
3725 attributes[1].location = 1;
3726 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
3727 attributes[1].offset =
sizeof(float) * 2;
3729 VkPipelineVertexInputStateCreateInfo vertex_input{};
3730 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
3731 vertex_input.vertexBindingDescriptionCount = 1;
3732 vertex_input.pVertexBindingDescriptions = &binding_description;
3733 vertex_input.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
3734 vertex_input.pVertexAttributeDescriptions = attributes.data();
3736 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
3737 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
3738 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
3739 input_assembly.primitiveRestartEnable = VK_FALSE;
3741 const std::array<VkDynamicState, 2> dynamic_states = {
3742 VK_DYNAMIC_STATE_VIEWPORT,
3743 VK_DYNAMIC_STATE_SCISSOR,
3745 VkPipelineDynamicStateCreateInfo dynamic_state{};
3746 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
3747 dynamic_state.dynamicStateCount =
static_cast<uint32_t
>(dynamic_states.size());
3748 dynamic_state.pDynamicStates = dynamic_states.data();
3750 VkPipelineViewportStateCreateInfo viewport_state{};
3751 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
3752 viewport_state.viewportCount = 1;
3753 viewport_state.scissorCount = 1;
3755 VkPipelineRasterizationStateCreateInfo rasterizer{};
3756 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
3757 rasterizer.depthClampEnable = VK_FALSE;
3758 rasterizer.rasterizerDiscardEnable = VK_FALSE;
3759 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
3760 rasterizer.lineWidth = 1.0F;
3761 rasterizer.cullMode = VK_CULL_MODE_NONE;
3762 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
3763 rasterizer.depthBiasEnable = VK_FALSE;
3765 VkPipelineMultisampleStateCreateInfo multisampling{};
3766 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
3767 multisampling.sampleShadingEnable = VK_FALSE;
3768 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
3770 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
3771 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
3772 depth_stencil.depthTestEnable = VK_FALSE;
3773 depth_stencil.depthWriteEnable = VK_FALSE;
3775 VkPipelineColorBlendAttachmentState color_attachment{};
3776 color_attachment.colorWriteMask =
3777 VK_COLOR_COMPONENT_R_BIT |
3778 VK_COLOR_COMPONENT_G_BIT |
3779 VK_COLOR_COMPONENT_B_BIT |
3780 VK_COLOR_COMPONENT_A_BIT;
3781 color_attachment.blendEnable = VK_TRUE;
3782 color_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
3783 color_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
3784 color_attachment.colorBlendOp = VK_BLEND_OP_ADD;
3785 color_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
3786 color_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
3787 color_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
3789 VkPipelineColorBlendStateCreateInfo color_blending{};
3790 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
3791 color_blending.logicOpEnable = VK_FALSE;
3792 color_blending.attachmentCount = 1;
3793 color_blending.pAttachments = &color_attachment;
3795 VkPushConstantRange push_constant_range{};
3796 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
3797 push_constant_range.offset = 0;
3798 push_constant_range.size =
sizeof(float) * 12;
3800 VkPipelineLayoutCreateInfo pipeline_layout_info{};
3801 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
3802 pipeline_layout_info.setLayoutCount = 1;
3804 pipeline_layout_info.pushConstantRangeCount = 1;
3805 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
3808 throw mxvk::Exception(
"Failed to create sprite pipeline layout");
3811 VkPipelineRenderingCreateInfo rendering_info{};
3812 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
3813 rendering_info.viewMask = 0;
3814 rendering_info.colorAttachmentCount = 1;
3820 VkGraphicsPipelineCreateInfo pipeline_info{};
3821 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
3822 pipeline_info.pNext = &rendering_info;
3823 pipeline_info.stageCount = 2;
3824 pipeline_info.pStages = shader_stages;
3825 pipeline_info.pVertexInputState = &vertex_input;
3826 pipeline_info.pInputAssemblyState = &input_assembly;
3827 pipeline_info.pViewportState = &viewport_state;
3828 pipeline_info.pRasterizationState = &rasterizer;
3829 pipeline_info.pMultisampleState = &multisampling;
3830 pipeline_info.pDepthStencilState = &depth_stencil;
3831 pipeline_info.pColorBlendState = &color_blending;
3832 pipeline_info.pDynamicState = &dynamic_state;
3834 pipeline_info.renderPass = VK_NULL_HANDLE;
3835 pipeline_info.subpass = 0;
3836 pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
3837 pipeline_info.basePipelineIndex = -1;
3840 throw mxvk::Exception(
"Failed to create sprite graphics pipeline");
3851 if (frag_module != VK_NULL_HANDLE) {
3852 vkDestroyShaderModule(
device, frag_module,
nullptr);
3854 vkDestroyShaderModule(
device, vert_module,
nullptr);
3858 vkDestroyShaderModule(
device, frag_module,
nullptr);
3859 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 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 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 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.
void setPipelineCache(VkPipelineCache cache)
Use the shared pipeline cache for custom/instanced pipeline creation.
void clearQueue()
Discard all pending draw commands without rendering.
void rebuildPipeline()
Destroy and recreate the custom graphics pipeline.
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.
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< bool > post_process_effect_time_enabled
VulkanContext context() const
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::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 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.
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
VkPipelineCache pipeline_cache
std::unique_ptr< SDL_Window, SDLWindowDeleter > window
std::vector< std::chrono::steady_clock::time_point > post_process_effect_start_times
VkPipeline sprite_pipeline
void exit()
Request loop termination.
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.
bool ensureRenderResources()
Ensure deferred render resources are initialized.
VK_Window()=default
Construct an empty window object.
VkPhysicalDevice physical_device
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
std::deque< ScreenshotSaveTask > screenshot_save_queue
void release()
Release Vulkan and SDL resources.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
std::vector< VkDeviceMemory > depth_image_memories
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
std::vector< VK_Sprite * > post_process_sprites
void trimMemory()
Return transient Vulkan command-pool allocations to the driver when supported.
VK_Sprite * post_process_sprite
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.
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.
std::vector< VkImage > depth_images
virtual void proc()
Execute one processing/update step.
bool validationEnabled() const
Check whether Vulkan validation layers are currently enabled.
std::vector< std::unique_ptr< VK_Sprite > > sprites
uint64_t last_resize_event_ms
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.
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
#define MXVK_TEXT_SHADER_DIR
#define MXVK_SPRITE_SHADER_DIR
#define MXVK_DEFAULT_FONT_DIR
PNG image loading and saving utilities via SDL3.
bool shouldLogMissingValidationLayer()
Utilities for loading and saving PNG images.
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
bool SavePNG_RGBA(const char *filename, void *buffer, int w, int h)
Save a raw RGBA pixel buffer to a PNG file.
bool defaultEnableScreenshot()
const std::string & defaultExecutableName()
std::vector< char > load_spv(const std::string &path)
Load a SPIR-V file from disk.
Minimal Vulkan handles required by MXVK resource helpers.