Disclaimer: in this post, I´m not going to show any new fancy technique or advanced pattern, I will describe the process I followed to port a rendering framework from dx11 to dx12.
Why?
I started working last June in a company that develops a real-time atmospherics system. We basically provide skies, weather effects and volumetric clouds all in real-time! We have a plugin that can be used in both UE4 and Unity, we basically "inject" our effects into the engine. This means that we are doing native rendering, we don´t render using the interface provided by the engine (the RHI in UE4 or the graphics components in Unity), the consequence is that we have full control of what is going on inside our rendering code but it also means we have to implement all the required rendering functionality. Until this point, we only had a dx11 and PS4 renderer, with that, we could reach most of the market: PC, XBox One and PS4 which are the main platforms of the generation. But we can´t just ignore the new APIs (namely Vulkan and DirectX12), at some point, we have to support them! Another major reason is that UE4 will deprecate DX11 support in favour of DX12, at least in XBox.
The process
When I started working on this task, I didn´t know much about DX12 (neither DX11) I´ve been exclusively using OpenGL for the last 4 years. I had played around with both Vulkan and DX12 (following tutorials and rendering the hello world triangle, and that's it). The first few weeks I studied all the resources online about DX12. Initially, I couldn´t find much, but I´ve made a list with a few essential sources:
- Direct 3D 12 Programming Guide (MSDN): this is the official DX12 doc, initially I thought that it was bad, but with the time, I started to appreciate it. Most of the enums are explained there and function parameters have a description too. You can also find a few tutorials about the resource model, pipelines, barriers etc.
- Microsoft DX12 Youtube Playlist: here you can find a 3 part tutorial related to resource binding, I really recommend watching this! Also, there is a video for dx11 developers porting to dx12 (this is useful even if you don´t know dx11).
- Braynzar Soft tutorial: some of you may know this website as it also has a dx11 series. For me, this was a really important source as it describes every basic aspect giving a lot of details and diagrams.
- Intel´s Direct 3D 12 overview: it covers the core aspects of the API: resource binding, heaps, command lists, bundles etc
- Intel: Migrating to DirectX12: another valuable resource as it helped me understand many elements of the API.
I may be missing something, I´ll update the list in the future.
With all this knowledge, I started working on the port! The first step was to replace dx11 with dx11on12. Dx11on12 is an API that allows you to run dx11 code in dx12, it basically translates all the commands to dx12, this API is not intended to be used in production just as a helper in the transition. I thought that I could keep working most of the functionality while updating small parts but that's not how it works sadly. I´ve been basically working blind for a few weeks. After these first weeks of hard work and a bit of desperation, things started to come together. Within the second month, I had our demoscene working!
Lessons learned
First and most important, you need to organize yourself, when you are confronted with a task of this magnitude is easy to lose track and get frustrated. Dividing tasks into smaller subparts helps a lot. One example, when I had to implement the texture class, I had to write different methods to create the texture as a 2D,3D or array texture, I also had to make the different methods to retrieve the views for the texture (SRV, UAV etc). Initially, I wanted to make everything at once, but there is a lot to write! (at this point the .cpp for the texture class has 2000 lines of code). It helped A LOT doing small parts at a time and do non-related things in between (a task related to another system). This doesn´t mean that each day you can work on 10 different tasks! Sometimes you must commit to something and finish it.
The next lesson, use a graphics debugger!!! Having something to check what the heck is going on the GPU is really useful sometimes. A graphics debugger will show you many things like API calls, a list of resources, information about a selected call etc. There are plenty to choose from. Visual studio has its own, I don´t think it is really good but for most of the time, it was the only one I could use (other debuggers didn´t like the dx11on12 stuff). NVIDIA Nsight, this one has a ton of tools the problem is that it works inside visual studio and I found it too intrusive and it also provides a lot of information (maybe too much). And finally, PIX, I started using it at the end of the task, but I loved it! It is not intrusive and it also gives a lot of useful information. I would recommend PIX for anyone doing graphics-related things. I will mention, that you can´t just trust the debugger and expect that it will give you the answer without some head-scratching. You need to understand what is going on with your software and you also need to know how to use the tool and interpret the info it provides. Once you know it, you will be able to catch 'em all!
Conclusion
Initially, I thought I could never complete this task. But with the time, it was just a mater of finishing small components and in the end it all came together nicely. There is a lot of things I could talk about like how to handle root signatures, resource deallocation, barriers etc. But I think that will wait for another post.
Nacho.