@@ -43,6 +43,17 @@ graphics architectures. It reduces the driver overhead by allowing programmers t
4343 purpose processing capabilities of modern graphics cards by unifying the
4444 graphics and compute functionality into a single API.
4545
46+ === Coding conventions
47+
48+ All the Vulkan functions, enumerations and structs are defined in the
49+ `vulkan.h` header, which is included in the https://lunarg.com/vulkan-sdk/[Vulkan SDK]
50+ developed by LunarG. We'll look into installing this SDK in the next chapter.
51+ In this tutorial, we’ll be using the C++ Vulkan API provided by the vulkan.hpp header,
52+ which comes with the official Vulkan SDK. This header offers a type-safe, RAII-friendly,
53+ and slightly more ergonomic interface over the raw C Vulkan API,
54+ while still maintaining a very close, low-level mapping
55+ to the underlying Vulkan functions and structures.
56+
4657== What it takes to draw a triangle
4758
4859We'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
5263
5364=== Step 1 - Instance and physical device selection
5465
55- A Vulkan application starts by setting up the Vulkan API through a `VkInstance `.
66+ A Vulkan application starts by setting up the Vulkan API through a `vk::Instance `.
5667An instance is created by describing your application and any API extensions
5768 you will be using. After creating the instance, you can query for Vulkan
58- supported hardware and select one or more ``VkPhysicalDevice ``s to use for
69+ supported hardware and select one or more ``vk::PhysicalDevice ``s to use for
5970 operations. You can query for properties like VRAM size and device
6071 capabilities to select desired devices, for example, to prefer using
6172 dedicated graphics cards.
6273
63- For vk::raii we need to use a `vk::raii::Context`, which manages functions that
64- are not bound to either the `VkInstance` or a `VkPhysicalDevice`
65-
6674=== Step 2 - Logical device and queue families
6775
6876After selecting the right hardware device to use, you need to create a
69- VkDevice (logical device), where you describe more specifically which
70- VkPhysicalDeviceFeatures you will be using, like multi viewport rendering
77+ vk::Device (logical device), where you describe more specifically which
78+ physical device features you will be using, like multi viewport rendering
7179and 64-bit floats.
7280You also need to specify which queue families you would like to use.
7381Most operations performed with Vulkan, like draw commands and memory
74- operations, are asynchronously executed by submitting them to a VkQueue .
82+ operations, are asynchronously executed by submitting them to a vk::Queue .
7583Queues are allocated from queue families, where each queue family supports a
7684 specific set of operations in its queues.
7785For 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
92100chapter.
93101
94102We need two more parts to actually render to a window: a window surface
95- (VkSurfaceKHR ) and a swap chain (VkSwapchainKHR ).
103+ (vk::SurfaceKHR ) and a swap chain (vk::SwapchainKHR ).
96104Note the `KHR` postfix, which means that these objects are part of a Vulkan
97105extension. The Vulkan API itself is completely platform-agnostic, which is
98106why 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
122130These allow you to create a surface that represents the entire screen and
123131could be used to implement your own window manager, for example.
124132
125- === Step 4 - Image views and framebuffers
133+ === Step 4 - Image views and Dynamic Rendering
134+
135+ To draw to an image acquired from the swap chain, we would typically wrap
136+ it into a vk::ImageView and vk::Framebuffer. An image view references a specific
137+ part of an image to be used, and a framebuffer references image views that are
138+ to be used for color, depth, and stencil targets.
139+ Because there could be many different images in the swap chain,
140+ we would preemptively create an image view and framebuffer for each
141+ of them and select the right one at draw time.
142+
143+ === Step 5 - Dynamic Rendering Overview
126144
127- To draw to an image acquired from the swap chain, we have to wrap it into a
128- VkImageView and VkFramebuffer.
129- An image view references a specific part of an image to be used, and a
130- framebuffer references image views that are to be used for color, depth and
131- stencil targets.
132- Because there could be many different images in the swap chain, we'll
133- preemptively create an image view and framebuffer for each of them and
134- select the right one at draw time.
145+ In earlier versions of Vulkan, a render pass defined how rendering operations
146+ should occur with framebuffers, specifying the types of images used (e.g., color, depth)
147+ and how their contents should be treated (e.g., cleared, loaded, or stored).
148+ A `vk::RenderPass` would define subpasses and attachment usage, and a `vk::Framebuffer`
149+ would bind specific image views to these attachments.
135150
136- === Step 5 - Render passes
151+ However, with dynamic rendering (introduced in Vulkan 1.3),
152+ you no longer need to create a `vk::Framebuffer` at all.
153+ Dynamic rendering eliminates the need for predefined render passes and framebuffers,
154+ allowing you to specify rendering attachments directly during command recording.
155+ This makes the API much simpler, as we can define the rendering targets on the
156+ fly without worrying about the overhead of managing framebuffers.
137157
138- Render passes in Vulkan describe the type of images that are used during
139- rendering operations, how they will be used, and how their contents should
140- be treated.
141- In our initial triangle rendering application, we'll tell Vulkan that we
142- will use a single image as a color target and that we want it to be cleared to
143- a solid color right before the drawing operation.
144- Whereas a render pass only describes the type of images, a VkFramebuffer
145- actually binds specific images to these slots.
158+ With dynamic rendering, you no longer need to predefine `vk::RenderPass` or `vk::Framebuffer`.
159+ Instead, you specify the rendering attachments at the start of command recording, using `vk::beginRendering`
160+ and structs like `vk::RenderingInfo` to provide all necessary attachment information dynamically.
161+
162+ In our initial triangle rendering application,
163+ we'll use dynamic rendering to specify
164+ a single image as a color target and instruct Vulkan to clear it to a solid color right before drawing.
146165
147166=== Step 6 - Graphics pipeline
148167
149168The graphics pipeline in Vulkan is set up by creating a VkPipeline object.
150169It describes the configurable state of the graphics card, like the viewport
151- size and depth buffer operation and the programmable state using VkShaderModule objects.
152- The VkShaderModule objects are created from shader byte code.
170+ size and depth buffer operation and the programmable state using vk::ShaderModule objects.
171+ The vk::ShaderModule objects are created from shader byte code.
153172The driver also needs to know which render targets will be used in the
154173pipeline, which we specify by referencing the render pass.
155174
@@ -159,7 +178,7 @@ One of the most distinctive features of Vulkan compared to existing APIs, is
159178That means that if you want to switch to a different shader or slightly
160179change your vertex layout, then you need to entirely recreate the graphics
161180pipeline.
162- That means that you will have to create many VkPipeline objects in advance
181+ That means that you will have to create many vk::Pipeline objects in advance
163182for all the different combinations you need for your rendering operations.
164183Only some basic configuration, like viewport size and clear color, can be
165184changed dynamically.
@@ -176,41 +195,47 @@ are made very explicit.
176195
177196As mentioned earlier, many of the operations in Vulkan that we want to
178197execute, like drawing operations, need to be submitted to a queue.
179- These operations first need to be recorded into a VkCommandBuffer before
198+ These operations first need to be recorded into a vk::CommandBuffer before
180199they can be submitted.
181- These command buffers are allocated from a `VkCommandPool ` that is
200+ These command buffers are allocated from a `vk::CommandPool ` that is
182201associated with a specific queue family.
183- To draw a simple triangle, we need to record a command buffer with the
202+ Traditionally, to draw a simple triangle, we need to record a command buffer with the
184203following operations:
185204
186205* Begin the render pass
187206* Bind the graphics pipeline
188207* Draw three vertices
189208* End the render pass
190209
191- Because the image in the framebuffer depends on which specific image the
192- swap chain will give us, we need to record a command buffer for each
193- possible image and select the right one at draw time.
194- The alternative would be to record the command buffer again every frame,
195- which is not as efficient.
210+ However, with dynamic rendering, things change.
211+ Instead of "beginning" and "ending" a render pass,
212+ you directly define the rendering attachments when you start
213+ rendering with vk::BeginRendering.
214+
215+ This simplifies the process by allowing you to specify the necessary attachments on the fly,
216+ making it more adaptable to scenarios where the swap chain images are dynamically selected.
217+ Therefore, you don't need to record a command buffer for each image in the swap
218+ chain or repeatedly record the same command buffer every frame.
219+ The operations become more streamlined and efficient,
220+ allowing Vulkan to be more flexible in handling rendering scenarios.
196221
197222=== Step 8 - Main loop
198223
199224Now that the drawing commands have been wrapped into a command buffer, the
200225main loop is quite straightforward.
201- We first acquire an image from the swap chain with vkAcquireNextImageKHR .
226+ We first acquire an image from the swap chain with device.acquireNextImageKHR .
202227We can then select the appropriate command buffer for that image and execute
203- it with vkQueueSubmit .
228+ it with graphicsQueue.submit() .
204229Finally, we return the image to the swap chain for presentation to the
205- screen with vkQueuePresentKHR .
230+ screen with presentQueue.presentKHR(presentInfo) .
206231
207232Operations that are submitted to queues are executed asynchronously.
208233Therefore, we have to use synchronization objects like semaphores to ensure a
209234 correct order of execution.
210235Execution of the draw command buffer must be set up to wait on image
211236acquisition to finish; otherwise it may occur that we start rendering to an
212237image that is still being read for presentation on the screen.
213- The vkQueuePresentKHR call in turn needs to wait for rendering to be
238+ The presentQueue.presentKHR(presentInfoKHR) call in turn needs to wait for rendering to be
214239finished, for which we'll use a second semaphore that is signaled after
215240rendering completes.
216241
@@ -229,13 +254,12 @@ command buffers first.
229254
230255So in short, to draw the first triangle, we need to:
231256
232- * Create a VkInstance
233- * Select a supported graphics card (VkPhysicalDevice )
234- * Create a VkDevice and VkQueue for drawing and presentation
257+ * Create an Instance
258+ * Select a supported graphics card (PhysicalDevice )
259+ * Create a Device and Queue for drawing and presentation
235260* Create a window, window surface and swap chain
236261* Wrap the swap chain images into VkImageView
237- * Create a render pass that specifies the render targets and usage
238- * Create framebuffers for the render pass
262+ * Set up dynamic rendering
239263* Set up the graphics pipeline
240264* Allocate and record a command buffer with the draw commands for every
241265possible swap chain image
@@ -252,28 +276,23 @@ If you're confused about the relation of a single step compared to the whole
252276This chapter will conclude with a short overview of how the Vulkan API is
253277structured at a lower level.
254278
255- === Coding conventions
256-
257- All the Vulkan functions, enumerations and structs are defined in the
258- `vulkan.h` header, which is included in the https://lunarg.com/vulkan-sdk/[Vulkan SDK]
259- developed by LunarG. We'll look into installing this SDK in the next chapter.
260-
261- Functions have a lower case `vk` prefix, types like enumerations and structs
262- have a `Vk` prefix and enumeration values have a `VK_` prefix.
263- The API heavily uses structs to provide parameters to functions.
264279For example, object creation generally follows this pattern:
265280
266281[,c++]
267282----
268- VkXXXCreateInfo createInfo{};
269- createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO ;
283+ vk::XXXCreateInfo createInfo{};
284+ createInfo.sType = vk::StructureType::eXXXCreateInfo ;
270285createInfo.pNext = nullptr;
271286createInfo.foo = ...;
272287createInfo.bar = ...;
273288
274- VkXXX object;
275- if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) {
276- std::cerr << "failed to create object" << std::endl;
289+ vk::XXX object;
290+
291+
292+ try {
293+ object = device.createXXX(createInfo);
294+ } catch (vk::SystemError& err) {
295+ std::cerr << "Failed to create object: " << err.what() << std::endl;
277296 return false;
278297}
279298----
@@ -286,23 +305,14 @@ Functions that create or destroy an object will have a VkAllocationCallbacks
286305 parameter that allows you to use a custom allocator for driver memory,
287306 which will also be left `nullptr` in this tutorial.
288307
289- Almost all functions return a VkResult that is either `VK_SUCCESS` or an
290- error code.
308+ Almost all functions return a vk::Result that is either `vk::result::eSuccess`
309+ or an error code.
291310The specification describes which error codes each function can return and
292311what they mean.
293312
294- To help illustrate the utility of using the RAII C++ Vulkan abstraction; this
295- is the same code written with our modern API:
296-
297- [,c++]
298- ----
299- auto createInfo = vk::xxx();
300- auto object = vk::raii::XXX(context, createInfo);
301- ----
302-
303313Failure of such calls is reported by C++ exceptions. The exception will
304314respond with more information about the error including the aforementioned
305- vkResult , this enables us to check multiple commands from one call and keep
315+ vk::Result , this enables us to check multiple commands from one call and keep
306316the command syntax clean.
307317
308318=== Validation layers
0 commit comments