#include "Volk/volk.h" #define VMA_IMPLEMENTATION #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 #include "vma/vk_mem_alloc.h" #define STB_IMAGE_IMPLEMENTATION #include #define TINYOBJLOADER_IMPLEMENTATION #include #define GLFW_INCLUDE_VULKAN #include #define GLM_FORCE_DEPTH_ZERO_TO_ONE #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; const std::string MODEL_PATH = "models/viking_room.obj"; const std::string TEXTURE_PATH = "textures/viking_room.png"; const int MAX_FRAMES_IN_FLIGHT = 2; const std::vector validationLayers = { "VK_LAYER_KHRONOS_validation" }; const std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SHADER_OBJECT_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, VK_KHR_MAINTENANCE_5_EXTENSION_NAME, }; #ifdef NDEBUG const bool enableValidationLayers = false; #else const bool enableValidationLayers = true; #endif VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debugMessenger, pAllocator); } } struct QueueFamilyIndices { std::optional graphicsFamily; std::optional presentFamily; bool isComplete() { return graphicsFamily.has_value() && presentFamily.has_value(); } }; struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector formats; std::vector presentModes; }; struct Vertex { glm::vec3 pos; glm::vec3 color; glm::vec2 texCoord; static VkVertexInputBindingDescription2EXT getBindingDescription() { VkVertexInputBindingDescription2EXT bindingDescription{}; bindingDescription.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; bindingDescription.divisor = 1; return bindingDescription; } static std::array getAttributeDescriptions() { std::array attributeDescriptions{}; attributeDescriptions[0].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; attributeDescriptions[0].offset = offsetof(Vertex, pos); attributeDescriptions[1].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; attributeDescriptions[1].binding = 0; attributeDescriptions[1].location = 1; attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; attributeDescriptions[1].offset = offsetof(Vertex, color); attributeDescriptions[2].sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT; attributeDescriptions[2].binding = 0; attributeDescriptions[2].location = 2; attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; attributeDescriptions[2].offset = offsetof(Vertex, texCoord); return attributeDescriptions; } bool operator==(const Vertex& other) const { return pos == other.pos && color == other.color && texCoord == other.texCoord; } }; namespace std { template<> struct hash { size_t operator()(Vertex const& vertex) const { return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); } }; } struct UniformBufferObject { glm::mat4 model; glm::mat4 view; glm::mat4 proj; }; inline VkDeviceSize alignUp(VkDeviceSize size, VkDeviceSize alignment) { return (size + alignment - 1) & ~(alignment - 1); } class HelloTriangleApplication { public: void run() { volkInitialize(); initWindow(); initVulkan(); mainLoop(); cleanup(); } private: GLFWwindow* window; VkInstance instance; VkDebugUtilsMessengerEXT debugMessenger; VkSurfaceKHR surface; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; VkDevice device; VmaAllocator allocator; VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties{}; std::vector descriptorHeapResourcesBuffers; std::vector descriptorHeapResourcesAllocations; VkBuffer descriptorHeapSamplerBuffer; VmaAllocation descriptorHeapSamplerAllocation; std::vector descriptorHeapResourcesAddresses; VkDeviceAddress descriptorHeapSamplerAddress{ 0 }; VkDeviceSize bufferDescriptorSize{ 0 }; VkDeviceSize samplerHeapOffset{ 0 }; VkDeviceSize samplerDescriptorSize{ 0 }; VkDeviceSize heapbufferSize{ 0 }; VkDeviceSize heapSamplerbufferSize{ 0 }; VkDeviceSize imageHeapOffset{ 0 }; VkDeviceSize imageDescriptorSize{ 0 }; VkQueue graphicsQueue; VkQueue presentQueue; VkSwapchainKHR swapChain; std::vector swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; std::vector swapChainImageViews; VkImage depthImage; VmaAllocation depthImageAllocation; VkImageView depthImageView; VkShaderEXT vertShader; VkShaderEXT fragShader; VkCommandPool commandPool; std::vector commandBuffers; std::vector vertices; std::vector indices; VkBuffer vertexBuffer; VmaAllocation vertexAllocation; VkBuffer indexBuffer; VmaAllocation indexAllocation; VkImage textureImage; VmaAllocation textureImageAllocation; std::vector uniformBuffers; std::vector uniformAllocations; std::vector uniformBuffersMapped; std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; VkSemaphore timelineSemaphore; uint64_t timelineValue = 0; uint32_t currentFrame = 0; bool framebufferResized = false; void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); glfwSetWindowUserPointer(window, this); glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); } static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->framebufferResized = true; } void initVulkan() { createInstance(); setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); createVMA(); createSwapChain(); createImageViews(); createCommandPool(); createDepthResources(); loadModel(); createVertexBuffer(); createIndexBuffer(); createTextureImage(); createUniformBuffers(); prepareDescriptorHeap(); prepareSamplerDescriptorHeap(); createShaderObjects(); createCommandBuffers(); createSyncObjects(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); drawFrame(); } vkDeviceWaitIdle(device); } void cleanupSwapChain() { // Depth image is sized to the swapchain extent, so it lives with the swapchain. vkDestroyImageView(device, depthImageView, nullptr); vmaDestroyImage(allocator, depthImage, depthImageAllocation); for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } vkDestroySwapchainKHR(device, swapChain, nullptr); } void cleanup() { cleanupSwapChain(); vmaDestroyImage(allocator, textureImage, textureImageAllocation); vmaDestroyBuffer(allocator, vertexBuffer, vertexAllocation); vmaDestroyBuffer(allocator, indexBuffer, indexAllocation); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vmaDestroyBuffer(allocator, uniformBuffers[i], uniformAllocations[i]); } for (size_t i = 0; i < descriptorHeapResourcesAllocations.size(); i++) { vmaDestroyBuffer(allocator, descriptorHeapResourcesBuffers[i], descriptorHeapResourcesAllocations[i]); } vmaDestroyBuffer(allocator, descriptorHeapSamplerBuffer, descriptorHeapSamplerAllocation); vmaDestroyAllocator(allocator); for (size_t i = 0; i < imageAvailableSemaphores.size(); i++) { vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr); } for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr); } vkDestroySemaphore(device, timelineSemaphore, nullptr); vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyShaderEXT(device, fragShader, nullptr); vkDestroyShaderEXT(device, vertShader, nullptr); vkDestroyDevice(device, nullptr); if (enableValidationLayers) { DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate(); } void recreateSwapChain() { int width = 0, height = 0; glfwGetFramebufferSize(window, &width, &height); while (width == 0 || height == 0) { glfwGetFramebufferSize(window, &width, &height); glfwWaitEvents(); } vkDeviceWaitIdle(device); cleanupSwapChain(); createSwapChain(); createImageViews(); createDepthResources(); } void createInstance() { if (enableValidationLayers && !checkValidationLayerSupport()) { throw std::runtime_error("validation layers requested, but not available!"); } VkApplicationInfo appInfo{}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_3; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto extensions = getRequiredExtensions(); createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); populateDebugMessengerCreateInfo(debugCreateInfo); createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo; } else { createInfo.enabledLayerCount = 0; createInfo.pNext = nullptr; } if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } volkLoadInstance(instance); } void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; createInfo.pfnUserCallback = debugCallback; } void setupDebugMessenger() { if (!enableValidationLayers) return; VkDebugUtilsMessengerCreateInfoEXT createInfo; populateDebugMessengerCreateInfo(createInfo); if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { throw std::runtime_error("failed to set up debug messenger!"); } } void createSurface() { if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } void pickPhysicalDevice() { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } std::vector devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); for (const auto& device : devices) { if (isDeviceSuitable(device)) { physicalDevice = device; break; } } if (physicalDevice == VK_NULL_HANDLE) { throw std::runtime_error("failed to find a suitable GPU!"); } // Get physical device properties descriptorHeapProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; VkPhysicalDeviceProperties2 props{}; props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; props.pNext = &descriptorHeapProperties; vkGetPhysicalDeviceProperties2(physicalDevice, &props); bufferDescriptorSize = alignUp(descriptorHeapProperties.bufferDescriptorSize, descriptorHeapProperties.bufferDescriptorAlignment); } void createLogicalDevice() { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector queueCreateInfos; std::set uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() }; float queuePriority = 1.0f; for (uint32_t queueFamily : uniqueQueueFamilies) { VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; queueCreateInfo.pQueuePriorities = &queuePriority; queueCreateInfos.push_back(queueCreateInfo); } VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures{}; shaderObjectFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT; shaderObjectFeatures.pNext = nullptr; shaderObjectFeatures.shaderObject = VK_TRUE; VkPhysicalDeviceFeatures2 deviceFeatures2{}; deviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; deviceFeatures2.features.samplerAnisotropy = VK_TRUE; deviceFeatures2.pNext = &shaderObjectFeatures; VkPhysicalDeviceVulkan12Features vulkan12Features{}; vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; vulkan12Features.timelineSemaphore = VK_TRUE; vulkan12Features.bufferDeviceAddress = VK_TRUE; vulkan12Features.pNext = &deviceFeatures2; VkPhysicalDeviceVulkan13Features vulkan13Features{}; vulkan13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; vulkan13Features.synchronization2 = VK_TRUE; vulkan13Features.dynamicRendering = VK_TRUE; vulkan13Features.pNext = &vulkan12Features; VkPhysicalDeviceDescriptorHeapFeaturesEXT descriptorHeapFeatures{}; descriptorHeapFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; descriptorHeapFeatures.descriptorHeap = VK_TRUE; descriptorHeapFeatures.pNext = &vulkan13Features; VkPhysicalDeviceMaintenance5Features maintenance5Features{}; maintenance5Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES; maintenance5Features.maintenance5 = VK_TRUE; maintenance5Features.pNext = &descriptorHeapFeatures; VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untypedPointersFeatures{}; untypedPointersFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; untypedPointersFeatures.pNext = &maintenance5Features; untypedPointersFeatures.shaderUntypedPointers = VK_TRUE; VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); createInfo.pNext = &maintenance5Features; createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } // Load device-level entry points directly (skips the instance dispatch hop). volkLoadDevice(device); vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } void createVMA() { VmaVulkanFunctions funcs{}; funcs.vkGetInstanceProcAddr = vkGetInstanceProcAddr; funcs.vkGetDeviceProcAddr = vkGetDeviceProcAddr; VmaAllocatorCreateInfo allocatorInfo{}; allocatorInfo.physicalDevice = physicalDevice; allocatorInfo.device = device; allocatorInfo.instance = instance; allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; allocatorInfo.pVulkanFunctions = &funcs; allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_3; if (vmaCreateAllocator(&allocatorInfo, &allocator) != VK_SUCCESS) { throw std::runtime_error("failed to create vma allocator!"); } } void createSwapChain() { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { imageCount = swapChainSupport.capabilities.maxImageCount; } VkSwapchainCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = surface; createInfo.minImageCount = imageCount; createInfo.imageFormat = surfaceFormat.format; createInfo.imageColorSpace = surfaceFormat.colorSpace; createInfo.imageExtent = extent; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() }; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; } else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } createInfo.preTransform = swapChainSupport.capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; createInfo.oldSwapchain = VK_NULL_HANDLE; if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; } VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) { VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = format; viewInfo.subresourceRange.aspectMask = aspectFlags; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; VkImageView imageView; if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { throw std::runtime_error("failed to create image view!"); } return imageView; } void createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for (uint32_t i = 0; i < swapChainImages.size(); i++) { swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); } } void createDepthResources() { VkFormat depthFormat = findDepthFormat(); createImage( swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depthImage, depthImageAllocation ); depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); // Dynamic rendering does not auto-transition attachments. The depth image is never // presented or sampled, so a single transition into the attachment layout suffices; // it stays there across frames (per-frame loadOp = CLEAR resets contents, not layout). VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkImageMemoryBarrier2 barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = depthImage; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; barrier.srcAccessMask = 0; barrier.dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; barrier.dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; VkDependencyInfo dependencyInfo{}; dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependencyInfo.imageMemoryBarrierCount = 1; dependencyInfo.pImageMemoryBarriers = &barrier; vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); endSingleTimeCommands(commandBuffer); } VkFormat findDepthFormat() { return findSupportedFormat( { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT ); } bool hasStencilComponent(VkFormat format) { return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; } VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) { for (VkFormat format : candidates) { VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { return format; } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { return format; } } throw std::runtime_error("failed to find supported format!"); } void prepareDescriptorHeap() { heapbufferSize = alignUp(2048 + descriptorHeapProperties.minResourceHeapReservedRange, descriptorHeapProperties.resourceHeapAlignment); descriptorHeapResourcesAllocations.resize(MAX_FRAMES_IN_FLIGHT); descriptorHeapResourcesBuffers.resize(MAX_FRAMES_IN_FLIGHT); std::vector allocResult{}; allocResult.resize(MAX_FRAMES_IN_FLIGHT); for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = heapbufferSize; bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; if (vmaCreateBuffer( allocator, &bufferInfo, &allocInfo, &descriptorHeapResourcesBuffers[i], &descriptorHeapResourcesAllocations[i], &allocResult[i] ) != VK_SUCCESS) { throw std::runtime_error("failed to create resource descriptor heap!"); } } // Cache the per-frame heap device addresses (queried once, used every frame at bind time). descriptorHeapResourcesAddresses.resize(MAX_FRAMES_IN_FLIGHT); for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VkBufferDeviceAddressInfo heapAddrInfo{}; heapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; heapAddrInfo.buffer = descriptorHeapResourcesBuffers[i]; descriptorHeapResourcesAddresses[i] = vkGetBufferDeviceAddress(device, &heapAddrInfo); } // Image imageHeapOffset = alignUp(uniformBuffers.size() * bufferDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); imageDescriptorSize = alignUp(descriptorHeapProperties.imageDescriptorSize, descriptorHeapProperties.imageDescriptorAlignment); std::array addrInfo{}; std::array deviceAddressRangesUniformBuffer{}; for (auto i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { std::vector hostAddressRangesResources; std::vector resourceDescriptorInfos; // Uniform buffer addrInfo[i].sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; addrInfo[i].buffer = uniformBuffers[i]; deviceAddressRangesUniformBuffer[i] = {}; deviceAddressRangesUniformBuffer[i].address = vkGetBufferDeviceAddress(device, &addrInfo[i]); deviceAddressRangesUniformBuffer[i].size = sizeof(UniformBufferObject); VkResourceDescriptorInfoEXT resourceDescriptorInfo = {}; resourceDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; resourceDescriptorInfo.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; resourceDescriptorInfo.data = {}; resourceDescriptorInfo.data.pAddressRange = &deviceAddressRangesUniformBuffer[i]; resourceDescriptorInfos.push_back(resourceDescriptorInfo); VkHostAddressRangeEXT hostAddressRangesResource = {}; hostAddressRangesResource.address = static_cast(allocResult[i].pMappedData); hostAddressRangesResource.size = bufferDescriptorSize; hostAddressRangesResources.push_back(hostAddressRangesResource); // Image views VkImageViewCreateInfo imageViewCreateInfo = {}; imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; imageViewCreateInfo.image = textureImage; imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; imageViewCreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB; imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imageViewCreateInfo.subresourceRange.baseMipLevel = 0; imageViewCreateInfo.subresourceRange.levelCount = 1; imageViewCreateInfo.subresourceRange.baseArrayLayer = 0; imageViewCreateInfo.subresourceRange.layerCount = 1; VkImageDescriptorInfoEXT imageDescriptorInfo = {}; imageDescriptorInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; imageDescriptorInfo.pView = &imageViewCreateInfo; imageDescriptorInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkResourceDescriptorInfoEXT resourceImageDescriptorInfo = {}; resourceImageDescriptorInfo.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; resourceImageDescriptorInfo.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; resourceImageDescriptorInfo.data = {}; resourceImageDescriptorInfo.data.pImage = &imageDescriptorInfo; resourceDescriptorInfos.push_back(resourceImageDescriptorInfo); VkHostAddressRangeEXT hostAddressRangesResourceImage; hostAddressRangesResourceImage = {}; hostAddressRangesResourceImage.address = static_cast(allocResult[i].pMappedData) + imageHeapOffset; hostAddressRangesResourceImage.size = imageDescriptorSize; hostAddressRangesResources.push_back(hostAddressRangesResourceImage); if (vkWriteResourceDescriptorsEXT( device, static_cast(resourceDescriptorInfos.size()), resourceDescriptorInfos.data(), hostAddressRangesResources.data() ) != VK_SUCCESS) { throw std::runtime_error("failed to write resource descriptors!"); } } } void prepareSamplerDescriptorHeap() { heapSamplerbufferSize = alignUp(2048 + descriptorHeapProperties.minSamplerHeapReservedRange, descriptorHeapProperties.samplerHeapAlignment); samplerDescriptorSize = alignUp(descriptorHeapProperties.samplerDescriptorSize, descriptorHeapProperties.samplerDescriptorAlignment); VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = heapSamplerbufferSize; bufferInfo.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; VmaAllocationInfo allocResult; if (vmaCreateBuffer( allocator, &bufferInfo, &allocInfo, &descriptorHeapSamplerBuffer, &descriptorHeapSamplerAllocation, &allocResult ) != VK_SUCCESS) { throw std::runtime_error("failed to create resource descriptor heap!"); } // Cache the sampler heap device address (queried once, used every frame at bind time). VkBufferDeviceAddressInfo samplerHeapAddrInfo{}; samplerHeapAddrInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; samplerHeapAddrInfo.buffer = descriptorHeapSamplerBuffer; descriptorHeapSamplerAddress = vkGetBufferDeviceAddress(device, &samplerHeapAddrInfo); VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.anisotropyEnable = VK_TRUE; samplerInfo.maxAnisotropy = 1.0f; VkPhysicalDeviceProperties properties{}; vkGetPhysicalDeviceProperties(physicalDevice, &properties); samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy; samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; samplerInfo.unnormalizedCoordinates = VK_FALSE; samplerInfo.compareEnable = VK_FALSE; samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.mipLodBias = 0.0f; samplerInfo.minLod = 0.0f; samplerInfo.maxLod = 0.0f; VkHostAddressRangeEXT hostAddressRangesSamplers = {}; hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData); hostAddressRangesSamplers.size = samplerDescriptorSize; // For multiple textures: // hostAddressRangesSamplers.address = static_cast(allocResult.pMappedData) + samplerDescriptorSize * i if (vkWriteSamplerDescriptorsEXT( device, 1, &samplerInfo, &hostAddressRangesSamplers ) != VK_SUCCESS) { throw std::runtime_error("failed to write resource descriptors!"); } } void createShaderObjects() { auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); vertShader = createShaderObject(vertShaderCode, VK_SHADER_STAGE_VERTEX_BIT); fragShader = createShaderObject(fragShaderCode, VK_SHADER_STAGE_FRAGMENT_BIT); return; } void loadModel() { tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string err; std::string war; if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &war, &err, MODEL_PATH.c_str())) { throw std::runtime_error(err); } std::unordered_map uniqueVertices{}; for (const auto& shape : shapes) { for (const auto& index : shape.mesh.indices) { Vertex vertex{}; vertex.pos = { attrib.vertices[3 * index.vertex_index + 0], attrib.vertices[3 * index.vertex_index + 1], attrib.vertices[3 * index.vertex_index + 2] }; vertex.texCoord = { attrib.texcoords[2 * index.texcoord_index + 0], 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] }; vertex.color = { 1.0f, 1.0f, 1.0f }; if (uniqueVertices.count(vertex) == 0) { uniqueVertices[vertex] = static_cast(vertices.size()); vertices.push_back(vertex); } indices.push_back(uniqueVertices[vertex]); } } } void createBuffer( VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage vmaUsage, VmaAllocationCreateFlags vmaFlags, VkMemoryPropertyFlags requiredFlags, VkBuffer& buffer, VmaAllocation& bufferAllocation, VmaAllocationInfo* outAllocResult = 0 ) { if (size == 0) { throw std::runtime_error("Vertex buffer size is 0!"); } VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = vmaUsage; allocInfo.flags = vmaFlags; allocInfo.requiredFlags = requiredFlags; VmaAllocationInfo* allocDst = outAllocResult ? outAllocResult : nullptr; if (vmaCreateBuffer( allocator, &bufferInfo, &allocInfo, &buffer, &bufferAllocation, allocDst ) != VK_SUCCESS) { throw std::runtime_error("failed to create buffer!"); } } void createVertexBuffer() { VkBuffer stagingBuffer; VmaAllocation stagingAllocation; VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size(); createBuffer( bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST, VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, 0, stagingBuffer, stagingAllocation ); void* data = nullptr; vmaMapMemory(allocator, stagingAllocation, &data); memcpy(data, vertices.data(), bufferSize); vmaUnmapMemory(allocator, stagingAllocation); createBuffer( bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0,//VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, 0, vertexBuffer, vertexAllocation ); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); } void createIndexBuffer() { VkBuffer stagingBuffer; VmaAllocation stagingAllocation; VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size(); createBuffer( bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST, VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, 0, stagingBuffer, stagingAllocation ); void* data = nullptr; vmaMapMemory(allocator, stagingAllocation, &data); memcpy(data, indices.data(), bufferSize); vmaUnmapMemory(allocator, stagingAllocation); createBuffer( bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0, indexBuffer, indexAllocation ); copyBuffer(stagingBuffer, indexBuffer, bufferSize); vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); } void createImage( uint32_t width, uint32_t height, VkFormat format, VkImageUsageFlags usage, VkImage& image, VmaAllocation& allocation ) { VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.extent.width = width; imageInfo.extent.height = height; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; if (vmaCreateImage( allocator, &imageInfo, &allocInfo, &image, &allocation, nullptr ) != VK_SUCCESS) { throw std::runtime_error("failed to create image!"); } } void createTextureImage() { int texWidth, texHeight, texChannels; stbi_uc* pixels = stbi_load(TEXTURE_PATH.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); VkDeviceSize imageSize = texWidth * texHeight * 4; if (!pixels) { throw std::runtime_error("failed to load texture image!"); } VkBuffer stagingBuffer; VmaAllocation stagingAllocation; createBuffer( imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO, VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, 0, stagingBuffer, stagingAllocation ); void* data = nullptr; vmaMapMemory(allocator, stagingAllocation, &data); memcpy(data, pixels, imageSize); vmaUnmapMemory(allocator, stagingAllocation); stbi_image_free(pixels); createImage( texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, textureImage, textureImageAllocation ); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation); } void createUniformBuffers() { VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = sizeof(UniformBufferObject); bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; VmaAllocationCreateInfo allocInfo{}; allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; // CPU can map and write allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT); uniformAllocations.resize(MAX_FRAMES_IN_FLIGHT); uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VmaAllocationInfo allocResult{}; if (vmaCreateBuffer( allocator, &bufferInfo, &allocInfo, &uniformBuffers[i], &uniformAllocations[i], &allocResult ) != VK_SUCCESS) { throw std::runtime_error("failed to create staging buffer!"); } // Persistently mapped (HOST_COHERENT) — write directly each frame, no map/unmap. uniformBuffersMapped[i] = allocResult.pMappedData; } } VkCommandBuffer beginSingleTimeCommands() { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); return commandBuffer; } void endSingleTimeCommands(VkCommandBuffer commandBuffer) { vkEndCommandBuffer(commandBuffer); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { throw std::runtime_error("failed to end single time commands"); } vkQueueWaitIdle(graphicsQueue); vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); } void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkBufferCopy copyRegion{}; copyRegion.size = size; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); endSingleTimeCommands(commandBuffer); } void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkImageMemoryBarrier2 barrier{ }; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; barrier.oldLayout = oldLayout; barrier.newLayout = newLayout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = image; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; barrier.srcAccessMask = 0; barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; } else { throw std::invalid_argument("unsupported layout transition!"); } VkDependencyInfo dependencyInfo{ }; dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dependencyInfo.imageMemoryBarrierCount = 1; dependencyInfo.pImageMemoryBarriers = &barrier; vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo); endSingleTimeCommands(commandBuffer); } void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkBufferImageCopy region{}; region.bufferOffset = 0; region.bufferRowLength = 0; region.bufferImageHeight = 0; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.mipLevel = 0; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset = { 0, 0, 0 }; region.imageExtent = { width, height, 1 }; vkCmdCopyBufferToImage( commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion ); endSingleTimeCommands(commandBuffer); } void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } void createCommandBuffers() { commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = (uint32_t)commandBuffers.size(); if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { throw std::runtime_error("failed to allocate command buffers!"); } } void setInitialRenderingState(VkCommandBuffer commandBuffer) { vkCmdSetCullModeEXT(commandBuffer, VK_CULL_MODE_NONE); vkCmdSetDepthWriteEnable(commandBuffer, VK_TRUE); vkCmdSetPolygonModeEXT(commandBuffer, VK_POLYGON_MODE_FILL); vkCmdSetStencilTestEnable(commandBuffer, VK_FALSE); vkCmdSetPrimitiveTopology(commandBuffer, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); vkCmdSetPrimitiveRestartEnableEXT(commandBuffer, VK_FALSE); vkCmdSetRasterizationSamplesEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT); vkCmdSetDepthTestEnable(commandBuffer, VK_TRUE); vkCmdSetDepthCompareOp(commandBuffer, VK_COMPARE_OP_LESS); vkCmdSetDepthBoundsTestEnable(commandBuffer, VK_FALSE); vkCmdSetDepthBiasEnable(commandBuffer, VK_FALSE); vkCmdSetRasterizerDiscardEnableEXT(commandBuffer, VK_FALSE); const VkSampleMask sample_mask = 0x1; vkCmdSetSampleMaskEXT(commandBuffer, VK_SAMPLE_COUNT_1_BIT, &sample_mask); vkCmdSetAlphaToCoverageEnableEXT(commandBuffer, VK_FALSE); VkColorComponentFlags color_component_flags[] = { VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_A_BIT }; vkCmdSetColorWriteMaskEXT(commandBuffer, 0, 1, color_component_flags); VkBool32 color_blend_enables[] = { VK_FALSE }; vkCmdSetColorBlendEnableEXT(commandBuffer, 0, 1, color_blend_enables); vkCmdSetVertexInputEXT(commandBuffer, 0, nullptr, 0, nullptr); } void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = 0; // Optional beginInfo.pInheritanceInfo = nullptr; // Optional if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { throw std::runtime_error("failed to begin recording command buffer!"); } // Transition swapchain image layout for optimal drawing VkImageMemoryBarrier2 barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; barrier.srcAccessMask = 0; barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // or PRESENT_SRC_KHR barrier.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; barrier.image = swapChainImages[imageIndex]; barrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; VkDependencyInfo dep{}; dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; dep.imageMemoryBarrierCount = 1; dep.pImageMemoryBarriers = &barrier; vkCmdPipelineBarrier2(commandBuffer, &dep); VkRenderingAttachmentInfo colorAttachment{}; colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; colorAttachment.imageView = swapChainImageViews[imageIndex]; colorAttachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.clearValue = { { 0.0f, 0.0f, 0.0f, 1.0f } }; VkRenderingAttachmentInfo depthAttachment{}; depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; depthAttachment.imageView = depthImageView; depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // depth is not sampled/used after this pass depthAttachment.clearValue.depthStencil = { 1.0f, 0 }; VkRenderingInfo renderingInfo{}; renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; renderingInfo.renderArea = { {0, 0}, swapChainExtent }; renderingInfo.layerCount = 1; renderingInfo.colorAttachmentCount = 1; renderingInfo.pColorAttachments = &colorAttachment; renderingInfo.pDepthAttachment = &depthAttachment; vkCmdBeginRendering(commandBuffer, &renderingInfo); { setInitialRenderingState(commandBuffer); vkCmdSetVertexInputEXT(commandBuffer, 1, &Vertex::getBindingDescription(), static_cast(Vertex::getAttributeDescriptions().size()), Vertex::getAttributeDescriptions().data() ); VkShaderStageFlagBits stages[] = { VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT }; VkShaderEXT shaders[] = { vertShader, fragShader }; vkCmdBindShadersEXT(commandBuffer, 2, stages, shaders); VkBuffer vertexBuffers[] = { vertexBuffer }; VkDeviceSize offsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32); uint32_t pushconstants = currentFrame; VkPushDataInfoEXT pushDataInfo{}; pushDataInfo.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; pushDataInfo.data.address = &pushconstants; pushDataInfo.data.size = sizeof(uint32_t); vkCmdPushDataEXT(commandBuffer, &pushDataInfo); VkBindHeapInfoEXT bindHeapinfo{}; bindHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindHeapinfo.heapRange.address = descriptorHeapResourcesAddresses[currentFrame]; bindHeapinfo.heapRange.size = heapbufferSize; bindHeapinfo.reservedRangeOffset = heapbufferSize - descriptorHeapProperties.minResourceHeapReservedRange; bindHeapinfo.reservedRangeSize = descriptorHeapProperties.minResourceHeapReservedRange; vkCmdBindResourceHeapEXT(commandBuffer, &bindHeapinfo); VkBindHeapInfoEXT bindSamplerHeapinfo{}; bindSamplerHeapinfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; bindSamplerHeapinfo.heapRange.address = descriptorHeapSamplerAddress; bindSamplerHeapinfo.heapRange.size = heapSamplerbufferSize; bindSamplerHeapinfo.reservedRangeOffset = heapSamplerbufferSize - descriptorHeapProperties.minSamplerHeapReservedRange; bindSamplerHeapinfo.reservedRangeSize = descriptorHeapProperties.minSamplerHeapReservedRange; vkCmdBindSamplerHeapEXT(commandBuffer, &bindSamplerHeapinfo); VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float)swapChainExtent.width; viewport.height = (float)swapChainExtent.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewportWithCount(commandBuffer, 1, &viewport); VkRect2D scissor{}; scissor.offset = { 0, 0 }; scissor.extent = swapChainExtent; vkCmdSetScissorWithCount(commandBuffer, 1, &scissor); vkCmdDrawIndexed(commandBuffer, static_cast(indices.size()), 1, 0, 0, 0); } vkCmdEndRendering(commandBuffer); VkImageMemoryBarrier2 barrierLayoutBack{}; barrierLayoutBack.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; barrierLayoutBack.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; barrierLayoutBack.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; barrierLayoutBack.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; barrierLayoutBack.dstAccessMask = 0; barrierLayoutBack.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL; barrierLayoutBack.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; barrierLayoutBack.image = swapChainImages[imageIndex]; barrierLayoutBack.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; VkDependencyInfo depLayoutBack{}; depLayoutBack.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; depLayoutBack.imageMemoryBarrierCount = 1; depLayoutBack.pImageMemoryBarriers = &barrierLayoutBack; vkCmdPipelineBarrier2(commandBuffer, &depLayoutBack); if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to record command buffer!"); } }; void createSyncObjects() { // Create semaphores VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); renderFinishedSemaphores.resize(swapChainImages.size()); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS) throw std::runtime_error("failed to create synchronization objects for a frame!"); } for (size_t i = 0; i < renderFinishedSemaphores.size(); i++) { if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) throw std::runtime_error("failed to create synchronization objects for a frame!"); } // Create timeline semaphore VkSemaphoreTypeCreateInfo typeInfo{}; typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; typeInfo.initialValue = 0; VkSemaphoreCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; createInfo.pNext = &typeInfo; if (vkCreateSemaphore(device, &createInfo, nullptr, &timelineSemaphore) != VK_SUCCESS) { throw std::runtime_error("failed to create timeline synchronization objects for a frame!"); } } void drawFrame() { if (timelineValue >= MAX_FRAMES_IN_FLIGHT) { VkSemaphoreWaitInfo waitInfo{}; waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; waitInfo.semaphoreCount = 1; waitInfo.pSemaphores = &timelineSemaphore; uint64_t waitValue = timelineValue - MAX_FRAMES_IN_FLIGHT + 1; waitInfo.pValues = &waitValue; vkWaitSemaphores(device, &waitInfo, UINT64_MAX); } uint32_t imageIndex; VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { recreateSwapChain(); return; } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { throw std::runtime_error("failed to acquire swap chain image!"); } timelineValue++; updateUniformBuffer(currentFrame); vkResetCommandBuffer(commandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); recordCommandBuffer(commandBuffers[currentFrame], imageIndex); VkSemaphoreSubmitInfo waitAcquire{}; waitAcquire.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; waitAcquire.semaphore = imageAvailableSemaphores[currentFrame]; waitAcquire.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; VkSemaphoreSubmitInfo waitSemaphoreInfo{}; waitSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; waitSemaphoreInfo.semaphore = timelineSemaphore; waitSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; waitSemaphoreInfo.deviceIndex = 0; waitSemaphoreInfo.value = timelineValue - 1; VkSemaphoreSubmitInfo waits[] = { waitAcquire, waitSemaphoreInfo }; VkSemaphoreSubmitInfo signalBinary{}; signalBinary.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; signalBinary.semaphore = renderFinishedSemaphores[imageIndex]; signalBinary.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; VkSemaphoreSubmitInfo signalSemaphoreInfo{}; signalSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO; signalSemaphoreInfo.semaphore = timelineSemaphore; signalSemaphoreInfo.stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; signalSemaphoreInfo.deviceIndex = 0; signalSemaphoreInfo.value = timelineValue; VkSemaphoreSubmitInfo signals[] = { signalSemaphoreInfo, signalBinary }; VkCommandBufferSubmitInfo commandBufferInfo{}; commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO; commandBufferInfo.commandBuffer = commandBuffers[currentFrame]; commandBufferInfo.deviceMask = 0; VkSubmitInfo2 submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2; submitInfo.waitSemaphoreInfoCount = 2; submitInfo.pWaitSemaphoreInfos = waits; submitInfo.commandBufferInfoCount = 1; submitInfo.pCommandBufferInfos = &commandBufferInfo; submitInfo.signalSemaphoreInfoCount = 2; submitInfo.pSignalSemaphoreInfos = signals; VkResult submitResult = vkQueueSubmit2(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); if (submitResult != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer! VkResult = " + std::to_string(submitResult) + " (frame " + std::to_string(timelineValue) + ")"); } VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = &renderFinishedSemaphores[imageIndex]; VkSwapchainKHR swapChains[] = { swapChain }; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = swapChains; presentInfo.pImageIndices = &imageIndex; result = vkQueuePresentKHR(presentQueue, &presentInfo); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) { framebufferResized = false; recreateSwapChain(); } else if (result != VK_SUCCESS) { throw std::runtime_error("failed to present swap chain image!"); } currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; } void updateUniformBuffer(uint32_t currentImage) { static auto startTime = std::chrono::high_resolution_clock::now(); auto currentTime = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration(currentTime - startTime).count(); UniformBufferObject ubo{}; ubo.model = glm::rotate(glm::mat4(1.0f), time * (glm::radians(90.0f)), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 100.0f); ubo.proj[1][1] *= -1; // Vulkan clip correction memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); } VkShaderEXT createShaderObject(const std::vector& code, VkShaderStageFlagBits stageFlags) { std::array setAndBindingMappings; // Buffer binding setAndBindingMappings[0] = {}; setAndBindingMappings[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; setAndBindingMappings[0].descriptorSet = 0; setAndBindingMappings[0].firstBinding = 0; setAndBindingMappings[0].bindingCount = 1; setAndBindingMappings[0].resourceMask = VK_SPIRV_RESOURCE_TYPE_UNIFORM_BUFFER_BIT_EXT; setAndBindingMappings[0].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; setAndBindingMappings[0].sourceData.constantOffset.heapArrayStride = static_cast(bufferDescriptorSize); // Image binding setAndBindingMappings[1] = {}; setAndBindingMappings[1].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; setAndBindingMappings[1].descriptorSet = 1; setAndBindingMappings[1].firstBinding = 0; setAndBindingMappings[1].bindingCount = 1; setAndBindingMappings[1].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLED_IMAGE_BIT_EXT; setAndBindingMappings[1].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; setAndBindingMappings[1].sourceData.constantOffset.heapArrayStride = static_cast(imageDescriptorSize); setAndBindingMappings[1].sourceData.constantOffset.heapOffset = static_cast(imageHeapOffset); // Sampler binding setAndBindingMappings[2] = {}; setAndBindingMappings[2].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_AND_BINDING_MAPPING_EXT; setAndBindingMappings[2].descriptorSet = 2; setAndBindingMappings[2].firstBinding = 0; setAndBindingMappings[2].bindingCount = 1; setAndBindingMappings[2].resourceMask = VK_SPIRV_RESOURCE_TYPE_SAMPLER_BIT_EXT; setAndBindingMappings[2].source = VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT; setAndBindingMappings[2].sourceData.constantOffset.heapArrayStride = static_cast(samplerDescriptorSize); setAndBindingMappings[2].sourceData.constantOffset.heapOffset = static_cast(samplerHeapOffset); VkShaderDescriptorSetAndBindingMappingInfoEXT descriptorSetAndBindingMappingInfo{}; descriptorSetAndBindingMappingInfo.sType = VK_STRUCTURE_TYPE_SHADER_DESCRIPTOR_SET_AND_BINDING_MAPPING_INFO_EXT; descriptorSetAndBindingMappingInfo.mappingCount = static_cast(setAndBindingMappings.size()); descriptorSetAndBindingMappingInfo.pMappings = setAndBindingMappings.data(); VkShaderCreateInfoEXT shaderCreateInfo{ VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT }; shaderCreateInfo.stage = stageFlags; shaderCreateInfo.codeType = VK_SHADER_CODE_TYPE_SPIRV_EXT; shaderCreateInfo.pCode = reinterpret_cast(code.data()); shaderCreateInfo.codeSize = code.size(); shaderCreateInfo.pName = "main"; shaderCreateInfo.flags = VK_SHADER_CREATE_DESCRIPTOR_HEAP_BIT_EXT; shaderCreateInfo.pNext = &descriptorSetAndBindingMappingInfo; if (stageFlags & VK_SHADER_STAGE_VERTEX_BIT) { shaderCreateInfo.nextStage = VK_SHADER_STAGE_FRAGMENT_BIT; } VkShaderEXT shader; if (vkCreateShadersEXT(device, 1, &shaderCreateInfo, nullptr, &shader) != VK_SUCCESS) { throw std::runtime_error("failed to create shader objects!"); } return shader; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { for (const auto& availableFormat : availableFormats) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } return availableFormats[0]; } VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == VK_PRESENT_MODE_FIFO_KHR) { return availablePresentMode; } } return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } else { int width, height; glfwGetFramebufferSize(window, &width, &height); VkExtent2D actualExtent = { static_cast(width), static_cast(height) }; actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } } SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { SwapChainSupportDetails details; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); if (formatCount != 0) { details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); } uint32_t presentModeCount; vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); } return details; } bool isDeviceSuitable(VkPhysicalDevice device) { QueueFamilyIndices indices = findQueueFamilies(device); bool extensionsSupported = checkDeviceExtensionSupport(device); VkPhysicalDeviceFeatures supportedFeatures; vkGetPhysicalDeviceFeatures(device, &supportedFeatures); bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { uint32_t extensionCount; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); std::vector availableExtensions(extensionCount); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); for (const auto& extension : availableExtensions) { requiredExtensions.erase(extension.extensionName); } return requiredExtensions.empty(); } QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { QueueFamilyIndices indices; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); int i = 0; for (const auto& queueFamily : queueFamilies) { if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); if (presentSupport) { indices.presentFamily = i; } if (indices.isComplete()) { break; } i++; } return indices; } std::vector getRequiredExtensions() { uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } return extensions; } bool checkValidationLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); for (const char* layerName : validationLayers) { bool layerFound = false; for (const auto& layerProperties : availableLayers) { if (strcmp(layerName, layerProperties.layerName) == 0) { layerFound = true; break; } } if (!layerFound) { return false; } } return true; } static std::vector readFile(const std::string& filename) { std::ifstream file(filename, std::ios::ate | std::ios::binary); if (!file.is_open()) { throw std::runtime_error("failed to open file!"); } size_t fileSize = (size_t)file.tellg(); std::vector buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; } static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; return VK_FALSE; } }; int main() { HelloTriangleApplication app; try { app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }