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.
* `DragValue::range` is now called `clamp_range` and also clamps incoming values.
* Renamed `Triangles` to `Mesh`.
* The tesselator now wraps the clip rectangle and mesh in `struct ClippedMesh(Rect, Mesh)`.
### 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
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
let mut egui_ctx = egui::Context::new();
@ -179,8 +179,8 @@ loop {
egui_ctx.begin_frame(raw_input);
my_app.ui(&mut egui_ctx); // add panels, windows and widgets to `egui_ctx` here
let (output, shapes) = egui_ctx.end_frame();
let paint_jobs = egui_ctx.tessellate(shapes); // create triangles to paint
my_integration.paint(paint_jobs);
let clipped_meshes = egui_ctx.tessellate(shapes); // create triangles to paint
my_integration.paint(clipped_meshes);
my_integration.set_cursor_icon(output.cursor_icon);
// Also see `egui::Output` for more
}

View File

@ -583,13 +583,14 @@ impl Context {
}
/// 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;
tessellation_options.aa_size = 1.0 / self.pixels_per_point();
let paint_stats = PaintStats::from_shapes(&shapes); // TODO: internal allocations
let paint_jobs = tessellator::tessellate_shapes(shapes, tessellation_options, self.fonts());
*self.paint_stats.lock() = paint_stats.with_paint_jobs(&paint_jobs);
paint_jobs
let clipped_meshes =
tessellator::tessellate_shapes(shapes, tessellation_options, self.fonts());
*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_mesh,
shape_vec,
jobs,
clipped_meshes,
vertices,
indices,
} = self;
@ -97,12 +97,13 @@ impl Widget for &epaint::stats::PaintStats {
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
label(ui, shape_text, "text");
label(ui, shape_path, "paths");
label(ui, shape_mesh, "meshes");
label(ui, shape_vec, "nested");
label(ui, shape_mesh, "nested meshes");
label(ui, shape_vec, "nested shapes");
ui.advance_cursor(10.0);
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, indices, "indices").on_hover_text("Three 32-bit indices per triangles");
ui.advance_cursor(10.0);

View File

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

View File

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

View File

@ -212,7 +212,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
.build();
app.update(&ctx, &mut 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;
previous_frame_time = Some(frame_time);
@ -220,7 +220,7 @@ pub fn run(mut app: Box<dyn epi::App>) -> ! {
&display,
ctx.pixels_per_point(),
app.clear_color(),
paint_jobs,
clipped_meshes,
&ctx.texture(),
);

View File

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

View File

@ -42,30 +42,30 @@ impl WebBackend {
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
.frame_start
.take()
.expect("unmatched calls to begin_frame/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();
self.previous_frame_time = Some((now - frame_start) as f32);
Ok((output, paint_jobs))
Ok((output, clipped_meshes))
}
pub fn paint(
&mut self,
clear_color: egui::Rgba,
paint_jobs: egui::PaintJobs,
clipped_meshes: Vec<egui::ClippedMesh>,
) -> Result<(), JsValue> {
self.painter.upload_egui_texture(&self.ctx.texture());
self.painter.clear(clear_color);
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 {
@ -190,7 +190,7 @@ impl AppRunner {
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());
let canvas_size = canvas_size_in_points(self.web_backend.canvas_id());
let raw_input = self.input.new_frame(canvas_size);
@ -216,7 +216,7 @@ impl AppRunner {
let egui_ctx = &self.web_backend.ctx;
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);
{
@ -227,11 +227,12 @@ impl AppRunner {
} = app_output;
}
Ok((egui_output, paint_jobs))
Ok((egui_output, clipped_meshes))
}
pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> {
self.web_backend.paint(self.app.clear_color(), paint_jobs)
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
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> {
let mut runner_lock = runner_ref.0.lock();
if runner_lock.needs_repaint.fetch_and_clear() {
let (output, paint_jobs) = runner_lock.logic()?;
runner_lock.paint(paint_jobs)?;
let (output, clipped_meshes) = runner_lock.logic()?;
runner_lock.paint(clipped_meshes)?;
if output.needs_repaint {
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 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)
-> Result<(), JsValue>;
fn paint_meshes(
&mut self,
clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32,
) -> Result<(), JsValue>;
}

View File

@ -6,7 +6,7 @@ use {
use egui::{
math::clamp,
paint::{Color32, Mesh, PaintJobs, Texture},
paint::{Color32, Mesh, Texture},
vec2,
};
@ -478,7 +478,11 @@ impl crate::Painter for WebGlPainter {
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();
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();
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) {
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));

View File

@ -8,7 +8,7 @@ use {
use egui::{
math::clamp,
paint::{Color32, Mesh, PaintJobs, Texture},
paint::{Color32, Mesh, Texture},
vec2,
};
@ -467,7 +467,11 @@ impl crate::Painter for WebGl2Painter {
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();
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();
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) {
gl.bind_texture(Gl::TEXTURE_2D, Some(gl_texture));

View File

@ -114,8 +114,14 @@ pub struct ClippedShape(
pub Shape,
);
/// A clip triangle and some textured triangles, all in points (logical pixels).
pub type PaintJob = (emath::Rect, Mesh);
/// Grouped by clip rectangles, in points (logical pixels).
pub type PaintJobs = Vec<PaintJob>;
/// A [`Mesh`] within a clip rectangle.
///
/// Everything is using logical points.
#[derive(Clone, Debug)]
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 {
if self.num_allocs() == 0 {
format!("{:6} {:12}", 0, what)
format!("{:6} {:14}", 0, what)
} else if self.num_allocs() == 1 {
format!(
"{:6} {:12} {} 1 allocation",
"{:6} {:14} {} 1 allocation",
self.num_elements,
what,
self.megabytes()
)
} else if self.element_size != ElementSize::Heterogenous {
format!(
"{:6} {:12} {} {:3} allocations",
"{:6} {:14} {} {:3} allocations",
self.num_elements(),
what,
self.megabytes(),
@ -125,7 +125,7 @@ impl AllocInfo {
)
} else {
format!(
"{:6} {:12} {} {:3} allocations",
"{:6} {:14} {} {:3} allocations",
"",
what,
self.megabytes(),
@ -145,7 +145,7 @@ pub struct PaintStats {
pub shape_vec: AllocInfo,
/// Number of separate clip rectangles
pub jobs: AllocInfo,
pub clipped_meshes: AllocInfo,
pub vertices: AllocInfo,
pub indices: AllocInfo,
}
@ -188,9 +188,9 @@ impl PaintStats {
}
}
pub fn with_paint_jobs(mut self, paint_jobs: &[crate::PaintJob]) -> Self {
self.jobs += AllocInfo::from_slice(paint_jobs);
for (_, indices) in paint_jobs {
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);
}
@ -202,7 +202,7 @@ impl PaintStats {
// + self.shape_text
// + self.shape_path
// + self.shape_mesh
// + self.jobs
// + self.clipped_meshes
// + self.vertices
// + self.indices
// }

View File

@ -668,27 +668,28 @@ pub fn tessellate_shapes(
shapes: Vec<ClippedShape>,
options: TessellationOptions,
fonts: &Fonts,
) -> Vec<(Rect, Mesh)> {
) -> Vec<ClippedMesh> {
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 {
let start_new_job = match jobs.last() {
let start_new_mesh = match clipped_meshes.last() {
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 {
jobs.push((clip_rect, Mesh::default()));
if start_new_mesh {
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.tessellate_shape(fonts, shape, out);
}
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.tessellate_shape(
fonts,
@ -704,14 +705,14 @@ pub fn tessellate_shapes(
}
if options.debug_ignore_clip_rects {
for (clip_rect, _) in &mut jobs {
for ClippedMesh(clip_rect, _) in &mut clipped_meshes {
*clip_rect = Rect::everything();
}
}
for (_, mesh) in &jobs {
for ClippedMesh(_, mesh) in &clipped_meshes {
debug_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh");
}
jobs
clipped_meshes
}