Perf: ColorEdit4 layout-only fast path for fully clipped rows, cached drag/slider value text, @native on color/group widgets; t_widget_cost + t_colors_hash

This commit is contained in:
4DBug
2026-10-08 12:31:10 -05:00
parent 0e06d846cf
commit 06523e5417
7 changed files with 210 additions and 6 deletions
+32
View File
@@ -0,0 +1,32 @@
-- Checksum of draw data + layout of Style Editor > Colors while scrolling its list (fast-path equivalence check)
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local realBTI = ImGui.BeginTabItem
ImGui.BeginTabItem = function(label, p_open, flags)
if label == "Colors" then flags = bit32.bor(flags or 0, G.ImGuiTabItemFlags.SetSelected) end
return realBTI(label, p_open, flags)
end
local frame, h = 0, 0
local function mix(v) h = (h * 31 + math.floor(v * 64 + 0.5)) % 2147483647 end
game:GetService("RunService").Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
io.DeltaTime = 1 / 60
io:AddMousePosEvent(200, 400 + (frame % 5) * 13)
if frame > 5 then io:AddMouseWheelEvent(0, (frame % 60 < 35) and -1 or 1) end
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(20, 20)); ImGui.SetNextWindowSize(ImVec2(550, 700))
ImGui.Begin("Style Editor"); ImGui.ShowStyleEditor(); ImGui.End()
ImGui.Render()
for _, dl in ImGui.GetDrawData().CmdLists:iter() do
local vb, id = dl.VtxBuffer.Buf, dl.IdxBuffer.Data
for i = 1, dl.VtxBuffer.Size do local o = (i - 1) * 36; mix(buffer.readf64(vb, o)); mix(buffer.readf64(vb, o + 8)); mix(buffer.readu32(vb, o + 32) / 64) end
for i = 1, dl.IdxBuffer.Size do mix(id[i]) end
end
for _, w in ipairs(ImGui.GetCurrentContext().Windows.Data) do mix(w.ContentSize.x); mix(w.ContentSize.y); mix(w.Scroll.y); mix(w.ScrollMax.y) end
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if frame == ARG_FRAMES - 1 then print("COLORSHASH", h) end
end)
+71
View File
@@ -0,0 +1,71 @@
-- Per-widget cost: wraps every ImGui.* function with self-time timing, shows the Style Editor on its Colors tab
-- (or the demo with --open-only ...), prints a table sorted by total self time and by cost per call.
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local STYLE = type(ARG_OPEN) ~= "table"
local realBTI = ImGui.BeginTabItem
ImGui.BeginTabItem = function(label, p_open, flags)
if label == "Colors" and STYLE then flags = bit32.bor(flags or 0, G.ImGuiTabItemFlags.SetSelected) end
return realBTI(label, p_open, flags)
end
local self_t, calls = {}, {}
local stack_child = {} -- time spent in nested wrapped calls, per depth
local depth, enabled = 0, false
local clock = os.realclock or os.clock -- harness fakes os.clock; realclock is the real one
for name, fn in pairs(ImGui) do
if type(fn) == "function" and name ~= "NewFrame" and name ~= "Render" and name ~= "EndFrame" then
ImGui[name] = function(...)
if not enabled then return fn(...) end
depth += 1; stack_child[depth] = 0
local t0 = clock()
local r = table.pack(fn(...))
local dt = clock() - t0
local own = dt - stack_child[depth]
depth -= 1
if depth > 0 then stack_child[depth] += dt end
self_t[name] = (self_t[name] or 0) + own; calls[name] = (calls[name] or 0) + 1
return table.unpack(r, 1, r.n)
end
end
end
local frame = 0
game:GetService("RunService").Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
io:AddMousePosEvent(5, 5)
ImGui.NewFrame()
enabled = frame > 10
if STYLE then
ImGui.SetNextWindowPos(ImVec2(20, 20)); ImGui.SetNextWindowSize(ImVec2(550, 900))
ImGui.Begin("Style Editor"); ImGui.ShowStyleEditor(); ImGui.End()
else
ImGui.ShowDemoWindow(true)
end
enabled = false
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if frame == ARG_FRAMES - 1 then
local nf = frame - 10
local names = {}
local total = 0
for k, v in pairs(self_t) do table.insert(names, k); total += v end
table.sort(names, function(a, b) return self_t[a] > self_t[b] end)
print(string.format("TOTAL %.3f ms/frame (wrapped calls, incl. wrapper overhead)", total / nf * 1000))
print("== by self time per frame")
for i = 1, math.min(20, #names) do local k = names[i]
print(string.format("%7.3f ms %6.1f%% %5d calls %6.1f us/call %s", self_t[k] / nf * 1000, 100 * self_t[k] / total, calls[k] / nf, self_t[k] / calls[k] * 1e6, k)) end
table.sort(names, function(a, b) return self_t[a] / calls[a] > self_t[b] / calls[b] end)
print("== by cost per call (>= 1 call/frame)")
local shown = 0
for _, k in ipairs(names) do
if calls[k] >= nf and shown < 15 then shown += 1
print(string.format("%6.1f us/call %5d calls %s", self_t[k] / calls[k] * 1e6, calls[k] / nf, k)) end
end
end
end)