As promised here is an overview of ‘the Void Engine’.
The Void Engine API does not look very much like the DirectX or OpenGL APIs, but it does share some of the concepts.
The engine design was heavily influenced by my experience with the Playstation 4 and while the current implementation is onto the DirectX 11 API it actually maps better to DirectX 12. Given the design basis of the engine, I also expect that it should be *relatively* straight forward to port to the PS4, though I do have some concerns about the reflection of the PS4 shaders.
The main sections of the API are: the factory; shaders; bindings; resources; and physicals. Every object in the API is a reference counted smart pointer.
Core to the system are shaders. A shader in the Void Engine represents the entire configuration of the GPU required to perform any action on the GPU. There are no separate shader stages and there is no distinction between graphics and compute shaders.
Shaders are created by a shader library object which represents a blob of binary data containing compiled shaders and all their configuration and binding details. The binary shader library data is constructed by a bespoke tool which runs as part of the general build process. The shader library tool uses reflection to validate the shader configuration and to construct a compact representation of the operations required to setup the GPU and to bind resources to the shader. In the run-time, shader libraries are created by the factory object from the binary blob data.
Bindings are created by the shader library, they can be created by name or automatically for an instance of a particular shader. Bindings represent resources referenced by the shader code or the shader configuration. Bindings are typed and named (the name comes from the shader source code), so bindings can have the same name as long as they have different types.
Bindings can be bound to a shader instance and act as the interface for selecting specific resources. Binding types represent the different ways that data can be connected to the GPU, these are: the various resource view types; constant buffers; vertex streams; stream-out streams; and states.
To automate most binding, bindings can also be held in anonymous binding groups which can be bound en-masse to a shader.
For every binding type, there is a resource type. Resources are constructed by the factory and represent the configuration of a resource including which physical resource to select. For instance, a stream resource configures the offset and stride of the stream and references the physical buffer holding the stream data. In addition to holding configuration data, resources simplify the renaming of physical buffers as data is updated.
Resources can be registered by name into a resource registry. This is a mechanism for sharing resources and to simplify the assignment of resources. Resource registries can be searched for specific resources using a combination of name and type, they can also automatically create bindings for a specific shader or can create a binding group for future use.
Physicals represent physical memory buffers (including textures, render targets and depth-stencils), views and states. Physicals are created by the factory object. Dynamic physical objects can be updated directly. Physical objects need to be bound to a resource to be used. The relationship between physicals and resources is complicated as some resources access physical memory indirectly via ‘view’ physicals and different parts of a single buffer may be referenced by multiple resources. I am considering moving some of the view and state handling API from the physicals to become directly part of the resource system, but currently the API operates in a consistent manner for all physical resources. If this change does go ahead, it will be mostly cosmetic as the current run-time data pipeline is very effective.
OK, that is pretty much it. It looks quite complicated when laid out in full, but in practice most cases are handled automatically. A shader process that uses specific resources named in the shader source code will have all its bindings, resources and physicals assigned or created automatically. It is only if a custom resource setup is required that specific bindings, resources or physicals need to be worked with.
In the simplest cases, the only code required is to request a shader instance and execute it.