Bindless DirectX12 engine

Period: 12 Feb - 5 Apr 

Speed: 50-100%

Language: C++

Engine: Shipyard


Why?


At TGA, we as a part of our education get to make our own game engine (Dx11) from a barebones template given to us from the school. This template includes a Render Hardware Interface (RHI) to ease the students into dealing with the GPU.

I had gotten really fond of my little engine and wanted to experiment a bit with newer (to me) features like raytracing and mesh shaders, but alas, i was out of luck in terms of technical requisite as Dx11 does not offer support. I decided that i wanted to upgrade my engine, which I've named Shipyard, to DirectX 12 to have at least the possibility of testing out these features.


While it would have been fun to focus my specialisation on any of those features, I had never written a RHI before. So I limited my scope to the rendering part of the engine, mostly because I did not want to base it on any pre-existing templates, which I realistically would have had to do in that case. So a RHI + integrating it with the engine would have to do. Now to add a bit of flair as I will already be rewriting all the rendering code from Dx11 -> Dx12 I thought i might
as well make the structure "bindless", or atleast try to.


Does this sound rather ignorant of how hard it is to make a engine in DirectX12? Yes.

Was it painful? Also yes.


Research & includes

To start out I began with researching different Dx12 engines and how they set up their structure. What I discovered was that almost all of them wrapped absolutely everything in the DirectX API. I noted that if I wanted this done right, there would have to be a lot of code in place before I could even hope to run the project, which wasn't a problem per say, but would cause problems to appear later into development.


I had prepared a bit beforehand and had already removed all rendering code in such a way that the engine still ran but obviously did not render anything. I wanted to do this so I could see structural problems that could occur faster than if I had implemented my interface in a separate project.


Adding the Dx12 API to the solution was quite easy and I also added the DirectXTK helpers.


Engines I looked through:

- Adira12

The engine that inspired me to try this myself.


- MiniEngine(DirectX-GraphicsSample)

Fantastic resource and well up to date reference engine for many online discussions out there.


- LearningDirectX12

The repository for a tutorial that was immensely helpful for me, and I kept looking back here when I got stuck.


"Hello world" stage

The "hello world" stage took a lot longer than expected mostly because I was partly inspired to encapsulate a lot of code, code I maybe didn't understand just yet. This was at about week 3 of 8.


Moving on from this stage however was somewhat easy. I had out figured how to push all the data I needed for rendering except the textures, a somewhat crucial part that for now had to wait.


From this point I moved on to creating the GBuffer and PSO for all the different passes I wanted to have. I had previous shader code from Dx11 that i could use here.


Understanding the PSO, resource states and transitioning resources took me a long time to really figure out to any greater extent than "This works, I dont know why", but eventually I managed to get to "This works, and I think I might know why".

Around this time I saw the first warning signals that threaded loading would be a bit painful, as it required my command lists to work concurrently. Something that, while in scope of any Dx12 engine/renderer, I really had not planned for. Clearly my mistake.


Gbuffer

Setting up the gbuffer was quite hard. Primarily I had problems when I dealt with resources that I would have to transition its state from render target to shader resource in a nice fashion. While transitionbarriers, RTVs and SRVs are individually well documented, I found it hard to discover how the optimal system behaves and how a normal RHI would handle this, and while usable I still feel this way about my current system. Clearly, redesigning and refactoring is needed.




Texture heap and finishing up.

Figuring out textures completely and importing was one of the trickier parts of the project. I ended up using the DirectXTK class ResourceUploadBatch to handle uploads and then of course using DirectTex to read the textures. Using those libraries I can now load just about any texture that I would ever need.

Now utilizing this to render textured meshes needs me to deliver these textures to the shader in some way. My solution is to utilize the DirectXTK:s DescriptorPiles. I use one dedicated to RTVs, DSVs or one special that holds SRVs and UAVs. The last heap will be bound to T0, and I will push the index where I know that the textures are within the heap and the shader will now be able to read this data.


The only binding I'm doing per drawcall now is the vertex/index buffers and the material buffer.


The vertex and index buffer calls could be removed by pushing that data to a heap and then binding that to a ByteAddressBuffer and using SV_VertexID to navigate the buffer. But as time was running out and the engine still wasn't looking that great visually, I decided against that and started quickly implementing the light/shadows/tonemapping passes so that I could have a nicer looking result even though the bindings didnt work the way I wanted them to.