| assets | ||
| scripts | ||
| shaders | ||
| src | ||
| .gitignore | ||
| CREDITS.md | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| README.md | ||
Odin Vulkan learning
I had started writing about my journey here, but after writing about the third data structure, I found out that there is a lot of work to get something running in Vulkan, and that would make for a very boring write-up. Instead I'll write about a few gotchas, and what is done and available in this repository.
My first objective was to do some calculation on my GPU by using a compute shader. Once this was achieved, I felt like I needed a display to show the results of whatever I was going to calculate. From there, I fell into a rabbit hole, as displaying a simple triangle in Vulkan is much harder than running a compute shader.
Now my objectives are:
- Display a GLTF model
- Do some compute and show the result on screen:
- A simple 2D example like the magnetic pendulum
- A more complex 3D example using isosurfaces algorithms (marching cubes, surface nets)
Running the stuff
To run anything, you'll need:
From there, you can use just to list the recipes and run them. For example, to run the simple graphic example, just do:
just run-graphics
The above will build the shaders, the program, and run it. All commands involving Odin can have parameters that will be passed to Odin.
Gotchas
Creating an instance
The beginning would be obviously to just create an instance, check that it was properly initialized, and destroy it. And that's where I hit my first issue. Whatever I did, any function I used would end up in a segfault. The most shocking is that the following C++ code was working perfectly, but porting it to Odin would crash immediately:
#include <iostream>
#include <vulkan/vulkan.h>
int main(int argv, char* argc[])
{
VkInstance instance;
VkApplicationInfo appInfo = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = NULL,
.pApplicationName = "Step 1",
.applicationVersion = 1,
.pEngineName = NULL,
.engineVersion = 0,
.apiVersion = VK_MAKE_VERSION(1, 2, 0) };
VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = NULL,
.flags = 0 };
createInfo.pApplicationInfo = &appInfo;
createInfo.enabledExtensionCount = 0;
createInfo.ppEnabledExtensionNames = NULL;
createInfo.enabledLayerCount = 0;
createInfo.ppEnabledLayerNames = NULL;
VkResult result = vkCreateInstance(&createInfo, NULL, &instance);
if(result != VK_SUCCESS) std::cout << "Failed to create a Vulkan instance" << result << std::endl;
else std::cout << "Successfully created a Vulkan instance" << std::endl;
return 0;
}
I've built this code with
zig c++ vktest.cpp -lvulkan
Nothing complex, right? It is straightforward and yet, didn't work when made in Odin. After a full day of investigation, and a lot of hair loss, I've found out that Odin (or Vulkan in general?) requires loading the Vulkan procedures. So I was basically calling empty procedures (or more exactly, null pointers).
It is now my understanding (and I hope I'll be corrected) that one should call on a "Vulkan loader" that will give you the procedures you want to call, but I still haven't understood why the C code works out of the box, whereas for Odin I have to use GLFW or SDL2 to get this loader thing. I've seen some references to GLFW in Vulkan samples, but is it really what's happening? Is it using GLFW out of the box when using the C library?
I tried both SDL2 and GLFW. SDL2 required a window but I didn't want any window because I first want to try to do some compute with Vulkan. I ended up using GLFW because it only requires an Init() and it is good to go (minus some obscure code I've found online to load the procedures).
Later addition: I think that the way to do it without using GLFW or SDL is by dynamically loading the Vulkan dynamic library. I might do that one day for the sake of understanding, but for now I'll stay on GLFW as it also give access to the window's surface and can handle events.
Listing the devices, queue families, and others
Vulkan has few functions like vkEnumeratePhysicalDevices and vkGetPhysicalDeviceQueueFamilyProperties that requires to be called twice to be useful. The first time with an empty array so it can return you the size of the array that will include the structs you asked for. The second time with your array allocated so it can fill it up.