1
0
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:
Zicklag
2022-05-28 10:52:36 -05:00
committed by GitHub
parent 8173093c67
commit 1d9524cc59
22 changed files with 595 additions and 130 deletions

View File

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

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

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

View File

@@ -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;

View File

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