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:
@@ -160,7 +160,7 @@ ROBLOX = setmetatable({
|
||||
Color3 = { new = function(r, g, b) return { R = r or 0, G = g or 0, B = b or 0 } end },
|
||||
Content = { fromObject = function(o) return { Object = o } end },
|
||||
warn = function(...) print("WARN", ...) end,
|
||||
os = { clock = function() return FAKE_TIME end, time = os.time },
|
||||
os = { clock = function() return FAKE_TIME end, time = os.time, realclock = os.clock },
|
||||
}, { __index = realG })
|
||||
|
||||
local mod = newInst("ModuleScript", "ImGui", ReplicatedStorage); rawset(mod, "_src", "ImGui.luau")
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user