#include #include #include #include #include #include #include #include #include #ifdef __INTELLISENSE__ #include #else import vulkan_hpp; #endif #include #define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. #include constexpr uint32_t WIDTH = 800; constexpr uint32_t HEIGHT = 600; const std::vector validationLayers = { "VK_LAYER_KHRONOS_validation" }; #ifdef NDEBUG constexpr bool enableValidationLayers = false; #else constexpr bool enableValidationLayers = true; #endif class HelloTriangleApplication { public: void run() { initWindow(); initVulkan(); mainLoop(); cleanup(); } private: GLFWwindow * window = nullptr; vk::raii::Context context; vk::raii::Instance instance = nullptr; vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; vk::raii::SurfaceKHR surface = nullptr; vk::raii::PhysicalDevice physicalDevice = nullptr; vk::raii::Device device = nullptr; vk::raii::Queue queue = nullptr; vk::raii::SwapchainKHR swapChain = nullptr; std::vector swapChainImages; vk::Format swapChainImageFormat = vk::Format::eUndefined; vk::Extent2D swapChainExtent; std::vector swapChainImageViews; vk::raii::PipelineLayout pipelineLayout = nullptr; vk::raii::Pipeline graphicsPipeline = nullptr; std::vector requiredDeviceExtension = { vk::KHRSwapchainExtensionName, vk::KHRSpirv14ExtensionName, vk::KHRSynchronization2ExtensionName, vk::KHRCreateRenderpass2ExtensionName }; void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); } void initVulkan() { createInstance(); setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); createSwapChain(); createImageViews(); createGraphicsPipeline(); } void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } } void cleanup() { glfwDestroyWindow(window); glfwTerminate(); } void createInstance() { constexpr vk::ApplicationInfo appInfo{ .pApplicationName = "Hello Triangle", .applicationVersion = VK_MAKE_VERSION( 1, 0, 0 ), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION( 1, 0, 0 ), .apiVersion = vk::ApiVersion14 }; // Get the required layers std::vector requiredLayers; if (enableValidationLayers) { requiredLayers.assign(validationLayers.begin(), validationLayers.end()); } // Check if the required layers are supported by the Vulkan implementation. auto layerProperties = context.enumerateInstanceLayerProperties(); for (auto const& requiredLayer : requiredLayers) { if (std::ranges::none_of(layerProperties, [requiredLayer](auto const& layerProperty) { return strcmp(layerProperty.layerName, requiredLayer) == 0; })) { throw std::runtime_error("Required layer not supported: " + std::string(requiredLayer)); } } // Get the required extensions. auto requiredExtensions = getRequiredExtensions(); // Check if the required extensions are supported by the Vulkan implementation. auto extensionProperties = context.enumerateInstanceExtensionProperties(); for (auto const& requiredExtension : requiredExtensions) { if (std::ranges::none_of(extensionProperties, [requiredExtension](auto const& extensionProperty) { return strcmp(extensionProperty.extensionName, requiredExtension) == 0; })) { throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension)); } } vk::InstanceCreateInfo createInfo{ .pApplicationInfo = &appInfo, .enabledLayerCount = static_cast(requiredLayers.size()), .ppEnabledLayerNames = requiredLayers.data(), .enabledExtensionCount = static_cast(requiredExtensions.size()), .ppEnabledExtensionNames = requiredExtensions.data() }; instance = vk::raii::Instance(context, createInfo); } void setupDebugMessenger() { if (!enableValidationLayers) return; vk::DebugUtilsMessageSeverityFlagsEXT severityFlags( vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError ); vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation ); vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ .messageSeverity = severityFlags, .messageType = messageTypeFlags, .pfnUserCallback = &debugCallback }; debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); } void createSurface() { VkSurfaceKHR _surface; if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { throw std::runtime_error("failed to create window surface!"); } surface = vk::raii::SurfaceKHR(instance, _surface); } void pickPhysicalDevice() { std::vector devices = instance.enumeratePhysicalDevices(); const auto devIter = std::ranges::find_if( devices, [&]( auto const & device ) { // Check if the device supports the Vulkan 1.3 API version bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; // Check if any of the queue families support graphics operations auto queueFamilies = device.getQueueFamilyProperties(); bool supportsGraphics = std::ranges::any_of( queueFamilies, []( auto const & qfp ) { return !!( qfp.queueFlags & vk::QueueFlagBits::eGraphics ); } ); // Check if all required device extensions are available auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties(); bool supportsAllRequiredExtensions = std::ranges::all_of( requiredDeviceExtension, [&availableDeviceExtensions]( auto const & requiredDeviceExtension ) { return std::ranges::any_of( availableDeviceExtensions, [requiredDeviceExtension]( auto const & availableDeviceExtension ) { return strcmp( availableDeviceExtension.extensionName, requiredDeviceExtension ) == 0; } ); } ); auto features = device.template getFeatures2(); bool supportsRequiredFeatures = features.template get().dynamicRendering && features.template get().extendedDynamicState; return supportsVulkan1_3 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures; } ); if ( devIter != devices.end() ) { physicalDevice = *devIter; } else { throw std::runtime_error( "failed to find a suitable GPU!" ); } } void createLogicalDevice() { std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); // get the first index into queueFamilyProperties which supports both graphics and present uint32_t queueIndex = ~0; for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) { if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) { // found a queue family that supports both graphics and present queueIndex = qfpIndex; break; } } if (queueIndex == ~0) { throw std::runtime_error("Could not find a queue for graphics and present -> terminating"); } // query for Vulkan 1.3 features vk::StructureChain featureChain = { {}, // vk::PhysicalDeviceFeatures2 {.dynamicRendering = true }, // vk::PhysicalDeviceVulkan13Features {.extendedDynamicState = true } // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT }; // create a Device float queuePriority = 0.0f; vk::DeviceQueueCreateInfo deviceQueueCreateInfo{ .queueFamilyIndex = queueIndex, .queueCount = 1, .pQueuePriorities = &queuePriority }; vk::DeviceCreateInfo deviceCreateInfo{ .pNext = &featureChain.get(), .queueCreateInfoCount = 1, .pQueueCreateInfos = &deviceQueueCreateInfo, .enabledExtensionCount = static_cast(requiredDeviceExtension.size()), .ppEnabledExtensionNames = requiredDeviceExtension.data() }; device = vk::raii::Device( physicalDevice, deviceCreateInfo ); queue = vk::raii::Queue( device, queueIndex, 0 ); } void createSwapChain() { auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR( surface ); swapChainImageFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR( surface )); swapChainExtent = chooseSwapExtent(surfaceCapabilities); auto minImageCount = std::max( 3u, surfaceCapabilities.minImageCount ); minImageCount = ( surfaceCapabilities.maxImageCount > 0 && minImageCount > surfaceCapabilities.maxImageCount ) ? surfaceCapabilities.maxImageCount : minImageCount; vk::SwapchainCreateInfoKHR swapChainCreateInfo{ .surface = surface, .minImageCount = minImageCount, .imageFormat = swapChainImageFormat, .imageColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear, .imageExtent = swapChainExtent, .imageArrayLayers =1, .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, .imageSharingMode = vk::SharingMode::eExclusive, .preTransform = surfaceCapabilities.currentTransform, .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR( surface )), .clipped = true }; swapChain = vk::raii::SwapchainKHR( device, swapChainCreateInfo ); swapChainImages = swapChain.getImages(); } void createImageViews() { swapChainImageViews.clear(); vk::ImageViewCreateInfo imageViewCreateInfo{ .viewType = vk::ImageViewType::e2D, .format = swapChainImageFormat, .subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 } }; for ( auto image : swapChainImages ) { imageViewCreateInfo.image = image; swapChainImageViews.emplace_back( device, imageViewCreateInfo ); } } void createGraphicsPipeline() { vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); vk::PipelineShaderStageCreateInfo vertShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eVertex, .module = shaderModule, .pName = "vertMain" }; vk::PipelineShaderStageCreateInfo fragShaderStageInfo{ .stage = vk::ShaderStageFlagBits::eFragment, .module = shaderModule, .pName = "fragMain" }; vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; vk::PipelineVertexInputStateCreateInfo vertexInputInfo; vk::PipelineInputAssemblyStateCreateInfo inputAssembly{ .topology = vk::PrimitiveTopology::eTriangleList }; vk::PipelineViewportStateCreateInfo viewportState{ .viewportCount = 1, .scissorCount = 1 }; vk::PipelineRasterizationStateCreateInfo rasterizer{ .depthClampEnable = vk::False, .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eClockwise, .depthBiasEnable = vk::False, .depthBiasSlopeFactor = 1.0f, .lineWidth = 1.0f }; vk::PipelineMultisampleStateCreateInfo multisampling{.rasterizationSamples = vk::SampleCountFlagBits::e1, .sampleShadingEnable = vk::False}; vk::PipelineColorBlendAttachmentState colorBlendAttachment{ .blendEnable = vk::False, .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA }; vk::PipelineColorBlendStateCreateInfo colorBlending{.logicOpEnable = vk::False, .logicOp = vk::LogicOp::eCopy, .attachmentCount = 1, .pAttachments = &colorBlendAttachment }; std::vector dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor }; vk::PipelineDynamicStateCreateInfo dynamicState{ .dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data() }; vk::PipelineLayoutCreateInfo pipelineLayoutInfo{ .setLayoutCount = 0, .pushConstantRangeCount = 0 }; pipelineLayout = vk::raii::PipelineLayout( device, pipelineLayoutInfo ); vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo{ .colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainImageFormat }; vk::GraphicsPipelineCreateInfo pipelineInfo{ .pNext = &pipelineRenderingCreateInfo, .stageCount = 2, .pStages = shaderStages, .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, .pViewportState = &viewportState, .pRasterizationState = &rasterizer, .pMultisampleState = &multisampling, .pColorBlendState = &colorBlending, .pDynamicState = &dynamicState, .layout = pipelineLayout, .renderPass = nullptr }; graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); } [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { vk::ShaderModuleCreateInfo createInfo{ .codeSize = code.size() * sizeof(char), .pCode = reinterpret_cast(code.data()) }; vk::raii::ShaderModule shaderModule{ device, createInfo }; return shaderModule; } static vk::Format chooseSwapSurfaceFormat(const std::vector& availableFormats) { const auto formatIt = std::ranges::find_if(availableFormats, [](const auto& format) { return format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; }); return formatIt != availableFormats.end() ? formatIt->format : availableFormats[0].format; } static vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { return std::ranges::any_of(availablePresentModes, [](const vk::PresentModeKHR value) { return vk::PresentModeKHR::eMailbox == value; } ) ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eFifo; } vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } int width, height; glfwGetFramebufferSize(window, &width, &height); return { std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) }; } std::vector getRequiredExtensions() { uint32_t glfwExtensionCount = 0; auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if (enableValidationLayers) { extensions.push_back(vk::EXTDebugUtilsExtensionName ); } return extensions; } static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData, void*) { if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { std::cerr << "validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; } return vk::False; } 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!"); } std::vector buffer(file.tellg()); file.seekg(0, std::ios::beg); file.read(buffer.data(), static_cast(buffer.size())); file.close(); return buffer; } }; int main() { try { HelloTriangleApplication app; app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }