From c138fe98ce41a7ee9f2d208f6e38c1ed2f2aad0e Mon Sep 17 00:00:00 2001 From: Patedam Date: Sat, 14 Feb 2026 16:47:51 -0500 Subject: [PATCH] Updated the memory viewer to have better naming, tooltip, zoom, etc. Made by claude opus --- Juliet/include/Core/Container/Vector.h | 4 +- Juliet/include/Core/Memory/MemoryArena.h | 4 +- Juliet/src/Core/Common/String.cpp | 2 +- Juliet/src/Core/HAL/Display/Display.cpp | 4 +- .../HAL/Display/Win32/Win32DisplayDevice.cpp | 2 +- Juliet/src/Core/HotReload/HotReload.cpp | 6 +- Juliet/src/Core/ImGui/ImGuiService.cpp | 2 +- Juliet/src/Core/Logging/LogManager.cpp | 2 +- Juliet/src/Core/Memory/MemoryArenaTests.cpp | 6 +- Juliet/src/Engine/Debug/MemoryDebugger.cpp | 437 ++++++++++++++++-- Juliet/src/Graphics/D3D12/D3D12Common.cpp | 2 +- .../Graphics/D3D12/D3D12DescriptorHeap.cpp | 4 +- .../Graphics/D3D12/D3D12GraphicsDevice.cpp | 2 +- .../src/UnitTest/Container/VectorUnitTest.cpp | 15 +- 14 files changed, 420 insertions(+), 72 deletions(-) diff --git a/Juliet/include/Core/Container/Vector.h b/Juliet/include/Core/Container/Vector.h index fecf6f4..8508559 100644 --- a/Juliet/include/Core/Container/Vector.h +++ b/Juliet/include/Core/Container/Vector.h @@ -9,7 +9,7 @@ namespace Juliet template struct VectorArena { - void Create() + void Create(JULIET_DEBUG_ONLY(const char* name = "VectorArena")) { Assert(!Arena); static_assert(AllowRealloc == false); @@ -17,7 +17,7 @@ namespace Juliet DataFirst = DataLast = nullptr; Count = 0; ArenaParams params{ .AllowRealloc = AllowRealloc JULIET_DEBUG_ONLY(, .CanReserveMore = false) }; - Arena = ArenaAllocate(params); + Arena = ArenaAllocate(params JULIET_DEBUG_ONLY(, name)); InternalArena = true; Reserve(ReserveSize); diff --git a/Juliet/include/Core/Memory/MemoryArena.h b/Juliet/include/Core/Memory/MemoryArena.h index 606f2a2..77f47b8 100644 --- a/Juliet/include/Core/Memory/MemoryArena.h +++ b/Juliet/include/Core/Memory/MemoryArena.h @@ -65,8 +65,8 @@ namespace Juliet JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;) }; - [[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params = {} - JULIET_DEBUG_ONLY(, const char* name = "Unnamed Arena"), + [[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params + JULIET_DEBUG_ONLY(, const char* name), const std::source_location& loc = std::source_location::current()); void ArenaRelease(NonNullPtr arena); diff --git a/Juliet/src/Core/Common/String.cpp b/Juliet/src/Core/Common/String.cpp index 1df1983..e20391d 100644 --- a/Juliet/src/Core/Common/String.cpp +++ b/Juliet/src/Core/Common/String.cpp @@ -493,7 +493,7 @@ namespace Juliet { String result; result.Size = str.Size; - result.Data = ArenaPushArray(arena, str.Size + 1); + result.Data = static_cast(ArenaPush(arena, str.Size + 1, alignof(char), true JULIET_DEBUG_ONLY(, "String"))); MemCopy(result.Data, str.Data, str.Size); result.Data[result.Size] = 0; return result; diff --git a/Juliet/src/Core/HAL/Display/Display.cpp b/Juliet/src/Core/HAL/Display/Display.cpp index db66949..2142fc2 100644 --- a/Juliet/src/Core/HAL/Display/Display.cpp +++ b/Juliet/src/Core/HAL/Display/Display.cpp @@ -23,7 +23,7 @@ namespace Juliet { Assert(!g_CurrentDisplayDevice); - Arena* arena = ArenaAllocate(); + Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Display System")); DisplayDevice* candidateDevice = nullptr; DisplayDeviceFactory* candidateFactory = nullptr; @@ -80,7 +80,7 @@ namespace Juliet Assert(g_CurrentDisplayDevice->CreatePlatformWindow); Window window = {}; - window.Arena = ArenaAllocate(); + window.Arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Window")); window.Width = width; window.Height = height; diff --git a/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp b/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp index 241ea09..29a3ffa 100644 --- a/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp +++ b/Juliet/src/Core/HAL/Display/Win32/Win32DisplayDevice.cpp @@ -36,7 +36,7 @@ namespace Juliet::Win32 device->PumpEvents = PumpEvents; - device->Windows.Create(); + device->Windows.Create(JULIET_DEBUG_ONLY("Display Windows")); return device; } diff --git a/Juliet/src/Core/HotReload/HotReload.cpp b/Juliet/src/Core/HotReload/HotReload.cpp index 9d41a68..8e24d89 100644 --- a/Juliet/src/Core/HotReload/HotReload.cpp +++ b/Juliet/src/Core/HotReload/HotReload.cpp @@ -11,7 +11,7 @@ namespace Juliet { void InitHotReloadCode(HotReloadCode& code, String dllName, String transientDllName, String lockFilename) { - code.Arena = ArenaAllocate(); + code.Arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Hot Reload")); // Get the app base path and build the dll path from there. String basePath = GetBasePath(); @@ -25,7 +25,7 @@ namespace Juliet const size_t dllFullPathLength = basePathLength + StringLength(dllName) + 1; // Need +1 because snprintf needs 0 terminated strings - code.DLLFullPath.Data = ArenaPushArray(code.Arena, dllFullPathLength); + code.DLLFullPath.Data = static_cast(ArenaPush(code.Arena, dllFullPathLength, alignof(char), true JULIET_DEBUG_ONLY(, "DLL Path"))); int writtenSize = snprintf(CStr(code.DLLFullPath), dllFullPathLength, "%s%s", CStr(basePath), CStr(dllName)); if (writtenSize < static_cast(dllFullPathLength) - 1) { @@ -38,7 +38,7 @@ namespace Juliet // Lock filename path const size_t lockPathLength = basePathLength + StringLength(lockFilename) + 1; // Need +1 because snprintf needs 0 terminated strings - code.LockFullPath.Data = ArenaPushArray(code.Arena, lockPathLength); + code.LockFullPath.Data = static_cast(ArenaPush(code.Arena, lockPathLength, alignof(char), true JULIET_DEBUG_ONLY(, "Lock File Path"))); writtenSize = snprintf(CStr(code.LockFullPath), lockPathLength, "%s%s", CStr(basePath), CStr(lockFilename)); if (writtenSize < static_cast(lockPathLength) - 1) { diff --git a/Juliet/src/Core/ImGui/ImGuiService.cpp b/Juliet/src/Core/ImGui/ImGuiService.cpp index 9bc478c..0629010 100644 --- a/Juliet/src/Core/ImGui/ImGuiService.cpp +++ b/Juliet/src/Core/ImGui/ImGuiService.cpp @@ -41,7 +41,7 @@ namespace Juliet::ImGuiService Assert(!g_Initialized); // Initialize ImGui Arena using Engine Pool - g_ImGuiArena = ArenaAllocate(); + g_ImGuiArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "ImGui")); // Setup Allocator ImGui::SetAllocatorFunctions(ImGuiAllocWrapper, ImGuiFreeWrapper, nullptr); diff --git a/Juliet/src/Core/Logging/LogManager.cpp b/Juliet/src/Core/Logging/LogManager.cpp index 7a021b2..7645ba5 100644 --- a/Juliet/src/Core/Logging/LogManager.cpp +++ b/Juliet/src/Core/Logging/LogManager.cpp @@ -48,7 +48,7 @@ namespace Juliet Logs* LogAllocate() { - Arena* arena = ArenaAllocate(); + Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Log Manager")); Logs* logs = ArenaPushStruct(arena); logs->Arena = arena; return logs; diff --git a/Juliet/src/Core/Memory/MemoryArenaTests.cpp b/Juliet/src/Core/Memory/MemoryArenaTests.cpp index d40abd2..73811f3 100644 --- a/Juliet/src/Core/Memory/MemoryArenaTests.cpp +++ b/Juliet/src/Core/Memory/MemoryArenaTests.cpp @@ -20,7 +20,7 @@ namespace Juliet::UnitTest // New Arena! ArenaParams param{ .ReserveSize = Megabytes(64llu), .CommitSize = Kilobytes(64llu) }; - Arena* testArena = ArenaAllocate(param); + Arena* testArena = ArenaAllocate(param JULIET_DEBUG_ONLY(, "Test Arena")); size_t pos = ArenaPos(testArena); Assert(pos == k_ArenaHeaderSize); @@ -119,7 +119,7 @@ namespace Juliet::UnitTest { // Test reallocate - Arena* arena = ArenaAllocate({ .AllowRealloc = true }); + Arena* arena = ArenaAllocate({ .AllowRealloc = true } JULIET_DEBUG_ONLY(, "Test Realloc")); char* charArray = ArenaPushArray(arena, 128); char* secondCharArray = ArenaPushArray(arena, 128); char* thirdCharArray = ArenaPushArray(arena, 128); @@ -138,7 +138,7 @@ namespace Juliet::UnitTest { // Test Reallocate Shrink (Buffer Overflow Bug Check) - Arena* arena = ArenaAllocate({ .AllowRealloc = true }); + Arena* arena = ArenaAllocate({ .AllowRealloc = true } JULIET_DEBUG_ONLY(, "Test Shrink")); size_t largeSize = 100; char* large = ArenaPushArray(arena, largeSize); diff --git a/Juliet/src/Engine/Debug/MemoryDebugger.cpp b/Juliet/src/Engine/Debug/MemoryDebugger.cpp index f8c74cd..48b4c27 100644 --- a/Juliet/src/Engine/Debug/MemoryDebugger.cpp +++ b/Juliet/src/Engine/Debug/MemoryDebugger.cpp @@ -40,7 +40,7 @@ namespace Juliet::Debug { if (s_PagedArenaStates.Arena == nullptr) { - s_PagedArenaStates.Create(); + s_PagedArenaStates.Create(JULIET_DEBUG_ONLY("DebugState States")); } for (auto& entry : s_PagedArenaStates) @@ -90,12 +90,37 @@ namespace Juliet::Debug // Simple FNV-1a style hash for (size_t i = 0; i < len; ++i) { - hash = hash * 65599 + (uint8)s[i]; + hash = hash * 65599 + static_cast(s[i]); } // Use hash to pick a Hue float h = static_cast(hash % 360) / 360.0f; return ImColor::HSV(h, 0.7f, 0.8f); } + + // Generate a unique color per allocation, using tag + offset + size + // so entries with same tag name get distinct colors + uint32 GetColorForAllocation(const char* tag, size_t offset, size_t size) + { + uint32 hash = 2166136261u; + if (tag) + { + for (const char* s = tag; *s; ++s) + { + hash ^= static_cast(*s); + hash *= 16777619u; + } + } + // Mix in offset and size to differentiate same-tag entries + hash ^= static_cast(offset); + hash *= 16777619u; + hash ^= static_cast(size); + hash *= 16777619u; + + float h = static_cast(hash % 360) / 360.0f; + float s = 0.5f + static_cast((hash >> 12) % 30) / 100.0f; + float v = 0.65f + static_cast((hash >> 20) % 25) / 100.0f; + return ImColor::HSV(h, s, v); + } #endif void DrawMemoryArena(String name, const MemoryArena& arena, [[maybe_unused]] ArenaAllocation* currentHighlight, @@ -456,16 +481,14 @@ namespace Juliet::Debug const char* name = (arena->Name && arena->Name[0] != '\0') ? arena->Name : "Unnamed Arena"; PagedArenaDebugState& state = GetPagedArenaState(arena); - if (ImGui::CollapsingHeader(name, ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader(name)) { - // Collect Blocks (Pages) in order (Oldest -> Newest) - // Arena->Previous chain goes Newest -> Oldest. // Collect Blocks (Pages) in order (Oldest -> Newest) // Arena->Previous chain goes Newest -> Oldest. static VectorArena blocks; if (blocks.Arena == nullptr) { - blocks.Create(); + blocks.Create(JULIET_DEBUG_ONLY("DebugState Blocks")); } blocks.Clear(); @@ -494,7 +517,7 @@ namespace Juliet::Debug } ImGui::Text("Used: %zu / %zu bytes (%zu pages)", totalUsed, totalCapacity, blocks.Size()); - ImGui::SliderFloat("Zoom", &state.Zoom, 0.1f, 1000.0f, "%.2f"); + ImGui::SliderFloat("Zoom", &state.Zoom, 0.1f, 1000000.0f, "%.2f", ImGuiSliderFlags_Logarithmic); // --- Visual View --- ImGui::Separator(); @@ -502,29 +525,104 @@ namespace Juliet::Debug float blockHeight = 24.0f; float blockSpacing = 4.0f; - float requiredVisHeight = (float)blocks.Size() * (blockHeight + blockSpacing) + 30.0f; + float requiredVisHeight = static_cast(blocks.Size()) * (blockHeight + blockSpacing) + 30.0f; // constrains - if (requiredVisHeight > 300.0f) requiredVisHeight = 300.0f; - if (requiredVisHeight < 50.0f) requiredVisHeight = 50.0f; + if (requiredVisHeight > 300.0f) + { + requiredVisHeight = 300.0f; + } + if (requiredVisHeight < 50.0f) + { + requiredVisHeight = 50.0f; + } if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) { + ImGui::SetScrollY(0.0f); + ImDrawList* dl = ImGui::GetWindowDrawList(); float availWidth = ImGui::GetContentRegionAvail().x; ImVec2 startPos = ImGui::GetCursorScreenPos(); float virtualWidth = availWidth * state.Zoom; - if (virtualWidth < availWidth) virtualWidth = availWidth; - ImGui::Dummy(ImVec2(virtualWidth, requiredVisHeight + 1.0f)); - - // Input Handling (Simple Pan/Zoom) - bool isMapHovered = ImGui::IsWindowHovered(); - if (isMapHovered && ImGui::GetIO().MouseWheel != 0.0f) + if (virtualWidth < availWidth) { - state.Zoom += ImGui::GetIO().MouseWheel * 0.2f * state.Zoom; - if (state.Zoom < 0.1f) state.Zoom = 0.1f; - if (state.Zoom > 1000.0f) state.Zoom = 1000.0f; + virtualWidth = availWidth; + } + ImGui::Dummy(ImVec2(virtualWidth, requiredVisHeight + 1.0f)); + + bool isMapHovered = ImGui::IsWindowHovered(); + + // Zoom with pivot + if (isMapHovered) + { + float wheel = ImGui::GetIO().MouseWheel; + if (wheel != 0.0f) + { + float mouseXScreen = ImGui::GetMousePos().x; + float activeScrollX = ImGui::GetScrollX(); + float viewportLeft = ImGui::GetWindowPos().x; + float mouseRelViewport = mouseXScreen - viewportLeft; + + float virtualWidthOld = std::max(availWidth * state.Zoom, availWidth); + float mouseContentPos = activeScrollX + mouseRelViewport; + float ratio = mouseContentPos / virtualWidthOld; + + state.Zoom += wheel * 0.2f * state.Zoom; + if (state.Zoom < 0.1f) + { + state.Zoom = 0.1f; + } + if (state.Zoom > 1000000.0f) + { + state.Zoom = 1000000.0f; + } + + float virtualWidthNew = std::max(availWidth * state.Zoom, availWidth); + float desiredScrollX = (ratio * virtualWidthNew) - mouseRelViewport; + ImGui::SetScrollX(desiredScrollX); + } + } + + // Pan + if (isMapHovered && (ImGui::IsMouseDragging(ImGuiMouseButton_Left) || ImGui::IsMouseDragging(ImGuiMouseButton_Right) || + ImGui::IsMouseDragging(ImGuiMouseButton_Middle))) + { + ImGui::SetScrollX(ImGui::GetScrollX() - ImGui::GetIO().MouseDelta.x); + } + + // Jump to Selected (Sync from List) + if (state.ScrollVisualToSelected && state.SelectedAlloc) + { + for (const Arena* searchBlk : blocks) + { + ArenaDebugInfo* search = searchBlk->FirstDebugInfo; + bool found = false; + while (search) + { + if (search == state.SelectedAlloc) + { + found = true; + break; + } + search = search->Next; + } + + if (found) + { + float virtualWidthLocal = std::max(availWidth * state.Zoom, availWidth); + if (searchBlk->Reserved > 0) + { + double scale = static_cast(virtualWidthLocal) / static_cast(searchBlk->Reserved); + float xStart = static_cast(static_cast(state.SelectedAlloc->Offset) * scale); + float centerOffset = availWidth * 0.5f; + ImGui::SetScrollX(xStart - centerOffset); + } + state.ScrollVisualToSelected = false; + break; + } + } } ImVec2 pos = startPos; @@ -534,43 +632,192 @@ namespace Juliet::Debug ImVec2 rectMin = pos; ImVec2 rectMax = ImVec2(pos.x + virtualWidth, pos.y + blockHeight); - dl->AddRect(rectMin, rectMax, IM_COL32(100, 100, 100, 255)); // Block Border + dl->AddRect(rectMin, rectMax, IM_COL32(100, 100, 100, 255)); - size_t dataSize = blk->Reserved - k_ArenaHeaderSize; // Approximate + size_t dataSize = blk->Reserved - k_ArenaHeaderSize; if (dataSize > 0) { - double scale = (double)virtualWidth / (double)blk->Reserved; + double scale = static_cast(virtualWidth) / static_cast(blk->Reserved); + + // Draw Arena Header + { + float hdrWidth = static_cast(static_cast(k_ArenaHeaderSize) * scale); + hdrWidth = std::max(hdrWidth, 1.0f); + ImVec2 hMin(pos.x, pos.y + 1); + ImVec2 hMax(pos.x + hdrWidth, pos.y + blockHeight - 1); + dl->AddRectFilled(hMin, hMax, IM_COL32(40, 60, 120, 255)); + + if (hdrWidth > 30.0f) + { + const char* hdrLabel = "Header"; + ImVec2 textSize = ImGui::CalcTextSize(hdrLabel); + if (hdrWidth >= textSize.x + 4.0f) + { + float textX = hMin.x + (hdrWidth - textSize.x) * 0.5f; + float textY = hMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f; + dl->AddText(ImVec2(textX, textY), IM_COL32(180, 200, 255, 255), hdrLabel); + } + } + + if (ImGui::IsMouseHoveringRect(hMin, hMax) && ImGui::IsWindowHovered()) + { + ImGui::BeginTooltip(); + ImGui::TextColored(ImVec4(0.5f, 0.6f, 0.9f, 1.0f), "Arena Header"); + ImGui::Separator(); + ImGui::Text("Size: %zu bytes", k_ArenaHeaderSize); + ImGui::EndTooltip(); + } + } // Draw Allocations ArenaDebugInfo* info = blk->FirstDebugInfo; + size_t expectedOffset = k_ArenaHeaderSize; while (info) { + // Draw alignment padding gap if present (only for small gaps that are actual alignment) + if (info->Offset > expectedOffset) + { + size_t padSize = info->Offset - expectedOffset; + constexpr size_t k_MaxAlignmentPadding = 256; + float padXStart = pos.x + static_cast(static_cast(expectedOffset) * scale); + float padWidth = static_cast(static_cast(padSize) * scale); + + if (padSize <= k_MaxAlignmentPadding && padWidth >= 1.0f) + { + ImVec2 pMin(padXStart, pos.y + 1); + ImVec2 pMax(padXStart + padWidth, pos.y + blockHeight - 1); + + // Clip + if (pMin.x < rectMin.x) { pMin.x = rectMin.x; } + if (pMax.x > rectMax.x) { pMax.x = rectMax.x; } + + // Draw hatched pattern (diagonal lines) - clipped to visible area + float visLeft = ImGui::GetWindowPos().x; + float visRight = visLeft + ImGui::GetWindowSize().x; + float clippedLeft = std::max(pMin.x, visLeft); + float clippedRight = std::min(pMax.x, visRight); + + dl->AddRectFilled(pMin, pMax, IM_COL32(30, 30, 30, 200)); + if (clippedLeft < clippedRight) + { + float step = 6.0f; + float startX = clippedLeft - (pMax.y - pMin.y); + startX = pMin.x + step * std::floor((startX - pMin.x) / step); + if (startX < pMin.x) { startX = pMin.x; } + + int lineCount = 0; + for (float lx = startX; lx < clippedRight && lineCount < 500; lx += step, ++lineCount) + { + float ly0 = pMin.y; + float lx1 = lx + (pMax.y - pMin.y); + float ly1 = pMax.y; + if (lx1 > pMax.x) { ly1 = pMin.y + (pMax.x - lx); lx1 = pMax.x; } + dl->AddLine(ImVec2(lx, ly0), ImVec2(lx1, ly1), IM_COL32(80, 80, 80, 180)); + } + } + + // Label when zoomed + if (padWidth > 30.0f) + { + const char* padLabel = "Pad"; + ImVec2 textSize = ImGui::CalcTextSize(padLabel); + if (padWidth >= textSize.x + 4.0f) + { + float textX = pMin.x + (padWidth - textSize.x) * 0.5f; + float textY = pMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f; + dl->AddText(ImVec2(textX, textY), IM_COL32(120, 120, 120, 255), padLabel); + } + } + + if (ImGui::IsMouseHoveringRect(pMin, pMax) && ImGui::IsWindowHovered()) + { + ImGui::BeginTooltip(); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "Alignment Padding"); + ImGui::Separator(); + ImGui::Text("Size: %zu bytes", padSize); + ImGui::Text("Offset: %zu - %zu", expectedOffset, info->Offset); + ImGui::EndTooltip(); + } + } + } + expectedOffset = info->Offset + info->Size; + ImGui::PushID(info); - float xStart = pos.x + (float)((double)info->Offset * scale); - float width = (float)((double)info->Size * scale); + float xStart = pos.x + static_cast(static_cast(info->Offset) * scale); + float width = static_cast(static_cast(info->Size) * scale); width = std::max(width, 1.0f); ImVec2 aMin(xStart, pos.y + 1); ImVec2 aMax(xStart + width, pos.y + blockHeight - 1); + + // Clip to block bounds + if (aMin.x < rectMin.x) + { + aMin.x = rectMin.x; + } + if (aMax.x > rectMax.x) + { + aMax.x = rectMax.x; + } - ImU32 color = GetColorForTag(ConstString(info->Tag)); - if (state.SelectedAlloc == info) - dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE); + ImU32 color = GetColorForAllocation(info->Tag, info->Offset, info->Size); + bool isSelected = (state.SelectedAlloc == info); + + if (isSelected) + { + dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE); + dl->AddRect(aMin, aMax, IM_COL32_BLACK); + } else - dl->AddRectFilled(aMin, aMax, color); + { + dl->AddRectFilled(aMin, aMax, color); + } + + // Draw text label when zoomed enough + if (width > 20.0f && info->Tag) + { + const char* tagStr = info->Tag; + size_t tagLen = 0; + while (tagStr[tagLen] != '\0') + { + ++tagLen; + } + ImVec2 textSize = ImGui::CalcTextSize(tagStr, tagStr + tagLen); + if (width >= textSize.x + 4.0f) + { + float textX = aMin.x + (width - textSize.x) * 0.5f; + float textY = aMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f; + ImU32 textColor = IM_COL32_BLACK; + dl->AddText(ImVec2(textX, textY), textColor, tagStr, tagStr + tagLen); + } + } if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", info->Tag); - ImGui::Text("Size: %zu", info->Size); - ImGui::Text("Offset: %zu", info->Offset); - ImGui::EndTooltip(); + ImGui::BeginTooltip(); + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", info->Tag); + ImGui::Separator(); + ImGui::Text("Size: %zu bytes", info->Size); + ImGui::Text("Offset: %zu", info->Offset); + + // Hex data dump (first 16 bytes) + ImGui::Separator(); + uint8* dataPtr = reinterpret_cast(const_cast(blk)) + info->Offset; + ImGui::Text("Data: "); + for (int i = 0; i < 16 && i < static_cast(info->Size); ++i) + { + ImGui::SameLine(); + ImGui::Text("%02X", dataPtr[i]); + } + + ImGui::EndTooltip(); - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) - { - state.SelectedAlloc = info; - } + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + state.SelectedAlloc = info; + state.ScrollListToSelected = true; + state.ScrollVisualToSelected = false; + } } info = info->Next; @@ -583,11 +830,11 @@ namespace Juliet::Debug ArenaFreeNode* freeNode = blk->FreeNodes; while(freeNode) { - float xStart = pos.x + (float)((double)freeNode->Position * scale); - float width = (float)((double)freeNode->Size * scale); + float fxStart = pos.x + static_cast(static_cast(freeNode->Position) * scale); + float fWidth = static_cast(static_cast(freeNode->Size) * scale); - ImVec2 fMin(xStart, pos.y + 1); - ImVec2 fMax(xStart + width, pos.y + blockHeight - 1); + ImVec2 fMin(fxStart, pos.y + 1); + ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1); dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200)); @@ -601,6 +848,93 @@ namespace Juliet::Debug } } ImGui::EndChild(); + + // --- Tree View (Allocations List) --- + ImGui::Separator(); + if (ImGui::TreeNode("Allocations List")) + { + // Calculate item count for dynamic height + int totalAllocCount = 0; + for (const Arena* blk : blocks) + { + totalAllocCount++; + ArenaDebugInfo* countInfo = blk->FirstDebugInfo; + while (countInfo) + { + totalAllocCount++; + countInfo = countInfo->Next; + } + } + + float lineHeight = ImGui::GetTextLineHeightWithSpacing(); + float currentNeededHeight = static_cast(totalAllocCount) * lineHeight; + float maxHeight = lineHeight * 25.0f; + + float listHeight = currentNeededHeight; + if (listHeight > maxHeight) + { + listHeight = maxHeight; + } + if (listHeight < lineHeight) + { + listHeight = lineHeight; + } + + if (ImGui::BeginChild("AllocList", ImVec2(0, listHeight), true)) + { + int blkIdx = 0; + for (const Arena* blk : blocks) + { + if (ImGui::TreeNode(reinterpret_cast(blk), "Page %d (%zu bytes, pos %zu)", blkIdx, blk->Reserved, blk->Position)) + { + ArenaDebugInfo* info = blk->FirstDebugInfo; + if (!info) + { + ImGui::TextDisabled("No allocations"); + } + while (info) + { + bool isSelected = (state.SelectedAlloc == info); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + if (isSelected) + { + flags |= ImGuiTreeNodeFlags_Selected; + } + + if (isSelected && state.ScrollListToSelected) + { + ImGui::SetScrollHereY(); + state.ScrollListToSelected = false; + } + + // Color Square + ImU32 color = GetColorForAllocation(info->Tag, info->Offset, info->Size); + ImGui::ColorButton("##color", ImColor(color), + ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, + ImVec2(12, 12)); + ImGui::SameLine(); + + ImGui::TreeNodeEx(info, flags, "[%zu] %s (%zu bytes)", info->Offset, info->Tag, info->Size); + + if (ImGui::IsItemClicked()) + { + state.SelectedAlloc = info; + state.ScrollVisualToSelected = true; + state.ScrollListToSelected = false; + } + + info = info->Next; + } + ImGui::TreePop(); + } + blkIdx++; + } + } + ImGui::EndChild(); + + ImGui::TreePop(); + } } ImGui::PopID(); } @@ -623,12 +957,6 @@ namespace Juliet::Debug { if (ImGui::BeginTabBar("ArenaTabs")) { - if (ImGui::BeginTabItem("Legacy Arenas")) - { - DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered); - ImGui::EndTabItem(); - } - #if JULIET_DEBUG if (ImGui::BeginTabItem("Paged Arenas")) { @@ -641,12 +969,27 @@ namespace Juliet::Debug { for (Arena* a = head; a != nullptr; a = a->GlobalNext) { + // Skip test and internal debug arenas + if (a->Name) + { + bool isTest = (a->Name[0] == 'T' && a->Name[1] == 'e' && a->Name[2] == 's' && a->Name[3] == 't'); + bool isDebugInfo = (a->Name[0] == 'D' && a->Name[1] == 'e' && a->Name[2] == 'b' && a->Name[3] == 'u' && a->Name[4] == 'g' && a->Name[5] == ' ' && a->Name[6] == 'I'); + if (isTest || isDebugInfo) + { + continue; + } + } DrawPagedArena(a); } } ImGui::EndTabItem(); } #endif + if (ImGui::BeginTabItem("Legacy Arenas")) + { + DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } } diff --git a/Juliet/src/Graphics/D3D12/D3D12Common.cpp b/Juliet/src/Graphics/D3D12/D3D12Common.cpp index afe5656..26cab12 100644 --- a/Juliet/src/Graphics/D3D12/D3D12Common.cpp +++ b/Juliet/src/Graphics/D3D12/D3D12Common.cpp @@ -49,7 +49,7 @@ namespace Juliet::D3D12::Internal D3D12StagingDescriptorPool* CreateStagingDescriptorPool(NonNullPtr driver, D3D12_DESCRIPTOR_HEAP_TYPE type) { - Arena* arena = ArenaAllocate(); + Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Staging Descriptors")); D3D12DescriptorHeap* heap = CreateDescriptorHeap(driver, arena, type, kStagingHeapDescriptorExpectedCount, true); if (!heap) diff --git a/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp b/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp index 2cd7c4a..338fdbb 100644 --- a/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp +++ b/Juliet/src/Graphics/D3D12/D3D12DescriptorHeap.cpp @@ -12,7 +12,7 @@ namespace Juliet::D3D12::Internal { // Heap pool is just single linked list of free elements constexpr size_t kInitialCapacity = 4; - heapPool.Arena = ArenaAllocate(); + heapPool.Arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Descriptor Heap Pool")); heapPool.FirstFreeDescriptorHeap = nullptr; // Pre allocate 4 @@ -44,7 +44,7 @@ namespace Juliet::D3D12::Internal heap->CurrentDescriptorIndex = 0; - heap->FreeIndices.Create(); + heap->FreeIndices.Create(JULIET_DEBUG_ONLY("DescriptorHeap FreeIndices")); heap->FreeIndices.Resize(16); heap->CurrentFreeIndex = 0; diff --git a/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp b/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp index bbe2971..e574786 100644 --- a/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp +++ b/Juliet/src/Graphics/D3D12/D3D12GraphicsDevice.cpp @@ -731,7 +731,7 @@ namespace Juliet::D3D12 auto driver = static_cast(Calloc(1, sizeof(D3D12Driver))); // TODO : Convert everything to arena - driver->DriverArena = ArenaAllocate(); + driver->DriverArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "D3D12 Driver")); #if JULIET_DEBUG #ifdef IDXGIINFOQUEUE_SUPPORTED diff --git a/Juliet/src/UnitTest/Container/VectorUnitTest.cpp b/Juliet/src/UnitTest/Container/VectorUnitTest.cpp index 19f9736..22f7795 100644 --- a/Juliet/src/UnitTest/Container/VectorUnitTest.cpp +++ b/Juliet/src/UnitTest/Container/VectorUnitTest.cpp @@ -149,7 +149,7 @@ namespace Juliet { // Initial capacity 4, allow realloc ArenaParams params{ .AllowRealloc = true }; - Arena* externalArena = ArenaAllocate(params); + Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest")); VectorArena vec = {}; vec.Create(externalArena); @@ -174,6 +174,7 @@ namespace Juliet Assert(vec[4] == 5); vec.Destroy(); + ArenaRelease(externalArena); } // Test 6: Move Semantics @@ -208,7 +209,7 @@ namespace Juliet VectorArena vec = {}; ArenaParams params{ .AllowRealloc = true }; - Arena* externalArena = ArenaAllocate(params); + Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest")); NonNullPtr validArena(externalArena); vec.Create(validArena); @@ -221,12 +222,13 @@ namespace Juliet Assert(!vec[0].MovedFrom); vec.Destroy(); + ArenaRelease(externalArena); } // Test 7: Resize Behavior { ArenaParams params{ .AllowRealloc = true }; - Arena* externalArena = ArenaAllocate(params); + Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest")); NonNullPtr validArena(externalArena); VectorArena vec = {}; @@ -247,12 +249,13 @@ namespace Juliet Assert(vec[0] == 1); vec.Destroy(); + ArenaRelease(externalArena); } // Test 8: External Arena Usage { ArenaParams params{ .AllowRealloc = true }; - Arena* externalArena = ArenaAllocate(params); + Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest")); NonNullPtr validArena(externalArena); VectorArena vec = {}; @@ -263,12 +266,13 @@ namespace Juliet Assert(vec[0] == 42); vec.Destroy(); + ArenaRelease(externalArena); } // Test 9: Volume Test (100 items with Realloc) { ArenaParams params{ .AllowRealloc = true }; - Arena* externalArena = ArenaAllocate(params); + Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest")); NonNullPtr validArena(externalArena); VectorArena vec = {}; @@ -287,6 +291,7 @@ namespace Juliet } vec.Destroy(); + ArenaRelease(externalArena); } } } // namespace Juliet