Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement viewport culling #667

Merged
merged 8 commits into from
Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion editor/src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use graphene::color::Color;
use graphene::layers::text_layer::Font;

// Viewport
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.;
Expand Down
2 changes: 1 addition & 1 deletion editor/src/document/artboard_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ impl MessageHandler<ArtboardMessage, &FontCache> for ArtboardMessageHandler {
} else {
responses.push_back(
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(),
);
Expand Down
8 changes: 3 additions & 5 deletions editor/src/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,8 +873,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
}
#[remain::unsorted]
Overlays(message) => {
self.overlays_message_handler.process_action(message, (self.overlays_visible, font_cache), responses);
// responses.push_back(OverlaysMessage::RenderOverlays.into());
self.overlays_message_handler.process_action(message, (self.overlays_visible, font_cache, ipp), responses);
}
#[remain::unsorted]
TransformLayers(message) => {
Expand Down Expand Up @@ -1060,7 +1059,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
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!(
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
Expand Down Expand Up @@ -1100,7 +1099,6 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
}
}
FolderChanged { affected_folder_path } => {
let _ = self.graphene_document.render_root(self.view_mode, font_cache);
let affected_layer_path = affected_folder_path;
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
}
Expand Down Expand Up @@ -1220,7 +1218,7 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
RenderDocument => {
responses.push_back(
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(),
);
Expand Down
2 changes: 1 addition & 1 deletion editor/src/document/layer_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, la

let mut thumbnail = 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 thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() {
format!(
Expand Down
7 changes: 4 additions & 3 deletions editor/src/document/overlays_message_handler.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::input::InputPreprocessorMessageHandler;
use crate::message_prelude::*;

use graphene::document::Document as GrapheneDocument;
Expand All @@ -9,9 +10,9 @@ pub struct OverlaysMessageHandler {
pub overlays_graphene_document: GrapheneDocument,
}

impl MessageHandler<OverlaysMessage, (bool, &FontCache)> for OverlaysMessageHandler {
impl MessageHandler<OverlaysMessage, (bool, &FontCache, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
#[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::*;

#[remain::sorted]
Expand All @@ -31,7 +32,7 @@ impl MessageHandler<OverlaysMessage, (bool, &FontCache)> for OverlaysMessageHand
responses.push_back(
FrontendMessage::UpdateDocumentOverlays {
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 {
String::from("")
},
Expand Down
7 changes: 7 additions & 0 deletions editor/src/input/input_preprocessor_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use crate::message_prelude::*;
#[doc(inline)]
pub use graphene::DocumentResponse;

use glam::DVec2;

#[derive(Debug, Default)]
pub struct InputPreprocessorMessageHandler {
pub keyboard: KeyStates,
Expand Down Expand Up @@ -152,4 +154,9 @@ impl InputPreprocessorMessageHandler {
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]
}
}
4 changes: 2 additions & 2 deletions graphene/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ impl Default for Document {

impl Document {
/// 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>");

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>");

Expand Down
4 changes: 2 additions & 2 deletions graphene/src/layers/folder_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ pub struct 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 {
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));
}
}

Expand Down
2 changes: 1 addition & 1 deletion graphene/src/layers/image_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct 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 inverse = transform.inverse();

Expand Down
30 changes: 22 additions & 8 deletions graphene/src/layers/layer_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub trait LayerData {
/// let mut svg = String::new();
///
/// // 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!(
/// svg,
Expand All @@ -70,7 +70,7 @@ pub trait LayerData {
/// </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.
/// # Example
Expand Down Expand Up @@ -117,8 +117,8 @@ pub trait LayerData {
}

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) {
self.inner_mut().render(svg, svg_defs, transforms, view_mode, font_cache)
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, viewport_bounds)
}

fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) {
Expand Down Expand Up @@ -223,16 +223,29 @@ impl Layer {
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 {
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 {
transforms.push(self.transform);
self.thumbnail_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();
let _ = writeln!(self.cache, r#"<g transform="matrix("#);
Expand All @@ -246,9 +259,10 @@ impl Layer {
self.opacity,
self.thumbnail_cache.as_str()
);
transforms.pop();

self.cache_dirty = false;
}
transforms.pop();
svg_defs.push_str(&self.svg_defs_cache);

self.cache.as_str()
Expand Down
5 changes: 3 additions & 2 deletions graphene/src/layers/shape_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ pub struct ShapeLayer {
pub path: BezPath,
/// The visual style of the shape.
pub style: style::PathStyle,
// TODO: We might be able to remove this in a future refactor
pub render_index: i32,
/// Whether or not the [path](ShapeLayer::path) connects to itself.
pub closed: bool,
}

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 kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
Expand Down Expand Up @@ -89,7 +90,7 @@ impl ShapeLayer {
(_, -1) => 0,
(_, 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 {
Expand Down
2 changes: 1 addition & 1 deletion graphene/src/layers/text_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub struct 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 inverse = transform.inverse();

Expand Down