From 7328d02d3dff2a0cec8b0dd127a596703d9b38f5 Mon Sep 17 00:00:00 2001 From: Patedam Date: Sun, 18 Jan 2026 14:33:52 -0500 Subject: [PATCH] Converted Descriptor heap to memory arena. Used Gemini with antigravity. Heap pool code is a mess right now (lot of useless comments) Will revisit later. --- .agent/rules/coding-guidelines.md | 5 +- .../cpp_game_engine_programmer/SKILL.md | 30 ++++ .agent/skills/graphics_programmer/SKILL.md | 30 ++++ .agent/skills/video_game_tester/SKILL.md | 32 ++++ .agent/workflows/build.md | 7 +- AgentData/MemoryArena.md | 8 +- Game/Game.vcxproj | 12 +- Game/game.cpp | 6 +- Juliet/Juliet.vcxproj | 4 + Juliet/Juliet.vcxproj.filters | 12 ++ Juliet/include/Core/Memory/MemoryArena.h | 7 + Juliet/src/Core/Memory/MemoryArena.cpp | 62 ++++++++ .../Graphics/D3D12/D3D12DescriptorHeap.cpp | 98 ++++++++++-- .../src/Graphics/D3D12/D3D12DescriptorHeap.h | 4 +- .../Graphics/D3D12/D3D12GraphicsDevice.cpp | 12 +- .../src/Graphics/D3D12/D3D12InternalTests.cpp | 141 ++++++++++++++++++ .../src/Graphics/D3D12/D3D12InternalTests.h | 9 ++ JulietApp/JulietApp.vcxproj | 12 +- 18 files changed, 446 insertions(+), 45 deletions(-) create mode 100644 .agent/skills/cpp_game_engine_programmer/SKILL.md create mode 100644 .agent/skills/graphics_programmer/SKILL.md create mode 100644 .agent/skills/video_game_tester/SKILL.md create mode 100644 Juliet/src/Graphics/D3D12/D3D12InternalTests.cpp create mode 100644 Juliet/src/Graphics/D3D12/D3D12InternalTests.h diff --git a/.agent/rules/coding-guidelines.md b/.agent/rules/coding-guidelines.md index 8dd0945..d0d33dd 100644 --- a/.agent/rules/coding-guidelines.md +++ b/.agent/rules/coding-guidelines.md @@ -7,4 +7,7 @@ Use [[nodiscard]] auto is allowed but when its a pointer add the * and when reference adds the & Member variable are CamelCase Types are CamelCase -Functions are CamelCase. \ No newline at end of file +Functions are CamelCase. +Add Assert to make sure all assumptions are good. Parameters of functions for example should be verified with Assert. +Code should be self commented using proper variable names, types and functions. No need to add comments most of the time, unless the algorithm is very complex and hard to read. +When creating a new system framework, make a unit test. To make the unit test we should not modify the framework code for special unit test case. \ No newline at end of file diff --git a/.agent/skills/cpp_game_engine_programmer/SKILL.md b/.agent/skills/cpp_game_engine_programmer/SKILL.md new file mode 100644 index 0000000..df176be --- /dev/null +++ b/.agent/skills/cpp_game_engine_programmer/SKILL.md @@ -0,0 +1,30 @@ +--- +name: C++ Game Engine Programmer +description: An expert C++ systems programmer specialized in game engine architecture, memory management, and D3D12 graphics. +--- + +# C++ Game Engine Programmer Skill + +## Role +You are a senior engine architect for the Juliet project. Your expertise lies in high-performance C++ systems programming, specifically within the context of game engine development. You value performance, memory efficiency, and maintainability. + +## Focus Areas +1. **High Performance**: Always consider cache locality and CPU cycle cost. +2. **Memory Management**: Prioritize manual memory management using Arenas (`MemoryArena`, `EngineArena`) over standard library containers or raw `new`/`delete`. +3. **Unified Style**: Enforce the project's coding style strictly. + +## Coding Guidelines +You must always follow the project's `coding-guidelines.md`. Key tenets include: +- **No Exceptions**: Use asserts and return codes. +- **[[nodiscard]]**: Use this attribute aggressively for functions returning values. +- **Type Safety**: Use strong types (CamelCase). +- **Asserts**: Validate all assumptions, especially function parameters (`ASSERT`). +- **Self-Documenting Code**: Prefer clear naming over excessive comments. + +## Workflows +- **Building**: Use the `/build` command (FastBuild) to compile the project. +- **Shaders**: If you modify HLSL files, use `/recompile_shaders` to update the shaders. +- **Unit Tests**: When creating a new system, always implement a unit test. Do not modify the framework solely for testing; use dependency injection or mock interfaces where appropriate. + +## Tone +Professional, technical, and precise. Focus on the *why* and *how* of architectural decisions. diff --git a/.agent/skills/graphics_programmer/SKILL.md b/.agent/skills/graphics_programmer/SKILL.md new file mode 100644 index 0000000..ebcd505 --- /dev/null +++ b/.agent/skills/graphics_programmer/SKILL.md @@ -0,0 +1,30 @@ +--- +name: Graphics Programmer & Tech Artist +description: An expert in 3D rendering, shader development (HLSL), and visual aesthetics, acting as a bridge between technical implementation and artistic vision. +--- + +# Graphics Programmer & Tech Artist Skill + +## Role +You are the bridge between code and art for the Juliet project. You are responsible for the final pixels on the screen, ensuring they are both performant and visually stunning. You speak fluent C++ (D3D12) and HLSL. + +## Focus Areas +1. **Visual Excellence**: Never settle for "it works." Make it look "premium." Use lighting, shadows, and post-processing to enhance the aesthetic. +2. **Pipeline Mastery**: Deep understanding of the D3D12 pipeline (PSOs, Root Signatures, Resource Barriers, Descriptor Heaps). +3. **Shader Craftsmanship**: Write efficient, clean, and documented HLSL. + +## Workflows +- **Shader Iteration**: Use the `/recompile_shaders` workflow immediately after editing any `.hlsl` file to catch syntax errors early. +- **Performance**: Be mindful of GPU bandwidth and generic overdraw. +- **Debugging**: Think like a frame capture (PIX/RenderDoc). Visualize data (normals, depth, etc.) if unsure. + +## Coding Guidelines +- **HLSL**: + - Use meaningful variable names. + - Group constant buffers logically by frequency of update (Global, PerObject, etc.). +- **C++ (D3D12)**: + - Follow the general `coding-guidelines.md`. + - Ensure correct `D3D12_RESOURCE_BARRIER` usage to avoid race conditions. + +## Tone +Creative, collaborative, and highly technical. You are excited about graphics techniques (PBR, Raytracing, etc.) but grounded in the reality of shipping a performant frame. diff --git a/.agent/skills/video_game_tester/SKILL.md b/.agent/skills/video_game_tester/SKILL.md new file mode 100644 index 0000000..fec7b7e --- /dev/null +++ b/.agent/skills/video_game_tester/SKILL.md @@ -0,0 +1,32 @@ +--- +name: Video Game Tester +description: A rigorous QA specialist and test automation engineer focused on verifying game stability and correctness. +--- + +# Video Game Tester Skill + +## Role +You are the gatekeeper of quality for the Juliet project. Your job is to break the game, find edge cases, and ensure that every change works as intended before it receives a seal of approval. + +## Focus Areas +1. **Verification**: Never assume a change works. Always run the game. +2. **Reproduction**: If you find an issue, define clear steps to reproduce it. +3. **Stress Testing**: Look for memory leaks, performance regressions, and stability issues. + +## Methodology +- **Always Run the Game**: After any code change, even minor ones, verify by running the build. +- **Launch Command**: Use the `/launch` workflow. + - **IMPORTANT**: When running automated or quick verification, ALWAYS use the `autoclose` parameter (e.g., `/launch autoclose`) so the game exits automatically after the test sequence. +- **Log Analysis**: strict checking of `OutputDebugString` or log files for any `ERROR` or `WARNING` lines. +- **Visual Inspection**: Report any visual artifacts, flickering, or incorrect rendering immediately. + +## Reporting +- When reporting issues, be concise but detailed. +- Include: + - Reproduction steps. + - Expected behavior vs. Actual behavior. + - Relevant log snippets. + - Screenshots (if applicable/possible). + +## Tone +Skeptical, thorough, and detail-oriented. You trust nothing until you see it running. diff --git a/.agent/workflows/build.md b/.agent/workflows/build.md index b535a3a..ee5fc71 100644 --- a/.agent/workflows/build.md +++ b/.agent/workflows/build.md @@ -1,6 +1,7 @@ --- description: Build the Juliet project using FastBuild --- + // turbo-all This workflow sets up the Juliet build environment and runs `fbuild`. @@ -8,8 +9,8 @@ This workflow sets up the Juliet build environment and runs `fbuild`. 1. To build a specific configuration (e.g., msvc-Debug): `cmd /c "misc\shell.bat & fbuild msvc-Debug"` -2. To build the default (msvc): -`cmd /c "misc\shell.bat & fbuild msvc"` +2. To build the default clang: +`cmd /c "misc\shell.bat & fbuild clang"` 3. To see all available targets: -`cmd /c "misc\shell.bat & fbuild -showtargets"` +`cmd /c "misc\shell.bat & fbuild -showtargets"` \ No newline at end of file diff --git a/AgentData/MemoryArena.md b/AgentData/MemoryArena.md index 8cd012a..3fa4084 100644 --- a/AgentData/MemoryArena.md +++ b/AgentData/MemoryArena.md @@ -13,17 +13,13 @@ ### Migrated Subsystems - **Display**: `Win32Window` and `Win32DisplayDevice` now use `EngineArena`. - **Game Entities**: `Entity.h` uses `GameArena` for entity allocation; manual `free` calls removed from `game.cpp`. +- **Hot Reload**: `HotReload.cpp` (persistent paths to `EngineArena`) and `Win32HotReload.cpp` (temp paths to `ScratchArena`). ## Remaining Work The following subsystems still use legacy `malloc`/`calloc`/`realloc`/`free` and need to be migrated. -### Hot Reload System -- **Files**: `Core/HotReload/HotReload.cpp`, `Core/HotReload/Win32/Win32HotReload.cpp` -- **Allocations**: `DLLFullPath`, `LockFullPath`, `tempDllPath`. -- **Strategy**: - - Use `EngineArena` for persistent paths (`DLLFullPath`, `LockFullPath`). - - Use `ScratchArena` for temporary paths (`tempDllPath`). + ### IO System - **Files**: `Core/HAL/IO/IOStream.cpp`, `Core/HAL/IO/Win32/Win32IOStream.cpp` diff --git a/Game/Game.vcxproj b/Game/Game.vcxproj index a1a4838..92a05ac 100644 --- a/Game/Game.vcxproj +++ b/Game/Game.vcxproj @@ -239,7 +239,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -249,7 +249,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -259,7 +259,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -269,7 +269,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -279,7 +279,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -289,7 +289,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ diff --git a/Game/game.cpp b/Game/game.cpp index dafbada..63e0349 100644 --- a/Game/game.cpp +++ b/Game/game.cpp @@ -40,11 +40,11 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/) int Score; }; - auto* gameState = ArenaPushType(GetGameArena()); + auto* gameState = ArenaPushType(GetGameArena()); gameState->TotalTime = 0.0f; gameState->Score = 0; - - printf("Game Arena Allocated: %p\n", gameState); + + printf("Game Arena Allocated: %p\n", static_cast(gameState)); using namespace Game; diff --git a/Juliet/Juliet.vcxproj b/Juliet/Juliet.vcxproj index 8da716c..0891ff3 100644 --- a/Juliet/Juliet.vcxproj +++ b/Juliet/Juliet.vcxproj @@ -58,6 +58,7 @@ + @@ -122,6 +123,7 @@ + @@ -162,6 +164,8 @@ + + diff --git a/Juliet/Juliet.vcxproj.filters b/Juliet/Juliet.vcxproj.filters index 4f9afb0..87ea988 100644 --- a/Juliet/Juliet.vcxproj.filters +++ b/Juliet/Juliet.vcxproj.filters @@ -94,6 +94,9 @@ include\Core\Memory + + include\Core\Memory + include\Core\Memory @@ -285,6 +288,9 @@ src\Core\Memory + + src\Core\Memory + src\Core\Networking @@ -405,6 +411,12 @@ src\Graphics\D3D12 + + src\Graphics\D3D12 + + + src\Graphics\D3D12 + src\Graphics\D3D12 diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h index 0e4da06..f605627 100644 --- a/Juliet/include/Core/Memory/MemoryArena.h +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -16,6 +16,7 @@ namespace Juliet JULIET_API void MemoryArenaCreate(MemoryArena* arena, void* backingMemory, size_t size); JULIET_API void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment = 16); + JULIET_API void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment = 16); JULIET_API void ArenaReset(MemoryArena* arena); JULIET_API size_t ArenaGetMarker(MemoryArena* arena); JULIET_API void ArenaResetToMarker(MemoryArena* arena, size_t marker); @@ -58,4 +59,10 @@ namespace Juliet } return result; } + + template + inline T* ArenaRealloc(MemoryArena* arena, T* oldPtr, size_t oldCount, size_t newCount) + { + return static_cast(Juliet::ArenaRealloc(arena, static_cast(oldPtr), sizeof(T) * oldCount, sizeof(T) * newCount, alignof(T))); + } } // namespace Juliet diff --git a/Juliet/src/Core/Memory/MemoryArena.cpp b/Juliet/src/Core/Memory/MemoryArena.cpp index 3043fcb..3577aa3 100644 --- a/Juliet/src/Core/Memory/MemoryArena.cpp +++ b/Juliet/src/Core/Memory/MemoryArena.cpp @@ -2,6 +2,8 @@ #include #include +#include + namespace Juliet { void MemoryArenaCreate(MemoryArena* arena, void* backingMemory, size_t size) @@ -36,6 +38,66 @@ namespace Juliet return result; } + void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment) + { + Assert(arena); + // Alignment must be power of 2 + Assert((alignment & (alignment - 1)) == 0); + + if (oldPtr == nullptr) + { + return ArenaPush(arena, newSize, alignment); + } + + if (newSize == 0) + { + return nullptr; + } + + // Check if the old allocation is at the top of the stack + // We need to verify if (oldPtr + oldSize) == (Data + Offset) + // Note: usage of reinterpret_cast is to be careful with pointer arithmetic on void* + + uint8* oldPtrBytes = static_cast(oldPtr); + uint8* arenaEnd = arena->Data + arena->Offset; + + if (oldPtrBytes + oldSize == arenaEnd) + { + // It is the last allocation! We can reuse the space. + // We just need to check if we can expand it (if growing) + + // Re-calculate the offset start for this block (to ensure nothing weird with padding) + // Ideally oldPtr was aligned. + + // Current Offset corresponds to oldPtrBytes + oldSize. + // We want to move Offset to oldPtrBytes + newSize. + + size_t oldPtrOffset = static_cast(oldPtrBytes - arena->Data); + size_t newOffset = oldPtrOffset + newSize; + + if (newOffset > arena->Size) + { + // Cannot expand in place, not enough space. + // Fallthrough to Alloc + Copy + } + else + { + // In-place Resize success + arena->Offset = newOffset; + return oldPtr; + } + } + + // Fallback: Alloc + Copy + void* newPtr = ArenaPush(arena, newSize, alignment); + if (newPtr) + { + size_t copySize = oldSize < newSize ? oldSize : newSize; + std::memcpy(newPtr, oldPtr, copySize); + } + return newPtr; + } + void ArenaReset(MemoryArena* arena) { Assert(arena); diff --git a/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp b/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp index f0a6f89..92304ca 100644 --- a/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp +++ b/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -91,7 +92,10 @@ namespace Juliet::D3D12::Internal void ReleaseDescriptor(const D3D12Descriptor& descriptor) { - if (descriptor.Index == UINT32_MAX || descriptor.Heap == nullptr) return; + if (descriptor.Index == UINT32_MAX || descriptor.Heap == nullptr) + { + return; + } D3D12DescriptorHeap* heap = descriptor.Heap; @@ -105,23 +109,64 @@ namespace Juliet::D3D12::Internal heap->FreeIndicesCount++; } - D3D12DescriptorHeap* AcquireSamplerHeapFromPool(NonNullPtr d3d12Driver) + D3D12DescriptorHeap* AcquireSamplerHeapFromPool(NonNullPtr d3d12Driver, DescriptorHeapCreator creator) { D3D12DescriptorHeapPool* pool = &d3d12Driver->SamplerHeapPool; uint32 count = GPUDriver::kSampler_HeapDescriptorCount; - - D3D12DescriptorHeap* result = nullptr; + if (pool->Count > 0) { - result = pool->Heaps[pool->Count - 1]; pool->Count -= 1; - } - else - { - result = CreateDescriptorHeap(d3d12Driver, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, count, false); + return pool->Heaps[pool->Count]; } - return result; + // Pool is exhausted (all heaps are in use). We need to create a new one. + // The pool array might be full of "Used" heaps, or it might have empty slots (nullptr) if we expanded before. + // We look for a free slot to store the new heap. + size_t freeSlotIndex = SIZE_MAX; + + // Scan for nullptr in the entire array. + // Optimization: active heaps are likely at the beginning, but since Count=0, "Used" heaps are 0..Capacity? + // Actually, if Count=0, ALL slots 0..Capacity-1 are effectively "Used" (unless some are nullptr). + for (size_t i = 0; i < pool->Capacity; ++i) + { + if (pool->Heaps[i] == nullptr) + { + freeSlotIndex = i; + break; + } + } + + if (freeSlotIndex == SIZE_MAX) + { + // No free slot, expand + size_t oldCapacity = pool->Capacity; + pool->Capacity = pool->Capacity == 0 ? 1 : pool->Capacity * 2; + + // Using ArenaRealloc (Template): + pool->Heaps = ArenaRealloc(GetEngineArena(), pool->Heaps, oldCapacity, pool->Capacity); + + // Initialize new slots to nullptr + for (size_t i = oldCapacity; i < pool->Capacity; ++i) + { + pool->Heaps[i] = nullptr; + } + freeSlotIndex = oldCapacity; + } + + // Create the new heap at the free slot + pool->Heaps[freeSlotIndex] = creator(d3d12Driver, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, count, false); + + // Now we have a new heap at freeSlotIndex. Ideally we want to return it. + // But to maintain the "Stack" invariant (Available are at 0..Count-1), + // and "Used" are at "Count..Capacity-1". + // Currently Count=0. So "Used" is 0..Capacity-1. + // freeSlotIndex is inside "Used" range. + // We can just return it. It is already in the "Used" partition. + // However, we want to ensure it doesn't get lost or overwritten. + // Since we didn't increment Count, it stays in "Used". + + return pool->Heaps[freeSlotIndex]; } void ReturnSamplerHeapToPool(NonNullPtr d3d12Driver, D3D12DescriptorHeap* heap) @@ -136,14 +181,35 @@ namespace Juliet::D3D12::Internal heap->CurrentDescriptorIndex = 0; - if (pool->Count >= pool->Capacity) + // The heap is currently in the "Used" partition (index >= Count). + // We want to move it to the "Available" partition (index < Count). + // Strategy: Swap heap with the element at pool->Heaps[pool->Count], then increment Count. + + // First, find the heap index. + size_t heapIndex = SIZE_MAX; + for (size_t i = pool->Count; i < pool->Capacity; ++i) { - pool->Capacity = pool->Capacity == 0 ? 1 : pool->Capacity * 2; - pool->Heaps = - static_cast(Realloc(pool->Heaps, pool->Capacity * sizeof(D3D12DescriptorHeap*))); + if (pool->Heaps[i] == heap) + { + heapIndex = i; + break; + } } - pool->Heaps[pool->Count] = heap; - pool->Count += 1; + if (heapIndex != SIZE_MAX) + { + D3D12DescriptorHeap* temp = pool->Heaps[pool->Count]; + pool->Heaps[pool->Count] = pool->Heaps[heapIndex]; + pool->Heaps[heapIndex] = temp; + pool->Count += 1; + } + else + { + // Should not happen if the logic is correct. + // Maybe it was not in the pool? (e.g. error scenario). + // In that case, we should probably add it? + // But per constraints, we assume it was allocated via pool. + Assert(false, "Returning a heap that was not found in the pool!"); + } } } // namespace Juliet::D3D12::Internal diff --git a/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.h b/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.h index c91dcac..6368cda 100644 --- a/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.h +++ b/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.h @@ -44,11 +44,13 @@ namespace Juliet::D3D12::Internal size_t Count; }; + using DescriptorHeapCreator = D3D12DescriptorHeap* (*)(NonNullPtr, D3D12_DESCRIPTOR_HEAP_TYPE, uint32, bool); + extern D3D12DescriptorHeap* CreateDescriptorHeap(NonNullPtr driver, D3D12_DESCRIPTOR_HEAP_TYPE type, uint32 count, bool isStaging); extern void DestroyDescriptorHeap(NonNullPtr heap); - extern D3D12DescriptorHeap* AcquireSamplerHeapFromPool(NonNullPtr d3d12Driver); + extern D3D12DescriptorHeap* AcquireSamplerHeapFromPool(NonNullPtr d3d12Driver, DescriptorHeapCreator creator = CreateDescriptorHeap); extern void ReturnSamplerHeapToPool(NonNullPtr d3d12Driver, D3D12DescriptorHeap* heap); extern bool AssignDescriptor(D3D12DescriptorHeap* heap, D3D12Descriptor& outDescriptor); diff --git a/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp b/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp index 4e287c2..89e7c9a 100644 --- a/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp +++ b/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp @@ -3,12 +3,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -488,7 +490,7 @@ namespace Juliet::D3D12 heapPool.Heaps[i] = nullptr; } } - SafeFree(heapPool.Heaps); + // SafeFree(heapPool.Heaps); // Allocated with Arena, do not free. } }; DestroyDescriptorHeapPool(driver->SamplerHeapPool); @@ -643,8 +645,13 @@ namespace Juliet::D3D12 Free(device.Get()); } + GraphicsDevice* CreateGraphicsDevice(bool enableDebug) { +#if JULIET_DEBUG + // Unit Tests for D3D12 Logic + UnitTest::TestDescriptorHeapPool(); +#endif auto driver = static_cast(Calloc(1, sizeof(D3D12Driver))); #if JULIET_DEBUG @@ -929,8 +936,7 @@ namespace Juliet::D3D12 { heapPool.Capacity = 4; heapPool.Count = 4; - heapPool.Heaps = static_cast( - Calloc(heapPool.Capacity, sizeof(Internal::D3D12DescriptorHeap*))); + heapPool.Heaps = ArenaPushArray(GetEngineArena(), heapPool.Capacity); for (uint32 i = 0; i < heapPool.Capacity; ++i) { diff --git a/Juliet/src/Graphics/D3D12/D3D12InternalTests.cpp b/Juliet/src/Graphics/D3D12/D3D12InternalTests.cpp new file mode 100644 index 0000000..c631273 --- /dev/null +++ b/Juliet/src/Graphics/D3D12/D3D12InternalTests.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +#if JULIET_DEBUG + +namespace Juliet::D3D12::UnitTest +{ + using namespace Juliet::D3D12; + using namespace Juliet::D3D12::Internal; + + static D3D12DescriptorHeap* MockCreateDescriptorHeap(NonNullPtr, D3D12_DESCRIPTOR_HEAP_TYPE type, uint32, bool) + { + D3D12DescriptorHeap* heap = static_cast(Calloc(1, sizeof(D3D12DescriptorHeap))); + if (heap) + { + heap->HeapType = type; + heap->MaxDescriptors = 128; + // Mock other fields as needed to pass verification + } + return heap; + } + + void TestDescriptorHeapPool() + { + D3D12Driver driver = {}; + ZeroStruct(driver); + // Ensure pool is empty + driver.SamplerHeapPool.Count = 0; + driver.SamplerHeapPool.Capacity = 0; + driver.SamplerHeapPool.Heaps = nullptr; + + NonNullPtr driverPtr(&driver); + + // 1. Acquire from empty -> Should create new + D3D12DescriptorHeap* heap1 = AcquireSamplerHeapFromPool(driverPtr, MockCreateDescriptorHeap); + Assert(heap1 != nullptr); + Assert(driver.SamplerHeapPool.Count == 0); // Used heaps are above Count + Assert(driver.SamplerHeapPool.Capacity >= 1); + Assert(driver.SamplerHeapPool.Heaps[0] == heap1); + + // 2. Return -> Should become available + ReturnSamplerHeapToPool(driverPtr, heap1); + Assert(driver.SamplerHeapPool.Count == 1); // One available + Assert(driver.SamplerHeapPool.Heaps[0] == heap1); // Available is at index 0 + + // 3. Acquire again -> Should reuse + D3D12DescriptorHeap* heap2 = AcquireSamplerHeapFromPool(driverPtr, MockCreateDescriptorHeap); + Assert(heap2 == heap1); // Reused + Assert(driver.SamplerHeapPool.Count == 0); // No available left + + // 4. Acquire another -> Should Expand and Create + D3D12DescriptorHeap* heap3 = AcquireSamplerHeapFromPool(driverPtr, MockCreateDescriptorHeap); + Assert(heap3 != nullptr); + Assert(heap3 != heap1); + Assert(driver.SamplerHeapPool.Count == 0); + Assert(driver.SamplerHeapPool.Capacity >= 2); + + // Cleanup + (void)heap1; (void)heap2; (void)heap3; + Free(heap1); + Free(heap3); + + // 5. Test Multiple Allocations (8 heaps) + // Reset pool for clean test or continue? Let's reset to be sure of state, + // effectively leaking previous pool array but that's fine for mock test. + // Actually, let's just continue using the pool, it has Capacity >= 2. + + // Return everything first to have clean slate if possible, but we already freed heap1 and heap3 mentally/physically. + // The pool thinks heap3 is "Used" (outside Count). + // We freed it physically, but pool has a dangling pointer in Heaps[1]. + // This is dangerous if we reuse it. + // So we MUST correct the pool state or reset it. + // Let's reset the pool state for the 8-loop test. + driver.SamplerHeapPool.Count = 0; + driver.SamplerHeapPool.Capacity = 0; + driver.SamplerHeapPool.Heaps = nullptr; // Leaks previous array + + const int kNumHeaps = 32; + D3D12DescriptorHeap* heaps[kNumHeaps]; + + // 5.1 Acquire 8 + for (int i = 0; i < kNumHeaps; ++i) + { + heaps[i] = AcquireSamplerHeapFromPool(driverPtr, MockCreateDescriptorHeap); + Assert(heaps[i] != nullptr); + // Verify uniqueness + for (int j = 0; j < i; ++j) + { + Assert(heaps[i] != heaps[j]); + } + } + Assert(driver.SamplerHeapPool.Capacity >= kNumHeaps); + Assert(driver.SamplerHeapPool.Count == 0); + + // 5.2 Release 8 + for (int i = 0; i < kNumHeaps; ++i) + { + ReturnSamplerHeapToPool(driverPtr, heaps[i]); + } + Assert(driver.SamplerHeapPool.Count == kNumHeaps); + + // 5.3 Acquire 8 Again (Should Reuse) + D3D12DescriptorHeap* heapsAgain[kNumHeaps]; + for (int i = 0; i < kNumHeaps; ++i) + { + heapsAgain[i] = AcquireSamplerHeapFromPool(driverPtr, MockCreateDescriptorHeap); + + // Verify it matches one of the original heaps (order might reverse due to stack-like behavior) + bool found = false; + for (int j = 0; j < kNumHeaps; ++j) + { + if (heapsAgain[i] == heaps[j]) + { + found = true; + break; + } + } + Assert(found); + // Verify uniqueness in the new set + for (int j = 0; j < i; ++j) + { + Assert(heapsAgain[i] != heapsAgain[j]); + } + } + Assert(driver.SamplerHeapPool.Count == 0); + // Capacity should not have increased if we reused + Assert(driver.SamplerHeapPool.Capacity == kNumHeaps); // Or >= 8 + + // Cleanup + for (int i = 0; i < kNumHeaps; ++i) + { + Free(heaps[i]); + } + + // Note: heap2 is heap1. + // Pool array in driver leaked for test scope, acceptable. + } +} +#endif diff --git a/Juliet/src/Graphics/D3D12/D3D12InternalTests.h b/Juliet/src/Graphics/D3D12/D3D12InternalTests.h new file mode 100644 index 0000000..7c19eca --- /dev/null +++ b/Juliet/src/Graphics/D3D12/D3D12InternalTests.h @@ -0,0 +1,9 @@ +#pragma once +#include + +#if JULIET_DEBUG +namespace Juliet::D3D12::UnitTest +{ + void TestDescriptorHeapPool(); +} +#endif diff --git a/JulietApp/JulietApp.vcxproj b/JulietApp/JulietApp.vcxproj index 55bebce..26ca4ad 100644 --- a/JulietApp/JulietApp.vcxproj +++ b/JulietApp/JulietApp.vcxproj @@ -315,7 +315,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -325,7 +325,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -335,7 +335,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -345,7 +345,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;DEBUG;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -355,7 +355,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;PROFILING_ENABLED;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\ @@ -365,7 +365,7 @@ cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache $(ProjectName)-$(Configuration) cd $(SolutionDir) & misc\fbuild -ide -dist -monitor -cache -clean $(ProjectName)-$(Configuration) - _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_WIN32; + _CRT_SECURE_NO_WARNINGS;_UNICODE;UNICODE;WIN32_LEAN_AND_MEAN;WIN32;_WIN32;__WINDOWS__;_HAS_EXCEPTIONS=0;WIN64;RELEASE;JULIET_EXPORT;JULIET_WIN32; ..\;..\Juliet\include;..\Game;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\ucrt;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared; /std:c++20 /wd5267 /wd4061 /wd4505 /wd4514 /wd4577 /wd4625 /wd4710 /wd4711 /wd4746 /wd4820 /wd5045 /wd5220 /wd5245 $(SolutionDir)\bin\$(Configuration)\