Hello Triangle with Vulkan 1.3 Features

The source for this sample can be found in the Khronos Vulkan samples github repository.

This sample demonstrates how to render a simple triangle using Vulkan 1.3 core features. It modernizes the traditional "Hello Triangle" Vulkan sample by incorporating:

  • Dynamic Rendering

  • Synchronization2

  • Extended Dynamic State

  • Vertex Buffers

Overview

The sample renders a colored triangle to the screen using Vulkan 1.3. It showcases how to:

  • Initialize Vulkan with Vulkan 1.3 features enabled.

  • Use dynamic rendering to simplify the rendering pipeline.

  • Employ the Synchronization2 API for improved synchronization.

  • Utilize extended dynamic states to reduce pipeline complexity.

  • Manage vertex data using vertex buffers instead of hard-coded vertices.

Key Features

1. Dynamic Rendering

What is Dynamic Rendering?

Dynamic Rendering is a feature introduced in Vulkan 1.3 that allows rendering without pre-defined render passes and framebuffers. It simplifies the rendering process by enabling you to specify rendering states directly during command buffer recording.

How It’s Used in the Sample:

  • No Render Passes or Framebuffers: The sample does not create VkRenderPass or VkFramebuffer objects.

  • vkCmdBeginRendering() and vkCmdEndRendering(): These functions are used to begin and end rendering operations dynamically.

  • Pipeline Creation: Uses VkPipelineRenderingCreateInfo during pipeline creation to specify rendering details.

Benefits:

  • Simplifies code by reducing boilerplate associated with render passes and framebuffers.

  • Increases flexibility by allowing rendering to different attachments without recreating render passes.

2. Synchronization2

What is Synchronization2?

Synchronization2 is an improved synchronization API introduced in Vulkan 1.3. It provides more granular control over synchronization primitives and simplifies the synchronization process.

How It’s Used in the Sample:

  • vkCmdPipelineBarrier2(): Replaces the older vkCmdPipelineBarrier() for more detailed synchronization.

  • VkDependencyInfo and VkImageMemoryBarrier2: Used to specify precise memory dependencies and image layout transitions.

Example Usage:

VkImageMemoryBarrier2 image_barrier = {
    .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
    // ... other members ...
};

VkDependencyInfo dependency_info = {
    .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
    .imageMemoryBarrierCount = 1,
    .pImageMemoryBarriers = &image_barrier,
};

vkCmdPipelineBarrier2(cmd, &dependency_info);

Benefits:

  • Provides more expressive and flexible synchronization.

  • Reduces the potential for synchronization errors.

  • Simplifies the specification of pipeline stages and access masks.

3. Extended Dynamic State

What is Extended Dynamic State?

Extended Dynamic State allows more pipeline states to be set dynamically at command buffer recording time rather than during pipeline creation. This reduces the number of pipeline objects needed.

How It’s Used in the Sample:

  • Dynamic States Enabled: The sample enables dynamic states like VK_DYNAMIC_STATE_CULL_MODE, VK_DYNAMIC_STATE_FRONT_FACE, and VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY.

  • Dynamic State Commands: Uses vkCmdSetCullMode(), vkCmdSetFrontFace(), and vkCmdSetPrimitiveTopology() to set these states dynamically.

Example Usage:

vkCmdSetCullMode(cmd, VK_CULL_MODE_NONE);
vkCmdSetFrontFace(cmd, VK_FRONT_FACE_CLOCKWISE);
vkCmdSetPrimitiveTopology(cmd, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);

Benefits:

  • Reduces the need to create multiple pipelines for different state configurations.

  • Enhances flexibility by allowing state changes without pipeline recreation.

4. Vertex Buffers

What Changed?

Unlike the original sample, which used hard-coded vertices in the shader, this sample uses a vertex buffer to store vertex data.

How It’s Used in the Sample:

  • Vertex Structure Defined:

  struct Vertex {
      glm::vec2 position;
      glm::vec3 color;
  };
  • Vertex Data Stored in a Buffer:

  std::vector<Vertex> vertices = {
      {{0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, // Red Vertex
      // ... other vertices ...
  };
  • Buffer Creation and Memory Allocation:

  VkBufferCreateInfo buffer_info = { /* ... */ };
  vkCreateBuffer(device, &buffer_info, nullptr, &vertex_buffer);

  VkMemoryAllocateInfo alloc_info = { /* ... */ };
  vkAllocateMemory(device, &alloc_info, nullptr, &vertex_buffer_memory);
  • Binding the Vertex Buffer:

  vkCmdBindVertexBuffers(cmd, 0, 1, &vertex_buffer, &offset);

Benefits:

  • Flexibility: Easier to modify vertex data without changing shaders.

  • Performance: Potentially better performance due to efficient memory usage.

  • Scalability: Simplifies rendering more complex geometries.

How the Sample Works

  1. Initialization:

    • Instance Creation: Initializes a Vulkan instance with Vulkan 1.3 API version and required extensions.

    • Device Selection: Chooses a physical device that supports Vulkan 1.3 and required features.

    • Logical Device Creation: Creates a logical device with enabled Vulkan 1.3 features like dynamic rendering, synchronization2, and extended dynamic state.

    • Surface and Swapchain Creation: Sets up the window surface and initializes the swapchain for presenting images.

  2. Vertex Buffer Setup:

    • Vertex Data Definition: Defines vertices with positions and colors.

    • Buffer Creation: Creates a buffer to store vertex data.

    • Memory Allocation: Allocates memory for the buffer and maps the vertex data into it.

  3. Pipeline Setup:

    • Shader Modules: Loads and compiles vertex and fragment shaders.

    • Pipeline Layout: Creates a pipeline layout (empty in this case as no descriptors are used).

    • Dynamic States Specification: Specifies which states will be dynamic.

    • Graphics Pipeline Creation: Creates the graphics pipeline with dynamic rendering info and dynamic states enabled.

  4. Rendering Loop:

    • Acquire Swapchain Image: Gets the next available image from the swapchain.

    • Command Buffer Recording:

    • Begin Rendering: Uses vkCmdBeginRendering() with dynamic rendering info.

    • Set Dynamic States: Sets viewport, scissor, cull mode, front face, and primitive topology dynamically.

    • Bind Pipeline and Vertex Buffer: Binds the graphics pipeline and the vertex buffer.

    • Draw Call: Issues a draw call to render the triangle.

    • End Rendering: Uses vkCmdEndRendering() to finish rendering.

    • Image Layout Transition: Transitions the swapchain image layout for presentation using vkCmdPipelineBarrier2().

    • Queue Submission: Submits the command buffer to the graphics queue.

    • Present Image: Presents the rendered image to the screen.

  5. Cleanup:

    • Resource Destruction: Cleans up Vulkan resources like pipelines, buffers, and swapchain images upon application exit.

Dependencies and Requirements

  • Vulkan SDK 1.3 or Later: Ensure you have the Vulkan SDK that supports Vulkan 1.3.

  • Hardware Support: A GPU that supports Vulkan 1.3 features, including dynamic rendering, synchronization2, and extended dynamic state.

  • GLM Library: Used for vector and matrix operations.

  • Shader Compiler: GLSL shaders are compiled at runtime using a GLSL compiler.