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"]]
+244 -153
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 _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end for kind = 1, 3 do
layer.Frame:Destroy() local layer = set[kind]
if layer then
for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end
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
@@ -33701,6 +33734,150 @@ local profiling = false
local function PB(name) if profiling then profilebegin(name) end end 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
local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
local scale = bd.Scale
-- Pass 2: hash primitives into layer-relative tiles
PB("ImGui tile hash")
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
local nt = 0
for pi = 1, np do
local p = plist[pi]
local tex_id = pr_tex[p]
-- 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.
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
local h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
local h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vax, vay = lx0 + ox, ly0 + oy
h1, h2 = HashVertex(h1, h2, pr_a[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_b[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_c[p], vax, vay)
local vd = pr_d[p]
if vd then h1, h2 = HashVertex(h1, h2, vd, vax, vay) end
for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do
for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do
local key = ty * KEY_W + tx
local k = tn[key]
if not k or k == 0 then
k = 0
th1[key], th2[key] = 0, 0
if not tl[key] then tl[key] = {} end
nt = nt + 1
touched[nt] = key
layer.TX[key], layer.TY[key] = tx, ty
end
k = k + 1
tn[key] = k
tl[key][k] = p
th1[key] = (th1[key] * 48271 + h1) % P1
th2[key] = (th2[key] * 16807 + h2 + k) % P2
end
end
end
layer.NTouched = nt
PE()
-- Place the layer (this is all a window move costs)
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
layer.X, layer.Y = lx0, ly0
layer.Frame.Position = UDim2.fromOffset((lx0 - clip_x0) / scale, (ly0 - clip_y0) / scale)
end
elseif layer.X ~= lx0 or layer.Y ~= ly0 then
layer.X, layer.Y = lx0, ly0
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
PB("ImGui raster+upload")
for key, tile in pairs(layer.Tiles) do
if not tn[key] or tn[key] == 0 then
if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1 end
end
end
for kk = 1, nt do
local key = touched[kk]
local count = tn[key]
local tx, ty = layer.TX[key], layer.TY[key]
local tile = GetTile(bd, layer, key, tx, ty)
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then
tile.H1, tile.H2 = th1[key], th2[key]
local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE
local x1, y1 = x0 + TILE, y0 + TILE
buf, buf_w = tile.Buffer, TILE
local list = tl[key]
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty).
local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0
for k = 1, count do
local p = list[k]
local a, b = pr_bx0[p] - x0, pr_by0[p] - y0
local c, d = pr_bx1[p] - x0, pr_by1[p] - y0
if a < nx0 then nx0 = a end
if b < ny0 then ny0 = b end
if c > nx1 then nx1 = c end
if d > ny1 then ny1 = d end
end
nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE)
local rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1)
tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1
local rw = rx1 - rx0
if rw == TILE then
buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4)
else
for y = ry0, ry1 - 1 do buffer.fill(buf, (y * TILE + rx0) * 4, 0, rw * 4) end
end
local tox, toy = ox + x0, oy + y0
for k = 1, count do
local p = list[k]
local cx0, cy0 = max(pr_cx0[p], x0) - x0, max(pr_cy0[p], y0) - y0
local cx1, cy1 = min(pr_cx1[p], x1) - x0, min(pr_cy1[p], y1) - y0
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
if vd then
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
local gk = pr_g[p]
if gk == 1 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 1, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
elseif gk == 2 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 2, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
else
DrawRect(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, ta.x, ta.y, tc.x, tc.y, va[3], pr_t[p], cx0, cy0, cx1, cy1)
end
else
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
end
end
PB("ImGui upload")
if rw == TILE and ry0 == 0 and ry1 == TILE then
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
elseif rw > 0 and ry1 > ry0 then
local rh = ry1 - ry0
local sub = buffer.create(rw * rh * 4)
for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end
tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub)
end
PE()
end
if not tile.Visible then
tile.Visible = true
tile.Label.Visible = true
end
end
PE()
end
@native @native
function ImGui_ImplRoblox_RenderDrawData(draw_data) function ImGui_ImplRoblox_RenderDrawData(draw_data)
local bd = ImGui_ImplRoblox_GetBackendData() local bd = ImGui_ImplRoblox_GetBackendData()
@@ -33745,7 +33922,6 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
local frame_no = bd.FrameNo local frame_no = bd.FrameNo
local force = bd.ForceRedraw local force = bd.ForceRedraw
bd.ForceRedraw = false bd.ForceRedraw = false
local scale = bd.Scale
local n = 0 local n = 0
local z = 0 local z = 0
@@ -33753,8 +33929,14 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
-- Pass 1: collect this list's primitives and their bounds -- Pass 1: collect this list's primitives and their bounds
PB("ImGui collect") PB("ImGui collect")
local first = n + 1 local first = n + 1
local lx0, ly0 = math.huge, math.huge
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data 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 for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd) pcmd.UserCallback(draw_list, pcmd)
@@ -33762,6 +33944,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
local clip = pcmd.ClipRect local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0) 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 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 if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID() local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id] local t = bd.Textures[tex_id]
@@ -33810,8 +34002,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
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_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_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_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
if bx0 < lx0 then lx0 = bx0 end pr_k[n] = kind
if by0 < ly0 then ly0 = by0 end
end end
end end
end end
@@ -33819,147 +34010,47 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
PE() PE()
local layer = GetLayer(bd, draw_list) -- Split into layer kinds and place each one
layer.LastUsed = frame_no local set = bd.Layers[draw_list]
if n < first then local nk1, nk2, nk3 = 0, 0, 0
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
else for p = first, n do
-- Pass 2: hash primitives into layer-relative tiles local k = pr_k[p]
PB("ImGui tile hash") if k == 1 then
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched nk1 += 1; plist1[nk1] = p
for k = 1, layer.NTouched do tn[touched[k]] = 0 end if pr_bx0[p] < o1x then o1x = pr_bx0[p] end
local nt = 0 if pr_by0[p] < o1y then o1y = pr_by0[p] end
for p = first, n do elseif k == 2 then
local tex_id = pr_tex[p] nk2 += 1; plist2[nk2] = p
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative) else
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved. nk3 += 1; plist3[nk3] = p
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0 if pr_bx0[p] < o3x then o3x = pr_bx0[p] end
local h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1 if pr_by0[p] < o3y then o3y = pr_by0[p] end
local h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vax, vay = lx0 + ox, ly0 + oy
h1, h2 = HashVertex(h1, h2, pr_a[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_b[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_c[p], vax, vay)
local vd = pr_d[p]
if vd then h1, h2 = HashVertex(h1, h2, vd, vax, vay) end
for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do
for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do
local key = ty * KEY_W + tx
local k = tn[key]
if not k or k == 0 then
k = 0
th1[key], th2[key] = 0, 0
if not tl[key] then tl[key] = {} end
nt = nt + 1
touched[nt] = key
end
k = k + 1
tn[key] = k
tl[key][k] = p
th1[key] = (th1[key] * 48271 + h1) % P1
th2[key] = (th2[key] * 16807 + h2 + k) % P2
end
end
end end
layer.NTouched = nt
PE()
-- Place the layer (this is all a window move costs)
local frame = layer.Frame
if layer.X ~= lx0 or layer.Y ~= ly0 then
layer.X, layer.Y = lx0, ly0
frame.Position = UDim2.fromOffset(lx0 / scale, ly0 / scale)
end
z = z + 1
if layer.Z ~= z then layer.Z = z; frame.ZIndex = z end
if not layer.Visible then layer.Visible = true; frame.Visible = true end
-- Pass 3: redraw + upload changed tiles, hide unused ones
PB("ImGui raster+upload")
for key, tile in pairs(layer.Tiles) do
if not tn[key] or tn[key] == 0 then
if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1 end
end
end
for kk = 1, nt do
local key = touched[kk]
local count = tn[key]
local tx, ty = key % KEY_W, floor(key / KEY_W)
if tx < 0 then tx = tx + KEY_W; ty = ty - 1 end
local tile = GetTile(bd, layer, key, tx, ty)
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then
tile.H1, tile.H2 = th1[key], th2[key]
local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE
local x1, y1 = x0 + TILE, y0 + TILE
buf, buf_w = tile.Buffer, TILE
local list = tl[key]
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty).
local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0
for k = 1, count do
local p = list[k]
local a, b = pr_bx0[p] - x0, pr_by0[p] - y0
local c, d = pr_bx1[p] - x0, pr_by1[p] - y0
if a < nx0 then nx0 = a end
if b < ny0 then ny0 = b end
if c > nx1 then nx1 = c end
if d > ny1 then ny1 = d end
end
nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE)
local rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1)
tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1
local rw = rx1 - rx0
if rw == TILE then
buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4)
else
for y = ry0, ry1 - 1 do buffer.fill(buf, (y * TILE + rx0) * 4, 0, rw * 4) end
end
local tox, toy = ox + x0, oy + y0
for k = 1, count do
local p = list[k]
local cx0, cy0 = max(pr_cx0[p], x0) - x0, max(pr_cy0[p], y0) - y0
local cx1, cy1 = min(pr_cx1[p], x1) - x0, min(pr_cy1[p], y1) - y0
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
if vd then
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
local gk = pr_g[p]
if gk == 1 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 1, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
elseif gk == 2 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 2, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
else
DrawRect(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, ta.x, ta.y, tc.x, tc.y, va[3], pr_t[p], cx0, cy0, cx1, cy1)
end
else
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
end
end
PB("ImGui upload")
if rw == TILE and ry0 == 0 and ry1 == TILE then
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
elseif rw > 0 and ry1 > ry0 then
local rh = ry1 - ry0
local sub = buffer.create(rw * rh * 4)
for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end
tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub)
end
PE()
end
if not tile.Visible then
tile.Visible = true
tile.Label.Visible = true
end
end
PE()
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
+239 -153
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 _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end for kind = 1, 3 do
layer.Frame:Destroy() local layer = set[kind]
if layer then
for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end
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
@@ -549,6 +577,150 @@ local profiling = false
local function PB(name) if profiling then profilebegin(name) end end 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
local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
local scale = bd.Scale
-- Pass 2: hash primitives into layer-relative tiles
PB("ImGui tile hash")
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
local nt = 0
for pi = 1, np do
local p = plist[pi]
local tex_id = pr_tex[p]
-- 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.
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
local h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
local h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vax, vay = lx0 + ox, ly0 + oy
h1, h2 = HashVertex(h1, h2, pr_a[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_b[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_c[p], vax, vay)
local vd = pr_d[p]
if vd then h1, h2 = HashVertex(h1, h2, vd, vax, vay) end
for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do
for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do
local key = ty * KEY_W + tx
local k = tn[key]
if not k or k == 0 then
k = 0
th1[key], th2[key] = 0, 0
if not tl[key] then tl[key] = {} end
nt = nt + 1
touched[nt] = key
layer.TX[key], layer.TY[key] = tx, ty
end
k = k + 1
tn[key] = k
tl[key][k] = p
th1[key] = (th1[key] * 48271 + h1) % P1
th2[key] = (th2[key] * 16807 + h2 + k) % P2
end
end
end
layer.NTouched = nt
PE()
-- Place the layer (this is all a window move costs)
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
layer.X, layer.Y = lx0, ly0
layer.Frame.Position = UDim2.fromOffset((lx0 - clip_x0) / scale, (ly0 - clip_y0) / scale)
end
elseif layer.X ~= lx0 or layer.Y ~= ly0 then
layer.X, layer.Y = lx0, ly0
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
PB("ImGui raster+upload")
for key, tile in pairs(layer.Tiles) do
if not tn[key] or tn[key] == 0 then
if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1 end
end
end
for kk = 1, nt do
local key = touched[kk]
local count = tn[key]
local tx, ty = layer.TX[key], layer.TY[key]
local tile = GetTile(bd, layer, key, tx, ty)
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then
tile.H1, tile.H2 = th1[key], th2[key]
local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE
local x1, y1 = x0 + TILE, y0 + TILE
buf, buf_w = tile.Buffer, TILE
local list = tl[key]
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty).
local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0
for k = 1, count do
local p = list[k]
local a, b = pr_bx0[p] - x0, pr_by0[p] - y0
local c, d = pr_bx1[p] - x0, pr_by1[p] - y0
if a < nx0 then nx0 = a end
if b < ny0 then ny0 = b end
if c > nx1 then nx1 = c end
if d > ny1 then ny1 = d end
end
nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE)
local rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1)
tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1
local rw = rx1 - rx0
if rw == TILE then
buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4)
else
for y = ry0, ry1 - 1 do buffer.fill(buf, (y * TILE + rx0) * 4, 0, rw * 4) end
end
local tox, toy = ox + x0, oy + y0
for k = 1, count do
local p = list[k]
local cx0, cy0 = max(pr_cx0[p], x0) - x0, max(pr_cy0[p], y0) - y0
local cx1, cy1 = min(pr_cx1[p], x1) - x0, min(pr_cy1[p], y1) - y0
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
if vd then
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
local gk = pr_g[p]
if gk == 1 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 1, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
elseif gk == 2 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 2, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
else
DrawRect(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, ta.x, ta.y, tc.x, tc.y, va[3], pr_t[p], cx0, cy0, cx1, cy1)
end
else
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
end
end
PB("ImGui upload")
if rw == TILE and ry0 == 0 and ry1 == TILE then
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
elseif rw > 0 and ry1 > ry0 then
local rh = ry1 - ry0
local sub = buffer.create(rw * rh * 4)
for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end
tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub)
end
PE()
end
if not tile.Visible then
tile.Visible = true
tile.Label.Visible = true
end
end
PE()
end
@native @native
function ImGui_ImplRoblox_RenderDrawData(draw_data) function ImGui_ImplRoblox_RenderDrawData(draw_data)
local bd = ImGui_ImplRoblox_GetBackendData() local bd = ImGui_ImplRoblox_GetBackendData()
@@ -593,7 +765,6 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
local frame_no = bd.FrameNo local frame_no = bd.FrameNo
local force = bd.ForceRedraw local force = bd.ForceRedraw
bd.ForceRedraw = false bd.ForceRedraw = false
local scale = bd.Scale
local n = 0 local n = 0
local z = 0 local z = 0
@@ -601,8 +772,14 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
-- Pass 1: collect this list's primitives and their bounds -- Pass 1: collect this list's primitives and their bounds
PB("ImGui collect") PB("ImGui collect")
local first = n + 1 local first = n + 1
local lx0, ly0 = math.huge, math.huge
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data 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 for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd) pcmd.UserCallback(draw_list, pcmd)
@@ -610,6 +787,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
local clip = pcmd.ClipRect local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0) 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 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 if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID() local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id] local t = bd.Textures[tex_id]
@@ -658,8 +845,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
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_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_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_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
if bx0 < lx0 then lx0 = bx0 end pr_k[n] = kind
if by0 < ly0 then ly0 = by0 end
end end
end end
end end
@@ -667,147 +853,47 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
PE() PE()
local layer = GetLayer(bd, draw_list) -- Split into layer kinds and place each one
layer.LastUsed = frame_no local set = bd.Layers[draw_list]
if n < first then local nk1, nk2, nk3 = 0, 0, 0
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
else for p = first, n do
-- Pass 2: hash primitives into layer-relative tiles local k = pr_k[p]
PB("ImGui tile hash") if k == 1 then
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched nk1 += 1; plist1[nk1] = p
for k = 1, layer.NTouched do tn[touched[k]] = 0 end if pr_bx0[p] < o1x then o1x = pr_bx0[p] end
local nt = 0 if pr_by0[p] < o1y then o1y = pr_by0[p] end
for p = first, n do elseif k == 2 then
local tex_id = pr_tex[p] nk2 += 1; plist2[nk2] = p
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative) else
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved. nk3 += 1; plist3[nk3] = p
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0 if pr_bx0[p] < o3x then o3x = pr_bx0[p] end
local h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1 if pr_by0[p] < o3y then o3y = pr_by0[p] end
local h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vax, vay = lx0 + ox, ly0 + oy
h1, h2 = HashVertex(h1, h2, pr_a[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_b[p], vax, vay)
h1, h2 = HashVertex(h1, h2, pr_c[p], vax, vay)
local vd = pr_d[p]
if vd then h1, h2 = HashVertex(h1, h2, vd, vax, vay) end
for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do
for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do
local key = ty * KEY_W + tx
local k = tn[key]
if not k or k == 0 then
k = 0
th1[key], th2[key] = 0, 0
if not tl[key] then tl[key] = {} end
nt = nt + 1
touched[nt] = key
end
k = k + 1
tn[key] = k
tl[key][k] = p
th1[key] = (th1[key] * 48271 + h1) % P1
th2[key] = (th2[key] * 16807 + h2 + k) % P2
end
end
end end
layer.NTouched = nt
PE()
-- Place the layer (this is all a window move costs)
local frame = layer.Frame
if layer.X ~= lx0 or layer.Y ~= ly0 then
layer.X, layer.Y = lx0, ly0
frame.Position = UDim2.fromOffset(lx0 / scale, ly0 / scale)
end
z = z + 1
if layer.Z ~= z then layer.Z = z; frame.ZIndex = z end
if not layer.Visible then layer.Visible = true; frame.Visible = true end
-- Pass 3: redraw + upload changed tiles, hide unused ones
PB("ImGui raster+upload")
for key, tile in pairs(layer.Tiles) do
if not tn[key] or tn[key] == 0 then
if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1 end
end
end
for kk = 1, nt do
local key = touched[kk]
local count = tn[key]
local tx, ty = key % KEY_W, floor(key / KEY_W)
if tx < 0 then tx = tx + KEY_W; ty = ty - 1 end
local tile = GetTile(bd, layer, key, tx, ty)
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then
tile.H1, tile.H2 = th1[key], th2[key]
local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE
local x1, y1 = x0 + TILE, y0 + TILE
buf, buf_w = tile.Buffer, TILE
local list = tl[key]
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty).
local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0
for k = 1, count do
local p = list[k]
local a, b = pr_bx0[p] - x0, pr_by0[p] - y0
local c, d = pr_bx1[p] - x0, pr_by1[p] - y0
if a < nx0 then nx0 = a end
if b < ny0 then ny0 = b end
if c > nx1 then nx1 = c end
if d > ny1 then ny1 = d end
end
nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE)
local rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1)
tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1
local rw = rx1 - rx0
if rw == TILE then
buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4)
else
for y = ry0, ry1 - 1 do buffer.fill(buf, (y * TILE + rx0) * 4, 0, rw * 4) end
end
local tox, toy = ox + x0, oy + y0
for k = 1, count do
local p = list[k]
local cx0, cy0 = max(pr_cx0[p], x0) - x0, max(pr_cy0[p], y0) - y0
local cx1, cy1 = min(pr_cx1[p], x1) - x0, min(pr_cy1[p], y1) - y0
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
if vd then
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
local gk = pr_g[p]
if gk == 1 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 1, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
elseif gk == 2 then
DrawRectGradient(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, va[3], vc[3], 2, pr_t[p], ta.x, ta.y, cx0, cy0, cx1, cy1)
else
DrawRect(pa.x - tox, pa.y - toy, pc.x - tox, pc.y - toy, ta.x, ta.y, tc.x, tc.y, va[3], pr_t[p], cx0, cy0, cx1, cy1)
end
else
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
end
end
PB("ImGui upload")
if rw == TILE and ry0 == 0 and ry1 == TILE then
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
elseif rw > 0 and ry1 > ry0 then
local rh = ry1 - ry0
local sub = buffer.create(rw * rh * 4)
for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end
tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub)
end
PE()
end
if not tile.Visible then
tile.Visible = true
tile.Label.Visible = true
end
end
PE()
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