6.0 KiB
6.0 KiB
NewEditor Live Resize Jitter Root Cleanup Plan
Date: 2026-04-23 Status: Planned
1. Objective
彻底清理 new_editor 自定义顶栏窗口在实时拖拽 resize 时的抖动问题,并且继续保持当前已经恢复的“内容不变形”契约。
这次修复只接受下面这个结果:
- 右侧和下侧,尤其右下角,拖拽时不再出现明显抖动
Scene内容继续实时跟随,不允许用冻结、延迟、复用旧帧拉伸来掩盖问题- 自定义顶栏和 borderless resize 机制继续保留
- 代码结构保持主线化,不能靠零散补丁维持
2. Confirmed Root Cause
当前问题的根源不是 Scene 视口本身慢,也不是自定义顶栏设计有问题。
已经确认的根因是窗口层的 resize / present 时序错误:
EditorWindowChromeController::HandleResizePointerMove(...)在 borderless live resize 中先把目标尺寸写进 predicted client sizeEditorWindow::ResolveRenderClientPixelSize(...)会优先使用这个 predicted size- 于是这一步立即帧会按“未来尺寸”完成整窗布局、viewport 请求和 UI 合成
- 但是此时 HWND/client rect 还没有通过
SetWindowPos(...)真正确认到这个尺寸 - 这帧随后直接进入可见 swapchain,再由 DXGI
DXGI_SCALING_STRETCH填到仍处于旧几何的窗口表面
因此抖动的本质不是“重绘慢”,而是:
- 可见 swapchain 被拿来展示了一帧与当前 HWND 几何不一致的 future-size 内容
- DXGI stretch 误差在归一化坐标接近
1.0的位置最大,所以右侧和下侧最明显 Scene只是把这段错误状态维持得更久,因此把问题放大了
直接调整 Scene 视口 splitter 之所以平滑,是因为那条路径没有“future-size frame 先进入旧 HWND 可见 swapchain”这个错误。
3. Architectural Decision
根修复必须把两个尺寸彻底分开:
predicted size用于 borderless resize 期间的下一步布局推演和离屏整窗合成committed visible size只对应已经被 Win32 / DXGI 真正确认的 HWND client size 和可见 swapchain backbuffer
最终原则:
- 预测尺寸可以驱动布局和离屏合成
- 可见 swapchain 只能 present committed size 的内容
- HWND 几何提交前,future-size frame 不能再直接进入可见 swapchain
4. Red Lines
本次修复不能做下面这些事:
- 不能取消自定义顶栏
- 不能把 resize 责任下沉到 UI Editor 层
- 不能冻结
Scene - 不能复用旧纹理做拉伸/延迟展示来冒充实时 resize
- 不能在各处消息处理里堆条件分支补丁
- 不能破坏 tab 拖拽拆窗、utility window、正常
WM_SIZE、viewport splitter resize
5. Target End State
修完之后,窗口层行为必须变成下面这样:
- pointer move 到来时,先计算 predicted rect
- predicted rect 只驱动离屏整窗 composition target 的大小和内容
SetWindowPos(...)提交 HWND 几何- 真正收到 committed client size 后,再 resize 可见 swapchain
- 可见 swapchain 只展示 committed size backbuffer
- 如果 predicted size 和 committed size 一致,则直接把最近一次预测合成结果拷到 committed backbuffer,再 present
- 如果 committed size 变化继续推进,则按新的 predicted size 重新离屏合成,但仍不提前污染可见 swapchain
6. Implementation Plan
Phase A. Separate predicted composition from visible swapchain state
- 在
D3D12WindowSwapChainPresenter/D3D12WindowRenderer引入窗口级离屏 composition target - 为它建立独立的 color texture、RTV、SRV 和
RenderSurface - 把“当前可见 swapchain 尺寸”和“最近预测 composition 尺寸”分成两套状态
Phase B. Route immediate resize frames into the offscreen composition target
- 给窗口 render loop 增加 present target policy
- 正常 steady-state 帧仍然走 committed swapchain backbuffer
- borderless resize pointer-move immediate frame 改为绘制到 predicted composition target
- 这一步仍然执行真实
Scene渲染和整窗 UI 合成,不做冻结
Phase C. Commit HWND geometry first, then publish only committed-size content
- 清理
HandleResizePointerMove(...)的顺序,让未来尺寸帧不再直接 present 到旧 swapchain SetWindowPos(...)提交后,由实际 committed client size 驱动 swapchain resize- 只有 committed size backbuffer 才允许进入
Present() - 如果最近的 predicted composition 与 committed size 匹配,则把该离屏结果拷贝到 committed backbuffer 后 present
Phase D. Narrow WM_SIZE / prediction acknowledgement semantics
- 预测尺寸状态不再表达“这帧已经进入可见 swapchain”
- 改为表达“这份 predicted composition 已经生成,等待 committed publish”
WM_SIZE只消费 committed size,并决定是否复用最近一次 composition 结果- 保留已经正确收窄的 frame retirement / lifecycle 安全逻辑
Phase E. Clean up right/bottom asymmetry at the actual source
- 彻底移除“future-size frame displayed in old HWND”这件事
- 让 DXGI stretch 不再承担预测阶段的显示职责
- 右侧、下侧、右下角的额外抖动应当因此一起消失
7. Validation Requirements
完成前必须验证:
Scene激活时拖右侧、下侧、右下角,不再出现现在这种严重抖动Scene内容继续实时响应 resize,而不是冻结或延后Game、空视口、普通面板 resize 行为不回退- 直接拖
Scene视口 splitter 仍然保持原来的平滑度 - tab 拖出独立窗口、utility window、maximize/restore、DPI change 不回退
- 不再出现 resize 期间 future-size frame 被直接 present 到旧窗口的问题
8. Completion Criteria
只有同时满足下面几点,这个问题才算真正收拾干净:
- 不变形契约保留
Scene保持实时 resize- 右/下/右下 resize 抖动被从源头消除
- 可见 swapchain 与 HWND committed 几何重新建立一一对应
- 实现落在窗口平台层和渲染主线上,而不是 UI 层补丁