diff --git a/VKernel/Kernel/services/desktop.vp b/VKernel/Kernel/services/desktop.vp index 4308628..3777a63 100644 --- a/VKernel/Kernel/services/desktop.vp +++ b/VKernel/Kernel/services/desktop.vp @@ -396,6 +396,11 @@ def _get_title_height(style: int) -> int: def _mark_rows_dirty(start_y: int, count: int): global _dirty_count, _dirty_y_min, _dirty_y_max + # 关键区:本函数会被 App 线程(draw_text/flush/set_title → syscall)与桌面线程并发调用, + # 对 _dirty_rows/_dirty_count/_dirty_y_min/_dirty_y_max 的读-改-写不加锁会丢置脏标记 + # ⇒ 该行永远不重绘,旧合成(例如下方窗口的 title 压在其它窗口上)会留在屏幕上, + # 直到别的操作再次置脏才"弹回去"——正是"更下方的 title 突然跑到最上方、不易复现"。 + asm.cli() for i in range(start_y, start_y + count): if i >= 0 and i < _screen_h: if not _dirty_rows[i]: @@ -405,23 +410,29 @@ def _mark_rows_dirty(start_y: int, count: int): _dirty_y_min = i if i > _dirty_y_max or _dirty_count == 1: _dirty_y_max = i + asm.sti() def _mark_all_dirty(): global _dirty_count, _dirty_y_min, _dirty_y_max + asm.cli() string.memset(_dirty_rows, 1, _screen_h) _dirty_count = _screen_h _dirty_y_min = 0 _dirty_y_max = _screen_h - 1 + asm.sti() def _has_dirty_rows() -> int: return _dirty_count def _clear_dirty_flags(): global _dirty_count, _dirty_y_min, _dirty_y_max + # 关键区:清除与(App 线程的)置脏并发时,会把刚置上的行清掉 ⇒ 同样丢一帧更新 + asm.cli() string.memset(_dirty_rows, 0, _screen_h) _dirty_count = 0 _dirty_y_min = _screen_h _dirty_y_max = -1 + asm.sti() def _wait_vblank(): global _vblank_ok @@ -573,8 +584,11 @@ def _bring_to_front(idx: int): if idx < 0 or idx >= _win_count: return aw: AppWindow* = &_wins[idx] if not aw.active or aw.minimized: return + # 与 create() 共用 _next_z,同样必须原子自增,否则可能出现两个窗口 z_order 相同 + asm.cli() _next_z += 1 aw.z_order = _next_z + asm.sti() def _hit_test_window(mx: int, my: int) -> int: best: int = -1 @@ -2558,18 +2572,24 @@ def _draw_app_window(back: uint32*, win: AppWindow*): def create(x: int, y: int, w: int, h: int, title: const str, style: int) -> int: global _win_count, _next_z, _focused_win - if _win_count >= MAX_APP_WINDOWS: return -1 + # 关键区①:槽位分配必须原子。create() 由各 App 线程经 syscall 并发调用(开机时多个 + # App 同时建窗,最易命中)。若被定时器抢占后读到同一个 _win_count,两个 App 会共用 + # 同一个 AppWindow 槽 ⇒ 标题/坐标/z_order/fb 被互相覆盖 ⇒ "更下方的 title 突然跑到 + # 最上方"(现场)。这里只做"占位",结构体填充放在关键区外,避免长时间关中断。 + asm.cli() + if _win_count >= MAX_APP_WINDOWS: + asm.sti() + return -1 idx: int = _win_count _win_count += 1 + asm.sti() aw: AppWindow* = &_wins[idx] - aw.active = 1 + aw.active = 0 aw.x = x aw.y = y aw.w = w aw.h = h aw.content_h = h - _get_title_height(style) - 2 - _next_z += 1 - aw.z_order = _next_z aw.style = style aw.close_hover = 0 aw.min_hover = 0 @@ -2608,8 +2628,16 @@ def create(x: int, y: int, w: int, h: int, title: const str, style: int) -> int: fb_size: int = w * aw.content_h for fi in range(fb_size): aw.fb[fi] = bg_col - _mark_rows_dirty(y, h) + # 关键区②:发布窗口 —— active/z_order/焦点一次性生效。渲染线程只在 active==1 之后 + # 才会读这个槽,所以上面的字段填充必须先完成;且 _next_z 与 _bring_to_front 共用, + # 必须原子自增,否则两个窗口会拿到相同 z_order ⇒ 排序退化成槽位下标顺序。 + asm.cli() + _next_z += 1 + aw.z_order = _next_z + aw.active = 1 _focused_win = idx + asm.sti() + _mark_rows_dirty(y, h) return idx def draw_text(win_id: int, x: int, y: int, text: const str, color: uint32) -> int: