Skip to content
Open
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
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