Vulkan Introduction — Modern Graphics API
In this tutorial, you'll learn about Vulkan Introduction. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Vulkan is a low-overhead, cross-platform graphics and compute API that gives developers explicit control over GPU resource management, command submission, and memory allocation for maximum performance.
What You'll Learn & Why It Matters
In this tutorial, you will learn the Vulkan API architecture — how to create a Vulkan instance, select a physical device, set up a logical device and swapchain, create render passes and pipelines, and record command buffers. Vulkan's explicit design lets you achieve near-metal performance.
Real-world use: AAA games, CAD software, and DodaTech's GPU-accelerated antivirus scanning use Vulkan for low-latency graphics and compute workloads.
Prerequisites
- OpenGL Basics tutorial (previous)
- C++ knowledge
- Understanding of the Rendering Pipeline
- Vulkan SDK installed
Learning Path
flowchart LR A[OpenGL Basics] --> B[Vulkan Introduction] B --> C[Shader Programming] B --> D[Compute Shaders] B --> E[GPU Architecture] C --> F[Ray Tracing] D --> G[Post-Processing] B:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
Vulkan vs OpenGL
Vulkan is not just a newer OpenGL. It represents a fundamentally different design philosophy:
| Aspect | OpenGL | Vulkan |
|---|---|---|
| Driver overhead | High | Minimal |
| Error checking | Runtime | Validation layers |
| Memory management | Automatic | Explicit |
| Multi-threading | Limited | First-class |
| Pipeline state | Monolithic | Programmatic |
| Shader format | GLSL source | SPIR-V binary |
The cost of this explicit control is more code. Drawing a triangle in OpenGL takes ~200 lines. In Vulkan, it takes ~2000 lines. The benefit is predictable performance and no driver surprises.
Initializing Vulkan
Every Vulkan application starts by creating an instance, which stores application-wide state.
#include <vulkan/vulkan.h>
#include <iostream>
int main()
{
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Vulkan Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "DodaTech Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_3;
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
VkInstance instance;
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
if (result == VK_SUCCESS)
std::cout << "Vulkan instance created" << std::endl;
else
std::cerr << "Failed to create Vulkan instance" << std::endl;
vkDestroyInstance(instance, nullptr);
return 0;
}
Required Extensions and Validation Layers
Real applications enable validation layers during development for debugging:
const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation"
};
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
debugCreateInfo.messageSeverity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debugCreateInfo.messageType =
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debugCreateInfo.pfnUserCallback = debugCallback;
Device Selection
Vulkan requires explicit GPU selection. You query available physical devices and pick one that supports your needs.
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
for (const auto& device : devices)
{
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(device, &props);
if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
std::cout << "Found discrete GPU: " << props.deviceName << std::endl;
}
flowchart TD A[Vulkan Instance] --> B[Physical Device Selection] B --> C[Logical Device] C --> D[Queue Families] D --> E[Swapchain] E --> F[Render Pass & Pipeline] F --> G[Command Buffers] G --> H[Submit & Present]
Swapchain and Presentation
The swapchain manages the image queue that will be displayed on screen.
VkSwapchainCreateInfoKHR swapchainInfo{};
swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchainInfo.surface = surface;
swapchainInfo.minImageCount = 2; // Double buffering
swapchainInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB;
swapchainInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
swapchainInfo.imageExtent = {800, 600};
swapchainInfo.imageArrayLayers = 1;
swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
VkSwapchainKHR swapchain;
vkCreateSwapchainKHR(device, &swapchainInfo, nullptr, &swapchain);
Render Pass and Pipeline
The render pass describes the framebuffer attachments. The pipeline describes the shaders and fixed-function state.
VkPipelineShaderStageCreateInfo vertStage{};
vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertStage.module = vertModule;
vertStage.pName = "main";
VkPipelineShaderStageCreateInfo fragStage{};
fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragStage.module = fragModule;
fragStage.pName = "main";
Command Buffers
Vulkan requires explicit command recording and submission. You allocate a command buffer, record commands into it, then submit it to a queue.
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
vkEndCommandBuffer(commandBuffer);
Fences and Semaphores
Synchronization in Vulkan is explicit. Fences synchronize CPU with GPU. Semaphores synchronize GPU operations.
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
VkFence fence;
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
vkCreateFence(device, &fenceInfo, nullptr, &fence);
vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence);
vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
Common Errors & Mistakes
1. Missing Validation Layers
Mistake: Not enabling validation layers during development, then getting crashes without error messages.
Fix: Always enable VK_LAYER_KHRONOS_validation during development. Install the Vulkan SDK which includes validation layers.
2. Forgetting to Destroy Resources
Mistake: Creating Vulkan objects without destroying them, causing memory leaks.
Fix: Vulkan does not track resources. Destroy everything you create in reverse order of creation using vkDestroy* functions.
3. Incorrect Queue Family Indices
Mistake: Assuming all queue families are the same, causing presentation or compute errors.
Fix: Query vkGetPhysicalDeviceQueueFamilyProperties and check for VK_QUEUE_GRAPHICS_BIT or VK_QUEUE_COMPUTE_BIT flags.
4. Buffer and Image Transitions
Mistake: Reading from an image before a write operation has completed.
Fix: Use pipeline barriers to transition image layouts explicitly. Every image layout change requires a barrier.
Practice Questions
Question 1
What is the main advantage of Vulkan over OpenGL?
Show answer
Vulkan offers lower driver overhead, explicit GPU control, better multi-threading support, and predictable performance. The driver does almost nothing behind your back.Question 2
What is a VkFence used for?
Show answer
A fence synchronizes CPU and GPU operations. The CPU can wait on a fence to know when GPU work has completed, preventing the CPU from reading data the GPU is still writing.Question 3
Why does Vulkan use SPIR-V instead of GLSL?
Show answer
SPIR-V is a binary intermediate language that compilers can target from multiple source languages (GLSL, HLSL, Slang). This avoids runtime shader compilation and enables offline optimization.Question 4
What is the swapchain and why is it needed?
Show answer
The swapchain manages the queue of images that get presented to the screen. It handles double or triple buffering and synchronizes rendering with the display refresh rate.Challenge
Implement a Vulkan application that renders a rotating colored triangle with proper synchronization, validation layers, and clean resource cleanup. Use at least two framebuffers.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Author: DodaTech | Last updated: June 21, 2026
DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro