Rendering Terrain Part 1 – Initial Setup

Originally, I had intended to cover both the initial setup and rendering the 2D view of the height map in this post; however, I discovered I actually had a lot more to say about the setup than I thought. So, this post will cover setting up a window and setting up the DirectX instance, and that’s about it.

I primarily followed 2 tutorials when I was getting started on this project:

  1. Braynzar Soft Tutorials
  2. Rastertek Tutorials

Either tutorial should be able to get you up and running with DirectX 12 in Visual Studio. There’s some fiddling with includes and libraries, but I found them both reasonably easy to follow.

Both tutorials focus on creating a Win32 application. There is also a UWP1 Game Project template you can use which would probably actually get things up and running more quickly, but depending on who you listen to, it may not be the way you want to go. As I’m used to Win32, I chose to go that route for this project.

Window Setup

I followed the Rastertek tutorial fairly closely for setting up my window code. One thing that they do that Braynzar Soft doesn’t is actual full screen. Braynzar Soft simply does full screen windowed. That may ultimately prove more desirable, especially as I have a bug that causes my display adapter to hang and reset itself on shutting down the program when in full screen mode.
I actually had the same bug happening in windowed mode as well. That turned out to be the order I had objects being released in my Graphics object’s destructor. Simply correcting the order fixed that. This one may be a bit more work to fix as I think it is related to my use of RAII2.
Let’s talk about that briefly and I’ll come back to my bug in a bit.

If you look at either of the tutorials I used, neither uses RAII. They don’t really take advantage of constructors and destructors. I was really hoping to do so. For example, here is the header for my Window class:

namespace window {
	class Window_Exception : public std::runtime_error {
		Window_Exception(const char *msg) : std::runtime_error(msg) {}

	class Window
		Window(LPCWSTR appName, int height, int width, WNDPROC WndProc, bool isFullscreen);

		HWND GetWindow() { return mWindow; }
		int Height() { return mHeight; }
		int Width() { return mWidth; }

		HINSTANCE mInstance;
		HWND mWindow;
		bool mFullscreen;
		int mHeight;
		int mWidth;
		LPCWSTR mAppName;

All of initialization and setup of the window that we need happens in the constructor, and all of the shutdown happens in the destructor.
In my Main.cpp file, I have a one-liner to create the window:


There is no need for any calls on shutdown as the Window object’s destructor will be called automatically when we leave the scope.

I think the bug I mentioned before occurs because I do the same thing for my Graphics and Scene objects:

Graphics GFX(WIN.Height(), WIN.Width(), WIN.GetWindow(), FULL_SCREEN);
Scene S(WIN.Height(), WIN.Width(), &GFX);

When we shutdown, the objects get destroyed in the exact opposite order they are created. But that means we’re destroying the Graphics object while the window is still in full screen mode. I THINK this might be the problem. If I’m correct, then I need a function I can call on shutdown BEFORE leaving scope so that I can switch back to windowed mode prior to destroying the objects.
I’ll probably do a ‘Bugs and Fixes’ post before I move on from this project to address small issues like this that crop up. For now, I’m just ignoring it.

On to DirectX Setup!

If you’ve played around with earlier versions of DirectX, then you know it can be a pretty verbose API. DirectX 12 is even more verbose. You’re doing just about everything that used to be handled by the drivers before. This lets you optimize your code more, but also means you have to deal with more.
To get up and running, we’ll need to do a number of steps:

  • Find and bind a DirectX 12 compatible display adapter.
  • Create a D3D12 Device.
  • Create the swap chain and however many buffers you plan on using.
  • Bind a render target view for each back buffer.
  • Create a command queue to load draw commands to the device.
  • Create a command allocator for each back buffer as you can’t reset an allocator while a frame is still being rendered.
  • Create a fence for each buffer. A fence is basically a value you set that the GPU will update to let you know when it is done with a frame.
  • Create a command list for each thread you plan to send graphics commands from in a multi-threaded application.

And that’s just to initialize the context. To actually draw stuff, you need to initialize a bunch more, and then run a bunch of commands every frame. We’ll save that for the next post, when we look at rendering a height map in 2D.

Rather than go over everything in that list here, I’ll refer you again to the tutorials I mentioned above. Both go over this step pretty thoroughly, with code. The biggest difference between my code and theirs is that I perform all my init in my constructor and tear down in the destructor, just like with the window code above.
Also, my code throws exceptions that the main program handles, rather than using boolean return values on function calls. This was a necessary change when doing init in the constructor since you can’t actually return anything3.

I mentioned above that I have a Graphics object and a Scene object. The Graphics object contains the Device-related code. The Scene object contains the world. I don’t really have a UI, per say, so that stuff remains in Main.cpp.
All of the initialization that needs to happen before we can set up shaders and load textures, etc, is handled by the Graphics constructor. Once that is done, the Scene object can setup things like the viewport size and camera, as well as initializing all of the objects in the world4.
Unfortunately, the way I currently have things working, the Scene object, and all drawable objects, will need to be aware of the fact that we are working in DirectX. I’d like to be able to hide all of that behind my own interface, but it’s just too much work for this project. So I pass a pointer to the Graphics object to the Scene.

The Scene object isn’t actually necessary for the initialization step. If you comment it out, the program will compile and run just fine. You’ll just get a blank screen:
blank window

Assuming you have objects in your scene, uncommenting the scene should give you something to look at. We’ll get to that next time.

For the latest code from this project, Go to GitHub.