Vulkan Tutorial in C - 001 - Initial Setup

You can find the full source code here.
This blog series documents my experience building a simple Vulkan application from scratch. My goal was to create the most straightforward Vulkan app I could while still making it somewhat useful. Along the way, I’ll share the challenges I faced, the lessons I learned, and the code I wrote.

Whether you’re new to Vulkan or just curious about graphics programming, I hope this series will be a helpful resource. Let’s dive in!

Why Use C?

I chose to write this tutorial in C (rather than C++) for several reasons:
  1. I prefer C over C++.
  2. There are already many Vulkan tutorials available in C++.
  3. C is a straightforward language, making it ideal for beginners (and tutorials).
  4. I personally prefer to avoid concepts like OOP, Clean Code, RAII, templates, and similar features commonly found in C++.
Even if you prefer C++ and its features, this tutorial will still be useful. C is straightforward and makes everything explicit, giving you full control over your code. I also made an effort to make most of the code compile in C++ with minimal modifications. The best part is that you won’t be tied to my architectural decisions, like class names or methods. You’re free to create your own structure, which means you can easily adapt the concepts to C++ if that’s your preference. In fact, you’ll likely find it easier to transform my C code into C++ than to follow someone else’s pre-defined architecture.

Windows but No Libraries

I chose to work directly with the Win32 API for a few reasons. First, it’s what I’m familiar with from my own projects, so it’s natural to stick with it. Also, since we’re using C, we’d typically need something like SDL, which requires extra setup. For a simple example like this, I don’t think that’s necessary.

As you’ll see in the next posts, we’ll hardly need more than 100 lines of Win32 API code to get started. Later, when we tackle keyboard and mouse input, we’ll add a few extra lines, but it’ll still be minimal. This approach does mean the code will be Windows-specific, but I don’t see that as a major issue—Windows is widely used. Even if you’re on a different OS, I believe you shouldn’t have much trouble adapting the code to your needs.

Installing the Vulkan SDK

Before we can start building our Vulkan application, you’ll need to download and install the Vulkan SDK. Follow these steps to get set up:
  1. Download the Vulkan SDK: Go to the official Vulkan website and download the latest version of the Vulkan SDK for your operating system. Choose the Windows version.
  2. Install the Vulkan SDK: Run the installer and follow the instructions. The installer will place the Vulkan SDK in a default directory, typically something like C:\VulkanSDK\1.x.x.x. You can also choose a custom directory during the installation process.
Once the Vulkan SDK is installed, you’re ready to move on to the next step.

Note on Microsoft Visual Studio

You’ll also need to have Microsoft Visual Studio installed with the Visual C++ tools, as well as the command-line compiler (CL). This is necessary to compile the code using the provided build.bat script.

If you don’t have Visual Studio installed, you can download it from the official Visual Studio website. The free Community Edition should work perfectly for this project.

Once Visual Studio is set up and you have the Vulkan SDK installed, you’re ready to move on to the next step.

Compiling the Vulkan Application

Once you've set up the Vulkan SDK, you can compile the app using a batch script. Name this script build.bat, and place it in the same directory as your main.c file:
@echo off

REM Vulkan paths
set vki=-I"C:\VulkanSDK\1.x.x.x\Include"
set vkl=-LIBPATH:"C:\VulkanSDK\1.x.x.x\Lib"

REM Compiler flags
set cf=-nologo -FC -Z7 -W4 -WX -wd4189 -wd4100 -wd4101

IF NOT EXIST bin mkdir bin
pushd bin
cl %cf% ..\main.c %vki% -link %vkl% user32.lib vulkan-1.lib
popd
This script does the following:
  • Sets up the include and library paths for Vulkan.
  • Defines some compiler flags for warnings and debug symbols.
  • Creates a bin folder if it doesn’t exist.
  • Compiles main.c, linking it with user32.lib and vulkan-1.lib.
Make sure to adjust the Vulkan SDK path (C:\VulkanSDK\1.x.x.x) to the specific location and version you've installed!
To build the project, open the x64 Native Tools Command Prompt for VS 20xx, navigate to the directory where you've saved the build.bat and main.c files, then type build and press enter to compile the project.

Note: This won't work just yet because we haven’t written the minimum steps required in main.c, but once that's done, this is how you'll compile the app.

The app structure

Here's an overview of the app's structure:
// Includes and helpful utilities
// VulkanContext struct
// File loading utility
// globalRunning and WindowProc
// Vulkan Validation layer's Debug Callback
// Vulkan Initialization Function
// Create shader module function
// WinMain application entry point

Setting Up Vulkan Includes

Now that we have the basic setup out of the way, we can include the necessary headers in main.c:
#include <windows.h> // Must be included before vulkan_win32.h
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_win32.h>
It turns out windows.h must be included before vulkan_win32.h, but you don’t need it before vulkan.h.

Even though windows.h isn’t strictly needed before vulkan.h, I prefer to include it first to keep things neat and consistent.
Do I need VK_USE_PLATFORM_WIN32_KHR?
Some Vulkan tutorials and documentation mention defining VK_USE_PLATFORM_WIN32_KHR before including vulkan_win32.h.

However, after testing my app—including the use of VkWin32SurfaceCreateInfoKHR—I found that everything works fine without it.
This suggests that modern Vulkan SDKs no longer require this macro explicitly.

If your code compiles and runs without issues, you can safely skip this define.

Useful Includes and Type Definitions

For better readability and ease of use, I include a few extra headers and define some custom types:
#include <assert.h>  // For assertions
#include <stdbool.h> // For booleans
#include <stdint.h>  // For fixed-width integers
#include <stdio.h>   // For printing

// Custom integer types
typedef uint8_t u8;
typedef uint32_t u32;
typedef int32_t s32;

// Custom float type
typedef float f32;

// Helper macro to get the count of elements in an array
#define array_count(array) (sizeof(array) / sizeof((array)[0]))
  • u8, u32 and s32: I define these types to make the code more readable when dealing with fixed-width integers.
  • f32: Again, just a short version of float type to reduce typing.
  • array_count: This macro calculates the number of elements in an array, which avoids hardcoding the size.
These are personal preferences, and you’re not required to follow them. Feel free to adjust the code as needed to suit your style.

I Won’t Use 'Frames in Flight'

The concept of "frames in flight" isn’t necessary for beginners learning Vulkan. It complicates setup, and for applications where each frame fits within the frame budget (like 16.6 ms for 60 FPS), it’s counterproductive.

Without frames in flight, the CPU and GPU can handle the work within the frame budget without parallel processing. Adding frames in flight introduces input lag, making the controls feel less responsive. While explaining why is complex, I’ll skip it for now.

In this tutorial, we’ll keep it simple: the CPU prepares the next frame while the GPU executes the last one. No extra frames in flight.

That said, frames in flight can be useful in cases where the GPU needs the full frame budget. But in such cases, input lag becomes inevitable. If you must choose between input lag and frequent frame drops, input lag is the lesser evil. Personally, I’d recommend optimizing to avoid this, but that’s up to you.

Conclusion

That wraps up the basic setup for our Vulkan application! With the Vulkan SDK in place, the necessary headers included, and the build process configured, we're ready to move on to the next step in our journey.

In the next post, we'll continue building on this foundation, where we'll dive into more code, starting with global variables, the VulkanContext struct, and whatever else comes next on our path to a fully functional Vulkan application.

Next

Comments

Popular Posts