
Slint WGPU 纹理导入实战用 set_rendering_notifier 把 WGPU 渲染结果嵌入 Slint 场景【免费下载链接】slintSlint is an open-source declarative GUI toolkit to build native user interfaces for Rust, C, JavaScript, or Python apps.项目地址: https://gitcode.com/GitHub_Trending/sl/slint导读在 Slint 中渲染工作由内置渲染器统一完成但有时你希望把自研的图形效果例如 WGPU 中的实时 3D 场景、粒子特效无缝嵌入到 Slint 的 UI 场景中。本文以仓库中的 wgpu_texture 示例 为骨架完整讲解先由 WGPU 渲染一张纹理再将该纹理导入slint::Image并显示在 Slint 的Image元素上的完整链路覆盖slint::Window::set_rendering_notifier的四个渲染阶段、RenderingState/GraphicsAPI匹配、slint::Image::try_from(wgpu::Texture)导入、以及通过request_redraw()驱动连续动画的写法。读完本文你将能在自己的 Rust Slint 项目中实现Slint UI 与外部图形 API 共融的混合渲染方案。示例运行与项目结构示例位于仓库 examples/wgpu_texture包含以下文件文件作用Cargo.toml项目依赖与构建配置启用unstable-wgpu-30特性main.rs程序入口仅调用wgpu_texture_lib::main()lib.rs核心逻辑渲染器实现、后台选择、渲染通知回调scene.slintSlint UI文本说明、Image元素、控制立方体颜色的三个 Slidershader.wgslWGPU 片段着色器SDF有向距离场光线步进渲染一个旋转圆角立方体运行方式在仓库根目录下执行cargo run -p wgpu_texture应用会打开一个标题为 Slint WGPU Texture Example 的窗口上部是 Slint 渲染的文字说明中间是 WGPU 离屏渲染出的旋转立方体纹理底部是三个 Slider 可实时调整立方体的 RGB 光照颜色。核心机制set_rendering_notifier渲染通知整个例子的技术支柱是slint::Window上的set_rendering_notifier。其声明位于 internal/core/api.rspub fn set_rendering_notifier( self, callback: impl FnMut(RenderingState, GraphicsAPI) static, ) - Result(), SetRenderingNotifierError { self.0.window_adapter().renderer().set_rendering_notifier(Box::new(callback)) }回调在渲染的不同阶段被调用回调会收到两个参数当前渲染阶段RenderingState和图形 API 描述GraphicsAPI。示例在回调中按阶段分派处理见 lib.rs。RenderingState 四个阶段RenderingState定义于 internal/core/api.rs为#[non_exhaustive]枚举包含四个阶段RenderingSetup窗口已创建、图形适配器/上下文已初始化。示例在此阶段拿到 WGPU 的device与queue创建DemoRenderer编译着色器、创建渲染管线与纹理。BeforeRenderingSlint 场景即将绘制。示例在此阶段执行 WGPU 渲染到纹理并把结果设为 Slint 的Image源。AfterRendering场景已绘制完成但后备缓冲尚未提交显示例如 GL 的 swap buffers 之前。可用于叠加绘制等。RenderingTeardown窗口即将销毁、需要释放图形资源。示例在此阶段drop(renderer.take())释放 WGPU 资源。文档注释还特别提醒当使用 OpenGL 渲染时回调执行期间 GL 上下文是当前的可以安全调用 GL 函数但必须完整保存/恢复上下文状态如TEXTURE_BINDING_2D、ARRAY_BUFFER_BINDING。对 WGPU 而言则是拿到设备与队列句柄直接使用。GraphicsAPI回调中的图形 API 信息回调的第二个参数GraphicsAPI是一个对渲染器所用底层图形 API 的低层访问描述见 internal/core/api.rs。示例的匹配写法if let slint::GraphicsAPI::WGPU30 { device, queue, .. } graphics_api { renderer Some(DemoRenderer::new(device, queue)); }GraphicsAPI同样标注#[non_exhaustive]包含NativeOpenGL、WGPU29、WGPU30等变体WGPU30需启用unstable-wgpu-30特性。注意这个枚举还存在于 internal/core/window.rsC 侧版本C 侧GraphicsAPI只有NativeOpenGL与Inaccessible两个变体——因为 WGPU 资源无法从 C 侧访问属于 Rust 专属能力。强制选择 WGPU 渲染后端要让回调在RenderingSetup阶段收到GraphicsAPI::WGPU30必须先让 Slint 使用 WGPU 渲染后端。示例通过BackendSelector强制选择let mut wgpu_settings WGPUSettings::default(); wgpu_settings.device_required_features wgpu::Features::IMMEDIATES; wgpu_settings.device_required_limits.max_immediate_size 16; slint::BackendSelector::new() .require_wgpu_30(WGPUConfiguration::Automatic(wgpu_settings)) .select() .expect(Unable to create Slint backend with WGPU based renderer);require_wgpu_30的实现在 internal/backends/selector/api.rs它将请求的图形 API 记录到requested_graphics_api随后select()创建对应后端。从源码注释可以看到该接口位于unstable-wgpu-30feature 标志之后在新版 WGPU 发布时可能被移除或变更因此属于不稳定 API。Cargo 依赖中必须显式启用该特性见 Cargo.toml[dependencies] slint { path ../../api/rs/slint, features [unstable-wgpu-30, backend-android-activity-06] } bytemuck { workspace true } wgpu-30 { workspace true, features [wgsl] }DemoRenderer用 WGPU 离屏渲染到纹理渲染管线与 push constantsDemoRenderer::new见 lib.rs完成 WGPU 初始化用include_str!(shader.wgsl)编译 WGSL 着色器创建管线布局并指定immediate_size: 16启用 immediatespush constants 机制创建渲染管线顶点着色器入口vs_main、片元着色器入口fs_main目标格式为Rgba8UnormSrgb创建一张 320×200 的离屏纹理作为渲染目标。片元着色器从shader.wgsl读取 16 字节的PushConstants即light_color_and_time: vec4f32该结构在 Rust 侧对应#[repr(C)] #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] struct PushConstants { light_color_and_time: [f32; 4], }写入方式rpass.set_pipeline(self.pipeline); rpass.set_immediates(0, bytemuck::bytes_of(push_constants)); rpass.draw(0..3, 0..1);着色器SDF 光线步进立方体shader.wgsl 使用经典的 SDF有向距离场 光线步进算法绘制一个绕 Y 轴与 Z 轴旋转的圆角立方体sdRoundBox圆角立方体的有向距离函数scene将光线命中点先绕 Z 再绕 Y 旋转旋转角度取 push constants 中的时间iTime再求立方体距离normal基于 iq 的四点差分法求表面法线源码注释保留了原作者链接render90 次迭代的光线步进 漫反射光照计算fs_main把片段坐标映射到着色器内部坐标系并传入来自 push constants 的灯光颜色。每次渲染时 Rust 侧把当前时间毫秒除以 500 作为旋转速度因子配合三个 Slider 的 RGB 值一起打包进 push constants见 lib.rs。渲染到纹理并保持尺寸同步DemoRenderer::render见 lib.rs每次渲染前检查目标纹理尺寸是否与 Slint 侧Image元素当前尺寸一致不一致就重建纹理if self.texture.size().width ! width || self.texture.size().height ! height { self.texture Self::create_texture(self.device, width, height); }随后创建命令编码器把离屏纹理作为RENDER_ATTACHMENT颜色附件清屏色为绿色执行渲染后queue.submit最后把纹理克隆返回给调用方。把纹理导入 Slintslint::Image::try_from在BeforeRendering阶段示例把 WGPU 渲染结果与 UI 状态接合slint::RenderingState::BeforeRendering { if let (Some(renderer), Some(app)) (renderer.as_mut(), app_weak.upgrade()) { let texture renderer.render( app.get_selected_red(), app.get_selected_green(), app.get_selected_blue(), app.get_requested_texture_width() as u32, app.get_requested_texture_height() as u32, ); app.set_texture(slint::Image::try_from(texture).unwrap()); app.window().request_redraw(); } }要点解读从组件读取 UI 状态app.get_selected_red()等是slint::include_modules!()根据 scene.slint 中out property float selected-red自动生成的 getter。同理get_requested_texture_width/Height对应out property int requested-texture-width: image.width / 1phx; out property int requested-texture-height: image.height / 1phx;1phx是 Slint 的物理像素单位这里把Image元素逻辑尺寸换算成物理像素作为离屏纹理的分辨率保证纹理清晰度与显示尺寸匹配。纹理导入slint::Image::try_from(texture)把wgpu::Texture直接包装为slint::Image这是 WGPU 纹理导入 Slint 的关键 API。随后app.set_texture(...)写入in property image texture该属性通过双向绑定直接驱动Image元素的sourcein property image texture image.source;连续动画驱动因为旋转立方体是持续动画回调每次BeforeRendering渲染完新帧后调用app.window().request_redraw()请求重绘从而形成渲染 → 通知 → 重绘的循环。阶段编排与资源生命周期完整回调整体结构lib.rsmatch state { slint::RenderingState::RenderingSetup { if let slint::GraphicsAPI::WGPU30 { device, queue, .. } graphics_api { renderer Some(DemoRenderer::new(device, queue)); } } slint::RenderingState::BeforeRendering { /* 渲染纹理 → 导入 Image → 请求重绘 */ } slint::RenderingState::AfterRendering {} slint::RenderingState::RenderingTeardown { drop(renderer.take()); } _ {} }这里有一个 Rust 所有权的细节app_weak在回调外通过app.as_weak()获取避免回调闭包与App之间产生循环引用renderer则作为闭包捕获的可变状态在RenderingSetup创建、BeforeRendering使用、RenderingTeardown释放。由于回调是FnMut且可能被多次调用RenderingSetup分支每次覆盖renderer也是安全的首次为None之后重建。渲染通知在渲染器内部的触发方式set_rendering_notifier最终落入各渲染器实现。从 internal/renderers/femtovg/lib.rs 可以看到典型实现模式——在窗口初始化的渲染设置阶段渲染器持有回调并主动触发if let Some(callback) self.rendering_notifier.borrow_mut().as_mut() { self.with_graphics_api(|api| { callback.notify(RenderingState::RenderingSetup, api) })?; }即渲染器在自身渲染循环的对应时点回调用户注册的 notifier并伴随传入当前GraphicsAPI。这也解释了示例回调中的模式匹配为何可行graphics_api就是渲染器在实际渲染时正在使用的图形 API。另外值得注意的是并非所有渲染器都支持该接口——internal/renderers/anyrender/lib.rs 中set_rendering_notifier直接返回Err(SetRenderingNotifierError::Unsupported)说明在选用不支持的渲染器时该调用会失败需要在初始化阶段处理Result示例对select()与set_rendering_notifier都用了expect。总结混合渲染模式的通用套路从本示例可以提炼出一个可复用的Slint 外部图形 API混合渲染模板选后端用BackendSelector::require_wgpu_30(WGPUConfiguration::Automatic(settings))强制启用 WGPU 渲染后端需unstable-wgpu-30特性。注册通知window.set_rendering_notifier(callback)注册阶段回调并处理SetRenderingNotifierError例如渲染器不支持。Setup 阶段建资源匹配GraphicsAPI::WGPU30 { device, queue, .. }创建离屏纹理与渲染管线等长期资源。BeforeRendering 阶段产帧用 WGPU 把自定义效果渲染到离屏纹理slint::Image::try_from(texture)导入通过属性绑定设置到Image元素需要动画时调用request_redraw()续帧。Teardown 阶段释放drop掉 WGPU 资源避免泄漏。配合 scene.slint 的1phx物理像素换算与双向绑定纹理分辨率会始终跟随显示尺寸而 Slider 控件则可作为渲染参数的实时调节入口。这套模式同样适用于粒子系统、实时流体、视频解码输出等外来内容 Slint UI场景。【免费下载链接】slintSlint is an open-source declarative GUI toolkit to build native user interfaces for Rust, C, JavaScript, or Python apps.项目地址: https://gitcode.com/GitHub_Trending/sl/slint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考