Files
ViperOS/Apps/Scene3D/main.vp
T
TermiNexus a3c1d221e6 fix(kbd,sched,scene3d): 键盘报告去重;用户线程 return 跳板修正;关窗时序
① Enter 洪流:usb_kbd._on_int_complete 的按下沿依赖 kbd_prev_keycodes,而状态提交在函数末尾第三趟循环;kbd_report 是常驻 DMA 缓冲,按键保持/NAK/重复完成都会让回调反复看到同一份报告 ⇒ 每次中断都注入一次 Enter,Terminal 刷出 [cmd] enter,explorer 被拖死。新增 kbd_prev_sig(modifier+6 keycode 的标量签名)整份报告去重;同一趟循环里先提交 prev 再判定;全 0 报告只更新状态;抽出 _rearm() 保证所有出口都重排中断 IN 且 DMA 读取先于重排。② explorer 限流:kb_pend[64] + _deliver_kb,单轮最多投递 64 字节,ring_push 返回 -1 时停在当前字节(不再当成功丢弃),pending 清空才补读;鼠标取包同样 64 包/轮上限。③ 关窗卡死根因:process.vp 的 _tramp_code 是 mov eax,34; int 0x80; jmp \$,而 Syscall.THREAD_EXIT=60 ⇒ 用户线程 return 后走 syscall 34(无匹配分支)返回 Ring3 在 jmp \$ 空转,空转不再产生 syscall,而内核只在 syscall 返回路径消费 needs_reschedule ⇒ 协作式调度彻底停摆,整机冻结。sched.create_user_thread 在跳板映射后把立即数低字节改为 60(幂等)。④ 次生:主线程先退会立即回收自己的用户栈,而 Scene3D 渲染线程还引用其栈上对象。Scene3D 渲染线程退出改为显式 process.thread_exit() 并置 _render_done;主线程 mainloop 返回后先 yield 等渲染线程退出(上限 10000 次)再 exit。⑤ window.flush 在槽位 used==0 时直接返回;vpui.Tk.redraw 入口判 win_id 与 is_active,窗口已回收时不碰 arena 不 flush。
2026-10-17 16:49:37 +08:00

104 lines
3.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import vpsdk.window as window
import vpsdk.process as process
import vpsdk.vpui as vpui
import vpsdk.devfs as devfs
import string
from stdint import *
import viperlib
import gfx
define SCENE_W = 640
define SCENE_H = 360
_zbuf_mem: [SCENE_W * SCENE_H * 8]uint8 = []
_fb_mem: [SCENE_W * SCENE_H]uint32 = []
_root_ptr: vpui.Tk*
_canvas_ptr: vpui.FBCanvas*
_s3d_ptr: gfx.Scene3D*
_time_val: float = float(0.0)
# 渲染线程的 tid / 已退出标记:主线程必须等它退完才能 process.exit。
# 主线程栈上有 root/canvas/s3d,渲染线程还在引用;process.exit 会立刻回收主线程用户栈,
# 先退主线程 ⇒ 渲染线程在已解映射的栈上取数 ⇒ #PF。
_render_tid: int = -1
_render_done: int = 0
# 时间回绕周期 = 4π:Render 里 time*1 / time*2 / time*1.5 的 sin/cos 参数整体平移 2π 的整数倍
# ⇒ 动画严格连续;同时让相位半径永远很小,避免 float32 精度随运行时间退化
# (x 变大后相邻采样点的相位差被量化 ⇒ 曲线塌成一条平线、热力图颜色失去层次)。
define TIME_WRAP = float(12.566370614359172)
def _dbg(msg: const str):
# 【诊断·临时】与 explorer 同路径的串口输出。app 在建窗失败时会**静默退出**
# (Tk.mainloop 的循环条件是 win_id 有效,win_id=-1 时一次都不循环),所以必须分步打点。
devfs.serial_puts(msg)
def _render_thread(arg: t.CPtr):
global _time_val, _render_done
root: vpui.Tk* = _root_ptr
canvas: vpui.FBCanvas* = _canvas_ptr
s3d: gfx.Scene3D* = _s3d_ptr
while root.winfo_exists():
s3d.Render(_time_val)
_time_val += float(0.02)
if _time_val >= TIME_WRAP: _time_val = _time_val - TIME_WRAP
root.update()
process.yield_cpu()
_render_done = 1
# 窗口已被 explorer 回收:显式 THREAD_EXIT,绝不 return —— 用户线程的 return 会落到
# 出口跳板(旧调用号 34,而 THREAD_EXIT=60)⇒ Ring3 `jmp $` 空转、全机不再调度。
process.thread_exit()
@__attribute__(section(".text.startup"), aligned(16))
export def _start() -> int:
_dbg("[s3d] start\n")
root: vpui.Tk = vpui.Tk()
_dbg("[s3d] tk ok\n")
root.title("3D Scene")
root.set_style(window.STYLE_WIN11)
root.bg_color = uint32(0x0F0F19)
master: vpui.Tk* = &root
canvas: vpui.FBCanvas = vpui.FBCanvas(master, SCENE_W, SCENE_H)
_dbg("[s3d] canvas ok\n")
root.pack(0, vpui.PACK_TOP, vpui.FILL_BOTH, 1, 0, 0)
zbuf: float* = &_zbuf_mem
string.memset(zbuf, 0, SCENE_W * SCENE_H * 8)
fb: UINT32PTR = &_fb_mem
string.memset(fb, 0, SCENE_W * SCENE_H * 4)
canvas.set_fb(fb, SCENE_W, SCENE_H)
s3d: gfx.Scene3D = gfx.Scene3D(fb, zbuf, SCENE_W, SCENE_H)
_dbg("[s3d] scene ok\n")
root.geometry(100, 30, SCENE_W, SCENE_H + 32)
_wbuf: [128]char
viperlib.snprintf(&_wbuf, 128, "[s3d] geometry win_id=%d buf=0x%lx\n",
root.win_id, uint64(root.buf))
_dbg(&_wbuf)
global _root_ptr, _canvas_ptr, _s3d_ptr, _render_tid
_root_ptr = &root
_canvas_ptr = &canvas
_s3d_ptr = &s3d
_render_tid = process.thread_create(_render_thread, None)
_dbg("[s3d] render thread created\n")
root.mainloop()
_dbg("[s3d] mainloop returned\n")
# 窗口已关闭:先等渲染线程退出,才允许回收主线程用户栈(root/canvas/s3d 都在其上)。
# 渲染线程每轮都会 yield 且只差一次循环判定,正常情况下几百次切换内即退出;
# 设上限只是防止它已异常终止却没置标记时把主线程永远卡在这里。
if _render_tid > 0:
wait_n: int = 0
while _render_done == 0 and wait_n < 10000:
wait_n += 1
process.yield_cpu()
process.exit(0)
return 0