In my previous blog post yesterday, I shared my thoughts on graphics APIs and libraries. Another problem that brought me to these thoughts is a question: How do you design an API for a library that implements a single algorithm, pass, or graphics effect, using Vulkan or DX12? It may seem trivial at first, like a task that just needs to be designed and implemented, but if you think about it more, it turns out to be a difficult issue. They are few software libraries like this in existence. I don’t mean here a complex library/framework/engine that “horizontally” wraps the entire graphics API and takes it to a higher level, like V-EZ, Nvidia Factor, or Google Filament. I mean just a small, “vertical”, plug-in library doing one thing, e.g. implementing ambient occlusion effect, efficient texture mipmap down-sampling, rendering UI, or simulating particle physics on the GPU. Such library needs to interact efficiently with the rest of the user’s code to be part of a large program or game. Vulkan Memory Allocator is also not a good example of this, because it only manages memory, implements no render passes, involves no shaders, and it interacts with a command buffer only in its part related to memory defragmentation.
I met this problem at my work. Later I also discussed it in details with my colleague. There are multiple questions to consider:
- Who allocates the memory for buffers and textures/images? There are two kinds of those – input/output ones that interact with the user’s code and internal ones, used for intermediate results. Should the user create them? If so, we must inform him about the requirements of those resources, like size and minimum required set of
VK_IMAGE_USAGE_
flags. If the library should allocate them internally, how should it do it? By just grabbing new pieces ofVkDeviceMemory
blocks? What if the user prefers all GPU memory to be allocated using a complex allocator of his choice, like Vulkan Memory Allocator? - Who allocates necessary descriptors? Like with memory resources, the library could just create its own internal
VkDescriptorPool
, but what if the user prefers all the descriptors to be allocated from his own descriptor pool? - Who does the barriers before and after render pass provided by the library, for all the resources involved? Should it be the user, or the library? Barriers are an obvious contact point between the user’s and library code, so there must be a way to issue them considering both source and destination image layout, pipeline stage, and access flags.
- How to provide SPIR-V code of the shaders? Should the library load it from files internally, or is it the user’s responsibility to load them from whatever source he wants and pass pointers to their memory on library initialization? Maybe we could somehow bundle them into the library source, e.g. as large constant byte arrays defined in an auto-generated C++ code?
- What if the user doesn’t want to link to Vulkan functions statically, but rather fetches pointers to its functions using
vkGetDeviceProcAddr
, possibly with help of something like volk? The library should be able to use those. - What if the user wants to pass custom CPU allocation callbacks (
VkAllocationCallbacks
) to all Vulkan functions? The library should be able to do it.
This is a problem similar to what we have with any C++ libraries. There is no consensus about the implementation of various basic facilities, like strings, containers, asserts, mutexes etc., so every major framework or game engine implements its own. Even something so simple as min/max function is defined is multiple places. It is defined once in <algorithm>
header, but some developers don’t use STL. <Windows.h>
provides its own, but these are defined as macros, so they break any other, unless you #define NOMINMAX
before the include… A typical C++ nightmare. Smaller libraries are better just configurable or define their own everything, like the Vulkan Memory Allocator having its own assert, vector (can be switched to standard STL one), and 3 versions of read-write mutex.
All these issues make it easier for developers to just write a paper, describe their algorithm, possibly share a piece of code, pseudo-code or a shader, rather than provide ready to use library. This is a very bad situation. I hope that over time patterns emerge of how the API of a library implementing a single pass or effect using Vulkan/DX12 should look like. Recently my colleague shared an idea with me that if there was some higher-level API that would implement all these interactions between various parts (like resource allocation, image barriers) and we all commonly agreed on using it, then authoring libraries and stitching them together on top of it would be way easier. That’s another argument for the need of such new, higher-level graphics API.
Top comments (1)
Ah the joys of dependencies and plumbing things together!
I would work on decoupling your core feature(s) that provide actual value from the data flow plumbing and management needs (like memory) - possibly via dependency injection techniques (if you have a language that supports it), or maybe simple callbacks.
If there are standard types in use (and in Vulkan there probably are) you can sensibly expect to use those for data structuring, otherwise you will be inventing your own.
If you want to provide sample implementations (perhaps in a demo app) of the library dependencies, then your consumers have a choice to plumb in their own components or use yours.