egui-wgpu: correctly handle viewport rectangle for callbacks

This is important for when a callback shape is inside a ScrollArea.
This commit is contained in:
Emil Ernerfeldt 2022-07-29 00:06:08 +02:00
parent 0571bf67e2
commit 09d636b089
4 changed files with 99 additions and 113 deletions

View file

@ -2,7 +2,7 @@
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32}; use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
use egui::{epaint::Primitive, PaintCallbackInfo}; use egui::{epaint::Primitive, NumExt, PaintCallbackInfo};
use type_map::TypeMap; use type_map::TypeMap;
use wgpu; use wgpu;
use wgpu::util::DeviceExt as _; use wgpu::util::DeviceExt as _;
@ -361,24 +361,21 @@ impl RenderPass {
needs_reset = false; needs_reset = false;
} }
let PixelRect { {
x, let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);
y,
width,
height,
} = calculate_pixel_rect(clip_rect, pixels_per_point, size_in_pixels);
if rect.width == 0 || rect.height == 0 {
// Skip rendering with zero-sized clip areas. // 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 { if let Primitive::Mesh(_) = primitive {
// If this is a mesh, we need to advance the index and vertex buffer iterators
index_buffers.next().unwrap(); index_buffers.next().unwrap();
vertex_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 { match primitive {
Primitive::Mesh(mesh) => { Primitive::Mesh(mesh) => {
@ -408,34 +405,28 @@ impl RenderPass {
if callback.rect.is_positive() { if callback.rect.is_positive() {
needs_reset = true; needs_reset = true;
{
// Set the viewport rect // Set the viewport rect
let PixelRect { // Transform callback rect to physical pixels:
x, let rect_min_x = pixels_per_point * callback.rect.min.x;
y, let rect_min_y = pixels_per_point * callback.rect.min.y;
width, let rect_max_x = pixels_per_point * callback.rect.max.x;
height, let rect_max_y = pixels_per_point * callback.rect.max.y;
} = calculate_pixel_rect(&callback.rect, pixels_per_point, size_in_pixels);
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( rpass.set_viewport(
x as f32, rect_min_x,
y as f32, rect_min_y,
width as f32, rect_max_x - rect_min_x,
height as f32, rect_max_y - rect_min_y,
0.0, 0.0,
1.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;
} }
rpass.set_scissor_rect(x, y, width, height);
(cbfn.paint)( (cbfn.paint)(
PaintCallbackInfo { PaintCallbackInfo {
@ -776,50 +767,42 @@ impl RenderPass {
} }
} }
/// A Rect in physical pixel space, used for setting viewport and cliipping rectangles. /// A Rect in physical pixel space, used for setting cliipping rectangles.
struct PixelRect { struct ScissorRect {
x: u32, x: u32,
y: u32, y: u32,
width: u32, width: u32,
height: u32, height: u32,
} }
/// Convert the Egui clip rect to a physical pixel rect we can use for the GPU viewport/scissor impl ScissorRect {
fn calculate_pixel_rect( fn new(clip_rect: &egui::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
clip_rect: &egui::Rect, // Transform clip rect to physical pixels:
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_x = pixels_per_point * clip_rect.min.x;
let clip_min_y = pixels_per_point * clip_rect.min.y; 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_x = pixels_per_point * clip_rect.max.x;
let clip_max_y = pixels_per_point * clip_rect.max.y; let clip_max_y = pixels_per_point * clip_rect.max.y;
// Make sure clip rect can fit within an `u32`. // Round to integer:
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_x = clip_min_x.round() as u32;
let clip_min_y = clip_min_y.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_x = clip_max_x.round() as u32;
let clip_max_y = clip_max_y.round() as u32; let clip_max_y = clip_max_y.round() as u32;
let width = (clip_max_x - clip_min_x).max(1); // Clamp:
let height = (clip_max_y - clip_min_y).max(1); 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]);
// Clip scissor rectangle to target size. let width = (clip_max_x - clip_min_x).at_least(1);
let x = clip_min_x.min(target_size[0]); let height = (clip_max_y - clip_min_y).at_least(1);
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 { ScissorRect {
x, x: clip_min_x,
y, y: clip_min_y,
width, width,
height, height,
} }
}
} }

View file

@ -24,15 +24,16 @@ impl Custom3d {
impl eframe::App for Custom3d { impl eframe::App for Custom3d {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both()
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0; ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using "); ui.label("The triangle is being painted using ");
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
ui.label(" (OpenGL)."); ui.label(" (OpenGL).");
}); });
ui.label( ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
"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| { egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui); self.custom_painting(ui);
@ -40,6 +41,7 @@ impl eframe::App for Custom3d {
ui.label("Drag to rotate!"); ui.label("Drag to rotate!");
ui.add(egui_demo_lib::egui_github_link_file!()); ui.add(egui_demo_lib::egui_github_link_file!());
}); });
});
} }
fn on_exit(&mut self, gl: Option<&glow::Context>) { fn on_exit(&mut self, gl: Option<&glow::Context>) {

View file

@ -98,15 +98,16 @@ impl Custom3d {
impl eframe::App for Custom3d { impl eframe::App for Custom3d {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both()
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0; ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using "); ui.label("The triangle is being painted using ");
ui.hyperlink_to("WGPU", "https://wgpu.rs"); ui.hyperlink_to("WGPU", "https://wgpu.rs");
ui.label(" (Portable Rust graphics API awesomeness)"); ui.label(" (Portable Rust graphics API awesomeness)");
}); });
ui.label( ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
"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| { egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui); self.custom_painting(ui);
@ -114,6 +115,7 @@ impl eframe::App for Custom3d {
ui.label("Drag to rotate!"); ui.label("Drag to rotate!");
ui.add(egui_demo_lib::egui_github_link_file!()); ui.add(egui_demo_lib::egui_github_link_file!());
}); });
});
} }
} }
@ -138,12 +140,10 @@ impl Custom3d {
let cb = egui_wgpu::CallbackFn::new() let cb = egui_wgpu::CallbackFn::new()
.prepare(move |device, queue, paint_callback_resources| { .prepare(move |device, queue, paint_callback_resources| {
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap(); let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
resources.prepare(device, queue, angle); resources.prepare(device, queue, angle);
}) })
.paint(move |_info, rpass, paint_callback_resources| { .paint(move |_info, rpass, paint_callback_resources| {
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap(); let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
resources.paint(rpass); resources.paint(rpass);
}); });

View file

@ -724,17 +724,18 @@ fn set_clip_rect(
let clip_max_x = pixels_per_point * clip_rect.max.x; let clip_max_x = pixels_per_point * clip_rect.max.x;
let clip_max_y = pixels_per_point * clip_rect.max.y; let clip_max_y = pixels_per_point * clip_rect.max.y;
// Make sure clip rect can fit within a `u32`: // Round to integer:
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);
let clip_min_x = clip_min_x.round() as i32; let clip_min_x = clip_min_x.round() as i32;
let clip_min_y = clip_min_y.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_x = clip_max_x.round() as i32;
let clip_max_y = clip_max_y.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 { unsafe {
gl.scissor( gl.scissor(
clip_min_x, clip_min_x,