ReactOS 图形系统分析(17):区域填充 --- EngPaint / IntEngPaint(paint.c)
1. 概述
paint.c 位于 win32ss/gdi/eng/paint.c,实现 GDI 引擎的区域填充(Paint/区域绘制) 。它向上服务内核 GDI 的 FillRgn/PaintRgn 请求,向下把填充动作统一转化为一次无源的 BitBlt(图案画刷填充),从而复用引擎既有的 BitBlt 裁剪/ROP 管线。
文件很小(104 行),由三部分构成:
| 构成 | 作用 |
|---|---|
gajRop2ToRop3[16] |
ROP2(1~16)→ ROP3 查表,MIX 转换的基础 |
FillSolid |
纯色矩形逐扫描线填充(DIB_HLine),被 EngEraseSurface 复用 |
EngPaint / IntEngPaint |
双层入口:EngPaint(MIX→ROP4 并转发 BitBlt),IntEngPaint(DrvPaint 挂钩决策) |
FillRgn(hdc, hrgn, hbrush) / PaintRgn(hdc, hrgn)
↓ gdi32.dll → NtGdiFillRgn
ntgdi/bitblt.c: IntGdiFillRgn (DC 裁剪合成 / 画刷准备 / MIX 构造)
↓ IntEngPaint
引擎: 目标表面挂钩了 DrvPaint? (HOOK_PAINT)
├─ 是 → GDIDEVFUNCS(pso).Paint (vga 等硬件驱动)
└─ 否 → EngPaint
↓ MIX_TO_ROP4 转 ROP4,断言无源
IntEngBitBlt(pso, NULL, NULL, pco, NULL, ..., pbo, ..., rop4)
↓ 无源 → 直接软件路径
EngBitBlt → BltPatCopy/纯色 → DIB 引擎写像素
2. 调用链路(从 API 到像素)
#mermaid-svg-DeleXL4zcDOlqTz4{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-DeleXL4zcDOlqTz4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DeleXL4zcDOlqTz4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DeleXL4zcDOlqTz4 .error-icon{fill:#552222;}#mermaid-svg-DeleXL4zcDOlqTz4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DeleXL4zcDOlqTz4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DeleXL4zcDOlqTz4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DeleXL4zcDOlqTz4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DeleXL4zcDOlqTz4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DeleXL4zcDOlqTz4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DeleXL4zcDOlqTz4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DeleXL4zcDOlqTz4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DeleXL4zcDOlqTz4 .marker.cross{stroke:#333333;}#mermaid-svg-DeleXL4zcDOlqTz4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DeleXL4zcDOlqTz4 p{margin:0;}#mermaid-svg-DeleXL4zcDOlqTz4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DeleXL4zcDOlqTz4 .cluster-label text{fill:#333;}#mermaid-svg-DeleXL4zcDOlqTz4 .cluster-label span{color:#333;}#mermaid-svg-DeleXL4zcDOlqTz4 .cluster-label span p{background-color:transparent;}#mermaid-svg-DeleXL4zcDOlqTz4 .label text,#mermaid-svg-DeleXL4zcDOlqTz4 span{fill:#333;color:#333;}#mermaid-svg-DeleXL4zcDOlqTz4 .node rect,#mermaid-svg-DeleXL4zcDOlqTz4 .node circle,#mermaid-svg-DeleXL4zcDOlqTz4 .node ellipse,#mermaid-svg-DeleXL4zcDOlqTz4 .node polygon,#mermaid-svg-DeleXL4zcDOlqTz4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DeleXL4zcDOlqTz4 .rough-node .label text,#mermaid-svg-DeleXL4zcDOlqTz4 .node .label text,#mermaid-svg-DeleXL4zcDOlqTz4 .image-shape .label,#mermaid-svg-DeleXL4zcDOlqTz4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-DeleXL4zcDOlqTz4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DeleXL4zcDOlqTz4 .rough-node .label,#mermaid-svg-DeleXL4zcDOlqTz4 .node .label,#mermaid-svg-DeleXL4zcDOlqTz4 .image-shape .label,#mermaid-svg-DeleXL4zcDOlqTz4 .icon-shape .label{text-align:center;}#mermaid-svg-DeleXL4zcDOlqTz4 .node.clickable{cursor:pointer;}#mermaid-svg-DeleXL4zcDOlqTz4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DeleXL4zcDOlqTz4 .arrowheadPath{fill:#333333;}#mermaid-svg-DeleXL4zcDOlqTz4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DeleXL4zcDOlqTz4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DeleXL4zcDOlqTz4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DeleXL4zcDOlqTz4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DeleXL4zcDOlqTz4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DeleXL4zcDOlqTz4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DeleXL4zcDOlqTz4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DeleXL4zcDOlqTz4 .cluster text{fill:#333;}#mermaid-svg-DeleXL4zcDOlqTz4 .cluster span{color:#333;}#mermaid-svg-DeleXL4zcDOlqTz4 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-DeleXL4zcDOlqTz4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DeleXL4zcDOlqTz4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-DeleXL4zcDOlqTz4 .icon-shape,#mermaid-svg-DeleXL4zcDOlqTz4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DeleXL4zcDOlqTz4 .icon-shape p,#mermaid-svg-DeleXL4zcDOlqTz4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DeleXL4zcDOlqTz4 .icon-shape .label rect,#mermaid-svg-DeleXL4zcDOlqTz4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DeleXL4zcDOlqTz4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DeleXL4zcDOlqTz4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DeleXL4zcDOlqTz4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
应用程序: FillRgn / PaintRgn / FrameRgn
gdi32.dll
NtGdiFillRgn
ntgdi/bitblt.c: NtGdiFillRgn
(锁 DC / 锁区域 / 锁画刷)
ntgdi: IntGdiFillRgn
合成裁剪区域 prgnClip
= DC 可见区 ∩ RAO/Vis 区域 + DC 原点偏移
构造 MIX
mix = rop2Fg | (BkMode==OPAQUE ? rop2Fg : R2_NOP)<<8
EBRUSHOBJ_vInit 初始化画刷对象
IntEngPaint(psurf, &xcoClip, pbo, ptlFillOrigin, mix)
表面非 STYPE_BITMAP
且 HOOK_PAINT?
DrvPaint
(vga: VGADDIPaintRgn)
EngPaint
MIX_TO_ROP4(mix)
gajRop2ToRop3 查表
低字节=前景ROP3 高字节=背景ROP3
NT_ASSERT(!ROP4_USES_SOURCE)
IntEngBitBlt(pso, NULL src, NULL mask,
pco, rclBounds, NULL, NULL, pbo,
pptlBrushOrg, rop4)
目标表面 HOOK_BITBLT?
驱动 DrvBitBlt
EngBitBlt 软件路径
(BltPatCopy / 纯色 BltPatCopy)
DibFunctionsForBitmapFormat
DIB_*_BitBlt / DIB_HLine
显存/帧缓冲
2.1 ntgdi 层(IntGdiFillRgn)关键决策
bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c#L1228-L1333) 的 IntGdiFillRgn 在调用引擎前完成四件事:
c
/* 1. 合成裁剪区域:DC 可见区与系统/RAO 区域求交,并平移到 DC 原点 */
IntGdiCombineRgn(prgnClip, prgnClip, pdc->prgnRao, RGN_AND);
REGION_bOffsetRgn(prgnClip, pdc->ptlDCOrig.x, pdc->ptlDCOrig.y);
/* 2. 构造 MIX:前景=当前 ROP2;背景= OPAQUE 时同 ROP2,TRANSPARENT 时为 R2_NOP */
rop2Fg = FIXUP_ROP2(pdc->pdcattr->jROP2);
mix = rop2Fg | (pdc->pdcattr->jBkMode == OPAQUE ? rop2Fg : R2_NOP) << 8;
/* 3. 初始化画刷:显式画刷或 DC 填充画刷(含 DIRTY 更新) */
EBRUSHOBJ_vInit(&eboFill, pbrFill, pdc->dclevel.pSurface, 0x00FFFFFF, 0, NULL);
/* ... 或 DC_vUpdateFillBrush + &pdc->eboFill.BrushObject */
/* 4. 调用引擎 */
bRet = IntEngPaint(&pdc->dclevel.pSurface->SurfObj,
(CLIPOBJ *)&xcoClip, pbo, &pdc->ptlFillOrigin, mix);
入口 API 对照: FillRgn(指定画刷,NtGdiFillRgn)、PaintRgn(用 DC 当前画刷,IntGdiPaintRgn → IntGdiFillRgn(pdc, prgn, NULL))、FrameRgn(先 GreCreateFrameRgn 生成边框区域再 FillRgn)。
3. 核心机制
3.1 gajRop2ToRop3 查表 + MIX_TO_ROP4(eng.h(file:///d:/reactos/win32ss/gdi/eng/eng.h#L3-L7))
填充的"笔"语义由 ROP2(16 种)描述;引擎内部 BitBlt 统一使用 ROP3/ROP4。paint.c 开头提供一张 ROP2 → ROP3 映射表:
c
const BYTE gajRop2ToRop3[16] =
{
0x00, // 1: R2_BLACK 0
0x05, // 2: R2_NOTMERGEPEN DPon
0x0A, // 3: R2_MASKNOTPEN DPna
0x0F, // 4: R2_NOTCOPYPEN Pn
0x50, // 5: R2_MASKPENNOT PDna
0x55, // 6: R2_NOT Dn
0x5A, // 7: R2_XORPEN DPx
0x5F, // 8: R2_NOTMASKPEN DPan
0xA0, // 9: R2_MASKPEN DPa
0xA5, // 10: R2_NOTXORPEN PDxn
0xAA, // 11: R2_NOP D
0xAF, // 12: R2_MERGENOTPEN DPno
0xF0, // 13: R2_COPYPEN P
0xF5, // 14: R2_MERGEPENNOT PDno
0xFA, // 15: R2_MERGEPEN DPo
0xFF, // 16: R2_WHITE 1
};
#define MIX_TO_ROP4(mix) \
(((ULONG)gajRop2ToRop3[((mix) - 1) & 0xF]) | \
((ULONG)gajRop2ToRop3[(((mix) >> 8) - 1) & 0xF] << 8))
MIX 编码(Windows 惯例): 低 8 位 = 前景 ROP2,高 8 位 = 背景 ROP2(R2_BLACK=1 ~ R2_WHITE=16)。MIX_TO_ROP4 用 (mix-1)&0xF 索引(-1 对齐 1~16 → 0~15),把前后景分别映射成 ROP3(低字节 = 前景,高字节 = 背景),恰好构成 ROP4。例:默认 R2_COPYPEN(13) → 0xF0,OPAQUE 背景 → ROP4 = 0xF0F0。
3.2 FillSolid --- 纯色矩形逐扫描线填充
c
BOOL APIENTRY FillSolid(SURFOBJ *pso, PRECTL pRect, ULONG iColor)
{
LONG y;
ULONG LineWidth;
LineWidth = pRect->right - pRect->left;
for (y = pRect->top; y < pRect->bottom; y++)
{
DibFunctionsForBitmapFormat[pso->iBitmapFormat].DIB_HLine(
pso, pRect->left, pRect->right, y, iColor);
}
return TRUE;
}
实现要点:
- 逐行调用按位深分发的
DIB_HLine(函数签名PFN_DIB_HLine(SURFOBJ*, LONG left, LONG right, LONG y, ULONG iColor),见 dib.h(file:///d:/reactos/win32ss/gdi/dib/dib.h#L36)),与 lineto.c 的水平线快路径同源。 - 不做 ROP/掩码处理,只做"整行覆盖写"------专用于纯色直接擦除场景。
- 唯一调用者是
EngEraseSurface(surface.c(file:///d:/reactos/win32ss/gdi/eng/surface.c#L583-L593)),用于设备表面初始擦除(显示驱动DrvEnableSurface之后)和EngEraseSurfaceDDI 服务。
3.3 EngPaint --- MIX→ROP4 并转发 BitBlt
c
BOOL APIENTRY EngPaint(SURFOBJ *pso, CLIPOBJ *pco, BRUSHOBJ *pbo,
POINTL *pptlBrushOrg, MIX mix)
{
ROP4 rop4;
rop4 = MIX_TO_ROP4(mix); /* MIX(2×ROP2) → ROP4 */
NT_ASSERT(!ROP4_USES_SOURCE(rop4)); /* 填充永不使用源表面 */
return IntEngBitBlt(pso, NULL, NULL, pco, NULL,
&pco->rclBounds, NULL, NULL,
pbo, pptlBrushOrg, rop4); /* 无源/无掩码的图案填充 */
}
设计本质: "区域填充"在引擎层被规约为一次特化 BitBlt ------目标表面 + 画刷(图案),源和掩码均为 NULL。由于 ROP4 必然不依赖源,ROP4_USES_SOURCE 断言保证安全。之后完全复用《分析_4》第 3.1 节的 IntEngBitBlt 管线(目标 HOOK_BITBLT → 驱动 DrvBitBlt,否则 EngBitBlt 软件路径,纯色走 BltPatCopy 快路径)。这也是引擎复用哲学的又一体现:不写专门的填充渲染器。
3.4 IntEngPaint --- DrvPaint 挂钩决策
c
BOOL APIENTRY IntEngPaint(SURFOBJ *pso, CLIPOBJ *pco, BRUSHOBJ *pbo,
POINTL *pptlBrushOrg, MIX mix)
{
SURFACE *psurf = CONTAINING_RECORD(pso, SURFACE, SurfObj);
/* 设备表面且挂钩了 DrvPaint → 调驱动;否则引擎软件实现 */
if ((pso->iType != STYPE_BITMAP) && (psurf->flags & HOOK_PAINT))
{
return GDIDEVFUNCS(pso).Paint(pso, pco, pbo, pptlBrushOrg, mix);
}
return EngPaint(pso, pco, pbo, pptlBrushOrg, mix);
}
挂钩条件两层:
pso->iType != STYPE_BITMAP--- 只有设备表面(屏幕)才可能让硬件接管;内存位图一律引擎实现。psurf->flags & HOOK_PAINT--- 表面是否在EngAssociateSurface时声明HOOK_PAINT。
4. DDI 交互:DrvPaint 与 HOOK_PAINT
4.1 标志与函数表
HOOK_PAINT = 0x00000010定义于 winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L1424)(注意:eng/surface.h中 HOOK_* 宏被注释掉了,实际编译用的是 winddi.h 的定义)。- DDI 函数表
DRIVER_FUNCTIONS.Paint(ntgdityp.h(file:///d:/reactos/win32ss/include/ntgdityp.h#L586)),vga 驱动以{INDEX_DrvPaint, (PFN) DrvPaint}注册(vga/enable.c(file:///d:/reactos/win32ss/drivers/displays/vga/main/enable.c#L25)),并在关联表面时声明HOOK_BITBLT | HOOK_PAINT | HOOK_LINETO | HOOK_COPYBITS | ...(enable.c(file:///d:/reactos/win32ss/drivers/displays/vga/main/enable.c#L491))。
4.2 vga 驱动 DrvPaint 示例(vga/objects/paint.c(file:///d:/reactos/win32ss/drivers/displays/vga/objects/paint.c#L183-L201))
DrvPaint(Surface, ClipRegion, Brush, BrushOrigin, Mix)
↓ 取 iSolidColor(当前只支持纯色画刷)
VGADDIPaintRgn(...)
├─ DC_RECT → 直接 VGADDIFillSolid(整矩形)
└─ 其他 → CLIPOBJ_cEnumStart + CLIPOBJ_bEnum 枚举矩形,
逐矩形 VGADDIFillSolid
↓
VGADDIFillSolid: 按字节对齐拆分 左掩码/中间整字节/右掩码,
通过 VGA 图形控制器端口(GRA_I/GRA_D 写掩码)
直接写显存,规避 CPU 不可寻址字节
注意 vga 的 DrvPaint 绕过引擎直接操作端口,体现了 DDI 挂钩的意义:硬件驱动用更高效的方式完成 GDI 兜底要做的同一件事。
5. 服务导出与调用方
| 函数 | 导出 | 调用方 |
|---|---|---|
EngPaint |
win32k.spec(file:///d:/reactos/win32ss/win32k.spec#L103) 直接导出(DDI 服务) | 驱动可选调用;内核经 IntEngPaint 调用 |
NtGdiEngPaint |
gdi32.spec(file:///d:/reactos/win32ss/gdi/gdi32/gdi32.spec#L187) → win32u → umpdstubs.c(file:///d:/reactos/win32ss/gdi/eng/umpdstubs.c#L319-L331) | UMPD 用户模式驱动路径(当前 UNIMPLEMENTED 桩) |
FillSolid |
引擎内部(paint.h(file:///d:/reactos/win32ss/gdi/ntgdi/paint.h#L3) 声明) | EngEraseSurface |
IntEngPaint |
内部 | IntGdiFillRgn |
内核驱动路径: DrvPaint ← GDIDEVFUNCS(pso).Paint ← IntEngPaint ← IntGdiFillRgn
UMPD 驱动路径: EngPaint ← NtGdiEngPaint(umpdstubs) ← gdi32.EngPaint ← UMPD
软件兜底路径: EngPaint → IntEngBitBlt → EngBitBlt → DIB
6. 与《分析_4》标注对照
| 标注 | 核对 |
|---|---|
| 《_4》第 37 行:paint.c =「区域填充」 | 准确。补充:实现主体是 EngPaint/IntEngPaint/FillSolid,其中 EngPaint 是 win32k 导出的 DDI 服务(NtGdiEngPaint UMPD 路径为桩) |
7. 源码索引
| 文件 | 关键内容 |
|---|---|
| paint.c(file:///d:/reactos/win32ss/gdi/eng/paint.c) | gajRop2ToRop3 表、FillSolid、EngPaint、IntEngPaint |
| eng.h(file:///d:/reactos/win32ss/gdi/eng/eng.h#L5-L7) | MIX_TO_ROP4 宏、gajRop2ToRop3 外部声明 |
| inteng.h(file:///d:/reactos/win32ss/gdi/eng/inteng.h#L143) | IntEngPaint 声明 |
| ntgdi/bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c#L1228-L1333) | IntGdiFillRgn / IntGdiPaintRgn / NtGdiFillRgn / NtGdiFrameRgn |
| ntgdi/paint.h(file:///d:/reactos/win32ss/gdi/ntgdi/paint.h) | FillSolid 声明 |
| surface.c(file:///d:/reactos/win32ss/gdi/eng/surface.c#L583-L593) | EngEraseSurface → FillSolid |
| bitblt.c(file:///d:/reactos/win32ss/gdi/eng/bitblt.c#L1104) | IntEngBitBlt(EngPaint 转发目标) |
| winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L1424) | HOOK_PAINT 定义 |
| vga/objects/paint.c(file:///d:/reactos/win32ss/drivers/displays/vga/objects/paint.c) | DrvPaint / VGADDIPaintRgn / VGADDIFillSolid(硬件填充示例) |
| umpdstubs.c(file:///d:/reactos/win32ss/gdi/eng/umpdstubs.c#L319-L331) | NtGdiEngPaint(UMPD 桩) |
关联模块:
- 上层调用者:ntgdi/(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c)(FillRgn/PaintRgn/FrameRgn 系统调用)
- 软件渲染后端:gdi/dib/(file:///d:/reactos/win32ss/gdi/dib/)(DIB_HLine / DIB_BitBlt 函数表)
- BitBlt 管线:eng/bitblt.c(file:///d:/reactos/win32ss/gdi/eng/bitblt.c)(详见《分析_4》3.1 节)
- 硬件填充示例:vga 驱动(file:///d:/reactos/win32ss/drivers/displays/vga/objects/paint.c)
本文档基于 ReactOS 源代码
win32ss/gdi/eng/paint.c及关联模块分析(2026 年 8 月)