Back in 2006 I used DirectX 9.0c with C++ to make toy games and learn graphics programming. Here I’ll share my initial notes in rekindling those old flames by integrating Racket and Vulkan.
Racket is a programming language that allows you to write new languages in terms of itself. This way, you automate the same translations from ideas to code that you have done manually in every other language.
Racket is not well-adopted, which is good news for first-movers. Creative assets and the designers that produce them are starving for expressive power that won’t be interrupted by the limitations of underlying technology. I believe being able to weave music, shape visuals, orchestrate animations, and assemble scenes in languages fitting each asset will have a high ROI.
Now we get to Vulkan, a C API that offers better abstractions for GPUs. It is explicit, verbose, and built on a system of asynchronous queues. Controlling Vulkan is a test of patience. You have to surrender a day or a weekend to draw a triangle even with someone holding your hand. But the flexibility afforded to developers is such that you can write specialized pipelines built for speed.
In my mind, combining Racket and Vulkan means opening the door to “speak” pipelines and scenes. I am starting this integration to give CV researchers, designers, and game programmers room to experiment with production in new ways.
I am seeking project contributors to help verify functionality once the C bindings are complete. If you are interested in following this currently incomplete project, the source code is on Github. Like with many of my other projects, I am giving away the code for free use.
State of the Project
The project is not yet complete, but in progress.
In my first sprint I started generating FFI bindings for Vulkan from
vk.xml, the XML+C hybrid acting as the machine-readable form of the API Registry. I stopped and stepped away to rethink my approach when I learned exactly how
the XML alone was too ambiguous to generate C bindings. At least the API Registry introduction includes
a warning about this.
The linked issue shows that the XML will indicate types, but not in a way that clarifies pointers. For example, the
VkDescripterSetLayoutCreateInfo struct shows a
void* member, but the XML puts all of that information in CDATA except for
void. Meaning that generating C declarations from XML alone would produce incorrect results.
<type category="struct" name="VkDescriptorSetLayoutCreateInfo"> <member values="VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO"><type>VkStructureType</type> <name>sType</name></member> <member>const <type>void</type>* <name>pNext</name></member> <member optional="true"><type>VkDescriptorSetLayoutCreateFlags</type> <name>flags</name></member> <member optional="true"><type>uint32_t</type> <name>bindingCount</name><comment>Number of bindings in the descriptor set layout</comment></member> <member len="bindingCount">const <type>VkDescriptorSetLayoutBinding</type>* <name>pBindings</name><comment>Array of descriptor set layout bindings</comment></member> </type>
So why not parse the C from
vulkan.h? After all, the header is generated from the same XML.
One reason: The XML still has data that the C doesn’t. Note the
optional attribute in the above declaration. If I want to generate type declarations or contracts in Racket, attributes like
optional can only help produce more exact restrictions. So, generating viable and correct bindings from the header would just be a matter of using libraries like dynamic-ffi to target
vulkan.h. But to make helpful annotations and meaningful guards against misuse, the project will need to incorporate both contextual data from XML and concrete specifics from C fragments.
Code generation challenges also have to consider preprocessor directives. Here are lines 136–137 from my copy of the XML.
<type category="define">#define <name>VK_MAKE_VERSION</name>(major, minor, patch) \ (((major) << 22) | ((minor) << 12) | (patch))</type>
This shows that
type does not only incorporate C data types. You interpret
<type> differently depending on the parent element. That and preprocessor directives —when contructed after decoding character entities— need some kind of representation in the Racket output. At least I can bank on C macros like these changing so infrequently that they can be included in generated code from a hand-maintained source.
I will process the XML and C together for completeness. This will incorporate techniques from—or direct use of—Vulkan-Docs’
reg.py, Stephan Houben’s readspec.rkt (for OpenGL), VkTk, and dynamic-ffi.