Squashed commit of the following:
commitc138fe98ceAuthor: Patedam <pgillen.pro@gmail.com> Date: Sat Feb 14 16:47:51 2026 -0500 Updated the memory viewer to have better naming, tooltip, zoom, etc. Made by claude opus commit16120dd865Author: Patedam <pgillen.pro@gmail.com> Date: Sat Feb 14 15:57:01 2026 -0500 Made memory arena debugger show the new arenas. Made by gemini
This commit is contained in:
@@ -9,7 +9,7 @@ namespace Juliet
|
||||
template <typename Type, size_t ReserveSize = 16, bool AllowRealloc = false>
|
||||
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);
|
||||
@@ -58,7 +58,8 @@ namespace Juliet
|
||||
else if (AllowRealloc && !InternalArena)
|
||||
{
|
||||
DataFirst = Data = static_cast<Type*>(ArenaReallocate(Arena, Data, Capacity * sizeof(Type),
|
||||
newCapacity * sizeof(Type), AlignOf(Type), true));
|
||||
newCapacity * sizeof(Type), AlignOf(Type), true
|
||||
JULIET_DEBUG_ONLY(, "VectorRealloc")));
|
||||
DataLast = Data + Count - 1;
|
||||
}
|
||||
Capacity = newCapacity;
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include <Core/Common/NonNullPtr.h>
|
||||
#include <Core/Common/String.h>
|
||||
#include <Core/Memory/Utils.h>
|
||||
#include <Core/Memory/Utils.h>
|
||||
#include <Juliet.h>
|
||||
#include <typeinfo> // Added for typeid
|
||||
|
||||
namespace Juliet
|
||||
{
|
||||
@@ -15,6 +17,10 @@ namespace Juliet
|
||||
|
||||
struct ArenaFreeNode;
|
||||
|
||||
#if JULIET_DEBUG
|
||||
struct ArenaDebugInfo;
|
||||
#endif
|
||||
|
||||
struct Arena
|
||||
{
|
||||
Arena* Previous;
|
||||
@@ -36,8 +42,13 @@ namespace Juliet
|
||||
|
||||
bool AllowRealloc : 1;
|
||||
|
||||
JULIET_DEBUG_ONLY(uint16 LostNodeCount);
|
||||
JULIET_DEBUG_ONLY(uint16 LostNodeCount;)
|
||||
JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)
|
||||
|
||||
JULIET_DEBUG_ONLY(Arena* GlobalNext;)
|
||||
JULIET_DEBUG_ONLY(Arena* GlobalPrev;)
|
||||
JULIET_DEBUG_ONLY(ArenaDebugInfo* FirstDebugInfo;)
|
||||
JULIET_DEBUG_ONLY(const char* Name;)
|
||||
};
|
||||
static_assert(sizeof(Arena) <= k_ArenaHeaderSize);
|
||||
|
||||
@@ -54,14 +65,17 @@ namespace Juliet
|
||||
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),
|
||||
const std::source_location& loc = std::source_location::current());
|
||||
void ArenaRelease(NonNullPtr<Arena> arena);
|
||||
|
||||
// Raw Push, can be used but templated helpers exists below
|
||||
[[nodiscard]] void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align, bool shouldBeZeroed);
|
||||
[[nodiscard]] void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
|
||||
size_t align, bool shouldBeZeroed);
|
||||
// Raw Push, can be used but templated helpers exists below
|
||||
[[nodiscard]] void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align, bool shouldBeZeroed
|
||||
JULIET_DEBUG_ONLY(, const char* tag));
|
||||
[[nodiscard]] void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
|
||||
size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
|
||||
void ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
|
||||
void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
|
||||
void ArenaClear(NonNullPtr<Arena> arena);
|
||||
@@ -70,7 +84,7 @@ namespace Juliet
|
||||
template <typename Type>
|
||||
[[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena)
|
||||
{
|
||||
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true));
|
||||
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
|
||||
}
|
||||
|
||||
// #define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (0))
|
||||
@@ -81,17 +95,11 @@ namespace Juliet
|
||||
template <typename Type>
|
||||
[[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count)
|
||||
{
|
||||
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true));
|
||||
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
|
||||
}
|
||||
|
||||
// --- Paged Memory Architecture ---
|
||||
struct ArenaAllocation
|
||||
{
|
||||
size_t Offset;
|
||||
size_t Size;
|
||||
String Tag;
|
||||
ArenaAllocation* Next;
|
||||
};
|
||||
struct ArenaAllocation;
|
||||
|
||||
struct MemoryBlock
|
||||
{
|
||||
@@ -159,9 +167,15 @@ namespace Juliet
|
||||
// Internal engine function to initialize memory arenas.
|
||||
void MemoryArenasInit();
|
||||
|
||||
// Internal engine function to shutdown memory arenas.
|
||||
// Internal engine function to shutdown memory arenas.
|
||||
void MemoryArenasShutdown();
|
||||
|
||||
#if JULIET_DEBUG
|
||||
JULIET_API Arena* GetGlobalArenaListHead();
|
||||
#endif
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline T* ArenaPushType(MemoryArena* arena, String tag)
|
||||
{
|
||||
|
||||
50
Juliet/include/Core/Memory/MemoryArenaDebug.h
Normal file
50
Juliet/include/Core/Memory/MemoryArenaDebug.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <Juliet.h>
|
||||
#include <Core/Common/CoreTypes.h>
|
||||
#include <Core/Common/String.h>
|
||||
#include <Core/Common/NonNullPtr.h>
|
||||
|
||||
#if JULIET_DEBUG
|
||||
|
||||
namespace Juliet
|
||||
{
|
||||
struct Arena;
|
||||
struct MemoryBlock;
|
||||
|
||||
// Arena (Struct)
|
||||
struct ArenaDebugInfo
|
||||
{
|
||||
const char* Tag;
|
||||
size_t Offset;
|
||||
size_t Size;
|
||||
ArenaDebugInfo* Next;
|
||||
};
|
||||
|
||||
// MemoryArena (Pool-based)
|
||||
struct ArenaAllocation
|
||||
{
|
||||
size_t Offset;
|
||||
size_t Size;
|
||||
String Tag;
|
||||
ArenaAllocation* Next;
|
||||
};
|
||||
|
||||
// Arena (Struct)
|
||||
void DebugRegisterArena(NonNullPtr<Arena> arena);
|
||||
void DebugUnregisterArena(NonNullPtr<Arena> arena);
|
||||
void DebugArenaSetDebugName(NonNullPtr<Arena> arena, const char* name);
|
||||
bool IsDebugInfoArena(const Arena* arena); // To prevent recursion
|
||||
void DebugArenaFreeBlock(Arena* block); // To clear all debug infos in a block
|
||||
void DebugArenaRemoveAllocation(Arena* block, size_t oldOffset);
|
||||
void DebugArenaPopTo(Arena* block, size_t newPosition);
|
||||
void DebugArenaAddDebugInfo(Arena* block, size_t size, size_t offset, const char* tag);
|
||||
|
||||
// MemoryArena (Pool-based)
|
||||
void DebugFreeArenaAllocations(MemoryBlock* blk);
|
||||
void DebugArenaAddAllocation(MemoryBlock* blk, size_t size, size_t offset, String tag);
|
||||
void DebugArenaRemoveLastAllocation(MemoryBlock* blk);
|
||||
|
||||
} // namespace Juliet
|
||||
|
||||
#endif
|
||||
@@ -24,7 +24,7 @@
|
||||
#define JULIET_DEBUG_ONLY(...) __VA_ARGS__
|
||||
#else
|
||||
#define JULIET_DEBUG 0
|
||||
#define JULIET_DEBUG_ONLY(expr)
|
||||
#define JULIET_DEBUG_ONLY(...)
|
||||
#endif
|
||||
|
||||
// Manual override to disable ImGui
|
||||
|
||||
@@ -493,7 +493,7 @@ namespace Juliet
|
||||
{
|
||||
String result;
|
||||
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);
|
||||
result.Data[result.Size] = 0;
|
||||
return result;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Juliet::Win32
|
||||
|
||||
device->PumpEvents = PumpEvents;
|
||||
|
||||
device->Windows.Create();
|
||||
device->Windows.Create(JULIET_DEBUG_ONLY("Display Windows"));
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
@@ -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<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));
|
||||
if (writtenSize < static_cast<int>(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<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));
|
||||
if (writtenSize < static_cast<int>(lockPathLength) - 1)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Juliet::ImGuiService
|
||||
|
||||
void* ImGuiAllocWrapper(size_t size, void* /*user_data*/)
|
||||
{
|
||||
return ArenaPush(g_ImGuiArena, size, 8, false);
|
||||
return ArenaPush(g_ImGuiArena, size, 8, false JULIET_DEBUG_ONLY(, "ImGuiAlloc"));
|
||||
}
|
||||
|
||||
void ImGuiFreeWrapper(void* /*ptr*/, void* /*user_data*/)
|
||||
@@ -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);
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Juliet
|
||||
|
||||
Logs* LogAllocate()
|
||||
{
|
||||
Arena* arena = ArenaAllocate();
|
||||
Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Log Manager"));
|
||||
Logs* logs = ArenaPushStruct<Logs>(arena);
|
||||
logs->Arena = arena;
|
||||
return logs;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Core/Logging/LogManager.h>
|
||||
#include <Core/Memory/Allocator.h>
|
||||
#include <Core/Memory/MemoryArena.h>
|
||||
#include <Core/Memory/MemoryArenaDebug.h>
|
||||
#include <Core/Memory/Utils.h>
|
||||
|
||||
#include <algorithm> // For std::max
|
||||
@@ -33,7 +34,7 @@ namespace Juliet
|
||||
|
||||
// https://github.com/EpicGamesExt/raddebugger/blob/master/src/base/base_arena.c
|
||||
|
||||
Arena* ArenaAllocate(const ArenaParams& params, const std::source_location& loc)
|
||||
Arena* ArenaAllocate(const ArenaParams& params JULIET_DEBUG_ONLY(, const char* name), const std::source_location& loc)
|
||||
{
|
||||
Log(LogLevel::Message, LogCategory::Core, "Allocating from %s : %ul", loc.file_name(), loc.line());
|
||||
|
||||
@@ -61,22 +62,34 @@ namespace Juliet
|
||||
arena->FreeNodes = nullptr;
|
||||
arena->AllowRealloc = params.AllowRealloc;
|
||||
|
||||
#if JULIET_DEBUG
|
||||
arena->CanReserveMore = params.CanReserveMore;
|
||||
arena->FirstDebugInfo = nullptr;
|
||||
|
||||
DebugArenaSetDebugName(arena, name);
|
||||
DebugRegisterArena(arena);
|
||||
#endif
|
||||
|
||||
return arena;
|
||||
}
|
||||
|
||||
void ArenaRelease(NonNullPtr<Arena> arena)
|
||||
{
|
||||
JULIET_DEBUG_ONLY(DebugUnregisterArena(arena);)
|
||||
|
||||
for (Arena *node = arena->Current, *previous = nullptr; node != nullptr; node = previous)
|
||||
{
|
||||
previous = node->Previous;
|
||||
|
||||
JULIET_DEBUG_ONLY(DebugArenaFreeBlock(node);)
|
||||
|
||||
Memory::OS_Release(node, node->Reserved);
|
||||
}
|
||||
}
|
||||
|
||||
void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align, bool shouldBeZeroed)
|
||||
void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag))
|
||||
{
|
||||
// Assert(IsPowerOfTwo(align));
|
||||
Arena* current = arena->Current;
|
||||
size_t positionPrePush = AlignPow2(current->Position, align);
|
||||
size_t positionPostPush = positionPrePush + size;
|
||||
@@ -104,7 +117,12 @@ namespace Juliet
|
||||
next->Previous = previous;
|
||||
}
|
||||
|
||||
JULIET_DEBUG_ONLY(remainingSize > 0 ? ++current->LostNodeCount : current->LostNodeCount);
|
||||
#if JULIET_DEBUG
|
||||
if (remainingSize > 0)
|
||||
{
|
||||
++current->LostNodeCount;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -158,7 +176,8 @@ namespace Juliet
|
||||
reserveSize = AlignPow2(size + k_ArenaHeaderSize, align);
|
||||
commitSize = AlignPow2(size + k_ArenaHeaderSize, align);
|
||||
}
|
||||
newBlock = ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize });
|
||||
|
||||
newBlock = ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize } JULIET_DEBUG_ONLY(, arena->Name));
|
||||
}
|
||||
|
||||
newBlock->BasePosition = current->BasePosition + current->Reserved;
|
||||
@@ -201,19 +220,22 @@ namespace Juliet
|
||||
MemoryZero(result, sizeToZero);
|
||||
}
|
||||
}
|
||||
|
||||
// If alloc failed, log and assert
|
||||
|
||||
if (result == nullptr) [[unlikely]]
|
||||
{
|
||||
Log(LogLevel::Error, LogCategory::Core, "Fatal Allocation Failure - Unexpected allocation failure");
|
||||
Assert(false, "Fatal Allocation Failure - Unexpected allocation failure");
|
||||
}
|
||||
|
||||
JULIET_DEBUG_ONLY(if (!IsDebugInfoArena(arena)) { DebugArenaAddDebugInfo(current, size, positionPrePush, tag); })
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize, size_t align, bool shouldBeZeroed)
|
||||
void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize, size_t align,
|
||||
bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag))
|
||||
{
|
||||
void* result = ArenaPush(arena, newSize, align, shouldBeZeroed);
|
||||
void* result = ArenaPush(arena, newSize, align, shouldBeZeroed JULIET_DEBUG_ONLY(, tag));
|
||||
|
||||
// Find the correct block to release
|
||||
Arena* block = nullptr;
|
||||
@@ -249,6 +271,12 @@ namespace Juliet
|
||||
block->FreeNodes->Previous = freeNode;
|
||||
}
|
||||
block->FreeNodes = freeNode;
|
||||
|
||||
#if JULIET_DEBUG
|
||||
// Remove the debug info for the old allocation (since it's now free)
|
||||
size_t oldOffset = static_cast<size_t>(static_cast<Byte*>(oldPtr) - reinterpret_cast<Byte*>(block));
|
||||
DebugArenaRemoveAllocation(block, oldOffset);
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -262,20 +290,18 @@ namespace Juliet
|
||||
{
|
||||
previous = current->Previous;
|
||||
current->Position = k_ArenaHeaderSize;
|
||||
|
||||
JULIET_DEBUG_ONLY(DebugArenaFreeBlock(current);)
|
||||
|
||||
SingleLinkedListPushPrevious(arena->FreeBlockLast, current);
|
||||
}
|
||||
|
||||
// No freelist :
|
||||
// for (Arena* previous = nullptr; current->BasePosition >= clampedPosition; current = previous)
|
||||
// {
|
||||
// previous = current->Previous;
|
||||
// Memory::OS_Release(current, current->Reserved);
|
||||
// }
|
||||
|
||||
arena->Current = current;
|
||||
size_t newPosition = clampedPosition - current->BasePosition;
|
||||
Assert(newPosition <= current->Position);
|
||||
current->Position = newPosition;
|
||||
|
||||
JULIET_DEBUG_ONLY(DebugArenaPopTo(current, newPosition);)
|
||||
}
|
||||
|
||||
void ArenaPop(NonNullPtr<Arena> arena, size_t amount)
|
||||
@@ -308,23 +334,6 @@ namespace Juliet
|
||||
|
||||
// --- MemoryPool Implementation ---
|
||||
|
||||
#if JULIET_DEBUG
|
||||
static void FreeDebugAllocations(MemoryBlock* blk)
|
||||
{
|
||||
if (!blk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ArenaAllocation* curr = blk->FirstAllocation;
|
||||
while (curr)
|
||||
{
|
||||
ArenaAllocation* next = curr->Next;
|
||||
SafeFree(curr);
|
||||
curr = next;
|
||||
}
|
||||
blk->FirstAllocation = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Simple First-Fit Allocator
|
||||
MemoryBlock* MemoryPool::AllocateBlock(size_t minCapacity)
|
||||
@@ -401,7 +410,7 @@ namespace Juliet
|
||||
|
||||
// Poison Header and Data in Debug
|
||||
#if JULIET_DEBUG
|
||||
FreeDebugAllocations(block);
|
||||
DebugFreeArenaAllocations(block);
|
||||
// 0xDD = Dead Data
|
||||
MemSet(block->GetData(), 0xDD, block->TotalSize - sizeof(MemoryBlock));
|
||||
block->Magic = 0xDEADBEEF;
|
||||
@@ -485,23 +494,7 @@ namespace Juliet
|
||||
blk->Used += alignmentOffset;
|
||||
void* ptr = blk->GetData() + blk->Used;
|
||||
|
||||
#if JULIET_DEBUG
|
||||
ArenaAllocation* node = (ArenaAllocation*)Malloc(sizeof(ArenaAllocation));
|
||||
node->Offset = blk->Used;
|
||||
node->Size = size;
|
||||
node->Tag = tag;
|
||||
node->Next = nullptr;
|
||||
|
||||
if (!blk->FirstAllocation)
|
||||
blk->FirstAllocation = node;
|
||||
else
|
||||
{
|
||||
ArenaAllocation* t = blk->FirstAllocation;
|
||||
while (t->Next)
|
||||
t = t->Next;
|
||||
t->Next = node;
|
||||
}
|
||||
#endif
|
||||
JULIET_DEBUG_ONLY(DebugArenaAddAllocation(blk, size, blk->Used, tag);)
|
||||
|
||||
blk->Used += size;
|
||||
|
||||
@@ -565,29 +558,7 @@ namespace Juliet
|
||||
{
|
||||
// Yes, we can just rewind the Used pointer
|
||||
blk->Used -= size;
|
||||
#if JULIET_DEBUG
|
||||
{
|
||||
ArenaAllocation* t = blk->FirstAllocation;
|
||||
ArenaAllocation* prev = nullptr;
|
||||
while (t && t->Next)
|
||||
{
|
||||
prev = t;
|
||||
t = t->Next;
|
||||
}
|
||||
if (t)
|
||||
{
|
||||
SafeFree(t);
|
||||
if (prev)
|
||||
{
|
||||
prev->Next = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
blk->FirstAllocation = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
JULIET_DEBUG_ONLY(DebugArenaRemoveLastAllocation(blk);)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -614,7 +585,7 @@ namespace Juliet
|
||||
|
||||
#if JULIET_DEBUG
|
||||
// Poison First Block
|
||||
FreeDebugAllocations(arena->FirstBlock);
|
||||
DebugFreeArenaAllocations(arena->FirstBlock);
|
||||
MemSet(arena->FirstBlock->GetData(), 0xCD, arena->FirstBlock->TotalSize - sizeof(MemoryBlock));
|
||||
#endif
|
||||
}
|
||||
|
||||
242
Juliet/src/Core/Memory/MemoryArenaDebug.cpp
Normal file
242
Juliet/src/Core/Memory/MemoryArenaDebug.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
#include <Core/Memory/MemoryArenaDebug.h>
|
||||
#include <Core/Memory/MemoryArena.h> // For Arena definition
|
||||
|
||||
#if JULIET_DEBUG
|
||||
|
||||
namespace Juliet
|
||||
{
|
||||
Arena* g_ArenaGlobalHead = nullptr;
|
||||
Arena* g_DebugInfoArena = nullptr;
|
||||
ArenaDebugInfo* g_DebugInfoFreeList = nullptr;
|
||||
ArenaAllocation* g_ArenaAllocationFreeList = nullptr;
|
||||
|
||||
// --- Internal Helpers ---
|
||||
static ArenaDebugInfo* AllocDebugInfo()
|
||||
{
|
||||
if (g_DebugInfoFreeList)
|
||||
{
|
||||
ArenaDebugInfo* info = g_DebugInfoFreeList;
|
||||
g_DebugInfoFreeList = info->Next;
|
||||
info->Next = nullptr;
|
||||
return info;
|
||||
}
|
||||
|
||||
if (!g_DebugInfoArena)
|
||||
{
|
||||
// Create a dedicated arena for debug info
|
||||
g_DebugInfoArena = ArenaAllocate({ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena"));
|
||||
}
|
||||
|
||||
return ArenaPushStruct<ArenaDebugInfo>(g_DebugInfoArena);
|
||||
}
|
||||
|
||||
static void FreeDebugInfo(ArenaDebugInfo* info)
|
||||
{
|
||||
if (info)
|
||||
{
|
||||
info->Next = g_DebugInfoFreeList;
|
||||
g_DebugInfoFreeList = info;
|
||||
}
|
||||
}
|
||||
|
||||
static ArenaAllocation* AllocArenaAllocation()
|
||||
{
|
||||
if (g_ArenaAllocationFreeList)
|
||||
{
|
||||
ArenaAllocation* info = g_ArenaAllocationFreeList;
|
||||
g_ArenaAllocationFreeList = info->Next;
|
||||
info->Next = nullptr;
|
||||
return info;
|
||||
}
|
||||
|
||||
if (!g_DebugInfoArena)
|
||||
{
|
||||
g_DebugInfoArena = ArenaAllocate({ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena"));
|
||||
}
|
||||
|
||||
return ArenaPushStruct<ArenaAllocation>(g_DebugInfoArena);
|
||||
}
|
||||
|
||||
static void FreeArenaAllocation(ArenaAllocation* info)
|
||||
{
|
||||
if (info)
|
||||
{
|
||||
info->Next = g_ArenaAllocationFreeList;
|
||||
g_ArenaAllocationFreeList = info;
|
||||
}
|
||||
}
|
||||
// ------------------------
|
||||
|
||||
void DebugRegisterArena(NonNullPtr<Arena> arena)
|
||||
{
|
||||
// Add to Global List
|
||||
// Note: Not thread safe, assuming single thread for now as per context
|
||||
if (g_ArenaGlobalHead)
|
||||
{
|
||||
g_ArenaGlobalHead->GlobalPrev = arena;
|
||||
}
|
||||
arena->GlobalNext = g_ArenaGlobalHead;
|
||||
arena->GlobalPrev = nullptr;
|
||||
g_ArenaGlobalHead = arena;
|
||||
}
|
||||
|
||||
void DebugUnregisterArena(NonNullPtr<Arena> arena)
|
||||
{
|
||||
// Remove from Global List
|
||||
if (arena->GlobalPrev)
|
||||
{
|
||||
arena->GlobalPrev->GlobalNext = arena->GlobalNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ArenaGlobalHead = arena->GlobalNext;
|
||||
}
|
||||
if (arena->GlobalNext)
|
||||
{
|
||||
arena->GlobalNext->GlobalPrev = arena->GlobalPrev;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugArenaSetDebugName(NonNullPtr<Arena> arena, const char* name)
|
||||
{
|
||||
arena->Name = name;
|
||||
}
|
||||
|
||||
bool IsDebugInfoArena(const Arena* arena)
|
||||
{
|
||||
return arena == g_DebugInfoArena;
|
||||
}
|
||||
|
||||
void DebugArenaFreeBlock(Arena* block)
|
||||
{
|
||||
ArenaDebugInfo* info = block->FirstDebugInfo;
|
||||
while (info)
|
||||
{
|
||||
ArenaDebugInfo* nextInfo = info->Next;
|
||||
FreeDebugInfo(info);
|
||||
info = nextInfo;
|
||||
}
|
||||
block->FirstDebugInfo = nullptr;
|
||||
}
|
||||
|
||||
void DebugArenaRemoveAllocation(Arena* block, size_t oldOffset)
|
||||
{
|
||||
ArenaDebugInfo** prevInfo = &block->FirstDebugInfo;
|
||||
ArenaDebugInfo* info = block->FirstDebugInfo;
|
||||
|
||||
while (info)
|
||||
{
|
||||
if (info->Offset == oldOffset) // Found it
|
||||
{
|
||||
*prevInfo = info->Next;
|
||||
FreeDebugInfo(info);
|
||||
break;
|
||||
}
|
||||
prevInfo = &info->Next;
|
||||
info = info->Next;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugArenaPopTo(Arena* block, size_t newPosition)
|
||||
{
|
||||
ArenaDebugInfo** prevInfo = &block->FirstDebugInfo;
|
||||
ArenaDebugInfo* info = block->FirstDebugInfo;
|
||||
while (info)
|
||||
{
|
||||
if (info->Offset >= newPosition)
|
||||
{
|
||||
ArenaDebugInfo* toFree = info;
|
||||
*prevInfo = info->Next; // Unlink
|
||||
info = info->Next; // Advance
|
||||
FreeDebugInfo(toFree);
|
||||
}
|
||||
else
|
||||
{
|
||||
prevInfo = &info->Next;
|
||||
info = info->Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebugArenaAddDebugInfo(Arena* block, size_t size, size_t offset, const char* tag)
|
||||
{
|
||||
ArenaDebugInfo* info = AllocDebugInfo();
|
||||
if (info)
|
||||
{
|
||||
info->Tag = tag ? tag : "Untagged";
|
||||
info->Size = size;
|
||||
info->Offset = offset;
|
||||
info->Next = block->FirstDebugInfo;
|
||||
block->FirstDebugInfo = info;
|
||||
}
|
||||
}
|
||||
|
||||
// Moved to top
|
||||
|
||||
void DebugFreeArenaAllocations(MemoryBlock* blk)
|
||||
{
|
||||
if (!blk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ArenaAllocation* curr = blk->FirstAllocation;
|
||||
while (curr)
|
||||
{
|
||||
ArenaAllocation* next = curr->Next;
|
||||
FreeArenaAllocation(curr);
|
||||
curr = next;
|
||||
}
|
||||
blk->FirstAllocation = nullptr;
|
||||
}
|
||||
|
||||
void DebugArenaAddAllocation(MemoryBlock* blk, size_t size, size_t offset, String tag)
|
||||
{
|
||||
ArenaAllocation* node = AllocArenaAllocation();
|
||||
node->Offset = offset;
|
||||
node->Size = size;
|
||||
node->Tag = tag;
|
||||
node->Next = nullptr;
|
||||
|
||||
if (!blk->FirstAllocation)
|
||||
blk->FirstAllocation = node;
|
||||
else
|
||||
{
|
||||
ArenaAllocation* t = blk->FirstAllocation;
|
||||
while (t->Next)
|
||||
t = t->Next;
|
||||
t->Next = node;
|
||||
}
|
||||
}
|
||||
|
||||
void DebugArenaRemoveLastAllocation(MemoryBlock* blk)
|
||||
{
|
||||
ArenaAllocation* t = blk->FirstAllocation;
|
||||
ArenaAllocation* prev = nullptr;
|
||||
while (t && t->Next)
|
||||
{
|
||||
prev = t;
|
||||
t = t->Next;
|
||||
}
|
||||
if (t)
|
||||
{
|
||||
FreeArenaAllocation(t);
|
||||
if (prev)
|
||||
{
|
||||
prev->Next = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
blk->FirstAllocation = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accessor for global list head (if needed by other files)
|
||||
Arena* GetGlobalArenaListHead()
|
||||
{
|
||||
return g_ArenaGlobalHead;
|
||||
}
|
||||
|
||||
} // namespace Juliet
|
||||
|
||||
#endif
|
||||
@@ -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,12 +119,12 @@ namespace Juliet::UnitTest
|
||||
|
||||
{
|
||||
// Test reallocate
|
||||
Arena* arena = ArenaAllocate({ .AllowRealloc = true });
|
||||
Arena* arena = ArenaAllocate({ .AllowRealloc = true } JULIET_DEBUG_ONLY(, "Test Realloc"));
|
||||
char* charArray = ArenaPushArray<char>(arena, 128);
|
||||
char* secondCharArray = ArenaPushArray<char>(arena, 128);
|
||||
char* thirdCharArray = ArenaPushArray<char>(arena, 128);
|
||||
|
||||
secondCharArray = static_cast<char*>(ArenaReallocate(arena, secondCharArray, 128, 256, alignof(char), true));
|
||||
secondCharArray = static_cast<char*>(ArenaReallocate(arena, secondCharArray, 128, 256, alignof(char), true JULIET_DEBUG_ONLY(, "ReallocChar")));
|
||||
|
||||
char* fourthCharArray = ArenaPushArray<char>(arena, 128);
|
||||
|
||||
@@ -138,14 +138,14 @@ 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<char>(arena, largeSize);
|
||||
MemSet(large, 'A', largeSize);
|
||||
|
||||
size_t smallSize = 50;
|
||||
char* smallData = static_cast<char*>(ArenaReallocate(arena, large, largeSize, smallSize, alignof(char), true));
|
||||
char* smallData = static_cast<char*>(ArenaReallocate(arena, large, largeSize, smallSize, alignof(char), true JULIET_DEBUG_ONLY(, "ResizeSmall")));
|
||||
|
||||
for (size_t i = 0; i < smallSize; ++i)
|
||||
{
|
||||
@@ -154,7 +154,7 @@ namespace Juliet::UnitTest
|
||||
|
||||
// Allocate next block (should be immediately after 'small')
|
||||
// Don't zero it, if overflow happened it will contain 'A's
|
||||
char* next = static_cast<char*>(ArenaPush(arena, 50, alignof(char), false));
|
||||
char* next = static_cast<char*>(ArenaPush(arena, 50, alignof(char), false JULIET_DEBUG_ONLY(, "OverflowCheck")));
|
||||
|
||||
bool corrupted = false;
|
||||
for(size_t i = 0; i < 50; ++i)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <Core/Common/String.h>
|
||||
#include <Engine/Debug/MemoryDebugger.h>
|
||||
#include <imgui.h>
|
||||
#include <Core/Common/String.h>
|
||||
#include <Core/Memory/MemoryArena.h> // Added for Arena definition
|
||||
#include <Engine/Debug/MemoryDebugger.h>
|
||||
#include <Core/Container/Vector.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Juliet::Debug
|
||||
{
|
||||
@@ -16,6 +18,49 @@ namespace Juliet::Debug
|
||||
bool ScrollListToSelected = false;
|
||||
};
|
||||
|
||||
#if JULIET_DEBUG
|
||||
struct PagedArenaDebugState
|
||||
{
|
||||
float Zoom = 1.0f;
|
||||
ArenaDebugInfo* SelectedAlloc = nullptr;
|
||||
bool ScrollVisualToSelected = false;
|
||||
bool ScrollListToSelected = false;
|
||||
};
|
||||
|
||||
struct PagedArenaStateEntry
|
||||
{
|
||||
const Arena* Arena;
|
||||
PagedArenaDebugState State;
|
||||
};
|
||||
|
||||
// Static Vector for lookup
|
||||
VectorArena<PagedArenaStateEntry> s_PagedArenaStates;
|
||||
|
||||
PagedArenaDebugState& GetPagedArenaState(const Arena* arena)
|
||||
{
|
||||
if (s_PagedArenaStates.Arena == nullptr)
|
||||
{
|
||||
s_PagedArenaStates.Create(JULIET_DEBUG_ONLY("DebugState States"));
|
||||
}
|
||||
|
||||
for (auto& entry : s_PagedArenaStates)
|
||||
{
|
||||
if (entry.Arena == arena)
|
||||
{
|
||||
return entry.State;
|
||||
}
|
||||
}
|
||||
|
||||
PagedArenaStateEntry newEntry;
|
||||
newEntry.Arena = arena;
|
||||
// Default construct state
|
||||
newEntry.State = PagedArenaDebugState();
|
||||
|
||||
s_PagedArenaStates.PushBack(newEntry);
|
||||
return s_PagedArenaStates.Last()->State;
|
||||
}
|
||||
#endif
|
||||
|
||||
ArenaDebugState& GetState(const String& name)
|
||||
{
|
||||
// Simple static map-like storage using standard map would be better but we minimize deps.
|
||||
@@ -45,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<uint8>(s[i]);
|
||||
}
|
||||
// Use hash to pick a Hue
|
||||
float h = static_cast<float>(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<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
|
||||
|
||||
void DrawMemoryArena(String name, const MemoryArena& arena, [[maybe_unused]] ArenaAllocation* currentHighlight,
|
||||
@@ -403,6 +473,473 @@ namespace Juliet::Debug
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
#if JULIET_DEBUG
|
||||
void DrawPagedArena(const Arena* arena)
|
||||
{
|
||||
ImGui::PushID(arena);
|
||||
const char* name = (arena->Name && arena->Name[0] != '\0') ? arena->Name : "Unnamed Arena";
|
||||
PagedArenaDebugState& state = GetPagedArenaState(arena);
|
||||
|
||||
if (ImGui::CollapsingHeader(name))
|
||||
{
|
||||
// Collect Blocks (Pages) in order (Oldest -> Newest)
|
||||
// Arena->Previous chain goes Newest -> Oldest.
|
||||
static VectorArena<const Arena*> blocks;
|
||||
if (blocks.Arena == nullptr)
|
||||
{
|
||||
blocks.Create(JULIET_DEBUG_ONLY("DebugState Blocks"));
|
||||
}
|
||||
blocks.Clear();
|
||||
|
||||
for (const Arena* curr = arena->Current; curr != nullptr; curr = curr->Previous)
|
||||
{
|
||||
blocks.PushBack(curr);
|
||||
}
|
||||
|
||||
// Reverse manually
|
||||
size_t count = blocks.Count;
|
||||
for (size_t i = 0; i < count / 2; ++i)
|
||||
{
|
||||
const Arena* temp = blocks[i];
|
||||
blocks[i] = blocks[count - 1 - i];
|
||||
blocks[count - 1 - i] = temp;
|
||||
}
|
||||
|
||||
// Calculate Stats
|
||||
size_t totalCapacity = 0;
|
||||
size_t totalUsed = 0;
|
||||
|
||||
for (const Arena* blk : blocks)
|
||||
{
|
||||
totalCapacity += blk->Reserved;
|
||||
totalUsed += blk->Position;
|
||||
}
|
||||
|
||||
ImGui::Text("Used: %zu / %zu bytes (%zu pages)", totalUsed, totalCapacity, blocks.Size());
|
||||
ImGui::SliderFloat("Zoom", &state.Zoom, 0.1f, 1000000.0f, "%.2f", ImGuiSliderFlags_Logarithmic);
|
||||
|
||||
// --- Visual View ---
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Visual Map");
|
||||
|
||||
float blockHeight = 24.0f;
|
||||
float blockSpacing = 4.0f;
|
||||
float requiredVisHeight = static_cast<float>(blocks.Size()) * (blockHeight + blockSpacing) + 30.0f;
|
||||
// constrains
|
||||
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));
|
||||
|
||||
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;
|
||||
for (const Arena* blk : blocks)
|
||||
{
|
||||
ImGui::PushID(blk);
|
||||
ImVec2 rectMin = pos;
|
||||
ImVec2 rectMax = ImVec2(pos.x + virtualWidth, pos.y + blockHeight);
|
||||
|
||||
dl->AddRect(rectMin, rectMax, IM_COL32(100, 100, 100, 255));
|
||||
|
||||
size_t dataSize = blk->Reserved - k_ArenaHeaderSize;
|
||||
if (dataSize > 0)
|
||||
{
|
||||
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
|
||||
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<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);
|
||||
float xStart = pos.x + static_cast<float>(static_cast<double>(info->Offset) * scale);
|
||||
float width = static_cast<float>(static_cast<double>(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 = 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);
|
||||
}
|
||||
|
||||
// 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::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<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))
|
||||
{
|
||||
state.SelectedAlloc = info;
|
||||
state.ScrollListToSelected = true;
|
||||
state.ScrollVisualToSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
info = info->Next;
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
// Draw Free Nodes (Holes) if Realloc is enabled
|
||||
if (arena->AllowRealloc && blk->FreeNodes)
|
||||
{
|
||||
ArenaFreeNode* freeNode = blk->FreeNodes;
|
||||
while(freeNode)
|
||||
{
|
||||
float fxStart = pos.x + static_cast<float>(static_cast<double>(freeNode->Position) * scale);
|
||||
float fWidth = static_cast<float>(static_cast<double>(freeNode->Size) * scale);
|
||||
|
||||
ImVec2 fMin(fxStart, pos.y + 1);
|
||||
ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1);
|
||||
|
||||
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
|
||||
|
||||
freeNode = freeNode->Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos.y += blockHeight + blockSpacing;
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
void DebugDrawMemoryArena()
|
||||
@@ -418,7 +955,43 @@ namespace Juliet::Debug
|
||||
|
||||
if (ImGui::Begin("Memory Debugger"))
|
||||
{
|
||||
DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered);
|
||||
if (ImGui::BeginTabBar("ArenaTabs"))
|
||||
{
|
||||
#if JULIET_DEBUG
|
||||
if (ImGui::BeginTabItem("Paged Arenas"))
|
||||
{
|
||||
Arena* head = GetGlobalArenaListHead();
|
||||
if (!head)
|
||||
{
|
||||
ImGui::Text("No active paged arenas.");
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Juliet::D3D12::Internal
|
||||
|
||||
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);
|
||||
if (!heap)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -731,7 +731,7 @@ namespace Juliet::D3D12
|
||||
auto driver = static_cast<D3D12Driver*>(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
|
||||
|
||||
@@ -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<int, 4, true> 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<MoveOnly, 4, true> vec = {};
|
||||
ArenaParams params{ .AllowRealloc = true };
|
||||
Arena* externalArena = ArenaAllocate(params);
|
||||
Arena* externalArena = ArenaAllocate(params JULIET_DEBUG_ONLY(, "VectorTest"));
|
||||
NonNullPtr<Arena> 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<Arena> validArena(externalArena);
|
||||
|
||||
VectorArena<int, 4, true> 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<Arena> validArena(externalArena);
|
||||
|
||||
VectorArena<int> 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<Arena> validArena(externalArena);
|
||||
|
||||
VectorArena<int, 4, true> vec = {};
|
||||
@@ -287,6 +291,7 @@ namespace Juliet
|
||||
}
|
||||
|
||||
vec.Destroy();
|
||||
ArenaRelease(externalArena);
|
||||
}
|
||||
}
|
||||
} // namespace Juliet
|
||||
|
||||
Reference in New Issue
Block a user