Browse Source

Fonts: encode additional data in ImFontAtlasRectId to detect invalid id + added Rects debug browser.

ocornut 4 months ago
parent
commit
ed2bb2cff0
4 changed files with 94 additions and 17 deletions
  1. 39 2
      imgui.cpp
  2. 1 1
      imgui.h
  3. 42 12
      imgui_draw.cpp
  4. 12 2
      imgui_internal.h

+ 39 - 2
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("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);
     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
     // Texture list
     // (ensure the last texture always use the same ID, so we can keep it open neatly)
     // (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++)
     for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++)
     {
     {
         if (tex_n == atlas->TexList.Size - 1)
         if (tex_n == atlas->TexList.Size - 1)
             SetNextItemOpen(true, ImGuiCond_Once);
             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;
     ImGuiContext& g = *GImGui;
     PushID(int_id);
     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));
             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)
         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));
             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();
         PopStyleVar();
 
 
         char texid_desc[20];
         char texid_desc[20];

+ 1 - 1
imgui.h

@@ -3509,7 +3509,7 @@ struct ImFontGlyphRangesBuilder
     IMGUI_API void  BuildRanges(ImVector<ImWchar>* out_ranges);                 // Output new ranges
     IMGUI_API void  BuildRanges(ImVector<ImWchar>* 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.
 // The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it.
 typedef int ImFontAtlasRectId;
 typedef int ImFontAtlasRectId;
 #define ImFontAtlasRectId_Invalid -1
 #define ImFontAtlasRectId_Invalid -1

+ 42 - 12
imgui_draw.cpp

@@ -3238,7 +3238,8 @@ ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasR
 
 
 void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id)
 void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id)
 {
 {
-    IM_ASSERT(id != ImFontAtlasRectId_Invalid);
+    if (ImFontAtlasPackGetRectSafe(this, id) == NULL)
+        return;
     ImFontAtlasPackDiscardRect(this, id);
     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
 bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const
 {
 {
-    ImTextureRect* r = ImFontAtlasPackGetRect((ImFontAtlas*)this, id);
+    ImTextureRect* r = ImFontAtlasPackGetRectSafe((ImFontAtlas*)this, id);
     if (r == NULL)
     if (r == NULL)
         return false;
         return false;
     IM_ASSERT(TexData->Width > 0 && TexData->Height > 0);   // Font atlas needs to be built before we can calculate UV coordinates
     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->x = r->x;
     out_r->y = r->y;
     out_r->y = r->y;
     out_r->w = r->w;
     out_r->w = r->w;
@@ -4001,7 +4004,7 @@ void ImFontAtlasBuildRepackTexture(ImFontAtlas* atlas, int w, int h)
             ImFontAtlasBuildGrowTexture(atlas, w, h); // Recurse
             ImFontAtlasBuildGrowTexture(atlas, w, h); // Recurse
             return;
             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);
         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);
         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);
         builder->RectsIndex.resize(builder->RectsIndex.Size + 1);
         index_idx = builder->RectsIndex.Size - 1;
         index_idx = builder->RectsIndex.Size - 1;
         index_entry = &builder->RectsIndex[index_idx];
         index_entry = &builder->RectsIndex[index_idx];
+        memset(index_entry, 0, sizeof(*index_entry));
     }
     }
     else
     else
     {
     {
         index_idx = builder->RectsIndexFreeListStart;
         index_idx = builder->RectsIndexFreeListStart;
         index_entry = &builder->RectsIndex[index_idx];
         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;
         builder->RectsIndexFreeListStart = index_entry->TargetIndex;
     }
     }
     index_entry->TargetIndex = rect_idx;
     index_entry->TargetIndex = rect_idx;
     index_entry->IsUsed = 1;
     index_entry->IsUsed = 1;
-    return (ImFontAtlasRectId)index_idx;
+    return ImFontAtlasRectId_Make(index_idx, index_entry->Generation);
 }
 }
 
 
 // Overwrite existing entry
 // Overwrite existing entry
@@ -4248,23 +4252,29 @@ static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFon
 {
 {
     IM_ASSERT(index_entry->IsUsed);
     IM_ASSERT(index_entry->IsUsed);
     index_entry->TargetIndex = atlas->Builder->Rects.Size - 1;
     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
 // This is expected to be called in batches and followed by a repack
 void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id)
 void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id)
 {
 {
     IM_ASSERT(id != ImFontAtlasRectId_Invalid);
     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);
     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->IsUsed = false;
     index_entry->TargetIndex = builder->RectsIndexFreeListStart;
     index_entry->TargetIndex = builder->RectsIndexFreeListStart;
+    index_entry->Generation++;
 
 
     const int pack_padding = atlas->TexGlyphPadding;
     const int pack_padding = atlas->TexGlyphPadding;
-    builder->RectsIndexFreeListStart = id;
+    builder->RectsIndexFreeListStart = index_idx;
     builder->RectsDiscardedCount++;
     builder->RectsDiscardedCount++;
     builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding);
     builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding);
     rect->w = rect->h = 0; // Clear rectangle so it won't be packed again
     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);
         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)
 ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id)
 {
 {
     IM_ASSERT(id != ImFontAtlasRectId_Invalid);
     IM_ASSERT(id != ImFontAtlasRectId_Invalid);
+    int index_idx = ImFontAtlasRectId_GetIndex(id);
+    int generation = ImFontAtlasRectId_GetGeneration(id);
     ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder;
     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);
     IM_ASSERT(index_entry->IsUsed);
     return &builder->Rects[index_entry->TargetIndex];
     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)
 // Important! This assume by ImFontConfig::GlyphFilter is a SMALL ARRAY (e.g. <10 entries)
 static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint)
 static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint)
 {
 {

+ 12 - 2
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          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          DebugNodeFont(ImFont* font);
     IMGUI_API void          DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph);
     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          DebugNodeStorage(ImGuiStorage* storage, const char* label);
     IMGUI_API void          DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label);
     IMGUI_API void          DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label);
     IMGUI_API void          DebugNodeTable(ImGuiTable* table);
     IMGUI_API void          DebugNodeTable(ImGuiTable* table);
@@ -3694,6 +3694,14 @@ IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype();
 // [SECTION] ImFontAtlas internal API
 // [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)
 // Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles)
 // User are returned ImFontAtlasRectId values which are meant to be persistent.
 // 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.
 // 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.
 // Having this also makes it easier to e.g. sort rectangles during repack.
 struct ImFontAtlasRectEntry
 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;
     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 void              ImFontAtlasPackInit(ImFontAtlas* atlas);
 IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL);
 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*    ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id);
+IMGUI_API ImTextureRect*    ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id);
 IMGUI_API void              ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id);
 IMGUI_API void              ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id);
 
 
 IMGUI_API void              ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count);
 IMGUI_API void              ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count);