如何实现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拷贝像素的低效流程。
实现思路
- 基于DRM设备创建GBM设备实例
- 创建GBM缓冲区(指定与DRM兼容的像素格式)
- 将GBM缓冲区注册为DRM帧缓冲区
- 通过EGL将Skia的GPU渲染上下文绑定到GBM缓冲区,直接在GPU端完成绘制
- 提交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
相关产品推荐
相关产品推荐

