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:
1 parent
9a9802e589
commit
b60debc4ee
4 files changed
+166
-12
No files matched your search
@@ -0,0 +1,55 @@
|
||||
#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__
|
||||
@@ -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
|
||||
Reference in new issue
Block a user