2022-05-28 15:52:36 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use eframe::{
|
|
|
|
egui_wgpu::{self, wgpu},
|
|
|
|
wgpu::util::DeviceExt,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub struct Custom3d {
|
|
|
|
angle: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Custom3d {
|
2022-08-20 17:54:18 +00:00
|
|
|
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
2022-05-28 15:52:36 +00:00
|
|
|
// 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.
|
2022-08-20 17:54:18 +00:00
|
|
|
let wgpu_render_state = cc.wgpu_render_state.as_ref()?;
|
2022-05-28 15:52:36 +00:00
|
|
|
|
2022-08-08 10:15:31 +00:00
|
|
|
let device = &wgpu_render_state.device;
|
2022-05-28 15:52:36 +00:00
|
|
|
|
2022-07-03 13:43:39 +00:00
|
|
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
2022-05-28 15:52:36 +00:00
|
|
|
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",
|
2022-08-08 10:15:31 +00:00
|
|
|
targets: &[Some(wgpu_render_state.target_format.into())],
|
2022-05-28 15:52:36 +00:00
|
|
|
}),
|
|
|
|
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.
|
2022-08-08 10:15:31 +00:00
|
|
|
wgpu_render_state
|
2022-09-07 12:20:21 +00:00
|
|
|
.renderer
|
2022-05-28 15:52:36 +00:00
|
|
|
.write()
|
|
|
|
.paint_callback_resources
|
|
|
|
.insert(TriangleRenderResources {
|
|
|
|
pipeline,
|
|
|
|
bind_group,
|
|
|
|
uniform_buffer,
|
|
|
|
});
|
|
|
|
|
2022-08-20 17:54:18 +00:00
|
|
|
Some(Self { angle: 0.0 })
|
2022-05-28 15:52:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl eframe::App for Custom3d {
|
|
|
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
2022-07-28 22:06:08 +00:00
|
|
|
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!());
|
|
|
|
});
|
2022-05-28 15:52:36 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
})
|
2022-09-07 12:20:21 +00:00
|
|
|
.paint(move |_info, render_pass, paint_callback_resources| {
|
2022-05-28 15:52:36 +00:00
|
|
|
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
2022-09-07 12:20:21 +00:00
|
|
|
resources.paint(render_pass);
|
2022-05-28 15:52:36 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
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]));
|
|
|
|
}
|
|
|
|
|
2022-09-07 12:20:21 +00:00
|
|
|
fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
|
2022-05-28 15:52:36 +00:00
|
|
|
// Draw our triangle!
|
2022-09-07 12:20:21 +00:00
|
|
|
render_pass.set_pipeline(&self.pipeline);
|
|
|
|
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
|
|
|
render_pass.draw(0..3, 0..1);
|
2022-05-28 15:52:36 +00:00
|
|
|
}
|
|
|
|
}
|