ReactOS 图形系统分析(22):数学与浮点 --- math.c / float.c
1. 概述
math.c / float.c 位于 win32ss/gdi/eng/,是引擎的两个小型基础模块:
| 文件 | 内容 | 本质 |
|---|---|---|
| math.c(file:///d:/reactos/win32ss/gdi/eng/math.c) | 仅一个函数 EngMulDiv |
定点整数乘除(64 位中间量 + 舍入 + 溢出钳制) |
| float.c(file:///d:/reactos/win32ss/gdi/eng/float.c) | EngSaveFloatingPointState / EngRestoreFloatingPointState |
FPU 状态保存/恢复 DDI(供驱动在内核态使用浮点) |
补充说明: 《分析_4》3.7 节把 float.c/math.c 描述为「FLOATOBJ 浮点坐标计算」。实际上 FLOATOBJ(浮点模拟对象)的实现不在 float.c,而在:
- floatobj.h(file:///d:/reactos/win32ss/gdi/eng/floatobj.h)------内联宏/常量(
FLOATOBJ_bConvertToLong等,x86 用 EFLOAT 编码); - i386/floatobj.S(file:///d:/reactos/win32ss/gdi/eng/i386/floatobj.S) + i386/floatobj.c(file:///d:/reactos/win32ss/gdi/eng/i386/floatobj.c)------x86 汇编实现的 FLOATOBJ_* 函数族。
三者共同构成引擎的"无 FPU 浮点支持"体系:
┌─────────────────────────────────────────────────────────────┐
│ 整数数学 FPU 状态管理 浮点模拟 │
│ math.c: EngMulDiv float.c: Save/Restore FLOATOBJ │
│ (64 位乘 + 舍入除) (驱动用浮点的前提) (EFLOAT 编码) │
│ floatobj.h(内联) │
│ i386/floatobj.S │
├─────────────────────────────────────────────────────────────┤
│ 消费者 │
│ ntuser(壁纸/滚动条) ntgdi(path/coord/xformobj/freetype) │
│ eng(bitblt/clip 等) XFORMOBJ/变换矩阵 │
└─────────────────────────────────────────────────────────────┘
1.1 为什么需要这些内容(设计动机)
这三个模块的存在,本质上是**"内核态 GDI 不信任/不依赖浮点环境"这一架构约束的直接产物**:
| 内容 | 要解决的根本问题 | 为什么必须由引擎提供 |
|---|---|---|
EngMulDiv |
GDI 大量"按比例缩放"运算(a×b/c)若用 32 位乘法会溢出 ,直接除法会截断产生偏差 |
需要统一的 64 位中间量 + 确定性舍入 + 越界钳制 语义,且必须与 gdi32 用户态 MulDiv、Windows 内核行为完全一致(否则同一逻辑在用户态/内核态算出不同结果);同时它是 DDI 契约的一部分------驱动也要用它做缩放 |
EngSave/RestoreFloatingPointState |
x86 内核不允许驱动随便用 FPU :Windows 对 FPU 采用"惰性保存(lazy save)",驱动直接执行浮点指令会污染其他线程/自身的 FPU 上下文,造成随机、难复现的错误 | DDI 契约规定:驱动在 DrvTextOut 等需要浮点的回调中必须先保存、后恢复 FPU 状态;引擎提供统一入口,并附带双保存/双恢复的合法性校验 (防御写坏驱动)与大小探测模式(驱动先问需要多大非分页缓冲) |
FLOATOBJ 浮点模拟 |
变换矩阵(XFORMOBJ)、字体度量(IFIMETRICS)等 DDI 数据结构需要"浮点",但不能真用 FPU(存在无 FPU 的 x86 处理器;内核切换 FPU 上下文代价高;驱动自算浮点无法保证一致性) | 用尾数+指数的整数对(EFLOAT)模拟单精度 ,使浮点运算变成纯整数运算------确定、可逆、可比较、与 FPU 无关 ;且 FLOATOBJ 结构布局固定(2×ULONG),保证第三方驱动的二进制兼容(非 x86 平台则退化为原生 FLOAT 宏,同一份驱动源码两平台都能编译) |
一句话总结:GDI 引擎既要给驱动提供"浮点能力",又不能依赖内核的浮点环境,于是用"整数定点运算(EngMulDiv)+ 受控的 FPU 状态管理(float.c)+ 纯整数浮点模拟(FLOATOBJ)"三层方案补齐------这正是本文件内容存在的全部理由。
2. EngMulDiv(math.c)
c
INT APIENTRY EngMulDiv(INT iMultiplicand, INT iMultiplier, INT iDivisor)
{
/* 1. 除数为 0 → 返回"无穷大"(符号由乘数决定) */
if (iDivisor == 0)
return ((iMultiplicand ^ iMultiplier) < 0) ? INT_MIN : INT_MAX;
/* 2. 除数归一为正,符号并入被乘数 */
if (iDivisor < 0) { iMultiplicand = -iMultiplicand; iDivisor = -iDivisor; }
/* 3. 64 位乘法避免 32 位溢出 */
i64Multiplied = Int32x32To64(iMultiplicand, iMultiplier);
/* 4. 舍入:加/减 divisor/2(按乘积符号),实现"四舍五入到最近整数" */
if (i64Multiplied >= 0) i64Multiplied += (iDivisor / 2);
else i64Multiplied -= (iDivisor / 2);
/* 5. 除法 + 溢出钳制到 INT 范围 */
i64Result = i64Multiplied / iDivisor;
if (i64Result > INT_MAX) return INT_MAX;
if (i64Result < INT_MIN) return INT_MIN;
return (INT)i64Result;
}
语义要点: result = round(multiplier × multiplicand / divisor),与 gdi32 的 MulDiv 一致(Int32x32To64 保精度、半除数舍入、越界钳制),是内核版 GDI 常用缩放助手。
树内使用场景(实证):
| 调用方 | 用途 |
|---|---|
| ntuser/desktop.c(file:///d:/reactos/win32ss/user/ntuser/desktop.c#L1923-L1924) | 壁纸按缩放比例计算(scaledWidth/Height = EngMulDiv(...)) |
| ntuser/scrollbar.c(file:///d:/reactos/win32ss/user/ntuser/scrollbar.c#L222-L228) | 滚动条滑块尺寸/位置(page/(posMax-posMin+1) 比例) |
| ntgdi/path.c(file:///d:/reactos/win32ss/gdi/ntgdi/path.c#L721-L731) | 圆角矩形/椭圆贝塞尔控制点偏移(w * 0.5522847 之类的整数近似) |
| ntgdi/coord.c(file:///d:/reactos/win32ss/gdi/ntgdi/coord.c#L872-L887) | MM_ISOTROPIC/MM_ANISOTROPIC 的 WindowExt(1000/10000/14400 每英寸映射) |
| floatobj.h(file:///d:/reactos/win32ss/gdi/eng/floatobj.h#L46) | FLOATOBJ_bConvertToLong 用 EngMulDiv 把 EFLOAT 转 LONG |
3. 浮点状态保存/恢复(float.c)
Windows 内核态不允许驱动直接使用 FPU (x86 内核无浮点环境保证)。驱动在 DrvTextOut 等需要浮点的 DDI 回调中,必须先 EngSaveFloatingPointState、用完再 EngRestoreFloatingPointState。ReactOS 用内核浮点保存(KFLOATING_SAVE)+ 状态标志实现:
c
typedef struct _WIN32K_FLOATING_SAVE
{
KFLOATING_SAVE FloatState; // 内核 FPU/MMX 状态块
BOOLEAN IsFloatingPointSaved; // 防双保存/双恢复
} WIN32K_FLOATING_SAVE;
3.1 EngSaveFloatingPointState(双模式)
c
if (pBuffer == NULL || cjBufferSize == 0)
{
/* 查询模式:实际保存一次以探测所需大小(临时缓冲) */
KeSaveFloatingPointState(&TempBuffer);
KeRestoreFloatingPointState(&TempBuffer);
return sizeof(WIN32K_FLOATING_SAVE); // 返回所需字节数
}
if (cjBufferSize < sizeof(WIN32K_FLOATING_SAVE)) return 0;
/* 保存模式:MSDN 要求缓冲区"零初始化且必须是非分页内存" */
KeSaveFloatingPointState(&State->FloatState);
State->IsFloatingPointSaved = TRUE;
return TRUE;
3.2 EngRestoreFloatingPointState
c
if (!State->IsFloatingPointSaved) // 未保存就恢复 → 驱动行为错误
{
DPRINT1("The driver has attempted to restore floating point state after already restoring it.\n");
DPRINT1("This (probably ICafe AMD) driver has done an incorrect behavior.\n");
return FALSE;
}
State->IsFloatingPointSaved = FALSE;
KeRestoreFloatingPointState(&State->FloatState);
return TRUE;
细节:
IsFloatingPointSaved标志用于检测驱动重复保存/重复恢复的错误用法(注释点名 ICafe AMD 驱动有过此错误);- 查询模式通过"真实保存一次再恢复"探测 KFLOATING_SAVE 大小,保证与目标平台一致;
- 缓冲区要求非分页内存且零初始化(KeSaveFloatingPointState 要求)。
4. FLOATOBJ 浮点模拟(floatobj.h + i386/floatobj.S)
4.1 EFLOAT 编码(为什么不用 FPU)
x86 内核态不能安全使用 FPU,故 win32k 用整数对模拟单精度浮点:
struct _EFLOAT { LONG lMant; LONG lExp; }; // floatobj.h 中 FLOATOBJ = 2×ULONG(x86)
f = (lMant / 0x40000000) * 2 ^ (lExp - 2)
= lMant * 2 ^ (lExp - 32)
FLOATOBJ_1 = {0x40000000, 2}(即 1.0)、FLOATOBJ_16 = {0x40000000, 6}(16.0)等预定义常量(floatobj.S(file:///d:/reactos/win32ss/gdi/eng/i386/floatobj.S#L84-L102))。FLOATOBJ_bConvertToLong正是用 EngMulDiv 完成定点到整数的转换(floatobj.h(file:///d:/reactos/win32ss/gdi/eng/floatobj.h#L46)),两个模块在此交汇。
4.2 实现分布
| 位置 | 内容 |
|---|---|
| floatobj.h(file:///d:/reactos/win32ss/gdi/eng/floatobj.h) | x86 内联:_FLOATOBJ_Equal、FLOATOBJ_bConvertToLong、FLOATOBJ_GetFix、FLOATOBJ_IsLong/Equal0/Equal1、常量宏;非 x86 退化为原生 FLOAT |
| i386/floatobj.S(file:///d:/reactos/win32ss/gdi/eng/i386/floatobj.S) | 汇编实现 FLOATOBJ_SetFloat/SetLong/GetFloat/Equal/GreaterThan/LessThan/Neg/Mul/Div/Add/Sub 及 *Float/*Long 包装(约 1100 行,含优化说明) |
| i386/floatobj.c(file:///d:/reactos/win32ss/gdi/eng/i386/floatobj.c) | FLOATOBJ_GetLong(C 实现,经 bConvertToLong) |
4.3 消费方(XFORMOBJ 变换矩阵为核心)
FLOATOBJ 是 DDI 的 FLOATOBJ_XFORM(6 个 FLOATOBJ 组成的 2×3 变换矩阵,winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L681-L688))的运算基础:
| 消费者 | 用途 |
|---|---|
| ntgdi/xformobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/xformobj.c#L36-L39) | XFORMOBJ 组合/求逆(FLOATOBJ_Mul/Add) |
| ntgdi/coord.h(file:///d:/reactos/win32ss/gdi/ntgdi/coord.h#L22-L38) | 世界→设备坐标变换点乘(FLOATOBJ_Mul/Add/bConvertToLong) |
| ntgdi/coord.c(file:///d:/reactos/win32ss/gdi/ntgdi/coord.c#L171) | 缩放矩阵归一化 |
| ntgdi/freetype.c(file:///d:/reactos/win32ss/gdi/ntgdi/freetype.c#L4541) | 字体变换矩阵(FLOATOBJ_SetFloat/Mul) |
| ntgdi/path.c(file:///d:/reactos/win32ss/gdi/ntgdi/path.c#L375-L389) | 路径点取整 |
4.4 导出(仅 x86)
win32k.spec(file:///d:/reactos/win32ss/win32k.spec#L144-L170) 中全部 FLOATOBJ_* 均带 -arch=i386(非 x86 用原生 FLOAT 宏,无需导出)。用户态对应实现见 gdi32/objects/efloat.c(file:///d:/reactos/win32ss/gdi/gdi32/objects/efloat.c)(EFtoF/FtoEF)。
5. 导出对照
| 函数 | win32k.spec | 用途 |
|---|---|---|
EngMulDiv |
L99 | 整数乘除缩放(驱动/内核通用) |
EngSaveFloatingPointState |
L117 | 驱动保存 FPU 状态 |
EngRestoreFloatingPointState |
L116 | 驱动恢复 FPU 状态 |
FLOATOBJ_*(约 27 个) |
L144-170,-arch=i386 |
XFORMOBJ 浮点模拟 |
6. 与《分析_4》标注对照
| 标注 | 核对 |
|---|---|
| 《_4》第 42 行:math.c/float.c =「数学/浮点运算」 | 大致准确。补充:math.c 仅 EngMulDiv;float.c 仅 FPU 状态保存/恢复;FLOATOBJ 浮点模拟实现在 floatobj.h + i386/floatobj.S,非 float.c |
7. 源码索引
| 文件 | 关键内容 |
|---|---|
| math.c(file:///d:/reactos/win32ss/gdi/eng/math.c) | EngMulDiv(64 位乘、舍入、钳制) |
| float.c(file:///d:/reactos/win32ss/gdi/eng/float.c) | EngSave/RestoreFloatingPointState(KFLOATING_SAVE 包装) |
| floatobj.h(file:///d:/reactos/win32ss/gdi/eng/floatobj.h) | FLOATOBJ 内联函数、EFLOAT 常量、非 x86 退化宏 |
| i386/floatobj.S(file:///d:/reactos/win32ss/gdi/eng/i386/floatobj.S) | FLOATOBJ_* 汇编实现(约 1100 行) |
| i386/floatobj.c(file:///d:/reactos/win32ss/gdi/eng/i386/floatobj.c) | FLOATOBJ_GetLong |
| gdi32/objects/efloat.c(file:///d:/reactos/win32ss/gdi/gdi32/objects/efloat.c) | 用户态 EFLOAT 转换 |
| winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L670-L691) | FLOATOBJ / FLOATOBJ_XFORM 定义(x86 vs 其他) |
| xformobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/xformobj.c) | XFORMOBJ 变换矩阵(FLOATOBJ 主要消费者) |
关联模块:
- 主要消费者:XFORMOBJ(变换)、coord(坐标映射)、path(椭圆/圆角)、freetype(字体变换)
- 内核浮点:
KeSaveFloatingPointState/KeRestoreFloatingPointState(ntoskrnl) - 相关导出:
XFORMOBJ_*(xformobj 服务,详见《分析_4》3.7)
本文档基于 ReactOS 源代码
win32ss/gdi/eng/math.c、float.c及关联模块分析(2026 年 8 月)