#Feedback on DirectX12 game engine structure, bad practices, and more

227 messages · Page 1 of 1 (latest)

drowsy swift
#

I'm working on a game engine and it would be awesome with some feedback of overall structure, bad practices I'm doing, what I'm doing well, and maybe even some DirectX specific stuff. I understand that it's A LOT of code, but it would be the most helpful to get feedback on some of these classes: Engine, ResourceCache, Texture2DCache, VertexBufferCache, MaterialManager, DescriptorManager, ResourceEngine, ECS (and stuff in ECSBase.h), Scene, BufferResource (and it's subclasses), and TextureResource (and it's subclasses).
Here is the source code directory: https://github.com/NoahSch1999/aZeroEngine-Project/tree/master/aZeroEngine/aZeroEngine

GitHub

A game-engine I'm currently working on using DirectX12. - aZeroEngine-Project/aZeroEngine/aZeroEngine at master · NoahSch1999/aZeroEngine-Project

slim cargo
#

Looks nice

#

I'll take a look at the code later

hard sedge
#

it should be divided into sub folders

#

so it's easier to move through them

drowsy swift
hard sedge
#

You also have no const correctness

#

no noexcept

#

why is scene a raw pointer?

#

and also why do you have so many shared pointers

#

why not just unique

drowsy swift
drowsy swift
hard sedge
drowsy swift
drowsy swift
#

should edit that!

hard sedge
#

its also a good practice to use C++ style cast instead of C style cast

#

since C style one will iterate over casts and in most cases it doesnt matter but once it does u will have quite a debugging time

drowsy swift
#

@hard sedge

hard sedge
#

just remove the deprecated code/files

#

why would it lay in the repo

drowsy swift
hard sedge
#

Also if you're using imgui its good to abstract some widget class that will encapsulate it

#

so it's less code to use it and you have an easier control

#
  • it's cleaner
#
if (ImGui::Begin("Global Lighting Manager"))
    {

        ImGui::Text("Directional Light");
        static float pitch = 0.f;
        static float yaw = 0.f;
        static float roll = 0.f;
        ImGui::SliderAngle("Pitch", &pitch);
        ImGui::SliderAngle("Yaw", &yaw);

        Matrix view = Matrix::CreateFromYawPitchRoll(yaw, pitch, roll);
        view = Matrix::CreateLookAt(Vector3(0, 0, 0), Vector3(0, 0, 1), Vector3(0, 1, 0));
        Matrix proj = Matrix::CreateOrthographic(10.f, 10.f, 1.f, 10.f);
        graphics.lightSystem->GetLightManager()->dLightData.VPMatrix = proj * view;

        ImGui::End();
    }```
#

ah ok its deprecated nvm

drowsy swift
#

The new UI is LevelEditorUI

#

I should change the scene inside UI to be unique_ptr btw

hard sedge
#
if (ImGui::IsKeyPressed(ImGuiKey_W))
                mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
            if (ImGui::IsKeyPressed(ImGuiKey_R))
                mCurrentGizmoOperation = ImGuizmo::ROTATE;
            if (ImGui::IsKeyPressed(ImGuiKey_E))
                mCurrentGizmoOperation = ImGuizmo::SCALE;```
#

I dont like this

#

YOu have a separate class for input

#

but you're adding another checks here

#

You should have only one object that is handling the input

drowsy swift
hard sedge
#

not using it in N places

drowsy swift
#

yeah, totally missed that.

hard sedge
#
Transform* tf = currentScene->GetComponentForEntity<Transform>(_entity);```
#

also naming

#

tf is a unreadable name

#

or cam

#

use proper names since its just hard to read

#

also you use a lot of shared ptrs in a lot of places where unique_ptr will suffice

#

its just once you get wrong references somewhere its hard to debug

#

and its unnecessary fight

#

you also have quite a number of unnecessary static variables

#

they could just be placed on stack in scope and everything should be fine

drowsy swift
hard sedge
#

like this cpp static int counter = 0; in level editor ui

#

It just created a lot of possible problems

#
static char matNameBuffer[64] = "";```
#

also this I hate

#

just use std::string and resize it

drowsy swift
hard sedge
#

and keep it as member variable

#

static bool alrExists = false;

#

same here

#

then - I dont like that the editor class is doing a lot of stuff like loading/saving/deleting

#

It should have the widgets abstracted and just draw/update widgets

drowsy swift
#

hm. i feel like it isnt necessary to have the class hold it. alrExists is only used for that specific area

hard sedge
#

and the widgets itself should handle all that stuff

#

(char*)&info.normalMapIndex

#

this is also bad just use the C++ cast everywhere

#
virtual void Update() override
    {
        // Prep pipeline
        if (!camera.expired())
        {
            rtv->Clear(resourceEngine->renderPassList);
            dsv->Clear(resourceEngine->renderPassList);
            resourceEngine->renderPassList.GetGraphicList()->OMSetRenderTargets(1, &rtv->GetHandle().GetCPUHandleRef(), true, &dsv->GetHandle().GetCPUHandleRef());
            resourceEngine->renderPassList.GetGraphicList()->IASetPrimitiveTopology(D3D12_PRIMITIVE_TOPOLOGY::D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
            resourceEngine->renderPassList.GetGraphicList()->RSSetScissorRects(1, &swapChain->GetScissorRect());
            resourceEngine->renderPassList.GetGraphicList()->RSSetViewports(1, &swapChain->GetViewPort());

            resourceEngine->renderPassList.GetGraphicList()->SetPipelineState(pso.GetPipelineState());
            resourceEngine->renderPassList.GetGraphicList()->SetGraphicsRootSignature(rootSignature.GetSignature());

            std::shared_ptr<Camera> cam = camera.lock();
            resourceEngine->renderPassList.GetGraphicList()->SetGraphicsRootConstantBufferView(1, cam->GetBuffer().GetGPUAddress());

            for (Entity ent : entityIDMap.GetObjects())
            {
                resourceEngine->renderPassList.GetGraphicList()->SetGraphicsRootConstantBufferView(0, componentManager.GetComponent<Transform>(ent)->GetBuffer().GetGPUAddress());
                resourceEngine->renderPassList.GetGraphicList()->SetGraphicsRoot32BitConstant(2, ent.id, 0);
                VertexBuffer* vb = vbCache->GetResource(componentManager.GetComponent<Mesh>(ent)->GetID());
                resourceEngine->renderPassList.GetGraphicList()->IASetVertexBuffers(0, 1, &vb->GetView());
                resourceEngine->renderPassList.GetGraphicList()->DrawInstanced(vb->GetNumVertices(), 1, 0, 0);
            }

            resourceEngine->RequestReadback(rtv);
        }
    }```
#

why in the world a class related to picking objects would have such update

drowsy swift
hard sedge
#

just store the entities in the scene/level and get the array here via some getter on an event based approach once we want to pick smth

drowsy swift
hard sedge
#

You can just have an hash map on each level and iterate through every level

#

or layer

#

or anything that you have

#

and do a simple lockup

#

only once you want to pick smth

#

I see no point in using such update

#

waste of cycles and makes the architecture worse to use

drowsy swift
#

but i need to draw the models to a texture in order to read back the entity id

#

what i really should be doing is merging that draw into the geometry pass instead

hard sedge
#

Then you could abstract the renderer into an array of render passess

drowsy swift
#

and just output to this rtv aswell

hard sedge
#

add an ID render pass

#

and just read it here

drowsy swift
#

yeah. i think that would be the best approuch

hard sedge
#

only one something is supposed to be selected

#

since you dont need to render ID every time

#

only if clicked

drowsy swift
#

as i wrote in the description. this class isnt really what i need feedback from atm since its not very polished. thats why its not in the list.

hard sedge
#
static bool minimized = false;
    static bool paused = false;```
#

also again

#

you instead of using members/local vars create statics

drowsy swift
#

i'll remove this later and output the geometry data and entity id to an extra rendertarget during the geometry pass, which i then can sample from on the cpu. this removes an extra draw call per entity

hard sedge
#

that may make your life way harder

#

for no reaosn

hard sedge
#

appwindow

drowsy swift
#

well. those arent used. ive totally missed those lol xd

hard sedge
#

then this

#
#define FORWARD Vector3(0,0,1)
    #define VECRIGHT Vector3(1,0,0)
    #define UP Vector3(0,1,0)```
#

those should actually be statics

drowsy swift
#

as i said, the classes in the description are the ones most polished and best to give feedback on, since they wont have a bunch of non-used code

hard sedge
#

wheres the description

#

I dont see it

drowsy swift
hard sedge
#

int Texture2DCache::GetTextureHeapID(int _key)
{
Texture2D* text = resources.GetObjectByKey(_key);
if (text)
return text->GetHandle().GetHeapIndex();

return -1;

}

#

the text should be const for example

#

the method marked as const

#

and as NODISCARD

#

its a good practice to give more info to other programmers and those keywords are great to do that

#

Texture2D* t = resources.GetObjectByKey(_key);

#

again naming

#

and no const

drowsy swift
#

Ah totally forgot about const cor when it comes to variables and not functions xD ye. Gotta make a lot of changes to that

hard sedge
#

std::vector<std::shared_ptr<TextureResource>>

#

this is asking for problems

#

DescriptorManager& GetDescriptorManager() { return descriptorManager; }

#

and this should return a const DescriptorManager&

#

if you dont want someone to modify but just get a variable you add a const to the type

#

if its not by copy

#

In summary name things properly, add const wherever you can, do things on event based approach, abstract stuff so its cleaner and easier to use

#

and make it somewhat solid

#

so that the class only does its job and nothing else

#

ok also this

#
namespace COMPONENTENUM
{
    enum COMPONENTBITID { TRANSFORM, MESH, MATERIAL, PLIGHT, DLIGHT, MAX };
    inline std::string COMPONENTNAMES[4]{ "Transform", "Mesh", "Material", "Point Light" };
}```
#

just create some component base class inherit from it and add it to the entities

#

have an array of components

#

why hardcoding name here

#

what if someone creates a new one

#

he has to move to this file and hardcode 2 values

#

its bad

#

then you have multiple classess in this file

#

its not good at all

#

harder to read/go through

#
struct Entity
{
    Entity() = default;
    int id = -1; /**<Unique ID mapped to a component within the ComponentManager class that the component was registered for using ComponentManager::RegisterComponent(Entity& _entity, const T& _initValue)*/
    std::bitset<MAXCOMPONENTS> componentMask; /**<Describes what type of components that the instance of the Entity has registered*/
};```
#

this is a big no no

#

it should just have an vector of components

#

and it should describe the main entity

#

that later u can inherit from like to create a widget, scene entities and so on

#

then I dont want to go though this whole file

#

it just looks like a mess - no offense

#

a lot of things happening in a file called ECSBase.h

drowsy swift
drowsy swift
hard sedge
#

not by hardcoding values somewhere

drowsy swift
hard sedge
#

it makes it way worse to use

#

even for you

#

after some time u still need to remember to go to a file and add values

#

and why

#

just use simple inheritance

#

and abstraction

#

to make it dynamic

#

and u wont need to modify some values somewhere

drowsy swift
#

ait. will look into that

hard sedge
#

and properly remove them from this array on event based approach once they die

drowsy swift
#

by calling ResourceEngine::RequestReadback() i can request the resource engine to readback data in a gpu/cpu safe way. What if the input resource goes out of scope before the gpu-commands are executed/while they are executed?

#

that will cause UB/crash

hard sedge
#

just abstract the commands

#

you overthink a simple problem

#

add a boolean that will tell if they're during execution

#

once all finished

#

remove the resource

drowsy swift
#

and how do i tell that they are executing?

hard sedge
#

synchronization techniques like fence

drowsy swift
#

that would also mean that i would have to traverse between all resources and check (or something similar atleast, no)?

hard sedge
#

you have fence already implemented in dx12

#

no lol

drowsy swift
hard sedge
#
ID3D12Fence* fence;
UINT64 fenceValue = 1;
HRESULT hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
//...

HANDLE fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);

UINT64 currentFenceValue = fenceValue++;
commandQueue->Signal(fence, currentFenceValue);

//...

if (fence->GetCompletedValue() < currentFenceValue)
{
    fence->SetEventOnCompletion(currentFenceValue, fenceEvent);
    WaitForSingleObject(fenceEvent, INFINITE);
}
drowsy swift
#

that queues a cpu side wait

#

i dont wanna do that every frame lol

hard sedge
#

bro just again

hard sedge
#

removal

#

once you want to remove something

#

you test it with the abstraction

hard sedge
#

and will cost nothing since ur bottleneck is on gpu

drowsy swift
#

thats such a broad word. could you be more specific?

drowsy swift
hard sedge
#

... xd

#

ok w/e

#

no it doesnt but have it your way

#

anyway gl

drowsy swift
#

lol. why so toxic? Instead of just saying that something is bad, explain it so I understand the reason that it is bad

hard sedge
#

your app does little to nothing on cpu

#

so all bottleneck is on gpu

#

its just obvious

#

by using shared pointers you just create a lot of ways to make it static/hard to use/with a lot of random bugs

#

whereas you can abstract it, use fences and what not

drowsy swift
#

thats what i dont get. later when i add collision, physics, etcetc it will tax the cpu more

hard sedge
#

and just queue a deletion once something dies

#

you just run it on separate thread and the fences will still cost nothing

#

to later synchroniza everything

drowsy swift
hard sedge
#

one of many ways

#

have an array of unique ptrs

#

have the resource know the holder id or whatnot

#

once the object with certain id is destroyed

#

queue destruction of certain resource

drowsy swift
#

so all resources are in unique ptrs?

hard sedge
#

yes

drowsy swift
#

thats gonna cause insane fragmentation

hard sedge
#

nah

drowsy swift
#

why? say i have 1000 transform constant buffers. they all are in unique_ptrs. i iterate through all transform components in some system. then i have to go all over the memory

hard sedge
#

you know whats the problem with C++ and C programmers once they start?

drowsy swift
#

"they overoptimize when its not neccessary"

hard sedge
#

They think that saving some cycles each frame is a great must and will make the architecture insanely bad just fo the sake of premature optimizations

#

ur cpu

#

wont give a single

#

fuck

#

about 1k objects

drowsy swift
#

but its just a fact that having all resources in pointers instead of contiguous in a vector is much slower and scales a lot worse. so why start doing something in a mediocre way?

hard sedge
#

because it doesnt matter and you have shared pointers anyway

drowsy swift
#

however, there's probably a better way to queue the resources to be removed without having to store the resources in pointers

hard sedge
#

std::vector<std::shared_ptr<TextureResource>>

drowsy swift
#

ah okay. then theres a missunderstanding. those arent components

#

mb

#

However, that method is just to read from arbitrary textureresources.

#

resourceEngine->RequestReadback(rtv);

drowsy swift