From cdf72ede17fa27d9f0271acb264419f9f33e260a Mon Sep 17 00:00:00 2001 From: 4DBug <4DBug@github.com> Date: Thu, 8 Oct 2026 10:21:16 -0500 Subject: [PATCH] Perf: memoize ImHashStr per seed, inline glyph lookup in ImFont:RenderText --- build/ImGui.linemap.json | 2 +- build/ImGui.luau | 39 +++++++++++++++++++++++++++++++++++---- imgui/imgui.lua | 24 ++++++++++++++++++++++-- imgui/imgui_draw.lua | 14 ++++++++++++-- 4 files changed, 70 insertions(+), 9 deletions(-) diff --git a/build/ImGui.linemap.json b/build/ImGui.linemap.json index 45bbb02..a19568f 100644 --- a/build/ImGui.linemap.json +++ b/build/ImGui.linemap.json @@ -1 +1 @@ -[[12, "imgui.lua"], [13741, "imgui_demo.lua"], [14674, "imgui_demo_2.lua"], [17749, "imgui_demo_apps.lua"], [19008, "imgui_demo_tools.lua"], [20334, "imgui_demo_widgets.lua"], [22258, "imgui_docking.lua"], [25386, "imgui_dragdrop.lua"], [25678, "imgui_draw.lua"], [30900, "imgui_h.lua"], [33066, "imgui_impl_roblox.lua"], [33890, "imgui_impl_roblox_fonts.lua"], [40293, "imgui_internal.lua"], [43449, "imgui_tables.lua"], [47796, "imgui_tabs.lua"], [48963, "imgui_widgets.lua"], [57487, "imstb_rectpack.lua"], [57933, "imstb_textedit.lua"], [59174, "imstb_truetype.lua"], [61642, "imstd_minstdio.lua"]] \ No newline at end of file +[[12, "imgui.lua"], [13761, "imgui_demo.lua"], [14694, "imgui_demo_2.lua"], [17769, "imgui_demo_apps.lua"], [19028, "imgui_demo_tools.lua"], [20354, "imgui_demo_widgets.lua"], [22278, "imgui_docking.lua"], [25406, "imgui_dragdrop.lua"], [25698, "imgui_draw.lua"], [30930, "imgui_h.lua"], [33096, "imgui_impl_roblox.lua"], [33920, "imgui_impl_roblox_fonts.lua"], [40323, "imgui_internal.lua"], [43479, "imgui_tables.lua"], [47826, "imgui_tabs.lua"], [48993, "imgui_widgets.lua"], [57517, "imstb_rectpack.lua"], [57963, "imstb_textedit.lua"], [59204, "imstb_truetype.lua"], [61672, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/ImGui.luau b/build/ImGui.luau index 29dd9fa..a2bc909 100644 --- a/build/ImGui.luau +++ b/build/ImGui.luau @@ -353,9 +353,29 @@ end --- @param size int? --- @param seed int? --- @return int +-- [Luau] memo for whole-string hashes: labels are re-hashed with the same seed every frame. +-- ponytail: whole-memo reset at HASH_MEMO_MAX entries, LRU if ID churn makes it thrash +local hash_memo, hash_memo_n, HASH_MEMO_MAX = {}, 0, 16384 +local ImHashStrRaw + function ImHashStr(str, size, seed) - if size == nil then size = #str end - if seed == nil then seed = 0 end + if seed == nil then seed = 0 end + if size == nil or size == #str then + local m = hash_memo[seed] + if m == nil then m = {}; hash_memo[seed] = m end + local h = m[str] + if h == nil then + if hash_memo_n >= HASH_MEMO_MAX then hash_memo = { [seed] = m }; table.clear(m); hash_memo_n = 0 end + h = ImHashStrRaw(str, #str, seed) + m[str] = h + hash_memo_n += 1 + end + return h + end + return ImHashStrRaw(str, size, seed) +end + +function ImHashStrRaw(str, size, seed) if str == "" or size == 0 then return seed -- need to match cpp code edge case behavior while using FNV ourselves @@ -30318,6 +30338,8 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b local word_wrap_eol local stop = false + local text_is_str = type(text) == "string" + local lookup, lookup_n, glyphs = baked.IndexLookup.Data, baked.IndexLookup.Size, baked.Glyphs.Data while s < text_end do repeat @@ -30339,7 +30361,8 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b end end - local c = ImStrByte(text, s) + local c + if text_is_str then c = string.byte(text, s) else c = text[s] end if c < 0x80 then s = s + 1 else @@ -30364,7 +30387,14 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b end end - local glyph = baked:FindGlyph(c) + local glyph + local gi = (c < lookup_n) and lookup[c + 1] or nil + if gi ~= nil and gi ~= IM_FONTGLYPH_INDEX_NOT_FOUND and gi ~= IM_FONTGLYPH_INDEX_UNUSED then + glyph = glyphs[gi] + else + glyph = baked:FindGlyph(c) -- may load the glyph and grow the tables + lookup, lookup_n, glyphs = baked.IndexLookup.Data, baked.IndexLookup.Size, baked.Glyphs.Data + end local char_width = glyph.AdvanceX * scale if glyph.Visible then @@ -62261,6 +62291,7 @@ return { ImGui_ImplRoblox_UpdateTexture = ImGui_ImplRoblox_UpdateTexture, ImHashData = ImHashData, ImHashStr = ImHashStr, + ImHashStrRaw = ImHashStrRaw, ImIsPowerOfTwo = ImIsPowerOfTwo, ImLengthSqr = ImLengthSqr, ImLerp = ImLerp, diff --git a/imgui/imgui.lua b/imgui/imgui.lua index e31f87c..ef30718 100644 --- a/imgui/imgui.lua +++ b/imgui/imgui.lua @@ -342,9 +342,29 @@ end --- @param size int? --- @param seed int? --- @return int +-- [Luau] memo for whole-string hashes: labels are re-hashed with the same seed every frame. +-- ponytail: whole-memo reset at HASH_MEMO_MAX entries, LRU if ID churn makes it thrash +local hash_memo, hash_memo_n, HASH_MEMO_MAX = {}, 0, 16384 +local ImHashStrRaw + function ImHashStr(str, size, seed) - if size == nil then size = #str end - if seed == nil then seed = 0 end + if seed == nil then seed = 0 end + if size == nil or size == #str then + local m = hash_memo[seed] + if m == nil then m = {}; hash_memo[seed] = m end + local h = m[str] + if h == nil then + if hash_memo_n >= HASH_MEMO_MAX then hash_memo = { [seed] = m }; table.clear(m); hash_memo_n = 0 end + h = ImHashStrRaw(str, #str, seed) + m[str] = h + hash_memo_n += 1 + end + return h + end + return ImHashStrRaw(str, size, seed) +end + +function ImHashStrRaw(str, size, seed) if str == "" or size == 0 then return seed -- need to match cpp code edge case behavior while using FNV ourselves diff --git a/imgui/imgui_draw.lua b/imgui/imgui_draw.lua index f141241..034beb1 100644 --- a/imgui/imgui_draw.lua +++ b/imgui/imgui_draw.lua @@ -4641,6 +4641,8 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b local word_wrap_eol local stop = false + local text_is_str = type(text) == "string" + local lookup, lookup_n, glyphs = baked.IndexLookup.Data, baked.IndexLookup.Size, baked.Glyphs.Data while s < text_end do repeat @@ -4662,7 +4664,8 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b end end - local c = ImStrByte(text, s) + local c + if text_is_str then c = string.byte(text, s) else c = text[s] end if c < 0x80 then s = s + 1 else @@ -4687,7 +4690,14 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b end end - local glyph = baked:FindGlyph(c) + local glyph + local gi = (c < lookup_n) and lookup[c + 1] or nil + if gi ~= nil and gi ~= IM_FONTGLYPH_INDEX_NOT_FOUND and gi ~= IM_FONTGLYPH_INDEX_UNUSED then + glyph = glyphs[gi] + else + glyph = baked:FindGlyph(c) -- may load the glyph and grow the tables + lookup, lookup_n, glyphs = baked.IndexLookup.Data, baked.IndexLookup.Size, baked.Glyphs.Data + end local char_width = glyph.AdvanceX * scale if glyph.Visible then