Files
DeepSeek V4.1 Flash b60debc4ee 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 保护仍保留为防御性正确做法。
2026-10-28 12:34:33 +08:00

56 lines
2.1 KiB
C++
Raw Permalink 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.
#ifndef __UI_SURFACE_HPP__
#define __UI_SURFACE_HPP__
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <cstdint>
#include "Theme.hpp"
namespace jdi
{
/**
* @brief 一块可绘制表面(DC + 矩形 + 可选像素直写视图)。
*
* 这是"整个应用共享一张后台位图"这一设想的落地载体:宿主创建一次,
* 之后把"我负责哪一块"以 `UiSurface::SubView()` 的形式发给子元素。
*
* ## 为什么需要 `version`
*
* 位图会在 `WM_SIZE` / `WM_DPICHANGED` 时被销毁重建。任何缓存下来的 `bits`
* **立刻变成野指针**,因此像素指针必须与"位图代次"绑定:版本不一致就作废重取。
* ⚠️ **绝不缓存裸 `bits` 跨帧使用**(本项目历史上因缓存裸指针踩过悬垂访问)。
*
* ## 什么时候才需要 `bits`
*
* `dc + rect` 已经足够做 GDI/GDI+ 绘制(往子矩形里画本来就是 O(1))。
* `bits` 只在**CPU 侧直接改像素**(memcpy / alpha 混合 / 重采样)时才有意义;
* 用它来"画控件"是净亏损(会破坏 ClearType、引入 stride/翻转/写序错误)。
*/
struct UiSurface
{
HDC dc = nullptr; /**< 目标 DC(GDI / GDI+ 都画这个)。 */
gd::RectF rect; /**< 该表面在宿主坐标系中的矩形(设备像素)。 */
int stride = 0; /**< 行字节数(**不可**假设等于 width × 4)。 */
std::uint32_t* bits = nullptr; /**< `rect` 左上角那一像素(不是位图首地址)。 */
std::uint64_t version = 0; /**< 位图代次;失配 ⇒ 该表面作废。 */
float scale = 1.0f; /**< DPI 缩放系数(逻辑像素 → 设备像素)。 */
/** @brief 表面是否可用。 */
bool Valid() const { return dc != nullptr && rect.Width > 0.0f && rect.Height > 0.0f; }
/**
* @brief 派生子视图(相对本表面左上角偏移)。
*
* `bits` 会按 `stride` 正确换算到子矩形左上角;子矩形会被夹在本表面之内。
*/
UiSurface SubView(const gd::RectF& localRect) const;
};
} // namespace jdi
#endif // __UI_SURFACE_HPP__