UI 重构 P5(1):共享表面 UiSurface + 后备缓冲改 top-down 32bpp DIB;修复退出期绘制崩溃

一、共享表面(为「整个应用共享一张后台位图」落地基础设施)
- 新增 Components/Shared/{Inc,Src}/UiSurface.*:{ dc, rect, stride, bits, version, scale }。
  SubView() 把子矩形换算到宿主坐标、夹取在本表面内、按 stride 重算 bits 偏移、传递 version。
  ⚠️ 注释里写明:bits 只在 CPU 侧合成时使用;画控件只需 dc + rect(用 bits 画字会破坏 ClearType)。
- Application 增 BackBufferSurface();后备缓冲由 CreateCompatibleBitmap 改为
  **top-down 32bpp CreateDIBSection**(biHeight 取负,y 向下递增),并记录 bits/stride/version
  (重建位图时 ++version,旧句柄因此可判定作废)。

二、修复「退出时访问违例」(真实缺陷)
- Application::Shutdown() 中 **DestroyWindow 必须在 GdiplusShutdown 之前**:前一帧的 InvalidateRect
  可能留下未处理更新区域,DestroyWindow 期间派发 WM_PAINT ⇒ Render() ⇒ 在已关闭的 GDI+ 上构造
  Graphics ⇒ 崩溃。
- Render() 开头加保护:!m_gdiplusReady 或 m_hwnd 为空时直接返回。

验证:
- 像素:8 场景(A 全览 / B 复杂浮层 / C 平移 / D 悬停 / E 按下 / F 松开 / G 悬停主按钮 / H 移出)
  哈希与 DIB 改造前**逐一相同**(A 2166933…、B C444ECF…、C 7984446…、D BF85587…、
  E 1C663E4…、F 0535292…、G 7A58172…、H 23F390D…)。
- 功能:locate-cursor-button (1291,73) / click-cursor-changes-mode (1->2) /
  release-outside-does-not-fire 全部 PASS。
- uicheck 69 passed / 0 failed(新增 7 条 UiSurface 断言:bits 按 stride 偏移、矩形夹取、
  完全在外时返回无效表面、version/scale 传递)。
- make release / make debug 零警告;Target.exe 883,200 B(< 900KB)。

附注:探针此前的退出崩溃其实来自「探针自己又调了一次 GdiplusShutdown」(Application::Shutdown
内部已 shutdown),与本轮修复无关;本轮的顺序调整与 Render 保护仍保留为防御性正确做法。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-28 12:34:33 +08:00
1 parent 9a9802e589
commit b60debc4ee
4 files changed
+166 -12

No files matched your search

+17 -4
View File
@@ -17,6 +17,7 @@
#include "TabBar.hpp"
#include "Theme.hpp"
#include "Toolbar.hpp"
#include "UiSurface.hpp"
namespace jdi
{
@@ -130,12 +131,21 @@ namespace jdi
* @param [IN] height 客户区高度。
*/
void EnsureBackBuffer(int width, int height);
/**
* @brief 释放后备缓冲区。
*/
void ReleaseBackBuffer();
/**
* @brief 取"整个应用共享的那张后台位图"作为一段可绘制表面。
*
* 这是 P5 的基础设施:后备缓冲是 **top-down 32bpp DIB 段**,因此除了 `dc` 之外
* 还能给出 `bits`(像素直写)与 `version`(位图代次;`WM_SIZE` / `WM_DPICHANGED`
* 重建后旧句柄必须作废)。
* ⚠️ 绝大多数绘制只需要 `dc + rect`;`bits` 仅用于 CPU 侧合成,**不要**用它画控件。
*/
UiSurface BackBufferSurface() const;
/**
* @brief 完整重绘客户区并呈现到窗口。
*/
@@ -428,10 +438,13 @@ namespace jdi
int m_splitterGrabOffset = 0; /**< 分隔条拖动的抓取偏移。 */
HDC m_backDc = nullptr; /**< 后备缓冲设备上下文。 */
HBITMAP m_backBitmap = nullptr; /**< 后备缓冲位图。 */
HBITMAP m_backBitmap = nullptr; /**< 后备缓冲位图(top-down 32bpp DIB 段)。 */
HGDIOBJ m_backOldBitmap = nullptr; /**< 后备缓冲原始位图。 */
int m_backWidth = 0; /**< 后备缓冲宽度。 */
int m_backHeight = 0; /**< 后备缓冲高度。 */
int m_backWidth = 0; /**< 后备缓冲宽度(设备像素)。 */
int m_backHeight = 0; /**< 后备缓冲高度(设备像素)。 */
std::uint32_t* m_backBits = nullptr; /**< 后备缓冲像素首地址(仅 CPU 侧合成时使用)。 */
int m_backStride = 0; /**< 后备缓冲行字节数(**不可**假设 = 宽 × 4)。 */
std::uint64_t m_backVersion = 0; /**< 后备缓冲代次(重建位图时递增,用于作废旧句柄)。 */
Target m_activeTarget = Target::NONE; /**< 当前鼠标作用目标。 */
bool m_trackingLeave = false; /**< 是否已注册鼠标离开通知。 */