Skip to content
27 changes: 14 additions & 13 deletions desktop/src/render/state.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::borrow::Cow;
use wgpu::PresentMode;

use crate::window::Window;
use crate::wrapper::{TargetTexture, WgpuContext, WgpuExecutor};
use crate::wrapper::{WgpuContext, WgpuExecutor};

#[derive(derivative::Derivative)]
#[derivative(Debug)]
Expand All @@ -19,7 +18,7 @@ pub(crate) struct RenderState {
viewport_scale: [f32; 2],
viewport_offset: [f32; 2],
viewport_texture: Option<std::sync::Arc<wgpu::Texture>>,
overlays_texture: Option<TargetTexture>,
overlays_texture: Option<std::sync::Arc<wgpu::Texture>>,
ui_texture: Option<wgpu::Texture>,
bind_group: Option<wgpu::BindGroup>,
#[derivative(Debug = "ignore")]
Expand Down Expand Up @@ -236,11 +235,17 @@ impl RenderState {
return;
};
let size = glam::UVec2::new(viewport_texture.width(), viewport_texture.height());
let result = futures::executor::block_on(self.executor.render_vello_scene_to_target_texture(&scene, size, &Default::default(), &mut self.overlays_texture));
if let Err(e) = result {
tracing::error!("Error rendering overlays: {:?}", e);
return;
let result = futures::executor::block_on(self.executor.render_vello_scene(&scene, size, &Default::default(), None));
match result {
Ok(texture) => {
self.overlays_texture = Some(texture);
}
Err(e) => {
self.overlays_texture = None;
tracing::error!("Error rendering overlays: {:?}", e);
}
}

self.update_bindgroup();
}

Expand Down Expand Up @@ -317,11 +322,7 @@ impl RenderState {
fn update_bindgroup(&mut self) {
self.surface_outdated = true;
let viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
let overlays_texture_view = self
.overlays_texture
.as_ref()
.map(|target| Cow::Borrowed(target.view()))
.unwrap_or_else(|| Cow::Owned(self.transparent_texture.create_view(&wgpu::TextureViewDescriptor::default())));
let overlays_texture_view = self.overlays_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());
let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default());

let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {
Expand All @@ -333,7 +334,7 @@ impl RenderState {
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(overlays_texture_view.as_ref()),
resource: wgpu::BindingResource::TextureView(&overlays_texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
Expand Down
1 change: 0 additions & 1 deletion desktop/wrapper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use message_dispatcher::DesktopWrapperMessageDispatcher;
use messages::{DesktopFrontendMessage, DesktopWrapperMessage};

pub use graphite_editor::consts::{DOUBLE_CLICK_MILLISECONDS, FILE_EXTENSION};
pub use wgpu_executor::TargetTexture;
pub use wgpu_executor::WgpuContext;
pub use wgpu_executor::WgpuContextBuilder;
pub use wgpu_executor::WgpuExecutor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::messages::prelude::*;
use glam::{DAffine2, IVec2};
use graph_craft::document::NodeId;
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::color::Color;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Image;
use graphene_std::subpath::Subpath;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::PointId;
use graphene_std::vector::VectorModificationType;
use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::{Artboard, Color};

#[impl_message(Message, DocumentMessage, GraphOperation)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ impl NodeGraphExecutor {
click_targets,
clip_targets,
vector_data,
backgrounds: _,
} = render_output.metadata;

// Run these update state messages immediately
Expand Down
15 changes: 12 additions & 3 deletions node-graph/interpreted-executor/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
let render_node = DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(4), 0)],
exports: vec![NodeInput::node(NodeId(5), 0)],
nodes: [
DocumentNode {
call_argument: concrete!(Context),
Expand All @@ -40,7 +40,6 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
},
..Default::default()
},
// Keep this in sync with the protonode in valid_input_types
DocumentNode {
call_argument: concrete!(Context),
inputs: vec![NodeInput::scope("editor-api"), NodeInput::node(NodeId(0), 0)],
Expand Down Expand Up @@ -71,9 +70,19 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
},
..Default::default()
},
DocumentNode {
call_argument: concrete!(Context),
inputs: vec![NodeInput::scope("editor-api"), NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::render_background::IDENTIFIER),
context_features: graphene_std::ContextDependencies {
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
inject: ContextFeatures::empty(),
},
..Default::default()
},
DocumentNode {
call_argument: concrete!(graphene_std::application_io::RenderConfig),
inputs: vec![NodeInput::node(NodeId(3), 0)],
inputs: vec![NodeInput::node(NodeId(4), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::create_context::IDENTIFIER),
context_features: graphene_std::ContextDependencies {
// We add the extract index annotation here to force the compiler to add a context nullification node before this node so the render context is properly nullified so the render cache node can do its's work
Expand Down
2 changes: 1 addition & 1 deletion node-graph/libraries/core-types/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ impl Default for Footprint {
impl Footprint {
pub const DEFAULT: Self = Self {
transform: DAffine2::IDENTITY,
resolution: UVec2::new(1920, 1080),
resolution: UVec2::ZERO,
quality: RenderQuality::Full,
};

Expand Down
158 changes: 158 additions & 0 deletions node-graph/libraries/rendering/src/background.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use crate::renderer::{RenderParams, SvgRender};
use core_types::transform::Footprint;
use core_types::uuid::generate_uuid;
use dyn_any::DynAny;
use glam::DVec2;
use glam::{DAffine2, IVec2};
use std::fmt::Write;
use std::sync::{Arc, LazyLock};

#[derive(Debug, Default, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct Background {
pub location: IVec2,
pub dimensions: IVec2,
}

pub trait RenderBackground {
fn render_background_to_vello(&self, scene: &mut vello::Scene, transform: DAffine2, render_params: &RenderParams);
fn render_background_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
}

impl RenderBackground for Background {
fn render_background_to_vello(&self, scene: &mut vello::Scene, transform: DAffine2, render_params: &RenderParams) {
let rect = background_rect(self);
checkerboard_fill_vello(scene, transform, rect, DVec2::new(rect.x0, rect.y0), render_params.viewport_zoom);
Comment thread
timon-schelling marked this conversation as resolved.
}

fn render_background_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let rect = background_rect(self);
checkerboard_fill_svg(render, rect, DVec2::new(rect.x0, rect.y0), render_params.viewport_zoom, "checkered-artboard");
}
}

impl RenderBackground for Vec<Background> {
fn render_background_to_vello(&self, scene: &mut vello::Scene, transform: DAffine2, render_params: &RenderParams) {
if self.is_empty() {
let Some(rect) = viewport_rect(render_params.footprint, render_params.scale) else { return };
checkerboard_fill_vello(scene, transform, rect, DVec2::ZERO, render_params.viewport_zoom);
return;
}

for background in self {
background.render_background_to_vello(scene, transform, render_params);
}
}

fn render_background_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
if self.is_empty() {
let Some(rect) = viewport_rect(render_params.footprint, render_params.scale) else { return };
checkerboard_fill_svg(render, rect, DVec2::ZERO, render_params.viewport_zoom, "checkered-viewport");
return;
}

for background in self {
background.render_background_svg(render, render_params);
}
}
}

fn checkerboard_fill_vello(scene: &mut vello::Scene, transform: DAffine2, rect: kurbo::Rect, pattern_origin: DVec2, viewport_zoom: f64) {
if viewport_zoom <= 0. {
return;
}

let brush = vello::peniko::Brush::Image(vello::peniko::ImageBrush {
image: vello::peniko::ImageData {
data: vello::peniko::Blob::new(CHECKERBOARD_IMAGE_DATA.clone()),
format: vello::peniko::ImageFormat::Rgba8,
width: 16,
height: 16,
alpha_type: vello::peniko::ImageAlphaType::Alpha,
},
sampler: vello::peniko::ImageSampler {
x_extend: vello::peniko::Extend::Repeat,
y_extend: vello::peniko::Extend::Repeat,
quality: vello::peniko::ImageQuality::Low,
alpha: 1.,
},
});
let brush_transform = kurbo::Affine::scale(1. / viewport_zoom).then_translate(kurbo::Vec2::new(pattern_origin.x, pattern_origin.y));
scene.fill(vello::peniko::Fill::NonZero, kurbo::Affine::new(transform.to_cols_array()), &brush, Some(brush_transform), &rect);
}

fn checkerboard_fill_svg(render: &mut SvgRender, rect: kurbo::Rect, pattern_origin: DVec2, viewport_zoom: f64, checker_id_prefix: &str) {
if viewport_zoom <= 0. {
return;
}

let checker_id = format!("{checker_id_prefix}-{}", generate_uuid());

let svg_defs: &mut String = &mut render.svg_defs;
let pattern_id: &str = &checker_id;

let cell_size = 8. / viewport_zoom;
let pattern_size = cell_size * 2.;

write!(
svg_defs,
r##"<pattern id="{pattern_id}" x="{}" y="{}" width="{pattern_size}" height="{pattern_size}" patternUnits="userSpaceOnUse"><rect width="{pattern_size}" height="{pattern_size}" fill="#ffffff" /><rect x="{cell_size}" y="0" width="{cell_size}" height="{cell_size}" fill="#cccccc" /><rect x="0" y="{cell_size}" width="{cell_size}" height="{cell_size}" fill="#cccccc" /></pattern>"##,
pattern_origin.x,
pattern_origin.y,
)
.unwrap();

render.leaf_tag("rect", |attributes| {
attributes.push("x", rect.x0.to_string());
attributes.push("y", rect.y0.to_string());
attributes.push("width", rect.width().to_string());
attributes.push("height", rect.height().to_string());
attributes.push("fill", format!("url(#{checker_id})"));
});
}

fn background_rect(background: &Background) -> kurbo::Rect {
let [a, b] = [background.location.as_dvec2(), background.location.as_dvec2() + background.dimensions.as_dvec2()];
kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y))
}

fn viewport_rect(footprint: Footprint, scale: f64) -> Option<kurbo::Rect> {
if scale <= 0. {
return None;
}

let logical_resolution = footprint.resolution.as_dvec2() / scale;
let logical_footprint = Footprint {
resolution: logical_resolution.round().as_uvec2().max(glam::UVec2::ONE),
..footprint
};
let bounds = logical_footprint.viewport_bounds_in_local_space();
let min = bounds.start.floor();
let max = bounds.end.ceil();

if !(min.is_finite() && max.is_finite()) {
return None;
}

Some(kurbo::Rect::new(min.x, min.y, max.x, max.y))
}

/// Cached 16x16 transparency checkerboard image data (four 8x8 cells of #ffffff and #cccccc).
static CHECKERBOARD_IMAGE_DATA: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| {
const SIZE: u32 = 16;
const HALF: u32 = 8;

let mut data = vec![0_u8; (SIZE * SIZE * 4) as usize];
for y in 0..SIZE {
for x in 0..SIZE {
let is_light = ((x / HALF) + (y / HALF)).is_multiple_of(2);
let value = if is_light { 0xff } else { 0xcc };
let index = ((y * SIZE + x) * 4) as usize;
data[index] = value;
data[index + 1] = value;
data[index + 2] = value;
data[index + 3] = 0xff;
}
}

Arc::new(data)
});
2 changes: 2 additions & 0 deletions node-graph/libraries/rendering/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod background;
pub mod convert_usvg_path;
pub mod render_ext;
mod renderer;
pub mod to_peniko;

pub use background::RenderBackground;
pub use renderer::*;
Loading
Loading