Cleaned up memory arena debugger + tag.

WIP
This commit is contained in:
2026-01-21 20:36:38 -05:00
parent a41a5e6b20
commit c10d371836
21 changed files with 169 additions and 215 deletions

View File

@@ -9,7 +9,7 @@
#include <type_traits> #include <type_traits>
// Add any new fields into the concept below // Add any new fields into the concept below
#define DECLARE_ENTITY() \ #define DECLARE_ENTITY() \
Entity* Base; \ Entity* Base; \
static const Juliet::Class* Kind; static const Juliet::Class* Kind;
@@ -18,8 +18,6 @@
constexpr Juliet::Class entityKind##entity(#entity, sizeof(#entity) / sizeof(char)); \ constexpr Juliet::Class entityKind##entity(#entity, sizeof(#entity) / sizeof(char)); \
const Juliet::Class* entity::Kind = &entityKind##entity; const Juliet::Class* entity::Kind = &entityKind##entity;
namespace Game namespace Game
{ {
using DerivedType = void*; using DerivedType = void*;
@@ -49,9 +47,9 @@ namespace Game
requires EntityConcept<EntityType> requires EntityConcept<EntityType>
EntityType* MakeEntity(EntityManager& manager, float x, float y) EntityType* MakeEntity(EntityManager& manager, float x, float y)
{ {
auto* arena = Juliet::GetGameArena(); auto* arena = Juliet::GetGameArena();
EntityType* result = Juliet::ArenaPushType<EntityType>(arena); EntityType* result = Juliet::ArenaPushType<EntityType>(arena, ConstString("EntityType"));
Entity* base = result->Base = Juliet::ArenaPushType<Entity>(arena); Entity* base = result->Base = Juliet::ArenaPushType<Entity>(arena, ConstString("Entity"));
base->X = x; base->X = x;
base->Y = y; base->Y = y;
base->Derived = result; base->Derived = result;

View File

@@ -40,7 +40,7 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
int Score; int Score;
}; };
auto* gameState = ArenaPushType<GameState>(GetGameArena()); auto* gameState = ArenaPushType<GameState>(GetGameArena(), ConstString("GameState"));
gameState->TotalTime = 0.0f; gameState->TotalTime = 0.0f;
gameState->Score = 0; gameState->Score = 0;

View File

@@ -18,10 +18,10 @@ namespace Juliet
JULIET_API void NewFrame(); JULIET_API void NewFrame();
JULIET_API void Render(); JULIET_API void Render();
JULIET_API bool IsInitialized(); JULIET_API bool IsInitialized();
JULIET_API ImGuiContext* GetContext(); JULIET_API ImGuiContext* GetContext();
// Run internal unit tests // Run internal unit tests
JULIET_API void RunTests(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window); JULIET_API void RunTests();
} } // namespace ImGuiService
} } // namespace Juliet

View File

@@ -8,5 +8,5 @@
namespace Juliet::UnitTest namespace Juliet::UnitTest
{ {
void TestImGui(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window); void TestImGui();
} }

View File

@@ -1,9 +1,10 @@
#pragma once #pragma once
#include <Juliet.h>
#include <Core/Common/CoreTypes.h> #include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h> #include <Core/Common/CoreUtils.h>
#include <Core/Common/String.h>
#include <Core/Memory/Utils.h> #include <Core/Memory/Utils.h>
#include <Juliet.h>
namespace Juliet namespace Juliet
{ {
@@ -12,12 +13,13 @@ namespace Juliet
uint8* Data; uint8* Data;
size_t Size; size_t Size;
size_t Offset; size_t Offset;
#if JULIET_DEBUG #if JULIET_DEBUG
struct AllocationInfo struct AllocationInfo
{ {
size_t Offset; size_t Offset;
size_t Size; size_t Size;
const char* Tag; String Tag;
}; };
// Use a simple array for now to avoid std::vector dependency in the header or complex management // Use a simple array for now to avoid std::vector dependency in the header or complex management
// Ideally this should be a linked list or similar // Ideally this should be a linked list or similar
@@ -25,17 +27,14 @@ namespace Juliet
AllocationInfo Allocations[kMaxAllocations]; AllocationInfo Allocations[kMaxAllocations];
size_t AllocationCount = 0; size_t AllocationCount = 0;
#endif #endif
}; };
JULIET_API void MemoryArenaCreate(MemoryArena* arena, void* backingMemory, size_t size); JULIET_API void MemoryArenaCreate(MemoryArena* arena, void* backingMemory, size_t size);
JULIET_API void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment = 16, const char* tag = nullptr); JULIET_API void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, String tag);
JULIET_API void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, String tag);
JULIET_API void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment = 16, const char* tag = nullptr); JULIET_API void ArenaReset(MemoryArena* arena);
JULIET_API void ArenaReset(MemoryArena* arena);
JULIET_API size_t ArenaGetMarker(MemoryArena* arena); JULIET_API size_t ArenaGetMarker(MemoryArena* arena);
JULIET_API void ArenaResetToMarker(MemoryArena* arena, size_t marker); JULIET_API void ArenaResetToMarker(MemoryArena* arena, size_t marker);
// --- Global Arenas & Management --- // --- Global Arenas & Management ---
@@ -50,12 +49,12 @@ namespace Juliet
// Internal engine function to initialize memory arenas. // Internal engine function to initialize memory arenas.
void MemoryArenasInit(); void MemoryArenasInit();
// Internal engine function to shutdown memory arenas. // Internal engine function to shutdown memory arenas.
void MemoryArenasShutdown(); void MemoryArenasShutdown();
template <typename T> template <typename T>
inline T* ArenaPushType(MemoryArena* arena, const char* tag = nullptr) inline T* ArenaPushType(MemoryArena* arena, String tag)
{ {
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T), alignof(T), tag)); T* result = static_cast<T*>(ArenaPush(arena, sizeof(T), alignof(T), tag));
@@ -67,7 +66,7 @@ namespace Juliet
} }
template <typename T> template <typename T>
inline T* ArenaPushArray(MemoryArena* arena, size_t count, const char* tag = nullptr) inline T* ArenaPushArray(MemoryArena* arena, size_t count, String tag)
{ {
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T) * count, alignof(T), tag)); T* result = static_cast<T*>(ArenaPush(arena, sizeof(T) * count, alignof(T), tag));
@@ -79,8 +78,9 @@ namespace Juliet
} }
template <typename T> template <typename T>
inline T* ArenaRealloc(MemoryArena* arena, T* oldPtr, size_t oldCount, size_t newCount) inline T* ArenaRealloc(MemoryArena* arena, T* oldPtr, size_t oldCount, size_t newCount, String tag)
{ {
return static_cast<T*>(Juliet::ArenaRealloc(arena, static_cast<void*>(oldPtr), sizeof(T) * oldCount, sizeof(T) * newCount, alignof(T))); return static_cast<T*>(Juliet::ArenaRealloc(arena, static_cast<void*>(oldPtr), sizeof(T) * oldCount,
sizeof(T) * newCount, alignof(T), tag));
} }
} // namespace Juliet } // namespace Juliet

View File

@@ -1,13 +1,9 @@
#pragma once #pragma once
#include <Juliet.h>
#include <Core/Memory/MemoryArena.h>
namespace Juliet #include <Core/Memory/MemoryArena.h>
#include <Juliet.h>
namespace Juliet::Debug
{ {
class JULIET_API MemoryDebugger JULIET_API void DebugDrawMemoryArena();
{ } // namespace Juliet::Debug
public:
static void DrawMemoryArena(const char* name, const MemoryArena& arena);
static void DrawGlobalArenas();
};
}

View File

@@ -2,8 +2,8 @@
#include <Core/HAL/Display/Display_Private.h> #include <Core/HAL/Display/Display_Private.h>
#include <Core/HAL/Display/DisplayDevice.h> #include <Core/HAL/Display/DisplayDevice.h>
#include <Core/Memory/Allocator.h> #include <Core/Memory/Allocator.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Memory/EngineArena.h> #include <Core/Memory/EngineArena.h>
#include <Core/Memory/MemoryArena.h>
#include <format> #include <format>
namespace Juliet namespace Juliet
@@ -17,7 +17,7 @@ namespace Juliet
{ {
// TODO : IfDef new factories that are not compatible // TODO : IfDef new factories that are not compatible
constexpr DisplayDeviceFactory* Factories[] = { &Win32DisplayDeviceFactory, nullptr }; constexpr DisplayDeviceFactory* Factories[] = { &Win32DisplayDeviceFactory, nullptr };
} // namespace } // namespace Internal::Display
void InitializeDisplaySystem() void InitializeDisplaySystem()
{ {
@@ -74,8 +74,8 @@ namespace Juliet
{ {
Assert(g_CurrentDisplayDevice->CreatePlatformWindow); Assert(g_CurrentDisplayDevice->CreatePlatformWindow);
MemoryArena* arena = GetEngineArena(); MemoryArena* arena = GetEngineArena();
auto window = ArenaPushType<Window>(arena); auto window = ArenaPushType<Window>(arena, ConstString("Window"));
if (!window) if (!window)
{ {
return nullptr; return nullptr;
@@ -84,7 +84,7 @@ namespace Juliet
window->Height = height; window->Height = height;
auto titleLen = StringLength(title); auto titleLen = StringLength(title);
auto buffer = ArenaPushArray<char>(arena, titleLen); auto buffer = ArenaPushArray<char>(arena, titleLen, ConstString("Window Title Array"));
MemCopy(buffer, title, titleLen); MemCopy(buffer, title, titleLen);
window->Title.Data = buffer; window->Title.Data = buffer;
@@ -93,7 +93,7 @@ namespace Juliet
g_CurrentDisplayDevice->MainWindow = window; g_CurrentDisplayDevice->MainWindow = window;
if (!g_CurrentDisplayDevice->CreatePlatformWindow(g_CurrentDisplayDevice, window)) if (!g_CurrentDisplayDevice->CreatePlatformWindow(g_CurrentDisplayDevice, window))
{ {
// Note: We don't "free" from arena easily, but since this is catastrophic // Note: We don't "free" from arena easily, but since this is catastrophic
// and persistent, we just leak the small amount of arena space or handle it if we had a marker. // and persistent, we just leak the small amount of arena space or handle it if we had a marker.
return nullptr; return nullptr;
} }

View File

@@ -18,8 +18,8 @@ namespace Juliet::Win32
DisplayDevice* CreateDevice() DisplayDevice* CreateDevice()
{ {
auto device = ArenaPushType<DisplayDevice>(GetEngineArena()); auto device = ArenaPushType<DisplayDevice>(GetEngineArena(), ConstString("DisplayDevice"));
if (!device) if (!device)
{ {
return nullptr; return nullptr;

View File

@@ -14,7 +14,7 @@ namespace Juliet::Win32
bool SetupWindowState(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window, HWND handle) bool SetupWindowState(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window, HWND handle)
{ {
auto state = ArenaPushType<Window32State>(GetEngineArena()); auto state = ArenaPushType<Window32State>(GetEngineArena(), ConstString("Window32State"));
window->State = state; window->State = state;
state->Handle = handle; state->Handle = handle;

View File

@@ -22,8 +22,9 @@ namespace Juliet
// First allocate all the full path. // First allocate all the full path.
// TODO: Add path composition into filesystem + string format + string builder // TODO: Add path composition into filesystem + string format + string builder
const size_t dllFullPathLength = basePathLength + StringLength(dllName) + 1; // Need +1 because snprintf needs 0 terminated strings const size_t dllFullPathLength =
code.DLLFullPath.Data = ArenaPushArray<char>(GetEngineArena(), dllFullPathLength); basePathLength + StringLength(dllName) + 1; // Need +1 because snprintf needs 0 terminated strings
code.DLLFullPath.Data = ArenaPushArray<char>(GetEngineArena(), dllFullPathLength, ConstString("DLLFullPath"));
int writtenSize = snprintf(CStr(code.DLLFullPath), dllFullPathLength, "%s%s", CStr(basePath), CStr(dllName)); int writtenSize = snprintf(CStr(code.DLLFullPath), dllFullPathLength, "%s%s", CStr(basePath), CStr(dllName));
if (writtenSize < static_cast<int>(dllFullPathLength) - 1) if (writtenSize < static_cast<int>(dllFullPathLength) - 1)
{ {
@@ -34,8 +35,9 @@ namespace Juliet
code.DLLFullPath.Size = static_cast<size_t>(writtenSize); code.DLLFullPath.Size = static_cast<size_t>(writtenSize);
// Lock filename path // Lock filename path
const size_t lockPathLength = basePathLength + StringLength(lockFilename) + 1; // Need +1 because snprintf needs 0 terminated strings const size_t lockPathLength =
code.LockFullPath.Data = ArenaPushArray<char>(GetEngineArena(), lockPathLength); basePathLength + StringLength(lockFilename) + 1; // Need +1 because snprintf needs 0 terminated strings
code.LockFullPath.Data = ArenaPushArray<char>(GetEngineArena(), lockPathLength, ConstString("LockFullPath"));
writtenSize = snprintf(CStr(code.LockFullPath), lockPathLength, "%s%s", CStr(basePath), CStr(lockFilename)); writtenSize = snprintf(CStr(code.LockFullPath), lockPathLength, "%s%s", CStr(basePath), CStr(lockFilename));
if (writtenSize < static_cast<int>(lockPathLength) - 1) if (writtenSize < static_cast<int>(lockPathLength) - 1)
{ {

View File

@@ -54,10 +54,10 @@ namespace Juliet
const size_t tempDllMaxBufferSize = const size_t tempDllMaxBufferSize =
basePathLength + StringLength(code.TransientDLLName) + /* _ */ 1 + kTempDLLBufferSizeForID + 1 /* \0 */; basePathLength + StringLength(code.TransientDLLName) + /* _ */ 1 + kTempDLLBufferSizeForID + 1 /* \0 */;
// Allocate from Scratch Arena (transient) // Allocate from Scratch Arena (transient)
auto tempDllPath = ArenaPushArray<char>(GetScratchArena(), tempDllMaxBufferSize); auto tempDllPath = ArenaPushArray<char>(GetScratchArena(), tempDllMaxBufferSize, ConstString("tempDllPath"));
for (uint32 attempt = 0; attempt < kMaxAttempts; ++attempt) for (uint32 attempt = 0; attempt < kMaxAttempts; ++attempt)
{ {
// int to char // int to char
@@ -79,7 +79,8 @@ namespace Juliet
return; return;
} }
if (static_cast<size_t>(writtenSize) + 1 < basePathLength + static_cast<size_t>(idLength) + code.TransientDLLName.Size) if (static_cast<size_t>(writtenSize) + 1 <
basePathLength + static_cast<size_t>(idLength) + code.TransientDLLName.Size)
{ {
// Scratch memory, no free needed // Scratch memory, no free needed
Log(LogLevel::Error, LogCategory::Core, "Cannot create temp full path"); Log(LogLevel::Error, LogCategory::Core, "Cannot create temp full path");

View File

@@ -1,16 +1,15 @@
#include <cstdio>
#include <Core/HAL/Display/Win32/Win32Window.h> #include <Core/HAL/Display/Win32/Win32Window.h>
#include <Core/HAL/Display/Window.h> #include <Core/HAL/Display/Window.h>
#include <Core/ImGui/ImGuiService.h> #include <Core/ImGui/ImGuiService.h>
#include <Core/ImGui/ImGuiTests.h> #include <Core/ImGui/ImGuiTests.h>
#include <Core/Logging/LogManager.h> #include <Core/Logging/LogManager.h>
#include <Core/Memory/EngineArena.h>
#include <Core/Memory/MemoryArena.h> #include <Core/Memory/MemoryArena.h>
#include <Graphics/D3D12/D3D12Includes.h>
#include <backends/imgui_impl_win32.h>
#include <imgui.h> #include <imgui.h>
#include <backends/imgui_impl_dx12.h> #include <cstdio>
#include <backends/imgui_impl_win32.h>
// Forward declare implementation functions from backends // Forward declare implementation functions from backends
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
@@ -24,7 +23,7 @@ namespace Juliet::ImGuiService
void* ImGuiAllocWrapper(size_t size, void* /*user_data*/) void* ImGuiAllocWrapper(size_t size, void* /*user_data*/)
{ {
return ArenaPush(GetGameArena(), size, 16, "ImGui"); return ArenaPush(GetEngineArena(), size, 16, ConstString("ImGui"));
} }
void ImGuiFreeWrapper(void* /*ptr*/, void* /*user_data*/) void ImGuiFreeWrapper(void* /*ptr*/, void* /*user_data*/)
@@ -35,10 +34,7 @@ namespace Juliet::ImGuiService
void Initialize(NonNullPtr<Window> window) void Initialize(NonNullPtr<Window> window)
{ {
if (g_Initialized) Assert(!g_Initialized);
{
return;
}
// Setup Allocator // Setup Allocator
ImGui::SetAllocatorFunctions(ImGuiAllocWrapper, ImGuiFreeWrapper, nullptr); ImGui::SetAllocatorFunctions(ImGuiAllocWrapper, ImGuiFreeWrapper, nullptr);
@@ -46,7 +42,6 @@ namespace Juliet::ImGuiService
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
g_ImGuiContext = ImGui::CreateContext(); g_ImGuiContext = ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
(void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
@@ -56,17 +51,12 @@ namespace Juliet::ImGuiService
auto* win32State = static_cast<Win32::Window32State*>(window->State); auto* win32State = static_cast<Win32::Window32State*>(window->State);
ImGui_ImplWin32_Init(win32State->Handle); ImGui_ImplWin32_Init(win32State->Handle);
// Renderer Init is done later or here?
// We need the ID3D12Device, which is in GraphicsDevice.
// We should probably split Init.
// For now, let's assume we do Renderer Init in GraphicsDevice.
g_Initialized = true; g_Initialized = true;
} }
void Shutdown() void Shutdown()
{ {
if (!g_Initialized) return; Assert(g_Initialized);
ImGui_ImplWin32_Shutdown(); ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext(g_ImGuiContext); ImGui::DestroyContext(g_ImGuiContext);
@@ -76,7 +66,7 @@ namespace Juliet::ImGuiService
void NewFrame() void NewFrame()
{ {
if (!g_Initialized) return; Assert(g_Initialized);
ImGui_ImplWin32_NewFrame(); ImGui_ImplWin32_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
@@ -84,7 +74,7 @@ namespace Juliet::ImGuiService
void Render() void Render()
{ {
if (!g_Initialized) return; Assert(g_Initialized);
ImGui::Render(); ImGui::Render();
} }
@@ -99,24 +89,9 @@ namespace Juliet::ImGuiService
return g_ImGuiContext; return g_ImGuiContext;
} }
void RunTests(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window) void RunTests()
{ {
printf("ImGuiService: Running Unit Tests...\n"); printf("ImGuiService: Running Unit Tests...\n");
Juliet::UnitTest::TestImGui(device, window); Juliet::UnitTest::TestImGui();
// Also run internal Dear ImGui validation
if (g_ImGuiContext)
{
// Verify version and data layout (Basic internal check)
bool result = ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx));
if (result)
{
printf("ImGuiService: DebugCheckVersionAndDataLayout Passed.\n");
}
else
{
printf("ImGuiService: DebugCheckVersionAndDataLayout FAILED!\n");
}
}
} }
} // namespace Juliet::ImGuiService } // namespace Juliet::ImGuiService

View File

@@ -1,29 +1,15 @@
#include <Core/HAL/Display/Display.h> #include <Core/HAL/Display/Display.h>
#include <Core/ImGui/ImGuiService.h> #include <Core/ImGui/ImGuiService.h>
#include <Core/Memory/MemoryArena.h> #include <Core/Memory/MemoryArena.h>
#include <cstdio>
#include <Graphics/Graphics.h> #include <Graphics/Graphics.h>
#include <imgui.h> #include <imgui.h>
#include <Juliet.h> #include <Juliet.h>
#include <cstdio>
namespace Juliet::UnitTest namespace Juliet::UnitTest
{ {
// Mocking window creation is hard because it needs real OS calls. void TestImGui()
// We will assume the test runner has created a window or we create a headless one?
// Win32Window requires RegisterClass etc.
// Let's rely on the fact that if we run this test in "App" mode it works,
// but in CI headless it might fail if we don't handle it.
// For now, let's skip the Platform Init part if we can't create a window,
// or try to create a dummy window.
void TestImGui(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)
{ {
(void)device;
// 1. Verify Allocator Hook
// Initialize (Idempotent safe)
ImGuiService::Initialize(window);
ImGuiContext* ctx = ImGuiService::GetContext(); ImGuiContext* ctx = ImGuiService::GetContext();
if (ImGui::GetCurrentContext() != ctx) if (ImGui::GetCurrentContext() != ctx)
@@ -35,31 +21,28 @@ namespace Juliet::UnitTest
(void)ctx; (void)ctx;
printf("TestImGui: Context Verified.\n"); printf("TestImGui: Context Verified.\n");
// 3. Verify IO
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
Assert(io.BackendPlatformName != nullptr); Assert(io.BackendPlatformName != nullptr);
printf("TestImGui: IO Verified. Backend: %s\n", io.BackendPlatformName); printf("TestImGui: IO Verified. Backend: %s\n", io.BackendPlatformName);
// 4. Verify Version
Assert(ImGui::GetVersion() != nullptr); Assert(ImGui::GetVersion() != nullptr);
printf("TestImGui: Version Verified: %s\n", ImGui::GetVersion()); printf("TestImGui: Version Verified: %s\n", ImGui::GetVersion());
// 5. Verify Fonts
Assert(io.Fonts != nullptr); Assert(io.Fonts != nullptr);
printf("TestImGui: Fonts Verified.\n"); printf("TestImGui: Fonts Verified.\n");
bool built = io.Fonts->IsBuilt(); bool built = io.Fonts->IsBuilt();
Assert(built);
printf("TestImGui: Fonts Built Status: %d\n", built); printf("TestImGui: Fonts Built Status: %d\n", built);
// Assert(io.Fonts->IsBuilt() == false); // Disabled as Renderer might have built it
unsigned char* pixels; unsigned char* pixels;
int width, height; int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
Assert(pixels != nullptr); Assert(pixels != nullptr);
Assert(width > 0 && height > 0); Assert(width > 0 && height > 0);
Assert(io.Fonts->IsBuilt() == true); (void)pixels;
(void)pixels; (void)width; (void)height; (void)width;
(void)height;
printf("TestImGui: Font Atlas Verified.\n"); printf("TestImGui: Font Atlas Verified.\n");
// 6. Verify Style // 6. Verify Style
@@ -70,14 +53,15 @@ namespace Juliet::UnitTest
// 7. Test New Frame // 7. Test New Frame
Assert(ImGuiService::IsInitialized()); Assert(ImGuiService::IsInitialized());
// Simulate a frame // Simulate a frame
if (io.DisplaySize.x <= 0.0f || io.DisplaySize.y <= 0.0f) { if (io.DisplaySize.x <= 0.0f || io.DisplaySize.y <= 0.0f)
io.DisplaySize = ImVec2(1920, 1080); {
io.DisplaySize = ImVec2(1920, 1080);
} }
io.DeltaTime = 1.0f / 60.0f; io.DeltaTime = 1.0f / 60.0f;
printf("TestImGui: About to DrawList check.\n"); printf("TestImGui: About to DrawList check.\n");
// 8. Check Draw List Access // 8. Check Draw List Access
ImDrawList* drawList = ImGui::GetForegroundDrawList(); ImDrawList* drawList = ImGui::GetForegroundDrawList();
Assert(drawList != nullptr); Assert(drawList != nullptr);
@@ -85,5 +69,4 @@ namespace Juliet::UnitTest
printf("ImGui tests passed (Exhaustive).\n"); printf("ImGui tests passed (Exhaustive).\n");
} }
} // namespace Juliet::UnitTest } // namespace Juliet::UnitTest

View File

@@ -23,9 +23,8 @@ namespace Juliet
#endif #endif
} }
void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, const char* tag) void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, [[maybe_unused]] String tag)
{ {
(void)tag; // TODO: Use tag for allocation tracking in Debug builds
Assert(arena); Assert(arena);
// Alignment must be power of 2 // Alignment must be power of 2
@@ -47,7 +46,8 @@ namespace Juliet
#if JULIET_DEBUG #if JULIET_DEBUG
if (arena->AllocationCount < MemoryArena::kMaxAllocations) if (arena->AllocationCount < MemoryArena::kMaxAllocations)
{ {
arena->Allocations[arena->AllocationCount] = { offset - reinterpret_cast<size_t>(arena->Data), size, tag ? tag : "Unknown" }; arena->Allocations[arena->AllocationCount] = { offset - reinterpret_cast<size_t>(arena->Data), size,
IsValid(tag) ? tag : WrapString("Unknown") };
arena->AllocationCount++; arena->AllocationCount++;
} }
#endif #endif
@@ -55,8 +55,7 @@ namespace Juliet
return result; return result;
} }
void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, const char* tag) void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, String tag)
{ {
Assert(arena); Assert(arena);
// Alignment must be power of 2 // Alignment must be power of 2
@@ -119,7 +118,7 @@ namespace Juliet
if (newPtr) if (newPtr)
{ {
size_t copySize = oldSize < newSize ? oldSize : newSize; size_t copySize = oldSize < newSize ? oldSize : newSize;
std::memcpy(newPtr, oldPtr, copySize); MemCopy(newPtr, oldPtr, copySize);
} }
return newPtr; return newPtr;
} }
@@ -161,7 +160,6 @@ namespace Juliet
} }
// --- Global Arenas & Management --- // --- Global Arenas & Management ---
namespace namespace
{ {
MemoryArena g_ScratchArena; MemoryArena g_ScratchArena;

View File

@@ -1,6 +1,6 @@
#include <cstdio>
#include <Core/Common/CoreUtils.h> #include <Core/Common/CoreUtils.h>
#include <Core/Memory/MemoryArena.h> #include <Core/Memory/MemoryArena.h>
#include <cstdio>
#if JULIET_DEBUG #if JULIET_DEBUG
@@ -16,12 +16,12 @@ namespace Juliet::UnitTest
Assert(arena.Offset == 0); Assert(arena.Offset == 0);
Assert(arena.Size == 1024); Assert(arena.Size == 1024);
void* p1 = ArenaPush(&arena, 100); void* p1 = ArenaPush(&arena, 100, 16, ConstString("Test"));
Assert(p1 != nullptr); Assert(p1 != nullptr);
Assert(arena.Offset >= 100); Assert(arena.Offset >= 100);
size_t marker = ArenaGetMarker(&arena); size_t marker = ArenaGetMarker(&arena);
void* p2 = ArenaPush(&arena, 200); void* p2 = ArenaPush(&arena, 200, 16, ConstString("Test"));
Assert(p2 != nullptr); Assert(p2 != nullptr);
Assert(arena.Offset >= marker + 200); Assert(arena.Offset >= marker + 200);
@@ -32,9 +32,9 @@ namespace Juliet::UnitTest
Assert(arena.Offset == 0); Assert(arena.Offset == 0);
// 2. Alignment Test // 2. Alignment Test
void* p3 = ArenaPush(&arena, 1, 1); void* p3 = ArenaPush(&arena, 1, 1, ConstString("Test"));
[[maybe_unused]] size_t addr = reinterpret_cast<size_t>(p3); [[maybe_unused]] size_t addr = reinterpret_cast<size_t>(p3);
void* p4 = ArenaPush(&arena, 1, 16); void* p4 = ArenaPush(&arena, 1, 16, ConstString("Test"));
size_t addr2 = reinterpret_cast<size_t>(p4); size_t addr2 = reinterpret_cast<size_t>(p4);
Assert((addr2 % 16) == 0); Assert((addr2 % 16) == 0);
@@ -44,18 +44,18 @@ namespace Juliet::UnitTest
int a; int a;
float b; float b;
}; };
TestData* data = ArenaPushType<TestData>(&arena); TestData* data = ArenaPushType<TestData>(&arena, ConstString("Test"));
Assert(data != nullptr); Assert(data != nullptr);
data->a = 10; data->a = 10;
data->b = 20.0f; data->b = 20.0f;
TestData* dataArray = ArenaPushArray<TestData>(&arena, 10); TestData* dataArray = ArenaPushArray<TestData>(&arena, 10, ConstString("Test"));
Assert(dataArray != nullptr); Assert(dataArray != nullptr);
// 4. Scratch Arena // 4. Scratch Arena
MemoryArena* scratch = GetScratchArena(); MemoryArena* scratch = GetScratchArena();
Assert(scratch != nullptr); Assert(scratch != nullptr);
void* sp = ArenaPush(scratch, 100); void* sp = ArenaPush(scratch, 100, 16, ConstString("Test"));
Assert(sp != nullptr); Assert(sp != nullptr);
ScratchArenaReset(); ScratchArenaReset();
Assert(scratch->Offset == 0); Assert(scratch->Offset == 0);

View File

@@ -1,68 +1,76 @@
#include <Core/Common/String.h>
#include <Core/Memory/EngineArena.h>
#include <cstdio>
#include <Engine/Debug/MemoryDebugger.h> #include <Engine/Debug/MemoryDebugger.h>
#include <imgui.h> #include <imgui.h>
#include <cstdio>
namespace Juliet namespace Juliet::Debug
{ {
void MemoryDebugger::DrawMemoryArena(const char* name, const MemoryArena& arena) namespace
{ {
if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) void DrawMemoryArena(String name, const MemoryArena& arena)
{ {
float progress = 0.0f; if (ImGui::CollapsingHeader(CStr(name), ImGuiTreeNodeFlags_DefaultOpen))
if (arena.Size > 0)
{ {
progress = (float)arena.Offset / (float)arena.Size; float progress = 0.0f;
} if (arena.Size > 0)
{
progress = (float)arena.Offset / (float)arena.Size;
}
char overlay[64]; char overlay[64];
sprintf_s(overlay, "%zu / %zu bytes", arena.Offset, arena.Size); sprintf_s(overlay, "%zu / %zu bytes", arena.Offset, arena.Size);
ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f), overlay); ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f), overlay);
#if JULIET_DEBUG #if JULIET_DEBUG
if (ImGui::TreeNode("Allocations")) ImGui::PushID(CStr(name));
{ if (ImGui::TreeNode("Allocations"))
size_t displayedSize = 0;
// Draw allocations as a list for now
if (ImGui::BeginTable("AllocationsTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
{ {
ImGui::TableSetupColumn("Tag"); size_t displayedSize = 0;
ImGui::TableSetupColumn("Size"); // Draw allocations as a list for now
ImGui::TableSetupColumn("Offset"); if (ImGui::BeginTable("AllocationsTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
ImGui::TableHeadersRow();
for (size_t i = 0; i < arena.AllocationCount; ++i)
{ {
const auto& alloc = arena.Allocations[i]; ImGui::TableSetupColumn("Tag");
ImGui::TableNextRow(); ImGui::TableSetupColumn("Size");
ImGui::TableSetColumnIndex(0); ImGui::TableSetupColumn("Offset");
ImGui::Text("%s", alloc.Tag); ImGui::TableHeadersRow();
ImGui::TableSetColumnIndex(1);
ImGui::Text("%zu", alloc.Size); for (size_t i = 0; i < arena.AllocationCount; ++i)
ImGui::TableSetColumnIndex(2); {
ImGui::Text("%zu", alloc.Offset); const auto& alloc = arena.Allocations[i];
ImGui::TableNextRow();
displayedSize += alloc.Size; ImGui::TableSetColumnIndex(0);
ImGui::Text("%s", CStr(alloc.Tag));
ImGui::TableSetColumnIndex(1);
ImGui::Text("%zu", alloc.Size);
ImGui::TableSetColumnIndex(2);
ImGui::Text("%zu", alloc.Offset);
displayedSize += alloc.Size;
}
ImGui::EndTable();
} }
ImGui::EndTable(); ImGui::Text("Total Tracked Size: %zu", displayedSize);
ImGui::Text("Untracked/Padding: %zu", arena.Offset - displayedSize);
ImGui::TreePop();
} }
ImGui::Text("Total Tracked Size: %zu", displayedSize); ImGui::PopID();
ImGui::Text("Untracked/Padding: %zu", arena.Offset - displayedSize);
ImGui::TreePop();
}
#else #else
ImGui::Text("Detailed allocation tracking disabled in Release build."); ImGui::Text("Detailed allocation tracking disabled in Release build.");
#endif #endif
}
} }
} } // namespace
void MemoryDebugger::DrawGlobalArenas() void DebugDrawMemoryArena()
{ {
if (ImGui::Begin("Memory Debugger")) if (ImGui::Begin("Memory Debugger"))
{ {
DrawMemoryArena("Scratch Arena", *GetScratchArena()); DrawMemoryArena(ConstString("Game Arena"), *GetGameArena());
DrawMemoryArena("Game Arena", *GetGameArena()); DrawMemoryArena(ConstString("Engine Arena"), *GetEngineArena());
DrawMemoryArena(ConstString("Scratch Arena"), *GetScratchArena());
} }
ImGui::End(); ImGui::End();
} }
} } // namespace Juliet::Debug

View File

@@ -1,8 +1,8 @@
#include <Core/Logging/LogManager.h>
#include <Engine/Engine.h>
#include <Core/Common/CoreUtils.h> #include <Core/Common/CoreUtils.h>
#include <Core/JulietInit.h> #include <Core/JulietInit.h>
#include <Core/Logging/LogManager.h>
#include <Core/Memory/MemoryArena.h> #include <Core/Memory/MemoryArena.h>
#include <Engine/Engine.h>
#include <Graphics/DebugDisplay.h> #include <Graphics/DebugDisplay.h>
#include <Graphics/Graphics.h> #include <Graphics/Graphics.h>
#include <Graphics/RenderPass.h> #include <Graphics/RenderPass.h>
@@ -24,7 +24,7 @@ namespace Juliet
void InitializeDependentSystems() void InitializeDependentSystems()
{ {
GraphicsDevice* device = EngineInstance.Application->GetGraphicsDevice(); GraphicsDevice* device = EngineInstance.Application->GetGraphicsDevice();
// DebugDisplay system // DebugDisplay system
if (device) if (device)
{ {
@@ -32,9 +32,7 @@ namespace Juliet
} }
#ifdef JULIET_ENABLE_IMGUI #ifdef JULIET_ENABLE_IMGUI
Window* window = EngineInstance.Application->GetPlatformWindow(); if (NonNullPtr window = EngineInstance.Application->GetPlatformWindow())
if (window)
{ {
ImGuiService::Initialize(window); ImGuiService::Initialize(window);
ImGui::SetCurrentContext(ImGuiService::GetContext()); ImGui::SetCurrentContext(ImGuiService::GetContext());
@@ -44,7 +42,7 @@ namespace Juliet
ImGuiRenderer_Initialize(device); ImGuiRenderer_Initialize(device);
// Run Unit Tests automatically // Run Unit Tests automatically
ImGuiService::RunTests(device, window); ImGuiService::RunTests();
} }
} }
#endif #endif

View File

@@ -10,7 +10,7 @@ namespace Juliet::D3D12::Internal
{ {
ID3D12DescriptorHeap* handle; ID3D12DescriptorHeap* handle;
auto heap = static_cast<D3D12DescriptorHeap*>(Calloc(1, sizeof(D3D12DescriptorHeap))); auto heap = ArenaPushType<D3D12DescriptorHeap>(GetEngineArena(), ConstString("D3D12DescriptorHeap"));
if (!heap) if (!heap)
{ {
return nullptr; return nullptr;
@@ -19,7 +19,8 @@ namespace Juliet::D3D12::Internal
heap->CurrentDescriptorIndex = 0; heap->CurrentDescriptorIndex = 0;
heap->FreeIndicesCapacity = 16; heap->FreeIndicesCapacity = 16;
heap->FreeIndicesCount = 0; heap->FreeIndicesCount = 0;
heap->FreeIndices = static_cast<uint32*>(Calloc(heap->FreeIndicesCapacity, sizeof(uint32))); heap->FreeIndices = ArenaPushArray<uint32>(GetEngineArena(), heap->FreeIndicesCapacity,
ConstString("D3D12DescriptorHeap/FreeIndices"));
D3D12_DESCRIPTOR_HEAP_DESC heapDesc; D3D12_DESCRIPTOR_HEAP_DESC heapDesc;
heapDesc.NumDescriptors = count; heapDesc.NumDescriptors = count;
@@ -27,8 +28,7 @@ namespace Juliet::D3D12::Internal
heapDesc.Flags = isStaging ? D3D12_DESCRIPTOR_HEAP_FLAG_NONE : D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; heapDesc.Flags = isStaging ? D3D12_DESCRIPTOR_HEAP_FLAG_NONE : D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
heapDesc.NodeMask = 0; heapDesc.NodeMask = 0;
HRESULT result = HRESULT result = driver->D3D12Device->CreateDescriptorHeap(&heapDesc, IID_ID3D12DescriptorHeap, (void**)&handle);
driver->D3D12Device->CreateDescriptorHeap(&heapDesc, IID_ID3D12DescriptorHeap, (void**)&handle);
if (FAILED(result)) if (FAILED(result))
{ {
LogError(driver->D3D12Device, "Failed to create descriptor heap!", result); LogError(driver->D3D12Device, "Failed to create descriptor heap!", result);
@@ -36,11 +36,11 @@ namespace Juliet::D3D12::Internal
return nullptr; return nullptr;
} }
heap->Handle = handle; heap->Handle = handle;
heap->HeapType = type; heap->HeapType = type;
heap->MaxDescriptors = count; heap->MaxDescriptors = count;
heap->Staging = isStaging; heap->Staging = isStaging;
heap->DescriptorSize = driver->D3D12Device->GetDescriptorHandleIncrementSize(type); heap->DescriptorSize = driver->D3D12Device->GetDescriptorHandleIncrementSize(type);
heap->DescriptorHeapCPUStart = handle->GetCPUDescriptorHandleForHeapStart(); heap->DescriptorHeapCPUStart = handle->GetCPUDescriptorHandleForHeapStart();
if (!isStaging) if (!isStaging)
{ {
@@ -143,8 +143,8 @@ namespace Juliet::D3D12::Internal
size_t oldCapacity = pool->Capacity; size_t oldCapacity = pool->Capacity;
pool->Capacity = pool->Capacity == 0 ? 1 : pool->Capacity * 2; pool->Capacity = pool->Capacity == 0 ? 1 : pool->Capacity * 2;
// Using ArenaRealloc (Template): pool->Heaps = ArenaRealloc<D3D12DescriptorHeap*>(GetEngineArena(), pool->Heaps, oldCapacity, pool->Capacity,
pool->Heaps = ArenaRealloc<D3D12DescriptorHeap*>(GetEngineArena(), pool->Heaps, oldCapacity, pool->Capacity); ConstString("DescriptorHeapArray"));
// Initialize new slots to nullptr // Initialize new slots to nullptr
for (size_t i = oldCapacity; i < pool->Capacity; ++i) for (size_t i = oldCapacity; i < pool->Capacity; ++i)

View File

@@ -1035,7 +1035,8 @@ namespace Juliet::D3D12
{ {
heapPool.Capacity = 4; heapPool.Capacity = 4;
heapPool.Count = 4; heapPool.Count = 4;
heapPool.Heaps = ArenaPushArray<Internal::D3D12DescriptorHeap*>(GetEngineArena(), heapPool.Capacity); heapPool.Heaps = ArenaPushArray<Internal::D3D12DescriptorHeap*>(GetEngineArena(), heapPool.Capacity,
ConstString("DescriptorHeap"));
for (uint32 i = 0; i < heapPool.Capacity; ++i) for (uint32 i = 0; i < heapPool.Capacity; ++i)
{ {

View File

@@ -5,10 +5,10 @@
#include <Core/Logging/LogManager.h> #include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h> #include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/MemoryArena.h> #include <Core/Memory/MemoryArena.h>
#include <Graphics/GraphicsPipeline.h> #include <Graphics/GraphicsPipeline.h>
#include <backends/imgui_impl_win32.h>
#include <imgui.h> #include <imgui.h>
namespace Juliet namespace Juliet

View File

@@ -267,10 +267,6 @@ void JulietApplication::Update()
} }
} }
if (evt.Type == EventType::Key_Down)
{
}
// Shader hot reload using keyboard. // Shader hot reload using keyboard.
if (!reloadShadersDebounce && ((GetKeyModState() & KeyMod::Alt) != KeyMod::None) && IsKeyDown(ScanCode::R)) if (!reloadShadersDebounce && ((GetKeyModState() & KeyMod::Alt) != KeyMod::None) && IsKeyDown(ScanCode::R))
{ {
@@ -355,6 +351,11 @@ void JulietApplication::Update()
Running = false; Running = false;
} }
} }
if (ShowMemoryDebugger)
{
Debug::DebugDrawMemoryArena();
}
} }
void JulietApplication::OnPreRender(CommandList* cmd) void JulietApplication::OnPreRender(CommandList* cmd)
@@ -383,8 +384,6 @@ void JulietApplication::OnPreRender(CommandList* cmd)
CopyBuffer(cmd, ConstantBuffer, TransferBuffer, 256); CopyBuffer(cmd, ConstantBuffer, TransferBuffer, 256);
TransitionBufferToReadable(cmd, ConstantBuffer); TransitionBufferToReadable(cmd, ConstantBuffer);
} }
} }
void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd) void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd)
@@ -403,11 +402,6 @@ void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd)
SetPushConstants(cmd, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData); SetPushConstants(cmd, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData);
DrawPrimitives(pass, 6, 1, 0, 0); DrawPrimitives(pass, 6, 1, 0, 0);
if (ShowMemoryDebugger)
{
MemoryDebugger::DrawGlobalArenas();
}
} }
ColorTargetInfo JulietApplication::GetColorTargetInfo(Texture* swapchainTexture) ColorTargetInfo JulietApplication::GetColorTargetInfo(Texture* swapchainTexture)