From 37227eb3c6120c8052d1777188bc9b337da4c3f6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Mar 2022 14:55:29 +0100 Subject: [PATCH] Add Shape::Callback to do custom rendering inside of an egui UI --- CHANGELOG.md | 11 +- Cargo.lock | 2 + README.md | 4 +- eframe/Cargo.toml | 2 + eframe/examples/custom_3d.rs | 193 ++++++++++++++++++++++++++++++++++ egui/src/context.rs | 19 ++-- egui/src/introspection.rs | 6 +- egui/src/lib.rs | 10 +- egui_demo_lib/src/lib.rs | 11 +- egui_glium/src/epi_backend.rs | 4 +- egui_glium/src/lib.rs | 4 +- egui_glium/src/painter.rs | 25 +++-- egui_glow/src/epi_backend.rs | 4 +- egui_glow/src/painter.rs | 123 +++++++++++++++------- egui_glow/src/post_process.rs | 4 + egui_glow/src/winit.rs | 6 +- egui_web/src/backend.rs | 13 ++- egui_web/src/glow_wrapping.rs | 10 +- egui_web/src/lib.rs | 4 +- egui_web/src/painter.rs | 8 +- egui_web/src/webgl1.rs | 75 +++++++------ egui_web/src/webgl2.rs | 75 +++++++------ epaint/CHANGELOG.md | 1 + epaint/src/lib.rs | 22 ++-- epaint/src/shape.rs | 73 ++++++++++++- epaint/src/shape_transform.rs | 3 + epaint/src/stats.rs | 31 +++--- epaint/src/tessellator.rs | 110 +++++++++++++------ epi/src/lib.rs | 7 ++ 29 files changed, 648 insertions(+), 212 deletions(-) create mode 100644 eframe/examples/custom_3d.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a7ffb8f36..2e966af2865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,16 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ## Unreleased -* Fixed ComboBoxes always being rendered left-aligned ([1304](https://github.com/emilk/egui/pull/1304)) +### Added ⭐ +* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). + +### Changed 🔧 +* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). + +### Fixed 🐛 +* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). + + ## 0.17.0 - 2022-02-22 - Improved font selection and image handling diff --git a/Cargo.lock b/Cargo.lock index 9338554a4b1..28983ca4836 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -992,7 +992,9 @@ dependencies = [ "egui_web", "ehttp", "epi", + "glow", "image", + "parking_lot 0.12.0", "poll-promise", "rfd", ] diff --git a/README.md b/README.md index 4110d04a6cc..9e584c09842 100644 --- a/README.md +++ b/README.md @@ -204,9 +204,9 @@ loop { let full_output = egui_ctx.run(raw_input, |egui_ctx| { my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here }); - let clipped_meshes = egui_ctx.tessellate(full_output.shapes); // creates triangles to paint + let clipped_primitives = egui_ctx.tessellate(full_output.shapes); // creates triangles to paint - my_integration.paint(&full_output.textures_delta, clipped_meshes); + my_integration.paint(&full_output.textures_delta, clipped_primitives); let platform_output = full_output.platform_output; my_integration.set_cursor_icon(platform_output.cursor_icon); diff --git a/eframe/Cargo.toml b/eframe/Cargo.toml index 32da7b50067..7f402861d7d 100644 --- a/eframe/Cargo.toml +++ b/eframe/Cargo.toml @@ -73,9 +73,11 @@ egui_web = { version = "0.17.0", path = "../egui_web", default-features = false, # For examples: egui_extras = { path = "../egui_extras", features = ["image", "svg"] } ehttp = "0.2" +glow = "0.11" image = { version = "0.24", default-features = false, features = [ "jpeg", "png", ] } +parking_lot = "0.12" poll-promise = "0.1" rfd = "0.8" diff --git a/eframe/examples/custom_3d.rs b/eframe/examples/custom_3d.rs new file mode 100644 index 00000000000..53dd850e2fa --- /dev/null +++ b/eframe/examples/custom_3d.rs @@ -0,0 +1,193 @@ +//! This demo shows how to embed 3D rendering using [`glow`](https://github.com/grovesNL/glow) in `eframe`. +//! +//! This is very advanced usage, and you need to be careful. +//! +//! If you want an easier way to show 3D graphics with egui, take a look at: +//! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) +//! * [`three-d`](https://github.com/asny/three-d) + +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::{egui, epi}; + +use parking_lot::Mutex; +use std::sync::Arc; + +#[derive(Default)] +struct MyApp { + rotating_triangle: Arc>>, + angle: f32, +} + +impl epi::App for MyApp { + fn name(&self) -> &str { + "Custom 3D painting inside an egui window" + } + + fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Here is some 3D stuff:"); + + egui::ScrollArea::both().show(ui, |ui| { + egui::Frame::dark_canvas(ui.style()).show(ui, |ui| { + self.custom_painting(ui); + }); + ui.label("Drag to rotate!"); + }); + }); + + let mut frame = egui::Frame::window(&*ctx.style()); + frame.fill = frame.fill.linear_multiply(0.5); // transparent + egui::Window::new("3D stuff in a window") + .frame(frame) + .show(ctx, |ui| { + self.custom_painting(ui); + }); + } +} + +impl MyApp { + fn custom_painting(&mut self, ui: &mut egui::Ui) { + let (rect, response) = + ui.allocate_exact_size(egui::Vec2::splat(256.0), egui::Sense::drag()); + + self.angle += response.drag_delta().x * 0.01; + + let angle = self.angle; + let rotating_triangle = self.rotating_triangle.clone(); + + let callback = egui::epaint::PaintCallback { + rect, + callback: std::sync::Arc::new(move |render_ctx| { + if let Some(gl) = render_ctx.downcast_ref::() { + let mut rotating_triangle = rotating_triangle.lock(); + let rotating_triangle = + rotating_triangle.get_or_insert_with(|| RotatingTriangle::new(gl)); + rotating_triangle.paint(gl, angle); + } else { + eprintln!("Can't do custom painting because we are not using a glow context"); + } + }), + }; + ui.painter().add(callback); + } +} + +struct RotatingTriangle { + program: glow::Program, + vertex_array: glow::VertexArray, +} + +impl RotatingTriangle { + fn new(gl: &glow::Context) -> Self { + use glow::HasContext as _; + + let shader_version = if cfg!(target_arch = "wasm32") { + "#version 300 es" + } else { + "#version 410" + }; + + unsafe { + let program = gl.create_program().expect("Cannot create program"); + + let (vertex_shader_source, fragment_shader_source) = ( + r#" + const vec2 verts[3] = vec2[3]( + vec2(0.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, -1.0) + ); + const vec4 colors[3] = vec4[3]( + vec4(1.0, 0.0, 0.0, 1.0), + vec4(0.0, 1.0, 0.0, 1.0), + vec4(0.0, 0.0, 1.0, 1.0) + ); + out vec4 v_color; + uniform float u_angle; + void main() { + v_color = colors[gl_VertexID]; + gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); + gl_Position.x *= cos(u_angle); + } + "#, + r#" + precision mediump float; + in vec4 v_color; + out vec4 out_color; + void main() { + out_color = v_color; + } + "#, + ); + + let shader_sources = [ + (glow::VERTEX_SHADER, vertex_shader_source), + (glow::FRAGMENT_SHADER, fragment_shader_source), + ]; + + let shaders: Vec<_> = shader_sources + .iter() + .map(|(shader_type, shader_source)| { + let shader = gl + .create_shader(*shader_type) + .expect("Cannot create shader"); + gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source)); + gl.compile_shader(shader); + if !gl.get_shader_compile_status(shader) { + panic!("{}", gl.get_shader_info_log(shader)); + } + gl.attach_shader(program, shader); + shader + }) + .collect(); + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + for shader in shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + let vertex_array = gl + .create_vertex_array() + .expect("Cannot create vertex array"); + + Self { + program, + vertex_array, + } + } + } + + // TODO: figure out how to call this in a nice way + #[allow(unused)] + fn destroy(self, gl: &glow::Context) { + use glow::HasContext as _; + unsafe { + gl.delete_program(self.program); + gl.delete_vertex_array(self.vertex_array); + } + } + + fn paint(&self, gl: &glow::Context, angle: f32) { + use glow::HasContext as _; + unsafe { + gl.use_program(Some(self.program)); + gl.uniform_1_f32( + gl.get_uniform_location(self.program, "u_angle").as_ref(), + angle, + ); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.draw_arrays(glow::TRIANGLES, 0, 3); + } + } +} + +fn main() { + let options = eframe::NativeOptions::default(); + eframe::run_native(Box::new(MyApp::default()), options); +} diff --git a/egui/src/context.rs b/egui/src/context.rs index c0c261fc5d8..e763495a3f4 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -137,8 +137,8 @@ impl ContextImpl { /// }); /// }); /// handle_platform_output(full_output.platform_output); -/// let clipped_meshes = ctx.tessellate(full_output.shapes); // create triangles to paint -/// paint(full_output.textures_delta, clipped_meshes); +/// let clipped_primitives = ctx.tessellate(full_output.shapes); // create triangles to paint +/// paint(full_output.textures_delta, clipped_primitives); /// } /// ``` #[derive(Clone)] @@ -773,7 +773,7 @@ impl Context { } /// Tessellate the given shapes into triangle meshes. - pub fn tessellate(&self, shapes: Vec) -> Vec { + pub fn tessellate(&self, shapes: Vec) -> Vec { // A tempting optimization is to reuse the tessellation from last frame if the // shapes are the same, but just comparing the shapes takes about 50% of the time // it takes to tessellate them, so it is not a worth optimization. @@ -782,13 +782,13 @@ impl Context { tessellation_options.pixels_per_point = self.pixels_per_point(); tessellation_options.aa_size = 1.0 / self.pixels_per_point(); let paint_stats = PaintStats::from_shapes(&shapes); - let clipped_meshes = tessellator::tessellate_shapes( + let clipped_primitives = tessellator::tessellate_shapes( shapes, tessellation_options, self.fonts().font_image_size(), ); - self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes); - clipped_meshes + self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives); + clipped_primitives } // --------------------------------------------------------------------- @@ -1246,3 +1246,10 @@ impl Context { self.set_style(style); } } + +#[cfg(test)] +#[test] +fn context_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} diff --git a/egui/src/introspection.rs b/egui/src/introspection.rs index e5f814cc957..e5b9c7ea168 100644 --- a/egui/src/introspection.rs +++ b/egui/src/introspection.rs @@ -91,9 +91,10 @@ impl Widget for &epaint::stats::PaintStats { shape_path, shape_mesh, shape_vec, + num_callbacks, text_shape_vertices, text_shape_indices, - clipped_meshes, + clipped_primitives, vertices, indices, } = self; @@ -104,6 +105,7 @@ impl Widget for &epaint::stats::PaintStats { label(ui, shape_path, "paths"); label(ui, shape_mesh, "nested meshes"); label(ui, shape_vec, "nested shapes"); + ui.label(format!("{} callbacks", num_callbacks)); ui.add_space(10.0); ui.label("Text shapes:"); @@ -113,7 +115,7 @@ impl Widget for &epaint::stats::PaintStats { ui.add_space(10.0); ui.label("Tessellated (and culled):"); - label(ui, clipped_meshes, "clipped_meshes") + label(ui, clipped_primitives, "clipped_primitives") .on_hover_text("Number of separate clip rectangles"); label(ui, vertices, "vertices"); label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles"); diff --git a/egui/src/lib.rs b/egui/src/lib.rs index 8c05fc1a076..07e283e42ca 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -112,7 +112,7 @@ //! ``` no_run //! # fn handle_platform_output(_: egui::PlatformOutput) {} //! # fn gather_input() -> egui::RawInput { egui::RawInput::default() } -//! # fn paint(textures_detla: egui::TexturesDelta, _: Vec) {} +//! # fn paint(textures_detla: egui::TexturesDelta, _: Vec) {} //! let mut ctx = egui::Context::default(); //! //! // Game loop: @@ -128,8 +128,8 @@ //! }); //! }); //! handle_platform_output(full_output.platform_output); -//! let clipped_meshes = ctx.tessellate(full_output.shapes); // create triangles to paint -//! paint(full_output.textures_delta, clipped_meshes); +//! let clipped_primitives = ctx.tessellate(full_output.shapes); // create triangles to paint +//! paint(full_output.textures_delta, clipped_primitives); //! } //! ``` //! @@ -386,8 +386,8 @@ pub use epaint::{ color, mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::TexturesDelta, - AlphaImage, ClippedMesh, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape, Stroke, - TextureHandle, TextureId, + AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape, + Stroke, TextureHandle, TextureId, }; pub mod text { diff --git a/egui_demo_lib/src/lib.rs b/egui_demo_lib/src/lib.rs index 96112481669..ca15d3482f6 100644 --- a/egui_demo_lib/src/lib.rs +++ b/egui_demo_lib/src/lib.rs @@ -148,8 +148,8 @@ fn test_egui_e2e() { let full_output = ctx.run(raw_input.clone(), |ctx| { demo_windows.ui(ctx); }); - let clipped_meshes = ctx.tessellate(full_output.shapes); - assert!(!clipped_meshes.is_empty()); + let clipped_primitives = ctx.tessellate(full_output.shapes); + assert!(!clipped_primitives.is_empty()); } } @@ -167,8 +167,11 @@ fn test_egui_zero_window_size() { let full_output = ctx.run(raw_input.clone(), |ctx| { demo_windows.ui(ctx); }); - let clipped_meshes = ctx.tessellate(full_output.shapes); - assert!(clipped_meshes.is_empty(), "There should be nothing to show"); + let clipped_primitives = ctx.tessellate(full_output.shapes); + assert!( + clipped_primitives.is_empty(), + "There should be nothing to show" + ); } } diff --git a/egui_glium/src/epi_backend.rs b/egui_glium/src/epi_backend.rs index dafca3b4c9a..cce366d625e 100644 --- a/egui_glium/src/epi_backend.rs +++ b/egui_glium/src/epi_backend.rs @@ -76,7 +76,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { integration.handle_platform_output(display.gl_window().window(), platform_output); - let clipped_meshes = integration.egui_ctx.tessellate(shapes); + let clipped_primitives = integration.egui_ctx.tessellate(shapes); // paint: { @@ -89,7 +89,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { &display, &mut target, integration.egui_ctx.pixels_per_point(), - clipped_meshes, + clipped_primitives, &textures_delta, ); diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 991a072aba6..9a0e5a36319 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -164,12 +164,12 @@ impl EguiGlium { pub fn paint(&mut self, display: &glium::Display, target: &mut T) { let shapes = std::mem::take(&mut self.shapes); let textures_delta = std::mem::take(&mut self.textures_delta); - let clipped_meshes = self.egui_ctx.tessellate(shapes); + let clipped_primitives = self.egui_ctx.tessellate(shapes); self.painter.paint_and_update_textures( display, target, self.egui_ctx.pixels_per_point(), - clipped_meshes, + clipped_primitives, &textures_delta, ); } diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 26f74133a4b..2e9ff9e2bee 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -1,6 +1,8 @@ #![allow(deprecated)] // legacy implement_vertex macro #![allow(semicolon_in_expressions_from_macros)] // glium::program! macro +use egui::epaint::Primitive; + use { ahash::AHashMap, egui::{emath::Rect, epaint::Mesh}, @@ -70,14 +72,14 @@ impl Painter { display: &glium::Display, target: &mut T, pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: Vec, textures_delta: &egui::TexturesDelta, ) { for (id, image_delta) in &textures_delta.set { self.set_texture(display, *id, image_delta); } - self.paint_meshes(display, target, pixels_per_point, clipped_meshes); + self.paint_primitives(display, target, pixels_per_point, clipped_primitives); for &id in &textures_delta.free { self.free_texture(id); @@ -87,15 +89,26 @@ impl Painter { /// Main entry-point for painting a frame. /// You should call `target.clear_color(..)` before /// and `target.finish()` after this. - pub fn paint_meshes( + pub fn paint_primitives( &mut self, display: &glium::Display, target: &mut T, pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: Vec, ) { - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh); + for egui::ClippedPrimitive { + clip_rect, + primitive, + } in clipped_primitives + { + match primitive { + Primitive::Mesh(mesh) => { + self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh); + } + Primitive::Callback(_) => { + panic!("Custom rendering callbacks are not implemented in egui_glium"); + } + } } } diff --git a/egui_glow/src/epi_backend.rs b/egui_glow/src/epi_backend.rs index 878a5e1cba6..9a9efdcc1f5 100644 --- a/egui_glow/src/epi_backend.rs +++ b/egui_glow/src/epi_backend.rs @@ -92,7 +92,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { integration.handle_platform_output(gl_window.window(), platform_output); - let clipped_meshes = integration.egui_ctx.tessellate(shapes); + let clipped_primitives = integration.egui_ctx.tessellate(shapes); // paint: { @@ -107,7 +107,7 @@ pub fn run(app: Box, native_options: &epi::NativeOptions) -> ! { &gl, gl_window.window().inner_size().into(), integration.egui_ctx.pixels_per_point(), - clipped_meshes, + clipped_primitives, &textures_delta, ); diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index c8c59b0b066..165b2ffafb5 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use egui::{ emath::Rect, - epaint::{Color32, Mesh, Vertex}, + epaint::{Color32, Mesh, Primitive, Vertex}, }; use glow::HasContext; use memoffset::offset_of; @@ -275,14 +275,14 @@ impl Painter { gl: &glow::Context, inner_size: [u32; 2], pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: Vec, textures_delta: &egui::TexturesDelta, ) { for (id, image_delta) in &textures_delta.set { self.set_texture(gl, *id, image_delta); } - self.paint_meshes(gl, inner_size, pixels_per_point, clipped_meshes); + self.paint_primitives(gl, inner_size, pixels_per_point, clipped_primitives); for &id in &textures_delta.free { self.free_texture(gl, id); @@ -308,12 +308,12 @@ impl Painter { /// /// Please be mindful of these effects when integrating into your program, and also be mindful /// of the effects your program might have on this code. Look at the source if in doubt. - pub fn paint_meshes( + pub fn paint_primitives( &mut self, gl: &glow::Context, inner_size: [u32; 2], pixels_per_point: f32, - clipped_meshes: Vec, + clipped_primitives: Vec, ) { self.assert_not_destroyed(); @@ -323,8 +323,52 @@ impl Painter { } } let size_in_pixels = unsafe { self.prepare_painting(inner_size, gl, pixels_per_point) }; - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - self.paint_mesh(gl, size_in_pixels, pixels_per_point, clip_rect, &mesh); + + for egui::ClippedPrimitive { + clip_rect, + primitive, + } in clipped_primitives + { + set_clip_rect(gl, size_in_pixels, pixels_per_point, clip_rect); + + match primitive { + Primitive::Mesh(mesh) => { + self.paint_mesh(gl, &mesh); + } + Primitive::Callback(callback) => { + if callback.rect.is_positive() { + // 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() as i32; + let rect_min_y = rect_min_y.round() as i32; + let rect_max_x = rect_max_x.round() as i32; + let rect_max_y = rect_max_y.round() as i32; + + unsafe { + gl.viewport( + rect_min_x, + size_in_pixels.1 as i32 - rect_max_y, + rect_max_x - rect_min_x, + rect_max_y - rect_min_y, + ); + } + + callback.call(gl); + + // Restore state: + unsafe { + if let Some(ref mut post_process) = self.post_process { + post_process.bind(gl); + } + self.prepare_painting(inner_size, gl, pixels_per_point) + }; + } + } + } } unsafe { self.vertex_array.unbind_vertex_array(gl); @@ -341,14 +385,7 @@ impl Painter { } #[inline(never)] // Easier profiling - fn paint_mesh( - &mut self, - gl: &glow::Context, - size_in_pixels: (u32, u32), - pixels_per_point: f32, - clip_rect: Rect, - mesh: &Mesh, - ) { + fn paint_mesh(&mut self, gl: &glow::Context, mesh: &Mesh) { debug_assert!(mesh.is_valid()); if let Some(texture) = self.get_texture(mesh.texture_id) { unsafe { @@ -369,30 +406,7 @@ impl Painter { gl.bind_texture(glow::TEXTURE_2D, Some(texture)); } - // 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 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); - - 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; - unsafe { - gl.scissor( - clip_min_x, - size_in_pixels.1 as i32 - clip_max_y, - clip_max_x - clip_min_x, - clip_max_y - clip_min_y, - ); gl.draw_elements( glow::TRIANGLES, mesh.indices.len() as i32, @@ -626,3 +640,36 @@ impl epi::NativeTexture for Painter { } } } + +fn set_clip_rect( + gl: &glow::Context, + size_in_pixels: (u32, u32), + pixels_per_point: f32, + clip_rect: Rect, +) { + // 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 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); + + 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; + + unsafe { + gl.scissor( + clip_min_x, + size_in_pixels.1 as i32 - clip_max_y, + clip_max_x - clip_min_x, + clip_max_y - clip_min_y, + ); + } +} diff --git a/egui_glow/src/post_process.rs b/egui_glow/src/post_process.rs index e12c1a0e28f..569f54364b4 100644 --- a/egui_glow/src/post_process.rs +++ b/egui_glow/src/post_process.rs @@ -190,6 +190,10 @@ impl PostProcess { gl.clear(glow::COLOR_BUFFER_BIT); } + pub(crate) unsafe fn bind(&self, gl: &glow::Context) { + gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); + } + pub(crate) unsafe fn end(&self, gl: &glow::Context) { gl.bind_framebuffer(glow::FRAMEBUFFER, None); gl.disable(glow::SCISSOR_TEST); diff --git a/egui_glow/src/winit.rs b/egui_glow/src/winit.rs index 8be08c88197..835e6a3804d 100644 --- a/egui_glow/src/winit.rs +++ b/egui_glow/src/winit.rs @@ -71,13 +71,13 @@ impl EguiGlow { self.painter.set_texture(gl, id, &image_delta); } - let clipped_meshes = self.egui_ctx.tessellate(shapes); + let clipped_primitives = self.egui_ctx.tessellate(shapes); let dimensions: [u32; 2] = window.inner_size().into(); - self.painter.paint_meshes( + self.painter.paint_primitives( gl, dimensions, self.egui_ctx.pixels_per_point(), - clipped_meshes, + clipped_primitives, ); for id in textures_delta.free.drain(..) { diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index f3033cac393..13ed549c429 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -260,7 +260,7 @@ impl AppRunner { /// Returns `true` if egui requests a repaint. /// /// Call [`Self::paint`] later to paint - pub fn logic(&mut self) -> Result<(bool, Vec), JsValue> { + pub fn logic(&mut self) -> Result<(bool, Vec), JsValue> { let frame_start = now_sec(); resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points()); @@ -279,7 +279,7 @@ impl AppRunner { self.handle_platform_output(platform_output); self.textures_delta.append(textures_delta); - let clipped_meshes = self.egui_ctx.tessellate(shapes); + let clipped_primitives = self.egui_ctx.tessellate(shapes); { let app_output = self.frame.take_app_output(); @@ -293,17 +293,20 @@ impl AppRunner { } self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32); - Ok((needs_repaint, clipped_meshes)) + Ok((needs_repaint, clipped_primitives)) } /// Paint the results of the last call to [`Self::logic`]. - pub fn paint(&mut self, clipped_meshes: Vec) -> Result<(), JsValue> { + pub fn paint( + &mut self, + clipped_primitives: Vec, + ) -> Result<(), JsValue> { let textures_delta = std::mem::take(&mut self.textures_delta); self.painter.clear(self.app.clear_color()); self.painter.paint_and_update_textures( - clipped_meshes, + clipped_primitives, self.egui_ctx.pixels_per_point(), &textures_delta, )?; diff --git a/egui_web/src/glow_wrapping.rs b/egui_web/src/glow_wrapping.rs index 839b23e473b..5585911298c 100644 --- a/egui_web/src/glow_wrapping.rs +++ b/egui_web/src/glow_wrapping.rs @@ -1,4 +1,4 @@ -use egui::{ClippedMesh, Rgba}; +use egui::{ClippedPrimitive, Rgba}; use egui_glow::glow; use wasm_bindgen::JsCast; use wasm_bindgen::JsValue; @@ -62,17 +62,17 @@ impl crate::Painter for WrappedGlowPainter { egui_glow::painter::clear(&self.glow_ctx, canvas_dimension, clear_color) } - fn paint_meshes( + fn paint_primitives( &mut self, - clipped_meshes: Vec, + clipped_primitives: Vec, pixels_per_point: f32, ) -> Result<(), JsValue> { let canvas_dimension = [self.canvas.width(), self.canvas.height()]; - self.painter.paint_meshes( + self.painter.paint_primitives( &self.glow_ctx, canvas_dimension, pixels_per_point, - clipped_meshes, + clipped_primitives, ); Ok(()) } diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 1f271eadbf1..7139d29e45c 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -355,8 +355,8 @@ fn paint_and_schedule(runner_ref: &AppRunnerRef, panicked: Arc) -> R fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.lock(); if runner_lock.needs_repaint.fetch_and_clear() { - let (needs_repaint, clipped_meshes) = runner_lock.logic()?; - runner_lock.paint(clipped_meshes)?; + let (needs_repaint, clipped_primitives) = runner_lock.logic()?; + runner_lock.paint(clipped_primitives)?; if needs_repaint { runner_lock.needs_repaint.set_true(); } diff --git a/egui_web/src/painter.rs b/egui_web/src/painter.rs index f0c26e7ca13..40c45859190 100644 --- a/egui_web/src/painter.rs +++ b/egui_web/src/painter.rs @@ -15,9 +15,9 @@ pub trait Painter { fn clear(&mut self, clear_color: egui::Rgba); - fn paint_meshes( + fn paint_primitives( &mut self, - clipped_meshes: Vec, + clipped_primitives: Vec, pixels_per_point: f32, ) -> Result<(), JsValue>; @@ -25,7 +25,7 @@ pub trait Painter { fn paint_and_update_textures( &mut self, - clipped_meshes: Vec, + clipped_primitives: Vec, pixels_per_point: f32, textures_delta: &egui::TexturesDelta, ) -> Result<(), JsValue> { @@ -33,7 +33,7 @@ pub trait Painter { self.set_texture(*id, image_delta); } - self.paint_meshes(clipped_meshes, pixels_per_point)?; + self.paint_primitives(clipped_primitives, pixels_per_point)?; for &id in &textures_delta.free { self.free_texture(id); diff --git a/egui_web/src/webgl1.rs b/egui_web/src/webgl1.rs index bfa3976249b..fa643d59113 100644 --- a/egui_web/src/webgl1.rs +++ b/egui_web/src/webgl1.rs @@ -9,7 +9,7 @@ use { }, }; -use egui::{emath::vec2, epaint::Color32}; +use egui::{emath::vec2, epaint::Color32, epaint::Primitive}; type Gl = WebGlRenderingContext; @@ -370,9 +370,9 @@ impl crate::Painter for WebGlPainter { gl.clear(Gl::COLOR_BUFFER_BIT); } - fn paint_meshes( + fn paint_primitives( &mut self, - clipped_meshes: Vec, + clipped_primitives: Vec, pixels_per_point: f32, ) -> Result<(), JsValue> { let gl = &self.gl; @@ -402,36 +402,47 @@ impl crate::Painter for WebGlPainter { let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); gl.uniform1i(Some(&u_sampler_loc), 0); - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - if let Some(gl_texture) = self.get_texture(mesh.texture_id) { - gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); - - 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; - let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x); - let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y); - let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x); - let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y); - 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; - - // scissor Y coordinate is from the bottom - gl.scissor( - clip_min_x, - self.canvas.height() as i32 - clip_max_y, - clip_max_x - clip_min_x, - clip_max_y - clip_min_y, - ); - - for mesh in mesh.split_to_u16() { - self.paint_mesh(&mesh)?; + for egui::ClippedPrimitive { + clip_rect, + primitive, + } in clipped_primitives + { + 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; + let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x); + let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y); + let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x); + let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y); + 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; + + // scissor Y coordinate is from the bottom + gl.scissor( + clip_min_x, + self.canvas.height() as i32 - clip_max_y, + clip_max_x - clip_min_x, + clip_max_y - clip_min_y, + ); + + match primitive { + Primitive::Callback(_) => { + tracing::warn!("Custom rendering callbacks are not implemented for WebGL. Use the glow backend instead."); + } + Primitive::Mesh(mesh) => { + if let Some(gl_texture) = self.get_texture(mesh.texture_id) { + gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); + + for mesh in mesh.split_to_u16() { + self.paint_mesh(&mesh)?; + } + } else { + tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id); + } } - } else { - tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id); } } diff --git a/egui_web/src/webgl2.rs b/egui_web/src/webgl2.rs index c8ede7fd74d..6e993abad26 100644 --- a/egui_web/src/webgl2.rs +++ b/egui_web/src/webgl2.rs @@ -10,7 +10,7 @@ use { }, }; -use egui::{emath::vec2, epaint::Color32}; +use egui::{emath::vec2, epaint::Color32, epaint::Primitive}; type Gl = WebGl2RenderingContext; @@ -350,9 +350,9 @@ impl crate::Painter for WebGl2Painter { gl.clear(Gl::COLOR_BUFFER_BIT); } - fn paint_meshes( + fn paint_primitives( &mut self, - clipped_meshes: Vec, + clipped_primitives: Vec, pixels_per_point: f32, ) -> Result<(), JsValue> { let gl = &self.gl; @@ -381,36 +381,47 @@ impl crate::Painter for WebGl2Painter { let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); gl.uniform1i(Some(&u_sampler_loc), 0); - for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes { - if let Some(gl_texture) = self.get_texture(mesh.texture_id) { - gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); - - 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; - let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x); - let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y); - let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x); - let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y); - 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; - - // scissor Y coordinate is from the bottom - gl.scissor( - clip_min_x, - self.canvas.height() as i32 - clip_max_y, - clip_max_x - clip_min_x, - clip_max_y - clip_min_y, - ); - - for mesh in mesh.split_to_u16() { - self.paint_mesh(&mesh)?; + for egui::ClippedPrimitive { + clip_rect, + primitive, + } in clipped_primitives + { + 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; + let clip_min_x = clip_min_x.clamp(0.0, screen_size_pixels.x); + let clip_min_y = clip_min_y.clamp(0.0, screen_size_pixels.y); + let clip_max_x = clip_max_x.clamp(clip_min_x, screen_size_pixels.x); + let clip_max_y = clip_max_y.clamp(clip_min_y, screen_size_pixels.y); + 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; + + // scissor Y coordinate is from the bottom + gl.scissor( + clip_min_x, + self.canvas.height() as i32 - clip_max_y, + clip_max_x - clip_min_x, + clip_max_y - clip_min_y, + ); + + match primitive { + Primitive::Callback(_) => { + tracing::warn!("Custom rendering callbacks are not implemented for WebGL. Use the glow backend instead."); + } + Primitive::Mesh(mesh) => { + if let Some(gl_texture) = self.get_texture(mesh.texture_id) { + gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); + + for mesh in mesh.split_to_u16() { + self.paint_mesh(&mesh)?; + } + } else { + tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id); + } } - } else { - tracing::warn!("WebGL: Failed to find texture {:?}", mesh.texture_id); } } diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index eb83894ce92..9a3007048b1 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to the epaint crate will be documented in this file. ## Unreleased +* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). ## 0.17.0 - 2022-02-22 diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index 55e6b7b7672..b041868e97b 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -110,7 +110,7 @@ pub use { image::{AlphaImage, ColorImage, ImageData, ImageDelta}, mesh::{Mesh, Mesh16, Vertex}, shadow::Shadow, - shape::{CircleShape, PathShape, RectShape, Rounding, Shape, TextShape}, + shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape}, stats::PaintStats, stroke::Stroke, tessellator::{tessellate_shapes, TessellationOptions, Tessellator}, @@ -166,18 +166,24 @@ pub struct ClippedShape( pub Shape, ); -/// A [`Mesh`] within a clip rectangle. +/// A [`Mesh`] or [`CallbackShape`] within a clip rectangle. /// /// Everything is using logical points. #[derive(Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct ClippedMesh( +pub struct ClippedPrimitive { /// Clip / scissor rectangle. /// Only show the part of the [`Mesh`] that falls within this. - pub emath::Rect, - /// The shape - pub Mesh, -); + pub clip_rect: emath::Rect, + /// What to paint - either a [`Mesh`] or a [`CallbackShape`]. + pub primitive: Primitive, +} + +/// A rendering primitive - either a [`Mesh`] or a [`CallbackShape`]. +#[derive(Clone, Debug)] +pub enum Primitive { + Mesh(Mesh), + Callback(PaintCallback), +} // ---------------------------------------------------------------------------- diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 40c9b3fe1b9..7154675395f 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -1,10 +1,13 @@ +//! The different shapes that can be painted. + use crate::{ text::{FontId, Fonts, Galley}, Color32, Mesh, Stroke, }; -use crate::{CubicBezierShape, QuadraticBezierShape}; use emath::*; +pub use crate::{CubicBezierShape, QuadraticBezierShape}; + /// A paint primitive such as a circle or a piece of text. /// Coordinates are all screen space points (not physical pixels). #[must_use = "Add a Shape to a Painter"] @@ -29,6 +32,16 @@ pub enum Shape { Mesh(Mesh), QuadraticBezier(QuadraticBezierShape), CubicBezier(CubicBezierShape), + + /// Backend-specific painting. + Callback(PaintCallback), +} + +#[cfg(test)] +#[test] +fn shape_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); } impl From> for Shape { @@ -196,6 +209,7 @@ impl Shape { Self::Mesh(mesh) => mesh.calc_bounds(), Self::QuadraticBezier(bezier) => bezier.visual_bounding_rect(), Self::CubicBezier(bezier) => bezier.visual_bounding_rect(), + Self::Callback(custom) => custom.rect, } } } @@ -252,6 +266,9 @@ impl Shape { *p += delta; } } + Shape::Callback(shape) => { + shape.rect = shape.rect.translate(delta); + } } } } @@ -616,3 +633,57 @@ fn dashes_from_line( position_on_segment -= segment_length; }); } + +// ---------------------------------------------------------------------------- + +/// If you want to paint some 3D shapes inside an egui region, you can use this. +/// +/// This is advanced usage, and is backend specific. +#[derive(Clone)] +pub struct PaintCallback { + /// Where to paint. + pub rect: Rect, + + /// Paint something custom using. + /// + /// The argument is the render context, and what it contains depends on the backend. + /// In `eframe` it will be a `glow::Context`. + /// + /// The rendering backend is responsible for first setting the active viewport to [`Self::rect`]. + /// The rendering backend is also responsible for restoring any state it needs, + /// such as the bound shader program and vertex array. + pub callback: std::sync::Arc, +} + +impl PaintCallback { + #[inline] + pub fn call(&self, render_ctx: &dyn std::any::Any) { + (self.callback)(render_ctx); + } +} + +impl std::fmt::Debug for PaintCallback { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CustomShape") + .field("rect", &self.rect) + .finish_non_exhaustive() + } +} + +impl std::cmp::PartialEq for PaintCallback { + fn eq(&self, other: &Self) -> bool { + // As I understand it, the problem this clippy tried to protect against can only happen + // if we do dynamic casts back and forth on the pointers, and we don't do that. + #[allow(clippy::vtable_address_comparisons)] + { + self.rect.eq(&other.rect) && std::sync::Arc::ptr_eq(&self.callback, &other.callback) + } + } +} + +impl From for Shape { + #[inline(always)] + fn from(shape: PaintCallback) -> Self { + Self::Callback(shape) + } +} diff --git a/epaint/src/shape_transform.rs b/epaint/src/shape_transform.rs index c033a8ee4b2..a4c060db74e 100644 --- a/epaint/src/shape_transform.rs +++ b/epaint/src/shape_transform.rs @@ -51,5 +51,8 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { adjust_color(&mut bezier.fill); adjust_color(&mut bezier.stroke.color); } + Shape::Callback(_) => { + // Can't tint user callback code + } } } diff --git a/epaint/src/stats.rs b/epaint/src/stats.rs index 322c0ddf13e..2316d905a32 100644 --- a/epaint/src/stats.rs +++ b/epaint/src/stats.rs @@ -162,12 +162,13 @@ pub struct PaintStats { pub shape_path: AllocInfo, pub shape_mesh: AllocInfo, pub shape_vec: AllocInfo, + pub num_callbacks: usize, pub text_shape_vertices: AllocInfo, pub text_shape_indices: AllocInfo, /// Number of separate clip rectangles - pub clipped_meshes: AllocInfo, + pub clipped_primitives: AllocInfo, pub vertices: AllocInfo, pub indices: AllocInfo, } @@ -215,27 +216,25 @@ impl PaintStats { Shape::Mesh(mesh) => { self.shape_mesh += AllocInfo::from_mesh(mesh); } + Shape::Callback(_) => { + self.num_callbacks += 1; + } } } - pub fn with_clipped_meshes(mut self, clipped_meshes: &[crate::ClippedMesh]) -> Self { - self.clipped_meshes += AllocInfo::from_slice(clipped_meshes); - for ClippedMesh(_, indices) in clipped_meshes { - self.vertices += AllocInfo::from_slice(&indices.vertices); - self.indices += AllocInfo::from_slice(&indices.indices); + pub fn with_clipped_primitives( + mut self, + clipped_primitives: &[crate::ClippedPrimitive], + ) -> Self { + self.clipped_primitives += AllocInfo::from_slice(clipped_primitives); + for clipped_primitive in clipped_primitives { + if let Primitive::Mesh(mesh) = &clipped_primitive.primitive { + self.vertices += AllocInfo::from_slice(&mesh.vertices); + self.indices += AllocInfo::from_slice(&mesh.indices); + } } self } - - // pub fn total(&self) -> AllocInfo { - // self.shapes - // + self.shape_text - // + self.shape_path - // + self.shape_mesh - // + self.clipped_meshes - // + self.vertices - // + self.indices - // } } fn megabytes(size: usize) -> String { diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index cc1c75fc190..f46af30dc69 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -781,6 +781,9 @@ impl Tessellator { self.tessellate_quadratic_bezier(quadratic_shape, out); } Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(cubic_shape, out), + Shape::Callback(_) => { + panic!("Shape::Callback passed to Tessellator"); + } } } @@ -1046,58 +1049,97 @@ pub fn tessellate_shapes( shapes: Vec, options: TessellationOptions, tex_size: [usize; 2], -) -> Vec { +) -> Vec { let mut tessellator = Tessellator::from_options(options); - let mut clipped_meshes: Vec = Vec::default(); + let mut clipped_primitives: Vec = Vec::default(); - for ClippedShape(clip_rect, shape) in shapes { - if !clip_rect.is_positive() { + for ClippedShape(new_clip_rect, new_shape) in shapes { + if !new_clip_rect.is_positive() { continue; // skip empty clip rectangles } - let start_new_mesh = match clipped_meshes.last() { - None => true, - Some(cm) => cm.0 != clip_rect || cm.1.texture_id != shape.texture_id(), - }; + if let Shape::Callback(callback) = new_shape { + clipped_primitives.push(ClippedPrimitive { + clip_rect: new_clip_rect, + primitive: Primitive::Callback(callback), + }); + } else { + let start_new_mesh = match clipped_primitives.last() { + None => true, + Some(output_clipped_primitive) => { + output_clipped_primitive.clip_rect != new_clip_rect + || if let Primitive::Mesh(output_mesh) = &output_clipped_primitive.primitive + { + output_mesh.texture_id != new_shape.texture_id() + } else { + true + } + } + }; - if start_new_mesh { - clipped_meshes.push(ClippedMesh(clip_rect, Mesh::default())); - } + if start_new_mesh { + clipped_primitives.push(ClippedPrimitive { + clip_rect: new_clip_rect, + primitive: Primitive::Mesh(Mesh::default()), + }); + } - let out = &mut clipped_meshes.last_mut().unwrap().1; - tessellator.clip_rect = clip_rect; - tessellator.tessellate_shape(tex_size, shape, out); - } + let out = clipped_primitives.last_mut().unwrap(); - if options.debug_paint_clip_rects { - for ClippedMesh(clip_rect, mesh) in &mut clipped_meshes { - if mesh.texture_id == TextureId::default() { - tessellator.clip_rect = Rect::EVERYTHING; - tessellator.tessellate_shape( - tex_size, - Shape::rect_stroke( - *clip_rect, - 0.0, - Stroke::new(2.0, Color32::from_rgb(150, 255, 150)), - ), - mesh, - ); + if let Primitive::Mesh(out_mesh) = &mut out.primitive { + tessellator.clip_rect = new_clip_rect; + tessellator.tessellate_shape(tex_size, new_shape, out_mesh); } else { - // TODO: create a new `ClippedMesh` just for the painted clip rectangle + unreachable!(); } } } + if options.debug_paint_clip_rects { + clipped_primitives = add_clip_rects(&mut tessellator, tex_size, clipped_primitives); + } + if options.debug_ignore_clip_rects { - for ClippedMesh(clip_rect, _) in &mut clipped_meshes { - *clip_rect = Rect::EVERYTHING; + for clipped_primitive in &mut clipped_primitives { + clipped_primitive.clip_rect = Rect::EVERYTHING; } } - for ClippedMesh(_, mesh) in &clipped_meshes { - crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); + for clipped_primitive in &clipped_primitives { + if let Primitive::Mesh(mesh) = &clipped_primitive.primitive { + crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); + } } - clipped_meshes + clipped_primitives +} + +fn add_clip_rects( + tessellator: &mut Tessellator, + tex_size: [usize; 2], + clipped_primitives: Vec, +) -> Vec { + tessellator.clip_rect = Rect::EVERYTHING; + let stroke = Stroke::new(2.0, Color32::from_rgb(150, 255, 150)); + + clipped_primitives + .into_iter() + .flat_map(|clipped_primitive| { + let mut clip_rect_mesh = Mesh::default(); + tessellator.tessellate_shape( + tex_size, + Shape::rect_stroke(clipped_primitive.clip_rect, 0.0, stroke), + &mut clip_rect_mesh, + ); + + [ + clipped_primitive, + ClippedPrimitive { + clip_rect: Rect::EVERYTHING, // whatever + primitive: Primitive::Mesh(clip_rect_mesh), + }, + ] + }) + .collect() } diff --git a/epi/src/lib.rs b/epi/src/lib.rs index ecda8514c93..88171aaac84 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -361,6 +361,13 @@ impl Frame { } } +#[cfg(test)] +#[test] +fn frame_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} + /// Information about the web environment (if applicable). #[derive(Clone, Debug)] pub struct WebInfo {