Skip to content

Commit 72ee77b

Browse files
committed
WIP - Fonts: encode additional data in ImFontAtlasRectId to detect invalid id + added Rects debug browser.
1 parent 72bbb44 commit 72ee77b

File tree

4 files changed

+94
-17
lines changed

4 files changed

+94
-17
lines changed

imgui.cpp

+39-2
Original file line numberDiff line numberDiff line change
@@ -21361,17 +21361,47 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas)
2136121361
Text("Packed rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsPackedCount, atlas->Builder->RectsPackedSurface, packed_surface_sqrt, packed_surface_sqrt);
2136221362
Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt);
2136321363

21364+
ImFontAtlasRectId highlight_r_id = ImFontAtlasRectId_Invalid;
21365+
if (TreeNode("Rects Index", "Rects Index (%d)", atlas->Builder->RectsPackedCount)) // <-- Use count of used rectangles
21366+
{
21367+
PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0f);
21368+
if (BeginTable("##table", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, ImVec2(0.0f, GetTextLineHeightWithSpacing() * 12)))
21369+
{
21370+
for (const ImFontAtlasRectEntry& entry : atlas->Builder->RectsIndex)
21371+
if (entry.IsUsed)
21372+
{
21373+
ImFontAtlasRectId id = ImFontAtlasRectId_Make(atlas->Builder->RectsIndex.index_from_ptr(&entry), entry.Generation);
21374+
ImFontAtlasRect r = {};
21375+
atlas->GetCustomRect(id, &r);
21376+
const char* buf;
21377+
ImFormatStringToTempBuffer(&buf, NULL, "ID:%08X, used:%d, { w:%3d, h:%3d } { x:%4d, y:%4d }", id, entry.IsUsed, r.w, r.h, r.x, r.y);
21378+
TableNextColumn();
21379+
Selectable(buf);
21380+
if (IsItemHovered())
21381+
highlight_r_id = id;
21382+
TableNextColumn();
21383+
Image(atlas->TexID, ImVec2(r.w, r.h), r.uv0, r.uv1);
21384+
}
21385+
EndTable();
21386+
}
21387+
PopStyleVar();
21388+
TreePop();
21389+
}
21390+
2136421391
// Texture list
2136521392
// (ensure the last texture always use the same ID, so we can keep it open neatly)
21393+
ImFontAtlasRect highlight_r;
21394+
if (highlight_r_id != ImFontAtlasRectId_Invalid)
21395+
atlas->GetCustomRect(highlight_r_id, &highlight_r);
2136621396
for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++)
2136721397
{
2136821398
if (tex_n == atlas->TexList.Size - 1)
2136921399
SetNextItemOpen(true, ImGuiCond_Once);
21370-
DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n);
21400+
DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n, (highlight_r_id != ImFontAtlasRectId_Invalid) ? &highlight_r : NULL);
2137121401
}
2137221402
}
2137321403

21374-
void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id)
21404+
void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect)
2137521405
{
2137621406
ImGuiContext& g = *GImGui;
2137721407
PushID(int_id);
@@ -21387,6 +21417,13 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id)
2138721417
ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
2138821418
if (cfg->ShowTextureUsedRect)
2138921419
GetWindowDrawList()->AddRect(ImVec2(p.x + tex->UsedRect.x, p.y + tex->UsedRect.y), ImVec2(p.x + tex->UsedRect.x + tex->UsedRect.w, p.y + tex->UsedRect.y + tex->UsedRect.h), IM_COL32(255, 0, 255, 255));
21420+
if (highlight_rect != NULL)
21421+
{
21422+
ImRect r_outer(p.x, p.y, p.x + tex->Width, p.y + tex->Height);
21423+
ImRect r_inner(p.x + highlight_rect->x, p.y + highlight_rect->y, p.x + highlight_rect->x + highlight_rect->w, p.y + highlight_rect->y + highlight_rect->h);
21424+
RenderRectFilledWithHole(GetWindowDrawList(), r_outer, r_inner, IM_COL32(0, 0, 0, 100), 0.0f);
21425+
GetWindowDrawList()->AddRect(r_inner.Min - ImVec2(1, 1), r_inner.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255));
21426+
}
2139021427
PopStyleVar();
2139121428

2139221429
char texid_desc[20];

imgui.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -3616,7 +3616,7 @@ struct ImFontGlyphRangesBuilder
36163616
IMGUI_API void BuildRanges(ImVector<ImWchar>* out_ranges); // Output new ranges
36173617
};
36183618

3619-
// An identifier to a rectangle in the atlas. -1 when invalid.
3619+
// An opaque identifier to a rectangle in the atlas. -1 when invalid.
36203620
// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it.
36213621
typedef int ImFontAtlasRectId;
36223622
#define ImFontAtlasRectId_Invalid -1

imgui_draw.cpp

+42-12
Original file line numberDiff line numberDiff line change
@@ -3244,7 +3244,8 @@ ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasR
32443244

32453245
void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id)
32463246
{
3247-
IM_ASSERT(id != ImFontAtlasRectId_Invalid);
3247+
if (ImFontAtlasPackGetRectSafe(this, id) == NULL)
3248+
return;
32483249
ImFontAtlasPackDiscardRect(this, id);
32493250
}
32503251

@@ -3300,10 +3301,12 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im
33003301

33013302
bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const
33023303
{
3303-
ImTextureRect* r = ImFontAtlasPackGetRect((ImFontAtlas*)this, id);
3304+
ImTextureRect* r = ImFontAtlasPackGetRectSafe((ImFontAtlas*)this, id);
33043305
if (r == NULL)
33053306
return false;
33063307
IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates
3308+
if (out_r == NULL)
3309+
return true;
33073310
out_r->x = r->x;
33083311
out_r->y = r->y;
33093312
out_r->w = r->w;
@@ -4007,7 +4010,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h)
40074010
ImFontAtlasBuildGrowTexture(atlas, w, h); // Recurse
40084011
return;
40094012
}
4010-
IM_ASSERT(new_r_id == builder->RectsIndex.index_from_ptr(&index_entry));
4013+
IM_ASSERT(ImFontAtlasRectId_GetIndex(new_r_id) == builder->RectsIndex.index_from_ptr(&index_entry));
40114014
ImTextureRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id);
40124015
ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h);
40134016
}
@@ -4236,41 +4239,48 @@ static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int r
42364239
builder->RectsIndex.resize(builder->RectsIndex.Size + 1);
42374240
index_idx = builder->RectsIndex.Size - 1;
42384241
index_entry = &builder->RectsIndex[index_idx];
4242+
memset(index_entry, 0, sizeof(*index_entry));
42394243
}
42404244
else
42414245
{
42424246
index_idx = builder->RectsIndexFreeListStart;
42434247
index_entry = &builder->RectsIndex[index_idx];
4244-
IM_ASSERT(index_entry->IsUsed == false);
4248+
IM_ASSERT(index_entry->IsUsed == false && index_entry->Generation > 0); // Generation is incremented during DiscardRect
42454249
builder->RectsIndexFreeListStart = index_entry->TargetIndex;
42464250
}
42474251
index_entry->TargetIndex = rect_idx;
42484252
index_entry->IsUsed = 1;
4249-
return (ImFontAtlasRectId)index_idx;
4253+
return ImFontAtlasRectId_Make(index_idx, index_entry->Generation);
42504254
}
42514255

42524256
// Overwrite existing entry
42534257
static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFontAtlasRectEntry* index_entry)
42544258
{
42554259
IM_ASSERT(index_entry->IsUsed);
42564260
index_entry->TargetIndex = atlas->Builder->Rects.Size - 1;
4257-
return atlas->Builder->RectsIndex.index_from_ptr(index_entry);
4261+
int index_idx = atlas->Builder->RectsIndex.index_from_ptr(index_entry);
4262+
return ImFontAtlasRectId_Make(index_idx, index_entry->Generation);
42584263
}
42594264

42604265
// This is expected to be called in batches and followed by a repack
42614266
void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id)
42624267
{
42634268
IM_ASSERT(id != ImFontAtlasRectId_Invalid);
4264-
ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder;
4265-
ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id];
4266-
IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0);
42674269

42684270
ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id);
4271+
if (rect == NULL)
4272+
return;
4273+
4274+
ImFontAtlasBuilder* builder = atlas->Builder;
4275+
int index_idx = ImFontAtlasRectId_GetIndex(id);
4276+
ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx];
4277+
IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0);
42694278
index_entry->IsUsed = false;
42704279
index_entry->TargetIndex = builder->RectsIndexFreeListStart;
4280+
index_entry->Generation++;
42714281

42724282
const int pack_padding = atlas->TexGlyphPadding;
4273-
builder->RectsIndexFreeListStart = id;
4283+
builder->RectsIndexFreeListStart = index_idx;
42744284
builder->RectsDiscardedCount++;
42754285
builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding);
42764286
rect->w = rect->h = 0; // Clear rectangle so it won't be packed again
@@ -4325,16 +4335,36 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon
43254335
return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1);
43264336
}
43274337

4328-
// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers.
4338+
// Generally for non-user facing functions: assert on invalid ID.
43294339
ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id)
43304340
{
43314341
IM_ASSERT(id != ImFontAtlasRectId_Invalid);
4342+
int index_idx = ImFontAtlasRectId_GetIndex(id);
4343+
int generation = ImFontAtlasRectId_GetGeneration(id);
43324344
ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder;
4333-
ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id];
4345+
ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx];
4346+
IM_ASSERT(index_entry->Generation == generation);
43344347
IM_ASSERT(index_entry->IsUsed);
43354348
return &builder->Rects[index_entry->TargetIndex];
43364349
}
43374350

4351+
// For user-facing functions: return NULL on invalid ID.
4352+
// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers.
4353+
ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id)
4354+
{
4355+
if (id == ImFontAtlasRectId_Invalid)
4356+
return NULL;
4357+
int index_idx = ImFontAtlasRectId_GetIndex(id);
4358+
int generation = ImFontAtlasRectId_GetGeneration(id);
4359+
ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder;
4360+
if (index_idx >= builder->RectsIndex.Size)
4361+
return NULL;
4362+
ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx];
4363+
if (index_entry->Generation != generation || !index_entry->IsUsed)
4364+
return NULL;
4365+
return &builder->Rects[index_entry->TargetIndex];
4366+
}
4367+
43384368
// Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries)
43394369
static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint)
43404370
{

imgui_internal.h

+12-2
Original file line numberDiff line numberDiff line change
@@ -3923,7 +3923,7 @@ namespace ImGui
39233923
IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb);
39243924
IMGUI_API void DebugNodeFont(ImFont* font);
39253925
IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph);
3926-
IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id); // ID used to facilitate persisting the "current" texture.
3926+
IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture.
39273927
IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label);
39283928
IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label);
39293929
IMGUI_API void DebugNodeTable(ImGuiTable* table);
@@ -3991,14 +3991,23 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype();
39913991
// [SECTION] ImFontAtlas internal API
39923992
//-----------------------------------------------------------------------------
39933993

3994+
// Refer to ImFontAtlasPackGetRect() to better understand how this works.
3995+
#define ImFontAtlasRectId_IndexMask_ (0x000FFFFF) // 20-bits: index to access builder->RectsIndex[].
3996+
#define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers.
3997+
#define ImFontAtlasRectId_GenerationShift_ (20)
3998+
inline int ImFontAtlasRectId_GetIndex(ImFontAtlasRectId id) { return id & ImFontAtlasRectId_IndexMask_; }
3999+
inline int ImFontAtlasRectId_GetGeneration(ImFontAtlasRectId id) { return (id & ImFontAtlasRectId_GenerationMask_) >> ImFontAtlasRectId_GenerationShift_; }
4000+
inline ImFontAtlasRectId ImFontAtlasRectId_Make(int index_idx, int gen_idx) { IM_ASSERT(index_idx < ImFontAtlasRectId_IndexMask_ && gen_idx < (ImFontAtlasRectId_GenerationMask_ >> ImFontAtlasRectId_GenerationShift_)); return (ImFontAtlasRectId)(index_idx | (gen_idx << ImFontAtlasRectId_GenerationShift_)); }
4001+
39944002
// Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles)
39954003
// User are returned ImFontAtlasRectId values which are meant to be persistent.
39964004
// We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId.
39974005
// RectsIndex[] is used both as an index into Rects[] and an index into itself. This is basically a free-list. See ImFontAtlasBuildAllocRectIndexEntry() code.
39984006
// Having this also makes it easier to e.g. sort rectangles during repack.
39994007
struct ImFontAtlasRectEntry
40004008
{
4001-
int TargetIndex : 31; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list.
4009+
int TargetIndex : 20; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list.
4010+
int Generation : 10; // Increased each time the entry is reused for a new rectangle.
40024011
unsigned int IsUsed : 1;
40034012
};
40044013

@@ -4089,6 +4098,7 @@ IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_s
40894098
IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas);
40904099
IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL);
40914100
IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id);
4101+
IMGUI_API ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id);
40924102
IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id);
40934103

40944104
IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count);

0 commit comments

Comments
 (0)