From 2ae9158285a22c023ebd0f88bbadd8b0832079ea Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Tue, 16 Dec 2025 11:38:28 +0100 Subject: Performance improvements part 1 --- src/renderer.rs | 158 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 77 deletions(-) (limited to 'src/renderer.rs') diff --git a/src/renderer.rs b/src/renderer.rs index 8ac025b..74f4708 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -5,7 +5,7 @@ use crate::configuration::Configuration; use crate::stage_type::StageType; use crate::utils::{percent_to_pixel, set_color}; use crate::RenderError; -use cairo::{Context, Error, FontSlant, FontWeight, Format, ImageSurface, LineCap, LineJoin, Operator, SvgSurface}; +use cairo::{Context, Error, FontSlant, FontWeight, Format, ImageSurface, LineCap, LineJoin, SvgSurface}; /// Renders a map to PNG format pub fn render_to_png( @@ -86,10 +86,22 @@ fn render( stage_type: StageType, config: &Configuration, ) -> Result<(), RenderError> { + use std::collections::HashMap; + // Apply padding transform ctx.save()?; ctx.translate(config.theme.sizes.padding, config.theme.sizes.padding); + // Build component map once and reuse it + let component_map: HashMap = map.components + .iter() + .map(|component| { + let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width); + let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height); + (component.label.to_lowercase(), (x, y)) + }) + .collect(); + // Render in order (back to front) draw_background(ctx, config)?; @@ -99,12 +111,12 @@ fn render( draw_axes(ctx, stage_type, map, config)?; draw_stage_dividers(ctx, map, config)?; - draw_dependencies(ctx, map, config)?; - draw_groups(ctx, map, config)?; + draw_dependencies(ctx, map, config, &component_map)?; + draw_groups(ctx, map, config, &component_map)?; draw_vertices(ctx, map, config)?; - draw_vertex_labels(ctx, map, config)?; - draw_inertias(ctx, map, config)?; - draw_evolutions(ctx, map, config)?; + draw_vertex_labels(ctx, map, config, &component_map)?; + draw_inertias(ctx, map, config, &component_map)?; + draw_evolutions(ctx, map, config, &component_map)?; draw_notes(ctx, map, config)?; ctx.restore()?; @@ -312,18 +324,10 @@ fn draw_dependencies( ctx: &Context, map: &Map, config: &Configuration, + component_map: &std::collections::HashMap, ) -> Result<(), RenderError> { - use std::collections::HashMap; use std::f64::consts::PI; - // Build component map for fast lookup - let mut component_map: HashMap = HashMap::new(); - for component in &map.components { - let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width); - let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height); - component_map.insert(component.label.to_lowercase(), (x, y)); - } - ctx.save()?; set_color(ctx, &config.theme.colors.vertex); ctx.set_line_width(config.theme.sizes.line_width); @@ -360,7 +364,6 @@ fn draw_dependencies( // Draw line ctx.move_to(offset_origin_x, offset_origin_y); ctx.line_to(offset_dest_x, offset_dest_y); - ctx.stroke()?; // Draw arrowhead if directed if dependency.is_directed { @@ -377,28 +380,30 @@ fn draw_dependencies( offset_dest_x - arrowhead_size * lower_angle.cos(), offset_dest_y - arrowhead_size * lower_angle.sin(), ); - ctx.stroke()?; } } + // Batch stroke all paths at once + ctx.stroke()?; + ctx.restore()?; Ok(()) } -fn draw_groups(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), RenderError> { +fn draw_groups( + ctx: &Context, + map: &Map, + config: &Configuration, + component_map: &std::collections::HashMap, +) -> Result<(), RenderError> { use geo::{ConcaveHull, LineString, Point}; - use std::collections::HashMap; use std::f64::consts::PI; - // Build component map (top-left corner positions) - let mut component_map: HashMap = HashMap::new(); - for component in &map.components { - let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width); - let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height); - component_map.insert(component.label.to_lowercase(), (x, y)); + if map.groups.is_empty() { + return Ok(()); } - // Create offscreen surface for opacity handling + // Create ONE offscreen surface for ALL groups for opacity handling let width = (config.theme.sizes.map_width + config.theme.sizes.vertex_size) as i32; let height = (config.theme.sizes.map_height + config.theme.sizes.vertex_size) as i32; let offscreen = ImageSurface::create(Format::ARgb32, width, height)?; @@ -454,12 +459,7 @@ fn draw_groups(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), R continue; } - // Clear offscreen canvas - offscreen_ctx.set_operator(Operator::Clear); - offscreen_ctx.paint()?; - offscreen_ctx.set_operator(Operator::Over); - - // Draw path + // Draw path on offscreen context set_color(&offscreen_ctx, color); offscreen_ctx.move_to(coords[0].0, coords[0].1); for &(x, y) in &coords[1..] { @@ -477,14 +477,14 @@ fn draw_groups(ctx: &Context, map: &Map, config: &Configuration) -> Result<(), R offscreen_ctx.set_line_cap(LineCap::Round); offscreen_ctx.set_line_join(LineJoin::Round); offscreen_ctx.stroke()?; - - // Composite onto main context with opacity - ctx.save()?; - ctx.set_source_surface(&offscreen, 0.0, 0.0)?; - ctx.paint_with_alpha(config.theme.opacity.groups)?; - ctx.restore()?; } + // Composite entire offscreen surface onto main context with opacity (once for all groups) + ctx.save()?; + ctx.set_source_surface(&offscreen, 0.0, 0.0)?; + ctx.paint_with_alpha(config.theme.opacity.groups)?; + ctx.restore()?; + Ok(()) } @@ -520,6 +520,7 @@ fn draw_vertex_labels( ctx: &Context, map: &Map, config: &Configuration, + component_map: &std::collections::HashMap, ) -> Result<(), RenderError> { ctx.save()?; set_color(ctx, &config.theme.colors.label); @@ -530,35 +531,55 @@ fn draw_vertex_labels( ); ctx.set_font_size(config.theme.fonts.vertex_label); - for component in &map.components { - let (label_x, label_y) = if config.options.smart_label_positioning { - // Use smart label positioning to find optimal position - use crate::smart_positioning::calculate_optimal_label_position; + if config.options.smart_label_positioning { + // Pre-compute collision data once for all labels + use crate::smart_positioning::{collect_all_lines, collect_note_rects, calculate_optimal_label_position_cached}; + + let all_lines = collect_all_lines( + map, + config.theme.sizes.map_width, + config.theme.sizes.map_height, + config.theme.sizes.vertex_size, + component_map, + ); - let position = calculate_optimal_label_position( + let note_rects = collect_note_rects( + map, + config.theme.sizes.map_width, + config.theme.sizes.map_height, + ctx, + config.theme.fonts.vertex_label, + )?; + + for component in &map.components { + let position = calculate_optimal_label_position_cached( component, - map, config.theme.sizes.map_width, config.theme.sizes.map_height, config.theme.sizes.vertex_size, ctx, config.theme.fonts.vertex_label, + &all_lines, + ¬e_rects, )?; - // Adjust Y position for text baseline - (position.x, position.y + 7.0) - } else { - // Simple label positioning (to the right of vertex) + let label_x = position.x; + let label_y = position.y + 7.0; // Adjust Y position for text baseline + + ctx.move_to(label_x, label_y); + ctx.show_text(&component.label)?; + } + } else { + // Simple label positioning (to the right of vertex) + for component in &map.components { let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width); let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height); - ( - x + config.theme.sizes.vertex_size + 5.0, - y + config.theme.sizes.vertex_size / 2.0 + config.theme.fonts.vertex_label / 3.0, - ) - }; + let label_x = x + config.theme.sizes.vertex_size + 5.0; + let label_y = y + config.theme.sizes.vertex_size / 2.0 + config.theme.fonts.vertex_label / 3.0; - ctx.move_to(label_x, label_y); - ctx.show_text(&component.label)?; + ctx.move_to(label_x, label_y); + ctx.show_text(&component.label)?; + } } ctx.restore()?; @@ -569,17 +590,8 @@ fn draw_inertias( ctx: &Context, map: &Map, config: &Configuration, + component_map: &std::collections::HashMap, ) -> Result<(), RenderError> { - use std::collections::HashMap; - - // Build component map (top-left corner positions) - let mut component_map: HashMap = HashMap::new(); - for component in &map.components { - let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width); - let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height); - component_map.insert(component.label.to_lowercase(), (x, y)); - } - ctx.save()?; set_color(ctx, &config.theme.colors.inertia); @@ -690,18 +702,10 @@ fn draw_evolutions( ctx: &Context, map: &Map, config: &Configuration, + component_map: &std::collections::HashMap, ) -> Result<(), RenderError> { - use std::collections::HashMap; use std::f64::consts::PI; - // Build component map (top-left corner positions) - let mut component_map: HashMap = HashMap::new(); - for component in &map.components { - let x = percent_to_pixel(component.coordinates.0, config.theme.sizes.map_width); - let y = percent_to_pixel(component.coordinates.1, config.theme.sizes.map_height); - component_map.insert(component.label.to_lowercase(), (x, y)); - } - ctx.save()?; set_color(ctx, &config.theme.colors.evolution); ctx.set_line_width(config.theme.sizes.line_width * 2.0); @@ -732,7 +736,6 @@ fn draw_evolutions( // Draw dashed line ctx.move_to(offset_origin_x, offset_origin_y); ctx.line_to(offset_dest_x, offset_dest_y); - ctx.stroke()?; // Draw arrowhead at destination only let upper_angle = -PI / 4.0; @@ -744,17 +747,18 @@ fn draw_evolutions( offset_dest_x - multiplier * arrowhead_size * upper_angle.cos(), offset_dest_y - multiplier * arrowhead_size * upper_angle.sin(), ); - ctx.stroke()?; ctx.move_to(offset_dest_x, offset_dest_y); ctx.line_to( offset_dest_x - multiplier * arrowhead_size * lower_angle.cos(), offset_dest_y - multiplier * arrowhead_size * lower_angle.sin(), ); - ctx.stroke()?; } } + // Batch stroke all paths at once + ctx.stroke()?; + ctx.restore()?; Ok(()) } -- cgit