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

+41
View File
@@ -0,0 +1,41 @@
#include "UiSurface.hpp"
#include <cmath>
#include "UiLayout.hpp"
namespace jdi
{
UiSurface UiSurface::SubView(const gd::RectF& localRect) const
{
UiSurface view;
if (!Valid())
{
return view;
}
// 子矩形先换算到宿主坐标,再夹在本表面之内:超出本表面的部分没有属于它的像素,
// 若照原样返回 `bits` 偏移会写到相邻区域(典型的"子控件涂到别人身上")。
const gd::RectF requested(rect.X + localRect.X, rect.Y + localRect.Y, localRect.Width,
localRect.Height);
gd::RectF clamped;
if (!UiRectIntersect(rect, requested, clamped))
{
return UiSurface();
}
view.dc = dc;
view.rect = clamped;
view.stride = stride;
view.version = version;
view.scale = scale;
if (bits != nullptr && stride > 0)
{
const int offsetX = static_cast<int>(std::lround(clamped.X - rect.X));
const int offsetY = static_cast<int>(std::lround(clamped.Y - rect.Y));
view.bits = bits + static_cast<std::ptrdiff_t>(offsetY) * (stride / 4) + offsetX;
}
return view;
}
} // namespace jdi