From 09d636b08943b14f182c25d65d25085959e7fc68 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Jul 2022 00:06:08 +0200 Subject: [PATCH] egui-wgpu: correctly handle viewport rectangle for callbacks This is important for when a callback shape is inside a ScrollArea. --- egui-wgpu/src/renderer.rs | 149 +++++++++++------------- egui_demo_app/src/apps/custom3d_glow.rs | 32 ++--- egui_demo_app/src/apps/custom3d_wgpu.rs | 34 +++--- egui_glow/src/painter.rs | 13 ++- 4 files changed, 107 insertions(+), 121 deletions(-) diff --git a/egui-wgpu/src/renderer.rs b/egui-wgpu/src/renderer.rs index cf42766b609..1a36ce07cce 100644 --- a/egui-wgpu/src/renderer.rs +++ b/egui-wgpu/src/renderer.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, collections::HashMap, num::NonZeroU32}; -use egui::{epaint::Primitive, PaintCallbackInfo}; +use egui::{epaint::Primitive, NumExt, PaintCallbackInfo}; use type_map::TypeMap; use wgpu; use wgpu::util::DeviceExt as _; @@ -361,24 +361,21 @@ impl RenderPass { needs_reset = false; } - let PixelRect { - x, - y, - width, - height, - } = calculate_pixel_rect(clip_rect, pixels_per_point, size_in_pixels); - - // Skip rendering with zero-sized clip areas. - if width == 0 || height == 0 { - // If this is a mesh, we need to advance the index and vertex buffer iterators - if let Primitive::Mesh(_) = primitive { - index_buffers.next().unwrap(); - vertex_buffers.next().unwrap(); + { + let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels); + + if rect.width == 0 || rect.height == 0 { + // Skip rendering with zero-sized clip areas. + if let Primitive::Mesh(_) = primitive { + // If this is a mesh, we need to advance the index and vertex buffer iterators + index_buffers.next().unwrap(); + vertex_buffers.next().unwrap(); + } + continue; } - continue; - } - rpass.set_scissor_rect(x, y, width, height); + rpass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height); + } match primitive { Primitive::Mesh(mesh) => { @@ -408,34 +405,28 @@ impl RenderPass { if callback.rect.is_positive() { needs_reset = true; - // Set the viewport rect - let PixelRect { - x, - y, - width, - height, - } = calculate_pixel_rect(&callback.rect, pixels_per_point, size_in_pixels); - rpass.set_viewport( - x as f32, - y as f32, - width as f32, - height as f32, - 0.0, - 1.0, - ); - - // Set the scissor rect - let PixelRect { - x, - y, - width, - height, - } = calculate_pixel_rect(clip_rect, pixels_per_point, size_in_pixels); - // Skip rendering with zero-sized clip areas. - if width == 0 || height == 0 { - continue; + { + // Set the viewport rect + // Transform callback rect to physical pixels: + let rect_min_x = pixels_per_point * callback.rect.min.x; + let rect_min_y = pixels_per_point * callback.rect.min.y; + let rect_max_x = pixels_per_point * callback.rect.max.x; + let rect_max_y = pixels_per_point * callback.rect.max.y; + + let rect_min_x = rect_min_x.round(); + let rect_min_y = rect_min_y.round(); + let rect_max_x = rect_max_x.round(); + let rect_max_y = rect_max_y.round(); + + rpass.set_viewport( + rect_min_x, + rect_min_y, + rect_max_x - rect_min_x, + rect_max_y - rect_min_y, + 0.0, + 1.0, + ); } - rpass.set_scissor_rect(x, y, width, height); (cbfn.paint)( PaintCallbackInfo { @@ -776,50 +767,42 @@ impl RenderPass { } } -/// A Rect in physical pixel space, used for setting viewport and cliipping rectangles. -struct PixelRect { +/// A Rect in physical pixel space, used for setting cliipping rectangles. +struct ScissorRect { x: u32, y: u32, width: u32, height: u32, } -/// Convert the Egui clip rect to a physical pixel rect we can use for the GPU viewport/scissor -fn calculate_pixel_rect( - clip_rect: &egui::Rect, - pixels_per_point: f32, - target_size: [u32; 2], -) -> PixelRect { - // Transform clip rect to physical pixels. - let clip_min_x = pixels_per_point * clip_rect.min.x; - let clip_min_y = pixels_per_point * clip_rect.min.y; - let clip_max_x = pixels_per_point * clip_rect.max.x; - let clip_max_y = pixels_per_point * clip_rect.max.y; - - // Make sure clip rect can fit within an `u32`. - let clip_min_x = clip_min_x.clamp(0.0, target_size[0] as f32); - let clip_min_y = clip_min_y.clamp(0.0, target_size[1] as f32); - let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0] as f32); - let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1] as f32); - - let clip_min_x = clip_min_x.round() as u32; - let clip_min_y = clip_min_y.round() as u32; - let clip_max_x = clip_max_x.round() as u32; - let clip_max_y = clip_max_y.round() as u32; - - let width = (clip_max_x - clip_min_x).max(1); - let height = (clip_max_y - clip_min_y).max(1); - - // Clip scissor rectangle to target size. - let x = clip_min_x.min(target_size[0]); - let y = clip_min_y.min(target_size[1]); - let width = width.min(target_size[0] - x); - let height = height.min(target_size[1] - y); - - PixelRect { - x, - y, - width, - height, +impl ScissorRect { + fn new(clip_rect: &egui::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self { + // Transform clip rect to physical pixels: + let clip_min_x = pixels_per_point * clip_rect.min.x; + let clip_min_y = pixels_per_point * clip_rect.min.y; + let clip_max_x = pixels_per_point * clip_rect.max.x; + let clip_max_y = pixels_per_point * clip_rect.max.y; + + // Round to integer: + let clip_min_x = clip_min_x.round() as u32; + let clip_min_y = clip_min_y.round() as u32; + let clip_max_x = clip_max_x.round() as u32; + let clip_max_y = clip_max_y.round() as u32; + + // Clamp: + let clip_min_x = clip_min_x.clamp(0, target_size[0]); + let clip_min_y = clip_min_y.clamp(0, target_size[1]); + let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]); + let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]); + + let width = (clip_max_x - clip_min_x).at_least(1); + let height = (clip_max_y - clip_min_y).at_least(1); + + ScissorRect { + x: clip_min_x, + y: clip_min_y, + width, + height, + } } } diff --git a/egui_demo_app/src/apps/custom3d_glow.rs b/egui_demo_app/src/apps/custom3d_glow.rs index d11823f3137..9798dc15bbe 100644 --- a/egui_demo_app/src/apps/custom3d_glow.rs +++ b/egui_demo_app/src/apps/custom3d_glow.rs @@ -24,21 +24,23 @@ impl Custom3d { 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("glow", "https://github.com/grovesNL/glow"); - ui.label(" (OpenGL)."); - }); - 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!()); + egui::ScrollArea::both() + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("The triangle is being painted using "); + ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); + ui.label(" (OpenGL)."); + }); + 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!()); + }); }); } diff --git a/egui_demo_app/src/apps/custom3d_wgpu.rs b/egui_demo_app/src/apps/custom3d_wgpu.rs index a836960c1d7..d868a57be2a 100644 --- a/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -98,21 +98,23 @@ impl Custom3d { 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!()); + egui::ScrollArea::both() + .auto_shrink([false; 2]) + .show(ui, |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!()); + }); }); } } @@ -138,12 +140,10 @@ impl Custom3d { 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); }); diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index cb83fe24120..8eb3ce37a54 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -724,17 +724,18 @@ fn set_clip_rect( let clip_max_x = pixels_per_point * clip_rect.max.x; let clip_max_y = pixels_per_point * clip_rect.max.y; - // Make sure clip rect can fit within a `u32`: - let clip_min_x = clip_min_x.clamp(0.0, size_in_pixels.0 as f32); - let clip_min_y = clip_min_y.clamp(0.0, size_in_pixels.1 as f32); - let clip_max_x = clip_max_x.clamp(clip_min_x, size_in_pixels.0 as f32); - let clip_max_y = clip_max_y.clamp(clip_min_y, size_in_pixels.1 as f32); - + // Round to integer: let clip_min_x = clip_min_x.round() as i32; let clip_min_y = clip_min_y.round() as i32; let clip_max_x = clip_max_x.round() as i32; let clip_max_y = clip_max_y.round() as i32; + // Clamp: + let clip_min_x = clip_min_x.clamp(0, size_in_pixels.0 as i32); + let clip_min_y = clip_min_y.clamp(0, size_in_pixels.1 as i32); + let clip_max_x = clip_max_x.clamp(clip_min_x, size_in_pixels.0 as i32); + let clip_max_y = clip_max_y.clamp(clip_min_y, size_in_pixels.1 as i32); + unsafe { gl.scissor( clip_min_x,