1. 概述
本文分析虚拟机图形加速的完整调用链路:从 Guest OS 中的应用程序发起图形请求,经过 virtio-gpu 驱动、QEMU 虚拟机管理器,最终到达 virglrenderer 库执行实际渲染的全过程。
1.1 整体架构
┌─────────────────────────────────────────────────────────────┐
│ Guest OS │
│ ┌─────────────┐ │
│ │ Application │ (OpenGL/Vulkan API) │
│ └──────┬──────┘ │
│ │ │
│ ┌──────▼──────────────────┐ │
│ │ Mesa / Vulkan Driver │ │
│ │ (virgl, venus) │ │
│ └──────┬──────────────────┘ │
│ │ ioctl() │
│ ┌──────▼──────────────────┐ │
│ │ virtio-gpu Kernel │ │
│ │ Driver (DRM/KMS) │ │
│ └──────┬──────────────────┘ │
└─────────┼──────────────────────────────────────────────────┘
│ virtio ring (virtqueue)
│
┌─────────▼──────────────────────────────────────────────────┐
│ QEMU │
│ ┌──────────────────────────────┐ │
│ │ virtio-gpu PCI Device │ │
│ │ (hw/display/virtio-gpu.c) │ │
│ └──────┬───────────────────────┘ │
│ │ virgl_renderer_*() │
│ ┌──────▼───────────────────────┐ │
│ │ virglrenderer 库接口层 │ │
└──┼─────────────────────────────┼───────────────────────────┘
│ │
│ libvirglrenderer.so │
│ │
┌──▼──────────────────────────────▼───────────────────────────┐
│ Virglrenderer Library │
│ ┌──────────────────┬──────────────────┬─────────────────┐ │
│ │ vrend (OpenGL) │ venus (Vulkan) │ drm │ │
│ └────────┬─────────┴────────┬─────────┴────────┬────────┘ │
└───────────┼──────────────────┼──────────────────┼───────────┘
│ │ │
┌───────▼──────┐ ┌───────▼──────┐ ┌──────▼──────┐
│ Host OpenGL │ │ Host Vulkan │ │ Host DRM │
│ Driver │ │ Driver │ │ │
└──────────────┘ └──────────────┘ └─────────────┘
2. 核心调用流程详解
2.1 初始化阶段
阶段 1: QEMU 启动 virglrenderer
当 QEMU 启动虚拟机时,如果配置了 virtio-gpu 设备,会执行以下初始化:
c
// QEMU 侧伪代码
virgl_renderer_callbacks cbs = {
.version = 4,
.write_fence = qemu_write_fence, // fence 回调
.write_context_fence = qemu_write_ctx_fence, // 上下文 fence
.create_gl_context = qemu_create_gl_context, // GL 上下文创建
.destroy_gl_context = qemu_destroy_gl_context,
.make_current = qemu_make_current,
.get_drm_fd = qemu_get_drm_fd, // 获取 DRM fd
.get_server_fd = qemu_get_server_fd, // Venus 专用
.get_egl_display = qemu_get_egl_display // EGL 显示
};
int flags = VIRGL_RENDERER_USE_EGL |
VIRGL_RENDERER_THREAD_SYNC |
VIRGL_RENDERER_ASYNC_FENCE_CB;
// 初始化 virglrenderer
virgl_renderer_init(qemu_cookie, flags, &cbs);
阶段 2: virglrenderer 内部初始化
virgl_renderer_init() 函数执行以下步骤(代码:src/virglrenderer.c:):
c
int virgl_renderer_init(void *cookie, int flags,
struct virgl_renderer_callbacks *cbs)
{
// 1. 保存回调和状态
state.cookie = cookie;
state.flags = flags;
state.cbs = cbs;
// 2. 初始化资源表(全局资源管理)
virgl_resource_table_init(pipe_cbs);
state.resource_initialized = true;
// 3. 初始化上下文表(全局上下文管理)
virgl_context_table_init();
state.context_initialized = true;
// 4. 初始化窗口系统(EGL/GLX)
if (flags & VIRGL_RENDERER_USE_EGL) {
int drm_fd = cbs->get_drm_fd(cookie);
vrend_winsys_init(flags, drm_fd);
state.winsys_initialized = true;
}
// 5. 初始化 OpenGL 渲染器
if (!(flags & VIRGL_RENDERER_NO_VIRGL)) {
vrend_renderer_init(&vrend_cbs, renderer_flags);
state.vrend_initialized = true;
}
// 6. 初始化 Vulkan 渲染器 (Venus)
if (flags & VIRGL_RENDERER_RENDER_SERVER) {
proxy_renderer_init(&proxy_cbs, flags);
state.proxy_initialized = true;
}
// 7. 初始化 DRM 原生渲染器
if (flags & VIRGL_RENDERER_DRM) {
drm_renderer_init(drm_fd);
state.drm_initialized = true;
}
// 8. 初始化 fence 同步表
virgl_fence_table_init();
state.fence_initialized = true;
return 0;
}
关键数据结构:
c
struct global_state {
void *cookie; // QEMU 传入的上下文指针
const struct virgl_renderer_callbacks *cbs; // 回调函数表
// 各子系统初始化标志
bool vrend_initialized; // OpenGL 渲染器
bool proxy_initialized; // Vulkan/Venus
bool drm_initialized; // DRM 原生
bool resource_initialized; // 资源管理
bool context_initialized; // 上下文管理
bool fence_initialized; // fence 同步
};
2.2 上下文创建与管理
上下文创建流程 (代码:src/virglrenderer.c:216)
c
int virgl_renderer_context_create_with_flags(
uint32_t ctx_id,
uint32_t ctx_flags,
uint32_t nlen,
const char *name)
{
// 1. 提取 capset_id(决定渲染器类型)
uint32_t capset_id = ctx_flags &
VIRGL_RENDERER_CONTEXT_FLAG_CAPSET_ID_MASK;
struct virgl_context *ctx;
// 2. 根据 capset_id 选择渲染器
switch (capset_id) {
case VIRTGPU_DRM_CAPSET_VIRGL:
case VIRTGPU_DRM_CAPSET_VIRGL2:
// OpenGL 渲染器
ctx = vrend_renderer_context_create(ctx_id, nlen, name);
break;
case VIRTGPU_DRM_CAPSET_VENUS:
// Vulkan 渲染器
ctx = proxy_context_create(ctx_id, ctx_flags, nlen, name);
break;
case VIRTGPU_DRM_CAPSET_DRM:
// DRM 原生渲染器
ctx = drm_renderer_create(nlen, name);
break;
// 3. 设置上下文属性
ctx->ctx_id = ctx_id;
ctx->in_fence_fd = -1;
ctx->capset_id = capset_id;
ctx->fence_retire = per_context_fence_retire; // Fence 回调
// 4. 注册到全局上下文表
virgl_context_add(ctx);
return 0;
}
上下文结构 (src/virgl_context.h):
c
struct virgl_context {
uint32_t ctx_id;
uint32_t capset_id; // 渲染器类型标识
int in_fence_fd; // 输入同步 fence
// 虚函数表(多态)
void (*destroy)(struct virgl_context *ctx);
void (*attach_resource)(struct virgl_context *ctx,
struct virgl_resource *res);
void (*detach_resource)(struct virgl_context *ctx,
struct virgl_resource *res);
int (*submit_cmd)(struct virgl_context *ctx,
const void *buffer, size_t size);
int (*submit_fence)(struct virgl_context *ctx,
uint32_t flags, uint32_t ring_idx,
uint64_t fence_id);
void (*retire_fences)(struct virgl_context *ctx);
int (*get_blob)(struct virgl_context *ctx, ...);
int (*transfer_3d)(struct virgl_context *ctx, ...);
// Fence 回调
virgl_context_fence_retire fence_retire;
};
2.3 资源创建流程
流程 1: Guest 创建纹理/缓冲区
Guest Application
│
├─► glGenTextures() / glCreateBuffers()
│
▼
Mesa Driver (virgl_dri.so)
│
├─► virgl_resource_create()
│
▼
Kernel virtio-gpu Driver
│
├─► VIRTIO_GPU_CMD_RESOURCE_CREATE_3D
│ (通过 virtqueue 发送命令)
│
▼
QEMU virtio-gpu Device
│
├─► virgl_cmd_resource_create_3d()
│
▼
Virglrenderer
流程 2: virglrenderer 资源创建
传统资源创建 (代码:src/virglrenderer.c):
c
int virgl_renderer_resource_create(
struct virgl_renderer_resource_create_args *args,
struct iovec *iov,
uint32_t num_iovs)
{
// 1. 参数验证
if (args->handle == 0) return EINVAL;
if (virgl_resource_lookup(args->handle)) return -EINVAL;
// 2. 转换参数为 vrend 格式
struct vrend_renderer_resource_create_args vrend_args = {
.target = args->target, // GL_TEXTURE_2D, GL_ARRAY_BUFFER 等
.format = args->format, // PIPE_FORMAT_*
.bind = args->bind, // VIRGL_RES_BIND_RENDER_TARGET 等
.width = args->width,
.height = args->height,
.depth = args->depth,
.array_size = args->array_size,
.nr_samples = args->nr_samples,
.last_level = args->last_level // mipmap 层级
};
// 3. 调用 vrend 创建实际 GL 资源
pipe_res = vrend_renderer_resource_create(&vrend_args, NULL);
// 4. 创建 virgl_resource 包装器
res = virgl_resource_create_from_pipe(args->handle, pipe_res,
iov, num_iovs);
// 5. 获取映射信息
res->map_info = vrend_renderer_resource_get_map_info(pipe_res);
return 0;
}
Blob 资源创建 (现代方式,代码:src/virglrenderer.c):
c
int virgl_renderer_resource_create_blob(
const struct virgl_renderer_resource_create_blob_args *args)
{
// 1. 确定存储位置
switch (args->blob_mem) {
case VIRGL_RENDERER_BLOB_MEM_GUEST:
has_host_storage = false; // 仅 Guest 内存
has_guest_storage = true;
break;
case VIRGL_RENDERER_BLOB_MEM_HOST3D:
has_host_storage = true; // 仅 Host GPU 内存
has_guest_storage = false;
break;
case VIRGL_RENDERER_BLOB_MEM_HOST3D_GUEST:
has_host_storage = true; // 两者都有(共享)
has_guest_storage = true;
break;
}
// 2. 如果无 Host 存储,直接从 iov 创建
if (!has_host_storage) {
res = virgl_resource_create_from_iov(args->res_handle,
args->iovecs, args->num_iovs);
res->map_info = VIRGL_RENDERER_MAP_CACHE_CACHED;
return 0;
}
// 3. 通过上下文创建 blob(Vulkan/DRM)
ctx = virgl_context_lookup(args->ctx_id);
ret = ctx->get_blob(ctx, args->res_handle, args->blob_id,
args->size, args->blob_flags, &blob);
// 4. 根据 blob 类型创建资源
switch (blob.type) {
case VIRGL_RESOURCE_FD_DMABUF:
case VIRGL_RESOURCE_FD_OPAQUE:
res = virgl_resource_create_from_fd(args->res_handle,
blob.type, blob.u.fd, ...);
break;
case VIRGL_RESOURCE_OPAQUE_HANDLE:
res = virgl_resource_create_from_opaque_handle(ctx,
args->res_handle, blob.u.opaque_handle);
break;
case VIRGL_RESOURCE_VA_HANDLE:
res = virgl_resource_create_from_va_handle(args->res_handle,
blob.u.va_handle);
break;
default:
res = virgl_resource_create_from_pipe(args->res_handle,
blob.u.pipe_resource, ...);
}
res->map_info = blob.map_info;
res->map_size = args->size;
return 0;
}
2.4 命令提交流程
完整命令提交路径
Guest: glDrawArrays()
│
▼
Mesa: virgl_encode_draw_vbo()
│ (编码为 VIRGL_CCMD_DRAW_VBO 命令)
▼
Kernel: virtio_gpu_execbuffer_ioctl()
│
▼
virtqueue: 发送命令缓冲区
│
▼
QEMU: virtio_gpu_cmd_submit_3d()
│
▼
virglrenderer: virgl_renderer_submit_cmd()
virglrenderer 命令提交 (代码:src/virglrenderer.c)
c
int virgl_renderer_submit_cmd(void *buffer, int ctx_id, int ndw)
{
// 1. 查找上下文
struct virgl_context *ctx = virgl_context_lookup(ctx_id);
if (!ctx) return EINVAL;
// 2. 参数验证
if (ndw < 0 || (unsigned)ndw > UINT32_MAX / sizeof(uint32_t))
return EINVAL;
if (((uintptr_t)buffer & 3) != 0) // 必须 4 字节对齐
return EFAULT;
// 3. 调用上下文的虚函数提交命令
// 对于 OpenGL 上下文,这会调用 vrend_decode_ctx_submit_cmd()
// 对于 Vulkan 上下文,这会调用 proxy_context_submit_cmd()
return ctx->submit_cmd(ctx, buffer, (uint32_t)ndw * sizeof(uint32_t));
}
OpenGL 命令解码与执行(vrend 渲染器)
c
// vrend_decode_ctx_submit_cmd() 伪代码
int vrend_decode_ctx_submit_cmd(ctx, buffer, size)
{
uint32_t *cmd = (uint32_t *)buffer;
while (size > 0) {
uint32_t header = cmd[0];
uint32_t cmd_id = header & 0xFF;
uint32_t cmd_len = (header >> 16) & 0xFFFF;
switch (cmd_id) {
case VIRGL_CCMD_CLEAR:
vrend_decode_clear(ctx, cmd, cmd_len);
break;
case VIRGL_CCMD_DRAW_VBO:
vrend_decode_draw_vbo(ctx, cmd, cmd_len);
break;
case VIRGL_CCMD_BIND_SHADER:
vrend_decode_bind_shader(ctx, cmd, cmd_len);
break;
case VIRGL_CCMD_SET_VIEWPORT_STATE:
vrend_decode_set_viewport(ctx, cmd, cmd_len);
break;
// ... 60+ 个命令类型
}
cmd += cmd_len;
size -= cmd_len * 4;
}
}
// 示例:绘制命令解码
void vrend_decode_draw_vbo(ctx, cmd, len)
{
uint32_t start = cmd[1];
uint32_t count = cmd[2];
uint32_t mode = cmd[3]; // GL_TRIANGLES 等
uint32_t indexed = cmd[4];
uint32_t instance_count = cmd[5];
// 设置 GL 状态
vrend_set_pipeline_state(ctx);
// 执行 OpenGL 绘制
if (indexed)
glDrawElementsInstanced(mode, count, ...);
else
glDrawArraysInstanced(mode, start, count, instance_count);
}
2.5 Fence 同步机制
Fence 创建与通知
c
// Guest 侧发起 fence
int virgl_renderer_context_create_fence(
uint32_t ctx_id,
uint32_t flags,
uint32_t ring_idx,
uint64_t fence_id)
{
struct virgl_context *ctx = virgl_context_lookup(ctx_id);
// 调用上下文的 submit_fence
return ctx->submit_fence(ctx, flags, ring_idx, fence_id);
}
// Fence 完成后的回调路径
static void per_context_fence_retire(
struct virgl_context *ctx,
uint32_t ring_idx,
uint64_t fence_id)
{
// 通知 QEMU fence 已完成
state.cbs->write_context_fence(state.cookie,
ctx->ctx_id,
ring_idx,
fence_id);
}
QEMU 侧处理
c
// QEMU 实现 write_context_fence 回调
void qemu_write_context_fence(void *cookie,
uint32_t ctx_id,
uint32_t ring_idx,
uint64_t fence_id)
{
// 1. 通过 virtqueue 发送 fence 完成通知给 Guest
virtio_gpu_ctrl_response(vgpu, cmd,
VIRTIO_GPU_RESP_OK_NODATA);
// 2. 触发 Guest 中断
virtio_notify_irq(vgpu);
}
// Guest 内核收到中断后
void virtio_gpu_fence_event_process(vgpu)
{
// 唤醒等待的用户空间进程
dma_fence_signal(fence);
}
2.6 数据传输流程
Host ← Guest 传输(Upload)
c
int virgl_renderer_transfer_write_iov(
uint32_t handle,
uint32_t ctx_id,
int level, // mipmap 层级
uint32_t stride, // 行跨度
uint32_t layer_stride, // 层跨度
struct virgl_box *box, // 传输区域
uint64_t offset,
struct iovec *iovec, // Guest 内存
unsigned int iovec_cnt)
{
struct virgl_resource *res = virgl_resource_lookup(handle);
struct vrend_transfer_info transfer_info = {
.level = level,
.stride = stride,
.layer_stride = layer_stride,
.box = (struct pipe_box *)box,
.offset = offset,
.iovec = iovec,
.iovec_cnt = iovec_cnt,
.synchronized = false
};
if (ctx_id) {
// 通过上下文传输(可能有额外处理)
struct virgl_context *ctx = virgl_context_lookup(ctx_id);
return ctx->transfer_3d(ctx, res, &transfer_info,
VIRGL_TRANSFER_TO_HOST);
} else {
// 直接传输到 GL 资源
return vrend_renderer_transfer_pipe(res->pipe_resource,
&transfer_info,
VIRGL_TRANSFER_TO_HOST);
}
}
数据传输实现(vrend)
c
// vrend_renderer_transfer_pipe() 简化流程
int vrend_renderer_transfer_pipe(pipe_res, info, direction)
{
// 1. 绑定纹理/缓冲区
if (pipe_res->target == GL_TEXTURE_2D) {
glBindTexture(GL_TEXTURE_2D, pipe_res->gl_id);
} else {
glBindBuffer(GL_ARRAY_BUFFER, pipe_res->gl_id);
}
// 2. 从 iovec 拷贝数据到 GL
if (direction == VIRGL_TRANSFER_TO_HOST) {
if (pipe_res->target == GL_TEXTURE_2D) {
// 纹理上传
glTexSubImage2D(GL_TEXTURE_2D, info->level,
info->box->x, info->box->y,
info->box->width, info->box->height,
format, type, iov_data);
} else {
// 缓冲区上传
glBufferSubData(GL_ARRAY_BUFFER, info->offset,
size, iov_data);
}
} else {
// VIRGL_TRANSFER_FROM_HOST (下载)
glGetTexImage(...); // 或 glGetBufferSubData
}
return 0;
}
3. 多渲染器架构
3.1 渲染器选择机制
c
// 能力集 (Capset) 决定渲染器类型
enum capset_id {
VIRTGPU_DRM_CAPSET_VIRGL = 1, // OpenGL (Legacy)
VIRTGPU_DRM_CAPSET_VIRGL2 = 2, // OpenGL (Modern)
VIRTGPU_DRM_CAPSET_VENUS = 3, // Vulkan
VIRTGPU_DRM_CAPSET_DRM = 4, // DRM Native
};
// Guest 查询能力集
virgl_renderer_get_cap_set(capset_id, &max_ver, &max_size);
virgl_renderer_fill_caps(capset_id, version, caps);
// Guest 创建上下文时指定 capset_id
ctx_flags = VIRTGPU_DRM_CAPSET_VENUS;
virgl_renderer_context_create_with_flags(ctx_id, ctx_flags, ...);
3.2 各渲染器特点
| 渲染器 | Capset ID | 命令协议 | 典型用途 |
|---|---|---|---|
| vrend | VIRGL/VIRGL2 | VIRGL_CCMD_* | OpenGL 应用 |
| venus | VENUS | Vulkan 原生命令流 | Vulkan 应用 |
| drm | DRM | DRM ioctl 封装 | 硬件特定优化 |
4. 关键数据结构总结
4.1 全局状态
c
static struct global_state {
void *cookie; // QEMU 上下文
const struct virgl_renderer_callbacks *cbs;
// 子系统初始化标志
bool vrend_initialized;
bool proxy_initialized;
bool drm_initialized;
} state;
4.2 回调函数表
c
struct virgl_renderer_callbacks {
int version;
void (*write_fence)(void *cookie, uint32_t fence);
void (*write_context_fence)(void *cookie, uint32_t ctx_id,
uint32_t ring_idx, uint64_t fence_id);
virgl_renderer_gl_context (*create_gl_context)(...);
int (*get_drm_fd)(void *cookie);
int (*get_server_fd)(void *cookie, uint32_t version);
void *(*get_egl_display)(void *cookie);
};
4.3 资源对象
c
struct virgl_resource {
uint32_t res_id;
struct pipe_resource *pipe_resource; // GL/Gallium 资源
enum virgl_resource_fd_type fd_type;
int fd; // dmabuf/shm fd
const struct iovec *iov; // Guest 内存映射
uint32_t map_info;
uint64_t map_size;
void *mapped;
};
4.4 上下文对象
c
struct virgl_context {
uint32_t ctx_id;
uint32_t capset_id;
// 虚函数(多态实现)
int (*submit_cmd)(struct virgl_context *ctx, ...);
int (*submit_fence)(struct virgl_context *ctx, ...);
int (*get_blob)(struct virgl_context *ctx, ...);
void (*retire_fences)(struct virgl_context *ctx);
};
5. 性能优化要点
5.1 零拷贝路径
- Blob 资源 + dmabuf: Guest/Host 共享物理页
- virgl_resource_map(): 直接映射 Host GPU 内存到 Guest
- GBM allocation: 使用硬件优化的分配器
5.2 异步执行
- VIRGL_RENDERER_THREAD_SYNC: Fence 轮询在独立线程
- VIRGL_RENDERER_ASYNC_FENCE_CB: Fence 回调异步触发
- virtqueue: 批量命令提交,减少上下文切换
5.3 命令批处理
- Guest 累积多个 GL 命令后一次性提交
- QEMU 批量读取 virtqueue 请求
- virglrenderer 批量解码命令流
6. 总结
调用链总览
Application (glClear, glDrawArrays, ...)
↓
Mesa Driver (编码 VIRGL 命令)
↓
Kernel virtio-gpu (virtqueue 传输)
↓
QEMU virtio-gpu Device (virgl_renderer_* API)
↓
Virglrenderer Library (路由到具体渲染器)
↓
┌──────────┬──────────┬──────────┐
│ vrend │ venus │ drm │
│ (OpenGL) │ (Vulkan) │ (Native) │
└──────────┴──────────┴──────────┘
↓ ↓ ↓
Host GPU Driver (实际硬件执行)
关键特性
- 多渲染器支持: 单一接口支持 OpenGL/Vulkan/DRM
- 异步架构: Fence 机制实现高效同步
- 灵活资源管理: 支持传统资源和 Blob 资源
- 零拷贝优化: dmabuf 共享减少内存拷贝
- 模块化设计: 渲染器可独立启用/禁用