ReactOS 图形系统分析(22):数学与浮点 — math.c / float.c

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_EqualFLOATOBJ_bConvertToLongFLOATOBJ_GetFixFLOATOBJ_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.cfloat.c 及关联模块分析(2026 年 8 月)

相关推荐
Nebula_g14 小时前
JavaSE基础语法:面向对象高级(代码中的成分)
java·开发语言·编程·javase·技术栈·高级语法
for_ever_love__14 小时前
python基础语法学习: 异常的传递性
开发语言·python·学习
依然鸣15 小时前
PTA团体程序设计天梯赛L1真题讲解L1-077-080
开发语言·c++·算法·深度优先·pat考试·图论
库玛西17 小时前
现代 C++ 智能指针全景指南:从 RAII 思想到工业级实践
c语言·开发语言·c++·笔记·面试
AC赳赳老秦17 小时前
风控岗应用:OpenClaw 采集公开司法与经营异常数据,自动生成企业风险评估报告
大数据·c语言·数据库·人工智能·python·php·openclaw
liulilittle17 小时前
无锁并发容器的设计与实现原理
开发语言·c++·set·map·并发·无锁·lock-free
朋克洛德的码农17 小时前
Go并发-sync包四剑客:Mutex、RWMutex、WaitGroup、Once-从入门到原理
开发语言·后端·golang
程序员与背包客_CoderZ18 小时前
Linux D-Bus通信协议详解:从入门到C/C++编码实战
linux·服务器·c语言·c++·嵌入式硬件·嵌入式软件·dbus
淼澄研学19 小时前
Kimi API黑产倒卖技术解析与Python合规接入指南
开发语言·网络·python
冻柠檬飞冰走茶19 小时前
PTA基础编程题目集 7-35有理数均值(C++语言实现)
开发语言·数据结构·c++·算法·均值算法