mirror of https://github.com/emilk/egui.git
Browse Source
* 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.pull/1693/head
Zicklag
2 years ago
committed by
GitHub
22 changed files with 594 additions and 129 deletions
@ -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); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
Loading…
Reference in new issue