egui/crates/egui_demo_app/src/apps/custom3d_wgpu.rs

177 lines
6.5 KiB
Rust

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<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 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);
}
}