fix(vpui): 窗口改双缓冲,消除合成器读到半成品帧造成的闪烁

根因:Tk.redraw 每帧先 _fill_rect 把整窗填成背景色、再逐控件重画,而这块缓冲就是 explorer 合成时逐行 blit 的共享 arena(render_window_rows 直接从 arena 读)。于是整窗纯背景色、控件还没画的那段窗口期一旦被合成器采到就是一次闪烁;鼠标移动让 explorer 重画窗口所在行时同样会撞上。改法:Tk 增加进程本地后备缓冲(懒分配、窗口变大时重新分配并 free 旧的),整帧画完后一次性 memcpy 进 arena 再 flush,合成器任何时刻读到的都是完整帧。代价是每帧一次全窗拷贝(零拷贝通路只在合成那一段保留)。分配失败不静默:打日志后退回直写 arena。
This commit is contained in:
TermiNexus committed 2026-10-16 21:27:14 +08:00
1 parent 6eb000d01d
commit d840c7546f
1 file changed
+23 -2
+23 -2
View File
@@ -1,6 +1,7 @@
import vpsdk.window as window
import vpsdk.process as process
import vpsdk.devfs as devfs
import vpsdk.stdlib as stdlib
import string
define TITLE_H = 32
@@ -1512,6 +1513,8 @@ virtual class Tk(Wm, Misc):
resizable_h: int
_grid_rows: int
_grid_cols: int
_bb: uint32* # 后备缓冲:整帧画完再一次性拷进共享 arena,避免合成器读到半成品帧
_bb_px: int # 后备缓冲容量(像素数),窗口变大时才重新分配
def __init__(self):
global _g_layout_init
@@ -1543,6 +1546,8 @@ virtual class Tk(Wm, Misc):
_g_wl_colspan[i] = 1
_g_wl_sticky[i] = 0
_g_layout_init = 1
self._bb = None
self._bb_px = 0
for j in range(32):
self._title[j] = 0
@@ -1615,11 +1620,27 @@ virtual class Tk(Wm, Misc):
self.bh = sz & 0xFFFF
if not self.buf: return
if self.bw <= 0 or self.bh <= 0: return
_fill_rect(self.buf, self.bw, self.bh, 0, 0, self.bw, self.bh, self.bg_color)
# 双缓冲:整帧先画在**进程本地**的后备缓冲里,画完一次性拷进共享 arena,再 flush。
# 为什么必须这么做:arena 正是 explorer 合成时读的那块内存(render_window_rows
# 直接从它逐行 blit 上屏)。若直接往 arena 上画,合成器会在"整窗已填成背景色、
# 控件还没画"的中途采样 ⇒ 窗口闪烁;光标移动让 explorer 重画窗口所在行时同样撞上。
px: int = self.bw * self.bh
if self._bb_px < px:
if self._bb: stdlib.free(rawptr(self._bb))
self._bb = uint32*(stdlib.malloc(uint64(px * 4)))
self._bb_px = px
tgt: uint32* = self._bb
if not tgt:
# 分配失败不静默:打日志后**退回直写 arena**(画面仍能出,只是回到有撕裂的形态)。
devfs.serial_puts("[vpui] backbuffer alloc FAILED, drawing straight into arena\n")
tgt = self.buf
_fill_rect(tgt, self.bw, self.bh, 0, 0, self.bw, self.bh, self.bg_color)
for i in range(self.widget_count):
w: Widget* = self.widgets[i]
if w.visible:
w.draw(self.buf, self.bw, self.bh)
w.draw(tgt, self.bw, self.bh)
if tgt != self.buf:
string.memcpy(self.buf, tgt, uint64(px * 4))
window.flush(self.win_id)
def _do_layout(self):