feat(fb0): 新增 FB_WAIT_VBLANK ioctl,合成前等垂直回扫

explorer 直接写 framebuffer 会与显示器扫描并发,造成撕裂/闪烁;而它在 Ring3 没有 IO 端口权限,不能像历史内核桌面那样自己 inb 0x3DA。所以在 /dev/fb0 上加 FB_WAIT_VBLANK(0x4601)由内核代等垂直回扫(带超时,无 VGA 时序时不死等),vpsdk 暴露 fb_wait_vblank(),desk_server.render() 写屏前调用一次。
This commit is contained in:
TermiNexus committed 2026-10-16 21:37:58 +08:00
1 parent 89c37cea22
commit e41701e201
4 files changed
+23 -6

No files matched your search

+6 -1
View File
@@ -62,6 +62,7 @@ _CURSOR_ARROW: [256]uint8 = [
class DeskServer:
# 屏幕
fb: uint32* # /dev/fb0 映射后的线性地址
fb_fd: int # /dev/fb0 的 fd(等 vblank 用)
fb_w: int
fb_h: int
pitch_px: int
@@ -97,8 +98,9 @@ class DeskServer:
return (uint32(255) << 24) | (b << 16) | (g << 8) | r
def init(self, fb: uint32*, w: int, h: int, pitch: int, fmt: uint32,
shm: uint64, kfd: int, mfd: int):
shm: uint64, kfd: int, mfd: int, fb_fd: int):
self.fb = fb
self.fb_fd = fb_fd
self.fb_w = w
self.fb_h = h
self.pitch_px = pitch / 4
@@ -355,6 +357,9 @@ class DeskServer:
def render(self):
if self.dirty_any == 0: return
# 写屏前等垂直回扫:直接写 framebuffer 会与显示器扫描并发 ⇒ 撕裂/闪烁。
# 用户态没有 IO 权限(不能 inb 0x3DA),由内核代等。
devfs.fb_wait_vblank(self.fb_fd)
y: int = 0
while y < self.fb_h:
if self.dirty[y] == 0:
+1 -1
View File
@@ -87,7 +87,7 @@ export def _start() -> int:
# 6) 进入窗口服务端主循环(永不返回):请求环 + 输入 + 合成。
srv: desk_server.DeskServer = desk_server.DeskServer()
srv.init(fb, int(info.width), int(info.height), int(info.pitch), info.format, shm, kfd, mfd)
srv.init(fb, int(info.width), int(info.height), int(info.pitch), info.format, shm, kfd, mfd, fb_fd)
_dbg("[explorer] desk server running\n")
srv.serve()
return 0
+10
View File
@@ -1,5 +1,6 @@
import drivers.serial.uart.serial as serial
import string
import asm
define MAX_FILE_DESCRIPTORS = 64
@@ -487,6 +488,8 @@ def devfs_register_mouse() -> int:
# 本节点只记录 BootInfo 里的物理地址与几何,不自己持有 framebuffer 映射。
# ============================================================================
define FB_GET_INFO = 0x4600
# 等一次垂直回扫:用户态(explorer)没有 IO 端口权限,不能自己 inb 0x3DA,由内核代做。
define FB_WAIT_VBLANK = 0x4601
@__attribute__(packed)
class FbInfo:
@@ -518,6 +521,13 @@ virtual class framebuffer_device(devfs_file):
return -1
def ioctl(self, fd: int, request: int, arg: rawptr) -> int:
if request == FB_WAIT_VBLANK:
# 等进入垂直回扫期(0x3DA bit3)。带超时:当前若无 VGA 时序不至于死等。
t: int = 100000
while (asm.inb(0x3DA) & 0x08) != 0 and t > 0: t -= 1
t = 100000
while (asm.inb(0x3DA) & 0x08) == 0 and t > 0: t -= 1
return 0
if request != FB_GET_INFO or not arg:
return -1
info: FbInfo* = FbInfo*(uint64(arg))
+6 -4
View File
@@ -22,6 +22,7 @@ define DEV_DESK = "/dev/desk"
# ioctl 命令与返回结构:必须与 VKernel/Kernel/drivers/devfs/devfs.vp 的
# FB_GET_INFO / FbInfo 逐字段一致(packed;4×u32 + 2×u64 = 32 字节)。
define FB_GET_INFO = 0x4600
define FB_WAIT_VBLANK = 0x4601
@__attribute__(packed)
class FbInfo:
@@ -50,11 +51,12 @@ def fb_get_info(fd: int, out: FbInfo*) -> int:
string.memset(out, 0, FbInfo.__sizeof__())
return fs.ioctl(fd, uint32(FB_GET_INFO), out)
def fb_map(fd: int, size: uint64) -> uint64:
"""把 framebuffer 映射进本进程,返回线性起始地址(0 = 失败),之后可直接写像素。
def fb_wait_vblank(fd: int) -> int:
"""等一次垂直回扫(合成前调用,减轻 tearing)。用户态无 IO 权限,由内核代做。"""
return fs.ioctl(fd, uint32(FB_WAIT_VBLANK), None)
走通用 mmap(fd = 该设备 fd):内核按设备的 mmap_info 把物理区间按页映射进来。
"""
def fb_map(fd: int, size: uint64) -> uint64:
"""把 framebuffer 映射进本进程,返回线性地址(0 = 失败)。"""
return syscall.mmap(0, size, uint64(syscall.PROT_READ | syscall.PROT_WRITE),
uint64(syscall.MAP_SHARED), uint64(fd), 0)