From 71054d63f563e947b88ecced4ebe2bb9ebf609d4 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Tue, 20 Jan 2026 12:04:39 +0100 Subject: [PATCH 1/2] Remove attachment 18_vertex_input --- attachments/18_vertex_input.cpp | 687 ------------------ ...uffer.frag => 19_shader_vertexbuffer.frag} | 0 ...fer.slang => 19_shader_vertexbuffer.slang} | 0 ...uffer.vert => 19_shader_vertexbuffer.vert} | 0 attachments/CMakeLists.txt | 10 +- 5 files changed, 3 insertions(+), 694 deletions(-) delete mode 100644 attachments/18_vertex_input.cpp rename attachments/{18_shader_vertexbuffer.frag => 19_shader_vertexbuffer.frag} (100%) rename attachments/{18_shader_vertexbuffer.slang => 19_shader_vertexbuffer.slang} (100%) rename attachments/{18_shader_vertexbuffer.vert => 19_shader_vertexbuffer.vert} (100%) diff --git a/attachments/18_vertex_input.cpp b/attachments/18_vertex_input.cpp deleted file mode 100644 index 2ebedc19..00000000 --- a/attachments/18_vertex_input.cpp +++ /dev/null @@ -1,687 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) -# include -#else -import vulkan_hpp; -#endif - -#define GLFW_INCLUDE_VULKAN // REQUIRED only for GLFW CreateWindowSurface. -#include -#include - -constexpr uint32_t WIDTH = 800; -constexpr uint32_t HEIGHT = 600; -constexpr int MAX_FRAMES_IN_FLIGHT = 2; - -const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation"}; - -#ifdef NDEBUG -constexpr bool enableValidationLayers = false; -#else -constexpr bool enableValidationLayers = true; -#endif - -struct Vertex -{ - glm::vec2 pos; - glm::vec3 color; - - static vk::VertexInputBindingDescription getBindingDescription() - { - return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; - } - - static std::array getAttributeDescriptions() - { - return { - vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), - vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color))}; - } -}; - -const std::vector vertices = { - {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, - {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; - -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; - uint32_t queueIndex = ~0; - vk::raii::Queue queue = nullptr; - vk::raii::SwapchainKHR swapChain = nullptr; - std::vector swapChainImages; - vk::SurfaceFormatKHR swapChainSurfaceFormat; - vk::Extent2D swapChainExtent; - std::vector swapChainImageViews; - - vk::raii::PipelineLayout pipelineLayout = nullptr; - vk::raii::Pipeline graphicsPipeline = nullptr; - - vk::raii::CommandPool commandPool = nullptr; - std::vector commandBuffers; - - std::vector presentCompleteSemaphores; - std::vector renderFinishedSemaphores; - std::vector inFlightFences; - uint32_t frameIndex = 0; - - bool framebufferResized = false; - - std::vector requiredDeviceExtension = { - vk::KHRSwapchainExtensionName}; - - void initWindow() - { - glfwInit(); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); - - 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(); - createSwapChain(); - createImageViews(); - createGraphicsPipeline(); - createCommandPool(); - createCommandBuffers(); - createSyncObjects(); - } - - void mainLoop() - { - while (!glfwWindowShouldClose(window)) - { - glfwPollEvents(); - drawFrame(); - } - - device.waitIdle(); - } - - void cleanupSwapChain() - { - swapChainImageViews.clear(); - swapChain = nullptr; - } - - void cleanup() - { - glfwDestroyWindow(window); - - glfwTerminate(); - } - - void recreateSwapChain() - { - int width = 0, height = 0; - glfwGetFramebufferSize(window, &width, &height); - while (width == 0 || height == 0) - { - glfwGetFramebufferSize(window, &width, &height); - glfwWaitEvents(); - } - - device.waitIdle(); - - cleanupSwapChain(); - createSwapChain(); - createImageViews(); - } - - 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().shaderDrawParameters && - 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 - 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 required features (Vulkan 1.1 and 1.3) - vk::StructureChain featureChain = { - {}, // vk::PhysicalDeviceFeatures2 - {.shaderDrawParameters = true}, // vk::PhysicalDeviceVulkan11Features - {.synchronization2 = true, .dynamicRendering = true}, // vk::PhysicalDeviceVulkan13Features - {.extendedDynamicState = true} // vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT - }; - - // create a Device - float queuePriority = 0.5f; - 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); - swapChainExtent = chooseSwapExtent(surfaceCapabilities); - swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); - vk::SwapchainCreateInfoKHR swapChainCreateInfo{.surface = *surface, - .minImageCount = chooseSwapMinImageCount(surfaceCapabilities), - .imageFormat = swapChainSurfaceFormat.format, - .imageColorSpace = swapChainSurfaceFormat.colorSpace, - .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() - { - assert(swapChainImageViews.empty()); - - vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, .format = swapChainSurfaceFormat.format, .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}; - - auto bindingDescription = Vertex::getBindingDescription(); - auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; - 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::StructureChain pipelineCreateInfoChain = { - {.stageCount = 2, - .pStages = shaderStages, - .pVertexInputState = &vertexInputInfo, - .pInputAssemblyState = &inputAssembly, - .pViewportState = &viewportState, - .pRasterizationState = &rasterizer, - .pMultisampleState = &multisampling, - .pColorBlendState = &colorBlending, - .pDynamicState = &dynamicState, - .layout = pipelineLayout, - .renderPass = nullptr}, - {.colorAttachmentCount = 1, .pColorAttachmentFormats = &swapChainSurfaceFormat.format}}; - - graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineCreateInfoChain.get()); - } - - void createCommandPool() - { - vk::CommandPoolCreateInfo poolInfo{.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, - .queueFamilyIndex = queueIndex}; - commandPool = vk::raii::CommandPool(device, poolInfo); - } - - void createCommandBuffers() - { - commandBuffers.clear(); - vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = MAX_FRAMES_IN_FLIGHT}; - commandBuffers = vk::raii::CommandBuffers(device, allocInfo); - } - - void recordCommandBuffer(uint32_t imageIndex) - { - auto &commandBuffer = commandBuffers[frameIndex]; - commandBuffer.begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL - transition_image_layout( - imageIndex, - vk::ImageLayout::eUndefined, - vk::ImageLayout::eColorAttachmentOptimal, - {}, // srcAccessMask (no need to wait for previous operations) - vk::AccessFlagBits2::eColorAttachmentWrite, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eColorAttachmentOutput // dstStage - ); - vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); - vk::RenderingAttachmentInfo attachmentInfo = { - .imageView = swapChainImageViews[imageIndex], - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = clearColor}; - vk::RenderingInfo renderingInfo = { - .renderArea = {.offset = {0, 0}, .extent = swapChainExtent}, - .layerCount = 1, - .colorAttachmentCount = 1, - .pColorAttachments = &attachmentInfo}; - commandBuffer.beginRendering(renderingInfo); - commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); - commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); - commandBuffer.draw(3, 1, 0, 0); - commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC - transition_image_layout( - imageIndex, - vk::ImageLayout::eColorAttachmentOptimal, - vk::ImageLayout::ePresentSrcKHR, - vk::AccessFlagBits2::eColorAttachmentWrite, // srcAccessMask - {}, // dstAccessMask - vk::PipelineStageFlagBits2::eColorAttachmentOutput, // srcStage - vk::PipelineStageFlagBits2::eBottomOfPipe // dstStage - ); - commandBuffer.end(); - } - - void transition_image_layout( - uint32_t imageIndex, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::AccessFlags2 src_access_mask, - vk::AccessFlags2 dst_access_mask, - vk::PipelineStageFlags2 src_stage_mask, - vk::PipelineStageFlags2 dst_stage_mask) - { - vk::ImageMemoryBarrier2 barrier = { - .srcStageMask = src_stage_mask, - .srcAccessMask = src_access_mask, - .dstStageMask = dst_stage_mask, - .dstAccessMask = dst_access_mask, - .oldLayout = old_layout, - .newLayout = new_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = swapChainImages[imageIndex], - .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1}}; - vk::DependencyInfo dependency_info = { - .dependencyFlags = {}, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier}; - commandBuffers[frameIndex].pipelineBarrier2(dependency_info); - } - - void createSyncObjects() - { - assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); - - for (size_t i = 0; i < swapChainImages.size(); i++) - { - renderFinishedSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - } - - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) - { - presentCompleteSemaphores.emplace_back(device, vk::SemaphoreCreateInfo()); - inFlightFences.emplace_back(device, vk::FenceCreateInfo{.flags = vk::FenceCreateFlagBits::eSignaled}); - } - } - - void drawFrame() - { - // Note: inFlightFences, presentCompleteSemaphores, and commandBuffers are indexed by frameIndex, - // while renderFinishedSemaphores is indexed by imageIndex - auto fenceResult = device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX); - if (fenceResult != vk::Result::eSuccess) - { - throw std::runtime_error("failed to wait for fence!"); - } - - auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); - - // Due to VULKAN_HPP_HANDLE_ERROR_OUT_OF_DATE_AS_SUCCESS being defined, eErrorOutOfDateKHR can be checked as a result - // here and does not need to be caught by an exception. - if (result == vk::Result::eErrorOutOfDateKHR) - { - recreateSwapChain(); - return; - } - // On other success codes than eSuccess and eSuboptimalKHR we just throw an exception. - // On any error code, aquireNextImage already threw an exception. - if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) - { - assert(result == vk::Result::eTimeout || result == vk::Result::eNotReady); - throw std::runtime_error("failed to acquire swap chain image!"); - } - - // Only reset the fence if we are submitting work - device.resetFences(*inFlightFences[frameIndex]); - - commandBuffers[frameIndex].reset(); - recordCommandBuffer(imageIndex); - - vk::PipelineStageFlags waitDestinationStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput); - const vk::SubmitInfo submitInfo{.waitSemaphoreCount = 1, - .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], - .pWaitDstStageMask = &waitDestinationStageMask, - .commandBufferCount = 1, - .pCommandBuffers = &*commandBuffers[frameIndex], - .signalSemaphoreCount = 1, - .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex]}; - queue.submit(submitInfo, *inFlightFences[frameIndex]); - - const vk::PresentInfoKHR presentInfoKHR{.waitSemaphoreCount = 1, - .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], - .swapchainCount = 1, - .pSwapchains = &*swapChain, - .pImageIndices = &imageIndex}; - result = queue.presentKHR(presentInfoKHR); - // Due to VULKAN_HPP_HANDLE_ERROR_OUT_OF_DATE_AS_SUCCESS being defined, eErrorOutOfDateKHR can be checked as a result - // here and does not need to be caught by an exception. - if ((result == vk::Result::eSuboptimalKHR) || (result == vk::Result::eErrorOutOfDateKHR) || framebufferResized) - { - framebufferResized = false; - recreateSwapChain(); - } - else - { - // There are no other success codes than eSuccess; on any error code, presentKHR already threw an exception. - assert(result == vk::Result::eSuccess); - } - frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; - } - - [[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 uint32_t chooseSwapMinImageCount(vk::SurfaceCapabilitiesKHR const &surfaceCapabilities) - { - auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); - if ((0 < surfaceCapabilities.maxImageCount) && (surfaceCapabilities.maxImageCount < minImageCount)) - { - minImageCount = surfaceCapabilities.maxImageCount; - } - return minImageCount; - } - - static vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector &availableFormats) - { - assert(!availableFormats.empty()); - 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 : availableFormats[0]; - } - - static vk::PresentModeKHR chooseSwapPresentMode(const std::vector &availablePresentModes) - { - assert(std::ranges::any_of(availablePresentModes, [](auto presentMode) { return presentMode == vk::PresentModeKHR::eFifo; })); - 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 != 0xFFFFFFFF) - { - 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; -} diff --git a/attachments/18_shader_vertexbuffer.frag b/attachments/19_shader_vertexbuffer.frag similarity index 100% rename from attachments/18_shader_vertexbuffer.frag rename to attachments/19_shader_vertexbuffer.frag diff --git a/attachments/18_shader_vertexbuffer.slang b/attachments/19_shader_vertexbuffer.slang similarity index 100% rename from attachments/18_shader_vertexbuffer.slang rename to attachments/19_shader_vertexbuffer.slang diff --git a/attachments/18_shader_vertexbuffer.vert b/attachments/19_shader_vertexbuffer.vert similarity index 100% rename from attachments/18_shader_vertexbuffer.vert rename to attachments/19_shader_vertexbuffer.vert diff --git a/attachments/CMakeLists.txt b/attachments/CMakeLists.txt index 1261ca30..5d2cb6cf 100644 --- a/attachments/CMakeLists.txt +++ b/attachments/CMakeLists.txt @@ -213,20 +213,16 @@ add_chapter (16_frames_in_flight add_chapter (17_swap_chain_recreation SHADER 09_shader_base) -add_chapter (18_vertex_input - SHADER 18_shader_vertexbuffer - LIBS glm::glm) - add_chapter (19_vertex_buffer - SHADER 18_shader_vertexbuffer + SHADER 19_shader_vertexbuffer LIBS glm::glm) add_chapter (20_staging_buffer - SHADER 18_shader_vertexbuffer + SHADER 19_shader_vertexbuffer LIBS glm::glm) add_chapter (21_index_buffer - SHADER 18_shader_vertexbuffer + SHADER 19_shader_vertexbuffer LIBS glm::glm) add_chapter (22_descriptor_layout From 9d7678b44bb033c28ece59c08afb9b455fdb44e6 Mon Sep 17 00:00:00 2001 From: asuessenbach Date: Tue, 20 Jan 2026 17:57:42 +0100 Subject: [PATCH 2/2] Adjust documentation --- .../00_Vertex_input_description.adoc | 48 +++++++++---------- .../01_Vertex_buffer_creation.adoc | 6 +-- en/04_Vertex_buffers/02_Staging_buffer.adoc | 6 +-- en/04_Vertex_buffers/03_Index_buffer.adoc | 6 +-- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/en/04_Vertex_buffers/00_Vertex_input_description.adoc b/en/04_Vertex_buffers/00_Vertex_input_description.adoc index f75a9845..1758ad9b 100644 --- a/en/04_Vertex_buffers/00_Vertex_input_description.adoc +++ b/en/04_Vertex_buffers/00_Vertex_input_description.adoc @@ -58,7 +58,8 @@ Create a new structure called `Vertex` with the two attributes that we're going [,c++] ---- -struct Vertex { +struct Vertex +{ glm::vec2 pos; glm::vec3 color; }; @@ -84,7 +85,7 @@ This is known as _interleaving_ vertex attributes. The next step is to tell Vulkan how to pass this data format to the vertex shader once it's been uploaded into GPU memory. There are two types of structures needed to convey this information. -The first structure is `VkVertexInputBindingDescription` and we'll add a member function to the `Vertex` struct to populate it with the right data. +The first structure is `vk::VertexInputBindingDescription` and we'll add a member function to the `Vertex` struct to populate it with the right data. [,c++] ---- @@ -105,14 +106,14 @@ All of our per-vertex data is packed together in one array, so we're only going The `binding` parameter specifies the index of the binding in the array of bindings. The `stride` parameter specifies the number of bytes from one entry to the next, and the `inputRate` parameter can have one of the following values: -* `VK_VERTEX_INPUT_RATE_VERTEX`: Move to the next data entry after each vertex -* `VK_VERTEX_INPUT_RATE_INSTANCE`: Move to the next data entry after each instance +* `vk::VertexInputRate::eVertex` : Move to the next data entry after each vertex +* `vk::VertexInputRate::eInstance`: Move to the next data entry after each instance We're not going to use instanced rendering, so we'll stick to per-vertex data. == Attribute descriptions -The second structure that describes how to handle vertex input is `VkVertexInputAttributeDescription`. +The second structure that describes how to handle vertex input is `vk::VertexInputAttributeDescription`. We're going to add another helper function to `Vertex` to fill in these structs. [,c++] @@ -141,22 +142,20 @@ The `format` parameter describes the type of data for the attribute. A bit confusingly, the formats are specified using the same enumeration as color formats. The following shader types and formats are commonly used together: -* `float`: `VK_FORMAT_R32_SFLOAT` -* `float2`: `VK_FORMAT_R32G32_SFLOAT` -* `float3`: `VK_FORMAT_R32G32B32_SFLOAT` -* `float4`: `VK_FORMAT_R32G32B32A32_SFLOAT` +* `float` : `vk::Format::eR32Sfloat` +* `float2`: `vk::Format::eR32G32Sfloat` +* `float3`: `vk::Format::eR32G32B32Sfloat` +* `float4`: `vk::Format::eR32G32B32A32Sfloat` As you can see, you should use the format where the amount of color channels matches the number of components in the shader data type. It is allowed to use more channels than the number of components in the shader, but they will be silently discarded. If the number of channels is lower than the number of components, then the BGA components will use default values of `(0, 0, 1)`. -The color type (`SFLOAT`, `UINT`, `SINT`) and bit width should also match the type of the shader input. +The color type (`Sfloat`, `Uint`, `Sint`) and bit width should also match the type of the shader input. See the following examples: -* `int2`: `VK_FORMAT_R32G32_SINT`, a 2-component vector of 32-bit signed -integers -* `uint4`: `VK_FORMAT_R32G32B32A32_UINT`, a 4-component vector of 32-bit -unsigned integers -* `double`: `VK_FORMAT_R64_SFLOAT`, a double-precision (64-bit) float +* `int2` : `vk::Format::eR32G32Sint`, a 2-component vector of 32-bit signed integers +* `uint4` : `vk::Format::eR32G32B32A32Uint`, a 4-component vector of 32-bit unsigned integers +* `double`: `vk::Format::eR64Sfloat`, a double-precision (64-bit) float The `format` parameter implicitly defines the byte size of attribute data and the `offset` parameter has specified the number of bytes since the start of the per-vertex data to read from. The binding is loading one `Vertex` at a time and the position attribute (`pos`) is at an offset of `0` bytes from the beginning of this struct. @@ -171,17 +170,18 @@ Find the `vertexInputInfo` struct and modify it to reference the two description [,c++] ---- -auto bindingDescription = Vertex::getBindingDescription(); -auto attributeDescriptions = Vertex::getAttributeDescriptions(); -vk::PipelineVertexInputStateCreateInfo vertexInputInfo { .vertexBindingDescriptionCount =1, .pVertexBindingDescriptions = &bindingDescription, - .vertexAttributeDescriptionCount = attributeDescriptions.size(), .pVertexAttributeDescriptions = attributeDescriptions.data() }; +auto bindingDescription = Vertex::getBindingDescription(); +auto attributeDescriptions = Vertex::getAttributeDescriptions(); +vk::PipelineVertexInputStateCreateInfo vertexInputInfo{ .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast( attributeDescriptions.size() ), + .pVertexAttributeDescriptions = attributeDescriptions.data() }; ---- The pipeline is now ready to accept vertex data in the format of the `vertices` container and pass it on to our vertex shader. -If you run the program now with validation layers enabled, you'll see that it complains that there is no vertex buffer bound to the binding. The xref:./01_Vertex_buffer_creation.adoc[next step] is to create a vertex buffer and move the vertex data to it so the GPU is able to access it. -link:/attachments/18_vertex_input.cpp[C{pp} code] / -link:/attachments/18_shader_vertexbuffer.slang[slang shader] / -link:/attachments/18_shader_vertexbuffer.vert[GLSL Vertex shader] / -link:/attachments/18_shader_vertexbuffer.frag[GLSL Fragment shader] +link:/attachments/19_vertex_buffer.cpp[C{pp} code] / +link:/attachments/19_shader_vertexbuffer.slang[slang shader] / +link:/attachments/19_shader_vertexbuffer.vert[GLSL Vertex shader] / +link:/attachments/19_shader_vertexbuffer.frag[GLSL Fragment shader] diff --git a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc index 341941b1..c26f4e29 100644 --- a/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc +++ b/en/04_Vertex_buffers/01_Vertex_buffer_creation.adoc @@ -262,6 +262,6 @@ image::/images/triangle_white.png[] In the xref:./02_Staging_buffer.adoc[next chapter,] we'll look at a different way to copy vertex data to a vertex buffer that results in better performance, but takes some more work. link:/attachments/19_vertex_buffer.cpp[C{pp} code] / -link:/attachments/18_shader_vertexbuffer.slang[slang shader] / -link:/attachments/18_shader_vertexbuffer.vert[GLSL Vertex shader] / -link:/attachments/18_shader_vertexbuffer.frag[GLSL Fragment shader] +link:/attachments/19_shader_vertexbuffer.slang[slang shader] / +link:/attachments/19_shader_vertexbuffer.vert[GLSL Vertex shader] / +link:/attachments/19_shader_vertexbuffer.frag[GLSL Fragment shader] diff --git a/en/04_Vertex_buffers/02_Staging_buffer.adoc b/en/04_Vertex_buffers/02_Staging_buffer.adoc index d6a8a8f4..e9dd93bc 100644 --- a/en/04_Vertex_buffers/02_Staging_buffer.adoc +++ b/en/04_Vertex_buffers/02_Staging_buffer.adoc @@ -206,6 +206,6 @@ However, for this tutorial, it's okay to use a separate allocation for every res In the xref:./03_Index_buffer.adoc[next chapter,] we'll learn about index buffers for vertex reuse. link:/attachments/20_staging_buffer.cpp[C{pp} code] / -link:/attachments/18_shader_vertexbuffer.slang[slang shader] / -link:/attachments/18_shader_vertexbuffer.vert[GLSL Vertex shader] / -link:/attachments/18_shader_vertexbuffer.frag[GLSL Fragment shader] +link:/attachments/19_shader_vertexbuffer.slang[slang shader] / +link:/attachments/19_shader_vertexbuffer.vert[GLSL Vertex shader] / +link:/attachments/19_shader_vertexbuffer.frag[GLSL Fragment shader] diff --git a/en/04_Vertex_buffers/03_Index_buffer.adoc b/en/04_Vertex_buffers/03_Index_buffer.adoc index ff8c3eaf..f6b85e64 100644 --- a/en/04_Vertex_buffers/03_Index_buffer.adoc +++ b/en/04_Vertex_buffers/03_Index_buffer.adoc @@ -141,6 +141,6 @@ This is known as _aliasing_ and some Vulkan functions have explicit flags to spe The xref:05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.adoc[next chapter] we'll learn how to pass frequently changing parameters to the GPU. link:/attachments/21_index_buffer.cpp[C{pp} code] / -link:/attachments/18_shader_vertexbuffer.slang[slang shader] / -link:/attachments/18_shader_vertexbuffer.vert[GLSL Vertex shader] / -link:/attachments/18_shader_vertexbuffer.frag[GLSL Fragment shader] +link:/attachments/19_shader_vertexbuffer.slang[slang shader] / +link:/attachments/19_shader_vertexbuffer.vert[GLSL Vertex shader] / +link:/attachments/19_shader_vertexbuffer.frag[GLSL Fragment shader]