Perf: ImDrawList vertices in a flat buffer (f64 pos/uv + u32 col); backend reads it directly and skips draw lists identical to last frame
This commit is contained in:
@@ -11,6 +11,7 @@ local AssetService = game:GetService("AssetService")
|
||||
|
||||
local floor, ceil, min, max = math.floor, math.ceil, math.min, math.max
|
||||
local readu8, readu32, writeu32, readstring = buffer.readu8, buffer.readu32, buffer.writeu32, buffer.readstring
|
||||
local readf64 = buffer.readf64
|
||||
local band, rshift = bit32.band, bit32.rshift
|
||||
|
||||
local ImGui_ImplRoblox_GetBackendData
|
||||
@@ -342,10 +343,10 @@ end
|
||||
|
||||
-- Generic triangle: per-vertex colour + uv, top-left fill rule, clipped to [cx0, cx1) x [cy0, cy1)
|
||||
@native
|
||||
local function DrawTriangle(va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy)
|
||||
local x0, y0 = va[1].x - ox, va[1].y - oy
|
||||
local x1, y1 = vb[1].x - ox, vb[1].y - oy
|
||||
local x2, y2 = vc[1].x - ox, vc[1].y - oy
|
||||
local function DrawTriangle(vbuf, va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy)
|
||||
local x0, y0 = readf64(vbuf, va) - ox, readf64(vbuf, va + 8) - oy
|
||||
local x1, y1 = readf64(vbuf, vb) - ox, readf64(vbuf, vb + 8) - oy
|
||||
local x2, y2 = readf64(vbuf, vc) - ox, readf64(vbuf, vc + 8) - oy
|
||||
local area = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0)
|
||||
if area == 0 then return end
|
||||
if area < 0 then -- make the winding consistent so "inside" means all edge functions >= 0
|
||||
@@ -367,10 +368,10 @@ local function DrawTriangle(va, vb, vc, t, cx0, cy0, cx1, cy1, ox, oy)
|
||||
local tl2 = A2 > 0 or (A2 == 0 and B2 > 0)
|
||||
|
||||
local inv = 1 / area
|
||||
local ra, ga, ba, aa = UnpackColor(va[3])
|
||||
local rb, gb, bb, ab = UnpackColor(vb[3])
|
||||
local rc, gc, bc, ac = UnpackColor(vc[3])
|
||||
local ua, vva, ub, vvb, uc, vvc = va[2].x, va[2].y, vb[2].x, vb[2].y, vc[2].x, vc[2].y
|
||||
local ra, ga, ba, aa = UnpackColor(readu32(vbuf, va + 32))
|
||||
local rb, gb, bb, ab = UnpackColor(readu32(vbuf, vb + 32))
|
||||
local rc, gc, bc, ac = UnpackColor(readu32(vbuf, vc + 32))
|
||||
local ua, vva, ub, vvb, uc, vvc = readf64(vbuf, va + 16), readf64(vbuf, va + 24), readf64(vbuf, vb + 16), readf64(vbuf, vb + 24), readf64(vbuf, vc + 16), readf64(vbuf, vc + 24)
|
||||
|
||||
local textured = t and not (ua == ub and ua == uc and vva == vvb and vva == vvc)
|
||||
if t and not textured then -- constant uv (e.g. the atlas white pixel): fold the texel into the vertex colours
|
||||
@@ -433,14 +434,15 @@ 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_bx0, pr_by0, pr_bx1, pr_by1 = {}, {}, {}, {} -- clipped bounds (screen pixels)
|
||||
local pr_tex = {} -- texture id
|
||||
local pr_vb = {} -- vertex buffer (draw list VtxBuffer.Buf)
|
||||
local pr_k = {} -- layer kind (1..3)
|
||||
local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind
|
||||
|
||||
@native
|
||||
local function HashVertex(h1, h2, v, ox, oy)
|
||||
local p, u, c = v[1], v[2], v[3]
|
||||
local x, y = p.x - ox, p.y - oy
|
||||
local uv = u.x * 8192 + u.y
|
||||
local function HashVertex(h1, h2, vbuf, v, ox, oy)
|
||||
local x, y = readf64(vbuf, v) - ox, readf64(vbuf, v + 8) - oy
|
||||
local uv = readf64(vbuf, v + 16) * 8192 + readf64(vbuf, v + 24)
|
||||
local c = readu32(vbuf, v + 32)
|
||||
h1 = (h1 * 48271 + x * 4096 + y) % P1
|
||||
h1 = (h1 * 48271 + uv * 4096) % P1
|
||||
h1 = (h1 * 48271 + c) % P1
|
||||
@@ -571,6 +573,33 @@ local function DestroyTiles(bd)
|
||||
bd.Layers = {}
|
||||
end
|
||||
|
||||
-- Exact signature of everything in a draw list that affects its layers (nil = can't skip: user callbacks)
|
||||
local sig_buf = buffer.create(4096)
|
||||
@native
|
||||
local function DrawListSignature(draw_list, ox, oy)
|
||||
local idx, ni = draw_list.IdxBuffer.Data, draw_list.IdxBuffer.Size
|
||||
local ncmd = draw_list.CmdBuffer.Size
|
||||
local need = ni * 4 + ncmd * 64 + 64
|
||||
if buffer.len(sig_buf) < need then sig_buf = buffer.create(need * 2) end
|
||||
local b, o = sig_buf, 0
|
||||
for i = 1, ni do writeu32(b, o, idx[i]); o += 4 end
|
||||
for _, pcmd in draw_list.CmdBuffer:iter() do
|
||||
if pcmd.UserCallback ~= nil then return nil end
|
||||
local c = pcmd.ClipRect
|
||||
buffer.writef64(b, o, c.x); buffer.writef64(b, o + 8, c.y); buffer.writef64(b, o + 16, c.z); buffer.writef64(b, o + 24, c.w)
|
||||
writeu32(b, o + 32, pcmd.ElemCount); writeu32(b, o + 36, pcmd.IdxOffset); writeu32(b, o + 40, pcmd.VtxOffset)
|
||||
buffer.writef64(b, o + 44, pcmd:GetTexID())
|
||||
o += 52
|
||||
end
|
||||
buffer.writef64(b, o, ox); buffer.writef64(b, o + 8, oy)
|
||||
buffer.writef64(b, o + 16, draw_list._ContentClipX0 or -1); buffer.writef64(b, o + 24, draw_list._ContentClipY0 or -1)
|
||||
buffer.writef64(b, o + 32, draw_list._ContentClipX1 or -1); buffer.writef64(b, o + 40, draw_list._ContentClipY1 or -1)
|
||||
buffer.writef64(b, o + 48, draw_list._ContentClipX0 and draw_list._ScrollOriginX or -1)
|
||||
buffer.writef64(b, o + 56, draw_list._ContentClipX0 and draw_list._ScrollOriginY or -1)
|
||||
o += 64
|
||||
return readstring(draw_list.VtxBuffer.Buf, 0, draw_list.VtxBuffer.Size * VTX_STRIDE) .. readstring(b, 0, o)
|
||||
end
|
||||
|
||||
-- MicroProfiler labels, enabled with ImGui_ImplRoblox.SetProfiling(true)
|
||||
local profilebegin, profileend = debug.profilebegin, debug.profileend
|
||||
local profiling = false
|
||||
@@ -594,11 +623,12 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
||||
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 vbuf = pr_vb[p]
|
||||
h1, h2 = HashVertex(h1, h2, vbuf, pr_a[p], vax, vay)
|
||||
h1, h2 = HashVertex(h1, h2, vbuf, pr_b[p], vax, vay)
|
||||
h1, h2 = HashVertex(h1, h2, vbuf, pr_c[p], vax, vay)
|
||||
local vd = pr_d[p]
|
||||
if vd then h1, h2 = HashVertex(h1, h2, vd, vax, vay) end
|
||||
if vd then h1, h2 = HashVertex(h1, h2, vbuf, 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
|
||||
@@ -687,19 +717,17 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
||||
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]
|
||||
local va, vc, vd, vbuf = pr_a[p], pr_c[p], pr_d[p], pr_vb[p]
|
||||
if vd then
|
||||
local pa, pc, ta, tc = va[1], vc[1], va[2], vc[2]
|
||||
local ax, ay, cx, cy = readf64(vbuf, va) - tox, readf64(vbuf, va + 8) - toy, readf64(vbuf, vc) - tox, readf64(vbuf, vc + 8) - toy
|
||||
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)
|
||||
if gk then
|
||||
DrawRectGradient(ax, ay, cx, cy, readu32(vbuf, va + 32), readu32(vbuf, vc + 32), gk, pr_t[p], readf64(vbuf, va + 16), readf64(vbuf, va + 24), 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)
|
||||
DrawRect(ax, ay, cx, cy, readf64(vbuf, va + 16), readf64(vbuf, va + 24), readf64(vbuf, vc + 16), readf64(vbuf, vc + 24), readu32(vbuf, va + 32), pr_t[p], cx0, cy0, cx1, cy1)
|
||||
end
|
||||
else
|
||||
DrawTriangle(va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
|
||||
DrawTriangle(vbuf, va, pr_b[p], vc, pr_t[p], cx0, cy0, cx1, cy1, tox, toy)
|
||||
end
|
||||
end
|
||||
PB("ImGui upload")
|
||||
@@ -769,10 +797,28 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
||||
local n = 0
|
||||
local z = 0
|
||||
for _, draw_list in draw_data.CmdLists:iter() do
|
||||
-- Unchanged draw list (same vertex bytes, indices, commands, origin and scroll): its layers are already
|
||||
-- correct, only restack them. Static windows then cost one byte compare instead of collect + hash.
|
||||
PB("ImGui unchanged check")
|
||||
local sig = DrawListSignature(draw_list, ox, oy)
|
||||
local set = bd.Layers[draw_list]
|
||||
if sig and set and not force and set.Sig == sig then
|
||||
for kind = 1, 3 do
|
||||
local layer = set[kind]
|
||||
if layer and layer.Visible then
|
||||
z += 1
|
||||
if layer.Z ~= z then layer.Z = z; layer.Root.ZIndex = z end
|
||||
end
|
||||
end
|
||||
set.LastUsed = frame_no
|
||||
PE()
|
||||
continue
|
||||
end
|
||||
PE()
|
||||
-- Pass 1: collect this list's primitives and their bounds
|
||||
PB("ImGui collect")
|
||||
local first = n + 1
|
||||
local vtx, idx = draw_list.VtxBuffer.Data, draw_list.IdxBuffer.Data
|
||||
local vbuf, idx = draw_list.VtxBuffer.Buf, 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
|
||||
@@ -804,36 +850,42 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
||||
local i, last = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount
|
||||
while i <= last do
|
||||
local ia, ic = idx[i], idx[i + 2]
|
||||
local va, vb, vc = vtx[vo + ia], vtx[vo + idx[i + 1]], vtx[vo + ic]
|
||||
-- byte offsets of the vertices (index is 1-based)
|
||||
local va, vb, vc = (vo + ia - 1) * VTX_STRIDE, (vo + idx[i + 1] - 1) * VTX_STRIDE, (vo + ic - 1) * VTX_STRIDE
|
||||
local vd = false
|
||||
local gk = false
|
||||
local pa, pb, pc = va[1], vb[1], vc[1]
|
||||
local pax, pay = readf64(vbuf, va), readf64(vbuf, va + 8)
|
||||
local pbx, pby = readf64(vbuf, vb), readf64(vbuf, vb + 8)
|
||||
local pcx, pcy = readf64(vbuf, vc), readf64(vbuf, vc + 8)
|
||||
local bx0, by0, bx1, by1
|
||||
if i + 5 <= last and idx[i + 3] == ia and idx[i + 4] == ic then
|
||||
local d = vtx[vo + idx[i + 5]]
|
||||
local pd = d[1]
|
||||
local d = (vo + idx[i + 5] - 1) * VTX_STRIDE
|
||||
local pdx, pdy = readf64(vbuf, d), readf64(vbuf, d + 8)
|
||||
-- axis aligned quad (TL, TR, BR, BL)
|
||||
if pa.y == pb.y and pb.x == pc.x and pc.y == pd.y and pd.x == pa.x and pa.x < pb.x and pa.y < pd.y then
|
||||
local col = va[3]
|
||||
local ta, tb, tc, td = va[2], vb[2], vc[2], d[2]
|
||||
if vb[3] == col and vc[3] == col and d[3] == col then
|
||||
if tb.x == tc.x and tb.y == ta.y and td.x == ta.x and td.y == tc.y then vd = d end
|
||||
elseif ta.x == tb.x and ta.x == tc.x and ta.x == td.x and ta.y == tb.y and ta.y == tc.y and ta.y == td.y then
|
||||
if pay == pby and pbx == pcx and pcy == pdy and pdx == pax and pax < pbx and pay < pdy then
|
||||
local col, colb, colc, cold = readu32(vbuf, va + 32), readu32(vbuf, vb + 32), readu32(vbuf, vc + 32), readu32(vbuf, d + 32)
|
||||
local tax, tay = readf64(vbuf, va + 16), readf64(vbuf, va + 24)
|
||||
local tbx, tby = readf64(vbuf, vb + 16), readf64(vbuf, vb + 24)
|
||||
local tcx, tcy = readf64(vbuf, vc + 16), readf64(vbuf, vc + 24)
|
||||
local tdx, tdy = readf64(vbuf, d + 16), readf64(vbuf, d + 24)
|
||||
if colb == col and colc == col and cold == col then
|
||||
if tbx == tcx and tby == tay and tdx == tax and tdy == tcy then vd = d end
|
||||
elseif tax == tbx and tax == tcx and tax == tdx and tay == tby and tay == tcy and tay == tdy 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
|
||||
if colb == col and cold == colc then vd = d; gk = 1
|
||||
elseif cold == col and colb == colc then vd = d; gk = 2 end
|
||||
end
|
||||
if vd then
|
||||
bx0, by0 = ceil(pa.x - ox - 0.5), ceil(pa.y - oy - 0.5)
|
||||
bx1, by1 = ceil(pc.x - ox - 0.5), ceil(pc.y - oy - 0.5)
|
||||
bx0, by0 = ceil(pax - ox - 0.5), ceil(pay - oy - 0.5)
|
||||
bx1, by1 = ceil(pcx - ox - 0.5), ceil(pcy - oy - 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
if vd then
|
||||
i = i + 6
|
||||
else
|
||||
bx0, by0 = floor(min(pa.x, pb.x, pc.x) - ox), floor(min(pa.y, pb.y, pc.y) - oy)
|
||||
bx1, by1 = ceil(max(pa.x, pb.x, pc.x) - ox) + 1, ceil(max(pa.y, pb.y, pc.y) - oy) + 1
|
||||
bx0, by0 = floor(min(pax, pbx, pcx) - ox), floor(min(pay, pby, pcy) - oy)
|
||||
bx1, by1 = ceil(max(pax, pbx, pcx) - ox) + 1, ceil(max(pay, pby, pcy) - oy) + 1
|
||||
i = i + 3
|
||||
end
|
||||
if bx0 < cx0 then bx0 = cx0 end
|
||||
@@ -842,7 +894,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
||||
if by1 > cy1 then by1 = cy1 end
|
||||
if bx0 < bx1 and by0 < by1 then
|
||||
n = n + 1
|
||||
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n], pr_g[n], pr_tex[n] = va, vb, vc, vd, t, gk, tex_id
|
||||
pr_a[n], pr_b[n], pr_c[n], pr_d[n], pr_t[n], pr_g[n], pr_tex[n], pr_vb[n] = va, vb, vc, vd, t, gk, tex_id, vbuf
|
||||
pr_cx0[n], pr_cy0[n], pr_cx1[n], pr_cy1[n] = cx0, cy0, cx1, cy1
|
||||
pr_bx0[n], pr_by0[n], pr_bx1[n], pr_by1[n] = bx0, by0, bx1, by1
|
||||
pr_k[n] = kind
|
||||
@@ -854,7 +906,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
||||
PE()
|
||||
|
||||
-- Split into layer kinds and place each one
|
||||
local set = bd.Layers[draw_list]
|
||||
set = bd.Layers[draw_list]
|
||||
local nk1, nk2, nk3 = 0, 0, 0
|
||||
local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
|
||||
for p = first, n do
|
||||
@@ -885,12 +937,13 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
||||
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
|
||||
if set then set.LastUsed = frame_no; set.Sig = sig end
|
||||
end
|
||||
|
||||
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
|
||||
for draw_list, set in pairs(bd.Layers) do
|
||||
if set.LastUsed ~= frame_no then
|
||||
set.Sig = nil -- hidden now: must go through the full path when shown again
|
||||
for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end
|
||||
if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then
|
||||
DestroyLayerSet(set)
|
||||
@@ -900,7 +953,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
||||
end
|
||||
|
||||
-- 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_t[k], pr_vb[k] = nil, nil end
|
||||
bd.LastPrimCount = n
|
||||
PE()
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user