Rendering Terrain Part 28 – Refactoring Redux

I haven’t posted in over a week. I feel pretty guilty about that, actually. My sleep schedule has been pretty erratic of late and my birthday was last week, so I just haven’t felt like doing any work. Combine that with the fact that the work I need to do is the rather tedious work of rewriting my code, and I really did no work at all for a week. So I’ve only actually been working on the rewrite for about four days now. And not full days either. More like four hours a day. That being said, I’ve made a decent amount of progress. Working at about the same pace I have been, I think I should be done by the time my next post is due1Which probably means I’ll miss that deadline..

Today, I thought it might be interesting to try and look at the evolution of the class structure. I want to try and review what the rough structure looked like, what my thought process was for the previous iterations, and what I’m going for this time. Then next post we can look at some code and talk about where I maybe need to think further or just haven’t taken things into account.

The Past

The original structure for the project was pretty naive. I was looking primarily at one file, one class tutorials that eschewed a more modular, reusable approach for simplicity. I was also interested in trying to use RAII as much as possible. What I came up with looked something like this:
Essentially, the main application instantiates a Window object, which then handles all initialization for the win32 application and window. Then main instantiates a Graphics object which handles DirectX initialization. Then the Scene itself can be instantiated and a pointer to the Graphics object is passed to it so it can make graphics requests but isn’t actually the owner of the Graphics object. The Scene then populates the world, in this case with just a Camera and a Terrain. I actually forgot to add to the Terrain notes that it also handles creation of the mesh, so there’s yet another thing for it to do.
As you can see from the diagram, both the Scene and the Terrain need to talk to the Graphics object. I talked a bunch about why this structure is a problem in my first refactoring post, so I won’t dive into it again. The gist of it is that I didn’t want objects in the world to care about talking to the device. I wanted an intermediary to handle that stuff.

The Present

This brings us to the current version of the code. As the project continued on, this also added in more objects. We added a DayNightCycle object, which contains a DirectionalLight object. We’ve added texturing, bump mapping, displacement mapping, and shadow mapping to the application. As the next diagram demonstrates a bit, most of this gets handles by the Scene now.
My problem now is that I feel like the Scene is doing too much. It needs to initialize multiple pipelines, including compiling the shaders, plus create all the buffer and texture resources, even though most of the data for those buffers and textures resides in other objects.
The Terrain object still loads all of the textures needed for the pipeline, which makes some sense since that sort of defines the terrain and the materials used in texturing it, but seems like it would be a problem in a larger project where having objects loading and unloading data all willy-nilly would be a bad thing.
I’ve also learned a lot more about DirectX 12 and looked more closely at Microsoft’s sample code and how it is structured. The original idea that the Graphics object should control the back buffers and depth/stencil buffer for the main render target doesn’t really make a ton of sense.

The Future

Ultimately, what I’d prefer to see is an object that wraps the display adapter and handles requests for things like resource creation and pipeline creation, but otherwise does very little.
Let’s also create a ResourceManager object that can sit between the Device and the other objects and handles everything having to do with resources. This puts all of our descriptor heaps, buffers, and textures in one place. It can also handle CPU-based memory as well, so when objects want to load textures or height maps, they talk to the ResourceManager, which then talks to the Device where necessary.
It makes a ton of sense to follow Microsoft’s example of creating an object to represent a Frame. Every frame, we need to use a different back buffer, a different set of constant buffers, and a different shadow atlas. The Frame object won’t directly handle the resources. It will talk to the ResourceManager for that, as stated above, but it will hold handles to those resources and will handle attaching them to the pipeline.
I’m going to give resource initialization for the terrain back to the Terrain object, but now it will do that through the ResourceManager. I also want to take all of the files used and make them parameters passed in to the constructor for the Terrain. The reason for this is that the Terrain object isn’t at all generic. If I instantiated two of them, they’d be identical and I have no way of loading two different terrains without making another class. Since it is the files that define the terrain, making them parameters means I could instantiate two terrains with different height maps, displacement maps, bump maps, and textures. Come to think of it, what would make the most sense would be to create a Material class to contain much of this information and then just pass the Terrain object a Material object.
So what will the Scene look like? It will get one or more command lists from the Device. It will instantiate an array of Frames equal to the number we want, three in our case. It will instantiate the Terrain and a Material for the Terrain. It will instantiate the Camera and DayNightCycle. It will also still handle initializing the pipelines. It won’t handle any resource acquisition at all as that has all been moved to other objects. When rendering, it will ask the current Frame to attach the frame related resources to the pipeline and then it will ask the Terrain to attach the terrain related resources. It will ask the Terrain to draw itself, more or less the same as now. And finally, it will ask the Device to execute the constructed command list.
Minus the objects that I don’t expect to change, the final structure should look something like this:
It looks and feels a lot more complicated. Still, it feels like the right structure to be pursuing based on my current knowledge level. I should be able to take this and move to other projects with a lot more ease than the past or present versions. As I learn more, I’ll likely need to change the structure again. But we’ll continue with this and hopefully I’ll have it done for the next post and then we can conclude this project and move on to the next.

No code changes to go along with this post, but the latest code is still available on GitHub.