diff --git a/build/ImGui.linemap.json b/build/ImGui.linemap.json index 637e0c3..ed71a5d 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"], [30866, "imgui_h.lua"], [33079, "imgui_impl_roblox.lua"], [33866, "imgui_impl_roblox_fonts.lua"], [40269, "imgui_internal.lua"], [43439, "imgui_tables.lua"], [47786, "imgui_tabs.lua"], [48953, "imgui_widgets.lua"], [57477, "imstb_rectpack.lua"], [57923, "imstb_textedit.lua"], [59164, "imstb_truetype.lua"], [61632, "imstd_minstdio.lua"]] \ No newline at end of file +[[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"], [30866, "imgui_h.lua"], [33079, "imgui_impl_roblox.lua"], [33903, "imgui_impl_roblox_fonts.lua"], [40306, "imgui_internal.lua"], [43476, "imgui_tables.lua"], [47823, "imgui_tabs.lua"], [48990, "imgui_widgets.lua"], [57514, "imstb_rectpack.lua"], [57960, "imstb_textedit.lua"], [59201, "imstb_truetype.lua"], [61669, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/ImGui.luau b/build/ImGui.luau index 52769ab..9a1f60c 100644 --- a/build/ImGui.luau +++ b/build/ImGui.luau @@ -33561,10 +33561,18 @@ local function DestroyTiles(bd) bd.Tiles = {} end +-- MicroProfiler labels, enabled with ImGui_ImplRoblox.SetProfiling(true) +local profilebegin, profileend = debug.profilebegin, debug.profileend +local profiling = false +local function PB(name) if profiling then profilebegin(name) end end +local function PE() if profiling then profileend() end end + @native function ImGui_ImplRoblox_RenderDrawData(draw_data) local bd = ImGui_ImplRoblox_GetBackendData() + PB("ImGui RenderDrawData") + PB("ImGui textures") if draw_data.Textures ~= nil then for _, tex in draw_data.Textures:iter() do if tex.Status ~= ImTextureStatus.OK then @@ -33573,9 +33581,17 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) end end end + PE() + + -- Render rate cap: textures are always updated (imgui expects it), tiles only at bd.RenderRate Hz + if bd.RenderRate > 0 then + local now = os.clock() + if now + 0.002 < bd.NextRender then PE(); return end + bd.NextRender = math.max(bd.NextRender + 1 / bd.RenderRate, now) + end local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y) - if sw <= 0 or sh <= 0 then return end + if sw <= 0 or sh <= 0 then PE(); return end local cols, rows = ceil(sw / TILE), ceil(sh / TILE) if cols ~= bd.TileCols or rows ~= bd.TileRows then DestroyTiles(bd) @@ -33592,6 +33608,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) end -- Pass 1: collect primitives, hash them into tiles + PB("ImGui tile hash") local n = 0 for _, draw_list in draw_data.CmdLists:iter() do local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data @@ -33666,7 +33683,10 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) end end + PE() + -- Pass 2: redraw + upload changed tiles + PB("ImGui raster+upload") local force = bd.ForceRedraw bd.ForceRedraw = false for ty = 0, rows - 1 do @@ -33702,7 +33722,9 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy) end end + PB("ImGui upload") tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf) + PE() end if not tile.Visible then tile.Visible = true @@ -33715,6 +33737,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) -- drop references so old vertex tables can be collected for k = n + 1, bd.LastPrimCount do pr_a[k], pr_b[k], pr_c[k], pr_d[k], pr_t[k] = nil, nil, nil, nil, nil end bd.LastPrimCount = n + PE(); PE() end --------------------------------------------------------- @@ -33738,7 +33761,7 @@ function ImGui_ImplRoblox_Init(parent, render_scale) local bd = { Time = os.clock(), Gui = gui, Connections = {}, Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1, - Tiles = {}, TileCols = 0, TileRows = 0, Scale = render_scale or 1, TilesActive = false, TH1 = {}, TH2 = {}, TN = {}, TL = {}, LastPrimCount = 0, ForceRedraw = true, + Tiles = {}, TileCols = 0, TileRows = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, NextRender = 0, TH1 = {}, TH2 = {}, TN = {}, TL = {}, LastPrimCount = 0, ForceRedraw = true, } io.BackendPlatformUserData = bd @@ -33811,6 +33834,7 @@ function ImGui_ImplRoblox_Shutdown() end function ImGui_ImplRoblox_NewFrame() + PB("ImGui NewFrame (backend)") local io = ImGui.GetIO() local bd = ImGui_ImplRoblox_GetBackendData() @@ -33839,8 +33863,18 @@ function ImGui_ImplRoblox_NewFrame() bd.TilesActive = want for _, tile in pairs(bd.Tiles) do tile.Label.Active = want end end + PE() end +--- Max tile redraws per second (default 60); 0 = every frame. imgui logic still runs every frame. +function ImGui_ImplRoblox_SetRenderRate(hz) + ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0 +end + +--- Toggle MicroProfiler labels (debug.profilebegin) for the backend +function ImGui_ImplRoblox_SetProfiling(enabled) profiling = enabled and true or false end +local function ImGui_ImplRoblox_IsProfiling() return profiling end + function ImGui_ImplRoblox_SetRenderScale(scale) local bd = ImGui_ImplRoblox_GetBackendData() if scale == bd.Scale then return end @@ -33857,6 +33891,9 @@ ImGui_ImplRoblox = { RenderDrawData = ImGui_ImplRoblox_RenderDrawData, AddFile = ImGui_ImplRoblox_AddFile, SetRenderScale = ImGui_ImplRoblox_SetRenderScale, + SetRenderRate = ImGui_ImplRoblox_SetRenderRate, + SetProfiling = ImGui_ImplRoblox_SetProfiling, + IsProfiling = ImGui_ImplRoblox_IsProfiling, } return true -- [Roblox] ModuleScripts must return exactly one value @@ -62244,6 +62281,8 @@ return { ImGui_ImplRoblox_Init = ImGui_ImplRoblox_Init, ImGui_ImplRoblox_NewFrame = ImGui_ImplRoblox_NewFrame, ImGui_ImplRoblox_RenderDrawData = ImGui_ImplRoblox_RenderDrawData, + ImGui_ImplRoblox_SetProfiling = ImGui_ImplRoblox_SetProfiling, + ImGui_ImplRoblox_SetRenderRate = ImGui_ImplRoblox_SetRenderRate, ImGui_ImplRoblox_SetRenderScale = ImGui_ImplRoblox_SetRenderScale, ImGui_ImplRoblox_Shutdown = ImGui_ImplRoblox_Shutdown, ImGui_ImplRoblox_UpdateTexture = ImGui_ImplRoblox_UpdateTexture, diff --git a/imgui/backend/imgui_impl_roblox.lua b/imgui/backend/imgui_impl_roblox.lua index a9918c6..0c24932 100644 --- a/imgui/backend/imgui_impl_roblox.lua +++ b/imgui/backend/imgui_impl_roblox.lua @@ -483,10 +483,18 @@ local function DestroyTiles(bd) bd.Tiles = {} end +-- MicroProfiler labels, enabled with ImGui_ImplRoblox.SetProfiling(true) +local profilebegin, profileend = debug.profilebegin, debug.profileend +local profiling = false +local function PB(name) if profiling then profilebegin(name) end end +local function PE() if profiling then profileend() end end + @native function ImGui_ImplRoblox_RenderDrawData(draw_data) local bd = ImGui_ImplRoblox_GetBackendData() + PB("ImGui RenderDrawData") + PB("ImGui textures") if draw_data.Textures ~= nil then for _, tex in draw_data.Textures:iter() do if tex.Status ~= ImTextureStatus.OK then @@ -495,9 +503,17 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) end end end + PE() + + -- Render rate cap: textures are always updated (imgui expects it), tiles only at bd.RenderRate Hz + if bd.RenderRate > 0 then + local now = os.clock() + if now + 0.002 < bd.NextRender then PE(); return end + bd.NextRender = math.max(bd.NextRender + 1 / bd.RenderRate, now) + end local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y) - if sw <= 0 or sh <= 0 then return end + if sw <= 0 or sh <= 0 then PE(); return end local cols, rows = ceil(sw / TILE), ceil(sh / TILE) if cols ~= bd.TileCols or rows ~= bd.TileRows then DestroyTiles(bd) @@ -514,6 +530,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) end -- Pass 1: collect primitives, hash them into tiles + PB("ImGui tile hash") local n = 0 for _, draw_list in draw_data.CmdLists:iter() do local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data @@ -588,7 +605,10 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) end end + PE() + -- Pass 2: redraw + upload changed tiles + PB("ImGui raster+upload") local force = bd.ForceRedraw bd.ForceRedraw = false for ty = 0, rows - 1 do @@ -624,7 +644,9 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy) end end + PB("ImGui upload") tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf) + PE() end if not tile.Visible then tile.Visible = true @@ -637,6 +659,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data) -- drop references so old vertex tables can be collected for k = n + 1, bd.LastPrimCount do pr_a[k], pr_b[k], pr_c[k], pr_d[k], pr_t[k] = nil, nil, nil, nil, nil end bd.LastPrimCount = n + PE(); PE() end --------------------------------------------------------- @@ -660,7 +683,7 @@ function ImGui_ImplRoblox_Init(parent, render_scale) local bd = { Time = os.clock(), Gui = gui, Connections = {}, Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1, - Tiles = {}, TileCols = 0, TileRows = 0, Scale = render_scale or 1, TilesActive = false, TH1 = {}, TH2 = {}, TN = {}, TL = {}, LastPrimCount = 0, ForceRedraw = true, + Tiles = {}, TileCols = 0, TileRows = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, NextRender = 0, TH1 = {}, TH2 = {}, TN = {}, TL = {}, LastPrimCount = 0, ForceRedraw = true, } io.BackendPlatformUserData = bd @@ -733,6 +756,7 @@ function ImGui_ImplRoblox_Shutdown() end function ImGui_ImplRoblox_NewFrame() + PB("ImGui NewFrame (backend)") local io = ImGui.GetIO() local bd = ImGui_ImplRoblox_GetBackendData() @@ -761,8 +785,18 @@ function ImGui_ImplRoblox_NewFrame() bd.TilesActive = want for _, tile in pairs(bd.Tiles) do tile.Label.Active = want end end + PE() end +--- Max tile redraws per second (default 60); 0 = every frame. imgui logic still runs every frame. +function ImGui_ImplRoblox_SetRenderRate(hz) + ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0 +end + +--- Toggle MicroProfiler labels (debug.profilebegin) for the backend +function ImGui_ImplRoblox_SetProfiling(enabled) profiling = enabled and true or false end +local function ImGui_ImplRoblox_IsProfiling() return profiling end + function ImGui_ImplRoblox_SetRenderScale(scale) local bd = ImGui_ImplRoblox_GetBackendData() if scale == bd.Scale then return end @@ -779,6 +813,9 @@ ImGui_ImplRoblox = { RenderDrawData = ImGui_ImplRoblox_RenderDrawData, AddFile = ImGui_ImplRoblox_AddFile, SetRenderScale = ImGui_ImplRoblox_SetRenderScale, + SetRenderRate = ImGui_ImplRoblox_SetRenderRate, + SetProfiling = ImGui_ImplRoblox_SetProfiling, + IsProfiling = ImGui_ImplRoblox_IsProfiling, } return true -- [Roblox] ModuleScripts must return exactly one value diff --git a/main.client.luau b/main.client.luau index 1983911..fa8751a 100755 --- a/main.client.luau +++ b/main.client.luau @@ -74,10 +74,17 @@ for _, name in ipairs({ "Cousine-Regular", "DroidSans", "Karla-Regular", "Proggy end ImGui_ImplRoblox.Init(nil, render_scale) +ImGui_ImplRoblox.SetRenderRate(60) -- tile redraws per second; imgui logic + your code still run every frame +ImGui_ImplRoblox.SetProfiling(true) -- MicroProfiler (Ctrl+F6) labels; turn off when not profiling + +local function P(name) if ImGui_ImplRoblox.IsProfiling() then debug.profilebegin(name) end end +local function PE() if ImGui_ImplRoblox.IsProfiling() then debug.profileend() end end RunService.Stepped:Connect(function() + P("ImGui frame") ImGui_ImplRoblox.NewFrame() - ImGui.NewFrame() + P("ImGui.NewFrame"); ImGui.NewFrame(); PE() + P("ImGui UI") -- Dockspace covering the whole screen; the centre stays transparent so the game shows through ImGui.DockSpaceOverViewport(0, nil, G.ImGuiDockNodeFlags.PassthruCentralNode) @@ -119,7 +126,8 @@ RunService.Stepped:Connect(function() ImGui.End() end - ImGui.EndFrame() - ImGui.Render() + PE() + P("ImGui.Render"); ImGui.EndFrame(); ImGui.Render(); PE() ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData()) + PE() end) diff --git a/tools/test/roblox_mock.luau b/tools/test/roblox_mock.luau index cb51e4c..6bfb0d2 100644 --- a/tools/test/roblox_mock.luau +++ b/tools/test/roblox_mock.luau @@ -1,3 +1,4 @@ +debug = setmetatable({ profilebegin = function() end, profileend = function() end }, { __index = debug }) -- Minimal Roblox mock for running the imgui Roblox backend + the real CanvasDraw module in the Luau CLI. local realG = {} -- (no getfenv: it would mark the global env unsafe and deoptimize everything) FAKE_TIME = 0