VIRGL 是 virglrenderer 的"原点":项目 2014 年诞生时就是为了它。它把 Guest 的 OpenGL 命令翻译成 Host 的 OpenGL/OpenGL ES 调用。相比后来的 venus/drm,vrend 是真正意义上的"翻译器" ------它要理解每一条图形命令的语义,而不只是转发。本章拆解 src/vrend/ 的内部结构。
9.1 定位:一个 GL→GL 的翻译器
- 输入 :Guest Mesa 的 virgl Gallium 驱动产出的命令流,着色器以 TGSI(Gallium 的着色器中间表示)内嵌;
- 输出:Host 的 OpenGL 3.3--4.6 / OpenGL ES 调用;
- capset :
VIRTGPU_DRM_CAPSET_VIRGL (1)与VIRGL2 (2)。
关键点:vrend 不假设 Host 有和 Guest 一样的 GL 特性,它要做能力协商 (通过 capset 上报 Host 实际支持的 GL 版本/扩展,见第5章),并在翻译时做必要的降级或模拟。
如下图,Guest 里跑的是 Mesa 的 virgl 虚拟驱动 (不对应真实硬件),把 GL 命令编码成 virgl 命令流经 virtio-gpu 送到 Host,由 vrend 翻译重放:

性能代价 :这种"API 层转发"意味着 Guest 与 Host 各跑一整套用户态驱动栈 ------Guest 侧 Mesa virgl 把 GL 编码成命令流,Host 侧 vrend 再用一套完整 GL 驱动把语义重走一遍,还要在 Host 重新编译着色器(TGSI→GLSL→驱动后端)。两套 UMD 的重复劳动 + 着色器重编译,是 vrend(以及同属 API 转发的 venus)开销高于 DRM native context 的根本原因;后者 Guest 只跑一套原生 UMD、着色器只编一次,Host 侧不再跑第 UMD全栈,而是直接走 libdrm 进 host 的 内核(见第11章、第17章)。
9.2 目录结构导览
src/vrend/ 各文件的职责:
| 文件 | 职责 |
|---|---|
vrend_renderer.c |
渲染器主逻辑:上下文、状态机、绘制、资源、fence |
vrend_decode.c |
命令解码:把 virgl 命令流分派到 renderer 的处理函数 |
vrend_shader.c |
着色器转换:TGSI → GLSL |
vrend_object.c |
GL 对象管理:句柄 ↔ GL 名字的映射表 |
vrend_formats.c |
像素格式映射与能力表 |
vrend_blitter.c |
用着色器实现的 blit(格式/尺寸转换拷贝) |
vrend_tweaks.c |
针对特定 Guest/驱动的兼容性微调 |
vrend_debug.c |
调试与日志 |
virgl_video.c / vrend_video.c |
视频编解码加速(可选) |
vrend_winsys*.c |
窗口系统后端:EGL / GLX / GBM |
iov.c / vrend_iov.h |
scatter-gather(iovec)读写辅助 |
9.3 命令解码:vrend_decode.c
解码器是后端的入口,对应 virgl_context->submit_cmd(见第8章)。它的骨架是一个大 switch:
c
/* 伪代码:命令解码主循环 */
while (has_more_commands(buf)) {
uint32_t header = read_dword(buf);
uint32_t cmd = header & 0xff; /* 命令类型 */
uint32_t length = header >> 16; /* 载荷长度(dword) */
switch (cmd) {
case VIRGL_CCMD_CREATE_OBJECT: vrend_decode_create_object(...); break;
case VIRGL_CCMD_DRAW_VBO: vrend_decode_draw_vbo(...); break;
case VIRGL_CCMD_SET_FRAMEBUFFER_STATE: ...; break;
case VIRGL_CCMD_BIND_SHADER: ...; break;
/* ...几十种命令... */
}
buf += length; /* 跳到下一条 */
}
每个 vrend_decode_* 函数负责:
9.4 着色器转换:TGSI → GLSL
这是 vrend 最有技术含量的部分(vrend_shader.c)。Guest 的着色器是 TGSI,Host 的 GL 只认 GLSL,所以要做源到源翻译:
TGSI 汇编 生成的 GLSL
------------------------ ------------------------
DCL IN[0], POSITION → in vec4 in0;
DCL OUT[0], COLOR → out vec4 out0;
MOV OUT[0], IN[0] → out0 = in0;
...
难点在于:
- 版本/扩展差异 :根据 Host GL 能力生成对应
#version和扩展声明; - 语义映射 :TGSI 的输入/输出/采样器语义要映射到 GLSL 的
in/out/uniform/sampler; - 修正与变通 :某些 GL 实现的 bug 或缺失特性要在生成的 GLSL 里绕过(与
vrend_tweaks.c配合); - 缓存:转换开销不小,生成的 GLSL/编译结果会被缓存复用。
9.5 GL 对象管理:vrend_object.c
Guest 用句柄(handle)引用纹理、缓冲、着色器、采样器等对象;Host GL 用自己的对象名(GLuint) 。vrend_object.c 维护二者的映射表:
- 每个上下文有自己的对象命名空间;
- 创建对象 → 分配 Host GL 名字 → 登记 handle↔name;
- 销毁 → 释放 GL 资源 + 从表中移除;
- 命令里的 handle 都要先查表校验(非法 handle 直接拒绝,防越权)。
9.6 Winsys:渲染到哪里去
vrend 需要一个 GL 上下文和渲染目标,这由 winsys 层提供,支持三种后端:
| winsys | 文件 | 场景 |
|---|---|---|
| EGL | vrend_winsys_egl.c |
主流;配合 surfaceless 无需真实窗口 |
| GBM | vrend_winsys_gbm.c |
直接经 DRM/GBM 分配可扫描输出的 buffer |
| GLX | vrend_winsys_glx.c |
传统 X11 环境 |
初始化标志与之对应:
c
#define VIRGL_RENDERER_USE_EGL 1
#define VIRGL_RENDERER_USE_GLX (1 << 2)
#define VIRGL_RENDERER_USE_SURFACELESS (1 << 3) /* 无窗口离屏渲染 */
#define VIRGL_RENDERER_USE_GLES (1 << 4) /* 用 GLES 而非桌面 GL */
服务器/云场景几乎都用 EGL + surfaceless :不需要 X server、不需要显示器,纯离屏渲染,结果通过 dma-buf 导出(见第7章)。
9.7 格式与 Blit
vrend_formats.c:维护 virgl 像素格式 ↔ GL 内部格式的映射表,以及每种格式支持的用途(可采样/可渲染/可 blit)。格式协商错误会导致创建资源失败或采样错乱。vrend_blitter.c:当硬件 blit(glBlitFramebuffer)不适用(格式不兼容、需要缩放/转换)时,用一对内置着色器画一个全屏四边形来完成拷贝转换。
9.8 视频加速(可选)
virgl_video.c / vrend_video.c 在开启 video 编译选项(VIRGL_RENDERER_USE_VIDEO,unstable)时提供硬件视频编解码虚拟化,把 Guest 的 VA-API 请求经由 Host 的视频引擎加速。属于可选特性,默认不编。
9.9 兼容性微调与调试
vrend_tweaks.c:针对特定 Guest 应用/驱动版本的已知问题做开关式修正(例如某些坐标约定、精度处理)。这是"现实世界不完美"的沉淀。vrend_debug.c:提供分类日志、命令 dump、对象统计等,配合环境变量开启(见第17章)。
9.10 一次绘制的内部旅程
#mermaid-svg-MUjJNGmuRlVLBQDe{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-MUjJNGmuRlVLBQDe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MUjJNGmuRlVLBQDe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MUjJNGmuRlVLBQDe .error-icon{fill:#552222;}#mermaid-svg-MUjJNGmuRlVLBQDe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MUjJNGmuRlVLBQDe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MUjJNGmuRlVLBQDe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MUjJNGmuRlVLBQDe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MUjJNGmuRlVLBQDe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MUjJNGmuRlVLBQDe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MUjJNGmuRlVLBQDe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MUjJNGmuRlVLBQDe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MUjJNGmuRlVLBQDe .marker.cross{stroke:#333333;}#mermaid-svg-MUjJNGmuRlVLBQDe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MUjJNGmuRlVLBQDe p{margin:0;}#mermaid-svg-MUjJNGmuRlVLBQDe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MUjJNGmuRlVLBQDe .cluster-label text{fill:#333;}#mermaid-svg-MUjJNGmuRlVLBQDe .cluster-label span{color:#333;}#mermaid-svg-MUjJNGmuRlVLBQDe .cluster-label span p{background-color:transparent;}#mermaid-svg-MUjJNGmuRlVLBQDe .label text,#mermaid-svg-MUjJNGmuRlVLBQDe span{fill:#333;color:#333;}#mermaid-svg-MUjJNGmuRlVLBQDe .node rect,#mermaid-svg-MUjJNGmuRlVLBQDe .node circle,#mermaid-svg-MUjJNGmuRlVLBQDe .node ellipse,#mermaid-svg-MUjJNGmuRlVLBQDe .node polygon,#mermaid-svg-MUjJNGmuRlVLBQDe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MUjJNGmuRlVLBQDe .rough-node .label text,#mermaid-svg-MUjJNGmuRlVLBQDe .node .label text,#mermaid-svg-MUjJNGmuRlVLBQDe .image-shape .label,#mermaid-svg-MUjJNGmuRlVLBQDe .icon-shape .label{text-anchor:middle;}#mermaid-svg-MUjJNGmuRlVLBQDe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MUjJNGmuRlVLBQDe .rough-node .label,#mermaid-svg-MUjJNGmuRlVLBQDe .node .label,#mermaid-svg-MUjJNGmuRlVLBQDe .image-shape .label,#mermaid-svg-MUjJNGmuRlVLBQDe .icon-shape .label{text-align:center;}#mermaid-svg-MUjJNGmuRlVLBQDe .node.clickable{cursor:pointer;}#mermaid-svg-MUjJNGmuRlVLBQDe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MUjJNGmuRlVLBQDe .arrowheadPath{fill:#333333;}#mermaid-svg-MUjJNGmuRlVLBQDe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MUjJNGmuRlVLBQDe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MUjJNGmuRlVLBQDe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MUjJNGmuRlVLBQDe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MUjJNGmuRlVLBQDe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MUjJNGmuRlVLBQDe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MUjJNGmuRlVLBQDe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MUjJNGmuRlVLBQDe .cluster text{fill:#333;}#mermaid-svg-MUjJNGmuRlVLBQDe .cluster span{color:#333;}#mermaid-svg-MUjJNGmuRlVLBQDe 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-MUjJNGmuRlVLBQDe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MUjJNGmuRlVLBQDe rect.text{fill:none;stroke-width:0;}#mermaid-svg-MUjJNGmuRlVLBQDe .icon-shape,#mermaid-svg-MUjJNGmuRlVLBQDe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MUjJNGmuRlVLBQDe .icon-shape p,#mermaid-svg-MUjJNGmuRlVLBQDe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MUjJNGmuRlVLBQDe .icon-shape rect,#mermaid-svg-MUjJNGmuRlVLBQDe .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MUjJNGmuRlVLBQDe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MUjJNGmuRlVLBQDe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MUjJNGmuRlVLBQDe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} submit_cmd
vrend_decode 循环
CREATE_OBJECT: 建 GL 对象, 登记 handle
BIND_SHADER: TGSI→GLSL, 编译, 缓存
SET_*_STATE: 设置管线状态
DRAW_VBO: 校验→设状态→glDraw*
Host GL 执行
glFenceSync → fence 完成回传
9.11 常见问题与调试
- 着色器编译失败:TGSI→GLSL 生成的代码在某些 Host 驱动上不被接受;开 shader dump 看生成的 GLSL。
- 格式不支持 :
vrend_formats.c没有该 virgl 格式到 Host GL 的映射,或 Host 驱动能力不足。 - 黑屏/无输出:winsys 没起来(EGL/surfaceless 配置问题),或 fence 没回传。
- 性能差:着色器缓存未命中导致频繁重编,或格式导致走了软件 blit 路径。
小结
- vrend 是真正的 GL→GL 翻译器,核心难点是 TGSI→GLSL 与 能力协商;
vrend_decode.c解码、vrend_shader.c转译、vrend_object.c管句柄、vrend_winsys*提供 GL 上下文;- 服务器场景标配 EGL + surfaceless;
- 大量兼容性微调(tweaks)是它成熟度的体现。
下一章看另一种完全不同的思路------Venus 后端:不翻译,而是把 Vulkan 命令原样转发。