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>) -> Option { // 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 wgpu_render_state = cc.wgpu_render_state.as_ref()?; let device = &wgpu_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: &[Some(wgpu_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. wgpu_render_state .renderer .write() .paint_callback_resources .insert(TriangleRenderResources { pipeline, bind_group, uniform_buffer, }); Some(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| { egui::ScrollArea::both() .auto_shrink([false; 2]) .show(ui, |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, render_pass, paint_callback_resources| { let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap(); resources.paint(render_pass); }); 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<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) { // Draw our triangle! render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.bind_group, &[]); render_pass.draw(0..3, 0..1); } }