Roblox backend: redraw budget is opt-in (SetRedrawBudget default 0 = every changed tile drawn the same frame)

This commit is contained in:
4DBug
2026-10-15 00:55:43 -05:00
parent 15dc766d82
commit 4ac57cb571
5 changed files with 11 additions and 9 deletions
+2 -2
View File
@@ -152,7 +152,7 @@ Backend options:
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). | | `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. | | `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `0` = every frame). Your code still runs every frame. | | `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `0` = every frame). Your code still runs every frame. |
| `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (default 1.5, `0` = no limit). Tiles over the budget keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame. | | `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (opt-in; default `0` = no limit, every change is drawn the same frame). With a budget, tiles over it keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame (parts of the UI may update a frame or two apart). |
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). | | `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. | | `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. | | `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
@@ -179,7 +179,7 @@ The cost is CPU time in Luau, so the backend is built around **not drawing**:
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers. 3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked. 4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer. 5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
6. **Redraw budget.** Changed tiles are redrawn every frame within `SetRedrawBudget(ms)` (default 1.5 ms); the rest keep their old pixels and follow on the next frames. Optionally capped with `SetRenderRate(hz)` / `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped. 6. **Redraw budget.** Changed tiles are redrawn in the frame they change. Optionally, `SetRedrawBudget(ms)` caps the redraw time per frame; tiles over it keep their old pixels and follow on the next frames. Optionally capped with `SetRenderRate(hz)` / `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too). **Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
+2 -2
View File
@@ -149,7 +149,7 @@ Backend options:
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). | | `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. | | `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `0` = every frame). Your code still runs every frame. | | `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `0` = every frame). Your code still runs every frame. |
| `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (default 1.5, `0` = no limit). Tiles over the budget keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame. | | `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (opt-in; default `0` = no limit, every change is drawn the same frame). With a budget, tiles over it keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame (parts of the UI may update a frame or two apart). |
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). | | `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. | | `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. | | `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
@@ -176,7 +176,7 @@ The cost is CPU time in Luau, so the backend is built around **not drawing**:
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers. 3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked. 4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer. 5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
6. **Redraw budget.** Changed tiles are redrawn every frame within `SetRedrawBudget(ms)` (default 1.5 ms); the rest keep their old pixels and follow on the next frames. Optionally capped with `SetRenderRate(hz)` / `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped. 6. **Redraw budget.** Changed tiles are redrawn in the frame they change. Optionally, `SetRedrawBudget(ms)` caps the redraw time per frame; tiles over it keep their old pixels and follow on the next frames. Optionally capped with `SetRenderRate(hz)` / `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too). **Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
+3 -2
View File
@@ -1353,7 +1353,7 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
local bd = { local bd = {
Time = os.clock(), Gui = gui, Connections = {}, Time = os.clock(), Gui = gui, Connections = {},
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1, Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 0, BusyRenderRate = 0, RedrawBudget = 0.0015, HB = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true, Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 0, BusyRenderRate = 0, RedrawBudget = 0, HB = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
} }
io.BackendPlatformUserData = bd io.BackendPlatformUserData = bd
@@ -1593,7 +1593,8 @@ function ImGui_ImplRoblox_NewFrame()
PE() PE()
end end
--- Max milliseconds per frame spent redrawing changed tiles (default 1.5; 0 = no limit). Tiles over the budget keep --- Opt-in: max milliseconds per frame spent redrawing changed tiles (default 0 = no limit, every change is drawn the
--- same frame). With a budget (e.g. 1.5), tiles over it keep
--- their previous pixels and are redrawn on the following frames (each one at most a few frames late), so a big change --- their previous pixels and are redrawn on the following frames (each one at most a few frames late), so a big change
--- (resizing a large window, opening one, scrolling a long list) is spread over frames instead of one long frame. --- (resizing a large window, opening one, scrolling a long list) is spread over frames instead of one long frame.
function ImGui_ImplRoblox_SetRedrawBudget(ms) function ImGui_ImplRoblox_SetRedrawBudget(ms)
+1 -1
View File
@@ -1 +1 @@
[[46, "imgui.lua"], [21389, "imgui_demo.lua"], [29807, "imgui_draw.lua"], [35745, "imgui_h.lua"], [38412, "imgui_impl_roblox.lua"], [40059, "imgui_impl_roblox_fonts.lua"], [46462, "imgui_internal.lua"], [49931, "imgui_tables.lua"], [54990, "imgui_widgets.lua"], [65630, "imstb_rectpack.lua"], [66294, "imstb_textedit.lua"], [67836, "imstb_truetype.lua"], [70624, "imstd_minstdio.lua"]] [[46, "imgui.lua"], [21389, "imgui_demo.lua"], [29807, "imgui_draw.lua"], [35745, "imgui_h.lua"], [38412, "imgui_impl_roblox.lua"], [40060, "imgui_impl_roblox_fonts.lua"], [46463, "imgui_internal.lua"], [49932, "imgui_tables.lua"], [54991, "imgui_widgets.lua"], [65631, "imstb_rectpack.lua"], [66295, "imstb_textedit.lua"], [67837, "imstb_truetype.lua"], [70625, "imstd_minstdio.lua"]]
+3 -2
View File
@@ -39764,7 +39764,7 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
local bd = { local bd = {
Time = os.clock(), Gui = gui, Connections = {}, Time = os.clock(), Gui = gui, Connections = {},
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1, Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 0, BusyRenderRate = 0, RedrawBudget = 0.0015, HB = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true, Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 0, BusyRenderRate = 0, RedrawBudget = 0, HB = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
} }
io.BackendPlatformUserData = bd io.BackendPlatformUserData = bd
@@ -40004,7 +40004,8 @@ function ImGui_ImplRoblox_NewFrame()
PE() PE()
end end
--- Max milliseconds per frame spent redrawing changed tiles (default 1.5; 0 = no limit). Tiles over the budget keep --- Opt-in: max milliseconds per frame spent redrawing changed tiles (default 0 = no limit, every change is drawn the
--- same frame). With a budget (e.g. 1.5), tiles over it keep
--- their previous pixels and are redrawn on the following frames (each one at most a few frames late), so a big change --- their previous pixels and are redrawn on the following frames (each one at most a few frames late), so a big change
--- (resizing a large window, opening one, scrolling a long list) is spread over frames instead of one long frame. --- (resizing a large window, opening one, scrolling a long list) is spread over frames instead of one long frame.
function ImGui_ImplRoblox_SetRedrawBudget(ms) function ImGui_ImplRoblox_SetRedrawBudget(ms)