如何使用skia-safe在winit窗口绘图并实现多平台(移动/桌面/嵌入式)兼容
如何使用skia-safe在winit窗口绘图并实现多平台(移动/桌面/嵌入式)兼容
嘿,我来帮你搞定这个跨平台绘图的问题~ 首先得明确你核心的需求:用skia-safe配合winit,尽量少依赖,还要覆盖桌面、移动、嵌入式这些平台。咱们一步步来解决:
核心问题分析
你之前踩的坑其实很典型:
- skia-safe自带的
gl-window例子依赖glutin,但glutin在iOS/Android上的支持还不完善,导致没法跑 - softbuffer+tiny-skia的组合虽然轻量,但softbuffer目前不支持Android NDK,没法覆盖移动平台
那最优解其实是利用winit的跨平台原生窗口句柄,直接对接skia-safe的渲染后端,既不用额外的中间层(比如glutin),又能让skia-safe自己适配不同平台的渲染方式。
解决方案:用skia-safe对接winit的原生窗口句柄
skia-safe支持通过raw-window-handle直接创建渲染表面,而winit刚好能提供所有平台的原生窗口句柄,这就完美匹配了。我们可以分两种渲染模式来实现:GPU加速渲染(优先)和软件渲染( fallback,适合嵌入式或低配置设备)。
第一步:配置依赖
在你的Cargo.toml里添加这些依赖,确保启用skia-safe的必要特性:
[dependencies] winit = { version = "0.29", features = ["rwh_05"] } skia-safe = { version = "0.72", features = ["gl", "raw-window-handle", "software"] } raw-window-handle = "0.5"
这里rwh_05是winit对接raw-window-handle 0.5版本的特性,skia-safe也对应支持这个版本。
第二步:跨平台实现代码
下面是一个完整的跨平台示例,能在桌面(Windows/macOS/Linux)、移动(Android/iOS)、嵌入式平台运行:
use skia_safe::{ gpu::{gl::FramebufferInfo, BackendRenderTarget, SurfaceOrigin}, Color, Paint, Surface, }; use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; fn main() { let event_loop = EventLoop::new().unwrap(); let window = WindowBuilder::new() .with_title("Skia-Winit Multiplatform") .build(&event_loop) .unwrap(); // 初始化Skia渲染上下文 let mut skia_surface = create_skia_surface(&window).unwrap(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { Event::WindowEvent { event, window_id } if window_id == window.id() => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Resized(new_size) => { // 窗口大小变化时重新创建Skia表面 skia_surface = create_skia_surface(&window).unwrap(); } WindowEvent::RedrawRequested => { // 获取Skia画布开始绘图 let mut canvas = skia_surface.canvas(); // 清空背景为白色 canvas.clear(Color::WHITE); // 绘制一个蓝色填充圆,红色描边 let center = (window.inner_size().width as f32 / 2.0, window.inner_size().height as f32 / 2.0); let radius = center.0.min(center.1) - 20.0; let mut fill_paint = Paint::default(); fill_paint.set_color(Color::from_rgba(0, 128, 128, 255)); canvas.draw_circle(center, radius, &fill_paint); let mut stroke_paint = Paint::default(); stroke_paint.set_color(Color::RED); stroke_paint.set_stroke_width(10.0); stroke_paint.set_style(skia_safe::PaintStyle::Stroke); canvas.draw_circle(center, radius, &stroke_paint); // 提交绘制内容到窗口 skia_surface.flush_and_submit(); window.request_redraw(); } _ => {} }, Event::RedrawRequested(window_id) if window_id == window.id() => { window.request_redraw(); } _ => {} } }).unwrap(); } // 根据平台创建对应的Skia Surface fn create_skia_surface(window: &winit::window::Window) -> Option<Surface> { let size = window.inner_size(); let (width, height) = (size.width as i32, size.height as i32); // 获取原生窗口句柄 let raw_handle = window.raw_window_handle(); match raw_handle { // 桌面平台优先用GL后端 RawWindowHandle::Xlib(_) | RawWindowHandle::Wayland(_) | RawWindowHandle::Win32(_) | RawWindowHandle::MacOS(_) => { // 初始化GL上下文(skia-safe会自动处理) let gl_context = skia_safe::gpu::gl::Context::new_shared(None, None).ok()?; gl_context.make_current().ok()?; let fb_info = FramebufferInfo { fboid: 0, format: skia_safe::gpu::gl::Format::RGBA8, }; let render_target = BackendRenderTarget::new_gl( (width, height), None, 8, fb_info, ); Surface::from_backend_render_target( &gl_context, &render_target, SurfaceOrigin::BottomLeft, skia_safe::ColorType::RGBA8888, None, None, ) } // 移动平台(Android/iOS)用对应的原生后端,skia-safe会自动适配 RawWindowHandle::AndroidNdk(_) | RawWindowHandle::UiKit(_) => { Surface::from_raw_window_handle( raw_handle, (width, height), None, None, ) } // 嵌入式或其他平台fallback到软件渲染 _ => { Surface::new_raster_n32_premul((width as u32, height as u32), None) } } }
代码关键点解释
- 原生窗口句柄对接:通过
window.raw_window_handle()获取各平台的原生窗口句柄,直接传给skia-safe创建渲染表面,不需要额外的窗口中间层。 - 多平台渲染适配:针对不同平台选择对应的渲染后端:桌面用GL加速,移动平台用原生后端,嵌入式用软件渲染。
- 窗口大小处理:窗口 resize 时重新创建Skia表面,保证绘图区域和窗口匹配。
- 绘图逻辑:用skia-safe的原生API绘图,和你之前用tiny-skia的逻辑一致,但功能更完整,毕竟是官方的Skia绑定。
平台特殊注意事项
- Android/iOS:需要确保winit的移动平台配置正确(比如Android需要AndroidManifest.xml,iOS需要Info.plist),这些winit的文档里都有详细说明。
- 嵌入式平台:如果是嵌入式Linux(比如树莓派),winit支持Wayland/X11后端,搭配skia-safe的软件渲染或GL渲染都可以,不需要额外依赖。
- 减少依赖:如果不需要GPU加速,可以只启用skia-safe的
software特性,完全去掉GL相关依赖,适合资源有限的设备。
对比你之前的尝试
这个方案比你之前的两种尝试更优:
- 不需要依赖glutin,避免了它在移动平台的兼容性问题
- 不需要softbuffer,用skia-safe自己的渲染后端就能覆盖所有平台
- 保留了skia-safe的完整功能,比tiny-skia更强大
备注:内容来源于stack exchange,提问作者iccc
相关产品推荐
相关产品推荐

