You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Skia渲染至GBM Surface以高效屏幕显示?

问题

我希望将内容渲染至GBM Buffer,以便在屏幕上进行显示,请问这是否可行?我曾尝试渲染至dumb buffer,但该方式速度极慢(这是意料之中的)。以下是我使用drm和skia_safe crate实现dumb buffer渲染的代码:

use drm::Device as _;
use skia_safe::surfaces;

let drm = MyDevice::new("/dev/dri/card0");
// 创建dumb缓冲区
let dumbbuffer = drm.create_dumb_buffer(size.into(), drm::buffer::DrmFourcc::Xrgb_8888, 32).unwrap();
// 添加为DRM帧缓冲区
let framebuffer = drm.add_framebuffer(&dumbbuffer, 32, 32).unwrap();
// 创建Skia光栅表面
let surface = surfaces::raster_n32_premul(size).unwrap();

// 在此处向表面绘制内容...
let paint = Paint::new(Color4f::new(255.0, 255.0, 255.0, 255.0), &ColorSpace::new_srgb());
surface.canvas().draw_circle(Point(0.0,0.0), 50.0, &paint);

// 从表面获取像素数据
let bytes = surface.peek_pixels().map(|pixel| pixel.bytes().map(|bytes| bytes.to_owned())).flatten();

// 将像素数据复制到dumb缓冲区
let mapping = drm.map_dumb_buffer(&mut dumbbuffer).unwrap();
let len = mapping.len();
mapping.copy_from_slice(&[bytes, &vec![0; len - bytes.len()]].concat());
回答

完全可行,GBM(Graphics Buffer Manager)正是为解决这类性能问题设计的——它能创建GPU可直接访问的共享缓冲区,让Skia可以利用GPU加速渲染,彻底避免dumb buffer必须通过CPU拷贝像素的低效流程。

实现思路

  1. 基于DRM设备创建GBM设备实例
  2. 创建GBM缓冲区(指定与DRM兼容的像素格式)
  3. 将GBM缓冲区注册为DRM帧缓冲区
  4. 通过EGL将Skia的GPU渲染上下文绑定到GBM缓冲区,直接在GPU端完成绘制
  5. 提交GBM缓冲区到DRM进行显示

示例代码

use drm::Device as _;
use gbm::{Device as GbmDevice, BufferObjectFlags, Format};
use skia_safe::{Color4f, ColorSpace, Paint, Point, surfaces, Canvas};
use egl::api::{EGLDisplay, EGLContext, EGLSurface};
use egl::consts::*;

// 1. 打开DRM设备并创建GBM设备
let drm = MyDevice::new("/dev/dri/card0").unwrap();
let gbm_device = GbmDevice::new(&drm).unwrap();

// 2. 创建GBM缓冲区(选择与DRM匹配的格式,比如XRGB8888)
let size = (800, 600);
let gbm_bo = gbm_device.create_buffer_object(
    size.0,
    size.1,
    Format::XRGB8888,
    BufferObjectFlags::SCANOUT | BufferObjectFlags::RENDERING,
).unwrap();

// 3. 将GBM缓冲区注册为DRM帧缓冲区
let framebuffer = drm.add_framebuffer(
    &gbm_bo,
    32, // 每像素位数
    32, // 每行像素跨度(stride)
).unwrap();

// 4. 初始化EGL上下文,绑定GBM缓冲区作为渲染表面
let egl_display = EGLDisplay::new(gbm_device.as_raw()).unwrap();
egl_display.initialize().unwrap();

let config = egl_display.choose_config(
    &[
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_NONE,
    ],
).unwrap()[0];

let egl_context = EGLContext::new(&egl_display, config).unwrap();
let egl_surface = EGLSurface::new_window(&egl_display, config, gbm_bo.as_raw()).unwrap();

egl_context.make_current(&egl_surface).unwrap();

// 5. 创建Skia GPU表面并绘制内容
let skia_surface = surfaces::from_backend_render_target(
    &egl_display.display(),
    size,
    skia_safe::BackendRenderTargetInfo::new(
        skia_safe::GlFramebufferInfo {
            fbo: 0,
            format: skia_safe::GLFormat::RGBA8,
        },
        32,
        skia_safe::SurfaceOrigin::TopLeft,
    ),
    skia_safe::ColorType::RGBA8888,
    Some(&ColorSpace::new_srgb()),
    None,
).unwrap();

let canvas = skia_surface.canvas();
// 绘制内容(示例:白色圆形)
let paint = Paint::new(Color4f::new(1.0, 1.0, 1.0, 1.0), &ColorSpace::new_srgb());
canvas.draw_circle(Point::new(400.0, 300.0), 50.0, &paint);

// 6. 提交渲染结果到DRM显示
egl_surface.swap_buffers().unwrap();
// 此处需结合DRM页面翻转逻辑(如drmModePageFlip)完成最终屏幕输出

关键优势

  • 无CPU拷贝:Skia直接在GPU端渲染到GBM缓冲区,避免了从光栅表面拷贝像素到dumb buffer的耗时操作
  • GPU加速:利用硬件渲染能力,渲染性能远高于纯CPU的dumb buffer方案
  • 原生DRM集成:GBM缓冲区可直接被DRM识别,无需额外格式转换

内容的提问来源于stack exchange,提问作者Flysoft

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 05:45:33