公司动态
ReactOS 图形系统分析(25):多显示器/平移显示与杂项 — multidisp.c / pandisp.c / engmisc.c
ReactOS 图形系统分析25多显示器/平移显示与杂项 — multidisp.c / pandisp.c / engmisc.c1. 概述《分析_4》第 45~46 行把multidisp.c/pandisp.c多显示器/平移显示与engmisc.c杂项并列。三者实为三种角色文件角色现状[multidisp.c](file:///d:/reactos/win32ss/gdi/eng/multidisp.c)多显示器META显示驱动入口MultiEnableDriverUNIMPLEMENTED 桩仅声明恒 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) 决定何时启用平移/* 1. META 设备 → 多显示器驱动桩 */if(ldevtypeLDEV_DEVICE_META)pldevLDEVOBJ_pLoadInternal(MultiEnableDriver,ldevtype);/* 2. 显示设备且请求了平移尺寸 → 用平移 vtable 覆盖驱动函数表 */if(ldevtypeLDEV_DEVICE_DISPLAY(dwAccelerationLevel5||pdm-dmFields(DM_PANNINGWIDTH|DM_PANNINGHEIGHT))){/* 缺省填平dmPanning* dmPels* */if(!(pdm-dmFieldsDM_PANNINGWIDTH))pdm-dmPanningWidthpdm-dmPelsWidth;if(!(pdm-dmFieldsDM_PANNINGHEIGHT))pdm-dmPanningHeightpdm-dmPelsHeight;/* 用 gPanDispDrvFn 覆盖 ppdev-apfn 对应项 */for(i0;igPanDispDrvCount;i)ppdev-apfn[gPanDispDrvFn[i].iFunc]gPanDispDrvFn[i].pfn;}elseppdev-pfnppdev-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)typedefstruct_PANDEV{ULONG iBitmapFormat;// 底层表面格式HDEV hdev;SIZEL szlDesktop;// 虚拟桌面大小dmPelsW×HRECTL rclViewport;// 视口物理屏幕大小的漫游窗口HSURF hsurf;// 设备表面对引擎暴露桌面大小HSURF hsurfShadow;// 影子表面实际绘制目标内存位图SURFOBJ*psoShadow;/* 底层 PDEV */ULONG flUnderlyingGraphicsCaps;SIZEL szlScreen;// 物理屏幕大小dmPanningW×HDHPDEV 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_PANNINGGCAPS2_SYNCFLUSH|SYNCTIMERPanCompletePDEV/PanDisablePDEV透传底层CompletePDEV/DisablePDEV释放 PANDEVPanEnableSurface① 启用底层表面并锁定surfObjScreen②EngCreateDeviceSurface建桌面大小设备表面 ③EngAssociateSurface挂钩 HOOK_BITBLT/COPYBITS/ALPHABLEND/GRADIENTFILL/STROKEPATH/SYNCHRONIZE/TEXTOUT ④EngCreateBitmap建影子表面桌面大小BMF_TOPDOWN视口初始居中PanDisableSurface逆序清理可被PanEnableSurface的失败路径调用故各步判空设计对 GDI/引擎而言设备表面是桌面大小而真正可见的物理屏幕只是其中rclViewport一块。引擎的绘制都发生在影子表面引擎可直接读写内存位图再由同步机制拷到物理屏。4. 绘制重定向与同步4.1 重定向到影子表面PanBitBlt(psoTrg,psoSrc,...){/* 设备表面 → 影子表面源/目标都换 */if(psoTrg-iTypeSTYPE_DEVICE)psoTrgpandev-psoShadow;if(psoSrcpsoSrc-iTypeSTYPE_DEVICE)psoSrcpandev-psoShadow;resEngBitBlt(psoTrg,psoSrc,...);// 引擎软件绘制if(res)PanSynchronize(gPan,NULL);// 绘制完立刻同步见下returnres;}同类包装PanCopyBits→PanBitBltROP4_SRCCOPY、PanAlphaBlend→EngAlphaBlend到影子、PanGradientFill→EngGradientFill到影子。PanStrokePath/PanTextOut未实现UNIMPLEMENTED ASSERT(FALSE)。4.2 同步 — PanSynchronizePanSynchronize(dhpdev,prcl){/* FIXME: 当前整块拷贝影子缓冲未按 prcl 局部优化 */ptlSrcrclViewport.left/top;// 视口左上角rclDest0,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语义PanMovePointer(pso,x,y,prcl){/* 忽略隐藏/平移通知无 DrvSetPointerShapeASSERT x0 y0 */if(x0||y0)return;if(底层支持 GCAPS_PANNING)底层MovePointer(...);// 透传底层光标ypso-sizlBitmap.cy;// 光标 y 相对屏幕底部换算/* 视口跟随光标越左/右/上/下边缘则滚动 */if(xrclViewport.left){rclViewport.leftx;rightxszlScreen.cx;bChangedTRUE;}elseif(xrclViewport.right){rightx1;leftx-szlScreen.cx;bChangedTRUE;}/* 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.c7.1 IntEngEnter / IntEngLeave — 设备表面的进入/离开核心工具问题引擎软件绘制BitBlt/AlphaBlend/StretchBlt/LineTo/TransBlt/Gradient需要能直接读写像素pvScan0/lDelta的位图表面而设备管理表面无像素缓冲无法直接操作。IntEngEnter 解决把设备表面换成临时内存位图再操作结束后IntEngLeave用DrvCopyBits拷回IntEngEnter(el,psoDest,DestRect,ReadOnly,Translate,psoOutput){if(设备表面无 pvScan0){if(!HOOK_COPYBITS)returnFALSE;// 驱动不支持 CopyBits 则失败psoOutputEngCreateBitmap(...);// 临时位图目标区域大小GDIDEVFUNCS(psoDest).CopyBits(psoOutput,psoDest,...);// 先把设备内容拷入Translate-DestRect.left/top;// 坐标系平移到临时位图}elsepsoOutputpsoDest;// 位图表面直接用/* 若挂钩 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恒返回 NULLWin2K 起废弃ABI 保留EngGetCurrentCodePage转发RtlGetDefaultCodePage返回 OEM/ANSI 代码页[winddi.h](file:///d:/reactos/sdk/include/psdk/winddi.h) DDI 服务EngQuerySystemAttribute查询系统属性EngNumberOfProcessorsNtQuerySystemInformation Basic→ CPU 数EngProcessorFeatureProcessor 信息→ 特性位EngGetTickCountKeQueryTickCountKiTickCountToMs返回毫秒 tick8. 与《分析_4》标注对照标注核对《_4》第 45 行multidisp.c/pandisp.c 「多显示器/平移显示」基本准确。补充multidisp.c 是META 设备入口桩真正实现是 pandisp.c平移显示 vtable 包装层多显示器聚合另见 mdevobj.cMDEVOBJ《_4》第 46 行engmisc.c 「杂项」准确。核心是IntEngEnter/IntEngLeave设备表面 ↔ 临时位图的 CopyBits 桥梁另有系统查询/代码页/tick9. 源码索引文件关键内容[pandisp.c](file:///d:/reactos/win32ss/gdi/eng/pandisp.c)PANDEV、PanEnablePDEV/EnableSurface/DisableSurface、PanBitBlt/PanCopyBits/PanAlphaBlend/PanGradientFill、PanSynchronize、PanMovePointer、gPanDispDrvFn13 项[multidisp.c](file:///d:/reactos/win32ss/gdi/eng/multidisp.c)MultiEnableDriverLDEV_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.hMDEVOBJ 多显示器映射对象[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设备对象PDEVOBJppdev-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|SYNCTIMER。失败返回 NULL已释放 PANDEVPanCompletePDEV设备就绪通知透传底层CompletePDEV保存hdevPanDisablePDEV卸载设备回调底层DisablePDEV后EngFreeMem释放 PANDEVPanEnableSurface启用表面建设备表面桌面尺寸影子表面 挂钩 HOOK_*引擎在启用显示后调用失败路径会自行调用PanDisableSurface清理故后者须容错未初始化状态PanDisableSurface逆序清理所有表面/影子/底层表面各步判空可被PanEnableSurface失败路径复用PanSynchronize把影子表面视口区域EngCopyBits到物理屏幕注册为DrvSynchronize当前由各绘制函数手动调用FIXME应定时同步PanBitBlt位块传输设备表面→影子表面绘制后同步引擎绘制 DDI 回调源/目标若是设备表面则替换为影子表面再调EngBitBlt成功后PanSynchronizePanCopyBits表面拷贝简化为PanBitBlt(..., ROP4_SRCCOPY)DrvCopyBits回调PanStrokePath路径描边UNIMPLEMENTEDASSERT(FALSE)当前不可用PanTextOut文本输出UNIMPLEMENTEDASSERT(FALSE)当前不可用PanMovePointer光标移动 视口漫游DrvMovePointer回调x0隐藏或y0平移通知直接忽略光标触边则滚动rclViewport并PanSynchronize底层支持GCAPS_PANNING时先透传光标PanAlphaBlendAlpha 混合到影子表面后同步引擎回调调EngAlphaBlend(psoShadow, ...)后PanSynchronizePanGradientFill渐变填充到影子表面后同步引擎回调调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获取进程句柄恒返回 NULLWin2K 起废弃驱动勿依赖EngGetCurrentCodePage查询 OEM/ANSI 代码页DDI 服务win32k 导出转发RtlGetDefaultCodePageEngQuerySystemAttribute查询系统属性驱动用CapNumEngNumberOfProcessors取 CPU 数、EngProcessorFeature取处理器特性位其余枚举返回 FALSEEngGetTickCount获取系统 tick毫秒KeQueryTickCountKiTickCountToMs驱动计时用本文档基于 ReactOS 源代码win32ss/gdi/eng/multidisp.c、pandisp.c、engmisc.c及关联模块分析2026 年 8 月