Sign in to follow this  
cozzie

Engine design v0.3, classes and systems. thoughts?

Recommended Posts

Hi all,

 

Last half year I've been learning D3D11 and I've now decided not to refactor my existing D3D9 engine, but instead develop a new one from scratch (reusing modules/ components where possible). But before I run off and start creating classes, I've made a high over design as a guideline, with the systems and classes I'd like to use.

 

I really like to hear your thoughts on this approach and design.

Any input is appreciated, better now then further in the development process.

 

Side note; the design is a guideline and will be a 'living document' as I progress through development.

?

 

crealysm11_design_v0.1.jpg

Edited by cozzie

Share this post


Link to post
Share on other sites
I'm not going to point out the problems in your design but I encourage you to search here on the forums about "game engine architecture". It is also recommended that you read Jason Gregory's Game Engine Architecture book to understand the details of each game engine system.
 
Roughly speaking, for your first game engine I recommend a source tree that looks like this:
 
External (Dependencies)
imgui
PhysX
...
Tools
ModelConverter
ModelEditor
...
Engine 
Core
Allocation
File
Math
Platform
Win32
Linux
iOS
Standard
Template
...
Rendering
GpuDevice.h
GpuContext.h
OpenGL
OpenGL_Device.cpp
OpenGL_Context.cpp
D3D11
D3D11_Device.cpp
D3D11_Context.cpp
...
Animation
...
Physics
...
Audio
...
Game Object
...
Game_1
Game_2
Game_3
...
 
Each module (e.g. Core, Rendering, etc.) being a static library that gets linked into the game. If you're really organized then you might consider using UML's Class Diagrams instead of paint sketches to model the relationship between each system.
Edited by Irlan Robson

Share this post


Link to post
Share on other sites

Agreed, drop the useless C in front of every class.  You will just kill that key that much sooner, what did that key ever do to you?  Plus, every decent editor has intellisense these days.  Forget setters and getters, I prefer my code to read like English.

 

class Window {
public:
    // ...
    // Why do you need Get/Set, it's pretty explicit this way:
    void        Title(const std::string& text);
    std::string Title() const;
};

Share this post


Link to post
Share on other sites

Thanks for the valuable input;

- as for the prefixes, this is and will always be a matter of taste. I'll take it in consideration :wink:

 

@Josh: thanks, this is a good thing, because I've made it myself based on own insights and the d3d9 engine I've created before this.

Regarding high-level, I fully agree; maybe I will make a separate dependencies overview, because combining both will definately not make it better readable.

 

@Irlan: thanks, this helps in defining the systems and what will belong where. I'll definately go for LIB's per system. This also helps readability of the projects further on. I'll also have to create a smooth build proces, because there will never be a moment that a system/ LIB is final :)

I'll think about the UML's, I did this once before, when I made a game with my d3d9 engine, this helped in that specific case.

https://www.crealysm.com/downloads/documents/booh-game-design-final.pdf

 

@Mike: I'm not sure that I can read your example better then a Set/ Get function, but I understand the point, maybe it's a matter of getting used too.

Share this post


Link to post
Share on other sites

Separating out classes based on API like D3dSkybox and D3dLight is something I'd avoid. For example what does D3dLight have that CLight doesn't? It's a structure that has a position+/orientation, colour, type etc. In terms of interfacing with D3D, all you'll be doing is setting some constants or other buffer type - that can be abstracted efficiently at a much lower level. Abstracting buffers and entire blobs of data (buffer + state) makes porting to other platforms much easier too (and fits well with modern rendering APIs).

T

Share this post


Link to post
Share on other sites
Thanks, that's a good thing to think about. For some classes for me this sounds easier then others.

For example, where would you store a mesh's vtx buffer? In my case it's the Cd3dMesh class, because CMesh is API/ platform independent and just an IO/ data thing.

Share this post


Link to post
Share on other sites

Why are you making platform dependent lights, skyboxes, and etc? What is the different between a model drawn in directx and a model draw in vulkan? There isn't one. The whole point of a model is to hold vertex buffers and index buffers, so just make platform specific vertex and index buffers. The same with everything else under CSceneManager. But in reality, you will spend years if you keep trying to make the perfect uml diagram for an engine. You don't know what you need until you need it.

Share this post


Link to post
Share on other sites

For example, where would you store a mesh's vtx buffer? In my case it's the Cd3dMesh class, because CMesh is API/ platform independent and just an IO/ data thing.
This depends where you want to draw a line through your code base between code that's implicitly cross-platform, and code that you will re-implement once per platform.

 

If you implement a "gpu buffer" (index buffers, vertex buffers, structured buffers are all the same) class once per platform (BufferD3D11, BufferVulkan, etc), then your Mesh class becomes portable. It can own a Buffer (which is a BufferD3D11/etc, hidden behind an interface).

Alternatively, Mesh can be an interface for MeshD3D11, MeshVulkan, etc, and you can implement the entire mesh class once per platform.

 

Personally, I like to create a low-level renderer that's implemented once per platform, and then build the high level renderer (meshes, materials, etc) on top of it (and without using any platform specific code).

Share this post


Link to post
Share on other sites

Thanks guys, this sounds like a good opportunity to both reduce complexity and lines of code.

So let's see how that could work:

- I only have one per for the scene 'part's: i.e. mesh, mesh instancance, renderable, skybox etc.

-- in these classes I store their orientations etc, using directxmath (in my design so far those were part of the cd3d.... classes)

-- some of these classes get 1 or more objects of a new class CD3d11buffer (mesh has at least 2, vertex + index)

 

This would work and reduce complexity and code for sure.

The only thing is that the scene parts classes (mesh, mesh instance etc etc.) are no longer API independent. But then the question is, what are the advantages of having say 10 classes less, versus having 1 or 2 class members within the almost API independent other classes (for meshes, etc.).

 

Or did you mean a different approach then above?

 

Note; I'm aware that trying to be 100% platfom independent is for sure an illision, I just have to find a balance so I'm able to reuse components in the future, if I decide to go for another API for example.

Share this post


Link to post
Share on other sites
The only thing is that the scene parts classes (mesh, mesh instance etc etc.) are no longer API independent.

The point is that this does make mesh/etc API independent  :wacko:

You have a cross platform interface (e.g. in C++ you could use an abstract base class, or compile time polymorphism) such as IBuffer, and hidden implementations of it, such as your CD3D11Buffer (which would inherit from the abstract base class IBuffer). This means that your mesh is completely platform agnostic.

 

p.s. implementing a pure interface is a good use of inheritance. Extending a concrete class (CAnything) is a code smell.

Edited by Hodgman

Share this post


Link to post
Share on other sites

That's quick, I get it know.

 

- I have a scene in some format

- This is loaded in a cscene class object, which has cmesh class objects

- The cmesh class has one or more cbuffer class objects

- The cbuffer class has a ID3D11Buffer

Share this post


Link to post
Share on other sites

Voila, version 0.3.

Getting better by the day :cool:

 

Just some thoughts/ left questions:

1. I assume I have to prevent needing a d3ddevice or context outside the D3DRENDERER system/ namespace, correct?

(this sounds managable)

2. Would you split the CD3d11 class, resulting in a separate d3ddevice and devicecontext class?

(I've read that, but I'm not sure it'll bring me much)

3. Directxmath is all over the place, but other then the name I believe it's not API dependent (D3D).

If I would split that, the overview will become spaghetti :)

 

Always appreciate your thoughts.

 

crealysm11_design_v0.3.jpg

Edited by cozzie

Share this post


Link to post
Share on other sites

I want to be helpful, but it's difficult for me to reason about how the architecture works from a high-level graph like that. For instance, what information do your scenes contain? What constitutes a scene? Is your front menu a scene? How will the scenes be loaded/unloaded? Will you support dynamic, asynchronous loading of level fragments as the player plays through the level? Is each one of those fragments a scene? If not, how do you keep track of which assets are actually needed and which aren't? Some sort of usage counting? These are the sorts of questions that generally give rise to an overall architecture. However, these questions are usually not answerable from a high-level graph of class names alone.

 

I tend to have more success building up levels of abstraction from the bottom-up. I know how to draw a scene using low-level API calls, but that is tedious and painful. What tools and abstractions would be helpful here? Identify the biggest pain points and fix them first. For instance, there's so much that goes into just loading and initializing a texture with all of the right parameters (size, dimension, mip levels, etc.). You may even need a small constellation/hierarchy of classes to make this less painful while retaining the flexibility you need. And how much flexibility will you need, by the way? It's important to keep your design goals in mind. Are there any extreme optimizations you have in mind in order to reduce texture state changes? Atlasing? Placing multiple same-sized textures into one array? Sparse virtual texturing? You'll want to make sure that your low-level design doesn't preclude these options when you get to a higher level.

 

I don't know, maybe that's just me. I just find it hard to *start* with the highest-level abstractions. It seems like, once you start to get to the granularity of concrete, single-purpose objects, you may find that the boundaries you drew at the higher levels aren't going to work so well. It looks like you've designed some engines before, though, so maybe you just have some domain knowledge (even if that knowledge is just your usual way of doing things) that I don't have.

Share this post


Link to post
Share on other sites


The input handler. Seems very, very thin for what would you expect (keyboard, mouse, button handling, analog input handling, etc). I have no idea what a "CTimer" is nor why its so important that you need it in the diagram.

What about event handling? If the player runs over a trigger, which system is in charge of calling that event handler? If a player runs over a metal plate how does the audio systems knows how to switch the step sounds?

Im not quite understanding the "Actor" thing. Is it supposed to be heavily WIP? Because I'm missing things like game logic, triggers, scripts, quests/missions, AI, etc.

Not sure in D3D land but at least in GL land I have a single GLSLShader class that can be vertex, geometry or fragment shader. The only difference is a type enum just to know what it is.

I *think* that if you want to do a high level design of an engine, you'll have to start smaller first. Try only with the renderer, since thats the most detailed part of the diagram, I guess thats the part you're most familiar with. Other parts (events, scripting, AI, more complex game logic) it looks like you'll have to discover on your own, and only after you learn more about them you'll be able to design for it.

Share this post


Link to post
Share on other sites
Thanks for the input.
I understand that one design diagram doesn't answer all questions.

It's purpose is to have a guideline while developing the engine along the way. It's a combination of my last engine and things I ran into.

And to be honest, I will for sure run into things and will improve it along the way. What it saved me already is lots of time writing duplicate code or unlogical inherited classes etc. Especially the part with the 2 buffers, reducing all scene classes by almost 50%.

So again, Thanks.
I'll surprise you with a nice demo in a while and perhaps a new game using the engine.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this