2
\$\begingroup\$

I'm looking for a design pattern that's going to help me to elegantly handle the rendering of my game objects.

Lots of game development guides talk about how to handle the game objects themselves, like monsters and characters and all that kind of thing. Nothing talks about linking all of that to DirectX. All the DirectX guides avoid good architecture in favour of getting something simplistic working.

The problem I am essentially having (that nobody seems to want to answer or give guidance on) is how to elegantly handle the COM objects that the DirectX 11 API gives us. I want to come up with a method of controlling how my COM objects are constructed and destructed, to protect my program from wanton destruction of these objects. I want them to be safely housed. The method of housing is consistently the stumbling block. I need the device and the device context in various parts of the program, but I want to avoid coupling every aspect of my program tightly to that.

I don't, for example, want a d3d "manager" object that I'm going to be passing all and sundry into with different methods for rendering the specific type of object I'm passing in. I don't want to have to be passing raw pointers to COM objects to every single method I create.

Ideally I don't want to be building a rigid, hard coded rendering pattern into my main() function or an equivalent Render() method of a cMain class.

I'm ok with how the COM objects interact together, all the interfacing with the DirectX API to achieve rendering. What I'm looking for is someone with experience of modelling a rendering engine, the bit that actually takes objects and renders them in a certain way and order based on a number of conditions. I'm looking for more generalised information on how I should go about designing the rendering element of my game to meet my needs.

My current solution, which includes:

class cMain {
public:
    cMain();
private:
    void Render();
    cD3D m_d3d; //<---- this class initializes and encapsulates all my d3d com objects
};

cMain::Render()
{
    float colour[4] = {0.2f, 0.4f, 1.0f, 1.0f};
    m_d3d.GetDevCon()->ClearRenderTargetView(&m_d3d.GetRenderTarget(), colour);
    m_d3d.GetSwapChain()->Present(0, 0);
}

And an entire philosophy built on that. Surely there's a more elegant way of designing it? I'm running the risk of coupling cD3D to everything using this method, or having all my render work in cMain, neither of which seem particularly sensible.

Alternatively, am I thinking about this too much? Is this the kind of thing that CAN go in my main class without sacrificing too much flexibility?

TL;DR - I'm having trouble wrapping my head around how I should approach building a rendering engine architecture that will accept all the different types of objects I want to render, including but not limited to game units, terrain objects, particles, etc.

I figure how I handle resource loading and access will be linked relatively tightly to how I handle the rendering process, and I want to get it right early on to minimize problems encountered at later stages.

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

This may not be a good solution to your specific needs but I'm gonna tell you how I handled a similar problem.

I developed a c++ object oriented game and wanted to encapsulate DirectX and COM in general inside my own classes, because I didn't wanted to use COM as base for the whole game.

The COM parts of my game (at the moment direct3d, direct2d, directWrite, WIC and directInput) are all encapsulated in two main classes:

  • GraphicsEngine: everything that has to do with 3d, 2d (direct3d) and font rendering (direct2d and directWrite) as well as image and texture loading (WIC)
  • InputEngine: Handles user input (directInput)

Each object which wants to render or use any of the COM functionality just stores a pointer to the object of these classes (They are singleton, so only one object exists). Both classes ensure the correct usage of the components and provide easy access:

Constructors and Destructors:

They handle initialization as well as releasing COM objects

Member functions:

They delegate access to the COM functionality. However, there is generally no direct access to COM objects like ID3D11Device or IDirectInput8.

example:

Instead of using for instance

deviceContext -> VSSetShaderResources(slot, count, shaderResourceViews);

one would use

graphicsEngine -> SetShaderResources(ShaderType::VertexShader, slot, count, shaderResourceViews);

ShaderType beeing a enumeration of all different shader types. This reduces the amount of member functions since XXSetShaderResources exists for multiple shader types.

Error checking

Since the COM functionality is encapsulated in these classes, it is possible to automaticly check for errors (for instance HRESULT return type of COM functions). This reduces the code classes using COM have to write.

example:

hr = this -> device -> CreateSamplerState(samplerDescriptions[i], &samplerState);
if(!SUCCEEDED(hr)) {
    // error handler here
}

This code is contained inside a method called GraphicsEngine::AddSamplerStates.

asynchronous object loading

DirectX gives you the possibility to create objects e.g. ID3D11Buffer on a different thread as the main thread. This allows you to load objects while the game is rendering other stuff (splash screen or loading animation). However, initializing a thread and later synchronize with the thread when loading is done (even in c++11) requires quite a bit of code. My classes allow me to encapsulate this loading functionality nicely. The class holds an array of loading threads currently loading objects. Once an object is complete, a flag is set and the main thread makes the newly created object available in the public interface of the class. This ensures the correct loading of objects and prevents the use of not fully loaded objects. (See example in the next section)

object management

This is the most important job the wrapper classes do. All objects created using one of the public wrapper methods around COM functions are stored in the class. The class uses a sort of Key - Value mechanism to give access to objects.

Example

HashKey vertexBufferLoaderId = this -> graphicsEngine -> AddBuffers(bufferKeys, bufferDescriptions, bufferData, 5);

This piece of code starts an object loading job for multiple ID3D11Buffer objects. HashKey is the type used by the classes as keys (code possessing a key can use the COM object stored under that key). vertexBufferLoaderId will (after the method returns) contain a key to a ObjectLoader, a class which keeps track of the loading progress. The code which launched the loading process can get the loaded object once ObjectLoader affirms the completion of the loading progress. bufferKeys is an array of HashKey pointers (HashKey**), that's where the the keys for the loaded objects are stored after the objects are done loading. bufferDescriptions and bufferData are parameters needed by direct3d to create the buffers. The last parameter holds the amount of buffers to load.

The caller can fetch the loading progress at any time using the key to ObjectLoader. When the process is done, the caller can access the created objects using the keys (bufferKeys) he provided.

(almost) all encapsulated functions of COM which need a COM object as parameter, are implemented as follow:

Example

void SetVertexBuffer(HashKey vertexBuffer, Slot slot, UInt32 stride, UInt32 offset);

instead of

void SetVertexBuffer(ID3D11Buffer* vertexBuffer, Slot slot, UInt32 stride, UInt32 offset);

By using keys instead of objects, the class can check whether the passed objects is valid and make sure it has been created by the wrapper itself:

if(!this -> buffers.Exists(vertexBuffer)) {
    // error handling
}

conclusion:

These wrapper classes are far from finished. Whenever I need a specific functionality of COM, I add specific methods and members for it, so the classes are "evolving" with my game.

pros of my approach:

  • (almost, there are exceptions) no COM objects visible
  • automatic management of COM objects
  • easy access through keys (proprietary use of objects)
  • multiple function calls forming a specific task are regrouped
  • easy and safe multithreaded object creation

cons:

  • a lot of code
  • additional function call for each api call (a little bit slower but not crucial)
  • each time a new functionality is needed it needs to be implemented

It surely took me some time (two weeks of my free time) to code and debug but now it works fine and I never have to worry about using resources such as ID3D11Texture or ID3D11SamplerState correctly in my game.

I hope I could give you an idea of how such an implementation could be done. This is the first time I programmed a thing like that so I'm curious to know whether it helped you and what do you think of it.

\$\endgroup\$
2
  • \$\begingroup\$ Ok, so basically, I eventually went with the Service Locator design pattern as detailed in Robert Nystroms Game Programming Patterns: \$\endgroup\$
    – Mark
    Commented Feb 8, 2015 at 15:46
  • \$\begingroup\$ Interesting, I read that book too and I use a similar pattern to provide provide access to GraphicsEngineto all objects that need it. \$\endgroup\$
    – user60012
    Commented Feb 8, 2015 at 20:08
2
\$\begingroup\$

Ok, so basically, I eventually went with the Service Locator design pattern as detailed in Robert Nystroms Game Programming Patterns:

http://gameprogrammingpatterns.com/service-locator.html

My main game object creates a D3D object that handles all the COM objects and exposes them through accessors, and then the game object registers this D3D object with the D3DService class, which contains a static member pointer to the D3D object. Register takes a D3D&, so it can only ever be an existing D3D object. Any classes that require access to it's functionality creates a D3DService object and then has access to the D3D object.

D3D.h:

#include <dxgi.h>
#include <d3d11.h>
#include <DirectXMath.h>

class D3D
{
public:
    D3D(HWND hWnd);
    ~D3D();

    ID3D11Device* GetDevice();
    ID3D11DeviceContext* GetDevCon();
    ID3D11RenderTargetView* GetRenderTarget();
    IDXGISwapChain* GetSwapChain();
private:
    D3D();
    D3D(const D3D&);

    UINT32 m_test;
    void Clean();

    ID3D11Device* m_device;
    ID3D11DeviceContext* m_devCon;
    IDXGISwapChain* m_swapChain;
    ID3D11RenderTargetView* m_backBuffer;

    static bool isInstantiated;
};

D3DService.h:

#include "D3D.h"
#include <memory>

using std::unique_ptr;

class D3DService {
    friend class Game;
public:
    D3DService();
    ~D3DService();

    D3D* GetD3D();
private:
    D3DService(const D3DService& other);
    static void Register(D3D& d3d);
    static D3D* _d3d;
};
\$\endgroup\$
1
  • 4
    \$\begingroup\$ Take a look at Microsoft::WRL::ComPtr. It makes life with COM objects a whole lost easier. \$\endgroup\$ Commented Feb 11, 2015 at 7:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .