ReactOS 图形系统分析(25):多显示器/平移显示与杂项 — multidisp.c / pandisp.c / engmisc.c

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 启用底层 PDEVGETPFN(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(桩)

MultiEnableDriverLDEV_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 解决 :把设备表面换成"临时内存位图"再操作,结束后 IntEngLeaveDrvCopyBits 拷回:

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 卸载设备 回调底层 DisablePDEVEngFreeMem 释放 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(平移通知)直接忽略;光标触边则滚动 rclViewportPanSynchronize;底层支持 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.cpandisp.cengmisc.c 及关联模块分析(2026 年 8 月)

相关推荐
Hello_Damon_Nikola1 小时前
CH573从入门到精通
c语言·开发语言·单片机·嵌入式硬件
Logintern091 小时前
Langgraph使用MemorySaver建立有记忆的图
开发语言·windows·python
豆沙沙包?1 小时前
C++~~~vector容器(p31-P40)
开发语言·c++
必须会一定会1 小时前
GLM-5.3 迁移实战:强制思考、1M 上下文与官方编程基准
android·开发语言·kotlin
牛油果子哥q2 小时前
C++STL容器详解:vector/list/string底层扩容、迭代器失效终极解决、容器选型避坑
开发语言·c++·list
meilindehuzi_a2 小时前
Next.js 全栈基础实战:从 SPA、SSR 到 App Router 与 Todo API
开发语言·javascript·ecmascript
Chen—LSN2 小时前
C语言——数据在内存中的存储
c语言·开发语言·经验分享·笔记
caimouse2 小时前
ReactOS 图形系统分析(24):指针(光标)管理 — mouse.c/h
c语言·开发语言
上玄code2 小时前
【Agent精讲】调一个LLMAPI背后的工程问题
java·开发语言·python·chatgpt
welfare9622 小时前
新号别搞:c++类与对象
开发语言·c++