Wrap tesselated output in struct ClippedMesh(Rect, Mesh)

This commit is contained in:
Emil Ernerfeldt 2021-01-25 21:43:17 +01:00
parent 75fa77e040
commit b493bc6efc
16 changed files with 89 additions and 71 deletions

View File

@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Backend: pointer (mouse/touch) position and buttons are now passed to egui in the event stream. * Backend: pointer (mouse/touch) position and buttons are now passed to egui in the event stream.
* `DragValue::range` is now called `clamp_range` and also clamps incoming values. * `DragValue::range` is now called `clamp_range` and also clamps incoming values.
* Renamed `Triangles` to `Mesh`. * Renamed `Triangles` to `Mesh`.
* The tesselator now wraps the clip rectangle and mesh in `struct ClippedMesh(Rect, Mesh)`.
### Fixed 🐛 ### Fixed 🐛

View File

@ -168,7 +168,7 @@ The same code can be compiled to a native app or a web app.
### Writing your own egui integration ### Writing your own egui integration
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html), paint [`egui::PaintJobs`](https://docs.rs/egui/latest/egui/paint/tessellator/type.PaintJobs.html) and handle [`egui::Output`](https://docs.rs/egui/latest/egui/struct.Output.html). The basic structure is this: You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html), paint [`egui::ClippedMesh`](https://docs.rs/epaint/):es and handle [`egui::Output`](https://docs.rs/egui/latest/egui/struct.Output.html). The basic structure is this:
``` rust ``` rust
let mut egui_ctx = egui::Context::new(); let mut egui_ctx = egui::Context::new();
@ -179,8 +179,8 @@ loop {
egui_ctx.begin_frame(raw_input); egui_ctx.begin_frame(raw_input);
my_app.ui(&mut egui_ctx); // add panels, windows and widgets to `egui_ctx` here my_app.ui(&mut egui_ctx); // add panels, windows and widgets to `egui_ctx` here
let (output, shapes) = egui_ctx.end_frame(); let (output, shapes) = egui_ctx.end_frame();
let paint_jobs = egui_ctx.tessellate(shapes); // create triangles to paint let clipped_meshes = egui_ctx.tessellate(shapes); // create triangles to paint
my_integration.paint(paint_jobs); my_integration.paint(clipped_meshes);
my_integration.set_cursor_icon(output.cursor_icon); my_integration.set_cursor_icon(output.cursor_icon);
// Also see `egui::Output` for more // Also see `egui::Output` for more
} }

View File

@ -583,13 +583,14 @@ impl Context {
} }
/// Tessellate the given shapes into triangle meshes. /// Tessellate the given shapes into triangle meshes.
pub fn tessellate(&self, shapes: Vec<ClippedShape>) -> PaintJobs { pub fn tessellate(&self, shapes: Vec<ClippedShape>) -> Vec<ClippedMesh> {
let mut tessellation_options = self.memory().options.tessellation_options; let mut tessellation_options = self.memory().options.tessellation_options;
tessellation_options.aa_size = 1.0 / self.pixels_per_point(); tessellation_options.aa_size = 1.0 / self.pixels_per_point();
let paint_stats = PaintStats::from_shapes(&shapes); // TODO: internal allocations let paint_stats = PaintStats::from_shapes(&shapes); // TODO: internal allocations
let paint_jobs = tessellator::tessellate_shapes(shapes, tessellation_options, self.fonts()); let clipped_meshes =
*self.paint_stats.lock() = paint_stats.with_paint_jobs(&paint_jobs); tessellator::tessellate_shapes(shapes, tessellation_options, self.fonts());
paint_jobs *self.paint_stats.lock() = paint_stats.with_clipped_meshes(&clipped_meshes);
clipped_meshes
} }
// --------------------------------------------------------------------- // ---------------------------------------------------------------------

View File

@ -88,7 +88,7 @@ impl Widget for &epaint::stats::PaintStats {
shape_path, shape_path,
shape_mesh, shape_mesh,
shape_vec, shape_vec,
jobs, clipped_meshes,
vertices, vertices,
indices, indices,
} = self; } = self;
@ -97,12 +97,13 @@ impl Widget for &epaint::stats::PaintStats {
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc"); label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
label(ui, shape_text, "text"); label(ui, shape_text, "text");
label(ui, shape_path, "paths"); label(ui, shape_path, "paths");
label(ui, shape_mesh, "meshes"); label(ui, shape_mesh, "nested meshes");
label(ui, shape_vec, "nested"); label(ui, shape_vec, "nested shapes");
ui.advance_cursor(10.0); ui.advance_cursor(10.0);
ui.label("Tessellated:"); ui.label("Tessellated:");
label(ui, jobs, "jobs").on_hover_text("Number of separate clip rectangles"); label(ui, clipped_meshes, "clipped_meshes")
.on_hover_text("Number of separate clip rectangles");
label(ui, vertices, "vertices"); label(ui, vertices, "vertices");
label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles"); label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles");
ui.advance_cursor(10.0); ui.advance_cursor(10.0);

View File

@ -27,8 +27,8 @@
//! egui_ctx.begin_frame(raw_input); //! egui_ctx.begin_frame(raw_input);
//! my_app.ui(&egui_ctx); // add panels, windows and widgets to `egui_ctx` here //! my_app.ui(&egui_ctx); // add panels, windows and widgets to `egui_ctx` here
//! let (output, shapes) = egui_ctx.end_frame(); //! let (output, shapes) = egui_ctx.end_frame();
//! let paint_jobs = egui_ctx.tessellate(shapes); // create triangles to paint //! let clipped_meshes = egui_ctx.tessellate(shapes); // create triangles to paint
//! my_integration.paint(paint_jobs); //! my_integration.paint(clipped_meshes);
//! my_integration.set_cursor_icon(output.cursor_icon); //! my_integration.set_cursor_icon(output.cursor_icon);
//! // Also see `egui::Output` for more //! // Also see `egui::Output` for more
//! } //! }
@ -109,7 +109,7 @@ pub use emath::{
pub use epaint::{ pub use epaint::{
color, mutex, color, mutex,
text::{FontDefinitions, FontFamily, TextStyle}, text::{FontDefinitions, FontFamily, TextStyle},
Color32, PaintJobs, Rgba, Shape, Stroke, Texture, TextureId, ClippedMesh, Color32, Rgba, Shape, Stroke, Texture, TextureId,
}; };
pub use { pub use {

View File

@ -100,7 +100,7 @@ fn test_egui_e2e() {
ctx.begin_frame(raw_input.clone()); ctx.begin_frame(raw_input.clone());
demo_windows.ui(&ctx); demo_windows.ui(&ctx);
let (_output, shapes) = ctx.end_frame(); let (_output, shapes) = ctx.end_frame();
let paint_jobs = ctx.tessellate(shapes); let clipped_meshes = ctx.tessellate(shapes);
assert!(!paint_jobs.is_empty()); assert!(!clipped_meshes.is_empty());
} }
} }

View File

@ -212,7 +212,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
.build(); .build();
app.update(&ctx, &mut frame); app.update(&ctx, &mut frame);
let (egui_output, shapes) = ctx.end_frame(); let (egui_output, shapes) = ctx.end_frame();
let paint_jobs = ctx.tessellate(shapes); let clipped_meshes = ctx.tessellate(shapes);
let frame_time = (Instant::now() - frame_start).as_secs_f64() as f32; let frame_time = (Instant::now() - frame_start).as_secs_f64() as f32;
previous_frame_time = Some(frame_time); previous_frame_time = Some(frame_time);
@ -220,7 +220,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
&display, &display,
ctx.pixels_per_point(), ctx.pixels_per_point(),
app.clear_color(), app.clear_color(),
paint_jobs, clipped_meshes,
&ctx.texture(), &ctx.texture(),
); );

View File

@ -1,11 +1,7 @@
#![allow(deprecated)] // legacy implement_vertex macro #![allow(deprecated)] // legacy implement_vertex macro
use { use {
egui::{ egui::{math::clamp, paint::Mesh, Color32, Rect},
math::clamp,
paint::{Mesh, PaintJobs},
Color32, Rect,
},
glium::{ glium::{
implement_vertex, implement_vertex,
index::PrimitiveType, index::PrimitiveType,
@ -128,7 +124,7 @@ impl Painter {
display: &glium::Display, display: &glium::Display,
pixels_per_point: f32, pixels_per_point: f32,
clear_color: egui::Rgba, clear_color: egui::Rgba,
jobs: PaintJobs, cipped_meshes: Vec<egui::ClippedMesh>,
egui_texture: &egui::Texture, egui_texture: &egui::Texture,
) { ) {
self.upload_egui_texture(display, egui_texture); self.upload_egui_texture(display, egui_texture);
@ -142,7 +138,7 @@ impl Painter {
clear_color[2], clear_color[2],
clear_color[3], clear_color[3],
); );
for (clip_rect, mesh) in jobs { for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
self.paint_mesh(&mut target, display, pixels_per_point, clip_rect, &mesh) self.paint_mesh(&mut target, display, pixels_per_point, clip_rect, &mesh)
} }
target.finish().unwrap(); target.finish().unwrap();

View File

@ -42,30 +42,30 @@ impl WebBackend {
self.ctx.begin_frame(raw_input) self.ctx.begin_frame(raw_input)
} }
pub fn end_frame(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> { pub fn end_frame(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
let frame_start = self let frame_start = self
.frame_start .frame_start
.take() .take()
.expect("unmatched calls to begin_frame/end_frame"); .expect("unmatched calls to begin_frame/end_frame");
let (output, shapes) = self.ctx.end_frame(); let (output, shapes) = self.ctx.end_frame();
let paint_jobs = self.ctx.tessellate(shapes); let clipped_meshes = self.ctx.tessellate(shapes);
let now = now_sec(); let now = now_sec();
self.previous_frame_time = Some((now - frame_start) as f32); self.previous_frame_time = Some((now - frame_start) as f32);
Ok((output, paint_jobs)) Ok((output, clipped_meshes))
} }
pub fn paint( pub fn paint(
&mut self, &mut self,
clear_color: egui::Rgba, clear_color: egui::Rgba,
paint_jobs: egui::PaintJobs, clipped_meshes: Vec<egui::ClippedMesh>,
) -> Result<(), JsValue> { ) -> Result<(), JsValue> {
self.painter.upload_egui_texture(&self.ctx.texture()); self.painter.upload_egui_texture(&self.ctx.texture());
self.painter.clear(clear_color); self.painter.clear(clear_color);
self.painter self.painter
.paint_meshes(paint_jobs, self.ctx.pixels_per_point()) .paint_meshes(clipped_meshes, self.ctx.pixels_per_point())
} }
pub fn painter_debug_info(&self) -> String { pub fn painter_debug_info(&self) -> String {
@ -190,7 +190,7 @@ impl AppRunner {
Ok(()) Ok(())
} }
pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> { pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
resize_canvas_to_screen_size(self.web_backend.canvas_id()); resize_canvas_to_screen_size(self.web_backend.canvas_id());
let canvas_size = canvas_size_in_points(self.web_backend.canvas_id()); let canvas_size = canvas_size_in_points(self.web_backend.canvas_id());
let raw_input = self.input.new_frame(canvas_size); let raw_input = self.input.new_frame(canvas_size);
@ -216,7 +216,7 @@ impl AppRunner {
let egui_ctx = &self.web_backend.ctx; let egui_ctx = &self.web_backend.ctx;
self.app.update(egui_ctx, &mut frame); self.app.update(egui_ctx, &mut frame);
let (egui_output, paint_jobs) = self.web_backend.end_frame()?; let (egui_output, clipped_meshes) = self.web_backend.end_frame()?;
handle_output(&egui_output); handle_output(&egui_output);
{ {
@ -227,11 +227,12 @@ impl AppRunner {
} = app_output; } = app_output;
} }
Ok((egui_output, paint_jobs)) Ok((egui_output, clipped_meshes))
} }
pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> { pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
self.web_backend.paint(self.app.clear_color(), paint_jobs) self.web_backend
.paint(self.app.clear_color(), clipped_meshes)
} }
} }

View File

@ -405,8 +405,8 @@ fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
if runner_lock.needs_repaint.fetch_and_clear() { if runner_lock.needs_repaint.fetch_and_clear() {
let (output, paint_jobs) = runner_lock.logic()?; let (output, clipped_meshes) = runner_lock.logic()?;
runner_lock.paint(paint_jobs)?; runner_lock.paint(clipped_meshes)?;
if output.needs_repaint { if output.needs_repaint {
runner_lock.needs_repaint.set_true(); runner_lock.needs_repaint.set_true();
} }

View File

@ -10,8 +10,11 @@ pub trait Painter {
fn upload_egui_texture(&mut self, texture: &egui::Texture); fn upload_egui_texture(&mut self, texture: &egui::Texture);
fn clear(&mut self, lear_color: egui::Rgba); fn clear(&mut self, clear_color: egui::Rgba);
fn paint_meshes(&mut self, jobs: egui::PaintJobs, pixels_per_point: f32) fn paint_meshes(
-> Result<(), JsValue>; &mut self,
clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue>;
} }

View File

@ -6,7 +6,7 @@ use {
use egui::{ use egui::{
math::clamp, math::clamp,
paint::{Color32, Mesh, PaintJobs, Texture}, paint::{Color32, Mesh, Texture},
vec2, vec2,
}; };
@ -478,7 +478,11 @@ impl crate::Painter for WebGlPainter {
gl.clear(Gl::COLOR_BUFFER_BIT); gl.clear(Gl::COLOR_BUFFER_BIT);
} }
fn paint_meshes(&mut self, jobs: PaintJobs, pixels_per_point: f32) -> Result<(), JsValue> { fn paint_meshes(
&mut self,
clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue> {
self.upload_user_textures(); self.upload_user_textures();
let gl = &self.gl; let gl = &self.gl;
@ -504,7 +508,7 @@ impl crate::Painter for WebGlPainter {
let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap();
gl.uniform1i(Some(&u_sampler_loc), 0); gl.uniform1i(Some(&u_sampler_loc), 0);
for (clip_rect, mesh) in jobs { for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes {
if let Some(gl_texture) = self.get_texture(mesh.texture_id) { if let Some(gl_texture) = self.get_texture(mesh.texture_id) {
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));

View File

@ -8,7 +8,7 @@ use {
use egui::{ use egui::{
math::clamp, math::clamp,
paint::{Color32, Mesh, PaintJobs, Texture}, paint::{Color32, Mesh, Texture},
vec2, vec2,
}; };
@ -467,7 +467,11 @@ impl crate::Painter for WebGl2Painter {
gl.clear(Gl::COLOR_BUFFER_BIT); gl.clear(Gl::COLOR_BUFFER_BIT);
} }
fn paint_meshes(&mut self, jobs: PaintJobs, pixels_per_point: f32) -> Result<(), JsValue> { fn paint_meshes(
&mut self,
clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue> {
self.upload_user_textures(); self.upload_user_textures();
let gl = &self.gl; let gl = &self.gl;
@ -493,7 +497,7 @@ impl crate::Painter for WebGl2Painter {
let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap(); let u_sampler_loc = gl.get_uniform_location(&self.program, "u_sampler").unwrap();
gl.uniform1i(Some(&u_sampler_loc), 0); gl.uniform1i(Some(&u_sampler_loc), 0);
for (clip_rect, mesh) in jobs { for egui::ClippedMesh(clip_rect, mesh) in clipped_meshes {
if let Some(gl_texture) = self.get_texture(mesh.texture_id) { if let Some(gl_texture) = self.get_texture(mesh.texture_id) {
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture)); gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));

View File

@ -114,8 +114,14 @@ pub struct ClippedShape(
pub Shape, pub Shape,
); );
/// A clip triangle and some textured triangles, all in points (logical pixels). /// A [`Mesh`] within a clip rectangle.
pub type PaintJob = (emath::Rect, Mesh); ///
/// Everything is using logical points.
/// Grouped by clip rectangles, in points (logical pixels). #[derive(Clone, Debug)]
pub type PaintJobs = Vec<PaintJob>; pub struct ClippedMesh(
/// Clip / scissor rectangle.
/// Only show the part of the [`Mesh`] that falls within this.
pub emath::Rect,
/// The shape
pub Mesh,
);

View File

@ -107,17 +107,17 @@ impl AllocInfo {
pub fn format(&self, what: &str) -> String { pub fn format(&self, what: &str) -> String {
if self.num_allocs() == 0 { if self.num_allocs() == 0 {
format!("{:6} {:12}", 0, what) format!("{:6} {:14}", 0, what)
} else if self.num_allocs() == 1 { } else if self.num_allocs() == 1 {
format!( format!(
"{:6} {:12} {} 1 allocation", "{:6} {:14} {} 1 allocation",
self.num_elements, self.num_elements,
what, what,
self.megabytes() self.megabytes()
) )
} else if self.element_size != ElementSize::Heterogenous { } else if self.element_size != ElementSize::Heterogenous {
format!( format!(
"{:6} {:12} {} {:3} allocations", "{:6} {:14} {} {:3} allocations",
self.num_elements(), self.num_elements(),
what, what,
self.megabytes(), self.megabytes(),
@ -125,7 +125,7 @@ impl AllocInfo {
) )
} else { } else {
format!( format!(
"{:6} {:12} {} {:3} allocations", "{:6} {:14} {} {:3} allocations",
"", "",
what, what,
self.megabytes(), self.megabytes(),
@ -145,7 +145,7 @@ pub struct PaintStats {
pub shape_vec: AllocInfo, pub shape_vec: AllocInfo,
/// Number of separate clip rectangles /// Number of separate clip rectangles
pub jobs: AllocInfo, pub clipped_meshes: AllocInfo,
pub vertices: AllocInfo, pub vertices: AllocInfo,
pub indices: AllocInfo, pub indices: AllocInfo,
} }
@ -188,9 +188,9 @@ impl PaintStats {
} }
} }
pub fn with_paint_jobs(mut self, paint_jobs: &[crate::PaintJob]) -> Self { pub fn with_clipped_meshes(mut self, clipped_meshes: &[crate::ClippedMesh]) -> Self {
self.jobs += AllocInfo::from_slice(paint_jobs); self.clipped_meshes += AllocInfo::from_slice(clipped_meshes);
for (_, indices) in paint_jobs { for ClippedMesh(_, indices) in clipped_meshes {
self.vertices += AllocInfo::from_slice(&indices.vertices); self.vertices += AllocInfo::from_slice(&indices.vertices);
self.indices += AllocInfo::from_slice(&indices.indices); self.indices += AllocInfo::from_slice(&indices.indices);
} }
@ -202,7 +202,7 @@ impl PaintStats {
// + self.shape_text // + self.shape_text
// + self.shape_path // + self.shape_path
// + self.shape_mesh // + self.shape_mesh
// + self.jobs // + self.clipped_meshes
// + self.vertices // + self.vertices
// + self.indices // + self.indices
// } // }

View File

@ -668,27 +668,28 @@ pub fn tessellate_shapes(
shapes: Vec<ClippedShape>, shapes: Vec<ClippedShape>,
options: TessellationOptions, options: TessellationOptions,
fonts: &Fonts, fonts: &Fonts,
) -> Vec<(Rect, Mesh)> { ) -> Vec<ClippedMesh> {
let mut tessellator = Tessellator::from_options(options); let mut tessellator = Tessellator::from_options(options);
let mut jobs = PaintJobs::default(); let mut clipped_meshes: Vec<ClippedMesh> = Vec::default();
for ClippedShape(clip_rect, shape) in shapes { for ClippedShape(clip_rect, shape) in shapes {
let start_new_job = match jobs.last() { let start_new_mesh = match clipped_meshes.last() {
None => true, None => true,
Some(job) => job.0 != clip_rect || job.1.texture_id != shape.texture_id(), Some(cm) => cm.0 != clip_rect || cm.1.texture_id != shape.texture_id(),
}; };
if start_new_job { if start_new_mesh {
jobs.push((clip_rect, Mesh::default())); clipped_meshes.push(ClippedMesh(clip_rect, Mesh::default()));
} }
let out = &mut jobs.last_mut().unwrap().1; let out = &mut clipped_meshes.last_mut().unwrap().1;
tessellator.clip_rect = clip_rect; tessellator.clip_rect = clip_rect;
tessellator.tessellate_shape(fonts, shape, out); tessellator.tessellate_shape(fonts, shape, out);
} }
if options.debug_paint_clip_rects { if options.debug_paint_clip_rects {
for (clip_rect, mesh) in &mut jobs { for ClippedMesh(clip_rect, mesh) in &mut clipped_meshes {
tessellator.clip_rect = Rect::everything(); tessellator.clip_rect = Rect::everything();
tessellator.tessellate_shape( tessellator.tessellate_shape(
fonts, fonts,
@ -704,14 +705,14 @@ pub fn tessellate_shapes(
} }
if options.debug_ignore_clip_rects { if options.debug_ignore_clip_rects {
for (clip_rect, _) in &mut jobs { for ClippedMesh(clip_rect, _) in &mut clipped_meshes {
*clip_rect = Rect::everything(); *clip_rect = Rect::everything();
} }
} }
for (_, mesh) in &jobs { for ClippedMesh(_, mesh) in &clipped_meshes {
debug_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); debug_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
} }
jobs clipped_meshes
} }