From aa4ae70054208eea62b51f0e23420931331e89be Mon Sep 17 00:00:00 2001 From: Joonas Javanainen Date: Sun, 12 Aug 2018 13:51:52 +0300 Subject: [PATCH] Make HiDPI rendering better + use linear filtering in both renderers. Nearest just won't work anymore if we have a non-integer scaling factor (which winit can give us on 1440p screens for example) + pass around FrameSize which has the necessary info with full f64 precision + extra care with conversions to/from f32 and u32 and rounding --- imgui-examples/examples/support/mod.rs | 13 ++- imgui-examples/examples/support_gfx/mod.rs | 13 ++- imgui-gfx-renderer/src/lib.rs | 32 +++--- imgui-glium-renderer/src/lib.rs | 115 +++++++++++---------- src/lib.rs | 33 +++--- 5 files changed, 108 insertions(+), 98 deletions(-) diff --git a/imgui-examples/examples/support/mod.rs b/imgui-examples/examples/support/mod.rs index 011b8f7..3cbcfff 100644 --- a/imgui-examples/examples/support/mod.rs +++ b/imgui-examples/examples/support/mod.rs @@ -1,4 +1,4 @@ -use imgui::{FontGlyphRange, ImFontConfig, ImGui, ImGuiMouseCursor, Ui}; +use imgui::{FontGlyphRange, FrameSize, ImFontConfig, ImGui, ImGuiMouseCursor, Ui}; use std::time::Instant; #[derive(Copy, Clone, PartialEq, Debug, Default)] @@ -129,11 +129,14 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ }); } - let size_points = gl_window.get_inner_size().unwrap(); - let hdipi = gl_window.get_hidpi_factor(); - let size_pixels = size_points.to_physical(hdipi); + let logical_size = gl_window.get_inner_size().unwrap(); - let ui = imgui.frame(size_points.into(), size_pixels.into(), delta_s); + let frame_size = FrameSize { + logical_size: logical_size.into(), + hidpi_factor: gl_window.get_hidpi_factor(), + }; + + let ui = imgui.frame(frame_size, delta_s); if !run_ui(&ui) { break; } diff --git a/imgui-examples/examples/support_gfx/mod.rs b/imgui-examples/examples/support_gfx/mod.rs index 5d03952..e48f7f0 100644 --- a/imgui-examples/examples/support_gfx/mod.rs +++ b/imgui-examples/examples/support_gfx/mod.rs @@ -1,4 +1,4 @@ -use imgui::{FontGlyphRange, ImFontConfig, ImGui, ImGuiMouseCursor, ImVec4, Ui}; +use imgui::{FontGlyphRange, FrameSize, ImFontConfig, ImGui, ImGuiMouseCursor, ImVec4, Ui}; use imgui_gfx_renderer::{Renderer, Shaders}; use std::time::Instant; @@ -172,11 +172,14 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ }); } - let size_points = window.get_inner_size().unwrap(); - let hdipi = window.get_hidpi_factor(); - let size_pixels = size_points.to_physical(hdipi); + let logical_size = window.get_inner_size().unwrap(); - let ui = imgui.frame(size_points.into(), size_pixels.into(), delta_s); + let frame_size = FrameSize { + logical_size: logical_size.into(), + hidpi_factor: window.get_hidpi_factor(), + }; + + let ui = imgui.frame(frame_size, delta_s); if !run_ui(&ui) { break; } diff --git a/imgui-gfx-renderer/src/lib.rs b/imgui-gfx-renderer/src/lib.rs index ccd32fd..9751505 100644 --- a/imgui-gfx-renderer/src/lib.rs +++ b/imgui-gfx-renderer/src/lib.rs @@ -7,7 +7,7 @@ use gfx::memory::Bind; use gfx::handle::{Buffer, RenderTargetView}; use gfx::texture::{FilterMethod, SamplerInfo, WrapMode}; use gfx::traits::FactoryExt; -use imgui::{DrawList, ImDrawIdx, ImDrawVert, ImGui, Ui}; +use imgui::{DrawList, FrameSize, ImDrawIdx, ImDrawVert, ImGui, Ui}; pub type RendererResult = Result; @@ -129,7 +129,7 @@ impl Renderer { })?; // TODO: set texture id in imgui let sampler = - factory.create_sampler(SamplerInfo::new(FilterMethod::Scale, WrapMode::Clamp)); + factory.create_sampler(SamplerInfo::new(FilterMethod::Trilinear, WrapMode::Clamp)); let data = pipe::Data { vertex_buffer: vertex_buffer, matrix: [ @@ -168,34 +168,36 @@ impl Renderer { factory: &mut F, encoder: &mut Encoder, ) -> RendererResult<()> { - let (width, height) = ui.imgui().display_size(); + let FrameSize { logical_size: (width, height), hidpi_factor } = ui.frame_size(); - if width == 0.0 || height == 0.0 { + if !(width > 0.0 && height > 0.0) { return Ok(()); } + let fb_size = ((width * hidpi_factor) as f32, (height * hidpi_factor) as f32); + self.bundle.data.matrix = [ - [2.0 / width as f32, 0.0, 0.0, 0.0], - [0.0, -2.0 / height as f32, 0.0, 0.0], + [(2.0 / width) as f32, 0.0, 0.0, 0.0], + [0.0, (2.0 / -height) as f32, 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [-1.0, 1.0, 0.0, 1.0], ]; - ui.render(|ui, draw_data| { + ui.render(|ui, mut draw_data| { + draw_data.scale_clip_rects(ui.imgui().display_framebuffer_scale()); for draw_list in &draw_data { - self.render_draw_list(ui, factory, encoder, &draw_list)?; + self.render_draw_list(factory, encoder, &draw_list, fb_size)?; } Ok(()) }) } fn render_draw_list<'a, F: Factory, C: CommandBuffer>( &mut self, - ui: &'a Ui<'a>, factory: &mut F, encoder: &mut Encoder, draw_list: &DrawList<'a>, + fb_size: (f32, f32) ) -> RendererResult<()> { - let (width, height) = ui.imgui().display_size(); - let (scale_width, scale_height) = ui.imgui().display_framebuffer_scale(); + let (fb_width, fb_height) = fb_size; self.upload_vertex_buffer(factory, encoder, draw_list.vtx_buffer)?; self.upload_index_buffer(factory, encoder, draw_list.idx_buffer)?; @@ -206,10 +208,10 @@ impl Renderer { self.bundle.slice.end = self.bundle.slice.start + cmd.elem_count; self.bundle.data.scissor = Rect { - x: (cmd.clip_rect.x.max(0.0) * scale_width) as u16, - y: (cmd.clip_rect.y.max(0.0) * scale_height) as u16, - w: ((cmd.clip_rect.z - cmd.clip_rect.x).abs().min(width) * scale_width) as u16, - h: ((cmd.clip_rect.w - cmd.clip_rect.y).abs().min(height) * scale_height) as u16, + x: cmd.clip_rect.x.max(0.0).round() as u16, + y: cmd.clip_rect.y.max(0.0).round() as u16, + w: (cmd.clip_rect.z - cmd.clip_rect.x).abs().min(fb_width).round() as u16, + h: (cmd.clip_rect.w - cmd.clip_rect.y).abs().min(fb_height).round() as u16, }; self.bundle.encode(encoder); self.bundle.slice.start = self.bundle.slice.end; diff --git a/imgui-glium-renderer/src/lib.rs b/imgui-glium-renderer/src/lib.rs index 4a70fba..3e01aa0 100644 --- a/imgui-glium-renderer/src/lib.rs +++ b/imgui-glium-renderer/src/lib.rs @@ -8,7 +8,7 @@ use glium::program; use glium::index::{self, PrimitiveType}; use glium::texture; use glium::vertex; -use imgui::{DrawList, ImDrawIdx, ImDrawVert, ImGui, Ui}; +use imgui::{DrawList, FrameSize, ImDrawIdx, ImDrawVert, ImGui, Ui}; use std::borrow::Cow; use std::fmt; use std::rc::Rc; @@ -64,7 +64,7 @@ pub struct Renderer { impl Renderer { pub fn init(imgui: &mut ImGui, ctx: &F) -> RendererResult { - let device_objects = try!(DeviceObjects::init(imgui, ctx)); + let device_objects = DeviceObjects::init(imgui, ctx)?; Ok(Renderer { ctx: Rc::clone(ctx.get_context()), device_objects: device_objects, @@ -73,9 +73,22 @@ impl Renderer { pub fn render<'a, S: Surface>(&mut self, surface: &mut S, ui: Ui<'a>) -> RendererResult<()> { let _ = self.ctx.insert_debug_marker("imgui-rs: starting rendering"); - let result = ui.render(|ui, draw_data| { + let FrameSize { logical_size: (width, height), hidpi_factor } = ui.frame_size(); + if !(width > 0.0 && height > 0.0) { + return Ok(()); + } + let fb_size = ((width * hidpi_factor) as f32, (height * hidpi_factor) as f32); + + let matrix = [ + [(2.0 / width) as f32, 0.0, 0.0, 0.0], + [0.0, (2.0 / -height) as f32, 0.0, 0.0], + [0.0, 0.0, -1.0, 0.0], + [-1.0, 1.0, 0.0, 1.0], + ]; + let result = ui.render(|ui, mut draw_data| { + draw_data.scale_clip_rects(ui.imgui().display_framebuffer_scale()); for draw_list in draw_data.into_iter() { - self.render_draw_list(surface, ui, &draw_list)?; + self.render_draw_list(surface, &draw_list, fb_size, matrix)?; } Ok(()) }); @@ -86,34 +99,24 @@ impl Renderer { fn render_draw_list<'a, S: Surface>( &mut self, surface: &mut S, - ui: &'a Ui<'a>, draw_list: &DrawList<'a>, + fb_size: (f32, f32), + matrix: [[f32; 4]; 4], ) -> RendererResult<()> { use glium::{Blend, DrawParameters, Rect}; use glium::uniforms::{MinifySamplerFilter, MagnifySamplerFilter}; - try!(self.device_objects.upload_vertex_buffer( + let (fb_width, fb_height) = fb_size; + + self.device_objects.upload_vertex_buffer( &self.ctx, draw_list.vtx_buffer, - )); - try!(self.device_objects.upload_index_buffer( + )?; + self.device_objects.upload_index_buffer( &self.ctx, draw_list.idx_buffer, - )); + )?; - let (width, height) = ui.imgui().display_size(); - let (scale_width, scale_height) = ui.imgui().display_framebuffer_scale(); - - if width == 0.0 || height == 0.0 { - return Ok(()); - } - - let matrix = [ - [2.0 / width as f32, 0.0, 0.0, 0.0], - [0.0, 2.0 / -(height as f32), 0.0, 0.0], - [0.0, 0.0, -1.0, 0.0], - [-1.0, 1.0, 0.0, 1.0], - ]; let font_texture_id = self.device_objects.texture.get_id() as usize; let mut idx_start = 0; @@ -123,32 +126,30 @@ impl Renderer { let idx_end = idx_start + cmd.elem_count as usize; - try!( - surface.draw( - &self.device_objects.vertex_buffer, - &self.device_objects - .index_buffer - .slice(idx_start..idx_end) - .expect("Invalid index buffer range"), - &self.device_objects.program, - &uniform! { - matrix: matrix, - tex: self.device_objects.texture.sampled() - .magnify_filter(MagnifySamplerFilter::Nearest) - .minify_filter(MinifySamplerFilter::Nearest), - }, - &DrawParameters { - blend: Blend::alpha_blending(), - scissor: Some(Rect { - left: (cmd.clip_rect.x * scale_width) as u32, - bottom: ((height - cmd.clip_rect.w) * scale_height) as u32, - width: ((cmd.clip_rect.z - cmd.clip_rect.x) * scale_width) as u32, - height: ((cmd.clip_rect.w - cmd.clip_rect.y) * scale_height) as u32, - }), - ..DrawParameters::default() - }, - ) - ); + surface.draw( + &self.device_objects.vertex_buffer, + &self.device_objects + .index_buffer + .slice(idx_start..idx_end) + .expect("Invalid index buffer range"), + &self.device_objects.program, + &uniform! { + matrix: matrix, + tex: self.device_objects.texture.sampled() + .magnify_filter(MagnifySamplerFilter::Linear) + .minify_filter(MinifySamplerFilter::Linear), + }, + &DrawParameters { + blend: Blend::alpha_blending(), + scissor: Some(Rect { + left: cmd.clip_rect.x.max(0.0).round() as u32, + bottom: (fb_height - cmd.clip_rect.w).max(0.0).round() as u32, + width: (cmd.clip_rect.z - cmd.clip_rect.x).abs().max(fb_width).round() as u32, + height: (cmd.clip_rect.w - cmd.clip_rect.y).abs().max(fb_height).round() as u32, + }), + ..DrawParameters::default() + }, + )?; idx_start = idx_end; } @@ -201,15 +202,15 @@ impl DeviceObjects { pub fn init(im_gui: &mut ImGui, ctx: &F) -> RendererResult { use glium::texture::{ClientFormat, RawImage2d}; - let vertex_buffer = try!(VertexBuffer::empty_dynamic(ctx, 0)); - let index_buffer = try!(IndexBuffer::empty_dynamic( + let vertex_buffer = VertexBuffer::empty_dynamic(ctx, 0)?; + let index_buffer = IndexBuffer::empty_dynamic( ctx, PrimitiveType::TrianglesList, 0, - )); + )?; - let program = try!(compile_default_program(ctx)); - let texture = try!(im_gui.prepare_texture(|handle| { + let program = compile_default_program(ctx)?; + let texture = im_gui.prepare_texture(|handle| { let data = RawImage2d { data: Cow::Borrowed(handle.pixels), width: handle.width, @@ -217,7 +218,7 @@ impl DeviceObjects { format: ClientFormat::U8U8U8U8, }; Texture2d::new(ctx, data) - })); + })?; im_gui.set_texture_id(texture.get_id() as usize); Ok(DeviceObjects { @@ -237,7 +238,7 @@ impl DeviceObjects { slice.write(vtx_buffer); return Ok(()); } - self.vertex_buffer = try!(VertexBuffer::dynamic(ctx, vtx_buffer)); + self.vertex_buffer = VertexBuffer::dynamic(ctx, vtx_buffer)?; let _ = ctx.get_context().insert_debug_marker(&format!( "imgui-rs: resized vertex buffer to {} bytes", self.vertex_buffer.get_size() @@ -254,11 +255,11 @@ impl DeviceObjects { slice.write(idx_buffer); return Ok(()); } - self.index_buffer = try!(IndexBuffer::dynamic( + self.index_buffer = IndexBuffer::dynamic( ctx, PrimitiveType::TrianglesList, idx_buffer, - )); + )?; let _ = ctx.get_context().insert_debug_marker(&format!( "imgui-rs: resized index buffer to {} bytes", self.index_buffer.get_size() diff --git a/src/lib.rs b/src/lib.rs index 046782c..2748069 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,6 +99,12 @@ pub enum ImMouseButton { Extra2 = 4, } +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct FrameSize { + pub logical_size: (f64, f64), + pub hidpi_factor: f64, +} + impl ImGui { pub fn init() -> ImGui { ImGui { @@ -357,31 +363,22 @@ impl ImGui { pub fn get_frame_rate(&self) -> f32 { self.io().framerate } pub fn frame<'ui, 'a: 'ui>( &'a mut self, - size_points: (u32, u32), - size_pixels: (u32, u32), + frame_size: FrameSize, delta_time: f32, ) -> Ui<'ui> { { let io = self.io_mut(); - io.display_size.x = size_points.0 as c_float; - io.display_size.y = size_points.1 as c_float; - io.display_framebuffer_scale.x = if size_points.0 > 0 { - size_pixels.0 as c_float / size_points.0 as c_float - } else { - 0.0 - }; - io.display_framebuffer_scale.y = if size_points.1 > 0 { - size_pixels.1 as c_float / size_points.1 as c_float - } else { - 0.0 - }; + io.display_size.x = frame_size.logical_size.0 as c_float; + io.display_size.y = frame_size.logical_size.1 as c_float; + io.display_framebuffer_scale.x = frame_size.hidpi_factor as c_float; + io.display_framebuffer_scale.y = frame_size.hidpi_factor as c_float; io.delta_time = delta_time; } unsafe { sys::igNewFrame(); - CURRENT_UI = Some(Ui { imgui: mem::transmute(self as &'a ImGui) }); + CURRENT_UI = Some(Ui { imgui: mem::transmute(self as &'a ImGui), frame_size }); } - Ui { imgui: self } + Ui { imgui: self, frame_size } } } @@ -466,6 +463,7 @@ pub struct DrawList<'a> { pub struct Ui<'ui> { imgui: &'ui ImGui, + frame_size: FrameSize, } static FMT: &'static [u8] = b"%s\0"; @@ -473,6 +471,9 @@ static FMT: &'static [u8] = b"%s\0"; fn fmt_ptr() -> *const c_char { FMT.as_ptr() as *const c_char } impl<'ui> Ui<'ui> { + pub fn frame_size(&self) -> FrameSize { + self.frame_size + } pub fn imgui(&self) -> &ImGui { self.imgui } pub fn want_capture_mouse(&self) -> bool { let io = self.imgui.io();