diff --git a/imgui.cpp b/imgui.cpp index a713390c3..881d40727 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -15747,17 +15747,47 @@ void ImGui::ShowFontAtlas(ImFontAtlas* atlas) Text("Packed rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsPackedCount, atlas->Builder->RectsPackedSurface, packed_surface_sqrt, packed_surface_sqrt); Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt); + ImFontAtlasRectId highlight_r_id = ImFontAtlasRectId_Invalid; + if (TreeNode("Rects Index", "Rects Index (%d)", atlas->Builder->RectsPackedCount)) // <-- Use count of used rectangles + { + PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0f); + if (BeginTable("##table", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, ImVec2(0.0f, GetTextLineHeightWithSpacing() * 12))) + { + for (const ImFontAtlasRectEntry& entry : atlas->Builder->RectsIndex) + if (entry.IsUsed) + { + ImFontAtlasRectId id = ImFontAtlasRectId_Make(atlas->Builder->RectsIndex.index_from_ptr(&entry), entry.Generation); + ImFontAtlasRect r = {}; + atlas->GetCustomRect(id, &r); + const char* buf; + 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); + TableNextColumn(); + Selectable(buf); + if (IsItemHovered()) + highlight_r_id = id; + TableNextColumn(); + Image(atlas->TexID, ImVec2(r.w, r.h), r.uv0, r.uv1); + } + EndTable(); + } + PopStyleVar(); + TreePop(); + } + // Texture list // (ensure the last texture always use the same ID, so we can keep it open neatly) + ImFontAtlasRect highlight_r; + if (highlight_r_id != ImFontAtlasRectId_Invalid) + atlas->GetCustomRect(highlight_r_id, &highlight_r); for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { if (tex_n == atlas->TexList.Size - 1) SetNextItemOpen(true, ImGuiCond_Once); - DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n); + DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n, (highlight_r_id != ImFontAtlasRectId_Invalid) ? &highlight_r : NULL); } } -void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id) +void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect) { ImGuiContext& g = *GImGui; PushID(int_id); @@ -15773,6 +15803,13 @@ void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id) 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)); if (cfg->ShowTextureUsedRect) 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)); + if (highlight_rect != NULL) + { + ImRect r_outer(p.x, p.y, p.x + tex->Width, p.y + tex->Height); + 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); + RenderRectFilledWithHole(GetWindowDrawList(), r_outer, r_inner, IM_COL32(0, 0, 0, 100), 0.0f); + GetWindowDrawList()->AddRect(r_inner.Min - ImVec2(1, 1), r_inner.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255)); + } PopStyleVar(); char texid_desc[20]; diff --git a/imgui.h b/imgui.h index 3d9ff1b72..2a0cf3546 100644 --- a/imgui.h +++ b/imgui.h @@ -3509,7 +3509,7 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; -// An identifier to a rectangle in the atlas. -1 when invalid. +// An opaque identifier to a rectangle in the atlas. -1 when invalid. // The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. typedef int ImFontAtlasRectId; #define ImFontAtlasRectId_Invalid -1 diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3feba78a1..f1d1cf0b2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3238,7 +3238,8 @@ ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasR void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) { - IM_ASSERT(id != ImFontAtlasRectId_Invalid); + if (ImFontAtlasPackGetRectSafe(this, id) == NULL) + return; ImFontAtlasPackDiscardRect(this, id); } @@ -3294,10 +3295,12 @@ int ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, Im bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const { - ImTextureRect* r = ImFontAtlasPackGetRect((ImFontAtlas*)this, id); + ImTextureRect* r = ImFontAtlasPackGetRectSafe((ImFontAtlas*)this, id); if (r == NULL) return false; IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates + if (out_r == NULL) + return true; out_r->x = r->x; out_r->y = r->y; out_r->w = r->w; @@ -4001,7 +4004,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h) ImFontAtlasBuildGrowTexture(atlas, w, h); // Recurse return; } - IM_ASSERT(new_r_id == builder->RectsIndex.index_from_ptr(&index_entry)); + IM_ASSERT(ImFontAtlasRectId_GetIndex(new_r_id) == builder->RectsIndex.index_from_ptr(&index_entry)); ImTextureRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); } @@ -4230,17 +4233,18 @@ static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int r builder->RectsIndex.resize(builder->RectsIndex.Size + 1); index_idx = builder->RectsIndex.Size - 1; index_entry = &builder->RectsIndex[index_idx]; + memset(index_entry, 0, sizeof(*index_entry)); } else { index_idx = builder->RectsIndexFreeListStart; index_entry = &builder->RectsIndex[index_idx]; - IM_ASSERT(index_entry->IsUsed == false); + IM_ASSERT(index_entry->IsUsed == false && index_entry->Generation > 0); // Generation is incremented during DiscardRect builder->RectsIndexFreeListStart = index_entry->TargetIndex; } index_entry->TargetIndex = rect_idx; index_entry->IsUsed = 1; - return (ImFontAtlasRectId)index_idx; + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); } // Overwrite existing entry @@ -4248,23 +4252,29 @@ static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFon { IM_ASSERT(index_entry->IsUsed); index_entry->TargetIndex = atlas->Builder->Rects.Size - 1; - return atlas->Builder->RectsIndex.index_from_ptr(index_entry); + int index_idx = atlas->Builder->RectsIndex.index_from_ptr(index_entry); + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); } // This is expected to be called in batches and followed by a repack void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id != ImFontAtlasRectId_Invalid); - ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; - ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; - IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id); + if (rect == NULL) + return; + + ImFontAtlasBuilder* builder = atlas->Builder; + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); index_entry->IsUsed = false; index_entry->TargetIndex = builder->RectsIndexFreeListStart; + index_entry->Generation++; const int pack_padding = atlas->TexGlyphPadding; - builder->RectsIndexFreeListStart = id; + builder->RectsIndexFreeListStart = index_idx; builder->RectsDiscardedCount++; builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding); rect->w = rect->h = 0; // Clear rectangle so it won't be packed again @@ -4319,16 +4329,36 @@ ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFon return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); } -// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +// Generally for non-user facing functions: assert on invalid ID. ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) { IM_ASSERT(id != ImFontAtlasRectId_Invalid); + int index_idx = ImFontAtlasRectId_GetIndex(id); + int generation = ImFontAtlasRectId_GetGeneration(id); ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; - ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[id]; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->Generation == generation); IM_ASSERT(index_entry->IsUsed); return &builder->Rects[index_entry->TargetIndex]; } +// For user-facing functions: return NULL on invalid ID. +// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + if (id == ImFontAtlasRectId_Invalid) + return NULL; + int index_idx = ImFontAtlasRectId_GetIndex(id); + int generation = ImFontAtlasRectId_GetGeneration(id); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + if (index_idx >= builder->RectsIndex.Size) + return NULL; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + if (index_entry->Generation != generation || !index_entry->IsUsed) + return NULL; + return &builder->Rects[index_entry->TargetIndex]; +} + // Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries) static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) { diff --git a/imgui_internal.h b/imgui_internal.h index 3e519325a..cb8ba5169 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3627,7 +3627,7 @@ namespace ImGui IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); - IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id); // ID used to facilitate persisting the "current" texture. + IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3694,6 +3694,14 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- +// Refer to ImFontAtlasPackGetRect() to better understand how this works. +#define ImFontAtlasRectId_IndexMask_ (0x000FFFFF) // 20-bits: index to access builder->RectsIndex[]. +#define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers. +#define ImFontAtlasRectId_GenerationShift_ (20) +inline int ImFontAtlasRectId_GetIndex(ImFontAtlasRectId id) { return id & ImFontAtlasRectId_IndexMask_; } +inline int ImFontAtlasRectId_GetGeneration(ImFontAtlasRectId id) { return (id & ImFontAtlasRectId_GenerationMask_) >> ImFontAtlasRectId_GenerationShift_; } +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_)); } + // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) // User are returned ImFontAtlasRectId values which are meant to be persistent. // We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId. @@ -3701,7 +3709,8 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); // Having this also makes it easier to e.g. sort rectangles during repack. struct ImFontAtlasRectEntry { - int TargetIndex : 31; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + int TargetIndex : 20; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + int Generation : 10; // Increased each time the entry is reused for a new rectangle. unsigned int IsUsed : 1; }; @@ -3792,6 +3801,7 @@ IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_s IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count);