diff --git a/src/imgui_renderer.rs b/src/imgui_renderer.rs new file mode 100644 index 00000000..15b7551d --- /dev/null +++ b/src/imgui_renderer.rs @@ -0,0 +1,520 @@ +use std::convert::TryFrom; +use std::io; +use std::mem; + +use imgui; +use imgui::internal::RawWrapper; + +use crate::include_shader; + +#[derive(Debug, Clone)] +pub enum ImguiRendererError { + BadTexture(imgui::TextureId), +} + +pub struct ImguiRenderer { + render_pipeline: wgpu::RenderPipeline, + uniform_buffer: wgpu::Buffer, + uniform_bind_group: wgpu::BindGroup, + texture_layout: wgpu::BindGroupLayout, + textures: imgui::Textures, + clear_color: Option, +} + +impl ImguiRenderer { + pub fn new( + imgui: &mut imgui::Context, + device: &mut wgpu::Device, + format: wgpu::TextureFormat, + clear_color: Option, + ) -> Result { + // Link shaders + + let vs_spv: &[u8] = include_shader!("imgui.vert.spv"); + let fs_spv: &[u8] = include_shader!("imgui.frag.spv"); + let vs_words = + wgpu::read_spirv(io::Cursor::new(vs_spv)).expect("Couldn't read pre-built SPIR-V"); + let fs_words = + wgpu::read_spirv(io::Cursor::new(fs_spv)).expect("Couldn't read pre-built SPIR-V"); + let vs_module = device.create_shader_module(&vs_words); + let fs_module = device.create_shader_module(&fs_words); + + // Create ortho projection matrix uniform buffer, layout and bind group + + let uniform_buffer_size = wgpu_size_of::(); + let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + size: uniform_buffer_size, + // FIXME(yanchith): `TRANSFER_DST` is required because the + // only way to upload the buffer currently is by issueing + // the transfer command in `upload_buffer_immediate`. We + // can remove the flag once we get rid of the hack and + // learn to write to mapped buffers correctly. + usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::TRANSFER_DST, + }); + + let uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer, + }], + }); + + let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &uniform_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &uniform_buffer, + range: 0..uniform_buffer_size, + }, + }], + }); + + // Create texture uniforms layout + + let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[ + wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture, + }, + wgpu::BindGroupLayoutBinding { + binding: 1, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }, + ], + }); + + // Create render pipeline + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&uniform_layout, &texture_layout], + }); + + // Setup render state: alpha-blending enabled, no face + // culling, no depth testing + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::PipelineStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::PipelineStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Ccw, + cull_mode: wgpu::CullMode::None, + // Depth test is disabled, no need for these to mean anything + depth_bias: 0, + depth_bias_clamp: 0.0, + depth_bias_slope_scale: 0.0, + }, + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format, + // Enable alpha blending + color_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + // Disabled depth test + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, // FIXME(yanchith): may need 32bit indices! + vertex_buffers: &[wgpu::VertexBufferDescriptor { + stride: wgpu_size_of::(), + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + offset: 0, + format: wgpu::VertexFormat::Float2, + shader_location: 0, + }, + wgpu::VertexAttributeDescriptor { + offset: 8, + format: wgpu::VertexFormat::Float2, + shader_location: 1, + }, + wgpu::VertexAttributeDescriptor { + offset: 16, + format: wgpu::VertexFormat::Uint, + shader_location: 2, + }, + ], + }], + sample_count: 1, + }); + + // Create the font atlas texture + + let mut fonts = imgui.fonts(); + let font_texture = fonts.build_rgba32_texture(); + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: font_texture.width, + height: font_texture.height, + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::TRANSFER_DST, + }); + + upload_texture_immediate( + device, + &texture, + font_texture.width, + font_texture.height, + font_texture.data, + ); + + let sampler = create_sampler(device); + let pair = Texture::new(device, &texture_layout, texture, sampler); + let mut textures = imgui::Textures::new(); + let atlas_id = textures.insert(pair); + fonts.tex_id = atlas_id; + + Ok(ImguiRenderer { + render_pipeline, + uniform_buffer, + uniform_bind_group, + texture_layout, + textures, + clear_color, + }) + } + + pub fn add_rgba32_texture( + &mut self, + device: &mut wgpu::Device, + width: u32, + height: u32, + data: &[u8], + ) -> imgui::TextureId { + assert_eq!(data.len() % 4, 0); + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width, + height, + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::TRANSFER_DST, + }); + + upload_texture_immediate(device, &texture, width, height, data); + + let sampler = create_sampler(device); + let pair = Texture::new(device, &self.texture_layout, texture, sampler); + self.textures.insert(pair) + } + + pub fn render( + &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target_attachment: &wgpu::TextureView, + draw_data: &imgui::DrawData, + ) -> Result<(), ImguiRendererError> { + // This is mostly a transcript of the following: + // https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_opengl3.cpp + // https://github.com/ocornut/imgui/blob/master/examples/imgui_impl_vulkan.cpp + // https://github.com/Gekkio/imgui-rs/blob/master/imgui-glium-renderer/src/lib.rs + + let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0]; + let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1]; + if fb_width <= 0.0 && fb_height <= 0.0 { + return Ok(()); + } + + let scale = [ + 2.0 / draw_data.display_size[0], + 2.0 / draw_data.display_size[1], + ]; + let translate = [ + -1.0 - draw_data.display_pos[0] * scale[0], + -1.0 - draw_data.display_pos[1] * scale[1], + ]; + + // FIXME(yanchith): try to use map_write_async here... but + // figure out how to use it correctly beforehand. Currently, + // even calling unmap does not force the callback in + // map_write_async. + + // self.uniform_buffer.map_write_async(0, UNIFORM_BUFFER_SIZE, move |target| { + // println!("MAP"); + // if let Ok(t) = target { + // t.data[0] = translate[0]; + // t.data[1] = translate[1]; + // t.data[2] = scale[0]; + // t.data[3] = scale[1]; + // } + // }); + // println!("UNMAP starting"); + // self.uniform_buffer.unmap(); + // println!("UNMAP done"); + + upload_buffer_immediate( + device, + &self.uniform_buffer, + TransformUniforms { translate, scale }, + ); + + // Will project scissor/clipping rectangles into framebuffer space + let clip_off = draw_data.display_pos; // (0,0) unless using multi-viewports + let clip_scale = draw_data.framebuffer_scale; // (1,1) unless using hidpi + + // The rendering process is as follows: + // + // 1) We begin the render pass and set the pipeline and bind + // group for the already uploaded uniforms as those don't + // change for the whole frame. + // + // 2) For each processed draw list, we create new vertex and + // index buffer, and set them to the render pass as they stay + // the same for the entire draw list. + // + // 3) For each draw command in a draw list, we figure out + // clipping (and don't draw anything if it would be clipped), + // the current texture to use, and our current index window + // `idx_start..idx_end` and set those to the render pass, and + // finally draw. + + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: target_attachment, + resolve_target: None, + load_op: match self.clear_color { + Some(_) => wgpu::LoadOp::Clear, + None => wgpu::LoadOp::Load, + }, + store_op: wgpu::StoreOp::Store, + clear_color: self.clear_color.unwrap_or(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + }], + depth_stencil_attachment: None, + }); + + rpass.set_pipeline(&self.render_pipeline); + rpass.set_bind_group(0, &self.uniform_bind_group, &[]); + + for draw_list in draw_data.draw_lists() { + let vtx_buffer = draw_list.vtx_buffer(); + let vertex_buffer = device + .create_buffer_mapped(vtx_buffer.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(vtx_buffer); + + let idx_buffer = draw_list.idx_buffer(); + let index_buffer = device + .create_buffer_mapped(idx_buffer.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(idx_buffer); + + rpass.set_vertex_buffers(&[(&vertex_buffer, 0)]); + rpass.set_index_buffer(&index_buffer, 0); + + let mut idx_start = 0; + for cmd in draw_list.commands() { + match cmd { + imgui::DrawCmd::Elements { + count, + cmd_params: + imgui::DrawCmdParams { + clip_rect, + texture_id, + .. + }, + } => { + let idx_end = idx_start + count as u32; + + let clip_rect = [ + (clip_rect[0] - clip_off[0]) * clip_scale[0], + (clip_rect[1] - clip_off[1]) * clip_scale[1], + (clip_rect[2] - clip_off[0]) * clip_scale[0], + (clip_rect[3] - clip_off[1]) * clip_scale[1], + ]; + + if clip_rect[0] < fb_width + && clip_rect[1] < fb_height + && clip_rect[2] >= 0.0 + && clip_rect[3] >= 0.0 + { + let texture = self + .textures + .get(texture_id) + .ok_or_else(|| ImguiRendererError::BadTexture(texture_id))?; + + rpass.set_bind_group(1, texture.bind_group(), &[]); + rpass.set_scissor_rect( + clip_rect[0].max(0.0).min(fb_width).round() as u32, + clip_rect[1].max(0.0).min(fb_height).round() as u32, + (clip_rect[2] - clip_rect[0]).abs().min(fb_width).round() as u32, + (clip_rect[3] - clip_rect[1]).abs().min(fb_height).round() as u32, + ); + rpass.draw_indexed(idx_start..idx_end, 0, 0..1); + + idx_start = idx_end; + } + } + + // Our render state is mostly predefined in the + // render pipeline, not much to reset here + imgui::DrawCmd::ResetRenderState => (), + + imgui::DrawCmd::RawCallback { callback, raw_cmd } => unsafe { + callback(draw_list.raw(), raw_cmd) + }, + } + } + } + + Ok(()) + } +} + +struct Texture { + bind_group: wgpu::BindGroup, +} + +impl Texture { + pub fn new( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + texture: wgpu::Texture, + sampler: wgpu::Sampler, + ) -> Self { + let view = texture.create_default_view(); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + bindings: &[ + wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView(&view), + }, + wgpu::Binding { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + }); + + Texture { bind_group } + } + + pub fn bind_group(&self) -> &wgpu::BindGroup { + &self.bind_group + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +struct TransformUniforms { + translate: [f32; 2], + scale: [f32; 2], +} + +fn upload_buffer_immediate( + device: &mut wgpu::Device, + buffer: &wgpu::Buffer, + transform_uniforms: TransformUniforms, +) { + let transform_uniforms_size = wgpu_size_of::(); + let source_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::TRANSFER_SRC) + .fill_from_slice(&[transform_uniforms]); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + + encoder.copy_buffer_to_buffer(&source_buffer, 0, buffer, 0, transform_uniforms_size); + + device.get_queue().submit(&[encoder.finish()]); +} + +fn upload_texture_immediate( + device: &mut wgpu::Device, + texture: &wgpu::Texture, + width: u32, + height: u32, + data: &[u8], +) { + let count = data.len(); + let buffer = device + .create_buffer_mapped(count, wgpu::BufferUsage::TRANSFER_SRC) + .fill_from_slice(data); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); + + let count = u32::try_from(count).expect("Should convert texture data length to u32"); + let pixel_size = count / width / height; + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &buffer, + offset: 0, + row_pitch: pixel_size * width, + image_height: height, + }, + wgpu::TextureCopyView { + texture, + mip_level: 0, + array_layer: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::Extent3d { + width, + height, + depth: 1, + }, + ); + + device.get_queue().submit(&[encoder.finish()]); +} + +fn create_sampler(device: &wgpu::Device) -> wgpu::Sampler { + device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }) +} + +fn wgpu_size_of() -> wgpu::BufferAddress { + let size = mem::size_of::(); + wgpu::BufferAddress::try_from(size) + .unwrap_or_else(|_| panic!("Size {} does not fit into wgpu BufferAddress", size)) +} diff --git a/src/input.rs b/src/input.rs index 3f3d4f1d..09e55d2f 100644 --- a/src/input.rs +++ b/src/input.rs @@ -41,7 +41,12 @@ impl InputManager { self.input_state = InputState::default(); } - pub fn process_event(&mut self, ev: winit::Event) { + pub fn process_event( + &mut self, + ev: winit::Event, + ui_captured_keyboard: bool, + ui_captured_mouse: bool, + ) { const MODIFIERS_NONE: winit::ModifiersState = winit::ModifiersState { logo: false, shift: false, @@ -49,10 +54,6 @@ impl InputManager { alt: false, }; - // FIXME: these should come in as parameters - let gui_captured_keyboard: bool = false; - let gui_captured_mouse: bool = false; - match ev { winit::Event::WindowEvent { event, .. } => match event { winit::WindowEvent::CloseRequested => { @@ -98,7 +99,7 @@ impl InputManager { }; // These events are responded to only when gui doesn't have focus - if !gui_captured_keyboard { + if !ui_captured_keyboard { match (virtual_keycode, state, modifiers) { ( Some(winit::VirtualKeyCode::A), @@ -142,7 +143,7 @@ impl InputManager { }, winit::Event::DeviceEvent { event, .. } => match event { winit::DeviceEvent::MouseMotion { delta } => { - if !gui_captured_mouse { + if !ui_captured_mouse { let x = delta.0 as f32; let y = delta.1 as f32; if self.lmb_down && self.rmb_down { @@ -165,7 +166,7 @@ impl InputManager { winit::MouseScrollDelta::PixelDelta(winit::dpi::LogicalPosition { y, .. }) => { - if !gui_captured_mouse { + if !ui_captured_mouse { match y.partial_cmp(&0.0) { Some(Ordering::Greater) => self.input_state.camera_zoom_steps += 1, Some(Ordering::Less) => self.input_state.camera_zoom_steps -= 1, @@ -174,7 +175,7 @@ impl InputManager { } } winit::MouseScrollDelta::LineDelta(_, y) => { - if !gui_captured_mouse { + if !ui_captured_mouse { match y.partial_cmp(&0.0) { Some(Ordering::Greater) => self.input_state.camera_zoom_steps += 1, Some(Ordering::Less) => self.input_state.camera_zoom_steps -= 1, diff --git a/src/lib.rs b/src/lib.rs index b810f0b3..87a5e20f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ pub mod camera; +pub mod imgui_renderer; pub mod importer; pub mod input; pub mod primitives; +pub mod shader; +pub mod ui; pub mod viewport_renderer; diff --git a/src/main.rs b/src/main.rs index fcf9f131..70eed8e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,11 @@ use std::time::Instant; use wgpu::winit; use hurban_selector::camera::{Camera, CameraOptions}; +use hurban_selector::imgui_renderer::ImguiRenderer; use hurban_selector::importer::Importer; use hurban_selector::input::InputManager; use hurban_selector::primitives; +use hurban_selector::ui; use hurban_selector::viewport_renderer::{ Geometry, Msaa, ViewportRenderer, ViewportRendererOptions, }; @@ -90,6 +92,15 @@ fn main() { let mut dynamic_models = Vec::new(); + let (mut imgui_context, mut winit_platform) = ui::init(&window); + let mut imgui_renderer = ImguiRenderer::new( + &mut imgui_context, + &mut device, + wgpu::TextureFormat::Bgra8Unorm, + None, + ) + .expect("Failed to create imgui renderer"); + let time_start = Instant::now(); let mut time = time_start; @@ -103,11 +114,42 @@ fn main() { let duration_running = now.duration_since(time_start); time = now; + // FIXME: Use `Duration::as_secs_f32` instead once it's stabilized. + let duration_last_frame_s = duration_last_frame.as_secs() as f32 + + duration_last_frame.subsec_nanos() as f32 / 1_000_000_000.0; + + imgui_context.io_mut().delta_time = duration_last_frame_s; + (duration_last_frame, duration_running) }; + // Since input manager needs to process events separately after imgui + // handles them, this buffer with copies of events is needed. + let mut input_events = vec![]; + + event_loop.poll_events(|event| { + input_events.push(event.clone()); + winit_platform.handle_event(imgui_context.io_mut(), &window, &event); + }); + + // Start UI and input manger frames + winit_platform + .prepare_frame(imgui_context.io_mut(), &window) + .expect("Failed to start imgui frame"); + let imgui_ui = imgui_context.frame(); + let imgui_ui_io = imgui_ui.io(); + input_manager.start_frame(); - event_loop.poll_events(|event| input_manager.process_event(event)); + + // Imgui's IO is updated after current frame starts, else it'd contain + // outdated values. + let ui_captured_keyboard = imgui_ui_io.want_capture_keyboard; + let ui_captured_mouse = imgui_ui_io.want_capture_mouse; + + for event in input_events { + input_manager.process_event(event, ui_captured_keyboard, ui_captured_mouse); + } + let input_state = input_manager.input_state(); let [pan_ground_x, pan_ground_y] = input_state.camera_pan_ground; @@ -185,6 +227,11 @@ fn main() { &camera.view_matrix(), ); + ui::draw_fps_window(&imgui_ui); + + winit_platform.prepare_render(&imgui_ui, &window); + let imgui_draw_data = imgui_ui.render(); + let frame = swap_chain.get_next_texture(); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { todo: 0 }); @@ -199,6 +246,10 @@ fn main() { viewport_renderer.draw_geometry(&mut encoder, &frame.view, &dynamic_models[..]); } + imgui_renderer + .render(&mut device, &mut encoder, &frame.view, imgui_draw_data) + .expect("Should render an imgui frame"); + device.get_queue().submit(&[encoder.finish()]); } diff --git a/src/shader.rs b/src/shader.rs new file mode 100644 index 00000000..e8d70a36 --- /dev/null +++ b/src/shader.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! include_shader { + ($name:expr) => {{ + include_bytes!(concat!(env!("OUT_DIR"), "/shaders/", $name)) + }}; +} diff --git a/src/shaders/imgui.frag b/src/shaders/imgui.frag new file mode 100644 index 00000000..7490c9c2 --- /dev/null +++ b/src/shaders/imgui.frag @@ -0,0 +1,13 @@ +#version 450 + +layout(set = 1, binding = 0) uniform texture2D u_texture; +layout(set = 1, binding = 1) uniform sampler u_sampler; + +layout(location = 0) in vec2 v_tex_coords; +layout(location = 1) in vec4 v_color; + +layout(location = 0) out vec4 o_color; + +void main() { + o_color = v_color * texture(sampler2D(u_texture, u_sampler), v_tex_coords); +} \ No newline at end of file diff --git a/src/shaders/imgui.vert b/src/shaders/imgui.vert new file mode 100644 index 00000000..2cf69512 --- /dev/null +++ b/src/shaders/imgui.vert @@ -0,0 +1,22 @@ +#version 450 + +layout(set = 0, binding = 0) uniform Transform { + vec2 u_translate; + vec2 u_scale; +}; + +layout(location = 0) in vec2 a_pos; +layout(location = 1) in vec2 a_tex_coords; +layout(location = 2) in uint a_color; + +layout(location = 0) out vec2 v_tex_coords; +layout(location = 1) out vec4 v_color; + +void main() { + v_tex_coords = a_tex_coords; + v_color = vec4(a_color & 0xFF, + (a_color >> 8) & 0xFF, + (a_color >> 16) & 0xFF, + (a_color >> 24) & 0xFF) / 255.0; + gl_Position = vec4(a_pos.xy * u_scale + u_translate, 0.0, 1.0); +} diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 00000000..71f76622 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,36 @@ +use imgui_winit_support::{HiDpiMode, WinitPlatform}; +use wgpu::winit; + +pub fn init(window: &winit::Window) -> (imgui::Context, WinitPlatform) { + let mut imgui_context = imgui::Context::create(); + let mut style = imgui_context.style_mut(); + style.window_padding = [10.0, 10.0]; + + imgui_context.set_ini_filename(None); + + let mut platform = WinitPlatform::init(&mut imgui_context); + + platform.attach_window(imgui_context.io_mut(), window, HiDpiMode::Default); + + let hidpi_factor = platform.hidpi_factor(); + let font_size = (13.0 * hidpi_factor) as f32; + + imgui_context + .fonts() + .add_font(&[imgui::FontSource::DefaultFontData { + config: Some(imgui::FontConfig { + size_pixels: font_size, + ..imgui::FontConfig::default() + }), + }]); + + imgui_context.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; + + (imgui_context, platform) +} + +pub fn draw_fps_window(ui: &imgui::Ui) { + ui.window(imgui::im_str!("FPS")).build(|| { + ui.text(imgui::im_str!("{:.3} fps", ui.io().framerate)); + }); +} diff --git a/src/viewport_renderer.rs b/src/viewport_renderer.rs index d32a0e04..e71035e1 100644 --- a/src/viewport_renderer.rs +++ b/src/viewport_renderer.rs @@ -6,14 +6,9 @@ use std::io; use std::mem; use nalgebra::base::Matrix4; - use wgpu::winit; -macro_rules! include_shader { - ($name:expr) => {{ - include_bytes!(concat!(env!("OUT_DIR"), "/shaders/", $name)) - }}; -} +use crate::include_shader; const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::D32Float;