第9章:VIRGL 后端(OpenGL / vrend)

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_* 函数负责:

  1. 边界校验 (命令来自不可信 Guest,见第8章 / 第16章);
  2. 解析参数;
  3. 调用 vrend_renderer.c 里对应的状态设置或 GL 调用。

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 命令原样转发。

相关推荐
DeeplyMind2 天前
第8章: 命令流与编解码
virtio-gpu·virglrenderer
凉、介2 个月前
Virtio(三):Virtqueue 建立与 Buffer 提交
笔记·学习·嵌入式·arm·虚拟化·virtio
凉、介3 个月前
Virtio 系列(一):框架概览
笔记·学习·嵌入式·虚拟化·virtio
Wanliang Li6 个月前
AArch64虚拟化——virtio-mmio实现
linux·虚拟化·virtio·hypervisor·mmio
DeeplyMind10 个月前
Linux Virtio 子系统核心数据结构解析
linux·驱动开发·virtio-gpu
DeeplyMind10 个月前
Guest → QEMU → Virglrenderer 调用逻辑分析
linux·驱动开发·虚拟化·virtio-gpu·virglrenderer
漫谈网络1 年前
Virtio 半虚拟化技术解析
virtio·半虚拟化技术
深度Linux2 年前
探秘Linux IO虚拟化:virtio的奇幻之旅
linux·c/c++·virtio·虚拟化技术
Wanliang Li2 年前
Linux电源管理——Device Power Management Interface
linux·嵌入式·virtio·电源管理·suspend