mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
Re-implement PaintCallbacks With Support for WGPU (#1684)
* Re-implement PaintCallbacks With Support for WGPU This makes breaking changes to the PaintCallback system, but makes it flexible enough to support both the WGPU and glow backends with custom rendering. Also adds a WGPU equivalent to the glow demo for custom painting.
This commit is contained in:
@@ -11,9 +11,11 @@ pub struct Custom3d {
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
pub fn new(gl: &glow::Context) -> Self {
|
||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Self {
|
||||
Self {
|
||||
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(gl))),
|
||||
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(
|
||||
cc.gl.as_ref().expect("GL Enabled"),
|
||||
))),
|
||||
angle: 0.0,
|
||||
}
|
||||
}
|
||||
@@ -58,15 +60,13 @@ impl Custom3d {
|
||||
let angle = self.angle;
|
||||
let rotating_triangle = self.rotating_triangle.clone();
|
||||
|
||||
let cb = egui_glow::CallbackFn::new(move |_info, painter| {
|
||||
rotating_triangle.lock().paint(painter.gl(), angle);
|
||||
});
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: std::sync::Arc::new(move |_info, render_ctx| {
|
||||
if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() {
|
||||
rotating_triangle.lock().paint(painter.gl(), angle);
|
||||
} else {
|
||||
eprintln!("Can't do custom painting because we are not using a glow context");
|
||||
}
|
||||
}),
|
||||
callback: Arc::new(cb),
|
||||
};
|
||||
ui.painter().add(callback);
|
||||
}
|
||||
177
egui_demo_app/src/apps/custom3d_wgpu.rs
Normal file
177
egui_demo_app/src/apps/custom3d_wgpu.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
39
egui_demo_app/src/apps/custom3d_wgpu_shader.wgsl
Normal file
39
egui_demo_app/src/apps/custom3d_wgpu_shader.wgsl
Normal file
@@ -0,0 +1,39 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
#[cfg(feature = "glow")]
|
||||
mod custom3d;
|
||||
#[cfg(all(feature = "glow", not(feature = "wgpu")))]
|
||||
mod custom3d_glow;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
mod custom3d_wgpu;
|
||||
|
||||
mod fractal_clock;
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod http_app;
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
pub use custom3d::Custom3d;
|
||||
#[cfg(all(feature = "glow", not(feature = "wgpu")))]
|
||||
pub use custom3d_glow::Custom3d;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub use custom3d_wgpu::Custom3d;
|
||||
|
||||
pub use fractal_clock::FractalClock;
|
||||
|
||||
|
||||
@@ -92,9 +92,7 @@ pub struct State {
|
||||
/// Wraps many demo/test apps into one.
|
||||
pub struct WrapApp {
|
||||
state: State,
|
||||
// not serialized (because it contains OpenGL buffers etc)
|
||||
#[cfg(feature = "glow")]
|
||||
custom3d: Option<crate::apps::Custom3d>,
|
||||
custom3d: crate::apps::Custom3d,
|
||||
dropped_files: Vec<egui::DroppedFile>,
|
||||
}
|
||||
|
||||
@@ -103,8 +101,7 @@ impl WrapApp {
|
||||
#[allow(unused_mut)]
|
||||
let mut slf = Self {
|
||||
state: State::default(),
|
||||
#[cfg(feature = "glow")]
|
||||
custom3d: cc.gl.as_ref().map(|gl| crate::apps::Custom3d::new(gl)),
|
||||
custom3d: crate::apps::Custom3d::new(cc),
|
||||
dropped_files: Default::default(),
|
||||
};
|
||||
|
||||
@@ -149,14 +146,11 @@ impl WrapApp {
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
if let Some(custom3d) = &mut self.custom3d {
|
||||
vec.push((
|
||||
"🔺 3D painting",
|
||||
"custom3e",
|
||||
custom3d as &mut dyn eframe::App,
|
||||
));
|
||||
}
|
||||
vec.push((
|
||||
"🔺 3D painting",
|
||||
"custom3d",
|
||||
&mut self.custom3d as &mut dyn eframe::App,
|
||||
));
|
||||
|
||||
vec.push((
|
||||
"🎨 Color test",
|
||||
@@ -224,9 +218,7 @@ impl eframe::App for WrapApp {
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
fn on_exit(&mut self, gl: Option<&glow::Context>) {
|
||||
if let Some(custom3d) = &mut self.custom3d {
|
||||
custom3d.on_exit(gl);
|
||||
}
|
||||
self.custom3d.on_exit(gl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user