3D Game Template

This page explains how the code is structured so far. The code has both a game loop and renderer to show the graphics to the screen. This acts as a template for our game. Read through the code and the explanations below.

Microsoft Windows & Xbox

Some explanation of the code files are here. This is a game template that provides a starting point for our game. We will then modify it.

My intention is to use some code from the DirectX12 Kit , DirectX Graphics Samples and sample games.

Some code is from a 3D sample game written in DirectX11, but some code is usable. There is an older source1 and newer source2 which is not quite complete 🙂 Also it is worth reading the tutorial on this sample game.

I will use some code from the DirectX Book by Frank Luna to develop the game by rewriting it – I found by reading Chapters 1 – 7 really helped me understand how DirectX12 worked, then I could pick other chapters to help when I needed it.

There is also a good starting tutorial on DirectX 12 here.

Note: the template uses C++/CX. I will eventually update the code to use C++/WinRT as this is the preferred standard now for writing Windows 10 apps. Luckily you can do this in portions as you go.

Setting Up the UWP App

If we were writing a Win32 program, it would build as a .exe file so that we can run it – this is how most programs or apps used to run. That’s how I did version 1 of the game, which is really just displaying graphics and moving around the screen using Frank Luna’s book.

Now apps for Windows run using a different structure called Universal Windows Platform. This is so that an app written for one version of Windows 10 can run on other devices. When you build the app, it runs on your Windows 10 PC but the same app also runs on the Xbox. That way the same app can also be bought on the Windows Store, regardless of device.

The main window you see when you run the app is called the CoreWindow. It is based on the Windows.ApplicationModel.CoreNamespace. More details on this is here.

The files App.h and App.cpp contain the code to build a UWP app.

The file Package.appxmanifest contains properties for our app. The files pch.h and pch.cpp are precompiled headers to help our code build faster before running our app.

A class is created called App which has been derived from IFrameworkView. A CoreWindow is created which represents the UWP app.

The App::Run() method then runs the app and also checks if Windows has any events to process. This is actually our game loop. If you read this method, it has:

  • m_main->Update() this function is used to update the game each frame at a time
  • m_main->Render() this function is used to render our game art each frame at a time
  • GetDeviceResources()->Present() this function is used to show our game art to the screen each frame at a time

A game loop also needs a timer. This is done in StepTimer.h and a class called StepTimer is created. This timer ensures smooth game animation and calculates frames per second (FPS)

The App class also handles things like what to do if the app is paused, what happens if the user resizes the window or quits the game.

Note that in App.h and App.cpp, there is some code to link in DirectX graphics to the CoreWindow – this is done via Direct3DApplicationSource.

Setting Up DirectX

Unfortunately setting up DirectX is complex. For 3D graphics, you need to create a Direct3D device and attach it to our game app. The device represents the graphics card of your computer (eg: Nvidia card).

The detailed steps to setup DirectX and also render to the screen from Microsoft is here. Also a description of the graphics pipeline for DirectX is here.

The setting up of DirectX is done in the files DeviceResources.h and DeviceResources.cpp

There is a class called DeviceResources which is created to handle initialisation of DirectX12.

The steps the game template carries out is as follows:

  • Create the graphics device ID3D12Device which represents the display adapter (graphics card). If a DirectX12 compatible card is not found, it will use the WARP device, which is a software video card
  • Create the command queue m_commandQueue – this belongs to the GPU. The CPU will feed this to the GPU command queue
  • Create descriptor heaps to store descriptors and views – m_rtvHeap is for render target views and m_dsvHeap is for depth stencil views
  • Create a command allocator m_commandAllocators[n]
  • Create the fence object m_fence – this is used to synchronise the CPU and GPU
  • Create the swap chain m_swapChain – this is the back buffer. You draw to the back buffer first before presenting it to the screen
  • Resize the back buffer to match the size of the window
  • Create a render target view m_renderTargets[n]
  • Create the depth/stencil buffer
  • Set the viewport m_screenViewport and scissor rectangles

Rendering Graphics

The template comes with a sample renderer with the files:

The steps in the Sample3DSceneRenderer is as follows:

Setting up the scene and loading the geometry or mesh:

  • Create the root signature m_rootSignature
  • Load the Vertex and Pixel shaders. Note that these are compiled from .hlsl to .cso
  • Create the pipeline state object m_pipelineState
  • Create the command list m_commandList
  • Create the geometry or mesh – this is the 3D cube consisting of a list of vertices
  • Create the vertex buffer m_vertexBuffer and upload it into the GPU – this is done using the command list (CPU -> GPU)
  • Load the geometry or mesh indices – this is the 3D cube consisting of a list of triangle indices
  • Create the index buffer m_indexBuffer and upload it into the GPU – this is done using the command list (CPU -> GPU)
  • Create the constant buffers and constant buffer views
  • Create the vertex and index buffer views
  • Wait for the GPU to finish loading
  • Create the viewport and scissor rectangle
  • Setup the camera

Updating the scene using the method Update(DX::StepTimer const& timer):

  • rotate the geometry or mesh – ie. rotate the cube
  • update the constant buffers

note – this uses the timer for smooth animation

Rendering the scene using the method Render():

  • Reset the command list
  • Set the graphics root signature
  • Set the descriptor heap
  • Bind the constant buffer to the pipeline
  • Set the viewport and scissor rectangle
  • Get the render target
  • Record the drawing commands. The geometry or mesh will be drawn as a triangle list
  • Indicate the back buffer will be used to present after the command list has executed to the render target
  • Execute the command list
  • Present the frame to the screen
  • Wait for the GPU to finish

Note: The Render() function draws one frame of the scene to the screen

Where to put Game specific code

There is a DirectXHelper.h file with miscellaneous functions to help with DirectX and also d3dx12.h that is written by Microsoft which is the D3DX12 utility for DirectX.

The game specific code is put in the files that you named the game. Mine is BlindfateMain.h and BlindfateMain.cpp You can also add extra source code files as you need if the code becomes too big.

Also you would need to update the Sample3DSceneRenderer.cpp and Sample3DSceneRenderer.h files to include more geometry as currently it has code for drawing a cube.

So far the Game class is as follows in Blindfate.h:

class BlindfateMain
void CreateRenderers(const std::shared_ptr& deviceResources);
void Update();
bool Render();
void OnWindowSizeChanged(); 
void OnSuspending(); 
void OnResuming(); 
void OnDeviceRemoved(); 

private: // TODO: Replace with your own content renderers. std::unique_ptr<Sample3DSceneRenderer> m_sceneRenderer; 

// Rendering loop timer. DX::StepTimer m_timer; };

We will add and modify to this template that has been created as we build our game.

Apple iOS & MacOS

The documentation of Metal is here. This is a game template that that provides a starting point for our game. We will then modify it.

Note that the template has code for a 3D spinning cube.

My intention is to look at the sample code for metal. In particular, I will make use of the sample code project from “Modern Rendering with Metal“. This sample is written using Objective C.

A screenshot from the sample code is below. You can move around with the W,S,A,D keys and rotate around with your mouse.

Some metal tutorials are here as well as tutorial videos on Metal with iOS.

Setting up the App

Documentation on setting up an app for iOS and MacOS is via the apple developer documentation. To develop for iOS, you use UIKit and for MacOS, you use AppKit. This documentation talks about Swift, but you can translate it to Objective C. Information on using Objective C is here plus a good Objective C Tutorial is here.

Objects in Objective C are derived from NSObject (or other built it objects) and then classes can be created using @interface, then you implement methods using @implementation and access public values using @property – hence this is a little bit different in syntax to using the C++ class{}, although I have seen @class used as well (see the sample above). But Objective C does work pretty similar to C++.

If you look at the code, the iOS app is separate from the MacOS app. Also some objects for the iOS app are named differently to the MacOS app.

The file main.m (desktop) and main.m (iOS) runs the main window for the app. It returns UIApplicationMain for iOS and NSApplicationMain for MacOS.

For iOS, UIApplication is the iOS app and for MacOS, NSApplication is the MacOS app. Built into the application is an event handler which runs in a loop – this will be also our game loop.

The files AppDelegate.h and AppDelegate.m (desktop) and AppDelegate.h and AppDelegate.m (iOS) contains the code to handle events for the open window of the app whether it’s on the computer monitor or phone. For example, for iOS, you can check to see if the app entered the foreground or background. For MacOS, you can check if the user terminated or quit the app.

The files GameViewController.h and GameViewController.m (desktop) and GameViewController.h and GameViewController.m (iOS) contains the code that controls what you see on the app itself (the screen part of the app). What you see is called the view – iOS has UIViewController and MacOS has NSViewController, and the object GameViewController is created based on these. The view also calls the renderer to render objects on the screen (the 3D cube).

Setting up Metal and Rendering Graphics

Metal seems to have a similar structure and attributes as DirectX12. The initialisation of Metal and rendering are done in an object created called Renderer.

The files Renderer.h and Renderer.m (desktop) and Renderer.h and Renderer.m (iOS) contain the code to render the graphics to the app window or screen and the files ShaderTypes.h and Shaders.metal contain the shaders.

In Renderer.h, the object or class Renderer calls the initWithMetalKitView method which points to the view (or app window). Behind the scenes with metal kit, there is a game timer and allows updates to be made per frame.

The implementation of the Renderer object is in Renderer.m

The steps to setup metal and render graphics is initialising some variables to use for setting up metal and then using initWithMetalKitView to

  • create the view device
  • create a semaphore (thread) to initialise buffers (memory)
  • load metal with the view via the method _loadMetalWithView
  • load the graphics assets

The method _loadMetalWithView does the following:

  • setup the view with defining the pixel formats (colour and depth stencil)
  • setup vertex descriptors
  • setup the graphics pipeline states and create the pipeline
  • setup the Depth Stencil
  • create the back buffer
  • create a command queue

The method _loadAssets does the following:

  • create a mesh buffer
  • create the mesh – in this case, it’s a 3D box using the function newBoxWithDimensions()
  • load vertex descriptors
  • create the metal kit mesh
  • create and load textures using a specific colour map

Another method is drawInMTKView which renders or draws the graphics to the view (app screen) once per frame. The steps involved are:

  • setup the command queue
  • update the back buffer
  • update the game state (rotate the cube)
  • get the render pass descriptor to prep for rendering
  • send rendering commands to the command queue
  • set the cull mode
  • set the graphics pipeline state
  • set the depth stencil state
  • set the vertex buffer
  • set the fragment buffer
  • set the fragment texture
  • cycle through meshes (if more than one) and draw them
  • present the graphics to the view (app screen)

The method _updateGameState is called by drawInMTKView once per frame. It is involved in rotating the cube and setting up the view to see the graphics on the screen as the cube rotates.

The method drawableSizeWillChange will respond if the view size changes (user resizes the app window). This method also sets up the _projectionMatrix which sets up the camera. _projectionMatrix is also in the _updateGameState method.

Where to put Game specific code

We can alter the existing files as needed and add new files as required.