Implement viewport culling (#667)

* culling is working

* fixed tests

* Ready for review

* cleanup

* code review

* Fix import

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mfish33 2022-06-10 14:38:42 -07:00 committed by Keavon Chambers
parent 6f6c67a508
commit a26a0ddfcf
12 changed files with 47 additions and 27 deletions

View File

@ -1,5 +1,4 @@
use graphene::color::Color; use graphene::color::Color;
use graphene::layers::text_layer::Font;
// Viewport // Viewport
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.; pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.;

View File

@ -87,7 +87,7 @@ impl MessageHandler<ArtboardMessage, &FontCache> for ArtboardMessageHandler {
} else { } else {
responses.push_back( responses.push_back(
FrontendMessage::UpdateDocumentArtboards { FrontendMessage::UpdateDocumentArtboards {
svg: self.artboards_graphene_document.render_root(ViewMode::Normal, font_cache), svg: self.artboards_graphene_document.render_root(ViewMode::Normal, font_cache, None),
} }
.into(), .into(),
); );

View File

@ -873,8 +873,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
} }
#[remain::unsorted] #[remain::unsorted]
Overlays(message) => { Overlays(message) => {
self.overlays_message_handler.process_action(message, (self.overlays_visible, font_cache), responses); self.overlays_message_handler.process_action(message, (self.overlays_visible, font_cache, ipp), responses);
// responses.push_back(OverlaysMessage::RenderOverlays.into());
} }
#[remain::unsorted] #[remain::unsorted]
TransformLayers(message) => { TransformLayers(message) => {
@ -1060,7 +1059,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
false => file_name + file_suffix, false => file_name + file_suffix,
}; };
let rendered = self.graphene_document.render_root(self.view_mode, font_cache); let rendered = self.graphene_document.render_root(self.view_mode, font_cache, None);
let document = format!( let document = format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}" width="{}px" height="{}">{}{}</svg>"#, r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}" width="{}px" height="{}">{}{}</svg>"#,
bbox[0].x, bbox[0].y, size.x, size.y, size.x, size.y, "\n", rendered bbox[0].x, bbox[0].y, size.x, size.y, size.x, size.y, "\n", rendered
@ -1100,7 +1099,6 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
} }
} }
FolderChanged { affected_folder_path } => { FolderChanged { affected_folder_path } => {
let _ = self.graphene_document.render_root(self.view_mode, font_cache);
let affected_layer_path = affected_folder_path; let affected_layer_path = affected_folder_path;
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]); responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
} }
@ -1220,7 +1218,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
RenderDocument => { RenderDocument => {
responses.push_back( responses.push_back(
FrontendMessage::UpdateDocumentArtwork { FrontendMessage::UpdateDocumentArtwork {
svg: self.graphene_document.render_root(self.view_mode, font_cache), svg: self.graphene_document.render_root(self.view_mode, font_cache, Some(ipp.document_bounds())),
} }
.into(), .into(),
); );

View File

@ -27,7 +27,7 @@ pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, la
let mut thumbnail = String::new(); let mut thumbnail = String::new();
let mut svg_defs = String::new(); let mut svg_defs = String::new();
layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], ViewMode::Normal, font_cache); layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], ViewMode::Normal, font_cache, None);
let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(","); let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",");
let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() { let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() {
format!( format!(

View File

@ -1,3 +1,4 @@
use crate::input::InputPreprocessorMessageHandler;
use crate::message_prelude::*; use crate::message_prelude::*;
use graphene::document::Document as GrapheneDocument; use graphene::document::Document as GrapheneDocument;
@ -9,9 +10,9 @@ pub struct OverlaysMessageHandler {
pub overlays_graphene_document: GrapheneDocument, pub overlays_graphene_document: GrapheneDocument,
} }
impl MessageHandler<OverlaysMessage, (bool, &FontCache)> for OverlaysMessageHandler { impl MessageHandler<OverlaysMessage, (bool, &FontCache, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
#[remain::check] #[remain::check]
fn process_action(&mut self, message: OverlaysMessage, (overlays_visible, font_cache): (bool, &FontCache), responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: OverlaysMessage, (overlays_visible, font_cache, ipp): (bool, &FontCache, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
use OverlaysMessage::*; use OverlaysMessage::*;
#[remain::sorted] #[remain::sorted]
@ -31,7 +32,7 @@ impl MessageHandler<OverlaysMessage, (bool, &FontCache)> for OverlaysMessageHand
responses.push_back( responses.push_back(
FrontendMessage::UpdateDocumentOverlays { FrontendMessage::UpdateDocumentOverlays {
svg: if overlays_visible { svg: if overlays_visible {
self.overlays_graphene_document.render_root(ViewMode::Normal, font_cache) self.overlays_graphene_document.render_root(ViewMode::Normal, font_cache, Some(ipp.document_bounds()))
} else { } else {
String::from("") String::from("")
}, },

View File

@ -6,6 +6,8 @@ use crate::message_prelude::*;
#[doc(inline)] #[doc(inline)]
pub use graphene::DocumentResponse; pub use graphene::DocumentResponse;
use glam::DVec2;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct InputPreprocessorMessageHandler { pub struct InputPreprocessorMessageHandler {
pub keyboard: KeyStates, pub keyboard: KeyStates,
@ -152,4 +154,9 @@ impl InputPreprocessorMessageHandler {
responses.push_back(InputMapperMessage::KeyDown(key).into()); responses.push_back(InputMapperMessage::KeyDown(key).into());
} }
} }
pub fn document_bounds(&self) -> [DVec2; 2] {
// ipp bounds are relative to the entire screen
[(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left]
}
} }

View File

@ -42,10 +42,10 @@ impl Default for Document {
impl Document { impl Document {
/// Wrapper around render, that returns the whole document as a Response. /// Wrapper around render, that returns the whole document as a Response.
pub fn render_root(&mut self, mode: ViewMode, font_cache: &FontCache) -> String { pub fn render_root(&mut self, mode: ViewMode, font_cache: &FontCache, culling_bounds: Option<[DVec2; 2]>) -> String {
let mut svg_defs = String::from("<defs>"); let mut svg_defs = String::from("<defs>");
self.root.render(&mut vec![], mode, &mut svg_defs, font_cache); self.root.render(&mut vec![], mode, &mut svg_defs, font_cache, culling_bounds);
svg_defs.push_str("</defs>"); svg_defs.push_str("</defs>");

View File

@ -22,9 +22,9 @@ pub struct FolderLayer {
} }
impl LayerData for FolderLayer { impl LayerData for FolderLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode, font_cache: &FontCache) { fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode, font_cache: &FontCache, culling_bounds: Option<[DVec2; 2]>) {
for layer in &mut self.layers { for layer in &mut self.layers {
let _ = writeln!(svg, "{}", layer.render(transforms, view_mode, svg_defs, font_cache)); let _ = writeln!(svg, "{}", layer.render(transforms, view_mode, svg_defs, font_cache, culling_bounds));
} }
} }

View File

@ -24,7 +24,7 @@ pub struct ImageLayer {
} }
impl LayerData for ImageLayer { impl LayerData for ImageLayer {
fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, _font_cache: &FontCache) { fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, _font_cache: &FontCache, _culling_bounds: Option<[DVec2; 2]>) {
let transform = self.transform(transforms, view_mode); let transform = self.transform(transforms, view_mode);
let inverse = transform.inverse(); let inverse = transform.inverse();

View File

@ -61,7 +61,7 @@ pub trait LayerData {
/// let mut svg = String::new(); /// let mut svg = String::new();
/// ///
/// // Render the shape without any transforms, in normal view mode /// // Render the shape without any transforms, in normal view mode
/// shape.render(&mut svg, &mut String::new(), &mut vec![], ViewMode::Normal, &Default::default()); /// shape.render(&mut svg, &mut String::new(), &mut vec![], ViewMode::Normal, &Default::default(), None);
/// ///
/// assert_eq!( /// assert_eq!(
/// svg, /// svg,
@ -70,7 +70,7 @@ pub trait LayerData {
/// </g>" /// </g>"
/// ); /// );
/// ``` /// ```
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode, font_cache: &FontCache); fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode, font_cache: &FontCache, culling_bounds: Option<[DVec2; 2]>);
/// Determine the layers within this layer that intersect a given quad. /// Determine the layers within this layer that intersect a given quad.
/// # Example /// # Example
@ -117,8 +117,8 @@ pub trait LayerData {
} }
impl LayerData for LayerDataType { impl LayerData for LayerDataType {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode, font_cache: &FontCache) { fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode, font_cache: &FontCache, viewport_bounds: Option<[DVec2; 2]>) {
self.inner_mut().render(svg, svg_defs, transforms, view_mode, font_cache) self.inner_mut().render(svg, svg_defs, transforms, view_mode, font_cache, viewport_bounds)
} }
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) { fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) {
@ -223,16 +223,29 @@ impl Layer {
LayerIter { stack: vec![self] } LayerIter { stack: vec![self] }
} }
pub fn render(&mut self, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, svg_defs: &mut String, font_cache: &FontCache) -> &str { pub fn render(&mut self, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, svg_defs: &mut String, font_cache: &FontCache, culling_bounds: Option<[DVec2; 2]>) -> &str {
if !self.visible { if !self.visible {
return ""; return "";
} }
transforms.push(self.transform);
if let Some(viewport_bounds) = culling_bounds {
if let Some(bounding_box) = self.data.bounding_box(transforms.iter().cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY), font_cache) {
let is_overlapping =
viewport_bounds[0].x < bounding_box[1].x && bounding_box[0].x < viewport_bounds[1].x && viewport_bounds[0].y < bounding_box[1].y && bounding_box[0].y < viewport_bounds[1].y;
if !is_overlapping {
transforms.pop();
self.cache.clear();
self.cache_dirty = true;
return "";
}
}
}
if self.cache_dirty { if self.cache_dirty {
transforms.push(self.transform);
self.thumbnail_cache.clear(); self.thumbnail_cache.clear();
self.svg_defs_cache.clear(); self.svg_defs_cache.clear();
self.data.render(&mut self.thumbnail_cache, &mut self.svg_defs_cache, transforms, view_mode, font_cache); self.data.render(&mut self.thumbnail_cache, &mut self.svg_defs_cache, transforms, view_mode, font_cache, culling_bounds);
self.cache.clear(); self.cache.clear();
let _ = writeln!(self.cache, r#"<g transform="matrix("#); let _ = writeln!(self.cache, r#"<g transform="matrix("#);
@ -246,9 +259,10 @@ impl Layer {
self.opacity, self.opacity,
self.thumbnail_cache.as_str() self.thumbnail_cache.as_str()
); );
transforms.pop();
self.cache_dirty = false; self.cache_dirty = false;
} }
transforms.pop();
svg_defs.push_str(&self.svg_defs_cache); svg_defs.push_str(&self.svg_defs_cache);
self.cache.as_str() self.cache.as_str()

View File

@ -25,13 +25,14 @@ pub struct ShapeLayer {
pub path: BezPath, pub path: BezPath,
/// The visual style of the shape. /// The visual style of the shape.
pub style: style::PathStyle, pub style: style::PathStyle,
// TODO: We might be able to remove this in a future refactor
pub render_index: i32, pub render_index: i32,
/// Whether or not the [path](ShapeLayer::path) connects to itself. /// Whether or not the [path](ShapeLayer::path) connects to itself.
pub closed: bool, pub closed: bool,
} }
impl LayerData for ShapeLayer { impl LayerData for ShapeLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, _font_cache: &FontCache) { fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, _font_cache: &FontCache, _culling_bounds: Option<[DVec2; 2]>) {
let mut path = self.path.clone(); let mut path = self.path.clone();
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box(); let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
@ -89,7 +90,7 @@ impl ShapeLayer {
(_, -1) => 0, (_, -1) => 0,
(_, x) => (transforms.len() as i32 - x).max(0) as usize, (_, x) => (transforms.len() as i32 - x).max(0) as usize,
}; };
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY) transforms.iter().skip(start).fold(DAffine2::IDENTITY, |a, b| a * *b)
} }
pub fn from_bez_path(bez_path: BezPath, style: PathStyle, closed: bool) -> Self { pub fn from_bez_path(bez_path: BezPath, style: PathStyle, closed: bool) -> Self {

View File

@ -37,7 +37,7 @@ pub struct TextLayer {
} }
impl LayerData for TextLayer { impl LayerData for TextLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, font_cache: &FontCache) { fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode, font_cache: &FontCache, _culling_bounds: Option<[DVec2; 2]>) {
let transform = self.transform(transforms, view_mode); let transform = self.transform(transforms, view_mode);
let inverse = transform.inverse(); let inverse = transform.inverse();