|
| 1 | +use graph_craft::document::value::RenderOutput; |
| 2 | +pub use graph_craft::document::value::RenderOutputType; |
| 3 | +pub use graph_craft::wasm_application_io::*; |
| 4 | +use graphene_application_io::{ApplicationIo, ExportFormat, ImageTexture, RenderConfig, SurfaceFrame}; |
| 5 | +use graphene_core::gradient::GradientStops; |
| 6 | +use graphene_core::raster::image::Image; |
| 7 | +use graphene_core::raster_types::{CPU, Raster}; |
| 8 | +use graphene_core::table::Table; |
| 9 | +use graphene_core::transform::Footprint; |
| 10 | +use graphene_core::vector::Vector; |
| 11 | +use graphene_core::{Artboard, CloneVarArgs, ExtractAll, ExtractVarArgs}; |
| 12 | +use graphene_core::{Color, Context, Ctx, ExtractFootprint, Graphic, OwnedContextImpl, WasmNotSend}; |
| 13 | +use graphene_svg_renderer::{Render, RenderOutputType as RenderOutputTypeRequest, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix}; |
| 14 | +use graphene_svg_renderer::{RenderMetadata, SvgSegment}; |
| 15 | +use std::sync::Arc; |
| 16 | +use wgpu_executor::RenderContext; |
| 17 | + |
| 18 | +/// List of (canvas id, image data) pairs for embedding images as canvases in the final SVG string. |
| 19 | +type ImageData = Vec<(u64, Image<Color>)>; |
| 20 | + |
| 21 | +#[derive(Clone, dyn_any::DynAny)] |
| 22 | +pub enum RenderIntermediateType { |
| 23 | + Vello(Arc<(vello::Scene, RenderContext)>), |
| 24 | + Svg(Arc<(String, ImageData, String)>), |
| 25 | +} |
| 26 | +#[derive(Clone, dyn_any::DynAny)] |
| 27 | +pub struct RenderIntermediate { |
| 28 | + ty: RenderIntermediateType, |
| 29 | + metadata: RenderMetadata, |
| 30 | + contains_artboard: bool, |
| 31 | +} |
| 32 | + |
| 33 | +#[node_macro::node(category(""))] |
| 34 | +async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>( |
| 35 | + ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs, |
| 36 | + #[implementations( |
| 37 | + Context -> Table<Artboard>, |
| 38 | + Context -> Table<Graphic>, |
| 39 | + Context -> Table<Vector>, |
| 40 | + Context -> Table<Raster<CPU>>, |
| 41 | + Context -> Table<Color>, |
| 42 | + Context -> Table<GradientStops>, |
| 43 | + )] |
| 44 | + data: impl Node<Context<'static>, Output = T>, |
| 45 | + editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>, |
| 46 | +) -> RenderIntermediate { |
| 47 | + let mut render = SvgRender::new(); |
| 48 | + let render_params = ctx |
| 49 | + .vararg(0) |
| 50 | + .expect("Did not find var args") |
| 51 | + .downcast_ref::<RenderParams>() |
| 52 | + .expect("Downcasting render params yielded invalid type"); |
| 53 | + |
| 54 | + let ctx = OwnedContextImpl::from(ctx.clone()).into_context(); |
| 55 | + let data = data.eval(ctx).await; |
| 56 | + |
| 57 | + let footprint = Footprint::default(); |
| 58 | + let mut metadata = RenderMetadata::default(); |
| 59 | + data.collect_metadata(&mut metadata, footprint, None); |
| 60 | + let contains_artboard = data.contains_artboard(); |
| 61 | + |
| 62 | + let editor_api = editor_api.eval(None).await; |
| 63 | + |
| 64 | + if !render_params.for_export && editor_api.editor_preferences.use_vello() && matches!(render_params.render_output_type, graphene_svg_renderer::RenderOutputType::Vello) { |
| 65 | + let mut scene = vello::Scene::new(); |
| 66 | + |
| 67 | + let mut context = wgpu_executor::RenderContext::default(); |
| 68 | + data.render_to_vello(&mut scene, Default::default(), &mut context, render_params); |
| 69 | + |
| 70 | + RenderIntermediate { |
| 71 | + ty: RenderIntermediateType::Vello(Arc::new((scene, context))), |
| 72 | + metadata, |
| 73 | + contains_artboard, |
| 74 | + } |
| 75 | + } else { |
| 76 | + data.render_svg(&mut render, render_params); |
| 77 | + |
| 78 | + RenderIntermediate { |
| 79 | + ty: RenderIntermediateType::Svg(Arc::new((render.svg.to_svg_string(), render.image_data, render.svg_defs.clone()))), |
| 80 | + metadata, |
| 81 | + contains_artboard, |
| 82 | + } |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +#[node_macro::node(category(""))] |
| 87 | +async fn create_context<'a: 'n>( |
| 88 | + // Context injections are defined in the wrap_network_in_scope function |
| 89 | + render_config: RenderConfig, |
| 90 | + data: impl Node<Context<'static>, Output = RenderOutput>, |
| 91 | +) -> RenderOutput { |
| 92 | + let footprint = render_config.viewport; |
| 93 | + |
| 94 | + let render_output_type = match render_config.export_format { |
| 95 | + ExportFormat::Svg => RenderOutputTypeRequest::Svg, |
| 96 | + ExportFormat::Png { .. } => todo!(), |
| 97 | + ExportFormat::Jpeg => todo!(), |
| 98 | + ExportFormat::Canvas => RenderOutputTypeRequest::Vello, |
| 99 | + ExportFormat::Texture => RenderOutputTypeRequest::Vello, |
| 100 | + }; |
| 101 | + let render_params = RenderParams { |
| 102 | + render_mode: render_config.render_mode, |
| 103 | + hide_artboards: render_config.hide_artboards, |
| 104 | + for_export: render_config.for_export, |
| 105 | + render_output_type, |
| 106 | + footprint: Footprint::default(), |
| 107 | + ..Default::default() |
| 108 | + }; |
| 109 | + let ctx = OwnedContextImpl::default() |
| 110 | + .with_footprint(footprint) |
| 111 | + .with_real_time(render_config.time.time) |
| 112 | + .with_animation_time(render_config.time.animation_time.as_secs_f64()) |
| 113 | + .with_vararg(Box::new(render_params)) |
| 114 | + .into_context(); |
| 115 | + |
| 116 | + data.eval(ctx).await |
| 117 | +} |
| 118 | + |
| 119 | +#[node_macro::node(category(""))] |
| 120 | +async fn render<'a: 'n>( |
| 121 | + ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, |
| 122 | + editor_api: &'a WasmEditorApi, |
| 123 | + data: RenderIntermediate, |
| 124 | + _surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>, |
| 125 | +) -> RenderOutput { |
| 126 | + let footprint = ctx.footprint(); |
| 127 | + let render_params = ctx |
| 128 | + .vararg(0) |
| 129 | + .expect("Did not find var args") |
| 130 | + .downcast_ref::<RenderParams>() |
| 131 | + .expect("Downcasting render params yielded invalid type"); |
| 132 | + let mut render_params = render_params.clone(); |
| 133 | + render_params.footprint = *footprint; |
| 134 | + let render_params = &render_params; |
| 135 | + |
| 136 | + let RenderIntermediate { ty, mut metadata, contains_artboard } = data; |
| 137 | + metadata.apply_transform(footprint.transform); |
| 138 | + |
| 139 | + let surface_handle = if cfg!(all(feature = "vello", target_family = "wasm")) { |
| 140 | + _surface_handle.eval(None).await |
| 141 | + } else { |
| 142 | + None |
| 143 | + }; |
| 144 | + |
| 145 | + let mut output_format = render_params.render_output_type; |
| 146 | + // TODO: Actually request the right thing upfront |
| 147 | + if let RenderIntermediateType::Svg(_) = ty { |
| 148 | + output_format = RenderOutputTypeRequest::Svg; |
| 149 | + } |
| 150 | + let data = match (output_format, &ty) { |
| 151 | + (RenderOutputTypeRequest::Svg, RenderIntermediateType::Svg(svg_data)) => { |
| 152 | + let mut svg_renderer = SvgRender::new(); |
| 153 | + if !contains_artboard && !render_params.hide_artboards { |
| 154 | + svg_renderer.leaf_tag("rect", |attributes| { |
| 155 | + attributes.push("x", "0"); |
| 156 | + attributes.push("y", "0"); |
| 157 | + attributes.push("width", footprint.resolution.x.to_string()); |
| 158 | + attributes.push("height", footprint.resolution.y.to_string()); |
| 159 | + let matrix = format_transform_matrix(footprint.transform.inverse()); |
| 160 | + if !matrix.is_empty() { |
| 161 | + attributes.push("transform", matrix); |
| 162 | + } |
| 163 | + attributes.push("fill", "white"); |
| 164 | + }); |
| 165 | + } |
| 166 | + svg_renderer.svg.push(SvgSegment::from(svg_data.0.clone())); |
| 167 | + svg_renderer.image_data = svg_data.1.clone(); |
| 168 | + svg_renderer.svg_defs = svg_data.2.clone(); |
| 169 | + |
| 170 | + svg_renderer.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2())); |
| 171 | + RenderOutputType::Svg { |
| 172 | + svg: svg_renderer.svg.to_svg_string(), |
| 173 | + image_data: svg_renderer.image_data, |
| 174 | + } |
| 175 | + } |
| 176 | + (RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => { |
| 177 | + let Some(exec) = editor_api.application_io.as_ref().unwrap().gpu_executor() else { |
| 178 | + unreachable!("Attempted to render with Vello when no GPU executor is available"); |
| 179 | + }; |
| 180 | + let (child, context) = Arc::as_ref(vello_data); |
| 181 | + let footprint_transform = vello::kurbo::Affine::new(footprint.transform.to_cols_array()); |
| 182 | + |
| 183 | + let mut scene = vello::Scene::new(); |
| 184 | + scene.append(child, Some(footprint_transform)); |
| 185 | + |
| 186 | + let encoding = scene.encoding_mut(); |
| 187 | + |
| 188 | + // We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport |
| 189 | + // See <https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Full.20screen.20color.2Fgradients/near/538435044> for more detail |
| 190 | + |
| 191 | + for transform in encoding.transforms.iter_mut() { |
| 192 | + if transform.matrix[0] == f32::INFINITY { |
| 193 | + *transform = vello_encoding::Transform::from_kurbo(&(vello::kurbo::Affine::scale_non_uniform(footprint.resolution.x as f64, footprint.resolution.y as f64))) |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + let mut background = Color::from_rgb8_srgb(0x22, 0x22, 0x22); |
| 198 | + if !contains_artboard && !render_params.hide_artboards { |
| 199 | + background = Color::WHITE; |
| 200 | + } |
| 201 | + if let Some(surface_handle) = surface_handle { |
| 202 | + exec.render_vello_scene(&scene, &surface_handle, footprint.resolution, context, background) |
| 203 | + .await |
| 204 | + .expect("Failed to render Vello scene"); |
| 205 | + |
| 206 | + let frame = SurfaceFrame { |
| 207 | + surface_id: surface_handle.window_id, |
| 208 | + resolution: footprint.resolution, |
| 209 | + transform: glam::DAffine2::IDENTITY, |
| 210 | + }; |
| 211 | + |
| 212 | + RenderOutputType::CanvasFrame(frame) |
| 213 | + } else { |
| 214 | + let texture = exec |
| 215 | + .render_vello_scene_to_texture(&scene, footprint.resolution, context, background) |
| 216 | + .await |
| 217 | + .expect("Failed to render Vello scene"); |
| 218 | + |
| 219 | + RenderOutputType::Texture(ImageTexture { texture }) |
| 220 | + } |
| 221 | + } |
| 222 | + _ => unreachable!("Render node did not receive its requested data type"), |
| 223 | + }; |
| 224 | + RenderOutput { data, metadata } |
| 225 | +} |
0 commit comments