Hello Triangle¶

This is the most basic KDGpu rendering example: a single rotating triangle on the screen. It demonstrates the fundamental graphics flow - initializing graphics resources, setting up a graphics pipeline, and submitting draw commands each frame. This example is the best starting point for understanding how to render with KDGpu.
This example makes use of the KDGpuExample helper API which simplifies example creation by hiding repetitive and common patterns such as Instance and Device initialize and Swapchain creation.
To see what it's like to use KDGpu without the KDGpuExample API, check out the Hello Triangle Native API example.
Overview¶
What this example demonstrates:
- Creating a KDGpu graphics application with window
- Setting up vertex and index buffers with geometry data
- Creating a graphics pipeline with shaders
- Binding pipeline state and buffers
- Recording and submitting draw commands each frame
- Rotating geometry using uniform buffers and matrix transforms
Key concepts for beginners:
- Vertex Buffer: GPU memory containing vertex positions and attributes
- Index Buffer: GPU memory telling GPU which vertices form triangles
- Uniform Buffer: GPU memory for data that changes per-frame (like transforms)
- Graphics Pipeline: GPU state including shaders, vertex layout, and rendering modes
- Command Recording: Building a list of GPU commands to execute
- Draw Command: GPU command to render vertices using current pipeline/buffers
Vulkan concepts:
- VkBuffer: GPU-accessible memory for vertices, indices, uniforms
- VkPipeline: Shader programs + GPU state (rasterization, depth testing, etc.)
- VkCommandBuffer: Recording draw and compute commands
- VkSwapchain: Queue of images for displaying to screen
See VkSwapchainKHR specs and Graphics Pipelines for technical details.
Let's start with your first KDGpu application entrypoint!
Initialization¶
Let's take a look at the initialization process that was necessary to get to the rendering stage. Starting at the top of initializeScene:
Vertex Data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
Filename: hello_triangle/hello_triangle.cpp
Here, we are creating geometry by manually writing out vertices.
- We create a buffer and then set it to be the
destinationBufferin the upload options. - Then we call KDGpuExample::ExampleEngineLayer::uploadBufferData, which will construct a new KDGpu::UploadStagingBuffer in the engine's list of staging buffers for later reference. 2.1. Creating a fence to mark when this buffer's lifetime ends is handled by KDGpu.
- Next, we need to initialize vertex indices. These will tell the GPU what order each of the vertices should be rendered in. In our case, with only one triangle, we just want to render each vertex in the order that they appear (this would not be the case if we had geometry which shared vertices).
Specifying vertices and indexing them correctly to form a model with a bunch of correctly oriented triangles is a complex task usually handled by loading in model files.
Index Data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Filename: hello_triangle/hello_triangle.cpp
Once again, we use the graphics device to create a new buffer, set it as the destination, and then call uploadBuffer. Note the different configuration bits.
Transformation Matrix as Uniform Buffer Object:
Now the transformation matrix:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Filename: hello_triangle/hello_triangle.cpp
Here we are creating a uniform buffer that will hold this transformation matrix. You can find this uniform being picked up in the vertex shader code for this example. Notice how we decided to use mappable memory for this buffer. This allows us to write to it directly from the CPU without needing to create a staging buffer and perform a copy operation. This is ideal for data that changes every frame, such as our transformation matrix.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Filename: hello_triangle/hello_triangle.cpp
Loading the shaders:
1 2 3 4 5 | |
Filename: hello_triangle/hello_triangle.cpp
A single call to the graphics device. Also check out that KDGpu::AssetPath.
The Graphics Pipeline:
The next step is to initialize the render pipeline object. Before we can do that, though, we need to describe its memory layout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Filename: hello_triangle/hello_triangle.cpp
This is pretty much boilerplate. It would be more interesting if we had a more complex array of uniforms and buffers. In our case, we are describing the transformation of a single triangle, so we just initialize a bindgroup layout containing one uniform buffer and a pipeline layout containing the bindgroup layout.
Next, we create the render pipeline object, passing in our shaders and configuring the format to match its constituent elements. The vertex buffer layout is described to match the vertex array we wrote earlier, the render target format is the same as the swapchain (created under the hood by KDGpuExample) and the depth and stencil buffers are configured to match the depth texture format.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Filename: hello_triangle/hello_triangle.cpp
After creating the pipeline we use the bind group layout to construct the actual bind group as well, attaching the transform buffer we made earlier.
1 2 3 4 5 6 7 8 9 10 | |
Filename: hello_triangle/hello_triangle.cpp
Rendering¶
Finally, we can look at the rendering code. This is where we record the commands that will be sent to the GPU each frame.
- We begin by creating a command recorder object which is then used to begin a render pass that will direct the rendering to the current swapchain image.
- Beginning a render pass returns a KDGpu::RenderPassCommandRecorder, which has many functions used to issue commands related to rendering. We use the pass recorder to bind a KDGpu::GraphicsPipeline to set the majority of the GPU state, followed by binding the vertex and index buffers that hold the geometry data. With all of the state set, we can issue a
drawIndexedcall to draw the 3 vertices of the triangle. - Once all of our drawing commands are recorded, we call the
renderImGuiOverlayhelper function to draw the UI. - To complete the frame, we have to end the renderpass and then tell the command recorder to finish. This returns a CommandBuffer that we then submit to a GPU Queue for execution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
Filename: hello_triangle/hello_triangle.cpp
See Also¶
- Hello Triangle MSAA - Rendering a triangle with multisample anti-aliasing (MSAA)
Updated on 2026-03-31 at 00:02:07 +0000