Cut over to the graphene execution model

This commit is contained in:
Dennis Kobert
2026-08-04 13:15:21 +02:00
parent 7623b68318
commit 76ec799496
71 changed files with 3544 additions and 2378 deletions
-27
View File
@@ -1,27 +0,0 @@
use core_types::NodeIO;
use core_types::WasmNotSend;
pub use core_types::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
pub use core_types::{Node, generic, ops};
use dyn_any::StaticType;
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{FutureAny, SharedNodeContainer};
pub trait IntoTypeErasedNode<'n> {
fn into_type_erased(self) -> TypeErasedBox<'n>;
}
impl<'n, N: 'n> IntoTypeErasedNode<'n> for N
where
N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + Sync + WasmNotSend,
{
fn into_type_erased(self) -> TypeErasedBox<'n> {
Box::new(self)
}
}
pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(), O> {
downcast_node(n)
}
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
DowncastBothNode::new(n)
}
+1 -1
View File
@@ -1,9 +1,9 @@
pub mod any;
pub mod platform_application_io;
pub mod render_background;
pub mod render_cache;
pub mod render_node;
pub mod render_pixel_preview;
pub mod runtime;
pub mod text;
pub use blending_nodes;
pub use brush_nodes as brush;
@@ -3,9 +3,11 @@ use base64::Engine;
#[cfg(target_family = "wasm")]
use canvas_utils::{Canvas, CanvasHandle};
use core_types::color::SRGBA8;
use core_types::gpoll::GPoll;
use core_types::list::{Item, List};
#[cfg(target_family = "wasm")]
use core_types::math::bbox::Bbox;
use core_types::runtime::SourceFuture;
#[cfg(target_family = "wasm")]
use core_types::transform::Footprint;
#[cfg(target_family = "wasm")]
@@ -137,7 +139,7 @@ fn image_to_bytes(_: impl Ctx, image: List<Raster<CPU>>) -> List<u8> {
/// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue.
#[node_macro::node(category("Web Request"))]
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[name("URL")] url: String) -> Arc<[u8]> {
async fn load_resource(_: impl Ctx, _primary: (), #[name("URL")] url: String) -> Arc<[u8]> {
let placeholder = || -> Arc<[u8]> { Arc::from(Vec::<u8>::new()) };
let response = match reqwest::Client::new().get(&url).send().await {
@@ -185,14 +187,14 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> List<Raster<CPU>> {
#[cfg(target_family = "wasm")]
#[node_macro::node(category(""))]
async fn create_canvas(_: impl Ctx) -> CanvasHandle {
fn create_canvas(_: impl Ctx) -> CanvasHandle {
CanvasHandle::new()
}
/// Renders a view of the input graphic within an area defined by the *Footprint*.
#[cfg(target_family = "wasm")]
#[node_macro::node(category(""))]
async fn rasterize<T: WasmNotSend + Clone + 'n>(
async fn rasterize<T: WasmNotSend + Clone>(
_: impl Ctx,
#[implementations(
List<Vector>,
@@ -262,29 +264,37 @@ where
}
#[node_macro::node(category(""), inject_scope)]
pub async fn editor_api<'a: 'n>(_: impl Ctx, #[scope("editor-api")] editor_api: &'a PlatformEditorApi) -> &'a PlatformEditorApi {
pub fn editor_api(_: impl Ctx, #[scope("editor-api")] editor_api: Arc<PlatformEditorApi>) -> Arc<PlatformEditorApi> {
editor_api
}
#[node_macro::node(category(""))]
pub async fn resource<'a: 'n>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Resource {
let application_io = editor_api.application_io.as_ref().expect("ApplicationIo must be available when using resources");
application_io.load_resource(hash).await.unwrap_or_else(|| {
panic!("Resource {hash} not found");
pub fn resource(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: Arc<PlatformEditorApi>) -> SourceFuture<GPoll<Resource>> {
let application_io = editor_api.application_io.clone();
Box::pin(async move {
let Some(application_io) = application_io else {
return GPoll::error("ApplicationIo not available");
};
match application_io.load_resource(hash).await {
Some(resource) => GPoll::Final(resource),
None => GPoll::error("resource not found"),
}
})
}
#[node_macro::node(category(""), inject_scope)]
pub async fn wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> &'a ::wgpu_executor::WgpuExecutor {
editor_api
.application_io
.as_ref()
.expect("ApplicationIo not not available")
.gpu_executor()
.expect("GPU executor not available")
pub fn wgpu_executor(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: Arc<PlatformEditorApi>) -> ::wgpu_executor::WgpuExecutorHandle {
::wgpu_executor::WgpuExecutorHandle(
editor_api
.application_io
.as_ref()
.expect("ApplicationIo not not available")
.gpu_executor_arc()
.expect("GPU executor not available"),
)
}
#[node_macro::node(category(""), inject_scope)]
pub async fn try_wgpu_executor<'a: 'n>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Option<&'a ::wgpu_executor::WgpuExecutor> {
editor_api.application_io.as_ref()?.gpu_executor()
pub fn try_wgpu_executor(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: Arc<PlatformEditorApi>) -> Option<::wgpu_executor::WgpuExecutorHandle> {
editor_api.application_io.as_ref()?.gpu_executor_arc().map(::wgpu_executor::WgpuExecutorHandle)
}
+13 -19
View File
@@ -9,14 +9,10 @@ use graphic_types::raster_types::Texture;
use rendering::{RenderParams, SvgRender, SvgRenderOutput};
use std::fmt::Write;
use wgpu::util::DeviceExt;
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache};
use wgpu_executor::{WgpuExecutor, WgpuPipeline, WgpuPipelineCache};
#[node_macro::node(category(""))]
async fn render_background<'a: 'n>(
ctx: impl Ctx + ExtractFootprint + ExtractVarArgs,
#[scope(composite_background_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
data: RenderOutput,
) -> RenderOutput {
fn render_background<'a>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, #[scope(composite_background_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache, data: RenderOutput) -> RenderOutput {
let footprint = ctx.footprint();
let render_params = ctx
.vararg(0)
@@ -35,14 +31,12 @@ async fn render_background<'a: 'n>(
let data = match foreground_data {
RenderOutputType::Texture(foreground_texture) => {
let doc_to_screen = render_params.footprint.transform.as_affine2();
let blended = pipeline
.run::<CompositeBackground>(&CompositeBackgroundArgs {
foreground: foreground_texture.as_ref(),
backgrounds: &metadata.backgrounds,
document_to_screen: doc_to_screen,
zoom: render_params.viewport_zoom.to_f32(),
})
.await;
let blended = pipeline.run::<CompositeBackground>(&CompositeBackgroundArgs {
foreground: foreground_texture.as_ref(),
backgrounds: &metadata.backgrounds,
document_to_screen: doc_to_screen,
zoom: render_params.viewport_zoom.to_f32(),
});
RenderOutputType::Texture(blended)
}
@@ -121,9 +115,9 @@ async fn render_background<'a: 'n>(
}
#[node_macro::node(category(""), inject_scope)]
async fn composite_background_pipeline<'a: 'n>(
fn composite_background_pipeline(
_ctx: impl Ctx,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<wgpu_executor::WgpuExecutorHandle>,
#[data] pipeline: WgpuPipelineCache,
) -> WgpuPipelineCache {
if let Some(executor) = executor {
@@ -148,7 +142,7 @@ pub struct CompositeBackgroundArgs<'a> {
zoom: f32,
}
impl AsyncWgpuPipeline for CompositeBackground {
impl WgpuPipeline for CompositeBackground {
type Args<'a> = CompositeBackgroundArgs<'a>;
type Out = Texture;
@@ -331,7 +325,7 @@ impl AsyncWgpuPipeline for CompositeBackground {
}
}
async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out {
fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out {
let &CompositeBackgroundArgs {
foreground,
backgrounds,
@@ -340,7 +334,7 @@ impl AsyncWgpuPipeline for CompositeBackground {
} = args;
let foreground_size = foreground.size();
let output = executor.request_texture(UVec2::new(foreground_size.width, foreground_size.height)).await;
let output = executor.request_texture(UVec2::new(foreground_size.width, foreground_size.height));
if zoom <= 0. {
return output;
+48 -70
View File
@@ -1,8 +1,9 @@
//! Tile-based render caching for efficient viewport panning.
use core_types::gpoll::Interrupt;
use core_types::math::bbox::AxisAlignedBbox;
use core_types::transform::{Footprint, RenderQuality, Transform};
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl};
use core_types::{Ctx, DeriveCtx, ExtractAll};
use glam::{DAffine2, DVec2, IVec2, UVec2};
use graph_craft::application_io::PlatformEditorApi;
use graph_craft::document::value::{RenderOutput, RenderOutputType};
@@ -11,7 +12,6 @@ use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
use std::collections::HashSet;
use std::hash::Hash;
use std::sync::{Arc, Mutex};
use wgpu_executor::WgpuExecutor;
pub const TILE_SIZE: u32 = 256;
pub const MAX_CACHE_MEMORY_BYTES: usize = 512 * 1024 * 1024;
@@ -321,25 +321,23 @@ fn flood_fill(start: &TileCoord, tile_set: &HashSet<TileCoord>, visited: &mut Ha
}
#[node_macro::node(category(""))]
pub async fn render_output_cache<'a: 'n>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
#[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi,
data: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,
pub fn render_output_cache(
ctx: impl Ctx + ExtractAll + DeriveCtx,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<wgpu_executor::WgpuExecutorHandle>,
#[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: std::sync::Arc<PlatformEditorApi>,
data: impl Node<Context<'_>, Output = RenderOutput>,
#[data] tile_cache: TileCache,
) -> RenderOutput {
let footprint = ctx.footprint();
) -> Result<RenderOutput, Interrupt> {
let footprint = *ctx.footprint();
let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()) else {
log::warn!("render_output_cache: missing or invalid render params, falling back to direct render");
let context = OwnedContextImpl::from(ctx.clone()).with_footprint(*footprint);
return data.eval(context.into_context()).await;
return data.eval(&ctx.derived());
};
// Fall back to direct render for non-Vello or zero-size viewports
let physical_resolution = footprint.resolution;
if !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || physical_resolution.x == 0 || physical_resolution.y == 0 {
let context = OwnedContextImpl::from(ctx.clone()).with_footprint(*footprint).with_vararg(Box::new(render_params.clone()));
return data.eval(context.into_context()).await;
return data.eval(&ctx.derived());
}
let zoom = footprint.scale_magnitudes().x;
@@ -375,8 +373,38 @@ pub async fn render_output_cache<'a: 'n>(
if missing_region.tiles.is_empty() {
continue;
}
let region = render_missing_region(missing_region, |ctx| data.eval(ctx), ctx.clone(), render_params, &footprint.transform, &device_origin_offset).await;
new_regions.push(region);
let min_tile = missing_region.tiles.iter().fold(IVec2::new(i32::MAX, i32::MAX), |acc, t| acc.min(IVec2::new(t.x, t.y)));
let max_tile = missing_region.tiles.iter().fold(IVec2::new(i32::MIN, i32::MIN), |acc, t| acc.max(IVec2::new(t.x, t.y)));
let tile_count = (max_tile - min_tile) + IVec2::ONE;
let region_pixel_size = (tile_count * TILE_SIZE as i32).as_uvec2();
let tile_global_offset = min_tile.as_dvec2() * TILE_SIZE as f64 + device_origin_offset;
let region_transform = DAffine2::from_translation(-tile_global_offset) * footprint.transform;
let region_footprint = Footprint {
transform: region_transform,
resolution: region_pixel_size,
quality: RenderQuality::Full,
};
let mut result = data.eval(&ctx.with_footprint(&region_footprint))?;
let RenderOutputType::Texture(texture) = result.data else {
unreachable!("render_output_cache: expected texture output from Vello render");
};
result.metadata.apply_transform(region_transform.inverse());
let memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL;
new_regions.push(CachedRegion {
texture,
texture_size: region_pixel_size,
tiles: missing_region.tiles.clone(),
metadata: result.metadata,
last_access: 0,
memory_size,
});
}
tile_cache.store_regions(new_regions.clone());
@@ -385,68 +413,18 @@ pub async fn render_output_cache<'a: 'n>(
// If no regions, fall back to direct render
if all_regions.is_empty() {
let context = OwnedContextImpl::from(ctx.clone()).with_footprint(*footprint).with_vararg(Box::new(render_params.clone()));
return data.eval(context.into_context()).await;
return data.eval(&ctx.derived());
}
let executor = executor.expect("GPU executor not available");
let output_texture = executor.request_texture(physical_resolution).await;
let output_texture = executor.request_texture(physical_resolution);
let combined_metadata = composite_cached_regions(&all_regions, &output_texture, &device_origin_offset, &footprint.transform, executor);
let combined_metadata = composite_cached_regions(&all_regions, &output_texture, &device_origin_offset, &footprint.transform, &executor);
RenderOutput {
Ok(RenderOutput {
data: RenderOutputType::Texture(output_texture),
metadata: combined_metadata,
}
}
async fn render_missing_region<F, Fut>(
region: &RenderRegion,
render_fn: F,
ctx: impl Ctx + ExtractAll + CloneVarArgs,
render_params: &RenderParams,
viewport_transform: &DAffine2,
viewport_origin_offset: &DVec2,
) -> CachedRegion
where
F: Fn(Context<'static>) -> Fut,
Fut: std::future::Future<Output = RenderOutput>,
{
let min_tile = region.tiles.iter().fold(IVec2::new(i32::MAX, i32::MAX), |acc, t| acc.min(IVec2::new(t.x, t.y)));
let max_tile = region.tiles.iter().fold(IVec2::new(i32::MIN, i32::MIN), |acc, t| acc.max(IVec2::new(t.x, t.y)));
let tile_count = (max_tile - min_tile) + IVec2::ONE;
let region_pixel_size = (tile_count * TILE_SIZE as i32).as_uvec2();
let tile_global_offset = min_tile.as_dvec2() * TILE_SIZE as f64 + *viewport_origin_offset;
let region_transform = DAffine2::from_translation(-tile_global_offset) * *viewport_transform;
let region_footprint = Footprint {
transform: region_transform,
resolution: region_pixel_size,
quality: RenderQuality::Full,
};
let region_params = render_params.clone();
let region_ctx = OwnedContextImpl::from(ctx).with_footprint(region_footprint).with_vararg(Box::new(region_params)).into_context();
let mut result = render_fn(region_ctx).await;
let RenderOutputType::Texture(texture) = result.data else {
unreachable!("render_missing_region: expected texture output from Vello render");
};
let pixel_to_document = region_transform.inverse();
result.metadata.apply_transform(pixel_to_document);
let memory_size = (region_pixel_size.x * region_pixel_size.y) as usize * BYTES_PER_PIXEL;
CachedRegion {
texture,
texture_size: region_pixel_size,
tiles: region.tiles.clone(),
metadata: result.metadata,
last_access: 0,
memory_size,
}
})
}
fn composite_cached_regions(
+102 -30
View File
@@ -1,7 +1,7 @@
use core_types::gpoll::Interrupt;
use core_types::list::List;
use core_types::transform::{Footprint, Transform};
use core_types::{CloneVarArgs, ExtractAll, ExtractVarArgs};
use core_types::{Color, Context, Ctx, ExtractFootprint, OwnedContextImpl, WasmNotSend};
use core_types::{Color, Context, Ctx, DeriveCtx, ExtractFootprint, ExtractVarArgs, VarArgLink, VarArgSlots, WasmNotSend};
use graph_craft::document::value::{RenderOutput, RenderOutputType};
use graphene_application_io::{ExportFormat, RenderConfig};
use graphic_types::raster_types::{CPU, Raster};
@@ -9,7 +9,7 @@ use graphic_types::{Artboard, Graphic, Vector};
use rendering::{Render, RenderMetadata, RenderOutputType as RenderOutputTypeRequest, RenderParams, SvgRender, SvgRenderOutput};
use std::sync::Arc;
use vector_types::GradientStops;
use wgpu_executor::{RenderContext, WgpuExecutor};
use wgpu_executor::RenderContext;
#[derive(Clone, dyn_any::DynAny)]
pub enum RenderIntermediateType {
@@ -23,8 +23,8 @@ pub struct RenderIntermediate {
}
#[node_macro::node(category(""))]
async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + Sync>(
ctx: impl Ctx + ExtractVarArgs + ExtractAll + CloneVarArgs,
fn render_intermediate<T: 'static + Render + WasmNotSend + Send + Sync>(
ctx: impl Ctx + ExtractVarArgs + DeriveCtx,
#[implementations(
Context -> List<Artboard>,
Context -> List<Graphic>,
@@ -34,21 +34,19 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
Context -> List<GradientStops>,
Context -> List<String>,
)]
data: impl Node<Context<'static>, Output = T>,
) -> RenderIntermediate {
data: impl Node<Context<'_>, Output = T>,
) -> Result<RenderIntermediate, Interrupt> {
let data = data.eval(&ctx.derived())?;
let render_params = ctx
.vararg(0)
.expect("Did not find var args")
.downcast_ref::<RenderParams>()
.expect("Downcasting render params yielded invalid type");
let ctx = OwnedContextImpl::from(ctx.clone()).into_context();
let data = data.eval(ctx).await;
let footprint = Footprint::default();
let mut metadata = RenderMetadata::default();
data.collect_metadata(&mut metadata, footprint, None);
match &render_params.render_output_type {
Ok(match &render_params.render_output_type {
RenderOutputTypeRequest::Vello => {
let mut scene = vello::Scene::new();
@@ -70,13 +68,13 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
metadata,
}
}
}
})
}
#[node_macro::node(category(""))]
async fn render<'a: 'n>(
fn render(
ctx: impl Ctx + ExtractFootprint + ExtractVarArgs,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<wgpu_executor::WgpuExecutorHandle>,
data: RenderIntermediate,
) -> RenderOutput {
let footprint = ctx.footprint();
@@ -133,7 +131,6 @@ async fn render<'a: 'n>(
let texture = executor
.expect("GPU executor not available")
.render_vello_scene(&transformed_scene, footprint.resolution, context, None)
.await
.expect("Failed to render Vello scene");
RenderOutputType::Texture(texture)
}
@@ -144,11 +141,13 @@ async fn render<'a: 'n>(
}
#[node_macro::node(category(""))]
async fn create_context<'a: 'n>(
// Context injections are defined in the wrap_network_in_scope function
render_config: RenderConfig,
data: impl Node<Context<'static>, Output = RenderOutput>,
) -> RenderOutput {
fn create_context(ctx: impl Ctx + ExtractVarArgs + DeriveCtx, data: impl Node<Context<'_>, Output = RenderOutput>) -> Result<RenderOutput, Interrupt> {
let render_config = *ctx
.vararg(0)
.expect("Did not find var args")
.downcast_ref::<RenderConfig>()
.expect("Downcasting render config yielded invalid type");
let render_output_type = match render_config.export_format {
ExportFormat::Svg => RenderOutputTypeRequest::Svg,
ExportFormat::Raster => RenderOutputTypeRequest::Vello,
@@ -169,16 +168,89 @@ async fn create_context<'a: 'n>(
..Default::default()
};
let ctx = OwnedContextImpl::default()
.with_footprint(footprint)
.with_real_time(render_config.time.time)
.with_animation_time(render_config.time.animation_time.as_secs_f64())
.with_pointer_position(render_config.pointer)
.with_vararg(Box::new(render_params))
.into_context();
let mut result = data.eval(ctx).await;
let scope = ctx
.scope()
.with_real_time(Some(render_config.time.time))
.with_animation_time(Some(render_config.time.animation_time.as_secs_f64()))
.with_pointer_position(Some(render_config.pointer));
let varargs = VarArgLink {
args: VarArgSlots::Single(&render_params),
outer: None,
};
let scoped = ctx.with_scope(&scope);
let with_params = scoped.with_varargs(&varargs);
let mut result = data.eval(&with_params.with_footprint(&footprint))?;
result.metadata.apply_transform(glam::DAffine2::from_scale(glam::DVec2::splat(1. / render_config.scale)));
result
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use core_types::arena::Arena;
use core_types::context::{ContextImpl, EvalScope, VarArgsResult};
use core_types::gpoll::GPoll;
use core_types::node::Node;
use core_types::{ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime};
use graphene_application_io::TimingInformation;
struct ProbeNode;
impl<'a> Node<ContextImpl<'a>> for ProbeNode {
type Output = RenderOutput;
fn eval(&self, ctx: &ContextImpl<'a>) -> GPoll<RenderOutput> {
let render_params = ctx.vararg(0).unwrap().downcast_ref::<RenderParams>().expect("the vararg chain must start with RenderParams");
assert_eq!(render_params.scale, 2.0);
assert!(matches!(ctx.vararg(1), Err(VarArgsResult::IndexOutOfBounds)), "the RenderConfig must not leak downstream");
assert_eq!(ctx.footprint().transform, glam::DAffine2::from_scale(glam::DVec2::splat(2.0)) * Footprint::DEFAULT.transform);
assert_eq!(ctx.try_real_time(), Some(1.5));
assert_eq!(ctx.try_animation_time(), Some(2.0));
assert_eq!(ctx.try_pointer_position(), Some(glam::DVec2::new(3.0, 4.0)));
GPoll::Final(RenderOutput {
data: RenderOutputType::Buffer {
data: Vec::new(),
width: 0,
height: 0,
},
metadata: RenderMetadata::default(),
})
}
}
#[test]
fn create_context_builds_the_render_context_from_the_root_vararg() {
let arena = Arena::new(256);
let generations = [];
let scope = EvalScope::new(None, None, None, &generations, &arena);
let root = ContextImpl::root(&scope);
let render_config = RenderConfig {
scale: 2.0,
time: TimingInformation {
time: 1.5,
animation_time: std::time::Duration::from_secs(2),
},
pointer: glam::DVec2::new(3.0, 4.0),
..Default::default()
};
let varargs = VarArgLink {
args: VarArgSlots::Single(&render_config),
outer: None,
};
let ctx = root.with_varargs(&varargs);
let graph = CreateContextNode::new(ProbeNode);
let GPoll::Final(result) = <CreateContextNode<ProbeNode> as Node<ContextImpl>>::eval(&graph, &ctx) else {
panic!("create_context must complete synchronously");
};
assert_eq!(
result.data,
RenderOutputType::Buffer {
data: Vec::new(),
width: 0,
height: 0
}
);
}
}
@@ -1,22 +1,22 @@
use core_types::gpoll::Interrupt;
use core_types::transform::{Footprint, Transform};
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl};
use core_types::{Ctx, DeriveCtx, ExtractAll};
use glam::{DAffine2, DVec2, UVec2, Vec2};
use graph_craft::document::value::{RenderOutput, RenderOutputType};
use graphic_types::raster_types::Texture;
use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
use vector_types::vector::style::RenderMode;
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache};
use wgpu_executor::{WgpuExecutor, WgpuPipeline, WgpuPipelineCache};
#[node_macro::node(category(""))]
pub async fn render_pixel_preview<'a: 'n>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
pub fn render_pixel_preview(
ctx: impl Ctx + ExtractAll + DeriveCtx,
#[scope(pixel_preview_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
data: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,
) -> RenderOutput {
data: impl Node<Context<'_>, Output = RenderOutput>,
) -> Result<RenderOutput, Interrupt> {
let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()).cloned() else {
log::error!("invalid render params for pixel preview");
let context = OwnedContextImpl::from(ctx).into_context();
return data.eval(context).await;
return data.eval(&ctx.derived());
};
let physical_scale = render_params.scale;
@@ -24,8 +24,7 @@ pub async fn render_pixel_preview<'a: 'n>(
let viewport_zoom = footprint.scale_magnitudes().x;
if render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. {
let context = OwnedContextImpl::from(ctx).into_context();
return data.eval(context).await;
return data.eval(&ctx.derived());
}
let physical_resolution = footprint.resolution;
@@ -51,33 +50,31 @@ pub async fn render_pixel_preview<'a: 'n>(
quality: footprint.quality,
};
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(upstream_footprint).with_vararg(Box::new(render_params)).into_context();
let mut result = data.eval(new_ctx).await;
let scoped = ctx.push_vararg(&render_params);
let mut result = data.eval(&scoped.ctx().with_footprint(&upstream_footprint))?;
let RenderOutputType::Texture(ref source_texture) = result.data else { return result };
let RenderOutputType::Texture(ref source_texture) = result.data else { return Ok(result) };
let logical_transform = DAffine2::from_scale(DVec2::splat(1. / physical_scale)) * footprint.transform;
let transform = DAffine2::from_translation(-upstream_min) * logical_transform.inverse() * DAffine2::from_scale(logical_resolution);
let resampled = pipeline
.run::<PixelPreview>(&PixelPreviewArgs {
source: source_texture.as_ref(),
transform: &transform,
size: physical_resolution,
})
.await;
let resampled = pipeline.run::<PixelPreview>(&PixelPreviewArgs {
source: source_texture.as_ref(),
transform: &transform,
size: physical_resolution,
});
result.data = RenderOutputType::Texture(resampled);
result.metadata.apply_transform(footprint.transform * DAffine2::from_translation(upstream_min));
result
Ok(result)
}
#[node_macro::node(category(""), inject_scope)]
async fn pixel_preview_pipeline<'a: 'n>(
fn pixel_preview_pipeline(
_ctx: impl Ctx,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<wgpu_executor::WgpuExecutorHandle>,
#[data] pipeline: WgpuPipelineCache,
) -> WgpuPipelineCache {
if let Some(executor) = executor {
@@ -97,7 +94,7 @@ pub struct PixelPreviewArgs<'a> {
size: UVec2,
}
impl AsyncWgpuPipeline for PixelPreview {
impl WgpuPipeline for PixelPreview {
type Args<'a> = PixelPreviewArgs<'a>;
type Out = Texture;
@@ -169,11 +166,11 @@ impl AsyncWgpuPipeline for PixelPreview {
PixelPreview { pipeline, bind_group_layout }
}
async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out {
fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out {
let context = &executor.context();
let &PixelPreviewArgs { source, transform, size } = args;
let output = executor.request_texture(size).await;
let output = executor.request_texture(size);
let source_view = source.create_view(&wgpu::TextureViewDescriptor::default());
let output_view = output.create_view(&wgpu::TextureViewDescriptor::default());
+11
View File
@@ -0,0 +1,11 @@
pub use core_types::runtime::*;
use crate::platform_application_io::editor_api;
use core_types::Ctx;
use graph_craft::application_io::PlatformEditorApi;
use std::sync::Arc;
#[node_macro::node(category(""), inject_scope)]
pub fn runtime(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: Arc<PlatformEditorApi>) -> RuntimeHandle {
editor_api.runtime.clone()
}