Created
May 26, 2022 19:59
-
-
Save zicklag/b9c1be31ec599fd940379cecafa1751b to your computer and use it in GitHub Desktop.
Egui WGPU custom rendering example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::sync::Arc; | |
| use eframe::{ | |
| egui_wgpu::{self, wgpu}, | |
| wgpu::util::DeviceExt, | |
| }; | |
| pub struct Custom3d { | |
| angle: f32, | |
| } | |
| impl Custom3d { | |
| pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Self { | |
| // Get the WGPU render state from the eframe creation context. This can also be retrieved | |
| // from `eframe::Frame` when you don't have a `CreationContext` available. | |
| let render_state = cc.render_state.as_ref().expect("WGPU enabled"); | |
| let device = &render_state.device; | |
| let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor { | |
| label: None, | |
| source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()), | |
| }); | |
| let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { | |
| label: None, | |
| entries: &[wgpu::BindGroupLayoutEntry { | |
| binding: 0, | |
| visibility: wgpu::ShaderStages::VERTEX, | |
| ty: wgpu::BindingType::Buffer { | |
| ty: wgpu::BufferBindingType::Uniform, | |
| has_dynamic_offset: false, | |
| min_binding_size: None, | |
| }, | |
| count: None, | |
| }], | |
| }); | |
| let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { | |
| label: None, | |
| bind_group_layouts: &[&bind_group_layout], | |
| push_constant_ranges: &[], | |
| }); | |
| let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { | |
| label: None, | |
| layout: Some(&pipeline_layout), | |
| vertex: wgpu::VertexState { | |
| module: &shader, | |
| entry_point: "vs_main", | |
| buffers: &[], | |
| }, | |
| fragment: Some(wgpu::FragmentState { | |
| module: &shader, | |
| entry_point: "fs_main", | |
| targets: &[render_state.target_format.into()], | |
| }), | |
| primitive: wgpu::PrimitiveState::default(), | |
| depth_stencil: None, | |
| multisample: wgpu::MultisampleState::default(), | |
| multiview: None, | |
| }); | |
| let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { | |
| label: None, | |
| contents: bytemuck::cast_slice(&[0.0]), | |
| usage: wgpu::BufferUsages::COPY_DST | |
| | wgpu::BufferUsages::MAP_WRITE | |
| | wgpu::BufferUsages::UNIFORM, | |
| }); | |
| let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { | |
| label: None, | |
| layout: &bind_group_layout, | |
| entries: &[wgpu::BindGroupEntry { | |
| binding: 0, | |
| resource: uniform_buffer.as_entire_binding(), | |
| }], | |
| }); | |
| // Because the graphics pipeline must have the same lifetime as the egui render pass, | |
| // instead of storing the pipeline in our `Custom3D` struct, we insert it into the | |
| // `paint_callback_resources` type map, which is stored alongside the render pass. | |
| render_state | |
| .egui_rpass | |
| .write() | |
| .paint_callback_resources | |
| .insert(TriangleRenderResources { | |
| pipeline, | |
| bind_group, | |
| uniform_buffer, | |
| }); | |
| Self { angle: 0.0 } | |
| } | |
| } | |
| impl eframe::App for Custom3d { | |
| fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { | |
| egui::CentralPanel::default().show(ctx, |ui| { | |
| ui.horizontal(|ui| { | |
| ui.spacing_mut().item_spacing.x = 0.0; | |
| ui.label("The triangle is being painted using "); | |
| ui.hyperlink_to("WGPU", "https://wgpu.rs"); | |
| ui.label(" (Portable Rust graphics API awesomeness)"); | |
| }); | |
| ui.label( | |
| "It's not a very impressive demo, but it shows you can embed 3D inside of egui.", | |
| ); | |
| egui::Frame::canvas(ui.style()).show(ui, |ui| { | |
| self.custom_painting(ui); | |
| }); | |
| ui.label("Drag to rotate!"); | |
| ui.add(egui_demo_lib::egui_github_link_file!()); | |
| }); | |
| } | |
| } | |
| impl Custom3d { | |
| fn custom_painting(&mut self, ui: &mut egui::Ui) { | |
| let (rect, response) = | |
| ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); | |
| self.angle += response.drag_delta().x * 0.01; | |
| // Clone locals so we can move them into the paint callback: | |
| let angle = self.angle; | |
| // The callback function for WGPU is in two stages: prepare, and paint. | |
| // | |
| // The prepare callback is called every frame before paint and is given access to the wgpu | |
| // Device and Queue, which can be used, for instance, to update buffers and uniforms before | |
| // rendering. | |
| // | |
| // The paint callback is called after prepare and is given access to the render pass, which | |
| // can be used to issue draw commands. | |
| let cb = egui_wgpu::CallbackFn::new() | |
| .prepare(move |device, queue, paint_callback_resources| { | |
| let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap(); | |
| resources.prepare(device, queue, angle); | |
| }) | |
| .paint(move |_info, rpass, paint_callback_resources| { | |
| let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap(); | |
| resources.paint(rpass); | |
| }); | |
| let callback = egui::PaintCallback { | |
| rect, | |
| callback: Arc::new(cb), | |
| }; | |
| ui.painter().add(callback); | |
| } | |
| } | |
| struct TriangleRenderResources { | |
| pipeline: wgpu::RenderPipeline, | |
| bind_group: wgpu::BindGroup, | |
| uniform_buffer: wgpu::Buffer, | |
| } | |
| impl TriangleRenderResources { | |
| fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) { | |
| // Update our uniform buffer with the angle from the UI | |
| queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[angle])); | |
| } | |
| fn paint<'rpass>(&'rpass self, rpass: &mut wgpu::RenderPass<'rpass>) { | |
| // Draw our triangle! | |
| rpass.set_pipeline(&self.pipeline); | |
| rpass.set_bind_group(0, &self.bind_group, &[]); | |
| rpass.draw(0..3, 0..1); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| struct VertexOut { | |
| [[location(0)]] color: vec4<f32>; | |
| [[builtin(position)]] position: vec4<f32>; | |
| }; | |
| struct Uniforms { | |
| angle: f32; | |
| }; | |
| [[group(0), binding(0)]] | |
| var<uniform> uniforms: Uniforms; | |
| var<private> v_positions: array<vec2<f32>, 3> = array<vec2<f32>, 3>( | |
| vec2<f32>(0.0, 1.0), | |
| vec2<f32>(1.0, -1.0), | |
| vec2<f32>(-1.0, -1.0), | |
| ); | |
| var<private> v_colors: array<vec4<f32>, 3> = array<vec4<f32>, 3>( | |
| vec4<f32>(1.0, 0.0, 0.0, 1.0), | |
| vec4<f32>(0.0, 1.0, 0.0, 1.0), | |
| vec4<f32>(0.0, 0.0, 1.0, 1.0), | |
| ); | |
| [[stage(vertex)]] | |
| fn vs_main([[builtin(vertex_index)]] v_idx: u32) -> VertexOut { | |
| var out: VertexOut; | |
| out.position = vec4<f32>(v_positions[v_idx], 0.0, 1.0); | |
| out.position.x = out.position.x * cos(uniforms.angle); | |
| out.color = v_colors[v_idx]; | |
| return out; | |
| } | |
| [[stage(fragment)]] | |
| fn fs_main(in: VertexOut) -> [[location(0)]] vec4<f32> { | |
| return in.color; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment