ReactOS 图形系统分析(25):多显示器/平移显示与杂项 --- multidisp.c / pandisp.c / engmisc.c
1. 概述
《分析_4》第 45~46 行把 multidisp.c/pandisp.c(多显示器/平移显示)与 engmisc.c(杂项)并列。三者实为三种角色:
| 文件 | 角色 | 现状 |
|---|---|---|
| multidisp.c(file:///d:/reactos/win32ss/gdi/eng/multidisp.c) | 多显示器(META)显示驱动入口 MultiEnableDriver |
UNIMPLEMENTED 桩(仅声明,恒 FALSE) |
| pandisp.c(file:///d:/reactos/win32ss/gdi/eng/pandisp.c) | 平移显示(Panning Display)驱动:桌面大于屏幕时,用"视口"漫游大桌面 | 完整实现(gPanDispDrvFn 13 个 DDI) |
| engmisc.c(file:///d:/reactos/win32ss/gdi/eng/engmisc.c) | 引擎杂项服务:设备表面"进入/离开"、系统查询、代码页、tick | 全部实现 |
平移显示(pandisp.c)是核心 :它包装一个真实显示驱动(underlying PDEV),对外提供更大的虚拟桌面 ,内部把绘制重定向到"影子表面(shadow)",再按需把**视口(viewport)**区域同步到物理屏幕。用户移动鼠标到屏幕边缘时视口随之滚动------这就是 DEVMODE.dmPanningWidth/dmPanningHeight 的语义。
DEVMODE: dmPelsWidth×dmPelsHeight(虚拟桌面) > dmPanningWidth×dmPanningHeight(物理屏幕)
┌─────────────────────────── 虚拟桌面 (szlDesktop) ───────────────────────────┐
│ │
│ ┌─────────────── 视口 rclViewport(物理屏幕大小) ───────────────┐ │
│ │ 光标移到边缘 → 视口滚动 (PanMovePointer) │ │
│ │ 绘制 → 影子表面 psoShadow → PanSynchronize 同步视口区域到屏幕 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────────┘
2. 加载机制(pdevobj.c 的 vtable 替换)
PDEVOBJ_pLoadPDEV/pEnableDisplay(file:///d:/reactos/win32ss/gdi/eng/pdevobj.c#L511-L575) 决定何时启用平移:
c
/* 1. META 设备 → 多显示器驱动(桩) */
if (ldevtype == LDEV_DEVICE_META)
pldev = LDEVOBJ_pLoadInternal(MultiEnableDriver, ldevtype);
/* 2. 显示设备且请求了平移尺寸 → 用平移 vtable 覆盖驱动函数表 */
if (ldevtype == LDEV_DEVICE_DISPLAY && (dwAccelerationLevel >= 5 ||
pdm->dmFields & (DM_PANNINGWIDTH | DM_PANNINGHEIGHT)))
{
/* 缺省填平:dmPanning* = dmPels* */
if (!(pdm->dmFields & DM_PANNINGWIDTH)) pdm->dmPanningWidth = pdm->dmPelsWidth;
if (!(pdm->dmFields & DM_PANNINGHEIGHT)) pdm->dmPanningHeight = pdm->dmPelsHeight;
/* 用 gPanDispDrvFn 覆盖 ppdev->apfn 对应项 */
for (i = 0; i < gPanDispDrvCount; i++)
ppdev->apfn[gPanDispDrvFn[i].iFunc] = gPanDispDrvFn[i].pfn;
}
else
ppdev->pfn = ppdev->pldev->pfn; /* 普通驱动函数表 */
关键: 平移驱动不是独立驱动模块 ,而是同一 PDEV 上对底层驱动函数表的包装层 (vtable 替换),无需加载新 DLL。PANDEV 保存了底层驱动的完整 apfn[INDEX_LAST] 表(用 GETPFN(name) 宏回调底层)。
3. PANDEV 结构与生命周期
3.1 PANDEV(pandisp.c(file:///d:/reactos/win32ss/gdi/eng/pandisp.c#L19-L36))
c
typedef struct _PANDEV
{
ULONG iBitmapFormat; // 底层表面格式
HDEV hdev;
SIZEL szlDesktop; // 虚拟桌面大小(dmPelsW×H)
RECTL rclViewport; // 视口(物理屏幕大小的漫游窗口)
HSURF hsurf; // 设备表面(对引擎暴露,桌面大小)
HSURF hsurfShadow; // 影子表面(实际绘制目标,内存位图)
SURFOBJ *psoShadow;
/* 底层 PDEV */
ULONG flUnderlyingGraphicsCaps;
SIZEL szlScreen; // 物理屏幕大小(dmPanningW×H)
DHPDEV dhpdevScreen; // 底层 PDEV 句柄
BOOL enabledScreen; // 底层表面是否已启用
SURFOBJ *surfObjScreen; // 底层屏幕表面
PFN apfn[INDEX_LAST]; // 底层驱动函数表(包装回调用)
} PANDEV, *PPANDEV;
3.2 生命周期 DDI
| 函数 | 行为 |
|---|---|
PanEnablePDEV |
用 dmPanningW×H 的 DEVMODE 启用底层 PDEV (GETPFN(EnablePDEV)),保存底层函数表;把上报给 GDI 的分辨率升级为虚拟桌面 dmPelsW×H;置 GCAPS_PANNING + `GCAPS2_SYNCFLUSH |
PanCompletePDEV / PanDisablePDEV |
透传底层 CompletePDEV/DisablePDEV,释放 PANDEV |
PanEnableSurface |
① 启用底层表面并锁定(surfObjScreen)② EngCreateDeviceSurface 建桌面大小设备表面 ③ EngAssociateSurface 挂钩 HOOK_BITBLT/COPYBITS/ALPHABLEND/GRADIENTFILL/STROKEPATH/SYNCHRONIZE/TEXTOUT ④ EngCreateBitmap 建影子表面(桌面大小,BMF_TOPDOWN);视口初始居中 |
PanDisableSurface |
逆序清理(可被 PanEnableSurface 的失败路径调用,故各步判空) |
设计: 对 GDI/引擎而言,设备表面是桌面大小 ;而真正可见的物理屏幕只是其中 rclViewport 一块。引擎的绘制都发生在影子表面(引擎可直接读写内存位图),再由同步机制拷到物理屏。
4. 绘制重定向与同步
4.1 重定向到影子表面
c
PanBitBlt(psoTrg, psoSrc, ...)
{
/* 设备表面 → 影子表面(源/目标都换) */
if (psoTrg->iType == STYPE_DEVICE) psoTrg = pandev->psoShadow;
if (psoSrc && psoSrc->iType == STYPE_DEVICE) psoSrc = pandev->psoShadow;
res = EngBitBlt(psoTrg, psoSrc, ...); // 引擎软件绘制
if (res) PanSynchronize(gPan, NULL); // 绘制完立刻同步(见下)
return res;
}
同类包装:PanCopyBits(→PanBitBlt,ROP4_SRCCOPY)、PanAlphaBlend(→EngAlphaBlend 到影子)、PanGradientFill(→EngGradientFill 到影子)。PanStrokePath/PanTextOut 未实现(UNIMPLEMENTED + ASSERT(FALSE))。
4.2 同步 --- PanSynchronize
c
PanSynchronize(dhpdev, prcl)
{
/* FIXME: 当前整块拷贝影子缓冲,未按 prcl 局部优化 */
ptlSrc = rclViewport.left/top; // 视口左上角
rclDest = 0,0,szlScreen.cx,szlScreen.cy; // 整块物理屏幕
EngCopyBits(surfObjScreen, psoShadow, NULL, NULL, &rclDest, &ptlSrc);
}
把影子表面中视口区域 拷贝到物理屏幕表面(EngCopyBits)。注册为 INDEX_DrvSynchronize,同时 GCAPS2_SYNCFLUSH 让引擎在绘制后调用;但源码 FIXME 明确:当前靠各绘制函数手动调用 (GCAPS2_SYNCTIMER 定时同步尚未实现,用全局 gPan 兜底)。
5. 视口漫游 --- PanMovePointer
光标移到屏幕边缘时移动视口(GCAPS_PANNING 语义):
c
PanMovePointer(pso, x, y, prcl)
{
/* 忽略隐藏/平移通知(无 DrvSetPointerShape,ASSERT x>=0 && y<0) */
if (x < 0 || y >= 0) return;
if (底层支持 GCAPS_PANNING) 底层 MovePointer(...); // 透传底层光标
y += pso->sizlBitmap.cy; // 光标 y 相对屏幕底部换算
/* 视口跟随光标:越左/右/上/下边缘则滚动 */
if (x < rclViewport.left) { rclViewport.left = x; right = x + szlScreen.cx; bChanged=TRUE; }
else if (x >= rclViewport.right) { right = x+1; left = x - szlScreen.cx; bChanged=TRUE; }
/* y 同理 */
if (bChanged) PanSynchronize(dhpdev, NULL); // 滚动后立即同步
}
6. 多显示器 --- multidisp.c(桩)
MultiEnableDriver 是 LDEV_DEVICE_META(多显示器设备)的驱动入口 ------Windows 中由它聚合多个 PDEV 成统一桌面(与 MDEVOBJ 多显示器管理配套)。ReactOS 当前为 UNIMPLEMENTED 桩(恒返回 FALSE),即 META 设备路径未启用 ,多显示器目前只靠 mdevobj.c 的 MDEV 映射对象做基础支持。
7. 杂项 --- engmisc.c
7.1 IntEngEnter / IntEngLeave --- 设备表面的"进入/离开"(核心工具)
问题: 引擎软件绘制(BitBlt/AlphaBlend/StretchBlt/LineTo/TransBlt/Gradient)需要能直接读写像素 (pvScan0/lDelta)的位图表面;而设备管理表面(无像素缓冲)无法直接操作。
IntEngEnter 解决 :把设备表面换成"临时内存位图"再操作,结束后 IntEngLeave 用 DrvCopyBits 拷回:
c
IntEngEnter(&el, psoDest, &DestRect, ReadOnly, &Translate, &psoOutput)
{
if (设备表面 && 无 pvScan0)
{
if (!HOOK_COPYBITS) return FALSE; // 驱动不支持 CopyBits 则失败
psoOutput = EngCreateBitmap(...); // 临时位图(目标区域大小)
GDIDEVFUNCS(psoDest).CopyBits(psoOutput, psoDest, ...); // 先把设备内容拷入
Translate = -DestRect.left/top; // 坐标系平移到临时位图
}
else
psoOutput = psoDest; // 位图表面直接用
/* 若挂钩 HOOK_SYNCHRONIZE → 调用 SynchronizeSurface/Synchronize */
}
IntEngLeave(&el)
{
if (OutputObj != DestObj) // 用了临时位图
{
if (!ReadOnly)
GDIDEVFUNCS(DestObj).CopyBits(DestObj, OutputObj, ...); // 结果拷回设备表面
释放临时位图 + TrivialClipObj;
}
}
调用方(36 处): bitblt.c(file:///d:/reactos/win32ss/gdi/eng/bitblt.c#L965)、alphablend.c(file:///d:/reactos/win32ss/gdi/eng/alphablend.c#L103)、stretchblt.c(file:///d:/reactos/win32ss/gdi/eng/stretchblt.c#L205)、lineto.c(file:///d:/reactos/win32ss/gdi/eng/lineto.c#L553)、transblt.c(file:///d:/reactos/win32ss/gdi/eng/transblt.c#L40)、gradient.c(file:///d:/reactos/win32ss/gdi/eng/gradient.c#L85)------这正是"引擎软件渲染能作用于设备表面"的通用桥梁(pandisp 的 PanBitBlt 回调 EngBitBlt 时即受益)。
7.2 其他杂项函数
| 函数 | 行为 |
|---|---|
EngGetProcessHandle |
恒返回 NULL(Win2K 起废弃,ABI 保留) |
EngGetCurrentCodePage |
转发 RtlGetDefaultCodePage 返回 OEM/ANSI 代码页(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h) DDI 服务) |
EngQuerySystemAttribute |
查询系统属性:EngNumberOfProcessors(NtQuerySystemInformation Basic)→ CPU 数;EngProcessorFeature(Processor 信息)→ 特性位 |
EngGetTickCount |
KeQueryTickCount + KiTickCountToMs 返回毫秒 tick |
8. 与《分析_4》标注对照
| 标注 | 核对 |
|---|---|
| 《_4》第 45 行:multidisp.c/pandisp.c =「多显示器/平移显示」 | 基本准确。补充:multidisp.c 是 META 设备入口(桩);真正实现是 pandisp.c(平移显示 vtable 包装层);多显示器聚合另见 mdevobj.c(MDEVOBJ) |
| 《_4》第 46 行:engmisc.c =「杂项」 | 准确。核心是 IntEngEnter/IntEngLeave(设备表面 ↔ 临时位图的 CopyBits 桥梁),另有系统查询/代码页/tick |
9. 源码索引
| 文件 | 关键内容 |
|---|---|
| pandisp.c(file:///d:/reactos/win32ss/gdi/eng/pandisp.c) | PANDEV、PanEnablePDEV/EnableSurface/DisableSurface、PanBitBlt/PanCopyBits/PanAlphaBlend/PanGradientFill、PanSynchronize、PanMovePointer、gPanDispDrvFn(13 项) |
| multidisp.c(file:///d:/reactos/win32ss/gdi/eng/multidisp.c) | MultiEnableDriver(LDEV_DEVICE_META 入口,桩) |
| engmisc.c(file:///d:/reactos/win32ss/gdi/eng/engmisc.c) | IntEngEnter/IntEngLeave、EngGetProcessHandle、EngGetCurrentCodePage、EngQuerySystemAttribute、EngGetTickCount |
| pdevobj.c(file:///d:/reactos/win32ss/gdi/eng/pdevobj.c#L511-L575) | 平移 vtable 替换逻辑、META 驱动加载 |
| mdevobj.c(file:///d:/reactos/win32ss/gdi/eng/mdevobj.c) / mdevobj.h | MDEVOBJ 多显示器映射对象 |
| ntgdi/misc.h(file:///d:/reactos/win32ss/gdi/ntgdi/misc.h#L14-L21) | IntEngEnter/IntEngLeave 声明 |
关联模块:
- 底层回调:
EngBitBlt/EngAlphaBlend/EngGradientFill/EngCopyBits(《分析_4》3.1) - 加载入口:
LDEVOBJ_pLoadInternal(《分析_4》3.4) - 设备对象:PDEVOBJ(ppdev->apfn 被替换)
- 光标联动:
PanMovePointer与《分析_24》鼠标管理的交互(FIXME 提及 MouseSafety 误调)
10. 各函数作用和使用
10.1 pandisp.c --- 平移显示驱动(DDI 函数表 gPanDispDrvFn)
| 函数 | 作用 | 使用方式/要点 |
|---|---|---|
PanEnablePDEV |
启用平移设备:以 dmPanningW×H 启用底层 PDEV,上报桌面尺寸 dmPelsW×H |
由引擎经 PDEVOBJ_bEnablePDEV 调用;内部回调底层 EnablePDEV 并保存其 apfn 表;置 GCAPS_PANNING/`GCAPS2_SYNCFLUSH |
PanCompletePDEV |
设备就绪通知 | 透传底层 CompletePDEV;保存 hdev |
PanDisablePDEV |
卸载设备 | 回调底层 DisablePDEV 后 EngFreeMem 释放 PANDEV |
PanEnableSurface |
启用表面:建设备表面(桌面尺寸)+ 影子表面 + 挂钩 HOOK_* | 引擎在启用显示后调用;失败路径会自行调用 PanDisableSurface 清理(故后者须容错未初始化状态) |
PanDisableSurface |
逆序清理所有表面/影子/底层表面 | 各步判空,可被 PanEnableSurface 失败路径复用 |
PanSynchronize |
把影子表面视口区域 EngCopyBits 到物理屏幕 |
注册为 DrvSynchronize;当前由各绘制函数手动调用(FIXME:应定时同步) |
PanBitBlt |
位块传输:设备表面→影子表面,绘制后同步 | 引擎绘制 DDI 回调;源/目标若是设备表面则替换为影子表面 ,再调 EngBitBlt,成功后 PanSynchronize |
PanCopyBits |
表面拷贝 | 简化为 PanBitBlt(..., ROP4_SRCCOPY)(DrvCopyBits 回调) |
PanStrokePath |
路径描边 | UNIMPLEMENTED(ASSERT(FALSE),当前不可用) |
PanTextOut |
文本输出 | UNIMPLEMENTED(ASSERT(FALSE),当前不可用) |
PanMovePointer |
光标移动 + 视口漫游 | DrvMovePointer 回调;x<0(隐藏)或 y>=0(平移通知)直接忽略;光标触边则滚动 rclViewport 并 PanSynchronize;底层支持 GCAPS_PANNING 时先透传光标 |
PanAlphaBlend |
Alpha 混合到影子表面后同步 | 引擎回调;调 EngAlphaBlend(psoShadow, ...) 后 PanSynchronize |
PanGradientFill |
渐变填充到影子表面后同步 | 引擎回调;调 EngGradientFill(psoShadow, ...) 后 PanSynchronize |
共用说明: GETPFN(name) 宏(pandev->apfn[INDEX_Drv##name])用于回调底层驱动同名 DDI------所有 Pan* 函数都是"底层驱动函数的包装层"。
10.2 multidisp.c --- 多显示器驱动入口
| 函数 | 作用 | 使用方式/要点 |
|---|---|---|
MultiEnableDriver |
多显示器(META 设备)驱动加载入口 | UNIMPLEMENTED 桩,恒返回 FALSE 。仅当 LDEV_DEVICE_META 时由 LDEVOBJ_pLoadInternal 引用;实现前 META 设备路径不可用 |
10.3 engmisc.c --- 引擎杂项
| 函数 | 作用 | 使用方式/要点 |
|---|---|---|
IntEngEnter |
设备表面"进入":把不可直接读写的设备表面换成临时内存位图,供引擎软件绘制 | 内部函数(misc.h(file:///d:/reactos/win32ss/gdi/ntgdi/misc.h#L14-L21) 声明);bitblt/alphablend/stretchblt/lineto/transblt/gradient 等绘制前成对调用。前置条件: 设备表面须挂钩 HOOK_COPYBITS,否则返回 FALSE;输出 Translate 用于坐标平移;成功后若挂钩 HOOK_SYNCHRONIZE 会先同步 |
IntEngLeave |
设备表面"离开" :把临时位图结果经 DrvCopyBits 拷回设备表面并释放临时资源 |
必须与 IntEngEnter 严格成对 (ReadOnly 为 TRUE 时不做回拷);漏调会泄漏位图/裁剪对象 |
EngGetProcessHandle |
获取进程句柄 | 恒返回 NULL(Win2K 起废弃,驱动勿依赖) |
EngGetCurrentCodePage |
查询 OEM/ANSI 代码页 | DDI 服务(win32k 导出);转发 RtlGetDefaultCodePage |
EngQuerySystemAttribute |
查询系统属性 | 驱动用 CapNum=EngNumberOfProcessors 取 CPU 数、EngProcessorFeature 取处理器特性位;其余枚举返回 FALSE |
EngGetTickCount |
获取系统 tick(毫秒) | KeQueryTickCount + KiTickCountToMs;驱动计时用 |
本文档基于 ReactOS 源代码
win32ss/gdi/eng/multidisp.c、pandisp.c、engmisc.c及关联模块分析(2026 年 8 月)