16 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
17 throw mx::Exception(
"SDL_Init: Failure: " + std::string(SDL_GetError()));
20 window = SDL_CreateWindow(title.c_str(),SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width,
height,SDL_WINDOW_VULKAN | SDL_WINDOW_FULLSCREEN_DESKTOP);
22 window = SDL_CreateWindow(title.c_str(),SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width,
height,SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE);
25 throw mx::Exception(
"failure to create window: " + std::string(SDL_GetError()));
27 SDL_Vulkan_GetDrawableSize(
window, &
w, &
h);
28 std::cout <<
">> [Window] Actual drawable size: " <<
w <<
"x" <<
h << std::endl;
47 const Uint32 controllerFlags = SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
48 if ((SDL_WasInit(controllerFlags) & controllerFlags) != controllerFlags) {
49 if (SDL_InitSubSystem(controllerFlags) != 0) {
50 std::cerr <<
"Warning: SDL controller subsystem init failed: " << SDL_GetError() << std::endl;
54 SDL_GameControllerEventState(SDL_ENABLE);
55 SDL_JoystickEventState(SDL_ENABLE);
89 }
catch (
const std::exception& e) {
90 std::cerr <<
"Warning: Text rendering initialization failed: " << e.what() << std::endl;
91 std::cerr <<
"Text rendering will be disabled." << std::endl;
97 VkDeviceSize imageSize = newSurface->w * newSurface->h * 4;
102 VkDescriptorSetLayoutBinding samplerLayoutBinding{};
103 samplerLayoutBinding.binding = 0;
104 samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
105 samplerLayoutBinding.descriptorCount = 1;
106 samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
108 VkDescriptorSetLayoutBinding uboLayoutBinding{};
109 uboLayoutBinding.binding = 1;
110 uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
111 uboLayoutBinding.descriptorCount = 1;
112 uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
114 std::array<VkDescriptorSetLayoutBinding, 2> bindings = {samplerLayoutBinding, uboLayoutBinding};
116 VkDescriptorSetLayoutCreateInfo layoutInfo{};
117 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
118 layoutInfo.bindingCount =
static_cast<uint32_t
>(bindings.size());
119 layoutInfo.pBindings = bindings.data();
122 throw mx::Exception(
"Failed to create descriptor set layout!");
125 std::cout <<
">> [DescriptorSetLayout] Created with 2 bindings:\n";
126 std::cout <<
" Binding 0: Combined Image Sampler (texture)\n";
127 std::cout <<
" Binding 1: Uniform Buffer (time, tint color)\n";
137 bool extensionsSupported =
false;
138 const std::vector<const char*> deviceExtensions = {
139 VK_KHR_SWAPCHAIN_EXTENSION_NAME
142 uint32_t extensionCount;
143 vkEnumerateDeviceExtensionProperties(
device,
nullptr, &extensionCount,
nullptr);
144 std::vector<VkExtensionProperties> availableExtensions(extensionCount);
145 vkEnumerateDeviceExtensionProperties(
device,
nullptr, &extensionCount, availableExtensions.data());
147 std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
148 for (
const auto& extension : availableExtensions) {
149 requiredExtensions.erase(extension.extensionName);
151 extensionsSupported = requiredExtensions.empty();
153 bool swapChainAdequate =
false;
154 if (extensionsSupported) {
156 swapChainAdequate = !swapChainSupport.
formats.empty() && !swapChainSupport.
presentModes.empty();
159 return indices.
isComplete() && extensionsSupported && swapChainAdequate;
163 VkShaderModuleCreateInfo createInfo{};
164 createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
165 createInfo.codeSize = code.size();
166 createInfo.pCode =
reinterpret_cast<const uint32_t*
>(code.data());
167 VkShaderModule shaderModule;
168 if (vkCreateShaderModule(
device, &createInfo,
nullptr, &shaderModule) != VK_SUCCESS) {
176 uint32_t queueFamilyCount = 0;
177 vkGetPhysicalDeviceQueueFamilyProperties(
device, &queueFamilyCount,
nullptr);
178 std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
179 vkGetPhysicalDeviceQueueFamilyProperties(
device, &queueFamilyCount, queueFamilies.data());
182 for (
const auto& queueFamily : queueFamilies) {
183 if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
186 VkBool32 presentSupport =
false;
187 vkGetPhysicalDeviceSurfaceSupportKHR(
device, i,
surface, &presentSupport);
188 if (presentSupport) {
200 if (availableFormats.empty()) {
203 for (
const auto& availableFormat : availableFormats) {
204 if (availableFormat.format == VK_FORMAT_R8G8B8A8_UNORM &&
205 availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
206 return availableFormat;
209 return availableFormats[0];
213 for (
const auto& availablePresentMode : availablePresentModes) {
214 if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
215 return availablePresentMode;
218 return VK_PRESENT_MODE_FIFO_KHR;
222 if (capabilities.currentExtent.width != UINT32_MAX) {
223 return capabilities.currentExtent;
225 VkExtent2D actualExtent = {
static_cast<uint32_t
>(
w),
static_cast<uint32_t
>(
h) };
226 actualExtent.width = std::max(capabilities.minImageExtent.width,
227 std::min(capabilities.maxImageExtent.width, actualExtent.width));
228 actualExtent.height = std::max(capabilities.minImageExtent.height,
229 std::min(capabilities.maxImageExtent.height, actualExtent.height));
237 while (SDL_PollEvent(&e)) {
247 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
248 VkDebugUtilsMessageTypeFlagsEXT messageType,
249 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
251 if (pCallbackData && pCallbackData->pMessage &&
252 std::strstr(pCallbackData->pMessage,
"VK_LAYER_VKBASALT_post_processing") &&
253 std::strstr(pCallbackData->pMessage,
"duplicate")) {
256 std::cerr <<
"[Vulkan Validation] " << pCallbackData->pMessage << std::endl;
262 if (volkInitialize() != VK_SUCCESS) {
267 VkApplicationInfo appInfo{};
268 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
269 appInfo.pApplicationName =
"VulkanApp";
270 appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
271 appInfo.apiVersion = VK_API_VERSION_1_3;
273 unsigned int sdlExtensionCount = 0;
274 SDL_Vulkan_GetInstanceExtensions(
window, &sdlExtensionCount,
nullptr);
275 std::vector<const char*> extensions(sdlExtensionCount);
276 SDL_Vulkan_GetInstanceExtensions(
window, &sdlExtensionCount, extensions.data());
278 const std::vector<const char*> validationLayers = {
279 "VK_LAYER_KHRONOS_validation"
281 std::vector<const char*> debugExtensions = {
282 VK_EXT_DEBUG_UTILS_EXTENSION_NAME
287 extensions.insert(extensions.end(), debugExtensions.begin(), debugExtensions.end());
292 extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
295 bool layersSupported =
true;
297 vkEnumerateInstanceLayerProperties(&layerCount,
nullptr);
298 std::vector<VkLayerProperties> availableLayers(layerCount);
299 vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
301 for (
const char* layerName : validationLayers) {
302 bool layerFound =
false;
303 for (
const auto& layer : availableLayers) {
304 if (strcmp(layerName, layer.layerName) == 0) {
310 layersSupported =
false;
315 VkInstanceCreateInfo createInfo{};
316 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
317 createInfo.pApplicationInfo = &appInfo;
318 createInfo.enabledExtensionCount =
static_cast<uint32_t
>(extensions.size());
319 createInfo.ppEnabledExtensionNames = extensions.data();
322 createInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
326 createInfo.enabledLayerCount =
static_cast<uint32_t
>(validationLayers.size());
327 createInfo.ppEnabledLayerNames = validationLayers.data();
330 createInfo.enabledLayerCount = 0;
333 VkDebugUtilsMessengerCreateInfoEXT messengerInfo{};
336 messengerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
337 messengerInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
338 VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
339 messengerInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
340 VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
341 VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
343 createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &messengerInfo;
348 uint32_t instanceVersion = 0;
349 vkEnumerateInstanceVersion(&instanceVersion);
350 std::cout <<
"Vulkan Instance Version: "
351 << VK_VERSION_MAJOR(instanceVersion) <<
"."
352 << VK_VERSION_MINOR(instanceVersion) <<
"."
353 << VK_VERSION_PATCH(instanceVersion) <<
"\n";
375 uint32_t formatCount = 0;
376 vkGetPhysicalDeviceSurfaceFormatsKHR(
device,
surface, &formatCount,
nullptr);
377 if (formatCount != 0) {
378 details.
formats.resize(formatCount);
382 uint32_t presentModeCount = 0;
383 vkGetPhysicalDeviceSurfacePresentModesKHR(
device,
surface, &presentModeCount,
nullptr);
384 if (presentModeCount != 0) {
392 uint32_t deviceCount = 0;
393 vkEnumeratePhysicalDevices(
instance, &deviceCount,
nullptr);
394 if (deviceCount == 0) {
395 throw mx::Exception(
"Failed to find GPUs with Vulkan support!");
397 std::vector<VkPhysicalDevice> devices(deviceCount);
398 vkEnumeratePhysicalDevices(
instance, &deviceCount, devices.data());
401 for (
const auto& deviceCandidate : devices) {
405 VkPhysicalDeviceProperties props;
406 vkGetPhysicalDeviceProperties(deviceCandidate, &props);
409 switch (props.deviceType) {
410 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: score = 4;
break;
411 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: score = 3;
break;
412 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: score = 2;
break;
413 case VK_PHYSICAL_DEVICE_TYPE_OTHER: score = 1;
break;
414 case VK_PHYSICAL_DEVICE_TYPE_CPU: score = 0;
break;
415 default: score = 0;
break;
418 std::cout <<
"Found Vulkan device: " << props.deviceName
419 <<
" (type=" << props.deviceType <<
", score=" << score <<
")\n";
421 if (score > bestScore) {
430 VkPhysicalDeviceProperties deviceProperties;
432 std::cout <<
"Selected device: " << deviceProperties.deviceName << std::endl;
437 std::set<uint32_t> uniqueQueueFamilies = {
441 std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
442 float queuePriority = 1.0f;
443 for (uint32_t queueFamily : uniqueQueueFamilies) {
444 VkDeviceQueueCreateInfo queueInfo{};
445 queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
446 queueInfo.queueFamilyIndex = queueFamily;
447 queueInfo.queueCount = 1;
448 queueInfo.pQueuePriorities = &queuePriority;
449 queueCreateInfos.push_back(queueInfo);
452 VkPhysicalDeviceFeatures supportedFeatures{};
455 VkPhysicalDeviceFeatures deviceFeatures{};
456 deviceFeatures.samplerAnisotropy = supportedFeatures.samplerAnisotropy;
461 std::cout <<
"[Vulkan] fillModeNonSolid not supported; wireframe pipeline will use fill mode fallback.\n";
464 std::vector<const char*> deviceExtensions = {
465 VK_KHR_SWAPCHAIN_EXTENSION_NAME
470 uint32_t extensionCount;
471 vkEnumerateDeviceExtensionProperties(
physicalDevice,
nullptr, &extensionCount,
nullptr);
472 std::vector<VkExtensionProperties> availableExtensions(extensionCount);
473 vkEnumerateDeviceExtensionProperties(
physicalDevice,
nullptr, &extensionCount, availableExtensions.data());
475 for (
const auto& ext : availableExtensions) {
476 if (strcmp(ext.extensionName,
"VK_KHR_portability_subset") == 0) {
477 deviceExtensions.push_back(
"VK_KHR_portability_subset");
483 VkDeviceCreateInfo createInfo{};
484 createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
485 createInfo.queueCreateInfoCount =
static_cast<uint32_t
>(queueCreateInfos.size());
486 createInfo.pQueueCreateInfos = queueCreateInfos.data();
487 createInfo.pEnabledFeatures = &deviceFeatures;
490 createInfo.enabledExtensionCount =
static_cast<uint32_t
>(deviceExtensions.size());
491 createInfo.ppEnabledExtensionNames = deviceExtensions.data();
511 uint32_t imageCount = swapChainSupport.
capabilities.minImageCount + 1;
513 imageCount > swapChainSupport.
capabilities.maxImageCount) {
514 imageCount = swapChainSupport.
capabilities.maxImageCount;
517 VkSwapchainCreateInfoKHR createInfo{};
518 createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
520 createInfo.minImageCount = imageCount;
521 createInfo.imageFormat = surfaceFormat.format;
522 createInfo.imageColorSpace = surfaceFormat.colorSpace;
523 createInfo.imageExtent = extent;
524 createInfo.imageArrayLayers = 1;
525 createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
530 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
531 createInfo.queueFamilyIndexCount = 2;
532 createInfo.pQueueFamilyIndices = queueFamilyIndices;
534 createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
535 createInfo.queueFamilyIndexCount = 0;
536 createInfo.pQueueFamilyIndices =
nullptr;
539 createInfo.preTransform = swapChainSupport.
capabilities.currentTransform;
540 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
541 createInfo.presentMode = presentMode;
542 createInfo.clipped = VK_TRUE;
543 createInfo.oldSwapchain = VK_NULL_HANDLE;
545 if (vkCreateSwapchainKHR(
device, &createInfo,
nullptr, &
swapChain) != VK_SUCCESS) {
562 VkImageViewCreateInfo createInfo{};
563 createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
565 createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
567 createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
568 createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
569 createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
570 createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
571 createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
572 createInfo.subresourceRange.baseMipLevel = 0;
573 createInfo.subresourceRange.levelCount = 1;
574 createInfo.subresourceRange.baseArrayLayer = 0;
575 createInfo.subresourceRange.layerCount = 1;
583 VkAttachmentDescription colorAttachment{};
585 colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
586 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
587 colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
588 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
589 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
590 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
591 colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
593 VkAttachmentDescription depthAttachment{};
595 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
596 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
597 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
598 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
599 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
600 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
601 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
603 VkAttachmentReference colorAttachmentRef{};
604 colorAttachmentRef.attachment = 0;
605 colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
607 VkAttachmentReference depthAttachmentRef{};
608 depthAttachmentRef.attachment = 1;
609 depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
611 VkSubpassDescription subpass{};
612 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
613 subpass.colorAttachmentCount = 1;
614 subpass.pColorAttachments = &colorAttachmentRef;
615 subpass.pDepthStencilAttachment = &depthAttachmentRef;
617 VkSubpassDependency dependency{};
618 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
619 dependency.dstSubpass = 0;
620 dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
621 dependency.srcAccessMask = 0;
622 dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
623 dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
625 std::array<VkAttachmentDescription, 2> attachments = {colorAttachment, depthAttachment};
626 VkRenderPassCreateInfo renderPassInfo{};
627 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
628 renderPassInfo.attachmentCount =
static_cast<uint32_t
>(attachments.size());
629 renderPassInfo.pAttachments = attachments.data();
630 renderPassInfo.subpassCount = 1;
631 renderPassInfo.pSubpasses = &subpass;
632 renderPassInfo.dependencyCount = 1;
633 renderPassInfo.pDependencies = &dependency;
635 if (vkCreateRenderPass(
device, &renderPassInfo,
nullptr, &
renderPass) != VK_SUCCESS) {
645 std::array<VkImageView, 2> attachments = {
649 VkFramebufferCreateInfo framebufferInfo{};
650 framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
652 framebufferInfo.attachmentCount =
static_cast<uint32_t
>(attachments.size());
653 framebufferInfo.pAttachments = attachments.data();
656 framebufferInfo.layers = 1;
665 VkCommandPoolCreateInfo poolInfo{};
666 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
667 poolInfo.queueFamilyIndex = queueFamilyIndices.
graphicsFamily.value();
668 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
669 if (vkCreateCommandPool(
device, &poolInfo,
nullptr, &
commandPool) != VK_SUCCESS) {
676 VkCommandBufferAllocateInfo allocInfo{};
677 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
679 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
680 allocInfo.commandBufferCount =
static_cast<uint32_t
>(
commandBuffers.size());
686 std::cout <<
">> [CommandBuffers] Allocated " <<
commandBuffers.size() <<
" command buffers\n";
690 VkSemaphoreCreateInfo semaphoreInfo{};
691 semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
693 VkFenceCreateInfo fenceInfo{};
694 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
695 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
700 throw mx::Exception(
"Failed to create synchronization objects!");
705 std::cout <<
">> [CleanupSwapChain] Releasing swapchain-dependent resources...\n";
709 if (framebuffer != VK_NULL_HANDLE) {
710 vkDestroyFramebuffer(
device, framebuffer,
nullptr);
716 std::cout <<
" Command buffers: " <<
commandBuffers.size() <<
"\n";
722 std::cout <<
" Destroying graphics pipeline\n";
727 std::cout <<
" Destroying matrix graphics pipeline\n";
732 std::cout <<
" Destroying pipeline layout\n";
737 std::cout <<
" Destroying render pass\n";
743 if (imageView != VK_NULL_HANDLE) {
744 vkDestroyImageView(
device, imageView,
nullptr);
750 std::cout <<
" Destroying swapchain\n";
768 std::cout <<
">> [CleanupSwapChain] Complete\n";
772 for (VkFormat format : candidates) {
773 VkFormatProperties props;
774 vkGetPhysicalDeviceFormatProperties(
physicalDevice, format, &props);
775 if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) {
777 }
else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) {
786 {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT},
787 VK_IMAGE_TILING_OPTIMAL,
788 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
799 VK_IMAGE_TILING_OPTIMAL,
800 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
801 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
814 std::vector<Vertex> vertices = {
816 {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
817 {{ 1.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}},
818 {{ 1.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
819 {{-1.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}},
822 std::vector<uint32_t> indices = {
827 indexCount =
static_cast<uint32_t
>(indices.size());
830 VkDeviceSize vertexBufferSize =
sizeof(
Vertex) * vertices.size();
832 VkBuffer stagingBuffer;
833 VkDeviceMemory stagingBufferMemory;
834 createBuffer(vertexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
835 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
836 stagingBuffer, stagingBufferMemory);
839 vkMapMemory(
device, stagingBufferMemory, 0, vertexBufferSize, 0, &data);
840 memcpy(data, vertices.data(), vertexBufferSize);
841 vkUnmapMemory(
device, stagingBufferMemory);
843 createBuffer(vertexBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
848 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
849 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
852 VkDeviceSize indexBufferSize =
sizeof(uint32_t) * indices.size();
854 createBuffer(indexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
855 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
856 stagingBuffer, stagingBufferMemory);
858 vkMapMemory(
device, stagingBufferMemory, 0, indexBufferSize, 0, &data);
859 memcpy(data, indices.data(), indexBufferSize);
860 vkUnmapMemory(
device, stagingBufferMemory);
862 createBuffer(indexBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
867 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
868 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
870 std::cout <<
">> [VertexBuffer] Created full-screen quad (4 vertices, 6 indices)\n";
872 void VKWindow::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
873 VkBufferCreateInfo bufferInfo{};
874 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
875 bufferInfo.size = size;
876 bufferInfo.usage = usage;
877 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
878 if (vkCreateBuffer(
device, &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
881 VkMemoryRequirements memRequirements;
882 vkGetBufferMemoryRequirements(
device, buffer, &memRequirements);
883 VkMemoryAllocateInfo allocInfo{};
884 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
885 allocInfo.allocationSize = memRequirements.size;
886 allocInfo.memoryTypeIndex =
findMemoryType(memRequirements.memoryTypeBits, properties);
887 if (vkAllocateMemory(
device, &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
890 vkBindBufferMemory(
device, buffer, bufferMemory, 0);
894 VkPhysicalDeviceMemoryProperties memProperties;
895 vkGetPhysicalDeviceMemoryProperties(
physicalDevice, &memProperties);
896 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
897 if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
905 VkCommandBufferAllocateInfo allocInfo{};
906 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
907 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
909 allocInfo.commandBufferCount = 1;
911 VkCommandBuffer commandBuffer;
914 VkCommandBufferBeginInfo beginInfo{};
915 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
916 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
920 VkBufferCopy copyRegion{};
921 copyRegion.size = size;
922 vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region);
926 VkSubmitInfo submitInfo{};
927 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
928 submitInfo.commandBufferCount = 1;
929 submitInfo.pCommandBuffers = &commandBuffer;
954 SDL_Surface*
surface = SDL_ConvertSurfaceFormat(surfacex, SDL_PIXELFORMAT_RGBA8888, 0);
960 VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(
width) *
static_cast<VkDeviceSize
>(
height) * 4;
961 VkBuffer stagingBuffer;
962 VkDeviceMemory stagingBufferMemory;
963 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
964 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
965 stagingBuffer, stagingBufferMemory);
966 void* data =
nullptr;
971 uint8_t* dst =
reinterpret_cast<uint8_t*
>(data);
972 const uint8_t* src =
reinterpret_cast<const uint8_t*
>(
surface->pixels);
973 const size_t rowBytes =
static_cast<size_t>(
width) * 4;
974 for (
int y = 0; y <
surface->h; ++y) {
975 memcpy(dst +
static_cast<size_t>(y) * rowBytes, src +
static_cast<size_t>(y) *
static_cast<size_t>(
surface->pitch), rowBytes);
980 vkUnmapMemory(
device, stagingBufferMemory);
982 VkFormat textureFormat = VK_FORMAT_R8G8B8A8_UNORM;
983 createImage(
static_cast<uint32_t
>(
width),
static_cast<uint32_t
>(
height), textureFormat, VK_IMAGE_TILING_OPTIMAL,
984 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
991 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
992 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
995 void VKWindow::createImage(uint32_t
width, uint32_t
height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
996 VkImageCreateInfo imageInfo{};
997 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
998 imageInfo.imageType = VK_IMAGE_TYPE_2D;
999 imageInfo.extent.width =
width;
1000 imageInfo.extent.height =
height;
1001 imageInfo.extent.depth = 1;
1002 imageInfo.mipLevels = 1;
1003 imageInfo.arrayLayers = 1;
1004 imageInfo.format = format;
1005 imageInfo.tiling = tiling;
1006 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1007 imageInfo.usage = usage;
1008 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1009 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1010 imageInfo.flags = 0;
1011 if (vkCreateImage(
device, &imageInfo,
nullptr, &image) != VK_SUCCESS) {
1014 VkMemoryRequirements memRequirements;
1015 vkGetImageMemoryRequirements(
device, image, &memRequirements);
1016 VkMemoryAllocateInfo allocInfo{};
1017 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1018 allocInfo.allocationSize = memRequirements.size;
1019 allocInfo.memoryTypeIndex =
findMemoryType(memRequirements.memoryTypeBits, properties);
1020 if (vkAllocateMemory(
device, &allocInfo,
nullptr, &imageMemory) != VK_SUCCESS) {
1023 vkBindImageMemory(
device, image, imageMemory, 0);
1028 VkImageMemoryBarrier barrier{};
1029 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1030 barrier.oldLayout = oldLayout;
1031 barrier.newLayout = newLayout;
1032 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1033 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1034 barrier.image = image;
1035 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1036 barrier.subresourceRange.baseMipLevel = 0;
1037 barrier.subresourceRange.levelCount = 1;
1038 barrier.subresourceRange.baseArrayLayer = 0;
1039 barrier.subresourceRange.layerCount = 1;
1041 VkPipelineStageFlags sourceStage;
1042 VkPipelineStageFlags destinationStage;
1044 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1045 barrier.srcAccessMask = 0;
1046 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1047 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1048 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1050 else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1051 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1052 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1053 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1054 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1056 else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1057 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
1058 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1059 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1060 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1064 vkCmdPipelineBarrier(
1066 sourceStage, destinationStage,
1083 VkFormat textureFormat = VK_FORMAT_R8G8B8A8_UNORM;
1085 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1093 VkBuffer stagingBuffer;
1094 VkDeviceMemory stagingBufferMemory;
1095 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
1096 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1097 stagingBuffer, stagingBufferMemory);
1100 memcpy(data, pixels,
static_cast<size_t>(imageSize));
1101 vkUnmapMemory(
device, stagingBufferMemory);
1102 VkFormat textureFormat = VK_FORMAT_R8G8B8A8_UNORM;
1113 vkDestroyBuffer(
device, stagingBuffer,
nullptr);
1114 vkFreeMemory(
device, stagingBufferMemory,
nullptr);
1120 VkBufferImageCopy region{};
1121 region.bufferOffset = 0;
1122 region.bufferRowLength = 0;
1123 region.bufferImageHeight = 0;
1124 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1125 region.imageSubresource.mipLevel = 0;
1126 region.imageSubresource.baseArrayLayer = 0;
1127 region.imageSubresource.layerCount = 1;
1128 region.imageOffset = { 0, 0, 0 };
1129 region.imageExtent = {
1135 vkCmdCopyBufferToImage(
1139 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1148 VkCommandBufferAllocateInfo allocInfo{};
1149 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1150 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1152 allocInfo.commandBufferCount = 1;
1154 VkCommandBuffer commandBuffer;
1157 VkCommandBufferBeginInfo beginInfo{};
1158 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1159 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1163 return commandBuffer;
1169 VkSubmitInfo submitInfo{};
1170 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1171 submitInfo.commandBufferCount = 1;
1172 submitInfo.pCommandBuffers = &commandBuffer;
1185 VkImageViewCreateInfo viewInfo{};
1186 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1187 viewInfo.image = image;
1188 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1189 viewInfo.format = format;
1190 viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
1191 viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
1192 viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
1193 viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
1194 viewInfo.subresourceRange.aspectMask = aspectFlags;
1195 viewInfo.subresourceRange.baseMipLevel = 0;
1196 viewInfo.subresourceRange.levelCount = 1;
1197 viewInfo.subresourceRange.baseArrayLayer = 0;
1198 viewInfo.subresourceRange.layerCount = 1;
1200 VkImageView imageView;
1201 if (vkCreateImageView(
device, &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
1202 throw mx::Exception(
"Failed to create texture image view!");
1208 VkSamplerCreateInfo samplerInfo{};
1209 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1210 samplerInfo.magFilter = VK_FILTER_LINEAR;
1211 samplerInfo.minFilter = VK_FILTER_LINEAR;
1212 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1213 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1214 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1215 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1216 samplerInfo.anisotropyEnable = VK_TRUE;
1217 samplerInfo.maxAnisotropy = 16.0f;
1218 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1219 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1220 samplerInfo.compareEnable = VK_FALSE;
1221 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1229 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1230 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1231 poolSizes[0].descriptorCount =
static_cast<uint32_t
>(
swapChainImages.size() * 2);
1232 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1233 poolSizes[1].descriptorCount =
static_cast<uint32_t
>(
swapChainImages.size());
1235 VkDescriptorPoolCreateInfo poolInfo{};
1236 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1237 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1238 poolInfo.pPoolSizes = poolSizes.data();
1240 std::cout <<
">> [DescriptorPool] Number of swap chain images: "
1242 std::cout <<
">> [DescriptorPool] Device address: " <<
device <<
"\n";
1243 std::cout <<
">> [DescriptorPool] Instance address: " <<
instance <<
"\n";
1246 if (result != VK_SUCCESS) {
1247 SDL_Log(
"Failed to create descriptor pool! VkResult: %d\n", result);
1250 SDL_Log(
"Descriptor pool created successfully.\n");
1256 std::cout <<
">> [DescriptorSets] Starting descriptor set creation...\n";
1264 VkDescriptorSetAllocateInfo allocInfo = {};
1265 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1267 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(
swapChainImages.size());
1268 allocInfo.pSetLayouts = layouts.data();
1273 if (result != VK_SUCCESS) {
1274 throw mx::Exception(
"Failed to allocate descriptor sets! Result: " + std::to_string(result));
1276 std::cout <<
">> [DescriptorSets] Successfully allocated descriptor sets\n";
1278 VkDescriptorImageInfo imageInfo = {};
1279 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1283 VkDescriptorBufferInfo bufferInfo = {};
1285 bufferInfo.offset = 0;
1287 std::array<VkWriteDescriptorSet, 2> descriptorWrites{};
1288 descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1290 descriptorWrites[0].dstBinding = 0;
1291 descriptorWrites[0].dstArrayElement = 0;
1292 descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1293 descriptorWrites[0].descriptorCount = 1;
1294 descriptorWrites[0].pImageInfo = &imageInfo;
1295 descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1297 descriptorWrites[1].dstBinding = 1;
1298 descriptorWrites[1].dstArrayElement = 0;
1299 descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1300 descriptorWrites[1].descriptorCount = 1;
1301 descriptorWrites[1].pBufferInfo = &bufferInfo;
1302 vkUpdateDescriptorSets(
device,
static_cast<uint32_t
>(descriptorWrites.size()), descriptorWrites.data(), 0,
nullptr);
1303 std::cout <<
">> [DescriptorSets] Updated descriptor set " << i <<
"\n";
1306 std::cout <<
">> [DescriptorSets] Completed descriptor set creation and updates\n";
1308 catch (
const std::exception& e) {
1309 SDL_Log(
"Exception in createDescriptorSets: %s", e.what());
1315 VkDescriptorImageInfo imageInfo{};
1316 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1320 VkWriteDescriptorSet descriptorWrite{};
1321 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1323 descriptorWrite.dstBinding = 0;
1324 descriptorWrite.dstArrayElement = 0;
1325 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1326 descriptorWrite.descriptorCount = 1;
1327 descriptorWrite.pImageInfo = &imageInfo;
1329 vkUpdateDescriptorSets(
device, 1, &descriptorWrite, 0,
nullptr);
1340 for (
size_t i = 0; i < imageCount; i++) {
1342 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
1343 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1353 std::cout <<
"\n>> [GraphicsPipeline] Creating graphics pipeline...\n";
1354 std::cout <<
" Device = " <<
device <<
"\n";
1357 if (
device == VK_NULL_HANDLE) {
1358 throw mx::Exception(
"Device is invalid in createGraphicsPipeline()");
1365 if (vertShaderModule == VK_NULL_HANDLE) {
1366 throw mx::Exception(
"Failed to create vertex shader module!");
1370 if (fragShaderModule == VK_NULL_HANDLE) {
1371 throw mx::Exception(
"Failed to create fragment shader module!");
1374 std::cout <<
" SPIR-V shaders loaded successfully.\n";
1376 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
1377 vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1378 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
1379 vertShaderStageInfo.module = vertShaderModule;
1380 vertShaderStageInfo.pName =
"main";
1382 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
1383 fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1384 fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1385 fragShaderStageInfo.module = fragShaderModule;
1386 fragShaderStageInfo.pName =
"main";
1388 VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
1390 VkVertexInputBindingDescription bindingDescription{};
1391 bindingDescription.binding = 0;
1392 bindingDescription.stride =
sizeof(
Vertex);
1393 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1396 std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};
1399 attributeDescriptions[0].binding = 0;
1400 attributeDescriptions[0].location = 0;
1401 attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1402 attributeDescriptions[0].offset = offsetof(
Vertex, pos);
1405 attributeDescriptions[1].binding = 0;
1406 attributeDescriptions[1].location = 1;
1407 attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
1408 attributeDescriptions[1].offset = offsetof(
Vertex, texCoord);
1411 attributeDescriptions[2].binding = 0;
1412 attributeDescriptions[2].location = 2;
1413 attributeDescriptions[2].format = VK_FORMAT_R32G32B32_SFLOAT;
1414 attributeDescriptions[2].offset = offsetof(
Vertex, normal);
1416 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
1417 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1418 vertexInputInfo.vertexBindingDescriptionCount = 1;
1419 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
1420 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributeDescriptions.size());
1421 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
1423 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1424 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1425 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1426 inputAssembly.primitiveRestartEnable = VK_FALSE;
1428 VkPipelineViewportStateCreateInfo viewportState{};
1429 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1430 viewportState.viewportCount = 1;
1431 viewportState.pViewports =
nullptr;
1432 viewportState.scissorCount = 1;
1433 viewportState.pScissors =
nullptr;
1435 std::array<VkDynamicState, 2> baseDynamicStates = {
1436 VK_DYNAMIC_STATE_VIEWPORT,
1437 VK_DYNAMIC_STATE_SCISSOR
1439 VkPipelineDynamicStateCreateInfo baseDynamicState{};
1440 baseDynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1441 baseDynamicState.dynamicStateCount =
static_cast<uint32_t
>(baseDynamicStates.size());
1442 baseDynamicState.pDynamicStates = baseDynamicStates.data();
1444 VkPipelineRasterizationStateCreateInfo rasterizer{};
1445 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1446 rasterizer.depthClampEnable = VK_FALSE;
1447 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1448 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1449 rasterizer.lineWidth = 1.0f;
1450 rasterizer.cullMode = VK_CULL_MODE_NONE;
1451 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1452 rasterizer.depthBiasEnable = VK_FALSE;
1454 VkPipelineMultisampleStateCreateInfo multisampling{};
1455 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1456 multisampling.sampleShadingEnable = VK_FALSE;
1457 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1459 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1460 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1461 depthStencil.depthTestEnable = VK_TRUE;
1462 depthStencil.depthWriteEnable = VK_TRUE;
1463 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1464 depthStencil.depthBoundsTestEnable = VK_FALSE;
1465 depthStencil.stencilTestEnable = VK_FALSE;
1467 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1468 colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1469 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1470 colorBlendAttachment.blendEnable = VK_FALSE;
1472 VkPipelineColorBlendStateCreateInfo colorBlending{};
1473 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1474 colorBlending.logicOpEnable = VK_FALSE;
1475 colorBlending.attachmentCount = 1;
1476 colorBlending.pAttachments = &colorBlendAttachment;
1478 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
1479 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1480 pipelineLayoutInfo.setLayoutCount = 1;
1482 pipelineLayoutInfo.pushConstantRangeCount = 0;
1483 pipelineLayoutInfo.pPushConstantRanges =
nullptr;
1485 if (vkCreatePipelineLayout(
device, &pipelineLayoutInfo,
nullptr, &
pipelineLayout) != VK_SUCCESS) {
1489 std::cout <<
" Pipeline Layout created (links to descriptor set layout)\n";
1491 VkGraphicsPipelineCreateInfo pipelineInfo{};
1492 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1493 pipelineInfo.stageCount = 2;
1494 pipelineInfo.pStages = shaderStages;
1495 pipelineInfo.pVertexInputState = &vertexInputInfo;
1496 pipelineInfo.pInputAssemblyState = &inputAssembly;
1497 pipelineInfo.pViewportState = &viewportState;
1498 pipelineInfo.pRasterizationState = &rasterizer;
1499 pipelineInfo.pMultisampleState = &multisampling;
1500 pipelineInfo.pDepthStencilState = &depthStencil;
1501 pipelineInfo.pColorBlendState = &colorBlending;
1502 pipelineInfo.pDynamicState = &baseDynamicState;
1505 pipelineInfo.subpass = 0;
1506 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1508 if (vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &
graphicsPipeline) != VK_SUCCESS) {
1512 rasterizer.polygonMode =
supportsWireFrame ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL;
1514 throw mx::Exception(
"Failed to create Matrix graphics pipeline!");
1517 std::cout <<
" Graphics Pipeline created successfully!\n";
1518 std::cout <<
" Matrix Pipeline created successfully!\n";
1519 std::cout <<
">> [GraphicsPipeline] Complete\n\n";
1522 vkDestroyShaderModule(
device, fragShaderModule,
nullptr);
1523 vkDestroyShaderModule(
device, vertShaderModule,
nullptr);
1526 SDL_Log(
"Exception in createGraphicsPipeline: %s\n", e.
text().c_str());
1532 uint32_t imageIndex = 0;
1534 VkResult result = vkAcquireNextImageKHR(
1543 if (result == VK_ERROR_OUT_OF_DATE_KHR) {
1546 }
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
1552 VkCommandBufferBeginInfo beginInfo{};
1553 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1555 if (vkBeginCommandBuffer(
commandBuffers[imageIndex], &beginInfo) != VK_SUCCESS) {
1556 throw mx::Exception(
"Failed to begin recording command buffer!");
1559 VkRenderPassBeginInfo renderPassInfo{};
1560 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1563 renderPassInfo.renderArea.offset = {0, 0};
1566 std::array<VkClearValue, 2> clearValues{};
1567 clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
1568 clearValues[1].depthStencil = {1.0f, 0};
1570 renderPassInfo.clearValueCount =
static_cast<uint32_t
>(clearValues.size());
1571 renderPassInfo.pClearValues = clearValues.data();
1575 vkCmdBeginRenderPass(cmd, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
1577 VkViewport viewport{};
1582 viewport.minDepth = 0.0f;
1583 viewport.maxDepth = 1.0f;
1586 scissor.offset = {0, 0};
1589 vkCmdSetViewport(cmd, 0, 1, &viewport);
1590 vkCmdSetScissor(cmd, 0, 1, &scissor);
1593 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
spritePipeline);
1595 for (
auto &sprite :
sprites) {
1596 if (!sprite)
continue;
1598 sprite->renderSprites(
1608 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
textPipeline);
1618 vkCmdEndRenderPass(cmd);
1620 if (vkEndCommandBuffer(cmd) != VK_SUCCESS) {
1624 VkSubmitInfo submitInfo{};
1625 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1628 VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
1629 submitInfo.waitSemaphoreCount = 1;
1630 submitInfo.pWaitSemaphores = waitSemaphores;
1631 submitInfo.pWaitDstStageMask = waitStages;
1633 submitInfo.commandBufferCount = 1;
1634 submitInfo.pCommandBuffers = &cmd;
1637 submitInfo.signalSemaphoreCount = 1;
1638 submitInfo.pSignalSemaphores = signalSemaphores;
1640 VkResult submitResult = vkQueueSubmit(
graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
1641 if (submitResult != VK_SUCCESS) {
1642 std::cerr <<
"vkQueueSubmit failed with VkResult: " << submitResult << std::endl;
1643 throw mx::Exception(
"Failed to submit draw command buffer!");
1646 VkPresentInfoKHR presentInfo{};
1647 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
1648 presentInfo.waitSemaphoreCount = 1;
1649 presentInfo.pWaitSemaphores = signalSemaphores;
1650 presentInfo.swapchainCount = 1;
1652 presentInfo.pImageIndices = &imageIndex;
1654 result = vkQueuePresentKHR(
presentQueue, &presentInfo);
1656 if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
1658 }
else if (result != VK_SUCCESS) {
1666 for (
auto &sprite :
sprites) {
1667 if (sprite) sprite->clearQueue();
1673 std::cout <<
">> [Cleanup] Releasing Vulkan resources...\n";
1674 vkDeviceWaitIdle(
device);
1677 std::cout <<
" Releasing text renderer\n";
1682 std::cout <<
" Releasing sprites: " <<
sprites.size() <<
"\n";
1713 std::cout <<
" Releasing uniform buffers: " <<
uniformBuffers.size() <<
"\n";
1746 if (
device != VK_NULL_HANDLE) {
1747 std::cout <<
" Destroying logical device\n";
1748 vkDestroyDevice(
device,
nullptr);
1750 if (
surface != VK_NULL_HANDLE) {
1751 std::cout <<
" Destroying surface\n";
1756 std::cout <<
" Destroying debug messenger\n";
1760 std::cout <<
" Destroying Vulkan instance\n";
1761 vkDestroyInstance(
instance,
nullptr);
1769 std::cout <<
" Destroying SDL window and quitting SDL\n";
1770 SDL_DestroyWindow(
window);
1772 SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK);
1778 std::cout <<
">> [Cleanup] Complete\n";
1783 vkDeviceWaitIdle(
device);
1785 SDL_SetWindowFullscreen(
window, SDL_WINDOW_FULLSCREEN_DESKTOP);
1787 SDL_SetWindowFullscreen(
window, 0);
1790 SDL_Vulkan_GetDrawableSize(
window, &newW, &newH);
1791 if (newW > 0 && newH > 0) {
1799 vkDeviceWaitIdle(
device);
1801 SDL_Vulkan_GetDrawableSize(
window, &
w, &
h);
1826 for (
auto &sprite :
sprites) {
1830 sprite->rebuildPipeline();
1831 sprite->rebuildInstancedPipeline();
1846 if (!fragmentShaderPath.empty()) {
1848 sprite->setVertexShaderPath(vertexShader);
1850 sprite->loadSprite(pngPath, fragmentShaderPath);
1857 sprites.push_back(std::move(sprite));
1867 if (!fragmentShaderPath.empty()) {
1869 sprite->setVertexShaderPath(vertexShader);
1871 sprite->loadSprite(
surface, fragmentShaderPath);
1877 sprites.push_back(std::move(sprite));
1887 if (!fragmentShaderPath.empty()) {
1889 sprite->setVertexShaderPath(vertexShader);
1891 sprite->createEmptySprite(
width,
height, vertexShader, fragmentShaderPath);
1897 sprites.push_back(std::move(sprite));
1902 VkDescriptorSetLayoutBinding samplerLayoutBinding{};
1903 samplerLayoutBinding.binding = 0;
1904 samplerLayoutBinding.descriptorCount = 1;
1905 samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1906 samplerLayoutBinding.pImmutableSamplers =
nullptr;
1907 samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1909 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1910 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1911 layoutInfo.bindingCount = 1;
1912 layoutInfo.pBindings = &samplerLayoutBinding;
1919 auto spriteVertShaderCode =
readFile(
util.getFilePath(
"data/sprite_vert.spv"));
1920 auto spriteFragShaderCode =
readFile(
util.getFilePath(
"data/sprite_frag.spv"));
1925 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
1926 vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1927 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
1928 vertShaderStageInfo.module = spriteVertShaderModule;
1929 vertShaderStageInfo.pName =
"main";
1931 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
1932 fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1933 fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1934 fragShaderStageInfo.module = spriteFragShaderModule;
1935 fragShaderStageInfo.pName =
"main";
1937 VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
1939 VkVertexInputBindingDescription bindingDescription{};
1940 bindingDescription.binding = 0;
1941 bindingDescription.stride =
sizeof(float) * 4;
1942 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1944 std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
1945 attributeDescriptions[0].binding = 0;
1946 attributeDescriptions[0].location = 0;
1947 attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
1948 attributeDescriptions[0].offset = 0;
1950 attributeDescriptions[1].binding = 0;
1951 attributeDescriptions[1].location = 1;
1952 attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
1953 attributeDescriptions[1].offset =
sizeof(float) * 2;
1955 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
1956 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1957 vertexInputInfo.vertexBindingDescriptionCount = 1;
1958 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
1959 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributeDescriptions.size());
1960 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
1962 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1963 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1964 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1965 inputAssembly.primitiveRestartEnable = VK_FALSE;
1967 std::vector<VkDynamicState> dynamicStates = {
1968 VK_DYNAMIC_STATE_VIEWPORT,
1969 VK_DYNAMIC_STATE_SCISSOR
1972 VkPipelineDynamicStateCreateInfo dynamicState{};
1973 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1974 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1975 dynamicState.pDynamicStates = dynamicStates.data();
1977 VkPipelineViewportStateCreateInfo viewportState{};
1978 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1979 viewportState.viewportCount = 1;
1980 viewportState.scissorCount = 1;
1982 VkPipelineRasterizationStateCreateInfo rasterizer{};
1983 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1984 rasterizer.depthClampEnable = VK_FALSE;
1985 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1986 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1987 rasterizer.lineWidth = 1.0f;
1988 rasterizer.cullMode = VK_CULL_MODE_NONE;
1989 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1990 rasterizer.depthBiasEnable = VK_FALSE;
1992 VkPipelineMultisampleStateCreateInfo multisampling{};
1993 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1994 multisampling.sampleShadingEnable = VK_FALSE;
1995 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1997 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1998 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1999 depthStencil.depthTestEnable = VK_FALSE;
2000 depthStencil.depthWriteEnable = VK_FALSE;
2001 depthStencil.depthCompareOp = VK_COMPARE_OP_ALWAYS;
2002 depthStencil.depthBoundsTestEnable = VK_FALSE;
2003 depthStencil.stencilTestEnable = VK_FALSE;
2005 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
2006 colorBlendAttachment.colorWriteMask =
2007 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
2008 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
2009 colorBlendAttachment.blendEnable = VK_TRUE;
2010 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
2011 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
2012 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
2013 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
2014 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
2015 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
2017 VkPipelineColorBlendStateCreateInfo colorBlending{};
2018 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
2019 colorBlending.logicOpEnable = VK_FALSE;
2020 colorBlending.attachmentCount = 1;
2021 colorBlending.pAttachments = &colorBlendAttachment;
2023 VkPushConstantRange pushConstantRange{};
2024 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
2025 pushConstantRange.offset = 0;
2026 pushConstantRange.size =
sizeof(float) * 12;
2028 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
2029 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
2030 pipelineLayoutInfo.setLayoutCount = 1;
2032 pipelineLayoutInfo.pushConstantRangeCount = 1;
2033 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
2037 VkGraphicsPipelineCreateInfo pipelineInfo{};
2038 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
2039 pipelineInfo.stageCount = 2;
2040 pipelineInfo.pStages = shaderStages;
2041 pipelineInfo.pVertexInputState = &vertexInputInfo;
2042 pipelineInfo.pInputAssemblyState = &inputAssembly;
2043 pipelineInfo.pViewportState = &viewportState;
2044 pipelineInfo.pRasterizationState = &rasterizer;
2045 pipelineInfo.pMultisampleState = &multisampling;
2046 pipelineInfo.pDepthStencilState = &depthStencil;
2047 pipelineInfo.pColorBlendState = &colorBlending;
2048 pipelineInfo.pDynamicState = &dynamicState;
2051 pipelineInfo.subpass = 0;
2052 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
2053 pipelineInfo.basePipelineIndex = -1;
2057 vkDestroyShaderModule(
device, spriteFragShaderModule,
nullptr);
2058 vkDestroyShaderModule(
device, spriteVertShaderModule,
nullptr);
2059 }
catch (
const std::exception& e) {
2060 std::cerr <<
"Warning: Sprite shader compilation skipped: " << e.what() << std::endl;
2066 VkDescriptorPoolSize poolSize{};
2067 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2068 poolSize.descriptorCount = 100;
2070 VkDescriptorPoolCreateInfo poolInfo{};
2071 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
2072 poolInfo.poolSizeCount = 1;
2073 poolInfo.pPoolSizes = &poolSize;
2074 poolInfo.maxSets = 100;
2075 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
2102 VkDescriptorSetLayoutBinding samplerLayoutBinding{};
2103 samplerLayoutBinding.binding = 0;
2104 samplerLayoutBinding.descriptorCount = 1;
2105 samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2106 samplerLayoutBinding.pImmutableSamplers =
nullptr;
2107 samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
2109 VkDescriptorSetLayoutCreateInfo layoutInfo{};
2110 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
2111 layoutInfo.bindingCount = 1;
2112 layoutInfo.pBindings = &samplerLayoutBinding;
2119 auto textVertShaderCode =
readFile(
util.getFilePath(
"data/text_vert.spv"));
2120 auto textFragShaderCode =
readFile(
util.getFilePath(
"data/text_frag.spv"));
2125 VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
2126 vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
2127 vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
2128 vertShaderStageInfo.module = textVertShaderModule;
2129 vertShaderStageInfo.pName =
"main";
2131 VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
2132 fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
2133 fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
2134 fragShaderStageInfo.module = textFragShaderModule;
2135 fragShaderStageInfo.pName =
"main";
2137 VkPipelineShaderStageCreateInfo shaderStages[] = { vertShaderStageInfo, fragShaderStageInfo };
2139 VkVertexInputBindingDescription bindingDescription{};
2140 bindingDescription.binding = 0;
2141 bindingDescription.stride =
sizeof(float) * 4;
2142 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
2144 std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
2145 attributeDescriptions[0].binding = 0;
2146 attributeDescriptions[0].location = 0;
2147 attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
2148 attributeDescriptions[0].offset = 0;
2150 attributeDescriptions[1].binding = 0;
2151 attributeDescriptions[1].location = 1;
2152 attributeDescriptions[1].format = VK_FORMAT_R32G32_SFLOAT;
2153 attributeDescriptions[1].offset =
sizeof(float) * 2;
2155 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
2156 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
2157 vertexInputInfo.vertexBindingDescriptionCount = 1;
2158 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
2159 vertexInputInfo.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributeDescriptions.size());
2160 vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
2162 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
2163 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
2164 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
2165 inputAssembly.primitiveRestartEnable = VK_FALSE;
2167 VkViewport viewport{};
2172 viewport.minDepth = 0.0f;
2173 viewport.maxDepth = 1.0f;
2176 scissor.offset = { 0, 0 };
2179 std::vector<VkDynamicState> dynamicStates = {
2180 VK_DYNAMIC_STATE_VIEWPORT,
2181 VK_DYNAMIC_STATE_SCISSOR
2184 VkPipelineDynamicStateCreateInfo dynamicState{};
2185 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
2186 dynamicState.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
2187 dynamicState.pDynamicStates = dynamicStates.data();
2189 VkPipelineViewportStateCreateInfo viewportState{};
2190 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
2191 viewportState.viewportCount = 1;
2192 viewportState.pViewports = &viewport;
2193 viewportState.scissorCount = 1;
2194 viewportState.pScissors = &scissor;
2196 VkPipelineRasterizationStateCreateInfo rasterizer{};
2197 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
2198 rasterizer.depthClampEnable = VK_FALSE;
2199 rasterizer.rasterizerDiscardEnable = VK_FALSE;
2200 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
2201 rasterizer.lineWidth = 1.0f;
2202 rasterizer.cullMode = VK_CULL_MODE_NONE;
2203 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
2204 rasterizer.depthBiasEnable = VK_FALSE;
2206 VkPipelineMultisampleStateCreateInfo multisampling{};
2207 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
2208 multisampling.sampleShadingEnable = VK_FALSE;
2209 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
2211 VkPipelineDepthStencilStateCreateInfo depthStencil{};
2212 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
2213 depthStencil.depthTestEnable = VK_FALSE;
2214 depthStencil.depthWriteEnable = VK_FALSE;
2215 depthStencil.depthCompareOp = VK_COMPARE_OP_ALWAYS;
2216 depthStencil.depthBoundsTestEnable = VK_FALSE;
2217 depthStencil.stencilTestEnable = VK_FALSE;
2219 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
2220 colorBlendAttachment.colorWriteMask =
2221 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
2222 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
2224 colorBlendAttachment.blendEnable = VK_TRUE;
2225 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
2226 colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
2227 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
2228 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
2229 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
2230 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
2232 VkPipelineColorBlendStateCreateInfo colorBlending{};
2233 colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
2234 colorBlending.logicOpEnable = VK_FALSE;
2235 colorBlending.attachmentCount = 1;
2236 colorBlending.pAttachments = &colorBlendAttachment;
2238 VkPushConstantRange pushConstantRange{};
2239 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
2240 pushConstantRange.offset = 0;
2241 pushConstantRange.size =
sizeof(float) * 2;
2243 VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
2244 pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
2245 pipelineLayoutInfo.setLayoutCount = 1;
2247 pipelineLayoutInfo.pushConstantRangeCount = 1;
2248 pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
2252 VkGraphicsPipelineCreateInfo pipelineInfo{};
2253 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
2254 pipelineInfo.stageCount = 2;
2255 pipelineInfo.pStages = shaderStages;
2256 pipelineInfo.pVertexInputState = &vertexInputInfo;
2257 pipelineInfo.pInputAssemblyState = &inputAssembly;
2258 pipelineInfo.pViewportState = &viewportState;
2259 pipelineInfo.pRasterizationState = &rasterizer;
2260 pipelineInfo.pMultisampleState = &multisampling;
2261 pipelineInfo.pDepthStencilState = &depthStencil;
2262 pipelineInfo.pColorBlendState = &colorBlending;
2263 pipelineInfo.pDynamicState = &dynamicState;
2266 pipelineInfo.subpass = 0;
2267 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
2271 vkDestroyShaderModule(
device, textFragShaderModule,
nullptr);
2272 vkDestroyShaderModule(
device, textVertShaderModule,
nullptr);
2274 catch (
const std::exception& e) {
2275 std::cerr <<
"Warning: Text shader compilation skipped: " << e.what() << std::endl;
2276 std::cerr <<
"Text rendering will be disabled. Compile text shaders with:" << std::endl;
2277 std::cerr <<
" glslc text_vertex.vert -o text_vert.spv" << std::endl;
2278 std::cerr <<
" glslc text_fragment.frag -o text_frag.spv" << std::endl;
2284 VkDescriptorPoolSize poolSize{};
2285 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2286 poolSize.descriptorCount =
static_cast<uint32_t
>(
swapChainImages.size());
2288 VkDescriptorPoolCreateInfo poolInfo{};
2289 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
2290 poolInfo.poolSizeCount = 1;
2291 poolInfo.pPoolSizes = &poolSize;
2302 vkDeviceWaitIdle(
device);
General-purpose exception class for the mx library.
std::string text() const
Retrieve the error message.
Vulkan 2-D sprite with texture, custom shader, and instancing support.
Renders SDL_ttf text into Vulkan image textures.
void setupTextureImage(uint32_t w, uint32_t h)
Allocate a blank texture image of the given dimensions.
VkCommandBuffer beginSingleTimeCommands()
Begin a one-shot command buffer for transfer operations.
VkDescriptorPool descriptorPool
Main descriptor pool.
void createVertexBuffer()
Create and upload the vertex buffer.
void updateDescriptorSets()
Write updated bindings into existing descriptor sets.
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities)
Determine the swapchain image extent from surface capabilities.
std::unique_ptr< VKText > textRenderer
Text rendering subsystem.
VkFormat findSupportedFormat(const std::vector< VkFormat > &candidates, VkImageTiling tiling, VkFormatFeatureFlags features)
Find a supported VkFormat from the candidate list.
VkImage depthImage
Depth/stencil image.
uint32_t indexCount
Number of indices in the quad.
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector< VkSurfaceFormatKHR > &availableFormats)
Choose the best surface format from available options.
void createDescriptorSetLayout()
Create the main descriptor set layout (UBO + sampler).
void createSurface()
Create the Vulkan surface from the SDL window.
void createTextDescriptorSetLayout()
Create the descriptor set layout for text rendering.
void initWindow(const std::string &title, int width, int height, bool full=false)
Open the SDL2 window (called automatically by constructor).
VkPipelineLayout pipelineLayout
Pipeline layout for the main pass.
VkPipeline graphicsPipelineMatrix
3-D matrix-enabled graphics pipeline.
std::vector< VkBuffer > uniformBuffers
Per-frame uniform buffers.
void createSyncObjects()
Create semaphores and fences for frame synchronisation.
std::vector< VkCommandBuffer > commandBuffers
Per-frame command buffers.
std::string font
Path to the current TTF font.
std::vector< std::unique_ptr< VKSprite > > sprites
Owned sprite instances.
VkPipelineLayout textPipelineLayout
Pipeline layout for text rendering.
void createTextPipeline()
Create the pipeline for text rendering.
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
Allocate a Vulkan buffer with the given usage and memory properties.
void recreateSwapChain()
Tear down and rebuild the swapchain and all dependent resources.
void quit()
Post a quit event to stop the loop.
VkFormat depthFormat
Format used for the depth buffer.
virtual void cleanup()
Free all Vulkan resources (overridable).
VkImageView textureImageView
View into the primary texture.
VkFence inFlightFence
CPU–GPU frame synchronisation fence.
uint32_t height
Internal width/height tracking.
void createCommandPool()
Create the graphics command pool.
VkDescriptorSetLayout spriteDescriptorSetLayout
Descriptor layout for sprites.
bool enableControllerInput()
Enable joystick and game controller subsystems on demand.
virtual void onResize()
Called when the window is resized (optional override).
VkDeviceMemory vertexBufferMemory
Memory for the vertex buffer.
int w
Current window width.
void setFont(const std::string &font, const int size=24)
Set the font used for printText().
std::vector< VkDescriptorSet > descriptorSets
Per-frame descriptor sets.
void createTextDescriptorPool()
Create the descriptor pool for text rendering.
virtual void initVulkan()
Initialise all Vulkan objects (overridable).
VkDeviceMemory depthImageMemory
Memory for the depth image.
void createImageViews()
Create image views for every swapchain image.
void clearTextQueue()
Clear all pending text-render entries.
VkPhysicalDevice physicalDevice
Selected GPU.
void createLogicalDevice()
Create the logical device and retrieve queue handles.
void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout)
Transition an image between two layouts using a pipeline barrier.
VkDevice device
Logical device.
VkPipelineLayout spritePipelineLayout
Pipeline layout for sprites.
VkShaderModule createShaderModule(const std::vector< char > &code)
Compile a SPIR-V shader module.
VkBuffer vertexBuffer
Full-screen quad vertex buffer.
VkQueue graphicsQueue
Graphics submission queue.
void endSingleTimeCommands(VkCommandBuffer commandBuffer)
End and submit a one-shot command buffer, then wait for completion.
void setFullScreen(const bool full)
Toggle fullscreen mode.
VkDeviceMemory textureImageMemory
Memory for the primary texture.
VKSprite * createSprite(const std::string &pngPath, const std::string &vertexShader, const std::string &fragmentShaderPath="")
Create a sprite from a PNG file.
VkImageLayout textureImageLayout
Current layout of the primary texture.
bool enableValidation
Enable Vulkan validation layers.
VkRenderPass renderPass
Main render pass.
void createDepthResources()
Create depth buffer image, memory, and view.
void createSpriteDescriptorPool()
Create the descriptor pool for sprite rendering.
VkImage textureImage
Primary texture image.
virtual void createSwapChain()
Create (or recreate) the swapchain and associated images (overridable).
void createCommandBuffers()
Allocate per-frame command buffers from the command pool.
bool active
true while the event loop should keep running.
bool isDeviceSuitable(VkPhysicalDevice device)
Check whether a physical device meets all application requirements.
void updateTexture(SDL_Surface *newSurface)
Replace the primary texture from an SDL_Surface.
std::vector< VkDeviceMemory > uniformBuffersMemory
Memory for per-frame UBOs.
void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
Copy data between two Vulkan buffers via a one-shot command.
std::vector< VkFramebuffer > swapChainFramebuffers
Framebuffers for each swapchain image.
virtual void cleanupSwapChain()
Destroy swapchain-dependent resources before recreation (overridable).
VkDescriptorSetLayout textDescriptorSetLayout
Descriptor layout for text.
void createSpritePipeline()
Create the pipeline for sprite rendering.
void loop()
Run the blocking event/render loop.
SDL_Window * window
SDL window handle.
std::vector< void * > uniformBuffersMapped
Persistently mapped UBO pointers.
std::vector< VkImage > swapChainImages
Swapchain image handles.
void createUniformBuffers()
Allocate per-frame uniform buffers and map them persistently.
VkPresentModeKHR chooseSwapPresentMode(const std::vector< VkPresentModeKHR > &availablePresentModes)
Choose the best present mode (prefers mailbox, falls back to FIFO).
void createFramebuffers()
Create framebuffers for each swapchain image view.
bool supportsWireFrame
Whether the device supports wireframe.
VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
Create an image view with specified format and aspect flags.
VkFormat findDepthFormat()
Select a depth buffer format supported by the physical device.
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
Copy a staging buffer into a Vulkan image.
VkQueue presentQueue
Presentation queue.
VkDescriptorPool textDescriptorPool
Descriptor pool for text.
int h
Current window height.
void createSpriteDescriptorSetLayout()
Create the descriptor set layout for sprite rendering.
VkSampler textureSampler
Sampler for the primary texture.
VkImageView depthImageView
View into the depth image.
virtual void proc()
Process one event/render iteration (overridable).
VkCommandPool commandPool
Command pool for graphics commands.
void createTextureSampler()
Create the texture sampler with default filter/addressing modes.
void createRenderPass()
Create the main render pass with colour and depth attachments.
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
Find a device memory type matching the filter and property flags.
void createTextureImage(SDL_Surface *surfacex)
Create a Vulkan texture image from an SDL surface.
virtual void event(SDL_Event &e)=0
Handle an SDL event (pure virtual).
void createTextureImageView()
Create the image view for the primary texture.
VkSurfaceKHR surface
Window surface.
VkSemaphore imageAvailableSemaphore
Signals when a swapchain image is acquired.
void setPath(const std::string &path)
Set the asset root directory.
VkPipeline textPipeline
Pipeline for text rendering.
bool getTextDimensions(const std::string &text, int &width, int &height)
Measure a text string without rendering it.
bool inputControllersInitialized
Whether joystick/controller input is enabled.
VkPipeline spritePipeline
Pipeline for sprite rendering.
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device)
Find queue families supporting graphics and presentation.
void createDescriptorPool()
Create the main descriptor pool.
void resizeWindow(int width, int height)
Resize the SDL window and recreate the swapchain.
VkDescriptorSetLayout descriptorSetLayout
Main descriptor set layout.
int font_size
Current font point size.
void createGraphicsPipeline()
(Re)create the graphics pipeline.
VkSwapchainKHR swapChain
Active swapchain.
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory)
Create a Vulkan image with the specified parameters.
VkDeviceMemory indexBufferMemory
Memory for the index buffer.
VkSemaphore renderFinishedSemaphore
Signals when rendering is complete.
VkPipeline graphicsPipeline
Filled-polygon graphics pipeline.
VkExtent2D swapChainExtent
Swapchain pixel dimensions.
virtual void draw()
Record rendering commands for the current frame (overridable).
VkDescriptorPool spriteDescriptorPool
Descriptor pool for sprites.
VkInstance instance
Vulkan instance.
std::vector< VkImageView > swapChainImageViews
Image views into swapchain images.
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device)
Query swapchain support details for a physical device.
VkBuffer indexBuffer
Full-screen quad index buffer.
VkDebugUtilsMessengerEXT debugMessenger
Vulkan debug messenger (validation layers).
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering at a screen position.
mxUtil util
Asset path and utility helpers.
void createInstance()
Create the Vulkan instance and enable extensions/layers.
void pickPhysicalDevice()
Enumerate GPUs and select the most suitable physical device.
VkFormat swapChainImageFormat
Colour format of swapchain images.
void createDescriptorSets()
Allocate main descriptor sets from the pool.
PNG image loading and saving utilities via SDL.
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData)
std::vector< char > readFile(const std::string &filename)
Read an entire file into a byte vector.
Tracks Vulkan queue family assignments needed to create a device.
std::optional< uint32_t > presentFamily
Index of the presentation queue family.
std::optional< uint32_t > graphicsFamily
Index of the graphics queue family.
bool isComplete()
Check whether both required families have been found.
Swapchain capabilities and format/mode candidates for a physical device.
std::vector< VkPresentModeKHR > presentModes
Supported presentation modes.
VkSurfaceCapabilitiesKHR capabilities
Surface capability limits.
std::vector< VkSurfaceFormatKHR > formats
Supported colour formats.
Per-vertex data sent to the Vulkan vertex buffer.
Vulkan application window and supporting structs for libmx.
#define VK_CHECK_RESULT(f)