ReactOS 图形系统分析(52):OpenGL 支持 --- wingl.c
1. 概述
1.1 文件定位与作用
wingl.c(file:///d:/reactos/win32ss/gdi/ntgdi/wingl.c)(win32ss/gdi/ntgdi/,共 255 行)是 win32k.sys 中与 OpenGL 窗口像素格式(Window Pixel Format) 相关的系统服务集合,即所谓 WinGL API 。它向用户态暴露 3 个 NtGdi* 系统调用入口:
| 系统调用 | 对应 Win32/WGL API | 作用 |
|---|---|---|
NtGdiDescribePixelFormat |
DescribePixelFormat / wglDescribePixelFormat |
枚举/描述设备(显示驱动)支持的像素格式(PIXELFORMATDESCRIPTOR),返回格式总数 |
NtGdiSetPixelFormat |
SetPixelFormat / wglSetPixelFormat |
把某个像素格式"绑定"到窗口(经窗口 DC → WNDOBJ → 所属表面),通知显示驱动 |
NtGdiSwapBuffers |
SwapBuffers / wglSwapBuffers |
交换前后台缓冲(双缓冲翻页),通知驱动/渲染器完成"翻页" |
此外还有一个文件内部使用的静态辅助函数 IntGetipfdDevMax(获取并缓存设备最大像素格式索引)。
这三个入口都不做实际的 OpenGL 渲染 ------渲染由用户态的 opengl32.dll(软件实现为 Mesa)或 ICD(Installable Client Driver,可安装客户驱动程序)完成。wingl.c 的职责非常纯粹:把"像素格式"这一概念从 GDI 的设备/窗口模型桥接到显示驱动(DDI)层,充当 GDI(win32k)与 OpenGL ICD/软件渲染器之间的"像素格式分发器"。
1.2 设计动机:为什么像素格式要进入内核
Windows 的 OpenGL 支持采用 ICD 架构 :opengl32.dll 是一个"薄分发层",真正的渲染由 ICD 或内置软件实现(Mesa)完成。但在绘制到窗口之前,必须先解决一个设备相关 的问题------像素格式(Pixel Format):
- 像素格式是"设备资源"而非"应用资源" :像素格式描述了帧缓冲的布局(颜色位深、Alpha、深度/模板/累积缓冲、双缓冲、立体等)。这些能力由显示驱动决定,只有驱动知道自己的硬件/模式支持什么;
- 设备相关接口必须在内核侧分发 :Windows 的显示驱动接口(DDI)运行在内核态(win32k 中),
DrvDescribePixelFormat/DrvSetPixelFormat/DrvSwapBuffers是 DDK 定义的显示驱动可选回调(见 winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h))。用户态无法直接调用内核态驱动的回调,必须由 win32k 的NtGdi*系统调用做中转; - 格式选择是"一次性绑定":Windows 规定一个窗口/DC 只能设置一次像素格式(再次设置只能设置相同的格式)。这个"只能设置一次"的约束需要在格式真正落到设备之前由驱动/内核侧记录,保证后续 OpenGL 上下文(HGLRC)与窗口的兼容性校验有据可依;
- 窗口表面是载体 :OpenGL 最终要往窗口所属的表面 (SURFOBJ)上画。
SetPixelFormat/SwapBuffers需要拿到与窗口关联的 WNDOBJ 及其所有者表面psoOwner,这只有在内核里(win32k 的 USER 子系统 + GDI 引擎)才能可靠地解析。
因此 ReactOS 的设计是:gdi32.dll(用户态薄封装)→ opengl32.dll(wgl 分发)→ ICD 或回退到 win32k(wingl.c)→ 显示驱动 DDI 回调*。wingl.c 处于这条链的"内核收口"位置。
1.3 总体架构图
应用(OpenGL 程序,如 glgears / Quake)
│
├─ glXxx() ─────────────► opengl32.dll(Mesa 软件渲染 / ICD 分派)
│
├─ ChoosePixelFormat(hdc,&pfd) ──► gdi32.ChoosePixelFormat
├─ DescribePixelFormat(hdc,..) ──► gdi32.DescribePixelFormat
├─ SetPixelFormat(hdc,pf,&pfd) ─► gdi32.SetPixelFormat("只能设一次"检查)
└─ SwapBuffers(hdc) ───────────► gdi32.SwapBuffers
│
▼ (opengl32.dll wgl* 层,dll/opengl/opengl32/wgl.c)
wglChoosePixelFormat / wglDescribePixelFormat / wglSetPixelFormat / wglSwapBuffers
│
│ ICD 存在且 win32k 不接管?
│ ├─ 是 → ICD DLL 的 DrvDescribePixelFormat/DrvSetPixelFormat/DrvSwapBuffers
│ └─ 否 → GdiDescribePixelFormat / GdiSetPixelFormat / GdiSwapBuffers
│ │(gdi32.spec:直接系统调用)
▼ ▼
win32k.sys ------ wingl.c(本文件,3 个 NtGdi* 入口)
│ │
│ DC_LockDc 锁 DC;UserGethWnd 取窗口/WNDOBJ
│ pso = pWndObj->psoOwner(窗口所属表面)
▼
PPDEVOBJ ppdev = pdc->ppdev
│
▼ ppdev->DriverFunctions.(DDI 可选回调,内核态显示驱动实现)
DrvDescribePixelFormat(dhpdev, ipfd, cjpfd, ppfd)
DrvSetPixelFormat(pso, ipfd, hWnd)
DrvSwapBuffers(pso, pWndObj)
1.4 文件概况
wingl.c 只有 4 个函数:
| 函数 | 行号 | 类型 | 说明 |
|---|---|---|---|
IntGetipfdDevMax |
L14--L39 | static FASTCALL INT |
内部辅助:查询并缓存 DC 所属设备的像素格式总数(pdc->ipfdDevMax) |
NtGdiDescribePixelFormat |
L41--L121 | __kernel_entry APIENTRY INT |
系统调用:描述像素格式 / 返回格式总数 |
NtGdiSetPixelFormat |
L124--L196 | APIENTRY BOOL |
系统调用:把像素格式绑定到窗口 |
NtGdiSwapBuffers |
L198--L254 | APIENTRY BOOL |
系统调用:交换前后缓冲 |
文件中使用了以下关键基础设施(均来自 win32k 其他模块):
DC_LockDc/DC_UnlockDc(dc.h 的 FORCEINLINE)------按 HDC 句柄锁定/解锁内核 DC 对象(GDI 句柄表);EngSetLastError------设置线程最后错误(对应用户态SetLastError);UserEnterExclusive/UserLeave------进入/离开 USER 子系统独占锁(串行化窗口对象访问);UserGethWnd(ntuser/windc.c)------由 HDC 反查窗口句柄 HWND 与 WNDOBJ;PDEV_META_DEVICE(win32ss/gdi/eng/pdevobj.h,值0x00020000)------"元文件设备"标记,wingl 对这类设备未实现(UNIMPLEMENTED);ppdev->DriverFunctions(DRIVER_FUNCTIONS,ntgdityp.h)------显示驱动函数表,本文件用到其中 3 个像素格式回调。
1.5 三条调用链概览
| 场景 | 用户态 API | wgl 层 | 内核 |
|---|---|---|---|
| 枚举格式 | DescribePixelFormat(hdc,i,n,ppfd) |
wglDescribePixelFormat |
NtGdiDescribePixelFormat |
| 选择最佳格式 | ChoosePixelFormat(hdc,pfd) |
wglChoosePixelFormat(内部多次调 wglDescribePixelFormat) |
NtGdiDescribePixelFormat(只读) |
| 绑定格式 | SetPixelFormat(hdc,i,pfd) |
wglSetPixelFormat |
NtGdiSetPixelFormat |
| 翻页 | SwapBuffers(hdc) |
wglSwapBuffers |
NtGdiSwapBuffers |
| 查询当前格式 | GetPixelFormat(hdc) |
wglGetPixelFormat(用户态缓存,不陷入内核) |
--- |
注意:GetPixelFormat 没有对应的 NtGdi* 入口。像素格式设置成功后由 opengl32.dll 在用户态 按 DC 缓存(struct wgl_dc_data.pixelformat),查询当前格式时直接读缓存,不走系统调用。
2. 核心数据结构
2.1 PIXELFORMATDESCRIPTOR(wingdi.h 全字段)
像素格式的描述符定义在 wingdi.h(file:///d:/reactos/sdk/include/psdk/wingdi.h) L3015--L3042,共 26 个字段、40 字节:
c
typedef struct tagPIXELFORMATDESCRIPTOR {
WORD nSize; // 结构大小(sizeof(PIXELFORMATDESCRIPTOR)=40)
WORD nVersion; // 版本,必须为 1
DWORD dwFlags; // PFD_* 标志位组合(见 2.2)
BYTE iPixelType; // PFD_TYPE_RGBA(0) / PFD_TYPE_COLORINDEX(1)
BYTE cColorBits; // 颜色缓冲位深(RGBA 时含 Alpha;调色板时为调色板索引位数)
BYTE cRedBits; // 红色分量位数
BYTE cRedShift; // 红色分量移位
BYTE cGreenBits; // 绿色分量位数
BYTE cGreenShift; // 绿色分量移位
BYTE cBlueBits; // 蓝色分量位数
BYTE cBlueShift; // 蓝色分量移位
BYTE cAlphaBits; // Alpha 分量位数
BYTE cAlphaShift; // Alpha 分量移位
BYTE cAccumBits; // 累积缓冲总位数
BYTE cAccumRedBits; // 累积缓冲红色分量位数
BYTE cAccumGreenBits; // 累积缓冲绿色分量位数
BYTE cAccumBlueBits; // 累积缓冲蓝色分量位数
BYTE cAccumAlphaBits; // 累积缓冲 Alpha 分量位数
BYTE cDepthBits; // 深度缓冲(Z 缓冲)位数
BYTE cStencilBits; // 模板缓冲(Stencil)位数
BYTE cAuxBuffers; // 辅助缓冲数量
BYTE iLayerType; // 层类型:PFD_MAIN_PLANE(0)/PFD_OVERLAY_PLANE(1)/PFD_UNDERLAY_PLANE(-1)
BYTE bReserved; // 保留:低 8 位表示覆盖/底层数(0/1/2),3--7 位为 Alpha 层数
DWORD dwLayerMask; // 层屏蔽(本格式覆盖哪些底层)
DWORD dwVisibleMask; // 透明色(相同颜色值在其他层上透明)
DWORD dwDamageMask; // 层损坏区域(本格式被覆盖时)
} PIXELFORMATDESCRIPTOR,*PPIXELFORMATDESCRIPTOR,*LPPIXELFORMATDESCRIPTOR;
字段布局与字节偏移(方便对照驱动实现与内存拷贝):
| 偏移 | 长度 | 字段 |
|---|---|---|
| 0x00 | 2 | nSize |
| 0x02 | 2 | nVersion |
| 0x04 | 4 | dwFlags |
| 0x08 | 1 | iPixelType |
| 0x09 | 7 | cColorBits, cRedBits, cRedShift, cGreenBits, cGreenShift, cBlueBits, cBlueShift |
| 0x10 | 2 | cAlphaBits, cAlphaShift |
| 0x12 | 5 | cAccumBits, cAccumRedBits, cAccumGreenBits, cAccumBlueBits, cAccumAlphaBits |
| 0x17 | 3 | cDepthBits, cStencilBits, cAuxBuffers |
| 0x1A | 1 | iLayerType |
| 0x1B | 1 | bReserved |
| 0x1C | 4 | dwLayerMask |
| 0x20 | 4 | dwVisibleMask |
| 0x24 | 4 | dwDamageMask |
共 0x28 = 40 字节。
wingdi.h 还定义了配套的 EMF 记录结构(用于把"设置像素格式"录制进增强型元文件,L3044--L3048):
c
typedef struct tagEMRPIXELFORMAT {
EMR emr; // 记录头(iType = EMR_PIXELFORMAT)
PIXELFORMATDESCRIPTOR pfd; // 像素格式描述
} EMRPIXELFORMAT, *PEMRPIXELFORMAT;
gdi32 在重放 EMF 时会据此调用 ChoosePixelFormat + SetPixelFormat(见 enhmetafile.c(file:///d:/reactos/win32ss/gdi/gdi32/wine/enhmetafile.c) L1791--L1792)。
2.2 PFD_* 标志位详解
dwFlags 使用的所有标志定义于 wingdi.h L296--L317:
像素类型(iPixelType)
| 常量 | 值 | 含义 |
|---|---|---|
PFD_TYPE_RGBA |
0 | RGBA 类型(颜色直接由 RGB 分量表示) |
PFD_TYPE_COLORINDEX |
1 | 调色板索引类型(颜色为调色板索引) |
层类型(iLayerType)
| 常量 | 值 | 含义 |
|---|---|---|
PFD_MAIN_PLANE |
0 | 主平面 |
PFD_OVERLAY_PLANE |
1 | 覆盖层 |
PFD_UNDERLAY_PLANE |
(-1) | 底层 |
dwFlags 能力标志
| 常量 | 值 | 含义 |
|---|---|---|
PFD_DOUBLEBUFFER |
0x00000001 | 双缓冲(需配合 SwapBuffers 翻页) |
PFD_STEREO |
0x00000002 | 立体(左右眼两套缓冲) |
PFD_DRAW_TO_WINDOW |
0x00000004 | 可绘制到窗口 |
PFD_DRAW_TO_BITMAP |
0x00000008 | 可绘制到位图(内存 DC) |
PFD_SUPPORT_GDI |
0x00000010 | 支持 GDI 绘制(可与 GDI 混用) |
PFD_SUPPORT_OPENGL |
0x00000020 | 支持 OpenGL(必要条件) |
PFD_GENERIC_FORMAT |
0x00000040 | 通用(软件)格式------由 GDI 软件实现提供,非硬件加速 |
PFD_NEED_PALETTE |
0x00000080 | 需要调色板(逻辑调色板,通常配合 8bpp) |
PFD_NEED_SYSTEM_PALETTE |
0x00000100 | 需要系统调色板 |
PFD_SWAP_EXCHANGE |
0x00000200 | SwapBuffers 交换前后缓冲(交换语义) |
PFD_SWAP_COPY |
0x00000400 | SwapBuffers 拷贝后缓冲(拷贝语义,不交换) |
PFD_SWAP_LAYER_BUFFERS |
0x00000800 | 支持层缓冲交换 |
PFD_GENERIC_ACCELERATED |
0x00001000 | 通用但加速的格式(GDI 软件渲染 + 硬件加速的混合) |
PFD_SUPPORT_COMPOSITION |
0x00008000 | 支持 DWM 合成(Vista 及以后) |
dwFlags 匹配辅助标志(仅用于 ChoosePixelFormat 匹配,不作为能力声明)
| 常量 | 值 | 含义 |
|---|---|---|
PFD_DEPTH_DONTCARE |
0x20000000 | 深度缓冲位数不重要 |
PFD_DOUBLEBUFFER_DONTCARE |
0x40000000 | 是否双缓冲不重要 |
PFD_STEREO_DONTCARE |
0x80000000 | 是否立体不重要 |
SP_* 常量(SP_ERROR = -1 等)也定义在同一区域,用于打印换页相关错误。
注意 :PFD_*_DONTCARE 三个标志只影响 ChoosePixelFormat 的匹配偏好 ,不会出现在设备返回的能力描述中。opengl32 的 wglChoosePixelFormat 对它们的处理有专门逻辑(见 7.2)。
2.3 像素格式索引
- 像素格式用从 1 开始的整数索引标识,
0表示"未设置/无效"; - 设备支持的格式数为
n,则合法索引为1..n,n同时也是DescribePixelFormat(hdc, 0, 0, NULL)的返回值; - wingl.c 中
IntGetipfdDevMax用 ipfd=1 探测格式总数,NtGdiDescribePixelFormat用(ipfd < 1) || (ipfd > pdc->ipfdDevMax)做范围校验; - opengl32 把格式索引分区 :
1..nb_icd_formats为 ICD 提供的硬件格式,nb_icd_formats+1 .. nb_icd_formats+nb_sw_formats为软件格式(Mesa)。索引在跨分区时要做偏移换算(见 7.5)。
2.4 DC 结构与像素格式相关的字段
wingl.c 直接操作内核 DC 对象(PDC),相关结构定义在 dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h)。
DC 对象(L95--L137)中与像素格式直接相关的字段:
| 字段 | 偏移意义 | 说明 |
|---|---|---|
DHPDEV dhpdev |
L101 | 设备句柄,对应 PDEVOBJ 的 dhpdev,转交给驱动回调 |
PPDEVOBJ ppdev |
L104 | 指向内核 PDEV 对象(PDEVOBJ),wingl 通过它访问 DriverFunctions 与 flFlags |
INT ipfdDevMax |
L133 | 像素格式总数缓存 :首次查询时由 IntGetipfdDevMax 填充,之后复用;创建 DC 时初始化为 0(dclife.c L344) |
其余常用字段:BaseObject(GDI 对象头)、dctype(DCTYPE_DIRECT/MEMORY/INFO)、pdcattr/dcattr(DC 属性)、dclevel(DCLEVEL 状态栈)、hdcNext/hdcPrev(DC 链表)、裁剪区域族(prgnVis/prgnAPI/prgnRao)等------wingl.c 不直接使用这些,但锁 DC 的合法性依赖整个对象结构。
DCLEVEL 结构(L49--L89) 是 DC 的"可保存/恢复状态"集合(对应 SaveDC/RestoreDC),wingl.c 虽未直接访问,但理解 DC 模型需要知道它的存在:
| 字段 | 说明 |
|---|---|
HPALETTE hpal; PPALETTE ppal |
当前调色板(句柄 + 对象指针) |
PVOID pColorSpace; LONG lIcmMode |
颜色空间(ICC)与 ICM 模式 |
LONG lSaveDepth; HGDIOBJ hdcSave |
SaveDC 深度与保存的 DC 句柄 |
POINTL ptlBrushOrigin |
画刷原点 |
PBRUSH pbrFill; PBRUSH pbrLine |
填充/线条画刷(含引用计数语义) |
LFONT *plfnt |
当前字体(LFONT/TEXTOBJ) |
HPATH hPath; FLONG flPath; LINEATTRS laPath |
路径对象、路径标志与线属性 |
PREGION prgnClip; PREGION prgnMeta |
设备裁剪区 / 元文件裁剪区 |
COLORADJUSTMENT ca |
颜色调整 |
FLONG flFontState; UNIVERSAL_FONT_ID ufi, ufiLoc[4]; ... |
字体状态与通用字体 ID 列表 |
FLONG fl; FLONG flBrush |
DC 标志 / 画刷标志 |
MATRIX mxWorldToDevice; mxDeviceToWorld; mxWorldToPage |
世界→设备、设备→世界、世界→页面变换 |
FLOATOBJ efM11PtoD ... efPr22 |
页面→设备变换系数(浮点)与 TWIPS 相关系数 |
PSURFACE pSurface; SIZE sizl |
当前选入表面与表面尺寸 |
DC_ATTR dcattr(DC 结构 L110 内嵌)与 PDC_ATTR pdcattr(L108)保存 DC 属性(文本对齐、映射模式、图形模式等)。
锁 DC 的 FORCEINLINE 工具(dc.h L218--L244):
c
FORCEINLINE PDC DC_LockDc(HDC hdc)
{
PDC pdc = (PDC)GDIOBJ_LockObject(hdc, GDIObjType_DC_TYPE);
if (pdc)
{
ASSERT(类型为 LO_DC_TYPE 或 LO_ALTDC_TYPE);
ASSERT(pdc->dclevel.plfnt != NULL);
ASSERT(plfnt 是 LO_FONT_TYPE);
}
return pdc;
}
FORCEINLINE VOID DC_UnlockDc(PDC pdc)
{
ASSERT(pdc->dclevel.plfnt != NULL); // 与锁定时的断言对称
GDIOBJ_vUnlockObject(&pdc->BaseObject);
}
wingl.c 的三个入口都遵循同一模板:DC_LockDc 进入 → 校验/操作 → DC_UnlockDc 离开 ,锁定失败返回错误并设置 ERROR_INVALID_HANDLE。
2.5 显示驱动函数表:DRIVER_FUNCTIONS 与 DDI 原型
wingl.c 通过 ppdev->DriverFunctions(PDEVOBJ 内嵌联合,见 pdevobj.h(file:///d:/reactos/win32ss/gdi/eng/pdevobj.h) L135--L140)调用内核态显示驱动的三个像素格式回调。函数表定义在 ntgdityp.h(file:///d:/reactos/win32ss/include/ntgdityp.h)(_DRIVER_FUNCTIONS,L566 起),其中:
| 函数表成员 | 行号 | 函数表索引(winddi.h) |
|---|---|---|
PFN_DrvSetPixelFormat SetPixelFormat |
L622 | INDEX_DrvSetPixelFormat = 54 |
PFN_DrvDescribePixelFormat DescribePixelFormat |
L623 | INDEX_DrvDescribePixelFormat = 55 |
PFN_DrvSwapBuffers SwapBuffers |
L624 | INDEX_DrvSwapBuffers = 56 |
DDI 函数原型(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h)):
c
// winddi.h L3485--L3492
typedef LONG (APIENTRY FN_DrvDescribePixelFormat)(
_In_ DHPDEV dhpdev, // 设备句柄
_In_ LONG iPixelFormat, // 像素格式索引(0 = 只问总数)
_In_ ULONG cjpfd, // 输出缓冲字节数
_Out_opt_ PIXELFORMATDESCRIPTOR *ppfd); // 输出描述符(可为 NULL)
typedef FN_DrvDescribePixelFormat *PFN_DrvDescribePixelFormat;
// winddi.h L4019--L4025
typedef BOOL (APIENTRY FN_DrvSetPixelFormat)(
_In_ SURFOBJ *pso, // 窗口所属表面
_In_ LONG iPixelFormat, // 像素格式索引
_In_ HWND hwnd); // 窗口句柄
typedef FN_DrvSetPixelFormat *PFN_DrvSetPixelFormat;
// winddi.h L4144--L4149
typedef BOOL (APIENTRY FN_DrvSwapBuffers)(
_In_ SURFOBJ *pso, // 窗口所属表面
_In_ WNDOBJ *pwo); // 窗口对象(WNDOBJ)
typedef FN_DrvSwapBuffers *PFN_DrvSwapBuffers;
PDEVOBJ 关键字段(pdevobj.h L79--L149):
| 字段 | 说明 |
|---|---|
FLONG flFlags |
设备标志;PDEV_META_DEVICE = 0x00020000 表示元文件设备 |
DHPDEV dhpdev |
传给驱动回调的设备句柄(L120) |
union { DRIVER_FUNCTIONS DriverFunctions; ... PVOID apfn[INDEX_LAST]; } |
驱动函数表(L135--L140),驱动在 DrvEnablePDEV 时用 EngDeviceIoControl/DrvEnableDriver 填充 |
当前 ReactOS 显示驱动的实现状态 :VGA 显示驱动(enable.c(file:///d:/reactos/win32ss/drivers/displays/vga/main/enable.c) L31--L55)在 #if 0 块中保留了可选的 {INDEX_DescribePixelFormat, VGADDIDescribePixelFormat}、{INDEX_DrvSetPixelFormat, ...}、{INDEX_DrvSwapBuffers, ...} 等条目,当前未编译 ------即默认显示驱动不提供 这三个回调。因此默认情况下 NtGdiDescribePixelFormat 返回 0,opengl32 走内置 Mesa 软件渲染;wingl.c 的驱动调用路径是为"实现了这些 DDI 的驱动(含第三方 ICD 配合)"预留的标准接口。
3. 内部辅助函数:IntGetipfdDevMax
3.1 签名与位置
c
// wingl.c L14--L39
static INT FASTCALL IntGetipfdDevMax(PDC pdc);
文件内部函数,非系统调用,不能被用户态直接调用。
3.2 参数与返回值
| 参数 | 类型 | 含义 |
|---|---|---|
pdc |
PDC |
已锁定的内核 DC 对象(调用者保证有效) |
| 返回值 | 含义 |
|---|---|
> 0 |
设备支持的像素格式总数 |
0 |
设备不支持(元文件设备 / 驱动未实现 DescribePixelFormat) |
副作用:返回值非 0 时写入 pdc->ipfdDevMax(缓存)。
3.3 实现流程
PPDEVOBJ ppdev = pdc->ppdev;取设备对象,INT Ret = 0;默认失败;- 元文件设备短路 :
if (ppdev->flFlags & PDEV_META_DEVICE) return 0;------元文件 DC(metafile)没有真实设备像素格式,直接返回 0; - 询问驱动格式总数 :若
ppdev->DriverFunctions.DescribePixelFormat非空,则以(dhpdev, ipfd=1, cjpfd=0, ppfd=NULL)调用。注意:ipfd=1:取第一个格式的索引做探测(Windows DDI 约定:iPixelFormat非 0 时返回设备支持的格式总数);cjpfd=0、ppfd=NULL:不要描述符内容,只取数量;
- 缓存 :
if (Ret) pdc->ipfdDevMax = Ret;------只有非 0 结果才缓存(0 表示"不支持",不污染缓存,下次再试); - 返回
Ret。
3.4 使用方式与注意事项
- 惰性求值 :
ipfdDevMax在 DC 创建时(dclife.c L344)被置 0,首次需要时由本函数填充。wingl.c 中三处使用:NtGdiDescribePixelFormatL65--L72:if (!pdc->ipfdDevMax) { if (!IntGetipfdDevMax(pdc)) goto Exit; }------失败直接退出(此时 Ret=0 且未设置错误码 ,源码注释/* EngSetLastError ? */表明这是个待完善点);NtGdiSetPixelFormatL146--L147:if (!pdc->ipfdDevMax) IntGetipfdDevMax(pdc);------返回值未检查 ,若仍为 0 则由随后的范围校验统一兜底(ipfd ≥ 1 > ipfdDevMax=0 →ERROR_INVALID_PARAMETER);
- 缓存语义:一次查询、多次复用,避免每次调用都陷入驱动;设备模式变化(如分辨率/色深切换)后缓存可能过时,但 ReactOS 未做失效处理(与 Windows 行为一致------像素格式一般只在 DC 创建后短期内查询/设置);
- 线程安全 :该函数只读驱动函数表并写
pdc->ipfdDevMax,调用者已持有 DC 锁(DC_LockDc),故无需额外同步。
4. NtGdiDescribePixelFormat:描述像素格式
4.1 签名与定位
c
// wingl.c L41--L121
_Success_(return != 0)
__kernel_entry
INT APIENTRY
NtGdiDescribePixelFormat(
_In_ HDC hdc,
_In_ INT ipfd,
_In_ UINT cjpfd,
_Out_writes_bytes_(cjpfd) PPIXELFORMATDESCRIPTOR ppfd);
- 系统调用入口(gdi32.spec:
262 stdcall GdiDescribePixelFormat(ptr long long ptr) NtGdiDescribePixelFormat,即 gdi32 的GdiDescribePixelFormat直接映射到本函数); - 对应 Win32 API
DescribePixelFormat(hdc, iPixelFormat, nBytes, ppfd)(gdi32.spec145,实现在 gdi32/misc/wingl.c,转发给 opengl32 的wglDescribePixelFormat); __kernel_entry+_Success_(return != 0):成功时返回值非 0;- 语义(与 Win32 一致):ppfd 为 NULL 时只返回格式总数(最大索引);否则填充指定索引的描述符并返回格式总数。
4.2 参数说明
| 参数 | 类型 | 含义 |
|---|---|---|
hdc |
HDC |
设备上下文句柄(内核按句柄锁定 DC 对象) |
ipfd |
INT |
像素格式索引,范围 1..ipfdDevMax(仅当 ppfd 非空时校验) |
cjpfd |
UINT |
输出缓冲区 ppfd 的字节数(调用者声明) |
ppfd |
PPIXELFORMATDESCRIPTOR |
输出缓冲(用户态指针,需 ProbeForWrite 探测) |
4.3 实现流程(逐步)
NtGdiDescribePixelFormat(hdc, ipfd, cjpfd, ppfd)
│
├─ [1] 快速失败:if (ppfd == NULL && cjpfd != 0) return 0;
│ (要写缓冲却不给缓冲 → 直接失败,不设错误码)
│
├─ [2] DC_LockDc(hdc)
│ └─ 失败 → EngSetLastError(ERROR_INVALID_HANDLE); return 0;
│
├─ [3] 惰性取格式总数
│ if (!pdc->ipfdDevMax)
│ if (!IntGetipfdDevMax(pdc)) goto Exit; // Ret=0,无错误码(注释 EngSetLastError ?)
│
├─ [4] 只问数量:if (!ppfd) { Ret = pdc->ipfdDevMax; goto Exit; }
│ ※ 注意:此分支在 ipfd 范围校验【之前】,所以 ipfd 可以是 0/任意值;
│ 这正是 icdload.c 用 GdiDescribePixelFormat(hdc, 0, 0, NULL) 探测 win32k 是否接管的前提。
│
├─ [5] 范围校验:if (ipfd < 1 || ipfd > pdc->ipfdDevMax)
│ └─ EngSetLastError(ERROR_INVALID_PARAMETER); goto Exit;
│
├─ [6] ppdev = pdc->ppdev;
│
├─ [7] 元文件设备:if (ppdev->flFlags & PDEV_META_DEVICE) { UNIMPLEMENTED; goto Exit; }
│ → 串口打 WARNING: ... UNIMPLEMENTED,Ret 保持 0
│
├─ [8] 驱动查询(写内核栈缓冲,避免直接碰用户指针):
│ if (ppdev->DriverFunctions.DescribePixelFormat)
│ Ret = DescribePixelFormat(ppdev->dhpdev, ipfd, sizeof(pfdSafe), &pfdSafe);
│ // pfdSafe 是函数栈上的 PIXELFORMATDESCRIPTOR
│
├─ [9] 拷贝回用户态(仅当驱动成功且调用者要缓冲):
│ if (Ret && cjpfd)
│ _SEH2_TRY {
│ cjpfd = min(cjpfd, sizeof(PIXELFORMATDESCRIPTOR)); // 截断到 40 字节
│ ProbeForWrite(ppfd, cjpfd, 1); // 探测用户缓冲可写
│ RtlCopyMemory(ppfd, &pfdSafe, cjpfd); // 拷贝
│ } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
│ SetLastNtError(_SEH2_GetExceptionCode()); // 吞掉异常,设置 NT 错误码
│ } _SEH2_END;
│
└─ Exit: DC_UnlockDc(pdc); return Ret;
4.4 关键实现细节
- 用户缓冲安全 :驱动回调填充的是栈上的
pfdSafe,之后才用_SEH2_TRY+ProbeForWrite把数据拷回用户指针。这样即使驱动实现有缺陷也不会直接写到用户地址;用户指针非法时异常被EXCEPTION_EXECUTE_HANDLER捕获并转成 NT 错误码(SetLastNtError); - 截断拷贝 :
min(cjpfd, sizeof(PIXELFORMATDESCRIPTOR))------若应用给的缓冲小于 40 字节,只拷贝其声明的字节数(宽容处理,符合 Windows 行为); - 异常后的返回值 :若拷贝阶段异常,
Ret保持不变(仍为驱动返回的格式数)------即"返回了格式数但缓冲没写成功"。这是当前实现的取舍(探测在拷贝前已挡住绝大多数非法指针); !ppfd分支提前返回 :ipfd范围校验只对"要填描述符"的调用生效,这与 Windows 文档一致(DescribePixelFormat(hdc, 0, 0, NULL)是合法的数量查询);- 错误码清单 :
ERROR_INVALID_HANDLE(DC 锁定失败)、ERROR_INVALID_PARAMETER(索引越界);数量查询成功不设错误码。
4.5 使用方式与注意事项
- 应用侧 :先用
DescribePixelFormat(hdc, 0, 0, NULL)取总数 n,再循环i=1..n逐个取描述符,配合ChoosePixelFormat选择; - opengl32 侧 :
wglDescribePixelFormat把 win32k 的结果(ICD 格式数)与软件格式数相加,作为"总格式数"返回(见 7.5); - icdload 探测 :
GdiDescribePixelFormat(hdc, 0, 0, NULL) != 0是"win32k/显示驱动接管像素格式"的判据(见 7.6); - 注意事项:本函数不要求 HDC 必须来自窗口(内存 DC 也可查询),但若设备是元文件设备(PDEV_META_DEVICE)则返回 0。
5. NtGdiSetPixelFormat:把像素格式绑定到窗口
5.1 签名与定位
c
// wingl.c L124--L196
BOOL APIENTRY
NtGdiSetPixelFormat(
_In_ HDC hdc,
_In_ INT ipfd);
- 系统调用入口(gdi32.spec:
322 stdcall GdiSetPixelFormat(ptr long) NtGdiSetPixelFormat); - 对应 Win32
SetPixelFormat(hdc, iPixelFormat, ppfd)(gdi32.spec564)与wglSetPixelFormat; - 注意 :Win32 版本有第三个参数
ppfd(像素格式描述符,用于校验),但内核入口没有 ------内核只接收索引ipfd,描述符校验发生在用户态 opengl32(wglSetPixelFormat通过get_dc_data_ex已把描述符传给 ICD 侧的DrvDescribePixelFormat); - 函数开头有一句
DPRINT1("Setting pixel format from win32k!\n")------这是调试期留下的"接管提示",任何一次从 win32k 路径设置像素格式都会在串口/调试器输出。
5.2 参数说明
| 参数 | 类型 | 含义 |
|---|---|---|
hdc |
HDC |
设备上下文句柄(必须能关联到窗口,否则失败) |
ipfd |
INT |
要绑定的像素格式索引(1..ipfdDevMax) |
5.3 实现流程(逐步)
NtGdiSetPixelFormat(hdc, ipfd)
│
├─ DPRINT1("Setting pixel format from win32k!\n")
│
├─ [1] DC_LockDc(hdc)
│ └─ 失败 → EngSetLastError(ERROR_INVALID_HANDLE); return FALSE;
│
├─ [2] 惰性取总数:if (!pdc->ipfdDevMax) IntGetipfdDevMax(pdc);
│ (返回值不检查;为 0 时由 [3] 兜底)
│
├─ [3] 范围校验:if (ipfd < 1 || ipfd > pdc->ipfdDevMax)
│ └─ EngSetLastError(ERROR_INVALID_PARAMETER); goto Exit;
│
├─ [4] 取窗口对象(进 USER 独占锁):
│ UserEnterExclusive();
│ hWnd = UserGethWnd(hdc, &pWndObj);
│ UserLeave();
│ └─ if (!hWnd) → EngSetLastError(ERROR_INVALID_WINDOW_STYLE); goto Exit;
│ (HDC 不是窗口 DC:内存 DC / GetDC(NULL) 屏幕 DC 等)
│
├─ [5] ppdev = pdc->ppdev;
│
├─ [6] 取窗口所属表面:
│ if (pWndObj) pso = pWndObj->psoOwner;
│ else { EngSetLastError(ERROR_INVALID_PIXEL_FORMAT); goto Exit; }
│ (WNDOBJ 是窗口与 GDI 引擎的桥接对象,psoOwner 为其所有者表面)
│
├─ [7] 元文件设备:if (ppdev->flFlags & PDEV_META_DEVICE) { UNIMPLEMENTED; goto Exit; }
│
├─ [8] 驱动绑定:
│ if (ppdev->DriverFunctions.SetPixelFormat)
│ Ret = SetPixelFormat(pso, ipfd, hWnd);
│ (驱动成功返回 TRUE;未实现则 Ret 保持 FALSE)
│
└─ Exit: DC_UnlockDc(pdc); return Ret;
5.4 关键实现细节
- USER 子系统互斥 :
UserEnterExclusive()/UserLeave()包裹UserGethWnd------窗口对象(PWND)、WNDOBJ 由 USER 管理,GDI 线程访问它们必须串行化,避免与窗口销毁/移动等并发操作竞争; UserGethWnd的实现 (ntuser/windc.c L996--L1014):IntWindowFromDC(hdc):由 HDC 反查窗口(通过 DC 与窗口的关联);UserGetWindowObject(hWnd):按句柄取窗口对象;UserGetProp(Wnd, AtomWndObj, TRUE):取窗口属性中的 WNDOBJ(EWNDOBJ),并校验Clip->Hwnd == hWnd;- 命中则
*pwndo = (PWNDOBJ)Clip,返回 hWnd;
- WNDOBJ 与 psoOwner :WNDOBJ 由
EngCreateWnd(pso, hWnd, pfn, fl, iPixelFormat)创建(engwindow.c L143 起),其中psoOwner记录窗口所属表面(engwindow.c L191),Clip->PixelFormat = iPixelFormat(L203)记录像素格式------这是驱动侧记录"窗口绑定了哪个像素格式"的标准途径; - "只能设置一次"由谁保证 :内核入口本身不 检查"是否已设置";用户态 gdi32 的
SetPixelFormat先调GetPixelFormat(opengl32 用户态缓存),若已设置则只允许相同索引(gdi32/misc/wingl.c L169--L171);wglSetPixelFormat同样在用户态缓存检查(wgl.c L835--L839)。winetesttest_setpixelformat专门验证了"重复设置相同格式成功、不同格式失败"; - 错误码语义 :
ERROR_INVALID_HANDLE:HDC 非法;ERROR_INVALID_PARAMETER:索引越界(含"设备不支持任何像素格式"的情形,此时 ipfdDevMax=0);ERROR_INVALID_WINDOW_STYLE:HDC 不是窗口 DC(注意:错误码虽名为 WINDOW_STYLE,实际表示"无法解析出窗口");ERROR_INVALID_PIXEL_FORMAT:窗口存在但没有 WNDOBJ(无法拿到表面)。
5.5 使用方式与注意事项
- 正确顺序 :
ChoosePixelFormat选定 →SetPixelFormat绑定 →wglCreateContext/wglMakeCurrent创建并绑定上下文 → 渲染 →SwapBuffers翻页。wglCreateContext要求dc_data->pixelformat已设置(否则ERROR_INVALID_PIXEL_FORMAT,wgl.c L398--L403); - 兼容性校验 :
wglMakeCurrent检查ctx->icd_data == dc_data->icd_data && ctx->pixelformat == dc_data->pixelformat,不匹配即失败(wgl.c L670--L677)------这正是"格式必须一致"的内核/用户协同保证; - 注意事项:设置像素格式会改变窗口表面的使用方式(可能被 OpenGL 独占),Windows 文档要求设置后不能再改变窗口样式;本函数未在设置成功后主动触发窗口重绘,由应用负责。
6. NtGdiSwapBuffers:交换前后缓冲
6.1 签名与定位
c
// wingl.c L198--L254
BOOL APIENTRY
NtGdiSwapBuffers(
_In_ HDC hdc);
- 系统调用入口(gdi32.spec:
326 stdcall GdiSwapBuffers(ptr) NtGdiSwapBuffers); - 对应 Win32
SwapBuffers(hdc)(gdi32.spec591)与wglSwapBuffers; - 职责:把双缓冲窗口的后缓冲内容呈现到屏幕(翻页/拷贝),由显示驱动(或软件实现)执行。
6.2 参数说明
| 参数 | 类型 | 含义 |
|---|---|---|
hdc |
HDC |
窗口 DC(必须能关联到窗口,且设置了像素格式) |
6.3 实现流程(逐步)
NtGdiSwapBuffers(hdc)
│
├─ [1] DC_LockDc(hdc)
│ └─ 失败 → EngSetLastError(ERROR_INVALID_HANDLE); return FALSE;
│
├─ [2] 取窗口对象:
│ UserEnterExclusive();
│ hWnd = UserGethWnd(hdc, &pWndObj);
│ UserLeave();
│ └─ if (!hWnd) → EngSetLastError(ERROR_INVALID_WINDOW_STYLE); goto Exit;
│
├─ [3] ppdev = pdc->ppdev;
│
├─ [4] if (pWndObj) pso = pWndObj->psoOwner;
│ else { EngSetLastError(ERROR_INVALID_PIXEL_FORMAT); goto Exit; }
│
├─ [5] 元文件设备:if (ppdev->flFlags & PDEV_META_DEVICE) { UNIMPLEMENTED; goto Exit; }
│
├─ [6] 驱动翻页:
│ if (ppdev->DriverFunctions.SwapBuffers)
│ Ret = SwapBuffers(pso, pWndObj);
│
└─ Exit: DC_UnlockDc(pdc); return Ret;
6.4 与另两个入口的差异(重要)
| 方面 | NtGdiDescribePixelFormat | NtGdiSetPixelFormat | NtGdiSwapBuffers |
|---|---|---|---|
| 是否查/校验 ipfdDevMax | 是 | 是 | 否(直接翻页,格式在设置时已定) |
| 是否用 WNDOBJ | 否 | 用于取 psoOwner | 用于取 psoOwner 并传给驱动 |
| 传给驱动的第 3 个参数 | --- | hWnd(HWND) |
pWndObj(WNDOBJ*) |
| DDI 原型 | (dhpdev, ipfd, cjpfd, ppfd) |
(pso, ipfd, hWnd) |
(pso, pwo) |
- SwapBuffers 不再校验
ipfdDevMax,因为翻页是"事后动作":格式必须在之前由NtGdiSetPixelFormat绑定过,且用户态wglSwapBuffers已经检查了dc_data->pixelformat(wgl.c L908--L912,未设置返回ERROR_INVALID_PIXEL_FORMAT); - DDI 把
WNDOBJ*传给驱动(而非 HWND):驱动可用WNDOBJ的pvConsumer/区域回调跟踪窗口变化(EngCreateWnd时传入的WNDOBJCHANGEPROC),翻页时知道窗口被覆盖/移动情况; - 若驱动未实现
SwapBuffers(当前 ReactOS 默认驱动即如此),本函数返回 FALSE------但不会 设置错误码(Ret默认 FALSE 直接返回)。用户态在驱动路径失败后会回退软件路径吗?不会------用户态wglSwapBuffers只在dc_data->icd_data非空时才调用DrvSwapBuffers(即GdiSwapBuffers),软件路径走sw_SwapBuffers,两条路径是互斥选择而非"先试后退"(见 7.5/7.6)。
6.5 使用方式与注意事项
- 应用在渲染完一帧后调用
SwapBuffers(hdc);对单缓冲格式调用是合法的(Windows 下一般直接返回成功/无操作,软件实现里if (!fb->gl_visual->DBflag) return TRUE;,swimpl.c L1499--L1500); wglSwapLayerBuffers/wglSwapMultipleBuffers在 opengl32 中为桩(wgl.c L920--L928),未走到本函数;- 与 DWM 合成的关系:
PFD_SUPPORT_COMPOSITION标志允许 OpenGL 内容进入合成器;ReactOS 无 DWM,此标志当前无实际作用。
7. 像素格式机制详解
7.1 PFD_* 标志与匹配规则总览
ChoosePixelFormat 的匹配本质是:应用给出"想要的格式"(PIXELFORMATDESCRIPTOR + PFD_ 标志),系统在设备支持的格式中选一个最接近的*。匹配维度:
| 维度 | 严格性 | 说明 |
|---|---|---|
iPixelType(RGBA / COLORINDEX) |
严格 | 必须一致(wgl.c L201--L205) |
PFD_DRAW_TO_BITMAP |
单向严格 | 应用要求可绘制到位图 → 候选必须支持(L208--L212) |
PFD_DRAW_TO_WINDOW |
单向严格 | 应用要求可绘制到窗口 → 候选必须支持(L215--L219) |
PFD_SUPPORT_OPENGL |
单向严格 | 应用要求 OpenGL → 候选必须支持(L222--L226) |
PFD_SUPPORT_GDI |
单向严格 | 应用要求 GDI 兼容 → 候选必须支持(L229--L233) |
PFD_DOUBLEBUFFER / _DONTCARE |
偏好 | 见 7.2 |
PFD_STEREO / _DONTCARE |
偏好 | 见 7.2 |
cColorBits |
偏好 | 优先级最高(越大越好,越接近越好) |
cAlphaBits |
偏好 | 次之 |
cDepthBits |
偏好 | 再次 |
cStencilBits |
偏好 | 再次 |
cAuxBuffers |
偏好 | 最低 |
PFD_GENERIC_FORMAT |
偏好 | 硬件加速优先:候选为通用(软件)格式而当前最佳为硬件格式 → 跳过(L346--L347) |
"偏好"维度的关键点(wgl.c L274--L341 注释):
cColorBits等为 0 时视为 DONTCARE(不参与比较)------Windows 驱动也如此,Serious Sam TSE等游戏依赖该行为;- 优先级顺序固定为
cColorBits > cAlphaBits > cDepthBits > cStencilBits > cAuxBuffers; - 匹配采用"逐步收紧"策略:先按最高优先级找候选,找到后再用次优先级筛选,依此类推。
7.2 ChoosePixelFormat 流程(opengl32/wgl.c L174--L356)
wglChoosePixelFormat(hdc, ppfd)
│
├─ count = wglDescribePixelFormat(hdc, 0, 0, NULL) // 总格式数 = ICD + 软件
│ └─ count == 0 → return 0
│
├─ 初始化 best:
│ best_format = 0
│ best.dwFlags = PFD_GENERIC_FORMAT(记录"目前最佳是软件格式")
│ best.cAlphaBits/cColorBits/cDepthBits/cStencilBits/cAuxBuffers = -1
│
├─ for i = 1 .. count:
│ ├─ wglDescribePixelFormat(hdc, i, sizeof(format), &format) // 逐个取描述
│ ├─ 严格过滤:iPixelType / DRAW_TO_BITMAP / DRAW_TO_WINDOW /
│ │ SUPPORT_OPENGL / SUPPORT_GDI 不匹配 → continue
│ ├─ 双缓冲偏好(无 DONTCARE 时):
│ │ · 候选的双缓冲位与请求相同且比当前最佳"更接近" → found
│ │ · 已找到过最佳后,双缓冲位不一致的候选 → continue
│ ├─ 立体偏好(逻辑同上,代码注释详述了 Windows 实测行为:
│ │ stereo 未设 → 优先非立体;stereo 已设 → 优先立体;DONTCARE → 忽略)
│ ├─ 数量维度(cColorBits→cAlphaBits→cDepthBits→cStencilBits→cAuxBuffers):
│ │ 若请求值非 0:
│ │ · 候选比当前 best 更接近请求(更大的值或更小的值)→ found
│ │ · 候选与 best 的该维度不同 → continue(该维度必须齐平才能继续)
│ ├─ found:(候选被认定为"更优")
│ │ · 若候选是软件格式而 best 已是硬件格式 → continue(不降级)
│ │ best_format = i; best = format;
│ │ bestDBuffer = format.dwFlags & PFD_DOUBLEBUFFER;
│ │ bestStereo = format.dwFlags & PFD_STEREO;
│ └─ continue
│
└─ return best_format // 0 = 无合适格式
gdi32 封装 (gdi32/misc/wingl.c L111--L121):ChoosePixelFormat 懒加载 opengl32(OpenGLEnable 解析 5 个 wgl 函数指针,全部成功才返回 TRUE),然后直接转发给 wglChoosePixelFormat。若 opengl32 加载失败返回 0。
7.3 SetPixelFormat 与 DC/窗口的绑定模型
"绑定"在系统中分三层记录:
① 用户态缓存(opengl32,wgl.c get_dc_data_ex L71--L86)
struct wgl_dc_data {
union { HWND hwnd; HDC hdc; } owner; // 标识:OBJ_DC→窗口句柄;OBJ_MEMDC→DC 句柄
DWORD flags; // WGL_DC_OBJ_DC
INT pixelformat; // ← 当前格式(wglGetPixelFormat 读它)
INT nb_icd_formats; // ICD 格式数(首次按 DC 缓存)
INT nb_sw_formats; // 软件格式数
struct ICD_Data* icd_data; // ICD 数据(NULL = 纯软件)
void* sw_data; // Mesa 软件帧缓冲
}
· get_dc_data_ex 用 GetObjectType 区分 OBJ_DC(→ WindowFromDC 取窗口)
与 OBJ_MEMDC(→ 直接以 DC 句柄为键);
· dc_data 挂在进程内链表 dc_data_list,按 owner 匹配,受 dc_data_cs 保护;
· pixelformat 在 wglSetPixelFormat 成功后写入(ICD 路径 L848;软件路径 L863)。
② 内核 WNDOBJ(win32k,engwindow.c)
· EngCreateWnd(..., iPixelFormat) 把像素格式记入 Clip->PixelFormat(L203),
由显示驱动在设置格式时(DrvSetPixelFormat 内)调用 EngCreateWnd 建立;
· wingl.c 通过 UserGethWnd → WNDOBJ 拿 psoOwner 表面。
③ 显示驱动私态(DDI 回调内)
· NtGdiSetPixelFormat → DriverFunctions.SetPixelFormat(pso, ipfd, hWnd):
驱动可据此关联其 OpenGL 硬件状态与窗口表面(如为窗口分配硬件后缓冲)。
关键设计 :内核 DC 对象不保存"当前像素格式" 。wingl.c 的三个入口都不读写 DC 内的格式字段(DC 中只有 ipfdDevMax 缓存)。格式的"权威记录"在用户态 opengl32(pixelformat)与驱动私态------这解释了为什么 GetPixelFormat 不需要系统调用。
"只能设置一次"的执行链:
gdi32.SetPixelFormat(hdc, i, pfd)
├─ GetPixelFormat(hdc) // opengl32.wglGetPixelFormat → 用户态缓存
│ └─ 已设置 → return (current == i) // 相同格式放行,不同失败
└─ wglSetPixelFormat(hdc, i, pfd)
├─ dc_data->pixelformat 已设置 → return (i == dc_data->pixelformat)
├─ i <= nb_icd_formats → icd_data->DrvSetPixelFormat(hdc, i)
│ └─ 成功 → dc_data->pixelformat = i
└─ 否则软件格式 → sw_SetPixelFormat(hdc, dc_data, i - nb_icd_formats)
└─ 成功 → dc_data->icd_data = NULL; dc_data->pixelformat = i
7.4 SwapBuffers 与驱动对接
翻页的完整链路:
应用 SwapBuffers(hdc)
→ gdi32.SwapBuffers(懒加载 opengl32)
→ wglSwapBuffers(hdc)(wgl.c L898--L918)
├─ dc_data 无效 → ERROR_INVALID_HANDLE
├─ !dc_data->pixelformat → ERROR_INVALID_PIXEL_FORMAT(未设置格式)
├─ dc_data->icd_data(ICD 路径)→ icd_data->DrvSwapBuffers(hdc)
│ └─ 当 win32k 接管时 DrvSwapBuffers == GdiSwapBuffers == NtGdiSwapBuffers
│ → wingl.c:DC_LockDc → UserGethWnd → psoOwner
│ → DriverFunctions.SwapBuffers(pso, pWndObj)(内核显示驱动翻页)
└─ 软件路径 → sw_SwapBuffers(hdc, dc_data)
→ swimpl.c L1492--L1527:若无双缓冲直接 TRUE;
否则把 Mesa 后缓冲经 SetDIBitsToDevice 拷到窗口 DC 完成"翻页"
(16bpp 用 BI_BITFIELDS + 5-6-5 掩码,COLORINDEX 用 DIB_PAL_COLORS)
驱动侧翻页语义 :DDI 的 DrvSwapBuffers(pso, pwo) 拿到窗口表面与 WNDOBJ,可执行:
- 硬件翻页(改变显示控制器读指针,PFD_SWAP_EXCHANGE 语义);
- 或后缓冲→可见区域拷贝(PFD_SWAP_COPY 语义);
- 同时依据 WNDOBJ 的可见区域(
EngCreateWnd维护的裁剪)只更新未遮挡部分。
7.5 软件实现(Mesa)路径
当显示驱动不支持 OpenGL(无 OPENGL_GETINFO escape)或用户强制时,opengl32 使用内置 Mesa 软件实现:
-
格式表 (swimpl.c L41--L200):按屏幕色深(
GetDeviceCaps(BITSPIXEL))选 32/24/16/8 四套pixel_formats_*静态表,每套含单缓冲/双缓冲 × RGBA/COLORINDEX × 有无 Alpha/深度 的组合,如:c#define SB_FLAGS (PFD_DRAW_TO_BITMAP | PFD_SUPPORT_GDI | PFD_SUPPORT_OPENGL | PFD_GENERIC_FORMAT) #define SB_FLAGS_WINDOW (SB_FLAGS | PFD_DRAW_TO_WINDOW) #define DB_FLAGS (PFD_DOUBLEBUFFER | PFD_SWAP_COPY | PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_GENERIC_FORMAT) // pixel_formats_32[] 示例:{flags, TYPE, cColorBits=32, cRedBits=8, cRedShift=16, ...} -
get_format(pf_index, &count)(L274--L313):按屏幕 bpp 选表;pf_index<=0或越界返回 NULL;未知 bpp 默认 32bpp; -
sw_DescribePixelFormat(L315--L357):从表填充PIXELFORMATDESCRIPTOR;nVersion=1、cStencilBits=STENCIL_BITS(8)、iLayerType=PFD_MAIN_PLANE、层掩码全 0; -
sw_SetPixelFormat(L359 起):HeapAlloc帧缓冲结构,gl_create_visual(Mesa 视觉,按 RGBA/Alpha/双缓冲/深度/模板/累积构建),gl_create_framebuffer(Mesa 帧缓冲,含前后缓冲分配)------从此该 DC 进入"软件 OpenGL"状态; -
sw_SwapBuffers(L1492--L1527):把后缓冲(fb->BackBuffer)用SetDIBitsToDevice画到窗口 DC; -
sw_CreateContext等:软件渲染上下文由 Mesa(dll/opengl/mesa)提供,wglGetProcAddress返回sw_GetProcAddress的表项。
格式索引分区 :wglDescribePixelFormat 返回 nb_icd_formats + nb_sw_formats;索引 1..nb_icd_formats 走 ICD(或 win32k),nb_icd_formats+1.. 减去偏移后走软件表(wgl.c L149--L168)。
7.6 ICD 加载与 win32k 覆盖(icdload.c)
IntGetIcdData(hdc)(icdload.c L61 起)决定每个 DC 使用哪个渲染后端:
IntGetIcdData(hdc)
├─ HKCU\Software\ReactOS\OpenGL 自定义 ICD 检查(仅一次,ReactOS 扩展):
│ · 值 "ReactOS Software Implementation" → 强制软件 → return NULL
│ · 其他名称 → 覆盖默认驱动(OGL_CD_CUSTOM_ICD)
├─ 默认路径:
│ · ExtEscape(hdc, QUERYESCSUPPORT, OPENGL_GETINFO) // 驱动是否声明 OpenGL
│ · ExtEscape(hdc, OPENGL_GETINFO, OPENGL_GETINFO_DRVNAME) // 取 ICD DLL 名/版本
│ └─ 驱动不支持 → return NULL(→ 纯软件)
├─ 按驱动名查找已加载列表 ICD_Data_List(命中直接复用)
├─ 注册表 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\<名>
│ → 读 DLL 名/版本/DriverVersion/Flags,LoadLibrary 加载 ICD DLL
├─ DrvValidateVersion 校验、DrvSetCallbackProcs 传回调(wglSetCurrentValue 等)
├─ DRV_LOAD 16 个 Drv* 导出(DrvCreateContext/DrvDescribePixelFormat/.../DrvSwapBuffers)
│
└─ ★ win32k 覆盖判定(icdload.c L329--L338):
if (GdiDescribePixelFormat(hdc, 0, 0, NULL) != 0) // win32k 能描述格式?
{
TRACE("Forwarding WGL calls to win32k!\n");
data->DrvDescribePixelFormat = GdiDescribePixelFormat;
data->DrvSetPixelFormat = GdiSetPixelFormat;
data->DrvSwapBuffers = GdiSwapBuffers;
}
// 即:只要内核显示驱动实现了 DrvDescribePixelFormat(NtGdiDescribePixelFormat 返回非 0),
// 像素格式的三种操作就全部转交 win32k/wingl.c,而不是 ICD DLL 自身的实现。
与 wingl.c 的闭环 :GdiDescribePixelFormat 直接是 NtGdiDescribePixelFormat 的系统调用(gdi32.spec 262 行),其返回值 pdc->ipfdDevMax(来自 IntGetipfdDevMax → 驱动 DriverFunctions.DescribePixelFormat)正是"驱动是否支持像素格式"的信号。因此 wingl.c 是否真正被使用,取决于显示驱动是否实现那三个 DDI 回调------目前 ReactOS 内置驱动(VGA 等)未实现(见 2.5),所以默认走 Mesa 软件渲染。
7.7 像素格式与 Direct3D/渲染器的关系(reactx)
win32ss/reactx/ 是内核侧 DirectX 对接层(dxg/ddraw/d3d、ntddraw、dxgthk、dxapi),与 wingl.c 属于平行机制:
| 机制 | 内核文件 | 面向 API | 格式描述 |
|---|---|---|---|
| OpenGL 像素格式 | wingl.c(本文件) | wgl*/GDI | PIXELFORMATDESCRIPTOR |
| DirectDraw 表面 | reactx/ntddraw/ddraw.c 等 | DirectDraw | DDPIXELFORMAT(FourCC、RGB 位域) |
| D3D 设备 | reactx/ntddraw/d3d.c、ntgdi/d3dkmt.c | D3D/D3DKMT | D3DFORMAT/DXGI_FORMAT |
共同点:最终都通过 ppdev->DriverFunctions(DrvGetDirectDrawInfo/DrvEnableDirectDraw 等,ntgdityp.h L628--L630)与内核显示驱动对接;区别是 OpenGL 的像素格式是"窗口级、一次性绑定",而 DirectDraw 表面是"对象级、可动态创建"。
8. 调用链(mermaid)
8.1 DescribePixelFormat / ChoosePixelFormat 调用链
#mermaid-svg-Hw4PaoiTafLBtkVL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Hw4PaoiTafLBtkVL .error-icon{fill:#552222;}#mermaid-svg-Hw4PaoiTafLBtkVL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Hw4PaoiTafLBtkVL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Hw4PaoiTafLBtkVL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Hw4PaoiTafLBtkVL .marker.cross{stroke:#333333;}#mermaid-svg-Hw4PaoiTafLBtkVL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Hw4PaoiTafLBtkVL p{margin:0;}#mermaid-svg-Hw4PaoiTafLBtkVL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Hw4PaoiTafLBtkVL .cluster-label text{fill:#333;}#mermaid-svg-Hw4PaoiTafLBtkVL .cluster-label span{color:#333;}#mermaid-svg-Hw4PaoiTafLBtkVL .cluster-label span p{background-color:transparent;}#mermaid-svg-Hw4PaoiTafLBtkVL .label text,#mermaid-svg-Hw4PaoiTafLBtkVL span{fill:#333;color:#333;}#mermaid-svg-Hw4PaoiTafLBtkVL .node rect,#mermaid-svg-Hw4PaoiTafLBtkVL .node circle,#mermaid-svg-Hw4PaoiTafLBtkVL .node ellipse,#mermaid-svg-Hw4PaoiTafLBtkVL .node polygon,#mermaid-svg-Hw4PaoiTafLBtkVL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Hw4PaoiTafLBtkVL .rough-node .label text,#mermaid-svg-Hw4PaoiTafLBtkVL .node .label text,#mermaid-svg-Hw4PaoiTafLBtkVL .image-shape .label,#mermaid-svg-Hw4PaoiTafLBtkVL .icon-shape .label{text-anchor:middle;}#mermaid-svg-Hw4PaoiTafLBtkVL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Hw4PaoiTafLBtkVL .rough-node .label,#mermaid-svg-Hw4PaoiTafLBtkVL .node .label,#mermaid-svg-Hw4PaoiTafLBtkVL .image-shape .label,#mermaid-svg-Hw4PaoiTafLBtkVL .icon-shape .label{text-align:center;}#mermaid-svg-Hw4PaoiTafLBtkVL .node.clickable{cursor:pointer;}#mermaid-svg-Hw4PaoiTafLBtkVL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Hw4PaoiTafLBtkVL .arrowheadPath{fill:#333333;}#mermaid-svg-Hw4PaoiTafLBtkVL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Hw4PaoiTafLBtkVL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Hw4PaoiTafLBtkVL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Hw4PaoiTafLBtkVL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Hw4PaoiTafLBtkVL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Hw4PaoiTafLBtkVL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Hw4PaoiTafLBtkVL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Hw4PaoiTafLBtkVL .cluster text{fill:#333;}#mermaid-svg-Hw4PaoiTafLBtkVL .cluster span{color:#333;}#mermaid-svg-Hw4PaoiTafLBtkVL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Hw4PaoiTafLBtkVL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Hw4PaoiTafLBtkVL rect.text{fill:none;stroke-width:0;}#mermaid-svg-Hw4PaoiTafLBtkVL .icon-shape,#mermaid-svg-Hw4PaoiTafLBtkVL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Hw4PaoiTafLBtkVL .icon-shape p,#mermaid-svg-Hw4PaoiTafLBtkVL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Hw4PaoiTafLBtkVL .icon-shape .label rect,#mermaid-svg-Hw4PaoiTafLBtkVL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Hw4PaoiTafLBtkVL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Hw4PaoiTafLBtkVL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Hw4PaoiTafLBtkVL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 计数/描述
win32k 接管时
DC_LockDc
否
是
应用: DescribePixelFormat / ChoosePixelFormat
gdi32.dll
DescribePixelFormat / ChoosePixelFormat
(wingl.c: 懒加载 opengl32)
opengl32.dll
wglDescribePixelFormat / wglChoosePixelFormat
(dll/opengl/opengl32/wgl.c)
ICD DLL 或 GdiDescribePixelFormat
NtGdiDescribePixelFormat
(wingl.c L41-121)
ipfdDevMax 已缓存?
IntGetipfdDevMax
(wingl.c L14-39)
DriverFunctions.DescribePixelFormat
(dhpdev, ipfd, cjpfd, &pfdSafe)
PIXELFORMATDESCRIPTOR
ProbeForWrite + RtlCopyMemory
拷贝回用户缓冲
8.2 SetPixelFormat 调用链
#mermaid-svg-3FnJEGH1T57JvaxG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3FnJEGH1T57JvaxG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3FnJEGH1T57JvaxG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3FnJEGH1T57JvaxG .error-icon{fill:#552222;}#mermaid-svg-3FnJEGH1T57JvaxG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3FnJEGH1T57JvaxG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3FnJEGH1T57JvaxG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3FnJEGH1T57JvaxG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3FnJEGH1T57JvaxG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3FnJEGH1T57JvaxG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3FnJEGH1T57JvaxG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3FnJEGH1T57JvaxG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3FnJEGH1T57JvaxG .marker.cross{stroke:#333333;}#mermaid-svg-3FnJEGH1T57JvaxG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3FnJEGH1T57JvaxG p{margin:0;}#mermaid-svg-3FnJEGH1T57JvaxG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3FnJEGH1T57JvaxG .cluster-label text{fill:#333;}#mermaid-svg-3FnJEGH1T57JvaxG .cluster-label span{color:#333;}#mermaid-svg-3FnJEGH1T57JvaxG .cluster-label span p{background-color:transparent;}#mermaid-svg-3FnJEGH1T57JvaxG .label text,#mermaid-svg-3FnJEGH1T57JvaxG span{fill:#333;color:#333;}#mermaid-svg-3FnJEGH1T57JvaxG .node rect,#mermaid-svg-3FnJEGH1T57JvaxG .node circle,#mermaid-svg-3FnJEGH1T57JvaxG .node ellipse,#mermaid-svg-3FnJEGH1T57JvaxG .node polygon,#mermaid-svg-3FnJEGH1T57JvaxG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3FnJEGH1T57JvaxG .rough-node .label text,#mermaid-svg-3FnJEGH1T57JvaxG .node .label text,#mermaid-svg-3FnJEGH1T57JvaxG .image-shape .label,#mermaid-svg-3FnJEGH1T57JvaxG .icon-shape .label{text-anchor:middle;}#mermaid-svg-3FnJEGH1T57JvaxG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3FnJEGH1T57JvaxG .rough-node .label,#mermaid-svg-3FnJEGH1T57JvaxG .node .label,#mermaid-svg-3FnJEGH1T57JvaxG .image-shape .label,#mermaid-svg-3FnJEGH1T57JvaxG .icon-shape .label{text-align:center;}#mermaid-svg-3FnJEGH1T57JvaxG .node.clickable{cursor:pointer;}#mermaid-svg-3FnJEGH1T57JvaxG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3FnJEGH1T57JvaxG .arrowheadPath{fill:#333333;}#mermaid-svg-3FnJEGH1T57JvaxG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3FnJEGH1T57JvaxG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3FnJEGH1T57JvaxG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FnJEGH1T57JvaxG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3FnJEGH1T57JvaxG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FnJEGH1T57JvaxG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3FnJEGH1T57JvaxG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3FnJEGH1T57JvaxG .cluster text{fill:#333;}#mermaid-svg-3FnJEGH1T57JvaxG .cluster span{color:#333;}#mermaid-svg-3FnJEGH1T57JvaxG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3FnJEGH1T57JvaxG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3FnJEGH1T57JvaxG rect.text{fill:none;stroke-width:0;}#mermaid-svg-3FnJEGH1T57JvaxG .icon-shape,#mermaid-svg-3FnJEGH1T57JvaxG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3FnJEGH1T57JvaxG .icon-shape p,#mermaid-svg-3FnJEGH1T57JvaxG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3FnJEGH1T57JvaxG .icon-shape .label rect,#mermaid-svg-3FnJEGH1T57JvaxG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3FnJEGH1T57JvaxG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3FnJEGH1T57JvaxG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3FnJEGH1T57JvaxG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
ICD 路径
否
是
软件路径
应用: SetPixelFormat(hdc, i, pfd)
gdi32.SetPixelFormat
先 GetPixelFormat 检查'只能设一次'
wglSetPixelFormat
(wgl.c L813-871)
dc_data->pixelformat 已设?
return (i == 已设格式)
i <= nb_icd_formats ?
DrvSetPixelFormat(hdc, i)
→ GdiSetPixelFormat
NtGdiSetPixelFormat
(wingl.c L124-196)
DC_LockDc → 范围校验
UserGethWnd(hdc,&pWndObj)
(ntuser/windc.c L996)
窗口/WNDOBJ 有效?
ERROR_INVALID_WINDOW_STYLE
或 ERROR_INVALID_PIXEL_FORMAT
pso = pWndObj->psoOwner
DriverFunctions.SetPixelFormat(pso, ipfd, hWnd)
(驱动记录格式,可调 EngCreateWnd)
sw_SetPixelFormat(hdc, dc_data, i - nb_icd_formats)
(Mesa 帧缓冲分配)
8.3 SwapBuffers 调用链
#mermaid-svg-J0PsDP92prQIs5iN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-J0PsDP92prQIs5iN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J0PsDP92prQIs5iN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J0PsDP92prQIs5iN .error-icon{fill:#552222;}#mermaid-svg-J0PsDP92prQIs5iN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J0PsDP92prQIs5iN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J0PsDP92prQIs5iN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J0PsDP92prQIs5iN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J0PsDP92prQIs5iN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J0PsDP92prQIs5iN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J0PsDP92prQIs5iN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J0PsDP92prQIs5iN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J0PsDP92prQIs5iN .marker.cross{stroke:#333333;}#mermaid-svg-J0PsDP92prQIs5iN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J0PsDP92prQIs5iN p{margin:0;}#mermaid-svg-J0PsDP92prQIs5iN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-J0PsDP92prQIs5iN .cluster-label text{fill:#333;}#mermaid-svg-J0PsDP92prQIs5iN .cluster-label span{color:#333;}#mermaid-svg-J0PsDP92prQIs5iN .cluster-label span p{background-color:transparent;}#mermaid-svg-J0PsDP92prQIs5iN .label text,#mermaid-svg-J0PsDP92prQIs5iN span{fill:#333;color:#333;}#mermaid-svg-J0PsDP92prQIs5iN .node rect,#mermaid-svg-J0PsDP92prQIs5iN .node circle,#mermaid-svg-J0PsDP92prQIs5iN .node ellipse,#mermaid-svg-J0PsDP92prQIs5iN .node polygon,#mermaid-svg-J0PsDP92prQIs5iN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J0PsDP92prQIs5iN .rough-node .label text,#mermaid-svg-J0PsDP92prQIs5iN .node .label text,#mermaid-svg-J0PsDP92prQIs5iN .image-shape .label,#mermaid-svg-J0PsDP92prQIs5iN .icon-shape .label{text-anchor:middle;}#mermaid-svg-J0PsDP92prQIs5iN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J0PsDP92prQIs5iN .rough-node .label,#mermaid-svg-J0PsDP92prQIs5iN .node .label,#mermaid-svg-J0PsDP92prQIs5iN .image-shape .label,#mermaid-svg-J0PsDP92prQIs5iN .icon-shape .label{text-align:center;}#mermaid-svg-J0PsDP92prQIs5iN .node.clickable{cursor:pointer;}#mermaid-svg-J0PsDP92prQIs5iN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J0PsDP92prQIs5iN .arrowheadPath{fill:#333333;}#mermaid-svg-J0PsDP92prQIs5iN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J0PsDP92prQIs5iN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J0PsDP92prQIs5iN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J0PsDP92prQIs5iN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J0PsDP92prQIs5iN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J0PsDP92prQIs5iN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J0PsDP92prQIs5iN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J0PsDP92prQIs5iN .cluster text{fill:#333;}#mermaid-svg-J0PsDP92prQIs5iN .cluster span{color:#333;}#mermaid-svg-J0PsDP92prQIs5iN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-J0PsDP92prQIs5iN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J0PsDP92prQIs5iN rect.text{fill:none;stroke-width:0;}#mermaid-svg-J0PsDP92prQIs5iN .icon-shape,#mermaid-svg-J0PsDP92prQIs5iN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J0PsDP92prQIs5iN .icon-shape p,#mermaid-svg-J0PsDP92prQIs5iN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J0PsDP92prQIs5iN .icon-shape .label rect,#mermaid-svg-J0PsDP92prQIs5iN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J0PsDP92prQIs5iN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J0PsDP92prQIs5iN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J0PsDP92prQIs5iN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
ICD/win32k 路径
软件路径
应用: SwapBuffers(hdc)
gdi32.SwapBuffers(懒加载 opengl32)
wglSwapBuffers
(wgl.c L898-918)
dc_data 有效且 pixelformat 已设?
ERROR_INVALID_HANDLE
或 ERROR_INVALID_PIXEL_FORMAT
icd_data 非空?
DrvSwapBuffers(hdc)
→ GdiSwapBuffers
NtGdiSwapBuffers
(wingl.c L198-254)
DC_LockDc → UserGethWnd
pso = pWndObj->psoOwner
DriverFunctions.SwapBuffers(pso, pWndObj)
(硬件翻页 / 拷贝)
sw_SwapBuffers(hdc, dc_data)
(swimpl.c L1492-1527)
SetDIBitsToDevice 把 Mesa 后缓冲
拷贝到窗口 DC
8.4 像素格式生命周期全景
#mermaid-svg-ptPI95i5rPuGo7WD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ptPI95i5rPuGo7WD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ptPI95i5rPuGo7WD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ptPI95i5rPuGo7WD .error-icon{fill:#552222;}#mermaid-svg-ptPI95i5rPuGo7WD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ptPI95i5rPuGo7WD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ptPI95i5rPuGo7WD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ptPI95i5rPuGo7WD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ptPI95i5rPuGo7WD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ptPI95i5rPuGo7WD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ptPI95i5rPuGo7WD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ptPI95i5rPuGo7WD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ptPI95i5rPuGo7WD .marker.cross{stroke:#333333;}#mermaid-svg-ptPI95i5rPuGo7WD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ptPI95i5rPuGo7WD p{margin:0;}#mermaid-svg-ptPI95i5rPuGo7WD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ptPI95i5rPuGo7WD .cluster-label text{fill:#333;}#mermaid-svg-ptPI95i5rPuGo7WD .cluster-label span{color:#333;}#mermaid-svg-ptPI95i5rPuGo7WD .cluster-label span p{background-color:transparent;}#mermaid-svg-ptPI95i5rPuGo7WD .label text,#mermaid-svg-ptPI95i5rPuGo7WD span{fill:#333;color:#333;}#mermaid-svg-ptPI95i5rPuGo7WD .node rect,#mermaid-svg-ptPI95i5rPuGo7WD .node circle,#mermaid-svg-ptPI95i5rPuGo7WD .node ellipse,#mermaid-svg-ptPI95i5rPuGo7WD .node polygon,#mermaid-svg-ptPI95i5rPuGo7WD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ptPI95i5rPuGo7WD .rough-node .label text,#mermaid-svg-ptPI95i5rPuGo7WD .node .label text,#mermaid-svg-ptPI95i5rPuGo7WD .image-shape .label,#mermaid-svg-ptPI95i5rPuGo7WD .icon-shape .label{text-anchor:middle;}#mermaid-svg-ptPI95i5rPuGo7WD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ptPI95i5rPuGo7WD .rough-node .label,#mermaid-svg-ptPI95i5rPuGo7WD .node .label,#mermaid-svg-ptPI95i5rPuGo7WD .image-shape .label,#mermaid-svg-ptPI95i5rPuGo7WD .icon-shape .label{text-align:center;}#mermaid-svg-ptPI95i5rPuGo7WD .node.clickable{cursor:pointer;}#mermaid-svg-ptPI95i5rPuGo7WD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ptPI95i5rPuGo7WD .arrowheadPath{fill:#333333;}#mermaid-svg-ptPI95i5rPuGo7WD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ptPI95i5rPuGo7WD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ptPI95i5rPuGo7WD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ptPI95i5rPuGo7WD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ptPI95i5rPuGo7WD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ptPI95i5rPuGo7WD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ptPI95i5rPuGo7WD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ptPI95i5rPuGo7WD .cluster text{fill:#333;}#mermaid-svg-ptPI95i5rPuGo7WD .cluster span{color:#333;}#mermaid-svg-ptPI95i5rPuGo7WD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ptPI95i5rPuGo7WD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ptPI95i5rPuGo7WD rect.text{fill:none;stroke-width:0;}#mermaid-svg-ptPI95i5rPuGo7WD .icon-shape,#mermaid-svg-ptPI95i5rPuGo7WD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ptPI95i5rPuGo7WD .icon-shape p,#mermaid-svg-ptPI95i5rPuGo7WD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ptPI95i5rPuGo7WD .icon-shape .label rect,#mermaid-svg-ptPI95i5rPuGo7WD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ptPI95i5rPuGo7WD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ptPI95i5rPuGo7WD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ptPI95i5rPuGo7WD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 内核
用户态
- ChoosePixelFormat
- SetPixelFormat
- wglCreateContext/MakeCurrent
- glXxx 渲染 + SwapBuffers
DescribePixelFormat 计数/描述
GdiSetPixelFormat
GdiSwapBuffers
应用
opengl32: 枚举 ICD+软件格式
按 PFD_* 匹配选最佳
opengl32: 缓存 pixelformat
→ win32k/驱动 绑定窗口
opengl32: 按格式创建上下文
校验与 DC 格式一致
opengl32: 软件(Mesa)渲染
或 ICD 渲染 + 翻页
wingl.c
NtGdiDescribePixelFormat /
NtGdiSetPixelFormat /
NtGdiSwapBuffers
PDEVOBJ.DriverFunctions
DrvDescribePixelFormat /
DrvSetPixelFormat /
DrvSwapBuffers
WNDOBJ
(psoOwner, PixelFormat)
9. 测试与验证
9.1 winetest(opengl32)
源码:opengl.c(file:///d:/reactos/modules/rostests/winetests/opengl32/opengl.c)(modules/rostests/winetests/opengl32/)。相关测试点:
| 测试函数 | 覆盖内容 | 与 wingl.c 的关系 |
|---|---|---|
test_choosepixelformat |
用各种 PIXELFORMATDESCRIPTOR(含 DONTCARE 组合、非法 iPixelType=32/33/15、cColorBits/cAlphaBits/cStencilBits/cAuxBuffers 变化)调 ChoosePixelFormat,期望都成功;iPixelType 非法时返回 RGBA 格式 |
间接走 NtGdiDescribePixelFormat(数量 + 描述) |
test_pfd |
创建窗口 → GetDC → ChoosePixelFormat → DescribePixelFormat 验证返回格式 |
直接覆盖 NtGdiDescribePixelFormat 的数量/描述两条路径 |
test_setpixelformat |
屏幕 DC 上 ChoosePixelFormat 应成功、SetPixelFormat 允许/禁止;窗口 DC 设置后重复设置相同格式成功、其他格式失败 (DescribePixelFormat 枚举后逐个试) |
覆盖 NtGdiSetPixelFormat 的"只能设一次"与范围校验(主要在校验发生在用户态缓存的前提下) |
test_pbuffers |
Pbuffer(离屏)格式的创建与 GetPixelFormat 行为 |
间接(经 opengl32 用户态缓存) |
winetest 的 test_setpixelformat 还验证了一个 Windows 兼容行为:对已设置格式的 DC,SetPixelFormat 只有传入相同索引才成功 (ok(res, ...) / ok(!res, ...),opengl.c L412--L417)------该行为在 ReactOS 中由 gdi32/opengl32 的用户态缓存保证,内核入口只做索引范围校验。
9.2 驱动侧验证途径
由于三个 DDI 回调是可选函数,可用以下方式验证 wingl.c 的驱动路径:
- 在显示驱动(如 VGA)的
FuncList中启用#if 0块内的INDEX_DescribePixelFormat/INDEX_DrvSetPixelFormat/INDEX_DrvSwapBuffers(enable.c L31--L55),实现三个回调并注册; - 驱动启用后,
NtGdiDescribePixelFormat返回非 0 → opengl32icdload.c打印Forwarding WGL calls to win32k!,像素格式操作全部转入 wingl.c; NtGdiSetPixelFormat开头的DPRINT1("Setting pixel format from win32k!\n")会在调试器/串口出现,用于确认走了内核路径;- 观察
IntGetipfdDevMax的缓存行为:首次调用后pdc->ipfdDevMax非 0,后续不再询问驱动。
9.3 已知待完善点(源码注释直接标注)
NtGdiDescribePixelFormatL69:/* EngSetLastError ? */------IntGetipfdDevMax失败时未设置错误码;NtGdiDescribePixelFormat拷贝阶段异常时Ret不变(见 4.4 第 3 点);NtGdiSetPixelFormat对IntGetipfdDevMax的返回值未检查,依赖后续范围校验兜底;- 三个函数对
PDEV_META_DEVICE均打UNIMPLEMENTED(元文件设备的像素格式未实现)。
10. 与《分析_31》及分册关系
| 项 | 说明 |
|---|---|
| 《分析_31》2.2 表(其余行)wingl.c | 本分析展开(31 个 ntgdi 文件之一,3 个 NtGdi* 入口) |
| 《分析_31》7.2 表 | OpenGL 行:wingl.c → SetPixelFormat/SwapBuffers/DescribePixelFormat,与 D3D/驱动对接 |
| DC 对象 | dc.h(本分析 2.4 节)、dclife.c(DC 创建时 ipfdDevMax=0,L344;《分析_49》) |
| 窗口/WNDOBJ | ntuser/windc.c UserGethWnd(L996)、gdi/eng/engwindow.c EngCreateWnd(L143,记录 PixelFormat) |
| 显示驱动 DDI | pdevobj.h(PDEVOBJ/DriverFunctions/PDEV_META_DEVICE)、ntgdityp.h(DRIVER_FUNCTIONS)、psdk/winddi.h(三个 Drv* 原型与 INDEX 54/55/56) |
| 用户态封装 | gdi32/misc/wingl.c(ChoosePixelFormat 等 5 个转发函数)、gdi32.spec(GdiDescribePixelFormat=262 等映射) |
| opengl32 分发 | dll/opengl/opengl32/wgl.c(wgl* 实现与格式匹配)、icdload.c(ICD 加载与 win32k 覆盖)、swimpl.c(Mesa 软件实现) |
| D3D/OpenGL 内核对接 | win32ss/reactx/(dxg/ntddraw/dxgthk/dxapi;见本分析 7.7) |
| 元文件 | PDEV_META_DEVICE 相关(metafile.c,《分析_51》) |
10.1 与 dclife.c(《分析_49》)的衔接
DC 对象在创建时(dclife.c 的 DC_vInitDc)把 pdc->ipfdDevMax 置 0(L344),wingl.c 首次使用时惰性填充。这意味着像素格式缓存随 DC 生命周期走:DC 删除后缓存随之消失,新 DC 重新探测------符合 Windows"像素格式与 DC/窗口绑定"的语义。
10.2 与 gdi32.spec 的系统调用映射
| gdi32.spec 行 | 导出 | 映射 |
|---|---|---|
| 25 | ChoosePixelFormat(ptr ptr) |
gdi32/misc/wingl.c 实现(转发 opengl32) |
| 145 | DescribePixelFormat(long long long ptr) |
同上(转发 wglDescribePixelFormat) |
| 262 | GdiDescribePixelFormat(ptr long long ptr) |
直连 NtGdiDescribePixelFormat(wingl.c L41) |
| 322 | GdiSetPixelFormat(ptr long) |
直连 NtGdiSetPixelFormat(wingl.c L124) |
| 326 | GdiSwapBuffers(ptr) |
直连 NtGdiSwapBuffers(wingl.c L198) |
| 415 | GetPixelFormat(long) |
转发 opengl32(用户态缓存,无内核入口) |
| 564 | SetPixelFormat(long long ptr) |
转发 opengl32 |
| 591 | SwapBuffers(long) |
转发 opengl32 |
Gdi* 三个导出是"直通系统调用"(直接进内核),而普通 DescribePixelFormat/SetPixelFormat/SwapBuffers/ChoosePixelFormat/GetPixelFormat 走 opengl32 分发------这正是 icdload.c 能拿 GdiDescribePixelFormat 做覆盖判定的原因(无需经过 wgl 层,直接问内核)。
11. 源码索引
| 文件 | 关键内容 |
|---|---|
| wingl.c(file:///d:/reactos/win32ss/gdi/ntgdi/wingl.c) | IntGetipfdDevMax(L14--39)、NtGdiDescribePixelFormat(L41--121)、NtGdiSetPixelFormat(L124--196)、NtGdiSwapBuffers(L198--254) |
| dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) | DC 结构(ipfdDevMax L133、ppdev/dhpdev)、DCLEVEL(L49--89)、DC_LockDc/DC_UnlockDc(L218--244) |
| dclife.c(file:///d:/reactos/win32ss/gdi/ntgdi/dclife.c) | DC 初始化:pdc->ipfdDevMax = 0(L344) |
| pdevobj.h(file:///d:/reactos/win32ss/gdi/eng/pdevobj.h) | PDEVOBJ 结构(DriverFunctions 联合 L135--140、dhpdev L120)、PDEV_META_DEVICE=0x00020000(L20) |
| ntgdityp.h(file:///d:/reactos/win32ss/include/ntgdityp.h) | DRIVER_FUNCTIONS:SetPixelFormat/DescribePixelFormat/SwapBuffers 成员(L622--624) |
| winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h) | DDI 原型 FN_DrvDescribePixelFormat(L3485--92)、FN_DrvSetPixelFormat(L4019--25)、FN_DrvSwapBuffers(L4144--49)、INDEX 54/55/56(L470--72) |
| wingdi.h(file:///d:/reactos/sdk/include/psdk/wingdi.h) | PIXELFORMATDESCRIPTOR(L3015--42)、EMRPIXELFORMAT(L3044--48)、PFD_* 标志(L296--317)、API 原型(L3526/3744/4661/4694) |
| windc.c(file:///d:/reactos/win32ss/user/ntuser/windc.c) | UserGethWnd(L996--1014):HDC → HWND + WNDOBJ |
| engwindow.c(file:///d:/reactos/win32ss/gdi/eng/engwindow.c) | EngCreateWnd(L143 起):创建 WNDOBJ,psoOwner(L191)、PixelFormat(L203) |
| wingl.c(file:///d:/reactos/win32ss/gdi/gdi32/misc/wingl.c) | gdi32 用户态封装:懒加载 opengl32,5 个转发函数(OpenGLEnable L66--104) |
| gdi32.spec(file:///d:/reactos/win32ss/gdi/gdi32/gdi32.spec) | GdiDescribePixelFormat=262、GdiSetPixelFormat=322、GdiSwapBuffers=326 直连系统调用 |
| wgl.c(file:///d:/reactos/dll/opengl/opengl32/wgl.c) | get_dc_data_ex(L22--88)、wglDescribePixelFormat(L123--172)、wglChoosePixelFormat(L174--356)、wglGetPixelFormat(L615--629)、wglSetPixelFormat(L813--871)、wglSwapBuffers(L898--918) |
| icdload.c(file:///d:/reactos/dll/opengl/opengl32/icdload.c) | IntGetIcdData(L61 起)、win32k 覆盖判定(L329--338) |
| swimpl.c(file:///d:/reactos/dll/opengl/opengl32/swimpl.c) | 软件格式表(L41--200)、get_format(L274--313)、sw_DescribePixelFormat(L315--357)、sw_SetPixelFormat(L359 起)、sw_SwapBuffers(L1492--1527) |
| enable.c(file:///d:/reactos/win32ss/drivers/displays/vga/main/enable.c) | VGA 驱动 FuncList:像素格式回调在 #if 0 中(L31--55),当前未实现 |
| opengl.c(file:///d:/reactos/modules/rostests/winetests/opengl32/opengl.c) | winetest:test_choosepixelformat(L248)、test_setpixelformat(L367)、test_pbuffers(L123) |
12. 总结
wingl.c 是 win32k 中体量最小(255 行)但地位特殊的模块之一:
- 接口标准化 :它把"像素格式"这一 OpenGL 窗口概念以三个系统调用(
NtGdiDescribePixelFormat/NtGdiSetPixelFormat/NtGdiSwapBuffers)暴露给用户态,是 gdi32 → opengl32 → 显示驱动 DDI 之间唯一的内核收口; - 惰性缓存 :
IntGetipfdDevMax把"设备支持的格式总数"缓存进 DC(ipfdDevMax),避免重复陷入驱动; - 窗口表面桥接 :
SetPixelFormat/SwapBuffers通过UserGethWnd拿到 WNDOBJ 与psoOwner表面,把 OpenGL 操作落实到窗口的底层表面,并将 HWND/WNDOBJ 交给驱动回调; - 可选 DDI 适配 :三个
DriverFunctions.*回调均为可选(if (fn) fn(...)),驱动不实现时函数安静返回(0/FALSE),由 opengl32 回退到 Mesa 软件渲染------这是 ReactOS 目前默认的运行方式; - 覆盖判定闭环 :icdload.c 用
GdiDescribePixelFormat(hdc,0,0,NULL)探测"win32k 是否接管",而该值正是本文件IntGetipfdDevMax的结果,形成"驱动能力 → wingl.c → opengl32 分发策略"的闭环。
理解 wingl.c 的关键在于认清它的边界:它不渲染、不创建上下文、不保存当前格式,只做"格式枚举、格式绑定、缓冲交换"三件设备相关的中转------真正的 OpenGL 世界(Mesa/ICD/上下文)全部在用户态。
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/wingl.c及关联模块(dc.h、pdevobj.h、ntgdityp.h、winddi.h、wingdi.h、gdi32/misc/wingl.c、dll/opengl/opengl32/{wgl,icdload,swimpl}.c、win32ss/reactx)分析(2026 年 8 月)