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
#Feedback on DirectX12 game engine structure, bad practices, and more
227 messages · Page 1 of 1 (latest)
First thing that catches the eye is you have a big folder with many files
it should be divided into sub folders
so it's easier to move through them
ah yes. Will keep that in mind and fix it sometime in the future 🙂
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
when?
i mightve forgotten const correctness in some classes then. cause a lot of them has it. look at the resourcecache subclasses 🙂
graphics
but yes, that i should add more, thanks 🙂
Oh, sry. Graphics and EditorUI are depricated!
should edit that!
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
ye. gonna add that as well. thanks for the reminder!
@hard sedge
good point!
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
The new UI is LevelEditorUI
I should change the scene inside UI to be unique_ptr btw
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
Oh yeah. thanks! will change that
not using it in N places
yeah, totally missed that.
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
which one specifically?
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
ah that is uneccessary. will remove counter fully
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
hm. i feel like it isnt necessary to have the class hold it. alrExists is only used for that specific area
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
its a system which handles picking for objects. it needs to render the objects to be able to use for picking
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
all entities might not be bound to that system thought. then i would have to look for all entities which should be pickable.
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
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
Then you could abstract the renderer into an array of render passess
and just output to this rtv aswell
yeah. i think that would be the best approuch
only one something is supposed to be selected
since you dont need to render ID every time
only if clicked
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.
static bool minimized = false;
static bool paused = false;```
also again
you instead of using members/local vars create statics
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
where is that?
appwindow
well. those arent used. ive totally missed those lol xd
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
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
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
Ah totally forgot about const cor when it comes to variables and not functions xD ye. Gotta make a lot of changes to that
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
not rly. it needs them to stay alive.
i need to separate my component types in different arrays
sure you can separate them by child classess
not by hardcoding values somewhere
no one is supposed to create new ones. me, the engine dev, is doing that
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
ait. will look into that
then just use unique ptrs in objects that own them
and properly remove them from this array on event based approach once they die
no. i need the resource to stay alive even if the requested resource goes out of scope.
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
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
and how do i tell that they are executing?
synchronization techniques like fence
that would also mean that i would have to traverse between all resources and check (or something similar atleast, no)?
why?
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);
}
bro just again
event based
removal
once you want to remove something
you test it with the abstraction
that will make your architecture clean/dynamic and simple to use
and will cost nothing since ur bottleneck is on gpu
thats such a broad word. could you be more specific?
not rly. depends on the gpu work, the cpu specs, and a lot more
lol. why so toxic? Instead of just saying that something is bad, explain it so I understand the reason that it is bad
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
thats what i dont get. later when i add collision, physics, etcetc it will tax the cpu more
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
how would that deletion look like?
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
so all resources are in unique ptrs?
yes
thats gonna cause insane fragmentation
nah
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
you know whats the problem with C++ and C programmers once they start?
"they overoptimize when its not neccessary"
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
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?
because it doesnt matter and you have shared pointers anyway
however, there's probably a better way to queue the resources to be removed without having to store the resources in pointers
i dont?
std::vector<std::shared_ptr<TextureResource>>
ah okay. then theres a missunderstanding. those arent components
mb
However, that method is just to read from arbitrary textureresources.
resourceEngine->RequestReadback(rtv);
i will look into some kind of version like this thought. that is good for non-components.