Vulkan Tutorial in C - 003 - Initialization Part 1
Setting Up Debugging
Let's define a debug callback to detect issues or warnings in our Vulkan code:
static VKAPI_ATTR VkBool32 VKAPI_CALL
vulkan_debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT *callbackData,
void *userData)
{
char buffer[4096] = {0};
sprintf_s(buffer, sizeof(buffer), "Vulkan Validation layer: %s\n",
callbackData->pMessage);
OutputDebugString(buffer);
return VK_FALSE;
}
That's just a callback that will be called whenever Vulkan's validation layer finds something wrong with our code.
vulkan_debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT *callbackData,
void *userData)
{
char buffer[4096] = {0};
sprintf_s(buffer, sizeof(buffer), "Vulkan Validation layer: %s\n",
callbackData->pMessage);
OutputDebugString(buffer);
return VK_FALSE;
}
Windows - The win32_init_vulkan function
Here's an overview of the initialization function (I'll change this later on when I port the code to linux):
VulkanContext
win32_init_vulkan(HINSTANCE instance, s32 windowX, s32 windowY, u32 windowWidth,
u32 windowHeight, char *windowTitle)
{
VulkanContext vk = {0};
// Create window
// Set up enabled layers and extensions
// Create Vulkan Instance
// Set up debug callback
// Create surface
// Pick a physical device and the graphicsAndPresent queue family
// Create logical device
// Get graphicsAndPresentQueue from device
// Create swapchain
// Get swapchain images and create their views
return vk;
}
In this post, we'll cover the first five steps of this function. The remaining five steps will be covered in the next post.
win32_init_vulkan(HINSTANCE instance, s32 windowX, s32 windowY, u32 windowWidth,
u32 windowHeight, char *windowTitle)
{
VulkanContext vk = {0};
// Create window
// Set up enabled layers and extensions
// Create Vulkan Instance
// Set up debug callback
// Create surface
// Pick a physical device and the graphicsAndPresent queue family
// Create logical device
// Get graphicsAndPresentQueue from device
// Create swapchain
// Get swapchain images and create their views
return vk;
}
Windows - Create window
On Windows, I'll use the Win32 API to create a window. The following code goes inside win32_init_vulkan:
// Register window class
WNDCLASSEX winClass =
{
sizeof(WNDCLASSEX),
0, // style
vulkan_window_proc, // window procedure
0, // cbClsExtra
0, // cbWndExtra
instance, // hInstance
NULL, // hIcon
NULL, // hCursor
NULL, // hbrBackground
NULL, // lpszMenuName
"MyUniqueVulkanWindowClassName",
NULL, // hIconSm
};
if (!RegisterClassEx(&winClass))
{
assert(!"Failed to register window class");
}
// Make sure the window is not resizable for simplicity
DWORD windowStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
RECT windowRect =
{
windowX, // left
windowY, // top
windowX + windowWidth, // right
windowY + windowHeight, // bottom
};
AdjustWindowRect(&windowRect, windowStyle, 0);
windowWidth = windowRect.right - windowRect.left;
windowHeight = windowRect.bottom - windowRect.top;
windowX = windowRect.left;
windowY = windowRect.top;
// Create window
vk.window = CreateWindowEx(0, // Extended style
winClass.lpszClassName,
windowTitle,
windowStyle,
windowX, windowY, windowWidth, windowHeight,
NULL, NULL, instance, NULL);
if (!vk.window)
{
assert(!"Failed to create window");
}
ShowWindow(vk.window, SW_SHOW);
That's pretty standard window creation code. You can look up that elsewhere for explanations if you're not familiar with it. Or just ignore it all--it's just creating a window for us.
WNDCLASSEX winClass =
{
sizeof(WNDCLASSEX),
0, // style
vulkan_window_proc, // window procedure
0, // cbClsExtra
0, // cbWndExtra
instance, // hInstance
NULL, // hIcon
NULL, // hCursor
NULL, // hbrBackground
NULL, // lpszMenuName
"MyUniqueVulkanWindowClassName",
NULL, // hIconSm
};
if (!RegisterClassEx(&winClass))
{
assert(!"Failed to register window class");
}
// Make sure the window is not resizable for simplicity
DWORD windowStyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
RECT windowRect =
{
windowX, // left
windowY, // top
windowX + windowWidth, // right
windowY + windowHeight, // bottom
};
AdjustWindowRect(&windowRect, windowStyle, 0);
windowWidth = windowRect.right - windowRect.left;
windowHeight = windowRect.bottom - windowRect.top;
windowX = windowRect.left;
windowY = windowRect.top;
// Create window
vk.window = CreateWindowEx(0, // Extended style
winClass.lpszClassName,
windowTitle,
windowStyle,
windowX, windowY, windowWidth, windowHeight,
NULL, NULL, instance, NULL);
if (!vk.window)
{
assert(!"Failed to create window");
}
ShowWindow(vk.window, SW_SHOW);
Set up enabled layers and extensions
// Query available instance layers
u32 propertyCount = 0;
vkEnumerateInstanceLayerProperties(&propertyCount, NULL);
assert(propertyCount <= 32); // Ensure we don't exceed our fixed-size array
VkLayerProperties layerProperties[32];
vkEnumerateInstanceLayerProperties(&propertyCount, layerProperties);
char *validationLayerName = "VK_LAYER_KHRONOS_validation";
// Check if the requested validation layer is available
bool validationLayerFound = false;
for (u32 i = 0; i < propertyCount; i++)
{
if (strcmp(validationLayerName, layerProperties[i].layerName) == 0)
{
validationLayerFound = true;
break;
}
}
assert(validationLayerFound && "Validation layer not found!");
char *enabledLayers[] = { validationLayerName };
char *extensions[] =
{
// These defines are used instead of raw strings for future compatibility
VK_KHR_SURFACE_EXTENSION_NAME, // "VK_KHR_surface"
VK_KHR_WIN32_SURFACE_EXTENSION_NAME, // "VK_KHR_win32_surface"
VK_EXT_DEBUG_UTILS_EXTENSION_NAME // "VK_EXT_debug_utils"
};
u32 propertyCount = 0;
vkEnumerateInstanceLayerProperties(&propertyCount, NULL);
assert(propertyCount <= 32); // Ensure we don't exceed our fixed-size array
VkLayerProperties layerProperties[32];
vkEnumerateInstanceLayerProperties(&propertyCount, layerProperties);
char *validationLayerName = "VK_LAYER_KHRONOS_validation";
// Check if the requested validation layer is available
bool validationLayerFound = false;
for (u32 i = 0; i < propertyCount; i++)
{
if (strcmp(validationLayerName, layerProperties[i].layerName) == 0)
{
validationLayerFound = true;
break;
}
}
assert(validationLayerFound && "Validation layer not found!");
char *enabledLayers[] = { validationLayerName };
char *extensions[] =
{
// These defines are used instead of raw strings for future compatibility
VK_KHR_SURFACE_EXTENSION_NAME, // "VK_KHR_surface"
VK_KHR_WIN32_SURFACE_EXTENSION_NAME, // "VK_KHR_win32_surface"
VK_EXT_DEBUG_UTILS_EXTENSION_NAME // "VK_EXT_debug_utils"
};
Vulkan Approach
Almost all (if not all) Vulkan functions take a struct that configures how the function behaves (God help us!). These structs serve as a way to pass parameters to functions. Some of the fields may themselves be structs with even more fields... In short, Vulkan is extremely verbose and usually requires (potentially a lot of) setup before calling functions.The structs typically start with two specific fields: sType and pNext. For example, a struct might look like this:
VkSomeVulkanStruct
{
VkStructureType sType;
const void *pNext;
// other fields
};
{
VkStructureType sType;
const void *pNext;
// other fields
};
There's often also a flags field after pNext, which we will be ignoring most of the time.
I've added comments to most fields of structs, but I omitted comments for these 3 fields most of the time to avoid redundancy.
Here's an explanation of sType and pNext: https://docs.vulkan.org/guide/latest/pnext_and_stype.html
Create Vulkan Instance
/* This struct is technically optional, but it's worth adding for a nicer
display when inspecting the app with tools like RenderDoc. */
VkApplicationInfo appInfo =
{
VK_STRUCTURE_TYPE_APPLICATION_INFO,
NULL,
"My Clever App Name",
1, // application Version
"My Even Cleverer Engine Name",
1, // engine Version
VK_API_VERSION_1_3
};
/* This struct is necessary. The main purpose of this is to inform the
Vulkan driver about which layers and extensions to load when calling
vkCreateInstance. */
VkInstanceCreateInfo createInfo =
{
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
NULL,
0, // flags (this is the only time I'm commenting on this)
&appInfo,
array_count(enabledLayers), // layer count
enabledLayers, // layers to enable
array_count(extensions), // extension count
extensions // extension names
};
if (vkCreateInstance(&createInfo, NULL,
&vk.instance) != VK_SUCCESS)
{
assert(!"Failed to create vulkan instance");
}
display when inspecting the app with tools like RenderDoc. */
VkApplicationInfo appInfo =
{
VK_STRUCTURE_TYPE_APPLICATION_INFO,
NULL,
"My Clever App Name",
1, // application Version
"My Even Cleverer Engine Name",
1, // engine Version
VK_API_VERSION_1_3
};
/* This struct is necessary. The main purpose of this is to inform the
Vulkan driver about which layers and extensions to load when calling
vkCreateInstance. */
VkInstanceCreateInfo createInfo =
{
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
NULL,
0, // flags (this is the only time I'm commenting on this)
&appInfo,
array_count(enabledLayers), // layer count
enabledLayers, // layers to enable
array_count(extensions), // extension count
extensions // extension names
};
if (vkCreateInstance(&createInfo, NULL,
&vk.instance) != VK_SUCCESS)
{
assert(!"Failed to create vulkan instance");
}
Set up debug callback
VkDebugUtilsMessageSeverityFlagsEXT messageSeverity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
VkDebugUtilsMessageTypeFlagsEXT 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;
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo =
{
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
NULL,
0,
messageSeverity,
messageType,
vulkan_debug_callback,
NULL // user data
};
// Load the debug utils extension function
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT =
(PFN_vkCreateDebugUtilsMessengerEXT)
vkGetInstanceProcAddr(vk.instance, "vkCreateDebugUtilsMessengerEXT");
VkDebugUtilsMessengerEXT debugMessenger;
if (vkCreateDebugUtilsMessengerEXT(vk.instance, &debugCreateInfo, NULL,
&debugMessenger) != VK_SUCCESS)
{
assert(!"Failed to create debug messenger!");
}
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
VkDebugUtilsMessageTypeFlagsEXT 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;
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo =
{
VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
NULL,
0,
messageSeverity,
messageType,
vulkan_debug_callback,
NULL // user data
};
// Load the debug utils extension function
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT =
(PFN_vkCreateDebugUtilsMessengerEXT)
vkGetInstanceProcAddr(vk.instance, "vkCreateDebugUtilsMessengerEXT");
VkDebugUtilsMessengerEXT debugMessenger;
if (vkCreateDebugUtilsMessengerEXT(vk.instance, &debugCreateInfo, NULL,
&debugMessenger) != VK_SUCCESS)
{
assert(!"Failed to create debug messenger!");
}
Create surface
We created the Vulkan instance, and as we saw in the Vulkan docs tutorial, we need to create the surface right after that. On Windows, that involves setting up the VkWin32SurfaceCreateInfoKHR struct:
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo =
{
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
NULL,
0,
instance, // HINSTANCE
vk.window // HWND
};
if (vkCreateWin32SurfaceKHR(vk.instance, &surfaceCreateInfo, NULL,
&vk.surface) != VK_SUCCESS)
{
assert(!"Failed to create surface");
}
{
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
NULL,
0,
instance, // HINSTANCE
vk.window // HWND
};
if (vkCreateWin32SurfaceKHR(vk.instance, &surfaceCreateInfo, NULL,
&vk.surface) != VK_SUCCESS)
{
assert(!"Failed to create surface");
}
It's important not to confuse this Windows HINSTANCE with vk.instance, which is the Vulkan instance.
That concludes part 1 of the initialization. We've completed the first five steps of the win32_init_vulkan function. In the next post, we'll finish the remaining five steps.Next
Comments
Post a Comment