/* ======================================================================== $File: $ $Date: $ $Revision: $ $Creator: Casey Muratori $ $Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $ ======================================================================== */ // TODO(casey): Stop using stdio! #include #include "handmade_debug.h" internal void RestartCollation(debug_state *DebugState, u32 InvalidEventArrayIndex); inline debug_id DebugIDFromLink(debug_tree *Tree, debug_variable_link *Link) { debug_id Result = {}; Result.Value[0] = Tree; Result.Value[1] = Link; return(Result); } inline debug_state * DEBUGGetState(game_memory *Memory) { debug_state *DebugState = 0; if(Memory) { DebugState = (debug_state *)Memory->DebugStorage; if(!DebugState->Initialized) { DebugState = 0; } } return(DebugState); } inline debug_state * DEBUGGetState(void) { debug_state *Result = DEBUGGetState(DebugGlobalMemory); return(Result); } internal debug_tree * AddTree(debug_state *DebugState, debug_variable_group *Group, v2 AtP) { debug_tree *Tree = PushStruct(&DebugState->DebugArena, debug_tree); Tree->UIP = AtP; Tree->Group = Group; DLIST_INSERT(&DebugState->TreeSentinel, Tree); return(Tree); } inline b32 IsHex(char Char) { b32 Result = (((Char >= '0') && (Char <= '9')) || ((Char >= 'A') && (Char <= 'F'))); return(Result); } inline u32 GetHex(char Char) { u32 Result = 0; if((Char >= '0') && (Char <= '9')) { Result = Char - '0'; } else if((Char >= 'A') && (Char <= 'F')) { Result = 0xA + (Char - 'A'); } return(Result); } internal rectangle2 DEBUGTextOp(debug_state *DebugState, debug_text_op Op, v2 P, char *String, v4 Color = V4(1, 1, 1, 1)) { rectangle2 Result = InvertedInfinityRectangle2(); if(DebugState && DebugState->DebugFont) { render_group *RenderGroup = DebugState->RenderGroup; loaded_font *Font = DebugState->DebugFont; hha_font *Info = DebugState->DebugFontInfo; u32 PrevCodePoint = 0; r32 CharScale = DebugState->FontScale; r32 AtY = P.y; r32 AtX = P.x; for(char *At = String; *At; ) { if((At[0] == '\\') && (At[1] == '#') && (At[2] != 0) && (At[3] != 0) && (At[4] != 0)) { r32 CScale = 1.0f / 9.0f; Color = V4(Clamp01(CScale*(r32)(At[2] - '0')), Clamp01(CScale*(r32)(At[3] - '0')), Clamp01(CScale*(r32)(At[4] - '0')), 1.0f); At += 5; } else if((At[0] == '\\') && (At[1] == '^') && (At[2] != 0)) { r32 CScale = 1.0f / 9.0f; CharScale = DebugState->FontScale*Clamp01(CScale*(r32)(At[2] - '0')); At += 3; } else { u32 CodePoint = *At; if((At[0] == '\\') && (IsHex(At[1])) && (IsHex(At[2])) && (IsHex(At[3])) && (IsHex(At[4]))) { CodePoint = ((GetHex(At[1]) << 12) | (GetHex(At[2]) << 8) | (GetHex(At[3]) << 4) | (GetHex(At[4]) << 0)); At += 4; } r32 AdvanceX = CharScale*GetHorizontalAdvanceForPair(Info, Font, PrevCodePoint, CodePoint); AtX += AdvanceX; if(CodePoint != ' ') { bitmap_id BitmapID = GetBitmapForGlyph(RenderGroup->Assets, Info, Font, CodePoint); hha_bitmap *Info = GetBitmapInfo(RenderGroup->Assets, BitmapID); r32 BitmapScale = CharScale*(r32)Info->Dim[1]; v3 BitmapOffset = V3(AtX, AtY, 0); if(Op == DEBUGTextOp_DrawText) { PushBitmap(RenderGroup, BitmapID, BitmapScale, BitmapOffset, Color); } else { Assert(Op == DEBUGTextOp_SizeText); loaded_bitmap *Bitmap = GetBitmap(RenderGroup->Assets, BitmapID, RenderGroup->GenerationID); if(Bitmap) { used_bitmap_dim Dim = GetBitmapDim(RenderGroup, Bitmap, BitmapScale, BitmapOffset, 1.0f); rectangle2 GlyphDim = RectMinDim(Dim.P.xy, Dim.Size); Result = Union(Result, GlyphDim); } } } PrevCodePoint = CodePoint; ++At; } } } return(Result); } internal void DEBUGTextOutAt(v2 P, char *String, v4 Color = V4(1, 1, 1, 1)) { debug_state *DebugState = DEBUGGetState(); if(DebugState) { render_group *RenderGroup = DebugState->RenderGroup; DEBUGTextOp(DebugState, DEBUGTextOp_DrawText, P, String, Color); } } internal rectangle2 DEBUGGetTextSize(debug_state *DebugState, char *String) { rectangle2 Result = DEBUGTextOp(DebugState, DEBUGTextOp_SizeText, V2(0, 0), String); return(Result); } internal void DEBUGTextLine(char *String) { debug_state *DebugState = DEBUGGetState(); if(DebugState) { render_group *RenderGroup = DebugState->RenderGroup; loaded_font *Font = PushFont(RenderGroup, DebugState->FontID); if(Font) { hha_font *Info = GetFontInfo(RenderGroup->Assets, DebugState->FontID); DEBUGTextOutAt(V2(DebugState->LeftEdge, DebugState->AtY - DebugState->FontScale*GetStartingBaselineY(DebugState->DebugFontInfo)), String); DebugState->AtY -= GetLineAdvanceFor(Info)*DebugState->FontScale; } } } struct debug_statistic { r64 Min; r64 Max; r64 Avg; u32 Count; }; inline void BeginDebugStatistic(debug_statistic *Stat) { Stat->Min = Real32Maximum; Stat->Max = -Real32Maximum; Stat->Avg = 0.0f; Stat->Count = 0; } inline void AccumDebugStatistic(debug_statistic *Stat, r64 Value) { ++Stat->Count; if(Stat->Min > Value) { Stat->Min = Value; } if(Stat->Max < Value) { Stat->Max = Value; } Stat->Avg += Value; } inline void EndDebugStatistic(debug_statistic *Stat) { if(Stat->Count) { Stat->Avg /= (r64)Stat->Count; } else { Stat->Min = 0.0f; Stat->Max = 0.0f; } } internal memory_index DEBUGEventToText(char *Buffer, char *End, debug_event *Event, u32 Flags) { char *At = Buffer; char *Name = Event->BlockName; if(Flags & DEBUGVarToText_AddDebugUI) { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "#define DEBUGUI_"); } if(Flags & DEBUGVarToText_AddName) { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "%s%s ", Name, (Flags & DEBUGVarToText_Colon) ? ":" : ""); } switch(Event->Type) { case DebugType_r32: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "%f", Event->Value_r32); if(Flags & DEBUGVarToText_FloatSuffix) { *At++ = 'f'; } } break; case DebugType_b32: { if(Flags & DEBUGVarToText_PrettyBools) { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "%s", Event->Value_b32 ? "true" : "false"); } else { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "%d", Event->Value_b32); } } break; case DebugType_s32: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "%d", Event->Value_s32); } break; case DebugType_u32: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "%u", Event->Value_u32); } break; case DebugType_v2: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "V2(%f, %f)", Event->Value_v2.x, Event->Value_v2.y); } break; case DebugType_v3: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "V3(%f, %f, %f)", Event->Value_v3.x, Event->Value_v3.y, Event->Value_v3.z); } break; case DebugType_v4: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "V4(%f, %f, %f, %f)", Event->Value_v4.x, Event->Value_v4.y, Event->Value_v4.z, Event->Value_v4.w); } break; case DebugType_rectangle2: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "Rect2(%f, %f -> %f, %f)", Event->Value_rectangle2.Min.x, Event->Value_rectangle2.Min.y, Event->Value_rectangle2.Max.x, Event->Value_rectangle2.Max.y); } break; case DebugType_rectangle3: { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "Rect2(%f, %f, %f -> %f, %f, %f)", Event->Value_rectangle3.Min.x, Event->Value_rectangle3.Min.y, Event->Value_rectangle3.Min.z, Event->Value_rectangle3.Max.x, Event->Value_rectangle3.Max.y, Event->Value_rectangle3.Max.z); } break; case DebugType_CounterThreadList: case DebugType_bitmap_id: case DebugType_OpenDataBlock: { } break; InvalidDefaultCase; } if(Flags & DEBUGVarToText_LineFeedEnd) { *At++ = '\n'; } if(Flags & DEBUGVarToText_NullTerminator) { *At++ = 0; } return(At - Buffer); } struct debug_variable_iterator { debug_variable_link *Link; debug_variable_link *Sentinel; }; internal void WriteHandmadeConfig(debug_state *DebugState) { #if 0 // TODO(casey): Need a giant buffer here! char Temp[4096]; char *At = Temp; char *End = Temp + sizeof(Temp); int Depth = 0; debug_variable_iterator Stack[DEBUG_MAX_VARIABLE_STACK_DEPTH]; Stack[Depth].Link = DebugState->RootGroup->VarGroup.Next; Stack[Depth].Sentinel = &DebugState->RootGroup->VarGroup; ++Depth; while(Depth > 0) { debug_variable_iterator *Iter = Stack + (Depth - 1); if(Iter->Link == Iter->Sentinel) { --Depth; } else { debug_variable *Var = Iter->Link->Var; Iter->Link = Iter->Link->Next; if(DEBUGShouldBeWritten(Var->Type)) { // TODO(casey): Other variable types! for(int Indent = 0; Indent < Depth; ++Indent) { *At++ = ' '; *At++ = ' '; *At++ = ' '; *At++ = ' '; } if(Var->Type == DebugVariableType_VarGroup) { At += _snprintf_s(At, (size_t)(End - At), (size_t)(End - At), "// "); } At += DEBUGVariableToText(At, End, Var, DEBUGVarToText_AddDebugUI| DEBUGVarToText_AddName| DEBUGVarToText_LineFeedEnd| DEBUGVarToText_FloatSuffix); } if(Var->Type == DebugVariableType_VarGroup) { Iter = Stack + Depth; Iter->Link = Var->VarGroup.Next; Iter->Sentinel = &Var->VarGroup; ++Depth; } } } Platform.DEBUGWriteEntireFile("../code/handmade_config.h", (u32)(At - Temp), Temp); if(!DebugState->Compiling) { DebugState->Compiling = true; DebugState->Compiler = Platform.DEBUGExecuteSystemCommand("..\\code", "c:\\windows\\system32\\cmd.exe", "/C build.bat"); } #endif } internal void DrawProfileIn(debug_state *DebugState, rectangle2 ProfileRect, v2 MouseP) { PushRect(DebugState->RenderGroup, ProfileRect, 0.0f, V4(0, 0, 0, 0.25f)); r32 BarSpacing = 4.0f; r32 LaneHeight = 0.0f; u32 LaneCount = DebugState->FrameBarLaneCount; r32 FrameBarScale = FLT_MAX; for(debug_frame *Frame = DebugState->OldestFrame; Frame; Frame = Frame->Next) { if(FrameBarScale < Frame->FrameBarScale) { FrameBarScale = Frame->FrameBarScale; } } u32 MaxFrame = DebugState->FrameCount; if(MaxFrame > 10) { MaxFrame = 10; } if((LaneCount > 0) && (MaxFrame > 0)) { r32 PixelsPerFramePlusSpacing = GetDim(ProfileRect).y / (r32)MaxFrame; r32 PixelsPerFrame = PixelsPerFramePlusSpacing - BarSpacing; LaneHeight = PixelsPerFrame / (r32)LaneCount; } r32 BarHeight = LaneHeight*LaneCount; r32 BarsPlusSpacing = BarHeight + BarSpacing; r32 ChartLeft = ProfileRect.Min.x; r32 ChartHeight = BarsPlusSpacing*(r32)MaxFrame; r32 ChartWidth = GetDim(ProfileRect).x; r32 ChartTop = ProfileRect.Max.y; r32 Scale = ChartWidth*FrameBarScale; v3 Colors[] = { {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {1, 1, 0}, {0, 1, 1}, {1, 0, 1}, {1, 0.5f, 0}, {1, 0, 0.5f}, {0.5f, 1, 0}, {0, 1, 0.5f}, {0.5f, 0, 1}, {0, 0.5f, 1}, }; u32 FrameIndex = 0; for(debug_frame *Frame = DebugState->OldestFrame; Frame; Frame = Frame->Next, ++FrameIndex) { r32 StackX = ChartLeft; r32 StackY = ChartTop - BarsPlusSpacing*(r32)FrameIndex; for(u32 RegionIndex = 0; RegionIndex < Frame->RegionCount; ++RegionIndex) { debug_frame_region *Region = Frame->Regions + RegionIndex; // v3 Color = Colors[RegionIndex%ArrayCount(Colors)]; v3 Color = Colors[Region->ColorIndex%ArrayCount(Colors)]; r32 ThisMinX = StackX + Scale*Region->MinT; r32 ThisMaxX = StackX + Scale*Region->MaxT; rectangle2 RegionRect = RectMinMax( V2(ThisMinX, StackY - LaneHeight*(Region->LaneIndex + 1)), V2(ThisMaxX, StackY - LaneHeight*Region->LaneIndex)); PushRect(DebugState->RenderGroup, RegionRect, 0.0f, V4(Color, 1)); if(IsInRectangle(RegionRect, MouseP)) { debug_event *Record = Region->Event; char TextBuffer[256]; _snprintf_s(TextBuffer, sizeof(TextBuffer), "%s: %10ucy [%s(%d)]", Record->BlockName, Region->CycleCount, Record->FileName, Record->LineNumber); DEBUGTextOutAt(MouseP + V2(0.0f, 10.0f), TextBuffer); // HotRecord = Record; } } } #if 0 PushRect(RenderGroup, V3(ChartLeft + 0.5f*ChartWidth, ChartMinY + ChartHeight, 0.0f), V2(ChartWidth, 1.0f), V4(1, 1, 1, 1)); #endif } inline b32 InteractionsAreEqual(debug_interaction A, debug_interaction B) { b32 Result = ((A.Type == B.Type) && (A.Generic == B.Generic)); return(Result); } inline b32 InteractionIsHot(debug_state *DebugState, debug_interaction B) { b32 Result = InteractionsAreEqual(DebugState->HotInteraction, B); return(Result); } struct layout { debug_state *DebugState; v2 MouseP; v2 At; int Depth; real32 LineAdvance; r32 SpacingY; }; inline rectangle2 PlaceRectangle(layout *Layout, v2 Dim) { } struct layout_element { // NOTE(casey): Storage; layout *Layout; v2 *Dim; v2 *Size; debug_interaction Interaction; // NOTE(casey): Out rectangle2 Bounds; }; inline layout_element BeginElementRectangle(layout *Layout, v2 *Dim) { layout_element Element = {}; Element.Layout = Layout; Element.Dim = Dim; return(Element); } inline void MakeElementSizable(layout_element *Element) { Element->Size = Element->Dim; } inline void DefaultInteraction(layout_element *Element, debug_interaction Interaction) { Element->Interaction = Interaction; } inline void EndElement(layout_element *Element) { layout *Layout = Element->Layout; debug_state *DebugState = Layout->DebugState; r32 SizeHandlePixels = 4.0f; v2 Frame = {0, 0}; if(Element->Size) { Frame.x = SizeHandlePixels; Frame.y = SizeHandlePixels; } v2 TotalDim = *Element->Dim + 2.0f*Frame; v2 TotalMinCorner = V2(Layout->At.x + Layout->Depth*2.0f*Layout->LineAdvance, Layout->At.y - TotalDim.y); v2 TotalMaxCorner = TotalMinCorner + TotalDim; v2 InteriorMinCorner = TotalMinCorner + Frame; v2 InteriorMaxCorner = InteriorMinCorner + *Element->Dim; rectangle2 TotalBounds = RectMinMax(TotalMinCorner, TotalMaxCorner); Element->Bounds = RectMinMax(InteriorMinCorner, InteriorMaxCorner); if(Element->Interaction.Type && IsInRectangle(Element->Bounds, Layout->MouseP)) { DebugState->NextHotInteraction = Element->Interaction; } if(Element->Size) { PushRect(DebugState->RenderGroup, RectMinMax(V2(TotalMinCorner.x, InteriorMinCorner.y), V2(InteriorMinCorner.x, InteriorMaxCorner.y)), 0.0f, V4(0, 0, 0, 1)); PushRect(DebugState->RenderGroup, RectMinMax(V2(InteriorMaxCorner.x, InteriorMinCorner.y), V2(TotalMaxCorner.x, InteriorMaxCorner.y)), 0.0f, V4(0, 0, 0, 1)); PushRect(DebugState->RenderGroup, RectMinMax(V2(InteriorMinCorner.x, TotalMinCorner.y), V2(InteriorMaxCorner.x, InteriorMinCorner.y)), 0.0f, V4(0, 0, 0, 1)); PushRect(DebugState->RenderGroup, RectMinMax(V2(InteriorMinCorner.x, InteriorMaxCorner.y), V2(InteriorMaxCorner.x, TotalMaxCorner.y)), 0.0f, V4(0, 0, 0, 1)); debug_interaction SizeInteraction = {}; SizeInteraction.Type = DebugInteraction_Resize; SizeInteraction.P = Element->Size; rectangle2 SizeBox = RectMinMax(V2(InteriorMaxCorner.x, TotalMinCorner.y), V2(TotalMaxCorner.x, InteriorMinCorner.y)); PushRect(DebugState->RenderGroup, SizeBox, 0.0f, (InteractionIsHot(DebugState, SizeInteraction) ? V4(1, 1, 0, 1) : V4(1, 1, 1, 1))); if(IsInRectangle(SizeBox, Layout->MouseP)) { DebugState->NextHotInteraction = SizeInteraction; } } r32 SpacingY = Layout->SpacingY; if(0) { SpacingY = 0.0f; } Layout->At.y = GetMinCorner(TotalBounds).y - SpacingY; } inline b32 DebugIDsAreEqual(debug_id A, debug_id B) { b32 Result = ((A.Value[0] == B.Value[0]) && (A.Value[1] == B.Value[1])); return(Result); } internal debug_view * GetOrCreateDebugViewFor(debug_state *DebugState, debug_id ID) { // TODO(casey): Better hash function u32 HashIndex = (((u32)ID.Value[0] >> 2) + ((u32)ID.Value[1] >> 2)) % ArrayCount(DebugState->ViewHash); debug_view **HashSlot = DebugState->ViewHash + HashIndex; debug_view *Result = 0; for(debug_view *Search = *HashSlot; Search; Search = Search->NextInHash) { if(DebugIDsAreEqual(Search->ID, ID)) { Result = Search; break; } } if(!Result) { Result = PushStruct(&DebugState->DebugArena, debug_view); Result->ID = ID; Result->Type = DebugViewType_Unknown; Result->NextInHash = *HashSlot; *HashSlot = Result; } return(Result); } inline debug_interaction VarLinkInteraction(debug_interaction_type Type, debug_tree *Tree, debug_variable_link *Link) { debug_interaction ItemInteraction = {}; ItemInteraction.ID = DebugIDFromLink(Tree, Link); ItemInteraction.Type = Type; ItemInteraction.Event = Link->Event; return(ItemInteraction); } inline debug_interaction DebugIDInteraction(debug_interaction_type Type, debug_id ID) { debug_interaction ItemInteraction = {}; ItemInteraction.ID = ID; ItemInteraction.Type = Type; return(ItemInteraction); } internal b32 IsSelected(debug_state *DebugState, debug_id ID) { b32 Result = false; for(u32 Index = 0; Index < DebugState->SelectedIDCount; ++Index) { if(DebugIDsAreEqual(ID, DebugState->SelectedID[Index])) { Result = true; break; } } return(Result); } internal void ClearSelection(debug_state *DebugState) { DebugState->SelectedIDCount = 0; } internal void AddToSelection(debug_state *DebugState, debug_id ID) { if((DebugState->SelectedIDCount < ArrayCount(DebugState->SelectedID)) && !IsSelected(DebugState, ID)) { DebugState->SelectedID[DebugState->SelectedIDCount++] = ID; } } internal void DEBUG_HIT(debug_id ID, r32 ZValue) { debug_state *DebugState = DEBUGGetState(); if(DebugState) { DebugState->NextHotInteraction = DebugIDInteraction(DebugInteraction_Select, ID); } } internal b32 DEBUG_HIGHLIGHTED(debug_id ID, v4 *Color) { b32 Result = false; debug_state *DebugState = DEBUGGetState(); if(DebugState) { if(IsSelected(DebugState, ID)) { *Color = V4(0, 1, 1, 1); Result = true; } if(DebugIDsAreEqual(DebugState->HotInteraction.ID, ID)) { *Color = V4(1, 1, 0, 1); Result = true; } } return(Result); } internal b32 DEBUG_REQUESTED(debug_id ID) { b32 Result = false; debug_state *DebugState = DEBUGGetState(); if(DebugState) { Result = IsSelected(DebugState, ID) || DebugIDsAreEqual(DebugState->HotInteraction.ID, ID); } return(Result); } internal void DEBUGDrawMainMenu(debug_state *DebugState, render_group *RenderGroup, v2 MouseP) { for(debug_tree *Tree = DebugState->TreeSentinel.Next; Tree != &DebugState->TreeSentinel; Tree = Tree->Next) { layout Layout = {}; Layout.DebugState = DebugState; Layout.MouseP = MouseP; Layout.At = Tree->UIP; Layout.LineAdvance = DebugState->FontScale*GetLineAdvanceFor(DebugState->DebugFontInfo); Layout.SpacingY = 4.0f; int Depth = 0; debug_variable_iterator Stack[DEBUG_MAX_VARIABLE_STACK_DEPTH]; debug_variable_group *Group = Tree->Group; if(DebugState->FrameCount > 0) { // debug_variable_group *HackyGroup = DebugState->Frames[0].RootGroup; debug_variable_group *HackyGroup = DebugState->ValuesGroup; if(HackyGroup) { Group = HackyGroup; } } if(Group) { Stack[Depth].Link = Group->Sentinel.Next; Stack[Depth].Sentinel = &Group->Sentinel; ++Depth; while(Depth > 0) { debug_variable_iterator *Iter = Stack + (Depth - 1); if(Iter->Link == Iter->Sentinel) { --Depth; } else { Layout.Depth = Depth; debug_variable_link *Link = Iter->Link; debug_event *Event = Iter->Link->Event; Iter->Link = Iter->Link->Next; debug_interaction ItemInteraction = VarLinkInteraction(DebugInteraction_AutoModifyVariable, Tree, Link); b32 IsHot = InteractionIsHot(DebugState, ItemInteraction); v4 ItemColor = IsHot ? V4(1, 1, 0, 1) : V4(1, 1, 1, 1); debug_view *View = GetOrCreateDebugViewFor(DebugState, DebugIDFromLink(Tree, Link)); switch(Event->Type) { case DebugType_CounterThreadList: { layout_element Element = BeginElementRectangle( &Layout, &View->InlineBlock.Dim); MakeElementSizable(&Element); DefaultInteraction(&Element, ItemInteraction); EndElement(&Element); DrawProfileIn(DebugState, Element.Bounds, MouseP); } break; case DebugType_bitmap_id: { loaded_bitmap *Bitmap = GetBitmap(RenderGroup->Assets, Event->Value_bitmap_id, RenderGroup->GenerationID); r32 BitmapScale = View->InlineBlock.Dim.y; if(Bitmap) { used_bitmap_dim Dim = GetBitmapDim(RenderGroup, Bitmap, BitmapScale, V3(0.0f, 0.0f, 0.0f), 1.0f); View->InlineBlock.Dim.x = Dim.Size.x; } debug_interaction TearInteraction = VarLinkInteraction(DebugInteraction_TearValue, Tree, Link); layout_element Element = BeginElementRectangle(&Layout, &View->InlineBlock.Dim); MakeElementSizable(&Element); DefaultInteraction(&Element, TearInteraction); EndElement(&Element); PushRect(DebugState->RenderGroup, Element.Bounds, 0.0f, V4(0, 0, 0, 1.0f)); PushBitmap(DebugState->RenderGroup, Event->Value_bitmap_id, BitmapScale, V3(GetMinCorner(Element.Bounds), 0.0f), V4(1, 1, 1, 1), 0.0f); } break; default: { char Text[256]; DEBUGEventToText(Text, Text + sizeof(Text), Event, DEBUGVarToText_AddName| DEBUGVarToText_NullTerminator| DEBUGVarToText_Colon| DEBUGVarToText_PrettyBools); rectangle2 TextBounds = DEBUGGetTextSize(DebugState, Text); v2 Dim = {GetDim(TextBounds).x, Layout.LineAdvance}; layout_element Element = BeginElementRectangle(&Layout, &Dim); DefaultInteraction(&Element, ItemInteraction); EndElement(&Element); DEBUGTextOutAt(V2(GetMinCorner(Element.Bounds).x, GetMaxCorner(Element.Bounds).y - DebugState->FontScale*GetStartingBaselineY(DebugState->DebugFontInfo)), Text, ItemColor); } break; } if(Link->Children // && View->Collapsible.ExpandedAlways ) { Iter = Stack + Depth; Iter->Link = Link->Children->Sentinel.Next; Iter->Sentinel = &Link->Children->Sentinel; ++Depth; } } } } DebugState->AtY = Layout.At.y; if(1) { debug_interaction MoveInteraction = {}; MoveInteraction.Type = DebugInteraction_Move; MoveInteraction.P = &Tree->UIP; rectangle2 MoveBox = RectCenterHalfDim(Tree->UIP - V2(4.0f, 4.0f), V2(4.0f, 4.0f)); PushRect(DebugState->RenderGroup, MoveBox, 0.0f, InteractionIsHot(DebugState, MoveInteraction) ? V4(1, 1, 0, 1) : V4(1, 1, 1, 1)); if(IsInRectangle(MoveBox, MouseP)) { DebugState->NextHotInteraction = MoveInteraction; } } } #if 0 u32 NewHotMenuIndex = ArrayCount(DebugVariableList); r32 BestDistanceSq = Real32Maximum; r32 MenuRadius = 400.0f; r32 AngleStep = Tau32 / (r32)ArrayCount(DebugVariableList); for(u32 MenuItemIndex = 0; MenuItemIndex < ArrayCount(DebugVariableList); ++MenuItemIndex) { debug_variable *Var = DebugVariableList + MenuItemIndex; char *Text = Var->Name; v4 ItemColor = Var->Value ? V4(1, 1, 1, 1) : V4(0.5f, 0.5f, 0.5f, 1); if(MenuItemIndex == DebugState->HotMenuIndex) { ItemColor = V4(1, 1, 0, 1); } r32 Angle = (r32)MenuItemIndex*AngleStep; v2 TextP = DebugState->MenuP + MenuRadius*Arm2(Angle); r32 ThisDistanceSq = LengthSq(TextP - MouseP); if(BestDistanceSq > ThisDistanceSq) { NewHotMenuIndex = MenuItemIndex; BestDistanceSq = ThisDistanceSq; } rectangle2 TextBounds = DEBUGGetTextSize(DebugState, Text); DEBUGTextOutAt(TextP - 0.5f*GetDim(TextBounds), Text, ItemColor); } if(LengthSq(MouseP - DebugState->MenuP) > Square(MenuRadius)) { DebugState->HotMenuIndex = NewHotMenuIndex; } else { DebugState->HotMenuIndex = ArrayCount(DebugVariableList); } #endif } internal void DEBUGBeginInteract(debug_state *DebugState, game_input *Input, v2 MouseP, b32 AltUI) { if(DebugState->HotInteraction.Type) { if(DebugState->HotInteraction.Type == DebugInteraction_AutoModifyVariable) { switch(DebugState->HotInteraction.Event->Type) { case DebugType_b32: { DebugState->HotInteraction.Type = DebugInteraction_ToggleValue; } break; case DebugType_r32: { DebugState->HotInteraction.Type = DebugInteraction_DragValue; } break; case DebugType_OpenDataBlock: { DebugState->HotInteraction.Type = DebugInteraction_ToggleValue; } break; } if(AltUI) { DebugState->HotInteraction.Type = DebugInteraction_TearValue; } } switch(DebugState->HotInteraction.Type) { case DebugInteraction_TearValue: { #if 0 debug_variable *RootGroup = DEBUGAddRootGroup(DebugState, "NewUserGroup"); DEBUGAddVariableToGroup(DebugState, RootGroup, DebugState->HotInteraction.Var); debug_tree *Tree = AddTree(DebugState, RootGroup, V2(0, 0)); Tree->UIP = MouseP; DebugState->HotInteraction.Type = DebugInteraction_Move; DebugState->HotInteraction.P = &Tree->UIP; #endif } break; case DebugInteraction_Select: { if(!Input->ShiftDown) { ClearSelection(DebugState); } AddToSelection(DebugState, DebugState->HotInteraction.ID); } break; } DebugState->Interaction = DebugState->HotInteraction; } else { DebugState->Interaction.Type = DebugInteraction_NOP; } } internal void DEBUGEndInteract(debug_state *DebugState, game_input *Input, v2 MouseP) { switch(DebugState->Interaction.Type) { case DebugInteraction_ToggleValue: { debug_event *Event = DebugState->Interaction.Event; Assert(Event); switch(Event->Type) { case DebugType_b32: { Event->Value_b32 = !Event->Value_b32; } break; case DebugType_OpenDataBlock: { debug_view *View = GetOrCreateDebugViewFor(DebugState, DebugState->Interaction.ID); View->Collapsible.ExpandedAlways = !View->Collapsible.ExpandedAlways; } break; } } break; } WriteHandmadeConfig(DebugState); DebugState->Interaction.Type = DebugInteraction_None; DebugState->Interaction.Generic = 0; } internal void DEBUGInteract(debug_state *DebugState, game_input *Input, v2 MouseP) { v2 dMouseP = MouseP - DebugState->LastMouseP; /* if(Input->MouseButtons[PlatformMouseButton_Right].EndedDown) { if(Input->MouseButtons[PlatformMouseButton_Right].HalfTransitionCount > 0) { DebugState->MenuP = MouseP; } DrawDebugMainMenu(DebugState, RenderGroup, MouseP); } else if(Input->MouseButtons[PlatformMouseButton_Right].HalfTransitionCount > 0) */ if(DebugState->Interaction.Type) { debug_event *Event = DebugState->Interaction.Event; debug_tree *Tree = DebugState->Interaction.Tree; v2 *P = DebugState->Interaction.P; // NOTE(casey): Mouse move interaction switch(DebugState->Interaction.Type) { case DebugInteraction_DragValue: { switch(Event->Type) { case DebugType_r32: { Event->Value_r32 += 0.1f*dMouseP.y; } break; } } break; case DebugInteraction_Resize: { *P += V2(dMouseP.x, -dMouseP.y); P->x = Maximum(P->x, 10.0f); P->y = Maximum(P->y, 10.0f); } break; case DebugInteraction_Move: { *P += V2(dMouseP.x, dMouseP.y); } break; } b32 AltUI = Input->MouseButtons[PlatformMouseButton_Right].EndedDown; // NOTE(casey): Click interaction for(u32 TransitionIndex = Input->MouseButtons[PlatformMouseButton_Left].HalfTransitionCount; TransitionIndex > 1; --TransitionIndex) { DEBUGEndInteract(DebugState, Input, MouseP); DEBUGBeginInteract(DebugState, Input, MouseP, AltUI); } if(!Input->MouseButtons[PlatformMouseButton_Left].EndedDown) { DEBUGEndInteract(DebugState, Input, MouseP); } } else { DebugState->HotInteraction = DebugState->NextHotInteraction; b32 AltUI = Input->MouseButtons[PlatformMouseButton_Right].EndedDown; for(u32 TransitionIndex = Input->MouseButtons[PlatformMouseButton_Left].HalfTransitionCount; TransitionIndex > 1; --TransitionIndex) { DEBUGBeginInteract(DebugState, Input, MouseP, AltUI); DEBUGEndInteract(DebugState, Input, MouseP); } if(Input->MouseButtons[PlatformMouseButton_Left].EndedDown) { DEBUGBeginInteract(DebugState, Input, MouseP, AltUI); } } DebugState->LastMouseP = MouseP; } global_variable debug_table GlobalDebugTable_; debug_table *GlobalDebugTable = &GlobalDebugTable_; inline u32 GetLaneFromThreadIndex(debug_state *DebugState, u32 ThreadIndex) { u32 Result = 0; // TODO(casey): Implement thread ID lookup. return(Result); } internal debug_thread * GetDebugThread(debug_state *DebugState, u32 ThreadID) { debug_thread *Result = 0; for(debug_thread *Thread = DebugState->FirstThread; Thread; Thread = Thread->Next) { if(Thread->ID == ThreadID) { Result = Thread; break; } } if(!Result) { FREELIST_ALLOCATE(debug_thread, Result, DebugState->FirstFreeThread, &DebugState->DebugArena); Result->ID = ThreadID; Result->LaneIndex = DebugState->FrameBarLaneCount++; Result->FirstOpenCodeBlock = 0; Result->FirstOpenDataBlock = 0; Result->Next = DebugState->FirstThread; DebugState->FirstThread = Result; } return(Result); } debug_frame_region * AddRegion(debug_state *DebugState, debug_frame *CurrentFrame) { Assert(CurrentFrame->RegionCount < MAX_REGIONS_PER_FRAME); debug_frame_region *Result = CurrentFrame->Regions + CurrentFrame->RegionCount++; return(Result); } inline open_debug_block * AllocateOpenDebugBlock(debug_state *DebugState, u32 FrameIndex, debug_event *Event, open_debug_block **FirstOpenBlock) { open_debug_block *Result = 0; FREELIST_ALLOCATE(open_debug_block, Result, DebugState->FirstFreeBlock, &DebugState->DebugArena); Result->StartingFrameIndex = FrameIndex; Result->OpeningEvent = Event; Result->NextFree = 0; Result->Parent = *FirstOpenBlock; *FirstOpenBlock = Result; return(Result); } inline void DeallocateOpenDebugBlock(debug_state *DebugState, open_debug_block **FirstOpenBlock) { open_debug_block *FreeBlock = *FirstOpenBlock; *FirstOpenBlock = FreeBlock->Parent; FreeBlock->NextFree = DebugState->FirstFreeBlock; DebugState->FirstFreeBlock = FreeBlock; } inline b32 EventsMatch(debug_event A, debug_event B) { // TODO(casey): Have counters for blocks? b32 Result = (A.ThreadID == B.ThreadID); return(Result); } internal debug_event * CreateVariable(debug_state *State, debug_type Type, char *Name) { debug_event *Var = PushStruct(&State->DebugArena, debug_event); ZeroStruct(*Var); Var->Type = (u8)Type; Var->BlockName = (char *)PushCopy(&State->DebugArena, StringLength(Name) + 1, Name); return(Var); } internal debug_variable_link * AddVariableToGroup(debug_state *DebugState, debug_variable_group *Group, debug_event *Add) { debug_variable_link *Link = PushStruct(&DebugState->DebugArena, debug_variable_link); DLIST_INSERT(&Group->Sentinel, Link); Link->Children = 0; Link->Event = Add; Assert(Link->Event->Type != DebugType_BeginBlock); return(Link); } internal debug_variable_group * CreateVariableGroup(debug_state *DebugState) { debug_variable_group *Group = PushStruct(&DebugState->DebugArena, debug_variable_group); DLIST_INIT(&Group->Sentinel); return(Group); } internal void FreeVariableGroup(debug_state *DebugState, debug_variable_group *Group) { // TODO(casey): Also remember to trigger freeing frames during arena pushes... // TODO(casey): Remember to copy out the debug events into the debug variable links. Assert(!"Not implemented"); } internal debug_variable_group * GetGroupForHierarchicalName(debug_state *DebugState, char *Name) { debug_variable_group *Result = DebugState->ValuesGroup; return(Result); } internal debug_frame * NewFrame(debug_state *DebugState, u64 BeginClock) { // TODO(casey): Simplify this once regions are more reasonable! debug_frame *Result = DebugState->FirstFreeFrame; if(Result) { DebugState->FirstFreeFrame = Result->NextFree; debug_frame_region *Regions = Result->Regions; ZeroStruct(*Result); Result->Regions = Regions; } else { Result = PushStruct(&DebugState->DebugArena, debug_frame); ZeroStruct(*Result); Result->Regions = PushArray(&DebugState->DebugArena, MAX_REGIONS_PER_FRAME, debug_frame_region); } Result->FrameBarScale = 1.0f; Result->RootGroup = CreateVariableGroup(DebugState); Result->BeginClock = BeginClock; return(Result); } internal void FreeFrame(debug_state *DebugState, debug_frame *Frame) { FreeVariableGroup(DebugState, Frame->RootGroup); FREELIST_DEALLOCATE(Frame, DebugState->FirstFreeFrame); } internal void CollateDebugRecords(debug_state *DebugState, u32 EventCount, debug_event *EventArray) { for(u32 EventIndex = 0; EventIndex < EventCount; ++EventIndex) { debug_event *Event = EventArray + EventIndex; if(!DebugState->CollationFrame) { DebugState->CollationFrame = NewFrame(DebugState, Event->Clock); } if(Event->Type == DebugType_MarkDebugValue) { AddVariableToGroup(DebugState, GetGroupForHierarchicalName(DebugState, Event->Value_debug_event->BlockName), Event->Value_debug_event); } else if(Event->Type == DebugType_FrameMarker) { Assert(DebugState->CollationFrame); DebugState->CollationFrame->EndClock = Event->Clock; DebugState->CollationFrame->WallSecondsElapsed = Event->Value_r32; r32 ClockRange = (r32)(DebugState->CollationFrame->EndClock - DebugState->CollationFrame->BeginClock); // TODO(casey): Can we reenable this now?? #if 0 if(ClockRange > 0.0f) { r32 FrameBarScale = 1.0f / ClockRange; if(DebugState->FrameBarScale > FrameBarScale) { DebugState->FrameBarScale = FrameBarScale; } } #endif if(DebugState->Paused) { FreeFrame(DebugState, DebugState->CollationFrame); } else { if(DebugState->MostRecentFrame) { DebugState->MostRecentFrame->Next = DebugState->CollationFrame; } else { DebugState->OldestFrame = DebugState->MostRecentFrame = DebugState->CollationFrame; } ++DebugState->FrameCount; } DebugState->CollationFrame = NewFrame(DebugState, Event->Clock); } else { Assert(DebugState->CollationFrame); u32 FrameIndex = DebugState->FrameCount - 1; debug_thread *Thread = GetDebugThread(DebugState, Event->ThreadID); u64 RelativeClock = Event->Clock - DebugState->CollationFrame->BeginClock; switch(Event->Type) { case DebugType_BeginBlock: { open_debug_block *DebugBlock = AllocateOpenDebugBlock( DebugState, FrameIndex, Event, &Thread->FirstOpenCodeBlock); } break; case DebugType_EndBlock: { if(Thread->FirstOpenCodeBlock) { open_debug_block *MatchingBlock = Thread->FirstOpenCodeBlock; debug_event *OpeningEvent = MatchingBlock->OpeningEvent; if(EventsMatch(*OpeningEvent, *Event)) { if(MatchingBlock->StartingFrameIndex == FrameIndex) { char *MatchName = MatchingBlock->Parent ? MatchingBlock->Parent->OpeningEvent->BlockName : 0; if(MatchName == DebugState->ScopeToRecord) { r32 MinT = (r32)(OpeningEvent->Clock - DebugState->CollationFrame->BeginClock); r32 MaxT = (r32)(Event->Clock - DebugState->CollationFrame->BeginClock); r32 ThresholdT = 0.01f; if((MaxT - MinT) > ThresholdT) { debug_frame_region *Region = AddRegion(DebugState, DebugState->CollationFrame); Region->Event = OpeningEvent; Region->CycleCount = (Event->Clock - OpeningEvent->Clock); Region->LaneIndex = (u16)Thread->LaneIndex; Region->MinT = MinT; Region->MaxT = MaxT; Region->ColorIndex = (u16)OpeningEvent->BlockName; } } } else { // TODO(casey): Record all frames in between and begin/end spans! } DeallocateOpenDebugBlock(DebugState, &Thread->FirstOpenCodeBlock); } else { // TODO(casey): Record span that goes to the beginning of the frame series? } } } break; case DebugType_OpenDataBlock: { open_debug_block *DebugBlock = AllocateOpenDebugBlock( DebugState, FrameIndex, Event, &Thread->FirstOpenDataBlock); DebugBlock->Group = CreateVariableGroup(DebugState); debug_variable_link *Link = AddVariableToGroup(DebugState, DebugBlock->Parent ? DebugBlock->Parent->Group : DebugState->CollationFrame->RootGroup, Event); Link->Children = DebugBlock->Group; } break; case DebugType_CloseDataBlock: { if(Thread->FirstOpenDataBlock) { open_debug_block *MatchingBlock = Thread->FirstOpenDataBlock; debug_event *OpeningEvent = MatchingBlock->OpeningEvent; if(EventsMatch(*OpeningEvent, *Event)) { DeallocateOpenDebugBlock(DebugState, &Thread->FirstOpenDataBlock); } else { // TODO(casey): Record span that goes to the beginning of the frame series? } } } break; default: { AddVariableToGroup(DebugState, Thread->FirstOpenDataBlock->Group, Event); } break; } } } } internal void DEBUGStart(debug_state *DebugState, game_assets *Assets, u32 Width, u32 Height) { TIMED_FUNCTION(); if(!DebugState->Initialized) { DebugState->FrameBarLaneCount = 0; DebugState->FirstThread = 0; DebugState->FirstFreeThread = 0; DebugState->FirstFreeBlock = 0; DebugState->FrameCount = 0; DebugState->OldestFrame = DebugState->MostRecentFrame = DebugState->FirstFreeFrame = 0; DebugState->CollationFrame = 0; DebugState->HighPriorityQueue = DebugGlobalMemory->HighPriorityQueue; DebugState->TreeSentinel.Next = &DebugState->TreeSentinel; DebugState->TreeSentinel.Prev = &DebugState->TreeSentinel; DebugState->TreeSentinel.Group = 0; InitializeArena(&DebugState->DebugArena, DebugGlobalMemory->DebugStorageSize - sizeof(debug_state), DebugState + 1); #if 0 debug_variable_definition_context Context = {}; Context.State = DebugState; Context.Arena = &DebugState->DebugArena; Context.GroupStack[0] = 0; DebugState->RootGroup = DEBUGBeginVariableGroup(&Context, "Root"); DEBUGBeginVariableGroup(&Context, "Debugging"); DEBUGCreateVariables(&Context); DEBUGBeginVariableGroup(&Context, "Profile"); DEBUGBeginVariableGroup(&Context, "By Thread"); DEBUGAddVariable(&Context, DebugType_CounterThreadList, ""); DEBUGEndVariableGroup(&Context); DEBUGBeginVariableGroup(&Context, "By Function"); DEBUGAddVariable(&Context, DebugType_CounterThreadList, ""); DEBUGEndVariableGroup(&Context); DEBUGEndVariableGroup(&Context); asset_vector MatchVector = {}; MatchVector.E[Tag_FacingDirection] = 0.0f; asset_vector WeightVector = {}; WeightVector.E[Tag_FacingDirection] = 1.0f; bitmap_id ID = GetBestMatchBitmapFrom(Assets, Asset_Head, &MatchVector, &WeightVector); DEBUGAddVariable(&Context, "Test Bitmap", ID); DEBUGEndVariableGroup(&Context); DEBUGEndVariableGroup(&Context); Assert(Context.GroupDepth == 0); #endif DebugState->RenderGroup = AllocateRenderGroup(Assets, &DebugState->DebugArena, Megabytes(16), false); DebugState->Paused = false; DebugState->ScopeToRecord = 0; DebugState->Initialized = true; DebugState->ValuesGroup = CreateVariableGroup(DebugState); AddTree(DebugState, DebugState->RootGroup, V2(-0.5f*Width, 0.5f*Height)); } BeginRender(DebugState->RenderGroup); DebugState->DebugFont = PushFont(DebugState->RenderGroup, DebugState->FontID); DebugState->DebugFontInfo = GetFontInfo(DebugState->RenderGroup->Assets, DebugState->FontID); DebugState->GlobalWidth = (r32)Width; DebugState->GlobalHeight = (r32)Height; asset_vector MatchVector = {}; asset_vector WeightVector = {}; MatchVector.E[Tag_FontType] = (r32)FontType_Debug; WeightVector.E[Tag_FontType] = 1.0f; DebugState->FontID = GetBestMatchFontFrom(Assets, Asset_Font, &MatchVector, &WeightVector); DebugState->FontScale = 1.0f; Orthographic(DebugState->RenderGroup, Width, Height, 1.0f); DebugState->LeftEdge = -0.5f*Width; DebugState->RightEdge = 0.5f*Width; DebugState->AtY = 0.5f*Height; } internal void DEBUGDumpStruct(u32 MemberCount, member_definition *MemberDefs, void *StructPtr, u32 IndentLevel = 0) { for(u32 MemberIndex = 0; MemberIndex < MemberCount; ++MemberIndex) { char TextBufferBase[256]; char *TextBuffer = TextBufferBase; for(u32 Indent = 0; Indent < IndentLevel; ++Indent) { *TextBuffer++ = ' '; *TextBuffer++ = ' '; *TextBuffer++ = ' '; *TextBuffer++ = ' '; } TextBuffer[0] = 0; size_t TextBufferLeft = (TextBufferBase + sizeof(TextBufferBase)) - TextBuffer; member_definition *Member = MemberDefs + MemberIndex; void *MemberPtr = (((u8 *)StructPtr) + Member->Offset); if(Member->Flags & MetaMemberFlag_IsPointer) { MemberPtr = *(void **)MemberPtr; } if(MemberPtr) { switch(Member->Type) { case MetaType_u32: { _snprintf_s(TextBuffer, TextBufferLeft, TextBufferLeft, "%s: %u", Member->Name, *(u32 *)MemberPtr); } break; case MetaType_b32: { _snprintf_s(TextBuffer, TextBufferLeft, TextBufferLeft, "%s: %u", Member->Name, *(b32 *)MemberPtr); } break; case MetaType_s32: { _snprintf_s(TextBuffer, TextBufferLeft, TextBufferLeft, "%s: %d", Member->Name, *(s32 *)MemberPtr); } break; case MetaType_r32: { _snprintf_s(TextBuffer, TextBufferLeft, TextBufferLeft, "%s: %f", Member->Name, *(r32 *)MemberPtr); } break; case MetaType_v2: { _snprintf_s(TextBuffer, TextBufferLeft, TextBufferLeft, "%s: {%f,%f}", Member->Name, ((v2 *)MemberPtr)->x, ((v2 *)MemberPtr)->y); } break; case MetaType_v3: { _snprintf_s(TextBuffer, TextBufferLeft, TextBufferLeft, "%s: {%f,%f,%f}", Member->Name, ((v3 *)MemberPtr)->x, ((v3 *)MemberPtr)->y, ((v3 *)MemberPtr)->z); } break; META_HANDLE_TYPE_DUMP(MemberPtr, IndentLevel + 1); } } if(TextBuffer[0]) { DEBUGTextLine(TextBufferBase); } } } internal void DEBUGEnd(debug_state *DebugState, game_input *Input, loaded_bitmap *DrawBuffer) { TIMED_FUNCTION(); render_group *RenderGroup = DebugState->RenderGroup; debug_event *HotEvent = 0; v2 MouseP = Unproject(DebugState->RenderGroup, V2(Input->MouseX, Input->MouseY)).xy; DEBUGDrawMainMenu(DebugState, RenderGroup, MouseP); DEBUGInteract(DebugState, Input, MouseP); if(DebugState->Compiling) { debug_process_state State = Platform.DEBUGGetProcessState(DebugState->Compiler); if(State.IsRunning) { DEBUGTextLine("COMPILING"); } else { DebugState->Compiling = false; } } loaded_font *Font = DebugState->DebugFont; hha_font *Info = DebugState->DebugFontInfo; if(Font) { #if 0 for(u32 CounterIndex = 0; CounterIndex < DebugState->CounterCount; ++CounterIndex) { debug_counter_state *Counter = DebugState->CounterStates + CounterIndex; debug_statistic HitCount, CycleCount, CycleOverHit; BeginDebugStatistic(&HitCount); BeginDebugStatistic(&CycleCount); BeginDebugStatistic(&CycleOverHit); for(u32 SnapshotIndex = 0; SnapshotIndex < DEBUG_SNAPSHOT_COUNT; ++SnapshotIndex) { AccumDebugStatistic(&HitCount, Counter->Snapshots[SnapshotIndex].HitCount); AccumDebugStatistic(&CycleCount, (u32)Counter->Snapshots[SnapshotIndex].CycleCount); r64 HOC = 0.0f; if(Counter->Snapshots[SnapshotIndex].HitCount) { HOC = ((r64)Counter->Snapshots[SnapshotIndex].CycleCount / (r64)Counter->Snapshots[SnapshotIndex].HitCount); } AccumDebugStatistic(&CycleOverHit, HOC); } EndDebugStatistic(&HitCount); EndDebugStatistic(&CycleCount); EndDebugStatistic(&CycleOverHit); if(Counter->BlockName) { if(CycleCount.Max > 0.0f) { r32 BarWidth = 4.0f; r32 ChartLeft = 0.0f; r32 ChartMinY = AtY; r32 ChartHeight = Info->AscenderHeight*FontScale; r32 Scale = 1.0f / (r32)CycleCount.Max; for(u32 SnapshotIndex = 0; SnapshotIndex < DEBUG_SNAPSHOT_COUNT; ++SnapshotIndex) { r32 ThisProportion = Scale*(r32)Counter->Snapshots[SnapshotIndex].CycleCount; r32 ThisHeight = ChartHeight*ThisProportion; PushRect(RenderGroup, V3(ChartLeft + BarWidth*(r32)SnapshotIndex + 0.5f*BarWidth, ChartMinY + 0.5f*ThisHeight, 0.0f), V2(BarWidth, ThisHeight), V4(ThisProportion, 1, 0.0f, 1)); } } #if 1 char TextBuffer[256]; _snprintf_s(TextBuffer, sizeof(TextBuffer), "%32s(%4d): %10ucy %8uh %10ucy/h", Counter->BlockName, Counter->LineNumber, (u32)CycleCount.Avg, (u32)HitCount.Avg, (u32)CycleOverHit.Avg); DEBUGTextLine(TextBuffer); #endif } } #endif if(DebugState->MostRecentFrame) { char TextBuffer[256]; _snprintf_s(TextBuffer, sizeof(TextBuffer), "Last frame time: %.02fms", DebugState->MostRecentFrame->WallSecondsElapsed * 1000.0f); DEBUGTextLine(TextBuffer); } } if(WasPressed(Input->MouseButtons[PlatformMouseButton_Left])) { if(HotEvent) { DebugState->ScopeToRecord = HotEvent->BlockName; } else { DebugState->ScopeToRecord = 0; } } TiledRenderGroupToOutput(DebugState->HighPriorityQueue, DebugState->RenderGroup, DrawBuffer); EndRender(DebugState->RenderGroup); // NOTE(casey): Clear the UI state for the next frame ZeroStruct(DebugState->NextHotInteraction); } extern "C" DEBUG_GAME_FRAME_END(DEBUGGameFrameEnd) { GlobalDebugTable->CurrentEventArrayIndex = !GlobalDebugTable->CurrentEventArrayIndex; u64 ArrayIndex_EventIndex = AtomicExchangeU64(&GlobalDebugTable->EventArrayIndex_EventIndex, (u64)GlobalDebugTable->CurrentEventArrayIndex << 32); u32 EventArrayIndex = ArrayIndex_EventIndex >> 32; Assert(EventArrayIndex <= 1); u32 EventCount = ArrayIndex_EventIndex & 0xFFFFFFFF; debug_state *DebugState = (debug_state *)Memory->DebugStorage; if(DebugState) { game_assets *Assets = DEBUGGetGameAssets(Memory); DEBUGStart(DebugState, Assets, Buffer->Width, Buffer->Height); CollateDebugRecords(DebugState, EventCount, GlobalDebugTable->Events[EventArrayIndex]); loaded_bitmap DrawBuffer = {}; DrawBuffer.Width = Buffer->Width; DrawBuffer.Height = Buffer->Height; DrawBuffer.Pitch = Buffer->Pitch; DrawBuffer.Memory = Buffer->Memory; DEBUGEnd(DebugState, Input, &DrawBuffer); } return(GlobalDebugTable); }