Starting Racket-Vulkan Integration
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 const
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
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.