Updated the memory viewer to have better naming, tooltip, zoom, etc.

Made by claude opus
This commit is contained in:
2026-02-14 16:47:51 -05:00
parent 16120dd865
commit c138fe98ce
14 changed files with 420 additions and 72 deletions

View File

@@ -9,7 +9,7 @@ namespace Juliet
template <typename Type, size_t ReserveSize = 16, bool AllowRealloc = false> template <typename Type, size_t ReserveSize = 16, bool AllowRealloc = false>
struct VectorArena struct VectorArena
{ {
void Create() void Create(JULIET_DEBUG_ONLY(const char* name = "VectorArena"))
{ {
Assert(!Arena); Assert(!Arena);
static_assert(AllowRealloc == false); static_assert(AllowRealloc == false);
@@ -17,7 +17,7 @@ namespace Juliet
DataFirst = DataLast = nullptr; DataFirst = DataLast = nullptr;
Count = 0; Count = 0;
ArenaParams params{ .AllowRealloc = AllowRealloc JULIET_DEBUG_ONLY(, .CanReserveMore = false) }; ArenaParams params{ .AllowRealloc = AllowRealloc JULIET_DEBUG_ONLY(, .CanReserveMore = false) };
Arena = ArenaAllocate(params); Arena = ArenaAllocate(params JULIET_DEBUG_ONLY(, name));
InternalArena = true; InternalArena = true;
Reserve(ReserveSize); Reserve(ReserveSize);

View File

@@ -65,8 +65,8 @@ namespace Juliet
JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;) JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
}; };
[[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params = {} [[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params
JULIET_DEBUG_ONLY(, const char* name = "Unnamed Arena"), JULIET_DEBUG_ONLY(, const char* name),
const std::source_location& loc = std::source_location::current()); const std::source_location& loc = std::source_location::current());
void ArenaRelease(NonNullPtr<Arena> arena); void ArenaRelease(NonNullPtr<Arena> arena);

View File

@@ -493,7 +493,7 @@ namespace Juliet
{ {
String result; String result;
result.Size = str.Size; result.Size = str.Size;
result.Data = ArenaPushArray<char>(arena, str.Size + 1); result.Data = static_cast<char*>(ArenaPush(arena, str.Size + 1, alignof(char), true JULIET_DEBUG_ONLY(, "String")));
MemCopy(result.Data, str.Data, str.Size); MemCopy(result.Data, str.Data, str.Size);
result.Data[result.Size] = 0; result.Data[result.Size] = 0;
return result; return result;

View File

@@ -23,7 +23,7 @@ namespace Juliet
{ {
Assert(!g_CurrentDisplayDevice); Assert(!g_CurrentDisplayDevice);
Arena* arena = ArenaAllocate(); Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Display System"));
DisplayDevice* candidateDevice = nullptr; DisplayDevice* candidateDevice = nullptr;
DisplayDeviceFactory* candidateFactory = nullptr; DisplayDeviceFactory* candidateFactory = nullptr;
@@ -80,7 +80,7 @@ namespace Juliet
Assert(g_CurrentDisplayDevice->CreatePlatformWindow); Assert(g_CurrentDisplayDevice->CreatePlatformWindow);
Window window = {}; Window window = {};
window.Arena = ArenaAllocate(); window.Arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Window"));
window.Width = width; window.Width = width;
window.Height = height; window.Height = height;

View File

@@ -36,7 +36,7 @@ namespace Juliet::Win32
device->PumpEvents = PumpEvents; device->PumpEvents = PumpEvents;
device->Windows.Create(); device->Windows.Create(JULIET_DEBUG_ONLY("Display Windows"));
return device; return device;
} }

View File

@@ -11,7 +11,7 @@ namespace Juliet
{ {
void InitHotReloadCode(HotReloadCode& code, String dllName, String transientDllName, String lockFilename) 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. // Get the app base path and build the dll path from there.
String basePath = GetBasePath(); String basePath = GetBasePath();
@@ -25,7 +25,7 @@ namespace Juliet
const size_t dllFullPathLength = const size_t dllFullPathLength =
basePathLength + StringLength(dllName) + 1; // Need +1 because snprintf needs 0 terminated strings basePathLength + StringLength(dllName) + 1; // Need +1 because snprintf needs 0 terminated strings
code.DLLFullPath.Data = ArenaPushArray<char>(code.Arena, dllFullPathLength); code.DLLFullPath.Data = static_cast<char*>(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)); 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)
{ {
@@ -38,7 +38,7 @@ namespace Juliet
// Lock filename path // Lock filename path
const size_t lockPathLength = const size_t lockPathLength =
basePathLength + StringLength(lockFilename) + 1; // Need +1 because snprintf needs 0 terminated strings basePathLength + StringLength(lockFilename) + 1; // Need +1 because snprintf needs 0 terminated strings
code.LockFullPath.Data = ArenaPushArray<char>(code.Arena, lockPathLength); code.LockFullPath.Data = static_cast<char*>(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)); 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

@@ -41,7 +41,7 @@ namespace Juliet::ImGuiService
Assert(!g_Initialized); Assert(!g_Initialized);
// Initialize ImGui Arena using Engine Pool // Initialize ImGui Arena using Engine Pool
g_ImGuiArena = ArenaAllocate(); g_ImGuiArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "ImGui"));
// Setup Allocator // Setup Allocator
ImGui::SetAllocatorFunctions(ImGuiAllocWrapper, ImGuiFreeWrapper, nullptr); ImGui::SetAllocatorFunctions(ImGuiAllocWrapper, ImGuiFreeWrapper, nullptr);

View File

@@ -48,7 +48,7 @@ namespace Juliet
Logs* LogAllocate() Logs* LogAllocate()
{ {
Arena* arena = ArenaAllocate(); Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Log Manager"));
Logs* logs = ArenaPushStruct<Logs>(arena); Logs* logs = ArenaPushStruct<Logs>(arena);
logs->Arena = arena; logs->Arena = arena;
return logs; return logs;

View File

@@ -20,7 +20,7 @@ namespace Juliet::UnitTest
// New Arena! // New Arena!
ArenaParams param{ .ReserveSize = Megabytes(64llu), .CommitSize = Kilobytes(64llu) }; 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); size_t pos = ArenaPos(testArena);
Assert(pos == k_ArenaHeaderSize); Assert(pos == k_ArenaHeaderSize);
@@ -119,7 +119,7 @@ namespace Juliet::UnitTest
{ {
// Test reallocate // Test reallocate
Arena* arena = ArenaAllocate({ .AllowRealloc = true }); Arena* arena = ArenaAllocate({ .AllowRealloc = true } JULIET_DEBUG_ONLY(, "Test Realloc"));
char* charArray = ArenaPushArray<char>(arena, 128); char* charArray = ArenaPushArray<char>(arena, 128);
char* secondCharArray = ArenaPushArray<char>(arena, 128); char* secondCharArray = ArenaPushArray<char>(arena, 128);
char* thirdCharArray = ArenaPushArray<char>(arena, 128); char* thirdCharArray = ArenaPushArray<char>(arena, 128);
@@ -138,7 +138,7 @@ namespace Juliet::UnitTest
{ {
// Test Reallocate Shrink (Buffer Overflow Bug Check) // 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; size_t largeSize = 100;
char* large = ArenaPushArray<char>(arena, largeSize); char* large = ArenaPushArray<char>(arena, largeSize);

View File

@@ -40,7 +40,7 @@ namespace Juliet::Debug
{ {
if (s_PagedArenaStates.Arena == nullptr) if (s_PagedArenaStates.Arena == nullptr)
{ {
s_PagedArenaStates.Create(); s_PagedArenaStates.Create(JULIET_DEBUG_ONLY("DebugState States"));
} }
for (auto& entry : s_PagedArenaStates) for (auto& entry : s_PagedArenaStates)
@@ -90,12 +90,37 @@ namespace Juliet::Debug
// Simple FNV-1a style hash // Simple FNV-1a style hash
for (size_t i = 0; i < len; ++i) for (size_t i = 0; i < len; ++i)
{ {
hash = hash * 65599 + (uint8)s[i]; hash = hash * 65599 + static_cast<uint8>(s[i]);
} }
// Use hash to pick a Hue // Use hash to pick a Hue
float h = static_cast<float>(hash % 360) / 360.0f; float h = static_cast<float>(hash % 360) / 360.0f;
return ImColor::HSV(h, 0.7f, 0.8f); 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<uint8>(*s);
hash *= 16777619u;
}
}
// Mix in offset and size to differentiate same-tag entries
hash ^= static_cast<uint32>(offset);
hash *= 16777619u;
hash ^= static_cast<uint32>(size);
hash *= 16777619u;
float h = static_cast<float>(hash % 360) / 360.0f;
float s = 0.5f + static_cast<float>((hash >> 12) % 30) / 100.0f;
float v = 0.65f + static_cast<float>((hash >> 20) % 25) / 100.0f;
return ImColor::HSV(h, s, v);
}
#endif #endif
void DrawMemoryArena(String name, const MemoryArena& arena, [[maybe_unused]] ArenaAllocation* currentHighlight, 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"; const char* name = (arena->Name && arena->Name[0] != '\0') ? arena->Name : "Unnamed Arena";
PagedArenaDebugState& state = GetPagedArenaState(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) // Collect Blocks (Pages) in order (Oldest -> Newest)
// Arena->Previous chain goes Newest -> Oldest. // Arena->Previous chain goes Newest -> Oldest.
static VectorArena<const Arena*> blocks; static VectorArena<const Arena*> blocks;
if (blocks.Arena == nullptr) if (blocks.Arena == nullptr)
{ {
blocks.Create(); blocks.Create(JULIET_DEBUG_ONLY("DebugState Blocks"));
} }
blocks.Clear(); blocks.Clear();
@@ -494,7 +517,7 @@ namespace Juliet::Debug
} }
ImGui::Text("Used: %zu / %zu bytes (%zu pages)", totalUsed, totalCapacity, blocks.Size()); 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 --- // --- Visual View ---
ImGui::Separator(); ImGui::Separator();
@@ -502,29 +525,104 @@ namespace Juliet::Debug
float blockHeight = 24.0f; float blockHeight = 24.0f;
float blockSpacing = 4.0f; float blockSpacing = 4.0f;
float requiredVisHeight = (float)blocks.Size() * (blockHeight + blockSpacing) + 30.0f; float requiredVisHeight = static_cast<float>(blocks.Size()) * (blockHeight + blockSpacing) + 30.0f;
// constrains // constrains
if (requiredVisHeight > 300.0f) requiredVisHeight = 300.0f; if (requiredVisHeight > 300.0f)
if (requiredVisHeight < 50.0f) requiredVisHeight = 50.0f; {
requiredVisHeight = 300.0f;
}
if (requiredVisHeight < 50.0f)
{
requiredVisHeight = 50.0f;
}
if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true, if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true,
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar))
{ {
ImGui::SetScrollY(0.0f);
ImDrawList* dl = ImGui::GetWindowDrawList(); ImDrawList* dl = ImGui::GetWindowDrawList();
float availWidth = ImGui::GetContentRegionAvail().x; float availWidth = ImGui::GetContentRegionAvail().x;
ImVec2 startPos = ImGui::GetCursorScreenPos(); ImVec2 startPos = ImGui::GetCursorScreenPos();
float virtualWidth = availWidth * state.Zoom; float virtualWidth = availWidth * state.Zoom;
if (virtualWidth < availWidth) virtualWidth = availWidth; if (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)
{ {
state.Zoom += ImGui::GetIO().MouseWheel * 0.2f * state.Zoom; virtualWidth = availWidth;
if (state.Zoom < 0.1f) state.Zoom = 0.1f; }
if (state.Zoom > 1000.0f) state.Zoom = 1000.0f; 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<double>(virtualWidthLocal) / static_cast<double>(searchBlk->Reserved);
float xStart = static_cast<float>(static_cast<double>(state.SelectedAlloc->Offset) * scale);
float centerOffset = availWidth * 0.5f;
ImGui::SetScrollX(xStart - centerOffset);
}
state.ScrollVisualToSelected = false;
break;
}
}
} }
ImVec2 pos = startPos; ImVec2 pos = startPos;
@@ -534,43 +632,192 @@ namespace Juliet::Debug
ImVec2 rectMin = pos; ImVec2 rectMin = pos;
ImVec2 rectMax = ImVec2(pos.x + virtualWidth, pos.y + blockHeight); 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) if (dataSize > 0)
{ {
double scale = (double)virtualWidth / (double)blk->Reserved; double scale = static_cast<double>(virtualWidth) / static_cast<double>(blk->Reserved);
// Draw Arena Header
{
float hdrWidth = static_cast<float>(static_cast<double>(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 // Draw Allocations
ArenaDebugInfo* info = blk->FirstDebugInfo; ArenaDebugInfo* info = blk->FirstDebugInfo;
size_t expectedOffset = k_ArenaHeaderSize;
while (info) 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<float>(static_cast<double>(expectedOffset) * scale);
float padWidth = static_cast<float>(static_cast<double>(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); ImGui::PushID(info);
float xStart = pos.x + (float)((double)info->Offset * scale); float xStart = pos.x + static_cast<float>(static_cast<double>(info->Offset) * scale);
float width = (float)((double)info->Size * scale); float width = static_cast<float>(static_cast<double>(info->Size) * scale);
width = std::max(width, 1.0f); width = std::max(width, 1.0f);
ImVec2 aMin(xStart, pos.y + 1); ImVec2 aMin(xStart, pos.y + 1);
ImVec2 aMax(xStart + width, pos.y + blockHeight - 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)); ImU32 color = GetColorForAllocation(info->Tag, info->Offset, info->Size);
if (state.SelectedAlloc == info) bool isSelected = (state.SelectedAlloc == info);
dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE);
if (isSelected)
{
dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE);
dl->AddRect(aMin, aMax, IM_COL32_BLACK);
}
else 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()) if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered())
{ {
ImGui::BeginTooltip(); ImGui::BeginTooltip();
ImGui::Text("%s", info->Tag); ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", info->Tag);
ImGui::Text("Size: %zu", info->Size); ImGui::Separator();
ImGui::Text("Offset: %zu", info->Offset); ImGui::Text("Size: %zu bytes", info->Size);
ImGui::EndTooltip(); ImGui::Text("Offset: %zu", info->Offset);
// Hex data dump (first 16 bytes)
ImGui::Separator();
uint8* dataPtr = reinterpret_cast<uint8*>(const_cast<Arena*>(blk)) + info->Offset;
ImGui::Text("Data: ");
for (int i = 0; i < 16 && i < static_cast<int>(info->Size); ++i)
{
ImGui::SameLine();
ImGui::Text("%02X", dataPtr[i]);
}
ImGui::EndTooltip();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{ {
state.SelectedAlloc = info; state.SelectedAlloc = info;
} state.ScrollListToSelected = true;
state.ScrollVisualToSelected = false;
}
} }
info = info->Next; info = info->Next;
@@ -583,11 +830,11 @@ namespace Juliet::Debug
ArenaFreeNode* freeNode = blk->FreeNodes; ArenaFreeNode* freeNode = blk->FreeNodes;
while(freeNode) while(freeNode)
{ {
float xStart = pos.x + (float)((double)freeNode->Position * scale); float fxStart = pos.x + static_cast<float>(static_cast<double>(freeNode->Position) * scale);
float width = (float)((double)freeNode->Size * scale); float fWidth = static_cast<float>(static_cast<double>(freeNode->Size) * scale);
ImVec2 fMin(xStart, pos.y + 1); ImVec2 fMin(fxStart, pos.y + 1);
ImVec2 fMax(xStart + width, pos.y + blockHeight - 1); ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1);
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200)); dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
@@ -601,6 +848,93 @@ namespace Juliet::Debug
} }
} }
ImGui::EndChild(); 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<float>(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<const void*>(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(); ImGui::PopID();
} }
@@ -623,12 +957,6 @@ namespace Juliet::Debug
{ {
if (ImGui::BeginTabBar("ArenaTabs")) if (ImGui::BeginTabBar("ArenaTabs"))
{ {
if (ImGui::BeginTabItem("Legacy Arenas"))
{
DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered);
ImGui::EndTabItem();
}
#if JULIET_DEBUG #if JULIET_DEBUG
if (ImGui::BeginTabItem("Paged Arenas")) if (ImGui::BeginTabItem("Paged Arenas"))
{ {
@@ -641,12 +969,27 @@ namespace Juliet::Debug
{ {
for (Arena* a = head; a != nullptr; a = a->GlobalNext) 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); DrawPagedArena(a);
} }
} }
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
#endif #endif
if (ImGui::BeginTabItem("Legacy Arenas"))
{
DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered);
ImGui::EndTabItem();
}
ImGui::EndTabBar(); ImGui::EndTabBar();
} }
} }

View File

@@ -49,7 +49,7 @@ namespace Juliet::D3D12::Internal
D3D12StagingDescriptorPool* CreateStagingDescriptorPool(NonNullPtr<D3D12Driver> driver, D3D12_DESCRIPTOR_HEAP_TYPE type) D3D12StagingDescriptorPool* CreateStagingDescriptorPool(NonNullPtr<D3D12Driver> 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); D3D12DescriptorHeap* heap = CreateDescriptorHeap(driver, arena, type, kStagingHeapDescriptorExpectedCount, true);
if (!heap) if (!heap)

View File

@@ -12,7 +12,7 @@ namespace Juliet::D3D12::Internal
{ {
// Heap pool is just single linked list of free elements // Heap pool is just single linked list of free elements
constexpr size_t kInitialCapacity = 4; constexpr size_t kInitialCapacity = 4;
heapPool.Arena = ArenaAllocate(); heapPool.Arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Descriptor Heap Pool"));
heapPool.FirstFreeDescriptorHeap = nullptr; heapPool.FirstFreeDescriptorHeap = nullptr;
// Pre allocate 4 // Pre allocate 4
@@ -44,7 +44,7 @@ namespace Juliet::D3D12::Internal
heap->CurrentDescriptorIndex = 0; heap->CurrentDescriptorIndex = 0;
heap->FreeIndices.Create(); heap->FreeIndices.Create(JULIET_DEBUG_ONLY("DescriptorHeap FreeIndices"));
heap->FreeIndices.Resize(16); heap->FreeIndices.Resize(16);
heap->CurrentFreeIndex = 0; heap->CurrentFreeIndex = 0;

View File

@@ -731,7 +731,7 @@ namespace Juliet::D3D12
auto driver = static_cast<D3D12Driver*>(Calloc(1, sizeof(D3D12Driver))); auto driver = static_cast<D3D12Driver*>(Calloc(1, sizeof(D3D12Driver)));
// TODO : Convert everything to arena // TODO : Convert everything to arena
driver->DriverArena = ArenaAllocate(); driver->DriverArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "D3D12 Driver"));
#if JULIET_DEBUG #if JULIET_DEBUG
#ifdef IDXGIINFOQUEUE_SUPPORTED #ifdef IDXGIINFOQUEUE_SUPPORTED

View File

@@ -149,7 +149,7 @@ namespace Juliet
{ {
// Initial capacity 4, allow realloc // Initial capacity 4, allow realloc
ArenaParams params{ .AllowRealloc = true }; ArenaParams params{ .AllowRealloc = true };
Arena* externalArena = ArenaAllocate(params); Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest"));
VectorArena<int, 4, true> vec = {}; VectorArena<int, 4, true> vec = {};
vec.Create(externalArena); vec.Create(externalArena);
@@ -174,6 +174,7 @@ namespace Juliet
Assert(vec[4] == 5); Assert(vec[4] == 5);
vec.Destroy(); vec.Destroy();
ArenaRelease(externalArena);
} }
// Test 6: Move Semantics // Test 6: Move Semantics
@@ -208,7 +209,7 @@ namespace Juliet
VectorArena<MoveOnly, 4, true> vec = {}; VectorArena<MoveOnly, 4, true> vec = {};
ArenaParams params{ .AllowRealloc = true }; ArenaParams params{ .AllowRealloc = true };
Arena* externalArena = ArenaAllocate(params); Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest"));
NonNullPtr<Arena> validArena(externalArena); NonNullPtr<Arena> validArena(externalArena);
vec.Create(validArena); vec.Create(validArena);
@@ -221,12 +222,13 @@ namespace Juliet
Assert(!vec[0].MovedFrom); Assert(!vec[0].MovedFrom);
vec.Destroy(); vec.Destroy();
ArenaRelease(externalArena);
} }
// Test 7: Resize Behavior // Test 7: Resize Behavior
{ {
ArenaParams params{ .AllowRealloc = true }; ArenaParams params{ .AllowRealloc = true };
Arena* externalArena = ArenaAllocate(params); Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest"));
NonNullPtr<Arena> validArena(externalArena); NonNullPtr<Arena> validArena(externalArena);
VectorArena<int, 4, true> vec = {}; VectorArena<int, 4, true> vec = {};
@@ -247,12 +249,13 @@ namespace Juliet
Assert(vec[0] == 1); Assert(vec[0] == 1);
vec.Destroy(); vec.Destroy();
ArenaRelease(externalArena);
} }
// Test 8: External Arena Usage // Test 8: External Arena Usage
{ {
ArenaParams params{ .AllowRealloc = true }; ArenaParams params{ .AllowRealloc = true };
Arena* externalArena = ArenaAllocate(params); Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest"));
NonNullPtr<Arena> validArena(externalArena); NonNullPtr<Arena> validArena(externalArena);
VectorArena<int> vec = {}; VectorArena<int> vec = {};
@@ -263,12 +266,13 @@ namespace Juliet
Assert(vec[0] == 42); Assert(vec[0] == 42);
vec.Destroy(); vec.Destroy();
ArenaRelease(externalArena);
} }
// Test 9: Volume Test (100 items with Realloc) // Test 9: Volume Test (100 items with Realloc)
{ {
ArenaParams params{ .AllowRealloc = true }; ArenaParams params{ .AllowRealloc = true };
Arena* externalArena = ArenaAllocate(params); Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest"));
NonNullPtr<Arena> validArena(externalArena); NonNullPtr<Arena> validArena(externalArena);
VectorArena<int, 4, true> vec = {}; VectorArena<int, 4, true> vec = {};
@@ -287,6 +291,7 @@ namespace Juliet
} }
vec.Destroy(); vec.Destroy();
ArenaRelease(externalArena);
} }
} }
} // namespace Juliet } // namespace Juliet