ReactOS 图形系统分析(52):OpenGL 支持 — wingl.c

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)

  1. 像素格式是"设备资源"而非"应用资源" :像素格式描述了帧缓冲的布局(颜色位深、Alpha、深度/模板/累积缓冲、双缓冲、立体等)。这些能力由显示驱动决定,只有驱动知道自己的硬件/模式支持什么;
  2. 设备相关接口必须在内核侧分发 :Windows 的显示驱动接口(DDI)运行在内核态(win32k 中),DrvDescribePixelFormat / DrvSetPixelFormat / DrvSwapBuffers 是 DDK 定义的显示驱动可选回调(见 winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h))。用户态无法直接调用内核态驱动的回调,必须由 win32k 的 NtGdi* 系统调用做中转;
  3. 格式选择是"一次性绑定":Windows 规定一个窗口/DC 只能设置一次像素格式(再次设置只能设置相同的格式)。这个"只能设置一次"的约束需要在格式真正落到设备之前由驱动/内核侧记录,保证后续 OpenGL 上下文(HGLRC)与窗口的兼容性校验有据可依;
  4. 窗口表面是载体 :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->DriverFunctionsDRIVER_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..nn 同时也是 DescribePixelFormat(hdc, 0, 0, NULL) 的返回值;
  • wingl.c 中 IntGetipfdDevMaxipfd=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 通过它访问 DriverFunctionsflFlags
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->DriverFunctionsPDEVOBJ 内嵌联合,见 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 实现流程

  1. PPDEVOBJ ppdev = pdc->ppdev; 取设备对象,INT Ret = 0; 默认失败;
  2. 元文件设备短路if (ppdev->flFlags & PDEV_META_DEVICE) return 0;------元文件 DC(metafile)没有真实设备像素格式,直接返回 0;
  3. 询问驱动格式总数 :若 ppdev->DriverFunctions.DescribePixelFormat 非空,则以 (dhpdev, ipfd=1, cjpfd=0, ppfd=NULL) 调用。注意:
    • ipfd=1:取第一个格式的索引做探测(Windows DDI 约定:iPixelFormat 非 0 时返回设备支持的格式总数);
    • cjpfd=0ppfd=NULL:不要描述符内容,只取数量;
  4. 缓存if (Ret) pdc->ipfdDevMax = Ret;------只有非 0 结果才缓存(0 表示"不支持",不污染缓存,下次再试);
  5. 返回 Ret

3.4 使用方式与注意事项

  • 惰性求值ipfdDevMax 在 DC 创建时(dclife.c L344)被置 0,首次需要时由本函数填充。wingl.c 中三处使用:
    • NtGdiDescribePixelFormat L65--L72:if (!pdc->ipfdDevMax) { if (!IntGetipfdDevMax(pdc)) goto Exit; }------失败直接退出(此时 Ret=0 且未设置错误码 ,源码注释 /* EngSetLastError ? */ 表明这是个待完善点);
    • NtGdiSetPixelFormat L146--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.spec 145,实现在 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 关键实现细节

  1. 用户缓冲安全 :驱动回调填充的是栈上的 pfdSafe ,之后才用 _SEH2_TRY + ProbeForWrite 把数据拷回用户指针。这样即使驱动实现有缺陷也不会直接写到用户地址;用户指针非法时异常被 EXCEPTION_EXECUTE_HANDLER 捕获并转成 NT 错误码(SetLastNtError);
  2. 截断拷贝min(cjpfd, sizeof(PIXELFORMATDESCRIPTOR))------若应用给的缓冲小于 40 字节,只拷贝其声明的字节数(宽容处理,符合 Windows 行为);
  3. 异常后的返回值 :若拷贝阶段异常,Ret 保持不变(仍为驱动返回的格式数)------即"返回了格式数但缓冲没写成功"。这是当前实现的取舍(探测在拷贝前已挡住绝大多数非法指针);
  4. !ppfd 分支提前返回ipfd 范围校验只对"要填描述符"的调用生效,这与 Windows 文档一致(DescribePixelFormat(hdc, 0, 0, NULL) 是合法的数量查询);
  5. 错误码清单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.spec 564)与 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 关键实现细节

  1. USER 子系统互斥UserEnterExclusive()/UserLeave() 包裹 UserGethWnd------窗口对象(PWND)、WNDOBJ 由 USER 管理,GDI 线程访问它们必须串行化,避免与窗口销毁/移动等并发操作竞争;
  2. 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;
  3. WNDOBJ 与 psoOwner :WNDOBJ 由 EngCreateWnd(pso, hWnd, pfn, fl, iPixelFormat) 创建(engwindow.c L143 起),其中 psoOwner 记录窗口所属表面(engwindow.c L191),Clip->PixelFormat = iPixelFormat(L203)记录像素格式------这是驱动侧记录"窗口绑定了哪个像素格式"的标准途径
  4. "只能设置一次"由谁保证 :内核入口本身 检查"是否已设置";用户态 gdi32 的 SetPixelFormat 先调 GetPixelFormat(opengl32 用户态缓存),若已设置则只允许相同索引(gdi32/misc/wingl.c L169--L171);wglSetPixelFormat 同样在用户态缓存检查(wgl.c L835--L839)。winetest test_setpixelformat 专门验证了"重复设置相同格式成功、不同格式失败";
  5. 错误码语义
    • 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.spec 591)与 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):驱动可用 WNDOBJpvConsumer/区域回调跟踪窗口变化(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):从表填充 PIXELFORMATDESCRIPTORnVersion=1cStencilBits=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->DriverFunctionsDrvGetDirectDrawInfo/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;} 内核
用户态

  1. ChoosePixelFormat
  2. SetPixelFormat
  3. wglCreateContext/MakeCurrent
  4. 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 创建窗口 → GetDCChoosePixelFormatDescribePixelFormat 验证返回格式 直接覆盖 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 的驱动路径:

  1. 在显示驱动(如 VGA)的 FuncList 中启用 #if 0 块内的 INDEX_DescribePixelFormat/INDEX_DrvSetPixelFormat/INDEX_DrvSwapBuffers(enable.c L31--L55),实现三个回调并注册;
  2. 驱动启用后,NtGdiDescribePixelFormat 返回非 0 → opengl32 icdload.c 打印 Forwarding WGL calls to win32k!,像素格式操作全部转入 wingl.c;
  3. NtGdiSetPixelFormat 开头的 DPRINT1("Setting pixel format from win32k!\n") 会在调试器/串口出现,用于确认走了内核路径;
  4. 观察 IntGetipfdDevMax 的缓存行为:首次调用后 pdc->ipfdDevMax 非 0,后续不再询问驱动。

9.3 已知待完善点(源码注释直接标注)

  • NtGdiDescribePixelFormat L69:/* EngSetLastError ? */------IntGetipfdDevMax 失败时未设置错误码;
  • NtGdiDescribePixelFormat 拷贝阶段异常时 Ret 不变(见 4.4 第 3 点);
  • NtGdiSetPixelFormatIntGetipfdDevMax 的返回值未检查,依赖后续范围校验兜底;
  • 三个函数对 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_FUNCTIONSSetPixelFormat/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 行)但地位特殊的模块之一:

  1. 接口标准化 :它把"像素格式"这一 OpenGL 窗口概念以三个系统调用(NtGdiDescribePixelFormat/NtGdiSetPixelFormat/NtGdiSwapBuffers)暴露给用户态,是 gdi32 → opengl32 → 显示驱动 DDI 之间唯一的内核收口
  2. 惰性缓存IntGetipfdDevMax 把"设备支持的格式总数"缓存进 DC(ipfdDevMax),避免重复陷入驱动;
  3. 窗口表面桥接SetPixelFormat/SwapBuffers 通过 UserGethWnd 拿到 WNDOBJ 与 psoOwner 表面,把 OpenGL 操作落实到窗口的底层表面,并将 HWND/WNDOBJ 交给驱动回调;
  4. 可选 DDI 适配 :三个 DriverFunctions.* 回调均为可选(if (fn) fn(...)),驱动不实现时函数安静返回(0/FALSE),由 opengl32 回退到 Mesa 软件渲染------这是 ReactOS 目前默认的运行方式;
  5. 覆盖判定闭环 :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 月)

相关推荐
caimouse2 小时前
ReactOS 图形系统分析(47):GDI 批处理 — gdibatch.c
c语言·开发语言
qq21084629532 小时前
在python中什么是 self 和 cls?
开发语言·前端·python
杨充2 小时前
05.多用组合和少继承
开发语言·bash
傻啦嘿哟3 小时前
王者荣耀爬虫:爬取全英雄皮肤数据,用Python做可视化分析
开发语言·爬虫·python
AI直播技术杂谈3 小时前
直播画面突然模糊了,排查了三个小时
开发语言·php
菜冻鱼3 小时前
Python-pytorch-数据加载
开发语言·人工智能·pytorch·python·深度学习·机器学习
heimeiyingwang3 小时前
【架构实战】可观测性三支柱实战:Metrics、Logging、Tracing 如何统一落地
开发语言·架构·php
caimouse3 小时前
ReactOS 图形系统分析(53):系统库存对象 — stockobj.c
c语言·开发语言·spring
ZJU_统一阿萨姆4 小时前
【推理优化进阶】通信关键路径:NCCL、RDMA 与计算通信重叠
开发语言·人工智能·语言模型·架构·系统架构