use bytemuck::{Pod, Zeroable};
use imgui::*;
use imgui_wgpu::{Renderer, RendererConfig, Texture, TextureConfig};
use imgui_winit_support::WinitPlatform;
use pollster::block_on;
use std::{sync::Arc, time::Instant};
use wgpu::{include_wgsl, util::DeviceExt, Extent3d};
use winit::{
application::ApplicationHandler,
dpi::LogicalSize,
event::{Event, WindowEvent},
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
keyboard::{Key, NamedKey},
window::Window,
};
const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5, 1.0,
);
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct Vertex {
_pos: [f32; 4],
_tex_coord: [f32; 2],
}
fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex {
Vertex {
_pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0],
_tex_coord: [tc[0] as f32, tc[1] as f32],
}
}
fn create_vertices() -> (Vec<Vertex>, Vec<u16>) {
let vertex_data = [
vertex([-1, -1, 1], [0, 0]),
vertex([1, -1, 1], [1, 0]),
vertex([1, 1, 1], [1, 1]),
vertex([-1, 1, 1], [0, 1]),
vertex([-1, 1, -1], [1, 0]),
vertex([1, 1, -1], [0, 0]),
vertex([1, -1, -1], [0, 1]),
vertex([-1, -1, -1], [1, 1]),
vertex([1, -1, -1], [0, 0]),
vertex([1, 1, -1], [1, 0]),
vertex([1, 1, 1], [1, 1]),
vertex([1, -1, 1], [0, 1]),
vertex([-1, -1, 1], [1, 0]),
vertex([-1, 1, 1], [0, 0]),
vertex([-1, 1, -1], [0, 1]),
vertex([-1, -1, -1], [1, 1]),
vertex([1, 1, -1], [1, 0]),
vertex([-1, 1, -1], [0, 0]),
vertex([-1, 1, 1], [0, 1]),
vertex([1, 1, 1], [1, 1]),
vertex([1, -1, 1], [0, 0]),
vertex([-1, -1, 1], [1, 0]),
vertex([-1, -1, -1], [1, 1]),
vertex([1, -1, -1], [0, 1]),
];
let index_data: &[u16] = &[
0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, 16, 17, 18, 18, 19, 16, 20, 21, 22, 22, 23, 20, ];
(vertex_data.to_vec(), index_data.to_vec())
}
fn create_texels(size: usize) -> Vec<u8> {
(0..size * size)
.map(|id| {
let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
let (mut x, mut y, mut count) = (cx, cy, 0);
while count < 0xFF && x * x + y * y < 4.0 {
let old_x = x;
x = x * x - y * y + cx;
y = 2.0 * old_x * y + cy;
count += 1;
}
count
})
.collect()
}
struct Example {
vertex_buf: wgpu::Buffer,
index_buf: wgpu::Buffer,
index_count: usize,
bind_group: wgpu::BindGroup,
uniform_buf: wgpu::Buffer,
pipeline: wgpu::RenderPipeline,
time: f32,
}
impl Example {
fn generate_matrix(aspect_ratio: f32) -> cgmath::Matrix4<f32> {
let mx_projection = cgmath::perspective(cgmath::Deg(45f32), aspect_ratio, 1.0, 10.0);
let mx_view = cgmath::Matrix4::look_at_rh(
cgmath::Point3::new(1.5f32, -5.0, 3.0),
cgmath::Point3::new(0f32, 0.0, 0.0),
cgmath::Vector3::unit_z(),
);
let mx_correction = OPENGL_TO_WGPU_MATRIX;
mx_correction * mx_projection * mx_view
}
}
impl Example {
fn init(
config: &wgpu::SurfaceConfiguration,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> Self {
use std::mem;
let vertex_size = mem::size_of::<Vertex>();
let (vertex_data, index_data) = create_vertices();
let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&vertex_data),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&index_data),
usage: wgpu::BufferUsages::INDEX,
});
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: wgpu::BufferSize::new(64),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
sample_type: wgpu::TextureSampleType::Uint,
view_dimension: wgpu::TextureViewDimension::D2,
},
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let size = 256u32;
let texels = create_texels(size as usize);
let texture_extent = wgpu::Extent3d {
width: size,
height: size,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: texture_extent,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Uint,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[wgpu::TextureFormat::R8Uint],
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
queue.write_texture(
texture.as_image_copy(),
&texels,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(size),
rows_per_image: None,
},
texture_extent,
);
let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
let mx_ref: &[f32; 16] = mx_total.as_ref();
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Uniform Buffer"),
contents: bytemuck::cast_slice(mx_ref),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&texture_view),
},
],
label: None,
});
let shader = device.create_shader_module(include_wgsl!("../resources/cube.wgsl"));
let vertex_buffers = [wgpu::VertexBufferLayout {
array_stride: vertex_size as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x4,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 4 * 4,
shader_location: 1,
},
],
}];
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &vertex_buffers,
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(config.format.into())],
}),
primitive: wgpu::PrimitiveState {
cull_mode: Some(wgpu::Face::Back),
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
Example {
vertex_buf,
index_buf,
index_count: index_data.len(),
bind_group,
uniform_buf,
pipeline,
time: 0.0,
}
}
fn update(&mut self, delta_time: f32) {
self.time += delta_time;
}
fn setup_camera(&mut self, queue: &wgpu::Queue, size: [f32; 2]) {
let mx_total = Self::generate_matrix(size[0] / size[1]);
let mx_ref: &[f32; 16] = mx_total.as_ref();
queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref));
}
fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
rpass.push_debug_group("Prepare data for draw.");
rpass.set_pipeline(&self.pipeline);
rpass.set_bind_group(0, &self.bind_group, &[]);
rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16);
rpass.set_vertex_buffer(0, self.vertex_buf.slice(..));
rpass.pop_debug_group();
rpass.insert_debug_marker("Draw!");
rpass.draw_indexed(0..self.index_count as u32, 0, 0..1);
}
queue.submit(Some(encoder.finish()));
}
}
struct ImguiState {
context: imgui::Context,
platform: WinitPlatform,
renderer: Renderer,
example: Example,
example_size: [f32; 2],
example_texture_id: TextureId,
last_frame: Instant,
last_cursor: Option<MouseCursor>,
}
struct AppWindow {
device: wgpu::Device,
queue: wgpu::Queue,
window: Arc<Window>,
surface_desc: wgpu::SurfaceConfiguration,
surface: wgpu::Surface<'static>,
hidpi_factor: f64,
imgui: Option<ImguiState>,
}
#[derive(Default)]
struct App {
window: Option<AppWindow>,
}
impl AppWindow {
fn setup_gpu(event_loop: &ActiveEventLoop) -> Self {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..wgpu::InstanceDescriptor::new_with_display_handle(Box::new(
event_loop.owned_display_handle(),
))
});
let window = {
let version = env!("CARGO_PKG_VERSION");
let size = LogicalSize::new(1280.0, 720.0);
let attributes = Window::default_attributes()
.with_inner_size(size)
.with_title(format!("imgui-wgpu {version}"));
Arc::new(event_loop.create_window(attributes).unwrap())
};
let size = window.inner_size();
let hidpi_factor = window.scale_factor();
let surface = instance.create_surface(window.clone()).unwrap();
let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}))
.unwrap();
let (device, queue) =
block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).unwrap();
let surface_desc = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Fifo,
desired_maximum_frame_latency: 2,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![wgpu::TextureFormat::Bgra8Unorm],
};
surface.configure(&device, &surface_desc);
let imgui = None;
Self {
device,
queue,
window,
surface_desc,
surface,
hidpi_factor,
imgui,
}
}
fn setup_imgui(&mut self) {
let mut context = imgui::Context::create();
let mut platform = imgui_winit_support::WinitPlatform::new(&mut context);
platform.attach_window(
context.io_mut(),
&self.window,
imgui_winit_support::HiDpiMode::Default,
);
context.set_ini_filename(None);
let font_size = (13.0 * self.hidpi_factor) as f32;
context.io_mut().font_global_scale = (1.0 / self.hidpi_factor) as f32;
context.fonts().add_font(&[FontSource::DefaultFontData {
config: Some(imgui::FontConfig {
size_pixels: font_size,
..Default::default()
}),
}]);
let renderer_config = RendererConfig {
texture_format: self.surface_desc.format,
..Default::default()
};
let mut renderer = Renderer::new(&mut context, &self.device, &self.queue, renderer_config);
let last_frame = Instant::now();
let last_cursor = None;
let example_size: [f32; 2] = [640.0, 480.0];
let example = Example::init(&self.surface_desc, &self.device, &self.queue);
let texture_config = TextureConfig {
size: wgpu::Extent3d {
width: example_size[0] as u32,
height: example_size[1] as u32,
..Default::default()
},
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
..Default::default()
};
let texture = Texture::new(&self.device, &renderer, texture_config);
let example_texture_id = renderer.textures.insert(texture);
self.imgui = Some(ImguiState {
context,
platform,
renderer,
example,
example_size,
example_texture_id,
last_frame,
last_cursor,
})
}
fn new(event_loop: &ActiveEventLoop) -> Self {
let mut window = Self::setup_gpu(event_loop);
window.setup_imgui();
window
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.window = Some(AppWindow::new(event_loop));
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
let window = self.window.as_mut().unwrap();
let imgui = window.imgui.as_mut().unwrap();
match &event {
WindowEvent::Resized(size) => {
window.surface_desc.width = size.width;
window.surface_desc.height = size.height;
window
.surface
.configure(&window.device, &window.surface_desc);
}
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
window.hidpi_factor = *scale_factor;
let font_size = (13.0 * window.hidpi_factor) as f32;
imgui.context.fonts().clear();
imgui
.context
.fonts()
.add_font(&[FontSource::DefaultFontData {
config: Some(imgui::FontConfig {
oversample_h: 1,
pixel_snap_h: true,
size_pixels: font_size,
..Default::default()
}),
}]);
imgui.renderer.reload_font_texture(
&mut imgui.context,
&window.device,
&window.queue,
);
}
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::KeyboardInput { event, .. } => {
if let Key::Named(NamedKey::Escape) = event.logical_key {
if event.state.is_pressed() {
event_loop.exit();
}
}
}
WindowEvent::RedrawRequested => {
let now = Instant::now();
imgui
.context
.io_mut()
.update_delta_time(now - imgui.last_frame);
imgui.last_frame = now;
let frame = match window.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame) => frame,
wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
wgpu::CurrentSurfaceTexture::Timeout
| wgpu::CurrentSurfaceTexture::Occluded => return,
wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
window
.surface
.configure(&window.device, &window.surface_desc);
return;
}
other => {
eprintln!("get_current_texture error: {other:?}");
return;
}
};
imgui
.platform
.prepare_frame(imgui.context.io_mut(), &window.window)
.expect("Failed to prepare frame");
let ui = imgui.context.frame();
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
imgui.example.update(ui.io().delta_time);
imgui
.example
.setup_camera(&window.queue, ui.io().display_size);
imgui.example.render(&view, &window.device, &window.queue);
let mut new_example_size: Option<[f32; 2]> = None;
ui.window("Cube")
.size([512.0, 512.0], Condition::FirstUseEver)
.build(|| {
new_example_size = Some(ui.content_region_avail());
imgui::Image::new(imgui.example_texture_id, new_example_size.unwrap())
.build(ui);
});
if let Some(size) = new_example_size {
if size != imgui.example_size && size[0] >= 1.0 && size[1] >= 1.0 {
imgui.example_size = size;
let scale = &ui.io().display_framebuffer_scale;
let texture_config = TextureConfig {
size: Extent3d {
width: (imgui.example_size[0] * scale[0]) as u32,
height: (imgui.example_size[1] * scale[1]) as u32,
..Default::default()
},
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
..Default::default()
};
imgui.renderer.textures.replace(
imgui.example_texture_id,
Texture::new(&window.device, &imgui.renderer, texture_config),
);
}
imgui.example.setup_camera(&window.queue, size);
imgui.example.render(
imgui
.renderer
.textures
.get(imgui.example_texture_id)
.unwrap()
.view(),
&window.device,
&window.queue,
);
}
let mut encoder: wgpu::CommandEncoder = window
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
if imgui.last_cursor != ui.mouse_cursor() {
imgui.last_cursor = ui.mouse_cursor();
imgui.platform.prepare_render(ui, &window.window);
}
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
imgui
.renderer
.render(
imgui.context.render(),
&window.queue,
&window.device,
&mut rpass,
)
.expect("Rendering failed");
drop(rpass);
window.queue.submit(Some(encoder.finish()));
frame.present();
}
_ => (),
}
imgui.platform.handle_event::<()>(
imgui.context.io_mut(),
&window.window,
&Event::WindowEvent { window_id, event },
);
}
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: ()) {
let window = self.window.as_mut().unwrap();
let imgui = window.imgui.as_mut().unwrap();
imgui.platform.handle_event::<()>(
imgui.context.io_mut(),
&window.window,
&Event::UserEvent(event),
);
}
fn device_event(
&mut self,
_event_loop: &ActiveEventLoop,
device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
let window = self.window.as_mut().unwrap();
let imgui = window.imgui.as_mut().unwrap();
imgui.platform.handle_event::<()>(
imgui.context.io_mut(),
&window.window,
&Event::DeviceEvent { device_id, event },
);
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
let window = self.window.as_mut().unwrap();
let imgui = window.imgui.as_mut().unwrap();
window.window.request_redraw();
imgui.platform.handle_event::<()>(
imgui.context.io_mut(),
&window.window,
&Event::AboutToWait,
);
}
}
fn main() {
env_logger::init();
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
event_loop.run_app(&mut App::default()).unwrap();
}