-
Notifications
You must be signed in to change notification settings - Fork 87
Update overview section #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
marty-johnson59
merged 2 commits into
KhronosGroup:main
from
johannes-el:overview-section
Nov 17, 2025
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,17 @@ graphics architectures. It reduces the driver overhead by allowing programmers t | |
| purpose processing capabilities of modern graphics cards by unifying the | ||
| graphics and compute functionality into a single API. | ||
|
|
||
| === Coding conventions | ||
|
|
||
| All the Vulkan functions, enumerations and structs are defined in the | ||
| `vulkan.h` header, which is included in the https://lunarg.com/vulkan-sdk/[Vulkan SDK] | ||
| developed by LunarG. We'll look into installing this SDK in the next chapter. | ||
| In this tutorial, we’ll be using the C++ Vulkan API provided by the vulkan.hpp header, | ||
| which comes with the official Vulkan SDK. This header offers a type-safe, RAII-friendly, | ||
| and slightly more ergonomic interface over the raw C Vulkan API, | ||
| while still maintaining a very close, low-level mapping | ||
| to the underlying Vulkan functions and structures. | ||
|
|
||
| == What it takes to draw a triangle | ||
|
|
||
| We'll now look at an overview of all the steps it takes to render a triangle | ||
|
|
@@ -52,26 +63,23 @@ This is just to give you a big picture to relate all the individual components t | |
|
|
||
| === Step 1 - Instance and physical device selection | ||
|
|
||
| A Vulkan application starts by setting up the Vulkan API through a `VkInstance`. | ||
| A Vulkan application starts by setting up the Vulkan API through a `vk::Instance`. | ||
| An instance is created by describing your application and any API extensions | ||
| you will be using. After creating the instance, you can query for Vulkan | ||
| supported hardware and select one or more ``VkPhysicalDevice``s to use for | ||
| supported hardware and select one or more ``vk::PhysicalDevice``s to use for | ||
| operations. You can query for properties like VRAM size and device | ||
| capabilities to select desired devices, for example, to prefer using | ||
| dedicated graphics cards. | ||
|
|
||
| For vk::raii we need to use a `vk::raii::Context`, which manages functions that | ||
| are not bound to either the `VkInstance` or a `VkPhysicalDevice` | ||
|
|
||
| === Step 2 - Logical device and queue families | ||
|
|
||
| After selecting the right hardware device to use, you need to create a | ||
| VkDevice (logical device), where you describe more specifically which | ||
| VkPhysicalDeviceFeatures you will be using, like multi viewport rendering | ||
| vk::Device (logical device), where you describe more specifically which | ||
| physical device features you will be using, like multi viewport rendering | ||
| and 64-bit floats. | ||
| You also need to specify which queue families you would like to use. | ||
| Most operations performed with Vulkan, like draw commands and memory | ||
| operations, are asynchronously executed by submitting them to a VkQueue. | ||
| operations, are asynchronously executed by submitting them to a vk::Queue. | ||
| Queues are allocated from queue families, where each queue family supports a | ||
| specific set of operations in its queues. | ||
| For example, there could be separate queue families for graphics, compute | ||
|
|
@@ -92,7 +100,7 @@ We will be using GLFW in this tutorial, but more about that in the next | |
| chapter. | ||
|
|
||
| We need two more parts to actually render to a window: a window surface | ||
| (VkSurfaceKHR) and a swap chain (VkSwapchainKHR). | ||
| (vk::SurfaceKHR) and a swap chain (vk::SwapchainKHR). | ||
| Note the `KHR` postfix, which means that these objects are part of a Vulkan | ||
| extension. The Vulkan API itself is completely platform-agnostic, which is | ||
| why we need to use the standardized WSI (Window System Interface) extension | ||
|
|
@@ -122,34 +130,45 @@ Some platforms allow you to render directly to a display without interacting | |
| These allow you to create a surface that represents the entire screen and | ||
| could be used to implement your own window manager, for example. | ||
|
|
||
| === Step 4 - Image views and framebuffers | ||
| === Step 4 - Image views and Dynamic Rendering | ||
|
|
||
| To draw to an image acquired from the swap chain, we would typically wrap | ||
| it into a vk::ImageView and vk::Framebuffer. An image view references a specific | ||
| part of an image to be used, and a framebuffer references image views that are | ||
| to be used for color, depth, and stencil targets. | ||
| Because there could be many different images in the swap chain, | ||
| we would preemptively create an image view and framebuffer for each | ||
| of them and select the right one at draw time. | ||
|
|
||
| However, with dynamic rendering (introduced in Vulkan 1.3), | ||
| you no longer need to create a `vk::Framebuffer` at all. | ||
| Dynamic rendering eliminates the need for predefined render passes and framebuffers, | ||
| allowing you to specify rendering attachments directly during command recording. | ||
| This makes the API much simpler, as we can define the rendering targets on the | ||
| fly without worrying about the overhead of managing framebuffers. | ||
|
|
||
| To draw to an image acquired from the swap chain, we have to wrap it into a | ||
| VkImageView and VkFramebuffer. | ||
| An image view references a specific part of an image to be used, and a | ||
| framebuffer references image views that are to be used for color, depth and | ||
| stencil targets. | ||
| Because there could be many different images in the swap chain, we'll | ||
| preemptively create an image view and framebuffer for each of them and | ||
| select the right one at draw time. | ||
| === Step 5 - Dynamic Rendering Overview | ||
|
|
||
| === Step 5 - Render passes | ||
| In earlier versions of Vulkan, a render pass defined how rendering operations | ||
| should occur with framebuffers, specifying the types of images used (e.g., color, depth) | ||
| and how their contents should be treated (e.g., cleared, loaded, or stored). | ||
| A `vk::RenderPass` would define subpasses and attachment usage, and a `vk::Framebuffer` | ||
| would bind specific image views to these attachments. | ||
|
|
||
| Render passes in Vulkan describe the type of images that are used during | ||
| rendering operations, how they will be used, and how their contents should | ||
| be treated. | ||
| In our initial triangle rendering application, we'll tell Vulkan that we | ||
| will use a single image as a color target and that we want it to be cleared to | ||
| a solid color right before the drawing operation. | ||
| Whereas a render pass only describes the type of images, a VkFramebuffer | ||
| actually binds specific images to these slots. | ||
| With dynamic rendering, you no longer need to predefine `vk::RenderPass` or `vk::Framebuffer`. | ||
| Instead, you specify the rendering attachments at the start of command recording, using `vk::beginRendering` | ||
| and structs like `vk::RenderingInfo` to provide all necessary attachment information dynamically. | ||
|
|
||
| In our initial triangle rendering application, | ||
| we'll use dynamic rendering to specify | ||
| a single image as a color target and instruct Vulkan to clear it to a solid color right before drawing. | ||
|
|
||
| === Step 6 - Graphics pipeline | ||
|
|
||
| The graphics pipeline in Vulkan is set up by creating a VkPipeline object. | ||
| It describes the configurable state of the graphics card, like the viewport | ||
| size and depth buffer operation and the programmable state using VkShaderModule objects. | ||
| The VkShaderModule objects are created from shader byte code. | ||
| size and depth buffer operation and the programmable state using vk::ShaderModule objects. | ||
| The vk::ShaderModule objects are created from shader byte code. | ||
| The driver also needs to know which render targets will be used in the | ||
| pipeline, which we specify by referencing the render pass. | ||
|
|
||
|
|
@@ -159,7 +178,7 @@ One of the most distinctive features of Vulkan compared to existing APIs, is | |
| That means that if you want to switch to a different shader or slightly | ||
| change your vertex layout, then you need to entirely recreate the graphics | ||
| pipeline. | ||
| That means that you will have to create many VkPipeline objects in advance | ||
| That means that you will have to create many vk::Pipeline objects in advance | ||
| for all the different combinations you need for your rendering operations. | ||
| Only some basic configuration, like viewport size and clear color, can be | ||
| changed dynamically. | ||
|
|
@@ -176,41 +195,47 @@ are made very explicit. | |
|
|
||
| As mentioned earlier, many of the operations in Vulkan that we want to | ||
| execute, like drawing operations, need to be submitted to a queue. | ||
| These operations first need to be recorded into a VkCommandBuffer before | ||
| These operations first need to be recorded into a vk::CommandBuffer before | ||
| they can be submitted. | ||
| These command buffers are allocated from a `VkCommandPool` that is | ||
| These command buffers are allocated from a `vk::CommandPool` that is | ||
| associated with a specific queue family. | ||
| To draw a simple triangle, we need to record a command buffer with the | ||
| Traditionally, to draw a simple triangle, we need to record a command buffer with the | ||
| following operations: | ||
|
|
||
| * Begin the render pass | ||
| * Bind the graphics pipeline | ||
| * Draw three vertices | ||
| * End the render pass | ||
|
|
||
| Because the image in the framebuffer depends on which specific image the | ||
| swap chain will give us, we need to record a command buffer for each | ||
| possible image and select the right one at draw time. | ||
| The alternative would be to record the command buffer again every frame, | ||
| which is not as efficient. | ||
| However, with dynamic rendering, things change. | ||
| Instead of "beginning" and "ending" a render pass, | ||
| you directly define the rendering attachments when you start | ||
| rendering with vk::BeginRendering. | ||
|
|
||
| This simplifies the process by allowing you to specify the necessary attachments on the fly, | ||
| making it more adaptable to scenarios where the swap chain images are dynamically selected. | ||
| Therefore, you don't need to record a command buffer for each image in the swap | ||
| chain or repeatedly record the same command buffer every frame. | ||
| The operations become more streamlined and efficient, | ||
| allowing Vulkan to be more flexible in handling rendering scenarios. | ||
|
|
||
| === Step 8 - Main loop | ||
|
|
||
| Now that the drawing commands have been wrapped into a command buffer, the | ||
| main loop is quite straightforward. | ||
| We first acquire an image from the swap chain with vkAcquireNextImageKHR. | ||
| We first acquire an image from the swap chain with device.acquireNextImageKHR. | ||
| We can then select the appropriate command buffer for that image and execute | ||
| it with vkQueueSubmit. | ||
| it with graphicsQueue.submit(). | ||
| Finally, we return the image to the swap chain for presentation to the | ||
| screen with vkQueuePresentKHR. | ||
| screen with presentQueue.presentKHR(presentInfo). | ||
|
|
||
| Operations that are submitted to queues are executed asynchronously. | ||
| Therefore, we have to use synchronization objects like semaphores to ensure a | ||
| correct order of execution. | ||
| Execution of the draw command buffer must be set up to wait on image | ||
| acquisition to finish; otherwise it may occur that we start rendering to an | ||
| image that is still being read for presentation on the screen. | ||
| The vkQueuePresentKHR call in turn needs to wait for rendering to be | ||
| The presentQueue.presentKHR(presentInfoKHR) call in turn needs to wait for rendering to be | ||
| finished, for which we'll use a second semaphore that is signaled after | ||
| rendering completes. | ||
|
|
||
|
|
@@ -229,13 +254,12 @@ command buffers first. | |
|
|
||
| So in short, to draw the first triangle, we need to: | ||
|
|
||
| * Create a VkInstance | ||
| * Select a supported graphics card (VkPhysicalDevice) | ||
| * Create a VkDevice and VkQueue for drawing and presentation | ||
| * Create a vk::Instance | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would remove the |
||
| * Select a supported graphics card (vk::PhysicalDevice) | ||
| * Create a vk::Device and vk::Queue for drawing and presentation | ||
| * Create a window, window surface and swap chain | ||
| * Wrap the swap chain images into VkImageView | ||
| * Create a render pass that specifies the render targets and usage | ||
| * Create framebuffers for the render pass | ||
| * Set up dynamic rendering | ||
| * Set up the graphics pipeline | ||
| * Allocate and record a command buffer with the draw commands for every | ||
| possible swap chain image | ||
|
|
@@ -252,28 +276,23 @@ If you're confused about the relation of a single step compared to the whole | |
| This chapter will conclude with a short overview of how the Vulkan API is | ||
| structured at a lower level. | ||
|
|
||
| === Coding conventions | ||
|
|
||
| All the Vulkan functions, enumerations and structs are defined in the | ||
| `vulkan.h` header, which is included in the https://lunarg.com/vulkan-sdk/[Vulkan SDK] | ||
| developed by LunarG. We'll look into installing this SDK in the next chapter. | ||
|
|
||
| Functions have a lower case `vk` prefix, types like enumerations and structs | ||
| have a `Vk` prefix and enumeration values have a `VK_` prefix. | ||
| The API heavily uses structs to provide parameters to functions. | ||
| For example, object creation generally follows this pattern: | ||
|
|
||
| [,c++] | ||
| ---- | ||
| VkXXXCreateInfo createInfo{}; | ||
| createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; | ||
| vk::XXXCreateInfo createInfo{}; | ||
| createInfo.sType = vk::StructureType::eXXXCreateInfo; | ||
| createInfo.pNext = nullptr; | ||
| createInfo.foo = ...; | ||
| createInfo.bar = ...; | ||
|
|
||
| VkXXX object; | ||
| if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { | ||
| std::cerr << "failed to create object" << std::endl; | ||
| vk::XXX object; | ||
|
|
||
|
|
||
| try { | ||
| object = device.createXXX(createInfo); | ||
| } catch (vk::SystemError& err) { | ||
| std::cerr << "Failed to create object: " << err.what() << std::endl; | ||
| return false; | ||
| } | ||
| ---- | ||
|
|
@@ -286,23 +305,14 @@ Functions that create or destroy an object will have a VkAllocationCallbacks | |
| parameter that allows you to use a custom allocator for driver memory, | ||
| which will also be left `nullptr` in this tutorial. | ||
|
|
||
| Almost all functions return a VkResult that is either `VK_SUCCESS` or an | ||
| error code. | ||
| Almost all functions return a vk::Result that is either `vk::result::eSuccess` | ||
| or an error code. | ||
| The specification describes which error codes each function can return and | ||
| what they mean. | ||
|
|
||
| To help illustrate the utility of using the RAII C++ Vulkan abstraction; this | ||
| is the same code written with our modern API: | ||
|
|
||
| [,c++] | ||
| ---- | ||
| auto createInfo = vk::xxx(); | ||
| auto object = vk::raii::XXX(context, createInfo); | ||
| ---- | ||
|
|
||
| Failure of such calls is reported by C++ exceptions. The exception will | ||
| respond with more information about the error including the aforementioned | ||
| vkResult, this enables us to check multiple commands from one call and keep | ||
| vk::Result, this enables us to check multiple commands from one call and keep | ||
| the command syntax clean. | ||
|
|
||
| === Validation layers | ||
|
|
@@ -332,4 +342,4 @@ layers are so extensive, it can actually be a lot easier to find out why | |
| your screen is black compared to OpenGL and Direct3D! | ||
|
|
||
| There's only one more step before we'll start writing code, and that's | ||
| xref:02_Development_environment.adoc[setting up the development environment]. | ||
| xref:02_Development_environment.adoc[setting up the development environment]. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would move all the dynamic rendering stuff to Step 5.