Roblox backend: window content in its own clipped layer at the scroll origin (scrolling moves pixels instead of redrawing), 128px tiles

This commit is contained in:
4DBug
2026-10-08 11:52:42 -05:00
parent c4816a4237
commit b19fe61236
6 changed files with 499 additions and 312 deletions
+1 -1
View File
@@ -1 +1 @@
[[12, "imgui.lua"], [13795, "imgui_demo.lua"], [14728, "imgui_demo_2.lua"], [17803, "imgui_demo_apps.lua"], [19062, "imgui_demo_tools.lua"], [20388, "imgui_demo_widgets.lua"], [22312, "imgui_docking.lua"], [25440, "imgui_dragdrop.lua"], [25732, "imgui_draw.lua"], [30979, "imgui_h.lua"], [33153, "imgui_impl_roblox.lua"], [34140, "imgui_impl_roblox_fonts.lua"], [40543, "imgui_internal.lua"], [43715, "imgui_tables.lua"], [48062, "imgui_tabs.lua"], [49229, "imgui_widgets.lua"], [57763, "imstb_rectpack.lua"], [58209, "imstb_textedit.lua"], [59450, "imstb_truetype.lua"], [61918, "imstd_minstdio.lua"]] [[12, "imgui.lua"], [13799, "imgui_demo.lua"], [14732, "imgui_demo_2.lua"], [17807, "imgui_demo_apps.lua"], [19066, "imgui_demo_tools.lua"], [20392, "imgui_demo_widgets.lua"], [22316, "imgui_docking.lua"], [25444, "imgui_dragdrop.lua"], [25736, "imgui_draw.lua"], [30984, "imgui_h.lua"], [33158, "imgui_impl_roblox.lua"], [34231, "imgui_impl_roblox_fonts.lua"], [40634, "imgui_internal.lua"], [43806, "imgui_tables.lua"], [48153, "imgui_tabs.lua"], [49320, "imgui_widgets.lua"], [57854, "imstb_rectpack.lua"], [58300, "imstb_textedit.lua"], [59541, "imstb_truetype.lua"], [62009, "imstd_minstdio.lua"]]
+237 -146
View File
@@ -7161,6 +7161,10 @@ function ImGui.Begin(name, open, flags)
if bit32.band(flags, ImGuiWindowFlags.DockNodeHost) == 0 and not window.SkipRefresh then if bit32.band(flags, ImGuiWindowFlags.DockNodeHost) == 0 and not window.SkipRefresh then
ImGui.PushClipRect(window.InnerClipRect.Min, window.InnerClipRect.Max, true) ImGui.PushClipRect(window.InnerClipRect.Min, window.InnerClipRect.Max, true)
-- [Roblox backend] content clip + scroll origin: lets the backend move scrolled content instead of redrawing it
local dl, cr = window.DrawList, window.DrawList._CmdHeader.ClipRect
dl._ContentClipX0, dl._ContentClipY0, dl._ContentClipX1, dl._ContentClipY1 = cr.x, cr.y, cr.z, cr.w
dl._ScrollOriginX, dl._ScrollOriginY = window.Pos.x - window.Scroll.x, window.Pos.y - window.Scroll.y
end end
window.WriteAccessed = false window.WriteAccessed = false
@@ -28889,6 +28893,7 @@ end
-- TODO: -- TODO:
function MT.ImDrawList:_ResetForNewFrame() function MT.ImDrawList:_ResetForNewFrame()
self._ContentClipX0 = nil -- set again by Begin() (Roblox backend scroll reuse)
if self._Splitter._Count > 1 then if self._Splitter._Count > 1 then
self._Splitter:Merge(self) self._Splitter:Merge(self)
end end
@@ -33573,7 +33578,7 @@ end
-- relative to the layer's origin (top-left of its content). Primitives are hashed into tiles in layer-relative -- relative to the layer's origin (top-left of its content). Primitives are hashed into tiles in layer-relative
-- coordinates, so moving a window only moves its Frame: no tile changes, nothing re-rasterized or uploaded. -- coordinates, so moving a window only moves its Frame: no tile changes, nothing re-rasterized or uploaded.
-- Only tiles whose hash changed are redrawn; the hash covers everything that affects a tile's pixels. -- Only tiles whose hash changed are redrawn; the hash covers everything that affects a tile's pixels.
local TILE = 256 local TILE = 128
TILE_SIZE = Vector2.new(TILE, TILE) TILE_SIZE = Vector2.new(TILE, TILE)
local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile (collision odds ~2^-62) local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile (collision odds ~2^-62)
local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide) local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide)
@@ -33585,6 +33590,8 @@ local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (p
local pr_cx0, pr_cy0, pr_cx1, pr_cy1 = {}, {}, {}, {} -- clip (screen pixels) local pr_cx0, pr_cy0, pr_cx1, pr_cy1 = {}, {}, {}, {} -- clip (screen pixels)
local pr_bx0, pr_by0, pr_bx1, pr_by1 = {}, {}, {}, {} -- clipped bounds (screen pixels) local pr_bx0, pr_by0, pr_bx1, pr_by1 = {}, {}, {}, {} -- clipped bounds (screen pixels)
local pr_tex = {} -- texture id local pr_tex = {} -- texture id
local pr_k = {} -- layer kind (1..3)
local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind
@native @native
local function HashVertex(h1, h2, v, ox, oy) local function HashVertex(h1, h2, v, ox, oy)
@@ -33647,22 +33654,43 @@ local function DrawRectGradient(x0, y0, x1, y1, ca, cb, kind, t, u, v, cx0, cy0,
end end
end end
local function GetLayer(bd, draw_list) -- Layer kinds per draw list: 1 = drawn before the window content (background, title bar, borders, scrollbar),
local layer = bd.Layers[draw_list] -- 2 = window content (positioned at the scroll origin inside a ClipsDescendants frame, so scrolling just moves it),
if layer then return layer end -- 3 = anything not clipped to the content drawn after content started (menu bar, overlays). Stacked 1 < 2 < 3.
local function NewFrame(parent, clips)
local frame = Instance.new("Frame") local frame = Instance.new("Frame")
frame.Name = "Layer" frame.Name = "Layer"
frame.BackgroundTransparency = 1 frame.BackgroundTransparency = 1
frame.BorderSizePixel = 0 frame.BorderSizePixel = 0
frame.Size = UDim2.fromOffset(0, 0) frame.Size = UDim2.fromOffset(0, 0)
frame.ClipsDescendants = clips
frame.Visible = false frame.Visible = false
frame.Parent = bd.Gui frame.Parent = parent
layer = { Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, Touched = {}, NTouched = 0, return frame
Visible = false, X = false, Y = false, Z = -1, LastUsed = 0 } end
bd.Layers[draw_list] = layer
local function GetLayer(bd, draw_list, kind)
local set = bd.Layers[draw_list]
if not set then set = { LastUsed = 0 }; bd.Layers[draw_list] = set end
local layer = set[kind]
if layer then return layer end
local root, frame
if kind == 2 then
root = NewFrame(bd.Gui, true)
frame = NewFrame(root, false); frame.Visible = true
else
frame = NewFrame(bd.Gui, false); root = frame
end
layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0,
Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false }
set[kind] = layer
return layer return layer
end end
local function HideLayer(layer)
if layer.Visible then layer.Visible = false; layer.Root.Visible = false end
end
local function GetTile(bd, layer, key, tx, ty) local function GetTile(bd, layer, key, tx, ty)
local tile = layer.Tiles[key] local tile = layer.Tiles[key]
if tile then return tile end if tile then return tile end
@@ -33685,13 +33713,18 @@ local function GetTile(bd, layer, key, tx, ty)
return tile return tile
end end
local function DestroyLayer(layer) local function DestroyLayerSet(set)
for kind = 1, 3 do
local layer = set[kind]
if layer then
for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end
layer.Frame:Destroy() layer.Root:Destroy()
end
end
end end
local function DestroyTiles(bd) local function DestroyTiles(bd)
for _, layer in pairs(bd.Layers) do DestroyLayer(layer) end for _, set in pairs(bd.Layers) do DestroyLayerSet(set) end
bd.Layers = {} bd.Layers = {}
end end
@@ -33702,134 +33735,15 @@ local function PB(name) if profiling then profilebegin(name) end end
local function PE() if profiling then profileend() end end local function PE() if profiling then profileend() end end
@native @native
function ImGui_ImplRoblox_RenderDrawData(draw_data) local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
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
ImGui_ImplRoblox_UpdateTexture(tex)
bd.ForceRedraw = true -- texture contents changed: tiles sampling it must be redrawn
end
end
end
PE()
-- Render rate cap: textures are always updated (imgui expects it), tiles only at bd.RenderRate Hz
-- Optional lower cap while scrolling / resizing / moving a window (SetBusyRenderRate, off by default)
local rate = bd.RenderRate
if bd.BusyRenderRate > 0 then
local g = ImGui.GetCurrentContext()
local busy = g.WheelingWindow ~= nil or g.MovingWindow ~= nil -- (io.MouseWheel is already cleared by EndFrame)
if not busy and g.ActiveId ~= 0 then
local w = g.ActiveIdWindow
local cur = g.MouseCursor -- ResizeAll..ResizeNWSE while a resize grip/border is held
busy = (cur >= ImGuiMouseCursor.ResizeAll and cur <= ImGuiMouseCursor.ResizeNWSE)
or (w ~= nil and (g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.X) or g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.Y)))
end
if busy and (rate <= 0 or bd.BusyRenderRate < rate) then rate = bd.BusyRenderRate end
end
if rate > 0 then
local now = os.clock()
if now + 0.002 < bd.NextRender then PE(); return end
bd.NextRender = math.max(bd.NextRender + 1 / rate, now)
end
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
if sw <= 0 or sh <= 0 then PE(); return end
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
local lim_x0, lim_y0, lim_x1, lim_y1 = -CLIP_PAD, -CLIP_PAD, sw + CLIP_PAD, sh + CLIP_PAD
bd.FrameNo += 1
local frame_no = bd.FrameNo
local force = bd.ForceRedraw
bd.ForceRedraw = false
local scale = bd.Scale local scale = bd.Scale
local n = 0
local z = 0
for _, draw_list in draw_data.CmdLists:iter() do
-- Pass 1: collect this list's primitives and their bounds
PB("ImGui collect")
local first = n + 1
local lx0, ly0 = math.huge, math.huge
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd)
elseif pcmd.ElemCount > 0 then
local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0)
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1)
if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id]
local vo = pcmd.VtxOffset
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
while i <= last do
local ia, ic = idx[i], idx[i + 2]
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
local vd = false
local gk = false
local pa, pb, pc = va[1], vb[1], vc[1]
local bx0, by0, bx1, by1
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
local d = vtx[vo + idx[i + 5]]
local pd = d[1]
-- axis aligned quad (TL, TR, BR, BL)
if pa.y == pb.y and pb.x == pc.x and pc.y == pd.y and pd.x == pa.x and pa.x < pb.x and pa.y < pd.y then
local col = va[3]
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
if vb[3] == col and vc[3] == col and d[3] == col then
if tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y then vd = d end
elseif ta.x == tb.x and ta.x == tc.x and ta.x == td.x and ta.y == tb.y and ta.y == tc.y and ta.y == td.y then
-- constant uv, linear gradient: vertical (top/bottom pairs equal) or horizontal (left/right pairs equal)
if vb[3] == col and d[3] == vc[3] then vd = d; gk = 1
elseif d[3] == col and vb[3] == vc[3] then vd = d; gk = 2 end
end
if vd then
bx0, by0 = ceil(pa.x - ox - 0.5), ceil(pa.y - oy - 0.5)
bx1, by1 = ceil(pc.x - ox - 0.5), ceil(pc.y - oy - 0.5)
end
end
end
if vd then
i = i + 6
else
bx0, by0 = floor(min(pa.x, pb.x, pc.x) - ox), floor(min(pa.y, pb.y, pc.y) - oy)
bx1, by1 = ceil(max(pa.x, pb.x, pc.x) - ox) + 1, ceil(max(pa.y, pb.y, pc.y) - oy) + 1
i = i + 3
end
if bx0 < cx0 then bx0 = cx0 end
if by0 < cy0 then by0 = cy0 end
if bx1 > cx1 then bx1 = cx1 end
if by1 > cy1 then by1 = cy1 end
if bx0 < bx1 and by0 < by1 then
n = n + 1
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n], pr_g[n], pr_tex[n] = va, vb, vc, vd, t, gk, tex_id
pr_cx0[n], pr_cy0[n], pr_cx1[n], pr_cy1[n] = cx0, cy0, cx1, cy1
pr_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
if bx0 < lx0 then lx0 = bx0 end
if by0 < ly0 then ly0 = by0 end
end
end
end
end
end
PE()
local layer = GetLayer(bd, draw_list)
layer.LastUsed = frame_no
if n < first then
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end
else
-- Pass 2: hash primitives into layer-relative tiles -- Pass 2: hash primitives into layer-relative tiles
PB("ImGui tile hash") PB("ImGui tile hash")
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
for k = 1, layer.NTouched do tn[touched[k]] = 0 end for k = 1, layer.NTouched do tn[touched[k]] = 0 end
local nt = 0 local nt = 0
for p = first, n do for pi = 1, np do
local p = plist[pi]
local tex_id = pr_tex[p] local tex_id = pr_tex[p]
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative) -- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved. -- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
@@ -33852,6 +33766,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
if not tl[key] then tl[key] = {} end if not tl[key] then tl[key] = {} end
nt = nt + 1 nt = nt + 1
touched[nt] = key touched[nt] = key
layer.TX[key], layer.TY[key] = tx, ty
end end
k = k + 1 k = k + 1
tn[key] = k tn[key] = k
@@ -33865,14 +33780,25 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
PE() PE()
-- Place the layer (this is all a window move costs) -- Place the layer (this is all a window move costs)
local frame = layer.Frame local root = layer.Root
if root ~= layer.Frame then
-- content: clip frame at the content clip rect, tiles frame at the scroll origin relative to it
if layer.CX0 ~= clip_x0 or layer.CY0 ~= clip_y0 or layer.CX1 ~= clip_x1 or layer.CY1 ~= clip_y1 then
layer.CX0, layer.CY0, layer.CX1, layer.CY1 = clip_x0, clip_y0, clip_x1, clip_y1
root.Position = UDim2.fromOffset(clip_x0 / scale, clip_y0 / scale)
root.Size = UDim2.fromOffset((clip_x1 - clip_x0) / scale, (clip_y1 - clip_y0) / scale)
layer.X = false
end
if layer.X ~= lx0 or layer.Y ~= ly0 then if layer.X ~= lx0 or layer.Y ~= ly0 then
layer.X, layer.Y = lx0, ly0 layer.X, layer.Y = lx0, ly0
frame.Position = UDim2.fromOffset(lx0 / scale, ly0 / scale) layer.Frame.Position = UDim2.fromOffset((lx0 - clip_x0) / scale, (ly0 - clip_y0) / scale)
end end
z = z + 1 elseif layer.X ~= lx0 or layer.Y ~= ly0 then
if layer.Z ~= z then layer.Z = z; frame.ZIndex = z end layer.X, layer.Y = lx0, ly0
if not layer.Visible then layer.Visible = true; frame.Visible = true end root.Position = UDim2.fromOffset(lx0 / scale, ly0 / scale)
end
if layer.Z ~= z then layer.Z = z; root.ZIndex = z end
if not layer.Visible then layer.Visible = true; root.Visible = true end
-- Pass 3: redraw + upload changed tiles, hide unused ones -- Pass 3: redraw + upload changed tiles, hide unused ones
PB("ImGui raster+upload") PB("ImGui raster+upload")
@@ -33884,8 +33810,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
for kk = 1, nt do for kk = 1, nt do
local key = touched[kk] local key = touched[kk]
local count = tn[key] local count = tn[key]
local tx, ty = key % KEY_W, floor(key / KEY_W) local tx, ty = layer.TX[key], layer.TY[key]
if tx < 0 then tx = tx + KEY_W; ty = ty - 1 end
local tile = GetTile(bd, layer, key, tx, ty) local tile = GetTile(bd, layer, key, tx, ty)
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then
tile.H1, tile.H2 = th1[key], th2[key] tile.H1, tile.H2 = th1[key], th2[key]
@@ -33952,14 +33877,180 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
PE() PE()
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
ImGui_ImplRoblox_UpdateTexture(tex)
bd.ForceRedraw = true -- texture contents changed: tiles sampling it must be redrawn
end
end
end
PE()
-- Render rate cap: textures are always updated (imgui expects it), tiles only at bd.RenderRate Hz
-- Optional lower cap while scrolling / resizing / moving a window (SetBusyRenderRate, off by default)
local rate = bd.RenderRate
if bd.BusyRenderRate > 0 then
local g = ImGui.GetCurrentContext()
local busy = g.WheelingWindow ~= nil or g.MovingWindow ~= nil -- (io.MouseWheel is already cleared by EndFrame)
if not busy and g.ActiveId ~= 0 then
local w = g.ActiveIdWindow
local cur = g.MouseCursor -- ResizeAll..ResizeNWSE while a resize grip/border is held
busy = (cur >= ImGuiMouseCursor.ResizeAll and cur <= ImGuiMouseCursor.ResizeNWSE)
or (w ~= nil and (g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.X) or g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.Y)))
end
if busy and (rate <= 0 or bd.BusyRenderRate < rate) then rate = bd.BusyRenderRate end
end
if rate > 0 then
local now = os.clock()
if now + 0.002 < bd.NextRender then PE(); return end
bd.NextRender = math.max(bd.NextRender + 1 / rate, now)
end
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
if sw <= 0 or sh <= 0 then PE(); return end
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
local lim_x0, lim_y0, lim_x1, lim_y1 = -CLIP_PAD, -CLIP_PAD, sw + CLIP_PAD, sh + CLIP_PAD
bd.FrameNo += 1
local frame_no = bd.FrameNo
local force = bd.ForceRedraw
bd.ForceRedraw = false
local n = 0
local z = 0
for _, draw_list in draw_data.CmdLists:iter() do
-- Pass 1: collect this list's primitives and their bounds
PB("ImGui collect")
local first = n + 1
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
local ccx0, ccy0, ccx1, ccy1 = draw_list._ContentClipX0, draw_list._ContentClipY0, draw_list._ContentClipX1, draw_list._ContentClipY1
local content_x0, content_y0, content_x1, content_y1
if ccx0 then
content_x0, content_y0 = max(floor(ccx0 - ox), lim_x0), max(floor(ccy0 - oy), lim_y0)
content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1)
end
local seen_content = false
for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd)
elseif pcmd.ElemCount > 0 then
local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0)
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1)
local kind = 1
if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then
kind = 2; seen_content = true
if clip.x == ccx0 and clip.y == ccy0 and clip.z == ccx1 and clip.w == ccy1 then
-- clipped by the layer's clip frame instead: partly visible rows stay hash-stable while scrolling
cx0, cy0, cx1, cy1 = lim_x0, lim_y0, lim_x1, lim_y1
end
elseif seen_content then
kind = 3
end
if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id]
local vo = pcmd.VtxOffset
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
while i <= last do
local ia, ic = idx[i], idx[i + 2]
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
local vd = false
local gk = false
local pa, pb, pc = va[1], vb[1], vc[1]
local bx0, by0, bx1, by1
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
local d = vtx[vo + idx[i + 5]]
local pd = d[1]
-- axis aligned quad (TL, TR, BR, BL)
if pa.y == pb.y and pb.x == pc.x and pc.y == pd.y and pd.x == pa.x and pa.x < pb.x and pa.y < pd.y then
local col = va[3]
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
if vb[3] == col and vc[3] == col and d[3] == col then
if tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y then vd = d end
elseif ta.x == tb.x and ta.x == tc.x and ta.x == td.x and ta.y == tb.y and ta.y == tc.y and ta.y == td.y then
-- constant uv, linear gradient: vertical (top/bottom pairs equal) or horizontal (left/right pairs equal)
if vb[3] == col and d[3] == vc[3] then vd = d; gk = 1
elseif d[3] == col and vb[3] == vc[3] then vd = d; gk = 2 end
end
if vd then
bx0, by0 = ceil(pa.x - ox - 0.5), ceil(pa.y - oy - 0.5)
bx1, by1 = ceil(pc.x - ox - 0.5), ceil(pc.y - oy - 0.5)
end
end
end
if vd then
i = i + 6
else
bx0, by0 = floor(min(pa.x, pb.x, pc.x) - ox), floor(min(pa.y, pb.y, pc.y) - oy)
bx1, by1 = ceil(max(pa.x, pb.x, pc.x) - ox) + 1, ceil(max(pa.y, pb.y, pc.y) - oy) + 1
i = i + 3
end
if bx0 < cx0 then bx0 = cx0 end
if by0 < cy0 then by0 = cy0 end
if bx1 > cx1 then bx1 = cx1 end
if by1 > cy1 then by1 = cy1 end
if bx0 < bx1 and by0 < by1 then
n = n + 1
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n], pr_g[n], pr_tex[n] = va, vb, vc, vd, t, gk, tex_id
pr_cx0[n], pr_cy0[n], pr_cx1[n], pr_cy1[n] = cx0, cy0, cx1, cy1
pr_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
pr_k[n] = kind
end
end
end
end
end
PE()
-- Split into layer kinds and place each one
local set = bd.Layers[draw_list]
local nk1, nk2, nk3 = 0, 0, 0
local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
for p = first, n do
local k = pr_k[p]
if k == 1 then
nk1 += 1; plist1[nk1] = p
if pr_bx0[p] < o1x then o1x = pr_bx0[p] end
if pr_by0[p] < o1y then o1y = pr_by0[p] end
elseif k == 2 then
nk2 += 1; plist2[nk2] = p
else
nk3 += 1; plist3[nk3] = p
if pr_bx0[p] < o3x then o3x = pr_bx0[p] end
if pr_by0[p] < o3y then o3y = pr_by0[p] end
end
end
if nk1 > 0 then
z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z)
elseif set and set[1] then HideLayer(set[1]) end
if nk2 > 0 then
z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z,
content_x0, content_y0, content_x1, content_y1)
elseif set and set[2] then HideLayer(set[2]) end
if nk3 > 0 then
z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z)
elseif set and set[3] then HideLayer(set[3]) end
set = bd.Layers[draw_list]
if set then set.LastUsed = frame_no end
end end
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips) -- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
for draw_list, layer in pairs(bd.Layers) do for draw_list, set in pairs(bd.Layers) do
if layer.LastUsed ~= frame_no then if set.LastUsed ~= frame_no then
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end
if frame_no - layer.LastUsed > LAYER_KEEP_FRAMES then if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then
DestroyLayer(layer) DestroyLayerSet(set)
bd.Layers[draw_list] = nil bd.Layers[draw_list] = nil
end end
end end
@@ -34093,7 +34184,7 @@ function ImGui_ImplRoblox_NewFrame()
local want = io.WantCaptureMouse local want = io.WantCaptureMouse
if want ~= bd.TilesActive then if want ~= bd.TilesActive then
bd.TilesActive = want bd.TilesActive = want
for _, layer in pairs(bd.Layers) do for _, tile in pairs(layer.Tiles) do tile.Label.Active = want end end for _, set in pairs(bd.Layers) do for kind = 1, 3 do local layer = set[kind]; if layer then for _, tile in pairs(layer.Tiles) do tile.Label.Active = want end end end end
end end
PE() PE()
end end
+232 -146
View File
@@ -421,7 +421,7 @@ end
-- relative to the layer's origin (top-left of its content). Primitives are hashed into tiles in layer-relative -- relative to the layer's origin (top-left of its content). Primitives are hashed into tiles in layer-relative
-- coordinates, so moving a window only moves its Frame: no tile changes, nothing re-rasterized or uploaded. -- coordinates, so moving a window only moves its Frame: no tile changes, nothing re-rasterized or uploaded.
-- Only tiles whose hash changed are redrawn; the hash covers everything that affects a tile's pixels. -- Only tiles whose hash changed are redrawn; the hash covers everything that affects a tile's pixels.
local TILE = 256 local TILE = 128
TILE_SIZE = Vector2.new(TILE, TILE) TILE_SIZE = Vector2.new(TILE, TILE)
local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile (collision odds ~2^-62) local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile (collision odds ~2^-62)
local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide) local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide)
@@ -433,6 +433,8 @@ local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (p
local pr_cx0, pr_cy0, pr_cx1, pr_cy1 = {}, {}, {}, {} -- clip (screen pixels) local pr_cx0, pr_cy0, pr_cx1, pr_cy1 = {}, {}, {}, {} -- clip (screen pixels)
local pr_bx0, pr_by0, pr_bx1, pr_by1 = {}, {}, {}, {} -- clipped bounds (screen pixels) local pr_bx0, pr_by0, pr_bx1, pr_by1 = {}, {}, {}, {} -- clipped bounds (screen pixels)
local pr_tex = {} -- texture id local pr_tex = {} -- texture id
local pr_k = {} -- layer kind (1..3)
local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind
@native @native
local function HashVertex(h1, h2, v, ox, oy) local function HashVertex(h1, h2, v, ox, oy)
@@ -495,22 +497,43 @@ local function DrawRectGradient(x0, y0, x1, y1, ca, cb, kind, t, u, v, cx0, cy0,
end end
end end
local function GetLayer(bd, draw_list) -- Layer kinds per draw list: 1 = drawn before the window content (background, title bar, borders, scrollbar),
local layer = bd.Layers[draw_list] -- 2 = window content (positioned at the scroll origin inside a ClipsDescendants frame, so scrolling just moves it),
if layer then return layer end -- 3 = anything not clipped to the content drawn after content started (menu bar, overlays). Stacked 1 < 2 < 3.
local function NewFrame(parent, clips)
local frame = Instance.new("Frame") local frame = Instance.new("Frame")
frame.Name = "Layer" frame.Name = "Layer"
frame.BackgroundTransparency = 1 frame.BackgroundTransparency = 1
frame.BorderSizePixel = 0 frame.BorderSizePixel = 0
frame.Size = UDim2.fromOffset(0, 0) frame.Size = UDim2.fromOffset(0, 0)
frame.ClipsDescendants = clips
frame.Visible = false frame.Visible = false
frame.Parent = bd.Gui frame.Parent = parent
layer = { Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, Touched = {}, NTouched = 0, return frame
Visible = false, X = false, Y = false, Z = -1, LastUsed = 0 } end
bd.Layers[draw_list] = layer
local function GetLayer(bd, draw_list, kind)
local set = bd.Layers[draw_list]
if not set then set = { LastUsed = 0 }; bd.Layers[draw_list] = set end
local layer = set[kind]
if layer then return layer end
local root, frame
if kind == 2 then
root = NewFrame(bd.Gui, true)
frame = NewFrame(root, false); frame.Visible = true
else
frame = NewFrame(bd.Gui, false); root = frame
end
layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0,
Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false }
set[kind] = layer
return layer return layer
end end
local function HideLayer(layer)
if layer.Visible then layer.Visible = false; layer.Root.Visible = false end
end
local function GetTile(bd, layer, key, tx, ty) local function GetTile(bd, layer, key, tx, ty)
local tile = layer.Tiles[key] local tile = layer.Tiles[key]
if tile then return tile end if tile then return tile end
@@ -533,13 +556,18 @@ local function GetTile(bd, layer, key, tx, ty)
return tile return tile
end end
local function DestroyLayer(layer) local function DestroyLayerSet(set)
for kind = 1, 3 do
local layer = set[kind]
if layer then
for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end
layer.Frame:Destroy() layer.Root:Destroy()
end
end
end end
local function DestroyTiles(bd) local function DestroyTiles(bd)
for _, layer in pairs(bd.Layers) do DestroyLayer(layer) end for _, set in pairs(bd.Layers) do DestroyLayerSet(set) end
bd.Layers = {} bd.Layers = {}
end end
@@ -550,134 +578,15 @@ local function PB(name) if profiling then profilebegin(name) end end
local function PE() if profiling then profileend() end end local function PE() if profiling then profileend() end end
@native @native
function ImGui_ImplRoblox_RenderDrawData(draw_data) local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
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
ImGui_ImplRoblox_UpdateTexture(tex)
bd.ForceRedraw = true -- texture contents changed: tiles sampling it must be redrawn
end
end
end
PE()
-- Render rate cap: textures are always updated (imgui expects it), tiles only at bd.RenderRate Hz
-- Optional lower cap while scrolling / resizing / moving a window (SetBusyRenderRate, off by default)
local rate = bd.RenderRate
if bd.BusyRenderRate > 0 then
local g = ImGui.GetCurrentContext()
local busy = g.WheelingWindow ~= nil or g.MovingWindow ~= nil -- (io.MouseWheel is already cleared by EndFrame)
if not busy and g.ActiveId ~= 0 then
local w = g.ActiveIdWindow
local cur = g.MouseCursor -- ResizeAll..ResizeNWSE while a resize grip/border is held
busy = (cur >= ImGuiMouseCursor.ResizeAll and cur <= ImGuiMouseCursor.ResizeNWSE)
or (w ~= nil and (g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.X) or g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.Y)))
end
if busy and (rate <= 0 or bd.BusyRenderRate < rate) then rate = bd.BusyRenderRate end
end
if rate > 0 then
local now = os.clock()
if now + 0.002 < bd.NextRender then PE(); return end
bd.NextRender = math.max(bd.NextRender + 1 / rate, now)
end
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
if sw <= 0 or sh <= 0 then PE(); return end
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
local lim_x0, lim_y0, lim_x1, lim_y1 = -CLIP_PAD, -CLIP_PAD, sw + CLIP_PAD, sh + CLIP_PAD
bd.FrameNo += 1
local frame_no = bd.FrameNo
local force = bd.ForceRedraw
bd.ForceRedraw = false
local scale = bd.Scale local scale = bd.Scale
local n = 0
local z = 0
for _, draw_list in draw_data.CmdLists:iter() do
-- Pass 1: collect this list's primitives and their bounds
PB("ImGui collect")
local first = n + 1
local lx0, ly0 = math.huge, math.huge
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd)
elseif pcmd.ElemCount > 0 then
local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0)
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1)
if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id]
local vo = pcmd.VtxOffset
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
while i <= last do
local ia, ic = idx[i], idx[i + 2]
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
local vd = false
local gk = false
local pa, pb, pc = va[1], vb[1], vc[1]
local bx0, by0, bx1, by1
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
local d = vtx[vo + idx[i + 5]]
local pd = d[1]
-- axis aligned quad (TL, TR, BR, BL)
if pa.y == pb.y and pb.x == pc.x and pc.y == pd.y and pd.x == pa.x and pa.x < pb.x and pa.y < pd.y then
local col = va[3]
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
if vb[3] == col and vc[3] == col and d[3] == col then
if tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y then vd = d end
elseif ta.x == tb.x and ta.x == tc.x and ta.x == td.x and ta.y == tb.y and ta.y == tc.y and ta.y == td.y then
-- constant uv, linear gradient: vertical (top/bottom pairs equal) or horizontal (left/right pairs equal)
if vb[3] == col and d[3] == vc[3] then vd = d; gk = 1
elseif d[3] == col and vb[3] == vc[3] then vd = d; gk = 2 end
end
if vd then
bx0, by0 = ceil(pa.x - ox - 0.5), ceil(pa.y - oy - 0.5)
bx1, by1 = ceil(pc.x - ox - 0.5), ceil(pc.y - oy - 0.5)
end
end
end
if vd then
i = i + 6
else
bx0, by0 = floor(min(pa.x, pb.x, pc.x) - ox), floor(min(pa.y, pb.y, pc.y) - oy)
bx1, by1 = ceil(max(pa.x, pb.x, pc.x) - ox) + 1, ceil(max(pa.y, pb.y, pc.y) - oy) + 1
i = i + 3
end
if bx0 < cx0 then bx0 = cx0 end
if by0 < cy0 then by0 = cy0 end
if bx1 > cx1 then bx1 = cx1 end
if by1 > cy1 then by1 = cy1 end
if bx0 < bx1 and by0 < by1 then
n = n + 1
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n], pr_g[n], pr_tex[n] = va, vb, vc, vd, t, gk, tex_id
pr_cx0[n], pr_cy0[n], pr_cx1[n], pr_cy1[n] = cx0, cy0, cx1, cy1
pr_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
if bx0 < lx0 then lx0 = bx0 end
if by0 < ly0 then ly0 = by0 end
end
end
end
end
end
PE()
local layer = GetLayer(bd, draw_list)
layer.LastUsed = frame_no
if n < first then
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end
else
-- Pass 2: hash primitives into layer-relative tiles -- Pass 2: hash primitives into layer-relative tiles
PB("ImGui tile hash") PB("ImGui tile hash")
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
for k = 1, layer.NTouched do tn[touched[k]] = 0 end for k = 1, layer.NTouched do tn[touched[k]] = 0 end
local nt = 0 local nt = 0
for p = first, n do for pi = 1, np do
local p = plist[pi]
local tex_id = pr_tex[p] local tex_id = pr_tex[p]
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative) -- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved. -- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
@@ -700,6 +609,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
if not tl[key] then tl[key] = {} end if not tl[key] then tl[key] = {} end
nt = nt + 1 nt = nt + 1
touched[nt] = key touched[nt] = key
layer.TX[key], layer.TY[key] = tx, ty
end end
k = k + 1 k = k + 1
tn[key] = k tn[key] = k
@@ -713,14 +623,25 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
PE() PE()
-- Place the layer (this is all a window move costs) -- Place the layer (this is all a window move costs)
local frame = layer.Frame local root = layer.Root
if root ~= layer.Frame then
-- content: clip frame at the content clip rect, tiles frame at the scroll origin relative to it
if layer.CX0 ~= clip_x0 or layer.CY0 ~= clip_y0 or layer.CX1 ~= clip_x1 or layer.CY1 ~= clip_y1 then
layer.CX0, layer.CY0, layer.CX1, layer.CY1 = clip_x0, clip_y0, clip_x1, clip_y1
root.Position = UDim2.fromOffset(clip_x0 / scale, clip_y0 / scale)
root.Size = UDim2.fromOffset((clip_x1 - clip_x0) / scale, (clip_y1 - clip_y0) / scale)
layer.X = false
end
if layer.X ~= lx0 or layer.Y ~= ly0 then if layer.X ~= lx0 or layer.Y ~= ly0 then
layer.X, layer.Y = lx0, ly0 layer.X, layer.Y = lx0, ly0
frame.Position = UDim2.fromOffset(lx0 / scale, ly0 / scale) layer.Frame.Position = UDim2.fromOffset((lx0 - clip_x0) / scale, (ly0 - clip_y0) / scale)
end end
z = z + 1 elseif layer.X ~= lx0 or layer.Y ~= ly0 then
if layer.Z ~= z then layer.Z = z; frame.ZIndex = z end layer.X, layer.Y = lx0, ly0
if not layer.Visible then layer.Visible = true; frame.Visible = true end root.Position = UDim2.fromOffset(lx0 / scale, ly0 / scale)
end
if layer.Z ~= z then layer.Z = z; root.ZIndex = z end
if not layer.Visible then layer.Visible = true; root.Visible = true end
-- Pass 3: redraw + upload changed tiles, hide unused ones -- Pass 3: redraw + upload changed tiles, hide unused ones
PB("ImGui raster+upload") PB("ImGui raster+upload")
@@ -732,8 +653,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
for kk = 1, nt do for kk = 1, nt do
local key = touched[kk] local key = touched[kk]
local count = tn[key] local count = tn[key]
local tx, ty = key % KEY_W, floor(key / KEY_W) local tx, ty = layer.TX[key], layer.TY[key]
if tx < 0 then tx = tx + KEY_W; ty = ty - 1 end
local tile = GetTile(bd, layer, key, tx, ty) local tile = GetTile(bd, layer, key, tx, ty)
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then
tile.H1, tile.H2 = th1[key], th2[key] tile.H1, tile.H2 = th1[key], th2[key]
@@ -800,14 +720,180 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
PE() PE()
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
ImGui_ImplRoblox_UpdateTexture(tex)
bd.ForceRedraw = true -- texture contents changed: tiles sampling it must be redrawn
end
end
end
PE()
-- Render rate cap: textures are always updated (imgui expects it), tiles only at bd.RenderRate Hz
-- Optional lower cap while scrolling / resizing / moving a window (SetBusyRenderRate, off by default)
local rate = bd.RenderRate
if bd.BusyRenderRate > 0 then
local g = ImGui.GetCurrentContext()
local busy = g.WheelingWindow ~= nil or g.MovingWindow ~= nil -- (io.MouseWheel is already cleared by EndFrame)
if not busy and g.ActiveId ~= 0 then
local w = g.ActiveIdWindow
local cur = g.MouseCursor -- ResizeAll..ResizeNWSE while a resize grip/border is held
busy = (cur >= ImGuiMouseCursor.ResizeAll and cur <= ImGuiMouseCursor.ResizeNWSE)
or (w ~= nil and (g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.X) or g.ActiveId == ImGui.GetWindowScrollbarID(w, ImGuiAxis.Y)))
end
if busy and (rate <= 0 or bd.BusyRenderRate < rate) then rate = bd.BusyRenderRate end
end
if rate > 0 then
local now = os.clock()
if now + 0.002 < bd.NextRender then PE(); return end
bd.NextRender = math.max(bd.NextRender + 1 / rate, now)
end
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
if sw <= 0 or sh <= 0 then PE(); return end
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
local lim_x0, lim_y0, lim_x1, lim_y1 = -CLIP_PAD, -CLIP_PAD, sw + CLIP_PAD, sh + CLIP_PAD
bd.FrameNo += 1
local frame_no = bd.FrameNo
local force = bd.ForceRedraw
bd.ForceRedraw = false
local n = 0
local z = 0
for _, draw_list in draw_data.CmdLists:iter() do
-- Pass 1: collect this list's primitives and their bounds
PB("ImGui collect")
local first = n + 1
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
local ccx0, ccy0, ccx1, ccy1 = draw_list._ContentClipX0, draw_list._ContentClipY0, draw_list._ContentClipX1, draw_list._ContentClipY1
local content_x0, content_y0, content_x1, content_y1
if ccx0 then
content_x0, content_y0 = max(floor(ccx0 - ox), lim_x0), max(floor(ccy0 - oy), lim_y0)
content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1)
end
local seen_content = false
for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd)
elseif pcmd.ElemCount > 0 then
local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0)
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1)
local kind = 1
if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then
kind = 2; seen_content = true
if clip.x == ccx0 and clip.y == ccy0 and clip.z == ccx1 and clip.w == ccy1 then
-- clipped by the layer's clip frame instead: partly visible rows stay hash-stable while scrolling
cx0, cy0, cx1, cy1 = lim_x0, lim_y0, lim_x1, lim_y1
end
elseif seen_content then
kind = 3
end
if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id]
local vo = pcmd.VtxOffset
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
while i <= last do
local ia, ic = idx[i], idx[i + 2]
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
local vd = false
local gk = false
local pa, pb, pc = va[1], vb[1], vc[1]
local bx0, by0, bx1, by1
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
local d = vtx[vo + idx[i + 5]]
local pd = d[1]
-- axis aligned quad (TL, TR, BR, BL)
if pa.y == pb.y and pb.x == pc.x and pc.y == pd.y and pd.x == pa.x and pa.x < pb.x and pa.y < pd.y then
local col = va[3]
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
if vb[3] == col and vc[3] == col and d[3] == col then
if tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y then vd = d end
elseif ta.x == tb.x and ta.x == tc.x and ta.x == td.x and ta.y == tb.y and ta.y == tc.y and ta.y == td.y then
-- constant uv, linear gradient: vertical (top/bottom pairs equal) or horizontal (left/right pairs equal)
if vb[3] == col and d[3] == vc[3] then vd = d; gk = 1
elseif d[3] == col and vb[3] == vc[3] then vd = d; gk = 2 end
end
if vd then
bx0, by0 = ceil(pa.x - ox - 0.5), ceil(pa.y - oy - 0.5)
bx1, by1 = ceil(pc.x - ox - 0.5), ceil(pc.y - oy - 0.5)
end
end
end
if vd then
i = i + 6
else
bx0, by0 = floor(min(pa.x, pb.x, pc.x) - ox), floor(min(pa.y, pb.y, pc.y) - oy)
bx1, by1 = ceil(max(pa.x, pb.x, pc.x) - ox) + 1, ceil(max(pa.y, pb.y, pc.y) - oy) + 1
i = i + 3
end
if bx0 < cx0 then bx0 = cx0 end
if by0 < cy0 then by0 = cy0 end
if bx1 > cx1 then bx1 = cx1 end
if by1 > cy1 then by1 = cy1 end
if bx0 < bx1 and by0 < by1 then
n = n + 1
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n], pr_g[n], pr_tex[n] = va, vb, vc, vd, t, gk, tex_id
pr_cx0[n], pr_cy0[n], pr_cx1[n], pr_cy1[n] = cx0, cy0, cx1, cy1
pr_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
pr_k[n] = kind
end
end
end
end
end
PE()
-- Split into layer kinds and place each one
local set = bd.Layers[draw_list]
local nk1, nk2, nk3 = 0, 0, 0
local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
for p = first, n do
local k = pr_k[p]
if k == 1 then
nk1 += 1; plist1[nk1] = p
if pr_bx0[p] < o1x then o1x = pr_bx0[p] end
if pr_by0[p] < o1y then o1y = pr_by0[p] end
elseif k == 2 then
nk2 += 1; plist2[nk2] = p
else
nk3 += 1; plist3[nk3] = p
if pr_bx0[p] < o3x then o3x = pr_bx0[p] end
if pr_by0[p] < o3y then o3y = pr_by0[p] end
end
end
if nk1 > 0 then
z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z)
elseif set and set[1] then HideLayer(set[1]) end
if nk2 > 0 then
z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z,
content_x0, content_y0, content_x1, content_y1)
elseif set and set[2] then HideLayer(set[2]) end
if nk3 > 0 then
z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z)
elseif set and set[3] then HideLayer(set[3]) end
set = bd.Layers[draw_list]
if set then set.LastUsed = frame_no end
end end
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips) -- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
for draw_list, layer in pairs(bd.Layers) do for draw_list, set in pairs(bd.Layers) do
if layer.LastUsed ~= frame_no then if set.LastUsed ~= frame_no then
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end
if frame_no - layer.LastUsed > LAYER_KEEP_FRAMES then if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then
DestroyLayer(layer) DestroyLayerSet(set)
bd.Layers[draw_list] = nil bd.Layers[draw_list] = nil
end end
end end
@@ -941,7 +1027,7 @@ function ImGui_ImplRoblox_NewFrame()
local want = io.WantCaptureMouse local want = io.WantCaptureMouse
if want ~= bd.TilesActive then if want ~= bd.TilesActive then
bd.TilesActive = want bd.TilesActive = want
for _, layer in pairs(bd.Layers) do for _, tile in pairs(layer.Tiles) do tile.Label.Active = want end end for _, set in pairs(bd.Layers) do for kind = 1, 3 do local layer = set[kind]; if layer then for _, tile in pairs(layer.Tiles) do tile.Label.Active = want end end end end
end end
PE() PE()
end end
+4
View File
@@ -7150,6 +7150,10 @@ function ImGui.Begin(name, open, flags)
if bit32.band(flags, ImGuiWindowFlags.DockNodeHost) == 0 and not window.SkipRefresh then if bit32.band(flags, ImGuiWindowFlags.DockNodeHost) == 0 and not window.SkipRefresh then
ImGui.PushClipRect(window.InnerClipRect.Min, window.InnerClipRect.Max, true) ImGui.PushClipRect(window.InnerClipRect.Min, window.InnerClipRect.Max, true)
-- [Roblox backend] content clip + scroll origin: lets the backend move scrolled content instead of redrawing it
local dl, cr = window.DrawList, window.DrawList._CmdHeader.ClipRect
dl._ContentClipX0, dl._ContentClipY0, dl._ContentClipX1, dl._ContentClipY1 = cr.x, cr.y, cr.z, cr.w
dl._ScrollOriginX, dl._ScrollOriginY = window.Pos.x - window.Scroll.x, window.Pos.y - window.Scroll.y
end end
window.WriteAccessed = false window.WriteAccessed = false
+1
View File
@@ -3158,6 +3158,7 @@ end
-- TODO: -- TODO:
function MT.ImDrawList:_ResetForNewFrame() function MT.ImDrawList:_ResetForNewFrame()
self._ContentClipX0 = nil -- set again by Begin() (Roblox backend scroll reuse)
if self._Splitter._Count > 1 then if self._Splitter._Count > 1 then
self._Splitter:Merge(self) self._Splitter:Merge(self)
end end
+10 -5
View File
@@ -13,21 +13,26 @@ local frame = 0
local function composite(gui) local function composite(gui)
local screen = buffer.create(W * H * 3) local screen = buffer.create(W * H * 3)
local function walk(inst, ox, oy) local function walk(inst, ox, oy, kx0, ky0, kx1, ky1)
local kids = inst:GetChildren() local kids = inst:GetChildren()
table.sort(kids, function(a, b) return (a.ZIndex or 1) < (b.ZIndex or 1) end) -- table.sort isn't stable, but siblings with equal ZIndex never overlap here table.sort(kids, function(a, b) return (a.ZIndex or 1) < (b.ZIndex or 1) end) -- table.sort isn't stable, but siblings with equal ZIndex never overlap here
for _, c in ipairs(kids) do for _, c in ipairs(kids) do
if c.Visible ~= false then if c.Visible ~= false then
local x, y = ox + (c.Position and c.Position.X or 0), oy + (c.Position and c.Position.Y or 0) local x, y = ox + (c.Position and c.Position.X or 0), oy + (c.Position and c.Position.Y or 0)
local cx0, cy0, cx1, cy1 = kx0, ky0, kx1, ky1
if c.ClipsDescendants then
cx0, cy0 = math.max(cx0, x), math.max(cy0, y)
cx1, cy1 = math.min(cx1, x + c.Size.X), math.min(cy1, y + c.Size.Y)
end
if c.ClassName == "ImageLabel" then if c.ClassName == "ImageLabel" then
local img = c.ImageContent.Object local img = c.ImageContent.Object
local px, sz = img.Pixels, img.Size.X local px, sz = img.Pixels, img.Size.X
for ty = 0, sz - 1 do for ty = 0, sz - 1 do
local sy = y + ty local sy = y + ty
if sy >= 0 and sy < H then if sy >= ky0 and sy < ky1 then
for tx = 0, sz - 1 do for tx = 0, sz - 1 do
local sx = x + tx local sx = x + tx
if sx >= 0 and sx < W then if sx >= kx0 and sx < kx1 then
local o = (ty * sz + tx) * 4 local o = (ty * sz + tx) * 4
local a = buffer.readu8(px, o + 3) local a = buffer.readu8(px, o + 3)
if a > 0 then if a > 0 then
@@ -43,11 +48,11 @@ local function composite(gui)
end end
end end
end end
walk(c, x, y) walk(c, x, y, cx0, cy0, cx1, cy1)
end end
end end
end end
walk(gui, 0, 0) walk(gui, 0, 0, 0, 0, W, H)
return screen return screen
end end