Jump to content
  • Advertisement
Sign in to follow this  
piluve

OpenGL Proper dx class structure.

This topic is 788 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello forum!

 

I´ve been working with OpenGl for the past 3 years (at the university and personal projects) mostly I used OpenGl 3.3. This year for my computing project I´ll work on a Rendering Engine. For now the engine will feature DX12 rendering (in the future I would like to add Vulkan too,but the university PCs dont support it).

 

I started this week to read through the msn forums and also a few online tutorials: braynzarsof. Those resources give you code base without a class structure (its mostly code hardcoded in the main function with a lot of globals).

 

The classes I´m used to work with are: Buffers,Textures,Materials(and material settings),Meshes,Frame Buffers,Render Managers/Cmd List etc

 

As I´ve seen so far, vulkan and dx12 add a lot of low level stuff like memory management,creation of the resources and uploading to the gpu, ability to define most of the properties etc.

 

What kind of structure do you use in your dx12 apps? I´m not sure if, for example, each material should have a PipelineStateObject or if I should make more than one RootSignature :C

 

See you!

Thanks.

Edited by piluve

Share this post


Link to post
Share on other sites
Advertisement

I based one of my games off the braynzarsoft tutorials for DX11.

 

I ended up taking all of his code and abstracting it into a class structure as follows:

 

The windowing functions were abstracted to a "Window" class which just had a basic message loop:

 

class Window
{
private:
    friend class Action;

    // Window class name
    static LPCTSTR WndClassName;
    // The minimum number of milliseconds per frame, used for fixed time step
    static int MinimumFrameTime;
    // Window handle
    HWND hwnd;
    // Window width, with decorations
    const int Width;
    // Window height, with decorations
    const int Height;
    // Instance
    HINSTANCE hInstance;
    static INT64 fps;
    static INT64 ups;
public:
    // Client width, inner part of window minus decorations
    static int ClientWidth;
    // Client height, inner part of window minus decorations
    static int ClientHeight;
    // Create new window with given parameters
    Window(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed, const std::wstring &title);
    virtual ~Window();
    // Windows message loop, returns when window is closed
    WPARAM Loop();
    // Process one windows message and dispatch it
    WPARAM Idle();
    // Window proc, handles low level windows messages dispatched by mesage loop
    // This version is static, so that windows can find it. Eww, C APIs!
    static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    // This version of the wndproc is called from the one above, by using a pointer to
    // the window's object stuffed into the GWL_USERDATA value.
    LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    // Returns window handle
    HWND GetHWnd();
    // Returns width
    const int GetWidth();
    // Returns height
    const int GetHeight();
    // Pure virtual, must be derived from.
    // Called each time around the main window loop whenever
    // a windows message does not occur.
    virtual void Iteration(IterationType it) = 0;
    virtual void KeyDown(WPARAM key);
    virtual void KeyUp(WPARAM key);

    static INT64 GetFramesPerSec();
    static INT64 GetUpdatesPerSec();

    static void WorkerThread(LPVOID creator);
};

 

 

Separate to the Window class is a DX11 class, which was composed of a Device object, Direct2D object (for drawing text and sprites etc), and a RenderTarget class.

 

For example:

using namespace DirectX;

class FireworkFactory;
class Camera;
class RenderTarget;
class DXDebug;

// Contains graphical functions specific to DirectX 11
class DX11
{
private:
    // Window handle
    HWND hWnd;
    // Creator class
    FireworkFactory* creator;
    // Debug interface
    DXDebug* dbg;
    // render target and depth stencil
    RenderTarget* render;
public:
    // Device
    CComPtr<ID3D11Device> Device;
    // Device context
    CComPtr<ID3D11DeviceContext> Context;
    // Swap chain
    CComPtr<IDXGISwapChain> SwapChain;
    // Clockwise culling mode
    RasterizerState* rasterizerState;
    // Direct2D subsystem
    Direct2D* D2D;
    // Vertex shader for models
    VertexShader* VS;
    // Pixel shader for models
    PixelShader* PS;
    // Per-object constant buffer
    ConstantBuffer* cbPerObjectBuffer;
    // Sampler state for texturing
    SamplerState* CubesTexSamplerState;
    // Lighting definition
    Lighting* lighting;
    // Initialise Direct3D 11 and Direct2D 10
    DX11(HWND hwnd, FireworkFactory* creator);
    ~DX11();
    // Call at start of frame
    void BeginDraw(Camera* cam);
    // Set up default render state
    void PrepareDraw(Camera* cam);
    // Present next screen from the swap chain
    void EndDraw();
};

Buffers were extracted to classes based on a base class called Buffer:

// There is no D3D11_RESOURCE_MISC_FLAG value for 'nothing', so lets make our own
// so we can preserve strong typing of the parameter.
#define NO_MISC_FLAGS (D3D11_RESOURCE_MISC_FLAG)0

/**
 * The buffer class is the parent class of all graphics-hardware-manged
 * memory buffers, e.g. constant buffers, vertex buffers.
 */
class Buffer
{
private:
    // Internal pointer to actual buffer object
    CComPtr<ID3D11Buffer> buffer;
    // Pointer to cpu-side copy of the data
    void* data;
public:
    // Instantiate as empty
    Buffer();
    // Instantiate to a given size, with a given set of contents (optional) and a given type.
    Buffer(ID3D11Device* dev, UINT size, D3D11_BIND_FLAG flags, const std::string &name, D3D11_RESOURCE_MISC_FLAG misc = NO_MISC_FLAGS, void* initdata = nullptr);
    // Get the D3D11 buffer object
    virtual ID3D11Buffer* Get();
    // Update the buffer (this may lock 'newdata' and/or copy it immediately to graphics ram)
    void Update(ID3D11DeviceContext* ctx, void* newdata);
    // Gets the cpu-side pointer to the buffer data
    virtual void* GetData();
    virtual ~Buffer();
};

For the shaders, each type of shader (i didn't go so far as materials) had a class derived from a base class called Shader. Shaders were expected to reference Buffer objects.

/* The base Shader class. All other shader types derive from this pure
 * virtual class. Contains a pointer to the device and context, an
 * implementation for loading shader binary files from disk, and a
 * destructor.
 */
class Shader
{
protected:
    ID3D11Device* device;
    ID3D11DeviceContext* context;
    // Implementation of shader binary loader. Takes the .cso file from
    // PhysicsFS and loads it into RAM returning a size and data array,
    // or NULL for the data array and zero for the size if the file cannot
    // be accessed.
    std::pair<const char*, UINT32> LoadShaderImpl(const std::wstring &filename);
public:
    // Save copy of the device and context pointers
    Shader(ID3D11Device* device, ID3D11DeviceContext* context);
    virtual void Set() = 0;
    virtual ~Shader();
    virtual void SetConstantBuffers(ConstantBuffer** buffers, UINT32 count) = 0;
    virtual void SetConstantBuffers(ConstantBuffer* buffer) = 0;
};

/* A pixel shader is represented by the PixelShader class. It supports several methods
 * which are specific to pixel shaders to apply them to the graphics pipeline.
 */
class PixelShader : public Shader
{
    // D3D11 Pixel shader object
    CComPtr<ID3D11PixelShader> ps;
public:
    // Load a pixel shader from disk
    PixelShader(ID3D11Device* device, ID3D11DeviceContext* context, const std::wstring &filename);
    virtual ~PixelShader();
    // Return the D3D11 pixel shader object
    ID3D11PixelShader* Get();
    // Set the shader as active in the pipeline
    virtual void Set();
    // Pass one or more constant buffers to the shader
    virtual void SetConstantBuffers(ConstantBuffer** buffers, UINT32 count);
    // Pass one or more constant buffers to the shader
    virtual void SetConstantBuffers(ConstantBuffer* buffer);
    // Pass one or more sampler states to the shader
    virtual void SetSamplers(SamplerState** samplers, UINT32 count);
    // Pass one or more sampler states to the shader
    virtual void SetSamplers(SamplerState* sampler);
    // Pass one or more SRVs (usually textures) to the shader
    virtual void SetResources(ID3D11ShaderResourceView** resources, UINT32 count);
    virtual void SetLighting(Lighting* lighting);
};

I then derived this for different types of buffer, e.g. a vertex buffer:

class VertexBuffer : public Buffer
{
public:    
    VertexBuffer(ID3D11Device* dev, UINT nvertices, const std::string &name, Vertex* initdata);
    void Set(ID3D11DeviceContext* ctx, D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    virtual ~VertexBuffer();
};

This isn't neccessarily the best way to do it, and might not port nicely to DX12. However, i hope it helps you, and if you need more code or information just let me know!

Edited by braindigitalis

Share this post


Link to post
Share on other sites

My tutorials were written specifically for how to use DirectX, so I made the decision of writing them with pretty much no OOP whatsoever so they were (I had hoped) easier to follow through and see what exactly is happening. The downside with this approach obviously is that now that you know how to use the DirectX APIs, how do you actually use it in a real application? haha, I get asked this question a lot, and it's hard to give a solid answer because there are a million ways to do the same thing, and you generally lose performance for flexibility, and flexibility for performance, which is another reason why I did not write the tutorials as a rendering engine, although i maybe should at some point write a tutorial on how you might (one of a million million ways) write a rendering engine utilizing DirectX.

 

I'm going to speak specifically about DirectX 12 because you had asked about that, and it being lower level, than about DirectX 11 for example. If you were asking about DirectX 11, I'd probably just refer you to google (actually I don't just refer people to google, I've always thought that was pretty rude) because there are a lot of tutorials, books, and probably videos (i don't watch many videos) on rendering systems.

 

So as you know already, DirectX 12 is a set of lower level API's compared to previous DirectX iterations. This means that much much more of the architecture that DirectX 11 did behind the scene is now up to the application programmer. You actually get to SEE the cost of the "simple" API calls you were making in DirectX 11 (I've seen people say "in dx11 all i had to do was this one line of code, but in dx12 i have to do like 100, that seems way too expensive"). Basically what I'm saying is that DirectX 12 assumes you have a thorough understanding of rendering systems architecture, so if you have to ask the question "how do I make a renderer with DirectX 12", then DirectX 12 is probably not (yet) for you. But don't worry! DirectX 12 is not a replacement for DirectX 11, in fact, you could almost look at it as DirectX 11 is a wrapper for DirectX 12. Take a look at Direct3D 11 on 12 - https://github.com/Microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D1211On12 . This might give you some ideas of how to write the rendering system as well.

 

Ok, so moving on. All i really have are some tips.

 

You will need one pipeline state object for every different state your pipeline will be in. This includes different combinations of shaders, different rasterizer states, blend states, and basically any other state that isn't set by a command you'd put into a command list, such as:

 

- Resource Bindings (includes vertex buffers, index buffers, stream output targets, render targets, descriptor heaps, and graphics root arguments)

- Viewports

- Scissor Rectangles

- Blend factor

- Depth/Stencil reference value

- Primitive topology order and adjacency type

 

You may have many pipeline state objects, and each material would contain a pointer (or handle) to a pipeline state object (you don't want to create a pso for each material!). You will have to decide what your rendering engine needs from a material.

 

The root signature basically says what data can be bound to the pipeline, and where (pretty much the exact same thing as a function signature). You want to try to reuse these as much as possible because changing them can be expensive. multiple pipeline state objects can share the same root signature, as long as the pso's accept the same data coming in (and where its coming in at). You could look at it like this "Pipeline(rootsignature)"

 

Another thing with DirectX 12 is that you will want multiples of things for each frame and/or thread. A command allocator cannot be accessed by the cpu while the gpu is accessing it, which means that when you execute a command list, you can't use the command allocator that you used with that command list the next frame. You will have to have a command allocator for every frame. Command lists on the other hand can be reused immediately after you submit it for execution. You can look at the command list as a doorway to put commands into a command allocator.
 
Constant buffers. There are multiple ways to manage memory in DirectX 12. The easiest is probably creating extra heaps. I like the idea of having a frame stack (basically a pointer to the next beginning free memory in a heap). Since you can't modify the constant buffer you just sent to the pipeline the previous frame, you will need to store it in memory that is not currently being  used, which means you will have duplicates of that memory, but there is no way around this unless you want to wait for a frame to finish before you move on to the next frame, which you don't want to do, usually anyway. Basically the way this works is you have a local (cpu side) copy of the constant buffer for an object. When you want to render that object ,you have to bind it's constant buffer to the pipeline. if you are doing double buffer (2 frames), you will have two heaps. if you are on the second frame ,you get the second heap. Then you get the frame stack pointer (which should be set to the beginning of the heap each frame), copy your local copy of the constant buffer to the heap at that pointer location, then increase the pointer by the size of the constant buffer (Remember the 64KB alignment!). Next frame you do the same thing, but use the other heap instead
 
One thing i have seen is people have a "RenderFrame" class. You would create one of these for each frame, and that would contain all the frame specific stuff, like the heap your working with, or if you only have one heap, the offset into the heap your frame stack starts, and the command allocator and command lists.
 
Keep in mind that the interface your render system exposes to the rest of your application should not really change based on the graphics API you are using
 
I felt i should make that bold because a lot of people think the entire app depends on the graphics API you are using, or the app needs to change because you switched from DirectX to OpenGL or something, which is totally not the case, and if it does? then you may need to go back to the drawing board and reconsider some of the design decisions you have made.
 
In conclusion i'd say start with the public interface of your renderer. Think about what the rest of your application needs from the renderer, then move on to implementing the guts. In my honest opinion, I think you should use DirectX 11. You are not downgrading by going from DirectX 12 to DirectX 11 (the name makes it sound like you are), and in fact, you may be upgrading, depending on how far you've gotten with DirectX 12. You'll certainly spend less time programming your renderer in DirectX 11.
 
P.S. I hope your finding my tutorials helpful!

Not going to lie, that was a lot more than i had planned on writing when i started the reply

Edited by iedoc

Share this post


Link to post
Share on other sites

I based one of my games off the braynzarsoft tutorials for DX11.

 

I ended up taking all of his code and abstracting it into a class structure as follows:

 

The windowing functions were abstracted to a "Window" class which just had a basic message loop:

class Window
{
private:
    friend class Action;

    // Window class name
    static LPCTSTR WndClassName;
    // The minimum number of milliseconds per frame, used for fixed time step
    static int MinimumFrameTime;
    // Window handle
    HWND hwnd;
    // Window width, with decorations
    const int Width;
    // Window height, with decorations
    const int Height;
    // Instance
    HINSTANCE hInstance;
    static INT64 fps;
    static INT64 ups;
public:
    // Client width, inner part of window minus decorations
    static int ClientWidth;
    // Client height, inner part of window minus decorations
    static int ClientHeight;
    // Create new window with given parameters
    Window(HINSTANCE hInstance, int ShowWnd, int width, int height, bool windowed, const std::wstring &title);
    virtual ~Window();
    // Windows message loop, returns when window is closed
    WPARAM Loop();
    // Process one windows message and dispatch it
    WPARAM Idle();
    // Window proc, handles low level windows messages dispatched by mesage loop
    // This version is static, so that windows can find it. Eww, C APIs!
    static LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    // This version of the wndproc is called from the one above, by using a pointer to
    // the window's object stuffed into the GWL_USERDATA value.
    LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
    // Returns window handle
    HWND GetHWnd();
    // Returns width
    const int GetWidth();
    // Returns height
    const int GetHeight();
    // Pure virtual, must be derived from.
    // Called each time around the main window loop whenever
    // a windows message does not occur.
    virtual void Iteration(IterationType it) = 0;
    virtual void KeyDown(WPARAM key);
    virtual void KeyUp(WPARAM key);

    static INT64 GetFramesPerSec();
    static INT64 GetUpdatesPerSec();

    static void WorkerThread(LPVOID creator);
};
 

 

Separate to the Window class is a DX11 class, which was composed of a Device object, Direct2D object (for drawing text and sprites etc), and a RenderTarget class.

 

For example:

using namespace DirectX;

class FireworkFactory;
class Camera;
class RenderTarget;
class DXDebug;

// Contains graphical functions specific to DirectX 11
class DX11
{
private:
    // Window handle
    HWND hWnd;
    // Creator class
    FireworkFactory* creator;
    // Debug interface
    DXDebug* dbg;
    // render target and depth stencil
    RenderTarget* render;
public:
    // Device
    CComPtr<ID3D11Device> Device;
    // Device context
    CComPtr<ID3D11DeviceContext> Context;
    // Swap chain
    CComPtr<IDXGISwapChain> SwapChain;
    // Clockwise culling mode
    RasterizerState* rasterizerState;
    // Direct2D subsystem
    Direct2D* D2D;
    // Vertex shader for models
    VertexShader* VS;
    // Pixel shader for models
    PixelShader* PS;
    // Per-object constant buffer
    ConstantBuffer* cbPerObjectBuffer;
    // Sampler state for texturing
    SamplerState* CubesTexSamplerState;
    // Lighting definition
    Lighting* lighting;
    // Initialise Direct3D 11 and Direct2D 10
    DX11(HWND hwnd, FireworkFactory* creator);
    ~DX11();
    // Call at start of frame
    void BeginDraw(Camera* cam);
    // Set up default render state
    void PrepareDraw(Camera* cam);
    // Present next screen from the swap chain
    void EndDraw();
};

Buffers were extracted to classes based on a base class called Buffer:

// There is no D3D11_RESOURCE_MISC_FLAG value for 'nothing', so lets make our own
// so we can preserve strong typing of the parameter.
#define NO_MISC_FLAGS (D3D11_RESOURCE_MISC_FLAG)0

/**
 * The buffer class is the parent class of all graphics-hardware-manged
 * memory buffers, e.g. constant buffers, vertex buffers.
 */
class Buffer
{
private:
    // Internal pointer to actual buffer object
    CComPtr<ID3D11Buffer> buffer;
    // Pointer to cpu-side copy of the data
    void* data;
public:
    // Instantiate as empty
    Buffer();
    // Instantiate to a given size, with a given set of contents (optional) and a given type.
    Buffer(ID3D11Device* dev, UINT size, D3D11_BIND_FLAG flags, const std::string &name, D3D11_RESOURCE_MISC_FLAG misc = NO_MISC_FLAGS, void* initdata = nullptr);
    // Get the D3D11 buffer object
    virtual ID3D11Buffer* Get();
    // Update the buffer (this may lock 'newdata' and/or copy it immediately to graphics ram)
    void Update(ID3D11DeviceContext* ctx, void* newdata);
    // Gets the cpu-side pointer to the buffer data
    virtual void* GetData();
    virtual ~Buffer();
};

For the shaders, each type of shader (i didn't go so far as materials) had a class derived from a base class called Shader. Shaders were expected to reference Buffer objects.

/* The base Shader class. All other shader types derive from this pure
 * virtual class. Contains a pointer to the device and context, an
 * implementation for loading shader binary files from disk, and a
 * destructor.
 */
class Shader
{
protected:
    ID3D11Device* device;
    ID3D11DeviceContext* context;
    // Implementation of shader binary loader. Takes the .cso file from
    // PhysicsFS and loads it into RAM returning a size and data array,
    // or NULL for the data array and zero for the size if the file cannot
    // be accessed.
    std::pair<const char*, UINT32> LoadShaderImpl(const std::wstring &filename);
public:
    // Save copy of the device and context pointers
    Shader(ID3D11Device* device, ID3D11DeviceContext* context);
    virtual void Set() = 0;
    virtual ~Shader();
    virtual void SetConstantBuffers(ConstantBuffer** buffers, UINT32 count) = 0;
    virtual void SetConstantBuffers(ConstantBuffer* buffer) = 0;
};

/* A pixel shader is represented by the PixelShader class. It supports several methods
 * which are specific to pixel shaders to apply them to the graphics pipeline.
 */
class PixelShader : public Shader
{
    // D3D11 Pixel shader object
    CComPtr<ID3D11PixelShader> ps;
public:
    // Load a pixel shader from disk
    PixelShader(ID3D11Device* device, ID3D11DeviceContext* context, const std::wstring &filename);
    virtual ~PixelShader();
    // Return the D3D11 pixel shader object
    ID3D11PixelShader* Get();
    // Set the shader as active in the pipeline
    virtual void Set();
    // Pass one or more constant buffers to the shader
    virtual void SetConstantBuffers(ConstantBuffer** buffers, UINT32 count);
    // Pass one or more constant buffers to the shader
    virtual void SetConstantBuffers(ConstantBuffer* buffer);
    // Pass one or more sampler states to the shader
    virtual void SetSamplers(SamplerState** samplers, UINT32 count);
    // Pass one or more sampler states to the shader
    virtual void SetSamplers(SamplerState* sampler);
    // Pass one or more SRVs (usually textures) to the shader
    virtual void SetResources(ID3D11ShaderResourceView** resources, UINT32 count);
    virtual void SetLighting(Lighting* lighting);
};

I then derived this for different types of buffer, e.g. a vertex buffer:

class VertexBuffer : public Buffer
{
public:    
    VertexBuffer(ID3D11Device* dev, UINT nvertices, const std::string &name, Vertex* initdata);
    void Set(ID3D11DeviceContext* ctx, D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    virtual ~VertexBuffer();
};

This isn't neccessarily the best way to do it, and might not port nicely to DX12. However, i hope it helps you, and if you need more code or information just let me know!

 

Hello again!
Thanks for the code snippets, I will check them for sure (And at least I made similar decisions to you ;) ).
 

 

My tutorials were written specifically for how to use DirectX, so I made the decision of writing them with pretty much no OOP whatsoever so they were (I had hoped) easier to follow through and see what exactly is happening. The downside with this approach obviously is that now that you know how to use the DirectX APIs, how do you actually use it in a real application? haha, I get asked this question a lot, and it's hard to give a solid answer because there are a million ways to do the same thing, and you generally lose performance for flexibility, and flexibility for performance, which is another reason why I did not write the tutorials as a rendering engine, although i maybe should at some point write a tutorial on how you might (one of a million million ways) write a rendering engine utilizing DirectX.

 

I'm going to speak specifically about DirectX 12 because you had asked about that, and it being lower level, than about DirectX 11 for example. If you were asking about DirectX 11, I'd probably just refer you to google (actually I don't just refer people to google, I've always thought that was pretty rude) because there are a lot of tutorials, books, and probably videos (i don't watch many videos) on rendering systems.

 

So as you know already, DirectX 12 is a set of lower level API's compared to previous DirectX iterations. This means that much much more of the architecture that DirectX 11 did behind the scene is now up to the application programmer. You actually get to SEE the cost of the "simple" API calls you were making in DirectX 11 (I've seen people say "in dx11 all i had to do was this one line of code, but in dx12 i have to do like 100, that seems way too expensive"). Basically what I'm saying is that DirectX 12 assumes you have a thorough understanding of rendering systems architecture, so if you have to ask the question "how do I make a renderer with DirectX 12", then DirectX 12 is probably not (yet) for you. But don't worry! DirectX 12 is not a replacement for DirectX 11, in fact, you could almost look at it as DirectX 11 is a wrapper for DirectX 12. Take a look at Direct3D 11 on 12 - https://github.com/Microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D1211On12 . This might give you some ideas of how to write the rendering system as well.

 

Ok, so moving on. All i really have are some tips.

 

You will need one pipeline state object for every different state your pipeline will be in. This includes different combinations of shaders, different rasterizer states, blend states, and basically any other state that isn't set by a command you'd put into a command list, such as:

 

- Resource Bindings (includes vertex buffers, index buffers, stream output targets, render targets, descriptor heaps, and graphics root arguments)

- Viewports

- Scissor Rectangles

- Blend factor

- Depth/Stencil reference value

- Primitive topology order and adjacency type

 

You may have many pipeline state objects, and each material would contain a pointer (or handle) to a pipeline state object (you don't want to create a pso for each material!). You will have to decide what your rendering engine needs from a material.

 

The root signature basically says what data can be bound to the pipeline, and where (pretty much the exact same thing as a function signature). You want to try to reuse these as much as possible because changing them can be expensive. multiple pipeline state objects can share the same root signature, as long as the pso's accept the same data coming in (and where its coming in at). You could look at it like this "Pipeline(rootsignature)"

 

Another thing with DirectX 12 is that you will want multiples of things for each frame and/or thread. A command allocator cannot be accessed by the cpu while the gpu is accessing it, which means that when you execute a command list, you can't use the command allocator that you used with that command list the next frame. You will have to have a command allocator for every frame. Command lists on the other hand can be reused immediately after you submit it for execution. You can look at the command list as a doorway to put commands into a command allocator.
 
Constant buffers. There are multiple ways to manage memory in DirectX 12. The easiest is probably creating extra heaps. I like the idea of having a frame stack (basically a pointer to the next beginning free memory in a heap). Since you can't modify the constant buffer you just sent to the pipeline the previous frame, you will need to store it in memory that is not currently being  used, which means you will have duplicates of that memory, but there is no way around this unless you want to wait for a frame to finish before you move on to the next frame, which you don't want to do, usually anyway. Basically the way this works is you have a local (cpu side) copy of the constant buffer for an object. When you want to render that object ,you have to bind it's constant buffer to the pipeline. if you are doing double buffer (2 frames), you will have two heaps. if you are on the second frame ,you get the second heap. Then you get the frame stack pointer (which should be set to the beginning of the heap each frame), copy your local copy of the constant buffer to the heap at that pointer location, then increase the pointer by the size of the constant buffer (Remember the 64KB alignment!). Next frame you do the same thing, but use the other heap instead
 
One thing i have seen is people have a "RenderFrame" class. You would create one of these for each frame, and that would contain all the frame specific stuff, like the heap your working with, or if you only have one heap, the offset into the heap your frame stack starts, and the command allocator and command lists.
 
Keep in mind that the interface your render system exposes to the rest of your application should not really change based on the graphics API you are using
 
I felt i should make that bold because a lot of people think the entire app depends on the graphics API you are using, or the app needs to change because you switched from DirectX to OpenGL or something, which is totally not the case, and if it does? then you may need to go back to the drawing board and reconsider some of the design decisions you have made.
 
In conclusion i'd say start with the public interface of your renderer. Think about what the rest of your application needs from the renderer, then move on to implementing the guts. In my honest opinion, I think you should use DirectX 11. You are not downgrading by going from DirectX 12 to DirectX 11 (the name makes it sound like you are), and in fact, you may be upgrading, depending on how far you've gotten with DirectX 12. You'll certainly spend less time programming your renderer in DirectX 11.
 
P.S. I hope your finding my tutorials helpful!

Not going to lie, that was a lot more than i had planned on writing when i started the reply

 

Hello!!!

First of all thanks for the tutorials (and yes, I found it really useful!), let me say that I didnt want to be rude when I said it was all harcoded in the main file (I mean it is but I understand why it is done like that).

 

Last year we had the task to make a simple "engine" using OpenGl, the teached told us to make the interface agnostic to the rendering API (OpenGL,dx etc) so I have that in mind!

The problem is that is the first time that I work with this new low-level APIs so things seem more complicates thats why Im not sure how to make my interface.

The decission of using DX12 is like a personal challenge (I have almost the entire year to work on it) and I think it will be helpful in the future when the time to find a job begins xD.

 

So yep, thanks for all the info!

 

See you.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!