Roblox backend: one layer (Frame + tiles) per draw list so moving windows costs no redraw; gradient rect fast path; t_drag/t_composite checks
This commit is contained in:
@@ -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"], [33977, "imgui_impl_roblox_fonts.lua"], [40380, "imgui_internal.lua"], [43552, "imgui_tables.lua"], [47899, "imgui_tabs.lua"], [49066, "imgui_widgets.lua"], [57600, "imstb_rectpack.lua"], [58046, "imstb_textedit.lua"], [59287, "imstb_truetype.lua"], [61755, "imstd_minstdio.lua"]]
|
[[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"], [34094, "imgui_impl_roblox_fonts.lua"], [40497, "imgui_internal.lua"], [43669, "imgui_tables.lua"], [48016, "imgui_tabs.lua"], [49183, "imgui_widgets.lua"], [57717, "imstb_rectpack.lua"], [58163, "imstb_textedit.lua"], [59404, "imstb_truetype.lua"], [61872, "imstd_minstdio.lua"]]
|
||||||
+211
-94
@@ -33568,36 +33568,28 @@ local function DrawTriangle(va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Quads emitted by PrimRect/PrimRectUV/RenderText use indices (a, b, c, a, c, d) with a=TL, b=TR, c=BR, d=BL.
|
-- Layers: every ImDrawList (one per top level window, popup, tooltip, plus the viewport bg/fg lists) gets its own
|
||||||
-- When such a quad is axis aligned with one colour it is drawn with the much cheaper DrawRect.
|
-- Frame, stacked by draw order. A layer is covered by TILE x TILE EditableImages (one ImageLabel each) placed
|
||||||
@native
|
-- relative to the layer's origin (top-left of its content). Primitives are hashed into tiles in layer-relative
|
||||||
local function TryDrawRect(va, vb, vc, vd, t, cx0, cy0, cx1, cy1, ox, oy)
|
-- coordinates, so moving a window only moves its Frame: no tile changes, nothing re-rasterized or uploaded.
|
||||||
local pa, pb, pc, pd = va[1], vb[1], vc[1], vd[1]
|
-- Only tiles whose hash changed are redrawn; the hash covers everything that affects a tile's pixels.
|
||||||
if not (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 return false end
|
|
||||||
local col = va[3]
|
|
||||||
if vb[3] ~= col or vc[3] ~= col or vd[3] ~= col then return false end
|
|
||||||
local ta, tb, tc, td = va[2], vb[2], vc[2], vd[2]
|
|
||||||
if not (tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y) then return false end
|
|
||||||
DrawRect(pa.x - ox, pa.y - oy, pc.x - ox, pc.y - oy, ta.x, ta.y, tc.x, tc.y, col, t, cx0, cy0, cx1, cy1)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Tiles: the screen is covered by TILE x TILE EditableImages (one ImageLabel each). Every frame each primitive
|
|
||||||
-- (triangle, or axis aligned quad) is hashed into the tiles it touches; only tiles whose hash changed are
|
|
||||||
-- re-rasterized and uploaded. Static UI therefore costs ~nothing to draw, and the result is pixel-identical to a
|
|
||||||
-- full redraw (the hash covers everything that affects a tile's pixels: geometry, uv, colour, texture, clip, order).
|
|
||||||
local TILE = 256
|
local TILE = 256
|
||||||
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 CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it
|
||||||
|
local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames
|
||||||
|
|
||||||
-- Primitive arrays (reused between frames, no per-frame allocation)
|
-- Primitive arrays (reused between frames, no per-frame allocation)
|
||||||
local pr_a, pr_b, pr_c, pr_d, pr_t = {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture
|
local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind
|
||||||
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_tex = {} -- texture id
|
||||||
|
|
||||||
@native
|
@native
|
||||||
local function HashVertex(h1, h2, v)
|
local function HashVertex(h1, h2, v, ox, oy)
|
||||||
local p, u, c = v[1], v[2], v[3]
|
local p, u, c = v[1], v[2], v[3]
|
||||||
local x, y = p.x, p.y
|
local x, y = p.x - ox, p.y - oy
|
||||||
local uv = u.x * 8192 + u.y
|
local uv = u.x * 8192 + u.y
|
||||||
h1 = (h1 * 48271 + x * 4096 + y) % P1
|
h1 = (h1 * 48271 + x * 4096 + y) % P1
|
||||||
h1 = (h1 * 48271 + uv * 4096) % P1
|
h1 = (h1 * 48271 + uv * 4096) % P1
|
||||||
@@ -33608,13 +33600,76 @@ local function HashVertex(h1, h2, v)
|
|||||||
return h1, h2
|
return h1, h2
|
||||||
end
|
end
|
||||||
|
|
||||||
local function GetTile(bd, index, tx, ty)
|
-- Axis aligned quad with a linear colour gradient and constant uv (ColorPicker hue/SV/alpha bars, AddRectFilledMultiColor).
|
||||||
local tile = bd.Tiles[index]
|
-- kind 1: colour varies along y (ca top, cb bottom), kind 2: along x (ca left, cb right). Same pixels as the two triangles.
|
||||||
|
@native
|
||||||
|
local function DrawRectGradient(x0, y0, x1, y1, ca, cb, kind, t, u, v, cx0, cy0, cx1, cy1)
|
||||||
|
local px0, py0 = max(ceil(x0 - 0.5), cx0), max(ceil(y0 - 0.5), cy0)
|
||||||
|
local px1, py1 = min(ceil(x1 - 0.5), cx1), min(ceil(y1 - 0.5), cy1)
|
||||||
|
if px0 >= px1 or py0 >= py1 then return end
|
||||||
|
local ra, ga, ba, aa = UnpackColor(ca)
|
||||||
|
local rb, gb, bb, ab = UnpackColor(cb)
|
||||||
|
if t then
|
||||||
|
local tr, tg, tb, ta = SampleTexture(t, u, v)
|
||||||
|
ra, ga, ba, aa = ra * tr / 255, ga * tg / 255, ba * tb / 255, aa * ta / 255
|
||||||
|
rb, gb, bb, ab = rb * tr / 255, gb * tg / 255, bb * tb / 255, ab * ta / 255
|
||||||
|
end
|
||||||
|
if kind == 1 then
|
||||||
|
local inv = 1 / (y1 - y0)
|
||||||
|
for y = py0, py1 - 1 do
|
||||||
|
local f = (y + 0.5 - y0) * inv
|
||||||
|
local nf = 1 - f
|
||||||
|
FillSpan(y, px0, px1, ra * nf + rb * f, ga * nf + gb * f, ba * nf + bb * f, aa * nf + ab * f)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Every row gets the same colours: when a row's destination equals the previous row's, copy its result
|
||||||
|
local inv = 1 / (x1 - x0)
|
||||||
|
local len = (px1 - px0) * 4
|
||||||
|
local opaque = aa >= 255 and ab >= 255
|
||||||
|
local prev_dst, prev_o = nil, 0
|
||||||
|
for y = py0, py1 - 1 do
|
||||||
|
local o = (y * buf_w + px0) * 4
|
||||||
|
local dst = (not opaque) and readstring(buf, o, len) or true
|
||||||
|
if prev_dst ~= nil and dst == prev_dst then
|
||||||
|
buffer.copy(buf, o, buf, prev_o, len)
|
||||||
|
else
|
||||||
|
local oo = o
|
||||||
|
for x = px0, px1 - 1 do
|
||||||
|
local f = (x + 0.5 - x0) * inv
|
||||||
|
local nf = 1 - f
|
||||||
|
BlendPixel(oo, ra * nf + rb * f, ga * nf + gb * f, ba * nf + bb * f, aa * nf + ab * f)
|
||||||
|
oo = oo + 4
|
||||||
|
end
|
||||||
|
prev_dst = dst
|
||||||
|
end
|
||||||
|
prev_o = o
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetLayer(bd, draw_list)
|
||||||
|
local layer = bd.Layers[draw_list]
|
||||||
|
if layer then return layer end
|
||||||
|
local frame = Instance.new("Frame")
|
||||||
|
frame.Name = "Layer"
|
||||||
|
frame.BackgroundTransparency = 1
|
||||||
|
frame.BorderSizePixel = 0
|
||||||
|
frame.Size = UDim2.fromOffset(0, 0)
|
||||||
|
frame.Visible = false
|
||||||
|
frame.Parent = bd.Gui
|
||||||
|
layer = { Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, Touched = {}, NTouched = 0,
|
||||||
|
Visible = false, X = false, Y = false, Z = -1, LastUsed = 0 }
|
||||||
|
bd.Layers[draw_list] = layer
|
||||||
|
return layer
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetTile(bd, layer, key, tx, ty)
|
||||||
|
local tile = layer.Tiles[key]
|
||||||
if tile then return tile end
|
if tile then return tile end
|
||||||
local img = AssetService:CreateEditableImage({ Size = Vector2.new(TILE, TILE) })
|
local img = AssetService:CreateEditableImage({ Size = Vector2.new(TILE, TILE) })
|
||||||
assert(img, "imgui_impl_roblox: EditableImage memory budget exceeded")
|
assert(img, "imgui_impl_roblox: EditableImage memory budget exceeded")
|
||||||
local label = Instance.new("ImageLabel")
|
local label = Instance.new("ImageLabel")
|
||||||
label.Name = "Tile" .. index
|
label.Name = "Tile" .. key
|
||||||
label.BackgroundTransparency = 1
|
label.BackgroundTransparency = 1
|
||||||
label.BorderSizePixel = 0
|
label.BorderSizePixel = 0
|
||||||
local scale = bd.Scale
|
local scale = bd.Scale
|
||||||
@@ -33624,15 +33679,20 @@ local function GetTile(bd, index, tx, ty)
|
|||||||
label.ImageContent = Content.fromObject(img)
|
label.ImageContent = Content.fromObject(img)
|
||||||
label.Visible = false
|
label.Visible = false
|
||||||
label.Active = bd.TilesActive
|
label.Active = bd.TilesActive
|
||||||
label.Parent = bd.Gui
|
label.Parent = layer.Frame
|
||||||
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false }
|
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false }
|
||||||
bd.Tiles[index] = tile
|
layer.Tiles[key] = tile
|
||||||
return tile
|
return tile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function DestroyLayer(layer)
|
||||||
|
for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end
|
||||||
|
layer.Frame:Destroy()
|
||||||
|
end
|
||||||
|
|
||||||
local function DestroyTiles(bd)
|
local function DestroyTiles(bd)
|
||||||
for _, tile in pairs(bd.Tiles) do tile.Label:Destroy(); tile.Image:Destroy() end
|
for _, layer in pairs(bd.Layers) do DestroyLayer(layer) end
|
||||||
bd.Tiles = {}
|
bd.Layers = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- MicroProfiler labels, enabled with ImGui_ImplRoblox.SetProfiling(true)
|
-- MicroProfiler labels, enabled with ImGui_ImplRoblox.SetProfiling(true)
|
||||||
@@ -33666,56 +33726,56 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
|
|
||||||
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
|
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
|
||||||
if sw <= 0 or sh <= 0 then PE(); return end
|
if sw <= 0 or sh <= 0 then PE(); return end
|
||||||
local cols, rows = ceil(sw / TILE), ceil(sh / TILE)
|
|
||||||
if cols ~= bd.TileCols or rows ~= bd.TileRows then
|
|
||||||
DestroyTiles(bd)
|
|
||||||
bd.TileCols, bd.TileRows = cols, rows
|
|
||||||
end
|
|
||||||
local ntiles = cols * rows
|
|
||||||
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
|
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
|
||||||
|
|
||||||
-- Per tile: hashes and the list of primitives touching it
|
|
||||||
local th1, th2, tn, tl = bd.TH1, bd.TH2, bd.TN, bd.TL
|
|
||||||
for t = 0, ntiles - 1 do
|
|
||||||
th1[t], th2[t], tn[t] = 0, 0, 0
|
|
||||||
if not tl[t] then tl[t] = {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Pass 1: collect primitives, hash them into tiles
|
|
||||||
PB("ImGui tile hash")
|
|
||||||
local n = 0
|
local n = 0
|
||||||
|
local z = 0
|
||||||
for _, draw_list in draw_data.CmdLists:iter() do
|
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
|
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
|
||||||
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)
|
||||||
elseif pcmd.ElemCount > 0 then
|
elseif pcmd.ElemCount > 0 then
|
||||||
local clip = pcmd.ClipRect
|
local clip = pcmd.ClipRect
|
||||||
local cx0, cy0 = max(floor(clip.x - ox), 0), max(floor(clip.y - oy), 0)
|
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), sw), min(floor(clip.w - oy), sh)
|
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1)
|
||||||
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]
|
||||||
local ch1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
|
|
||||||
local ch2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
|
|
||||||
local vo = pcmd.VtxOffset
|
local vo = pcmd.VtxOffset
|
||||||
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
|
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
|
||||||
while i <= last do
|
while i <= last do
|
||||||
local ia, ic = idx[i], idx[i + 2]
|
local ia, ic = idx[i], idx[i + 2]
|
||||||
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
|
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
|
||||||
local vd = false
|
local vd = false
|
||||||
|
local gk = false
|
||||||
local pa, pb, pc = va[1], vb[1], vc[1]
|
local pa, pb, pc = va[1], vb[1], vc[1]
|
||||||
local bx0, by0, bx1, by1
|
local bx0, by0, bx1, by1
|
||||||
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
|
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
|
||||||
local d = vtx[vo + idx[i + 5]]
|
local d = vtx[vo + idx[i + 5]]
|
||||||
local pd = d[1]
|
local pd = d[1]
|
||||||
-- axis aligned quad (TL, TR, BR, BL) with one colour and a matching uv rect
|
-- 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
|
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 col = va[3]
|
||||||
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
|
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
|
if vb[3] == col and vc[3] == col and d[3] == col then
|
||||||
and tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y 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
|
||||||
vd = d
|
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)
|
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)
|
bx1, by1 = ceil(pc.x - ox - 0.5), ceil(pc.y - oy - 0.5)
|
||||||
end
|
end
|
||||||
@@ -33734,55 +33794,93 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
if by1 > cy1 then by1 = cy1 end
|
if by1 > cy1 then by1 = cy1 end
|
||||||
if bx0 < bx1 and by0 < by1 then
|
if bx0 < bx1 and by0 < by1 then
|
||||||
n = n + 1
|
n = n + 1
|
||||||
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n] = va, vb, vc, vd, t
|
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
|
||||||
local h1, h2 = HashVertex(ch1, ch2, va)
|
pr_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
|
||||||
h1, h2 = HashVertex(h1, h2, vb)
|
if bx0 < lx0 then lx0 = bx0 end
|
||||||
h1, h2 = HashVertex(h1, h2, vc)
|
if by0 < ly0 then ly0 = by0 end
|
||||||
if vd then h1, h2 = HashVertex(h1, h2, vd) end
|
|
||||||
for ty = floor(by0 / TILE), floor((by1 - 1) / TILE) do
|
|
||||||
for tx = floor(bx0 / TILE), floor((bx1 - 1) / TILE) do
|
|
||||||
local ti = ty * cols + tx
|
|
||||||
local k = tn[ti] + 1
|
|
||||||
tn[ti] = k
|
|
||||||
tl[ti][k] = n
|
|
||||||
th1[ti] = (th1[ti] * 48271 + h1) % P1
|
|
||||||
th2[ti] = (th2[ti] * 16807 + h2 + k) % P2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
PE()
|
||||||
|
|
||||||
PE()
|
local layer = GetLayer(bd, draw_list)
|
||||||
|
layer.LastUsed = frame_no
|
||||||
-- Pass 2: redraw + upload changed tiles
|
if n < first then
|
||||||
PB("ImGui raster+upload")
|
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end
|
||||||
local force = bd.ForceRedraw
|
else
|
||||||
bd.ForceRedraw = false
|
-- Pass 2: hash primitives into layer-relative tiles
|
||||||
for ty = 0, rows - 1 do
|
PB("ImGui tile hash")
|
||||||
for tx = 0, cols - 1 do
|
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
|
||||||
local ti = ty * cols + tx
|
for k = 1, layer.NTouched do tn[touched[k]] = 0 end
|
||||||
local count = tn[ti]
|
local nt = 0
|
||||||
local tile = bd.Tiles[ti]
|
for p = first, n do
|
||||||
if count == 0 then
|
local tex_id = pr_tex[p]
|
||||||
if tile and tile.Visible then
|
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
|
||||||
tile.Visible = false
|
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
|
||||||
tile.Label.Visible = false
|
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
|
||||||
tile.H1 = -1
|
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
|
||||||
|
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
|
||||||
else
|
end
|
||||||
tile = tile or GetTile(bd, ti, tx, ty)
|
layer.NTouched = nt
|
||||||
if force or tile.H1 ~= th1[ti] or tile.H2 ~= th2[ti] then
|
PE()
|
||||||
tile.H1, tile.H2 = th1[ti], th2[ti]
|
|
||||||
local x0, y0 = tx * TILE, ty * TILE
|
-- 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
|
local x1, y1 = x0 + TILE, y0 + TILE
|
||||||
buf, buf_w = tile.Buffer, TILE
|
buf, buf_w = tile.Buffer, TILE
|
||||||
buffer.fill(buf, 0, 0)
|
buffer.fill(buf, 0, 0)
|
||||||
local list = tl[ti]
|
local list = tl[key]
|
||||||
local tox, toy = ox + x0, oy + y0
|
local tox, toy = ox + x0, oy + y0
|
||||||
for k = 1, count do
|
for k = 1, count do
|
||||||
local p = list[k]
|
local p = list[k]
|
||||||
@@ -33791,7 +33889,14 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
|
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
|
||||||
if vd then
|
if vd then
|
||||||
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
|
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
|
||||||
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)
|
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
|
else
|
||||||
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
|
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
|
||||||
end
|
end
|
||||||
@@ -33805,13 +33910,25 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
tile.Label.Visible = true
|
tile.Label.Visible = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
PE()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
|
||||||
|
for draw_list, layer in pairs(bd.Layers) do
|
||||||
|
if layer.LastUsed ~= frame_no then
|
||||||
|
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end
|
||||||
|
if frame_no - layer.LastUsed > LAYER_KEEP_FRAMES then
|
||||||
|
DestroyLayer(layer)
|
||||||
|
bd.Layers[draw_list] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- drop references so old vertex tables can be collected
|
-- drop references so old vertex tables can be collected
|
||||||
for k = n + 1, bd.LastPrimCount do pr_a[k], pr_b[k], pr_c[k], pr_d[k], pr_t[k] = nil, nil, nil, nil, nil end
|
for k = n + 1, bd.LastPrimCount do pr_a[k], pr_b[k], pr_c[k], pr_d[k], pr_t[k] = nil, nil, nil, nil, nil end
|
||||||
bd.LastPrimCount = n
|
bd.LastPrimCount = n
|
||||||
PE(); PE()
|
PE()
|
||||||
end
|
end
|
||||||
|
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
@@ -33830,12 +33947,13 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
gui.IgnoreGuiInset = true -- so GetMouseLocation() and the tiles share the same origin
|
gui.IgnoreGuiInset = true -- so GetMouseLocation() and the tiles share the same origin
|
||||||
gui.ResetOnSpawn = false
|
gui.ResetOnSpawn = false
|
||||||
gui.DisplayOrder = 1000
|
gui.DisplayOrder = 1000
|
||||||
|
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling -- layers (one Frame per draw list) stack by ZIndex
|
||||||
gui.Parent = parent or Players.LocalPlayer:WaitForChild("PlayerGui")
|
gui.Parent = parent or Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||||
|
|
||||||
local bd = {
|
local bd = {
|
||||||
Time = os.clock(), Gui = gui, Connections = {},
|
Time = os.clock(), Gui = gui, Connections = {},
|
||||||
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
|
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
|
||||||
Tiles = {}, TileCols = 0, TileRows = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, NextRender = 0, TH1 = {}, TH2 = {}, TN = {}, TL = {}, LastPrimCount = 0, ForceRedraw = true,
|
Layers = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
|
||||||
}
|
}
|
||||||
io.BackendPlatformUserData = bd
|
io.BackendPlatformUserData = bd
|
||||||
|
|
||||||
@@ -33935,7 +34053,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 _, tile in pairs(bd.Tiles) do tile.Label.Active = want end
|
for _, layer in pairs(bd.Layers) do for _, tile in pairs(layer.Tiles) do tile.Label.Active = want end end
|
||||||
end
|
end
|
||||||
PE()
|
PE()
|
||||||
end
|
end
|
||||||
@@ -33954,7 +34072,6 @@ function ImGui_ImplRoblox_SetRenderScale(scale)
|
|||||||
if scale == bd.Scale then return end
|
if scale == bd.Scale then return end
|
||||||
bd.Scale = scale
|
bd.Scale = scale
|
||||||
DestroyTiles(bd)
|
DestroyTiles(bd)
|
||||||
bd.TileCols, bd.TileRows = 0, 0
|
|
||||||
bd.ForceRedraw = true
|
bd.ForceRedraw = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -416,36 +416,28 @@ local function DrawTriangle(va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Quads emitted by PrimRect/PrimRectUV/RenderText use indices (a, b, c, a, c, d) with a=TL, b=TR, c=BR, d=BL.
|
-- Layers: every ImDrawList (one per top level window, popup, tooltip, plus the viewport bg/fg lists) gets its own
|
||||||
-- When such a quad is axis aligned with one colour it is drawn with the much cheaper DrawRect.
|
-- Frame, stacked by draw order. A layer is covered by TILE x TILE EditableImages (one ImageLabel each) placed
|
||||||
@native
|
-- relative to the layer's origin (top-left of its content). Primitives are hashed into tiles in layer-relative
|
||||||
local function TryDrawRect(va, vb, vc, vd, t, cx0, cy0, cx1, cy1, ox, oy)
|
-- coordinates, so moving a window only moves its Frame: no tile changes, nothing re-rasterized or uploaded.
|
||||||
local pa, pb, pc, pd = va[1], vb[1], vc[1], vd[1]
|
-- Only tiles whose hash changed are redrawn; the hash covers everything that affects a tile's pixels.
|
||||||
if not (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 return false end
|
|
||||||
local col = va[3]
|
|
||||||
if vb[3] ~= col or vc[3] ~= col or vd[3] ~= col then return false end
|
|
||||||
local ta, tb, tc, td = va[2], vb[2], vc[2], vd[2]
|
|
||||||
if not (tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y) then return false end
|
|
||||||
DrawRect(pa.x - ox, pa.y - oy, pc.x - ox, pc.y - oy, ta.x, ta.y, tc.x, tc.y, col, t, cx0, cy0, cx1, cy1)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Tiles: the screen is covered by TILE x TILE EditableImages (one ImageLabel each). Every frame each primitive
|
|
||||||
-- (triangle, or axis aligned quad) is hashed into the tiles it touches; only tiles whose hash changed are
|
|
||||||
-- re-rasterized and uploaded. Static UI therefore costs ~nothing to draw, and the result is pixel-identical to a
|
|
||||||
-- full redraw (the hash covers everything that affects a tile's pixels: geometry, uv, colour, texture, clip, order).
|
|
||||||
local TILE = 256
|
local TILE = 256
|
||||||
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 CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it
|
||||||
|
local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames
|
||||||
|
|
||||||
-- Primitive arrays (reused between frames, no per-frame allocation)
|
-- Primitive arrays (reused between frames, no per-frame allocation)
|
||||||
local pr_a, pr_b, pr_c, pr_d, pr_t = {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture
|
local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind
|
||||||
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_tex = {} -- texture id
|
||||||
|
|
||||||
@native
|
@native
|
||||||
local function HashVertex(h1, h2, v)
|
local function HashVertex(h1, h2, v, ox, oy)
|
||||||
local p, u, c = v[1], v[2], v[3]
|
local p, u, c = v[1], v[2], v[3]
|
||||||
local x, y = p.x, p.y
|
local x, y = p.x - ox, p.y - oy
|
||||||
local uv = u.x * 8192 + u.y
|
local uv = u.x * 8192 + u.y
|
||||||
h1 = (h1 * 48271 + x * 4096 + y) % P1
|
h1 = (h1 * 48271 + x * 4096 + y) % P1
|
||||||
h1 = (h1 * 48271 + uv * 4096) % P1
|
h1 = (h1 * 48271 + uv * 4096) % P1
|
||||||
@@ -456,13 +448,76 @@ local function HashVertex(h1, h2, v)
|
|||||||
return h1, h2
|
return h1, h2
|
||||||
end
|
end
|
||||||
|
|
||||||
local function GetTile(bd, index, tx, ty)
|
-- Axis aligned quad with a linear colour gradient and constant uv (ColorPicker hue/SV/alpha bars, AddRectFilledMultiColor).
|
||||||
local tile = bd.Tiles[index]
|
-- kind 1: colour varies along y (ca top, cb bottom), kind 2: along x (ca left, cb right). Same pixels as the two triangles.
|
||||||
|
@native
|
||||||
|
local function DrawRectGradient(x0, y0, x1, y1, ca, cb, kind, t, u, v, cx0, cy0, cx1, cy1)
|
||||||
|
local px0, py0 = max(ceil(x0 - 0.5), cx0), max(ceil(y0 - 0.5), cy0)
|
||||||
|
local px1, py1 = min(ceil(x1 - 0.5), cx1), min(ceil(y1 - 0.5), cy1)
|
||||||
|
if px0 >= px1 or py0 >= py1 then return end
|
||||||
|
local ra, ga, ba, aa = UnpackColor(ca)
|
||||||
|
local rb, gb, bb, ab = UnpackColor(cb)
|
||||||
|
if t then
|
||||||
|
local tr, tg, tb, ta = SampleTexture(t, u, v)
|
||||||
|
ra, ga, ba, aa = ra * tr / 255, ga * tg / 255, ba * tb / 255, aa * ta / 255
|
||||||
|
rb, gb, bb, ab = rb * tr / 255, gb * tg / 255, bb * tb / 255, ab * ta / 255
|
||||||
|
end
|
||||||
|
if kind == 1 then
|
||||||
|
local inv = 1 / (y1 - y0)
|
||||||
|
for y = py0, py1 - 1 do
|
||||||
|
local f = (y + 0.5 - y0) * inv
|
||||||
|
local nf = 1 - f
|
||||||
|
FillSpan(y, px0, px1, ra * nf + rb * f, ga * nf + gb * f, ba * nf + bb * f, aa * nf + ab * f)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Every row gets the same colours: when a row's destination equals the previous row's, copy its result
|
||||||
|
local inv = 1 / (x1 - x0)
|
||||||
|
local len = (px1 - px0) * 4
|
||||||
|
local opaque = aa >= 255 and ab >= 255
|
||||||
|
local prev_dst, prev_o = nil, 0
|
||||||
|
for y = py0, py1 - 1 do
|
||||||
|
local o = (y * buf_w + px0) * 4
|
||||||
|
local dst = (not opaque) and readstring(buf, o, len) or true
|
||||||
|
if prev_dst ~= nil and dst == prev_dst then
|
||||||
|
buffer.copy(buf, o, buf, prev_o, len)
|
||||||
|
else
|
||||||
|
local oo = o
|
||||||
|
for x = px0, px1 - 1 do
|
||||||
|
local f = (x + 0.5 - x0) * inv
|
||||||
|
local nf = 1 - f
|
||||||
|
BlendPixel(oo, ra * nf + rb * f, ga * nf + gb * f, ba * nf + bb * f, aa * nf + ab * f)
|
||||||
|
oo = oo + 4
|
||||||
|
end
|
||||||
|
prev_dst = dst
|
||||||
|
end
|
||||||
|
prev_o = o
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetLayer(bd, draw_list)
|
||||||
|
local layer = bd.Layers[draw_list]
|
||||||
|
if layer then return layer end
|
||||||
|
local frame = Instance.new("Frame")
|
||||||
|
frame.Name = "Layer"
|
||||||
|
frame.BackgroundTransparency = 1
|
||||||
|
frame.BorderSizePixel = 0
|
||||||
|
frame.Size = UDim2.fromOffset(0, 0)
|
||||||
|
frame.Visible = false
|
||||||
|
frame.Parent = bd.Gui
|
||||||
|
layer = { Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, Touched = {}, NTouched = 0,
|
||||||
|
Visible = false, X = false, Y = false, Z = -1, LastUsed = 0 }
|
||||||
|
bd.Layers[draw_list] = layer
|
||||||
|
return layer
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetTile(bd, layer, key, tx, ty)
|
||||||
|
local tile = layer.Tiles[key]
|
||||||
if tile then return tile end
|
if tile then return tile end
|
||||||
local img = AssetService:CreateEditableImage({ Size = Vector2.new(TILE, TILE) })
|
local img = AssetService:CreateEditableImage({ Size = Vector2.new(TILE, TILE) })
|
||||||
assert(img, "imgui_impl_roblox: EditableImage memory budget exceeded")
|
assert(img, "imgui_impl_roblox: EditableImage memory budget exceeded")
|
||||||
local label = Instance.new("ImageLabel")
|
local label = Instance.new("ImageLabel")
|
||||||
label.Name = "Tile" .. index
|
label.Name = "Tile" .. key
|
||||||
label.BackgroundTransparency = 1
|
label.BackgroundTransparency = 1
|
||||||
label.BorderSizePixel = 0
|
label.BorderSizePixel = 0
|
||||||
local scale = bd.Scale
|
local scale = bd.Scale
|
||||||
@@ -472,15 +527,20 @@ local function GetTile(bd, index, tx, ty)
|
|||||||
label.ImageContent = Content.fromObject(img)
|
label.ImageContent = Content.fromObject(img)
|
||||||
label.Visible = false
|
label.Visible = false
|
||||||
label.Active = bd.TilesActive
|
label.Active = bd.TilesActive
|
||||||
label.Parent = bd.Gui
|
label.Parent = layer.Frame
|
||||||
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false }
|
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false }
|
||||||
bd.Tiles[index] = tile
|
layer.Tiles[key] = tile
|
||||||
return tile
|
return tile
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function DestroyLayer(layer)
|
||||||
|
for _, tile in pairs(layer.Tiles) do tile.Image:Destroy() end
|
||||||
|
layer.Frame:Destroy()
|
||||||
|
end
|
||||||
|
|
||||||
local function DestroyTiles(bd)
|
local function DestroyTiles(bd)
|
||||||
for _, tile in pairs(bd.Tiles) do tile.Label:Destroy(); tile.Image:Destroy() end
|
for _, layer in pairs(bd.Layers) do DestroyLayer(layer) end
|
||||||
bd.Tiles = {}
|
bd.Layers = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- MicroProfiler labels, enabled with ImGui_ImplRoblox.SetProfiling(true)
|
-- MicroProfiler labels, enabled with ImGui_ImplRoblox.SetProfiling(true)
|
||||||
@@ -514,56 +574,56 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
|
|
||||||
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
|
local sw, sh = floor(draw_data.DisplaySize.x), floor(draw_data.DisplaySize.y)
|
||||||
if sw <= 0 or sh <= 0 then PE(); return end
|
if sw <= 0 or sh <= 0 then PE(); return end
|
||||||
local cols, rows = ceil(sw / TILE), ceil(sh / TILE)
|
|
||||||
if cols ~= bd.TileCols or rows ~= bd.TileRows then
|
|
||||||
DestroyTiles(bd)
|
|
||||||
bd.TileCols, bd.TileRows = cols, rows
|
|
||||||
end
|
|
||||||
local ntiles = cols * rows
|
|
||||||
local ox, oy = draw_data.DisplayPos.x, draw_data.DisplayPos.y
|
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
|
||||||
|
|
||||||
-- Per tile: hashes and the list of primitives touching it
|
|
||||||
local th1, th2, tn, tl = bd.TH1, bd.TH2, bd.TN, bd.TL
|
|
||||||
for t = 0, ntiles - 1 do
|
|
||||||
th1[t], th2[t], tn[t] = 0, 0, 0
|
|
||||||
if not tl[t] then tl[t] = {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Pass 1: collect primitives, hash them into tiles
|
|
||||||
PB("ImGui tile hash")
|
|
||||||
local n = 0
|
local n = 0
|
||||||
|
local z = 0
|
||||||
for _, draw_list in draw_data.CmdLists:iter() do
|
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
|
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
|
||||||
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)
|
||||||
elseif pcmd.ElemCount > 0 then
|
elseif pcmd.ElemCount > 0 then
|
||||||
local clip = pcmd.ClipRect
|
local clip = pcmd.ClipRect
|
||||||
local cx0, cy0 = max(floor(clip.x - ox), 0), max(floor(clip.y - oy), 0)
|
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), sw), min(floor(clip.w - oy), sh)
|
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1)
|
||||||
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]
|
||||||
local ch1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
|
|
||||||
local ch2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
|
|
||||||
local vo = pcmd.VtxOffset
|
local vo = pcmd.VtxOffset
|
||||||
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
|
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
|
||||||
while i <= last do
|
while i <= last do
|
||||||
local ia, ic = idx[i], idx[i + 2]
|
local ia, ic = idx[i], idx[i + 2]
|
||||||
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
|
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
|
||||||
local vd = false
|
local vd = false
|
||||||
|
local gk = false
|
||||||
local pa, pb, pc = va[1], vb[1], vc[1]
|
local pa, pb, pc = va[1], vb[1], vc[1]
|
||||||
local bx0, by0, bx1, by1
|
local bx0, by0, bx1, by1
|
||||||
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
|
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
|
||||||
local d = vtx[vo + idx[i + 5]]
|
local d = vtx[vo + idx[i + 5]]
|
||||||
local pd = d[1]
|
local pd = d[1]
|
||||||
-- axis aligned quad (TL, TR, BR, BL) with one colour and a matching uv rect
|
-- 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
|
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 col = va[3]
|
||||||
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
|
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
|
if vb[3] == col and vc[3] == col and d[3] == col then
|
||||||
and tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y 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
|
||||||
vd = d
|
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)
|
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)
|
bx1, by1 = ceil(pc.x - ox - 0.5), ceil(pc.y - oy - 0.5)
|
||||||
end
|
end
|
||||||
@@ -582,55 +642,93 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
if by1 > cy1 then by1 = cy1 end
|
if by1 > cy1 then by1 = cy1 end
|
||||||
if bx0 < bx1 and by0 < by1 then
|
if bx0 < bx1 and by0 < by1 then
|
||||||
n = n + 1
|
n = n + 1
|
||||||
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n] = va, vb, vc, vd, t
|
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
|
||||||
local h1, h2 = HashVertex(ch1, ch2, va)
|
pr_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
|
||||||
h1, h2 = HashVertex(h1, h2, vb)
|
if bx0 < lx0 then lx0 = bx0 end
|
||||||
h1, h2 = HashVertex(h1, h2, vc)
|
if by0 < ly0 then ly0 = by0 end
|
||||||
if vd then h1, h2 = HashVertex(h1, h2, vd) end
|
|
||||||
for ty = floor(by0 / TILE), floor((by1 - 1) / TILE) do
|
|
||||||
for tx = floor(bx0 / TILE), floor((bx1 - 1) / TILE) do
|
|
||||||
local ti = ty * cols + tx
|
|
||||||
local k = tn[ti] + 1
|
|
||||||
tn[ti] = k
|
|
||||||
tl[ti][k] = n
|
|
||||||
th1[ti] = (th1[ti] * 48271 + h1) % P1
|
|
||||||
th2[ti] = (th2[ti] * 16807 + h2 + k) % P2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
PE()
|
||||||
|
|
||||||
PE()
|
local layer = GetLayer(bd, draw_list)
|
||||||
|
layer.LastUsed = frame_no
|
||||||
-- Pass 2: redraw + upload changed tiles
|
if n < first then
|
||||||
PB("ImGui raster+upload")
|
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end
|
||||||
local force = bd.ForceRedraw
|
else
|
||||||
bd.ForceRedraw = false
|
-- Pass 2: hash primitives into layer-relative tiles
|
||||||
for ty = 0, rows - 1 do
|
PB("ImGui tile hash")
|
||||||
for tx = 0, cols - 1 do
|
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
|
||||||
local ti = ty * cols + tx
|
for k = 1, layer.NTouched do tn[touched[k]] = 0 end
|
||||||
local count = tn[ti]
|
local nt = 0
|
||||||
local tile = bd.Tiles[ti]
|
for p = first, n do
|
||||||
if count == 0 then
|
local tex_id = pr_tex[p]
|
||||||
if tile and tile.Visible then
|
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
|
||||||
tile.Visible = false
|
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
|
||||||
tile.Label.Visible = false
|
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
|
||||||
tile.H1 = -1
|
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
|
||||||
|
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
|
||||||
else
|
end
|
||||||
tile = tile or GetTile(bd, ti, tx, ty)
|
layer.NTouched = nt
|
||||||
if force or tile.H1 ~= th1[ti] or tile.H2 ~= th2[ti] then
|
PE()
|
||||||
tile.H1, tile.H2 = th1[ti], th2[ti]
|
|
||||||
local x0, y0 = tx * TILE, ty * TILE
|
-- 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
|
local x1, y1 = x0 + TILE, y0 + TILE
|
||||||
buf, buf_w = tile.Buffer, TILE
|
buf, buf_w = tile.Buffer, TILE
|
||||||
buffer.fill(buf, 0, 0)
|
buffer.fill(buf, 0, 0)
|
||||||
local list = tl[ti]
|
local list = tl[key]
|
||||||
local tox, toy = ox + x0, oy + y0
|
local tox, toy = ox + x0, oy + y0
|
||||||
for k = 1, count do
|
for k = 1, count do
|
||||||
local p = list[k]
|
local p = list[k]
|
||||||
@@ -639,7 +737,14 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
|
local va, vc, vd = pr_a[p], pr_c[p], pr_d[p]
|
||||||
if vd then
|
if vd then
|
||||||
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
|
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
|
||||||
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)
|
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
|
else
|
||||||
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
|
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
|
||||||
end
|
end
|
||||||
@@ -653,13 +758,25 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
tile.Label.Visible = true
|
tile.Label.Visible = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
PE()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
|
||||||
|
for draw_list, layer in pairs(bd.Layers) do
|
||||||
|
if layer.LastUsed ~= frame_no then
|
||||||
|
if layer.Visible then layer.Visible = false; layer.Frame.Visible = false end
|
||||||
|
if frame_no - layer.LastUsed > LAYER_KEEP_FRAMES then
|
||||||
|
DestroyLayer(layer)
|
||||||
|
bd.Layers[draw_list] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- drop references so old vertex tables can be collected
|
-- drop references so old vertex tables can be collected
|
||||||
for k = n + 1, bd.LastPrimCount do pr_a[k], pr_b[k], pr_c[k], pr_d[k], pr_t[k] = nil, nil, nil, nil, nil end
|
for k = n + 1, bd.LastPrimCount do pr_a[k], pr_b[k], pr_c[k], pr_d[k], pr_t[k] = nil, nil, nil, nil, nil end
|
||||||
bd.LastPrimCount = n
|
bd.LastPrimCount = n
|
||||||
PE(); PE()
|
PE()
|
||||||
end
|
end
|
||||||
|
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
@@ -678,12 +795,13 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
gui.IgnoreGuiInset = true -- so GetMouseLocation() and the tiles share the same origin
|
gui.IgnoreGuiInset = true -- so GetMouseLocation() and the tiles share the same origin
|
||||||
gui.ResetOnSpawn = false
|
gui.ResetOnSpawn = false
|
||||||
gui.DisplayOrder = 1000
|
gui.DisplayOrder = 1000
|
||||||
|
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling -- layers (one Frame per draw list) stack by ZIndex
|
||||||
gui.Parent = parent or Players.LocalPlayer:WaitForChild("PlayerGui")
|
gui.Parent = parent or Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||||
|
|
||||||
local bd = {
|
local bd = {
|
||||||
Time = os.clock(), Gui = gui, Connections = {},
|
Time = os.clock(), Gui = gui, Connections = {},
|
||||||
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
|
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
|
||||||
Tiles = {}, TileCols = 0, TileRows = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, NextRender = 0, TH1 = {}, TH2 = {}, TN = {}, TL = {}, LastPrimCount = 0, ForceRedraw = true,
|
Layers = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
|
||||||
}
|
}
|
||||||
io.BackendPlatformUserData = bd
|
io.BackendPlatformUserData = bd
|
||||||
|
|
||||||
@@ -783,7 +901,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 _, tile in pairs(bd.Tiles) do tile.Label.Active = want end
|
for _, layer in pairs(bd.Layers) do for _, tile in pairs(layer.Tiles) do tile.Label.Active = want end end
|
||||||
end
|
end
|
||||||
PE()
|
PE()
|
||||||
end
|
end
|
||||||
@@ -802,7 +920,6 @@ function ImGui_ImplRoblox_SetRenderScale(scale)
|
|||||||
if scale == bd.Scale then return end
|
if scale == bd.Scale then return end
|
||||||
bd.Scale = scale
|
bd.Scale = scale
|
||||||
DestroyTiles(bd)
|
DestroyTiles(bd)
|
||||||
bd.TileCols, bd.TileRows = 0, 0
|
|
||||||
bd.ForceRedraw = true
|
bd.ForceRedraw = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Compare two t_composite.luau outputs: compare_composite.py a.txt b.txt
|
||||||
|
import sys
|
||||||
|
def load(p):
|
||||||
|
rows = {}
|
||||||
|
for l in open(p):
|
||||||
|
if l.startswith("ROW"):
|
||||||
|
_, y, h = l.split(); rows[int(y)] = bytes.fromhex(h)
|
||||||
|
return rows
|
||||||
|
a, b = load(sys.argv[1]), load(sys.argv[2])
|
||||||
|
assert a and len(a) == len(b), "missing/uneven output"
|
||||||
|
diff = big = 0; worst = 0
|
||||||
|
for y in a:
|
||||||
|
for x, (p, q) in enumerate(zip(a[y], b[y])):
|
||||||
|
d = abs(p - q)
|
||||||
|
if d: diff += 1
|
||||||
|
if d > 2: big += 1
|
||||||
|
worst = max(worst, d)
|
||||||
|
print("channels differing: %d, by more than 2: %d, max diff: %d" % (diff, big, worst))
|
||||||
|
sys.exit(1 if big else 0)
|
||||||
+1
-1
@@ -67,7 +67,7 @@ def main():
|
|||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
code, out = run(args, "")
|
code, out = run(args, "")
|
||||||
print(out[-4000:])
|
print(out if os.environ.get("FULL_OUTPUT") else out[-4000:])
|
||||||
sys.exit(code)
|
sys.exit(code)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
-- Composites every visible tile ImageLabel (walking the ScreenGui like Roblox: ZIndex order, nested offsets) onto a
|
||||||
|
-- black screen and prints the RGB of a region as hex rows ("ROW y hex"). Compare two backend versions with
|
||||||
|
-- tools/test/compare_composite.py to prove a backend change renders the same pixels.
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
||||||
|
ImGui.CreateContext()
|
||||||
|
G.ImGui_ImplRoblox.Init()
|
||||||
|
G.ImGui_ImplRoblox.SetRenderRate(0)
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local col = ImVec4(0.4, 0.7, 0.2, 0.5)
|
||||||
|
local W, H = 1280, 760
|
||||||
|
local frame = 0
|
||||||
|
|
||||||
|
local function composite(gui)
|
||||||
|
local screen = buffer.create(W * H * 3)
|
||||||
|
local function walk(inst, ox, oy)
|
||||||
|
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
|
||||||
|
for _, c in ipairs(kids) do
|
||||||
|
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)
|
||||||
|
if c.ClassName == "ImageLabel" then
|
||||||
|
local img = c.ImageContent.Object
|
||||||
|
local px, sz = img.Pixels, img.Size.X
|
||||||
|
for ty = 0, sz - 1 do
|
||||||
|
local sy = y + ty
|
||||||
|
if sy >= 0 and sy < H then
|
||||||
|
for tx = 0, sz - 1 do
|
||||||
|
local sx = x + tx
|
||||||
|
if sx >= 0 and sx < W then
|
||||||
|
local o = (ty * sz + tx) * 4
|
||||||
|
local a = buffer.readu8(px, o + 3)
|
||||||
|
if a > 0 then
|
||||||
|
local d = (sy * W + sx) * 3
|
||||||
|
local fa = a / 255
|
||||||
|
for ch = 0, 2 do
|
||||||
|
local v = buffer.readu8(px, o + ch) * fa + buffer.readu8(screen, d + ch) * (1 - fa)
|
||||||
|
buffer.writeu8(screen, d + ch, math.floor(v + 0.5))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
walk(c, x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
walk(gui, 0, 0)
|
||||||
|
return screen
|
||||||
|
end
|
||||||
|
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
frame += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
io.DeltaTime = 1 / 60
|
||||||
|
io:AddMousePosEvent(300 + (frame * 37) % 300, 100 + (frame * 53) % 500)
|
||||||
|
ImGui.NewFrame()
|
||||||
|
ImGui.ShowDemoWindow(true)
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(20, 20)); ImGui.SetNextWindowSize(ImVec2(600, 700))
|
||||||
|
ImGui.Begin("Pickers")
|
||||||
|
ImGui.ColorPicker4("bar", col, G.ImGuiColorEditFlags.PickerHueBar)
|
||||||
|
ImGui.ColorPicker4("wheel", col, G.ImGuiColorEditFlags.PickerHueWheel)
|
||||||
|
ImGui.End()
|
||||||
|
ImGui.Render()
|
||||||
|
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if frame == ARG_FRAMES - 1 then
|
||||||
|
local gui = game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")
|
||||||
|
local screen = composite(gui)
|
||||||
|
for y = 0, H - 1 do
|
||||||
|
local t = table.create(W * 3)
|
||||||
|
for i = 0, W * 3 - 1 do t[i + 1] = string.format("%02x", buffer.readu8(screen, y * W * 3 + i)) end
|
||||||
|
print("ROW", y, table.concat(t))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
-- Benchmark: drags the demo window (real mouse drag on its title bar) and keeps a color picker window open.
|
||||||
|
-- Watch "render" ms and uploads/frame: with per-window layers, dragging should upload ~nothing.
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
||||||
|
ImGui.CreateContext()
|
||||||
|
G.ImGui_ImplRoblox.Init()
|
||||||
|
G.ImGui_ImplRoblox.SetRenderRate(0)
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local col = ImVec4(0.4, 0.7, 0.2, 0.5)
|
||||||
|
local frame = 0
|
||||||
|
local bx, by = 0, 0
|
||||||
|
local MODE = ARG_OPEN == true and "picker" or "drag"
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
frame += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
io.DeltaTime = 1 / 60
|
||||||
|
if MODE == "drag" then
|
||||||
|
local t = math.abs((frame % 200) - 100) -- triangle wave, smooth back and forth
|
||||||
|
if frame == 5 then local w = ImGui.FindWindowByName("ImGui Sincerely Demo"); bx, by = w.Pos.x + 80, w.Pos.y + 8; io:AddMousePosEvent(bx, by) elseif frame == 6 then io:AddMouseButtonEvent(0, true)
|
||||||
|
elseif frame > 6 then io:AddMousePosEvent(bx - t * 5, by + t * 2) end
|
||||||
|
else
|
||||||
|
io:AddMousePosEvent(60 + (frame * 7) % 200, 80 + (frame * 5) % 200)
|
||||||
|
if frame == 6 then io:AddMouseButtonEvent(0, true) end
|
||||||
|
end
|
||||||
|
ImGui.NewFrame()
|
||||||
|
if MODE == "drag" then
|
||||||
|
if frame < 3 then ImGui.SetNextWindowPos(ImVec2(20, 20)); ImGui.SetNextWindowSize(ImVec2(500, 650)) end
|
||||||
|
ImGui.ShowDemoWindow(true)
|
||||||
|
else
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(20, 20)); ImGui.SetNextWindowSize(ImVec2(700, 700))
|
||||||
|
ImGui.Begin("Pickers")
|
||||||
|
ImGui.ColorPicker4("bar", col, G.ImGuiColorEditFlags.PickerHueBar)
|
||||||
|
ImGui.ColorPicker4("wheel", col, G.ImGuiColorEditFlags.PickerHueWheel)
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
ImGui.Render()
|
||||||
|
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if frame == ARG_FRAMES - 1 and MODE == "drag" then
|
||||||
|
local w = ImGui.FindWindowByName("ImGui Sincerely Demo")
|
||||||
|
assert(w.Pos.y > 25, "window was not dragged")
|
||||||
|
print("DRAG OK", w.Pos.x, w.Pos.y, w.Collapsed, ImGui.GetDrawData().TotalVtxCount)
|
||||||
|
end
|
||||||
|
end)
|
||||||
Reference in New Issue
Block a user