Make the GPU render and dispatch pipelines synchronous

This commit is contained in:
Dennis Kobert
2026-07-27 10:47:44 +00:00
parent 6392c02dee
commit 9e83632647
9 changed files with 102 additions and 149 deletions
@@ -9,7 +9,7 @@ use crate::texture_cache::TextureCache;
use anyhow::Result; use anyhow::Result;
use core_types::Color; use core_types::Color;
use core_types::color::SRGBA8; use core_types::color::SRGBA8;
use futures::lock::Mutex; use std::sync::Mutex;
use glam::UVec2; use glam::UVec2;
use graphene_application_io::{ApplicationIo, EditorApi}; use graphene_application_io::{ApplicationIo, EditorApi};
use raster_types::Texture; use raster_types::Texture;
@@ -19,7 +19,6 @@ use wgpu::{Origin3d, TextureAspect};
pub use context::Context as WgpuContext; pub use context::Context as WgpuContext;
pub use context::ContextBuilder as WgpuContextBuilder; pub use context::ContextBuilder as WgpuContextBuilder;
pub use pipeline::AsyncPipeline as AsyncWgpuPipeline;
pub use pipeline::Pipeline as WgpuPipeline; pub use pipeline::Pipeline as WgpuPipeline;
pub use pipeline::PipelineCache as WgpuPipelineCache; pub use pipeline::PipelineCache as WgpuPipelineCache;
pub use rendering::RenderContext; pub use rendering::RenderContext;
@@ -68,8 +67,8 @@ impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &
} }
impl WgpuExecutor { impl WgpuExecutor {
pub async fn render_vello_scene(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Option<Color>) -> Result<Texture> { pub fn render_vello_scene(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Option<Color>) -> Result<Texture> {
let texture = self.request_texture(size).await; let texture = self.request_texture(size);
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
@@ -82,7 +81,7 @@ impl WgpuExecutor {
}; };
{ {
let mut renderer = self.inner.vello_renderer.lock().await; let mut renderer = self.inner.vello_renderer.lock().unwrap();
for (image_brush, texture) in context.resource_overrides.iter() { for (image_brush, texture) in context.resource_overrides.iter() {
let texture_view = wgpu::TexelCopyTextureInfoBase { let texture_view = wgpu::TexelCopyTextureInfoBase {
texture: (**texture).clone(), texture: (**texture).clone(),
@@ -109,8 +108,8 @@ impl WgpuExecutor {
pipeline.init::<P>(self); pipeline.init::<P>(self);
} }
pub async fn request_texture(&self, size: UVec2) -> Texture { pub fn request_texture(&self, size: UVec2) -> Texture {
self.inner.texture_cache.lock().await.request_texture(&self.context().device, size) self.inner.texture_cache.lock().unwrap().request_texture(&self.context().device, size)
} }
} }
@@ -1,42 +1,16 @@
use dyn_any::DynAny; use dyn_any::DynAny;
use std::any::Any; use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use crate::WgpuExecutor; use crate::WgpuExecutor;
pub type PipelineFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait Pipeline: Any + Send + Sync + Sized { pub trait Pipeline: Any + Send + Sync + Sized {
type Args<'a>; type Args<'a>;
type Out: Send; type Out: Send;
fn create(executor: &WgpuExecutor) -> Self; fn create(executor: &WgpuExecutor) -> Self;
fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out>; fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out;
}
pub trait AsyncPipeline: Any + Send + Sync + Sized {
type Args<'a>;
type Out: Send;
fn create(executor: &WgpuExecutor) -> Self;
fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> impl Future<Output = Self::Out> + Send + 'a;
}
impl<P: AsyncPipeline> Pipeline for P {
type Args<'a> = <P as AsyncPipeline>::Args<'a>;
type Out = <P as AsyncPipeline>::Out;
fn create(executor: &WgpuExecutor) -> Self {
<P as AsyncPipeline>::create(executor)
}
fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> PipelineFuture<'a, Self::Out> {
Box::pin(<P as AsyncPipeline>::run(self, executor, args))
}
} }
#[derive(Default, Clone, DynAny)] #[derive(Default, Clone, DynAny)]
@@ -51,13 +25,13 @@ impl PipelineCache {
self.pipeline.get_or_init(|| Box::new(P::create(executor))); self.pipeline.get_or_init(|| Box::new(P::create(executor)));
} }
pub async fn run<P: Pipeline>(&self, args: &P::Args<'_>) -> P::Out { pub fn run<P: Pipeline>(&self, args: &P::Args<'_>) -> P::Out {
let executor = self.executor.get().expect("PipelineCache not initialized"); let executor = self.executor.get().expect("PipelineCache not initialized");
let entry = self.pipeline.get().expect("PipelineCache not initialized"); let entry = self.pipeline.get().expect("PipelineCache not initialized");
let pipeline = (&**entry) let pipeline = (&**entry)
.downcast_ref::<P>() .downcast_ref::<P>()
.unwrap_or_else(|| panic!("PipelineCache type mismatch: run::<{}>() but init used a different pipeline type", std::any::type_name::<P>(),)); .unwrap_or_else(|| panic!("PipelineCache type mismatch: run::<{}>() but init used a different pipeline type", std::any::type_name::<P>(),));
pipeline.run(executor, args).await pipeline.run(executor, args)
} }
} }
@@ -2,7 +2,7 @@ use crate::WgpuContext;
use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime}; use crate::shader_runtime::{FULLSCREEN_VERTEX_SHADER_NAME, ShaderRuntime};
use core_types::list::{Item, List}; use core_types::list::{Item, List};
use core_types::shaders::buffer_struct::BufferStruct; use core_types::shaders::buffer_struct::BufferStruct;
use futures::lock::Mutex; use std::sync::Mutex;
use raster_types::{GPU, Raster}; use raster_types::{GPU, Raster};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
@@ -33,8 +33,8 @@ impl PerPixelAdjustShaderRuntime {
} }
impl ShaderRuntime { impl ShaderRuntime {
pub async fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: List<Raster<GPU>>, args: Option<&T>) -> List<Raster<GPU>> { pub fn run_per_pixel_adjust<T: BufferStruct>(&self, shaders: &Shaders<'_>, textures: List<Raster<GPU>>, args: Option<&T>) -> List<Raster<GPU>> {
let mut cache = self.per_pixel_adjust.pipeline_cache.lock().await; let mut cache = self.per_pixel_adjust.pipeline_cache.lock().unwrap();
let pipeline = cache let pipeline = cache
.entry(shaders.fragment_shader_name.to_owned()) .entry(shaders.fragment_shader_name.to_owned())
.or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, shaders)); .or_insert_with(|| PerPixelAdjustGraphicsPipeline::new(&self.context, shaders));
@@ -287,7 +287,7 @@ impl PerPixelAdjustCodegen<'_> {
wgsl_shader: crate::WGSL_SHADER, wgsl_shader: crate::WGSL_SHADER,
fragment_shader_name: super::#entry_point_name, fragment_shader_name: super::#entry_point_name,
has_uniform: #has_uniform, has_uniform: #has_uniform,
}, #gpu_image, #uniform_buffer).await }, #gpu_image, #uniform_buffer)
} }
}; };
@@ -314,13 +314,12 @@ impl PerPixelAdjustCodegen<'_> {
context_features: self.parsed.input.context_features.clone(), context_features: self.parsed.input.context_features.clone(),
}, },
output_type: raster_gpu, output_type: raster_gpu,
is_async: true, is_async: false,
fields, fields,
body, body,
description: self.parsed.description.clone(), description: self.parsed.description.clone(),
}; };
parsed_node_fn.replace_impl_trait_in_input(); parsed_node_fn.replace_impl_trait_in_input();
parsed_node_fn.inject_async_source_fields(self.crate_ident.gcore()?);
let gpu_node_impl = crate::codegen::generate_node_code(self.crate_ident, &parsed_node_fn)?; let gpu_node_impl = crate::codegen::generate_node_code(self.crate_ident, &parsed_node_fn)?;
// wrap node in `mod #gpu_node_mod` // wrap node in `mod #gpu_node_mod`
@@ -10,6 +10,8 @@ use core_types::math::bbox::Bbox;
use core_types::transform::Footprint; use core_types::transform::Footprint;
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
use core_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, WasmNotSend}; use core_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, WasmNotSend};
use core_types::gpoll::GPoll;
use core_types::runtime::SourceFuture;
use core_types::{Color, Ctx}; use core_types::{Color, Ctx};
pub use graph_craft::application_io::resource::{Resource, ResourceHash}; pub use graph_craft::application_io::resource::{Resource, ResourceHash};
pub use graph_craft::application_io::*; pub use graph_craft::application_io::*;
@@ -267,10 +269,16 @@ pub fn editor_api<'a>(_: impl Ctx, #[scope("editor-api")] editor_api: &'a Platfo
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub async fn resource<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Resource { pub fn resource<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> SourceFuture<GPoll<Resource>> {
let application_io = editor_api.application_io.as_ref().expect("ApplicationIo must be available when using resources"); let application_io = editor_api.application_io.clone();
application_io.load_resource(hash).await.unwrap_or_else(|| { Box::pin(async move {
panic!("Resource {hash} not found"); 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"),
}
}) })
} }
+10 -12
View File
@@ -9,7 +9,7 @@ use graphic_types::raster_types::Texture;
use rendering::{RenderParams, SvgRender, SvgRenderOutput}; use rendering::{RenderParams, SvgRender, SvgRenderOutput};
use std::fmt::Write; use std::fmt::Write;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache}; use wgpu_executor::{WgpuExecutor, WgpuPipeline, WgpuPipelineCache};
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn render_background<'a>( fn render_background<'a>(
@@ -35,14 +35,12 @@ fn render_background<'a>(
let data = match foreground_data { let data = match foreground_data {
RenderOutputType::Texture(foreground_texture) => { RenderOutputType::Texture(foreground_texture) => {
let doc_to_screen = render_params.footprint.transform.as_affine2(); let doc_to_screen = render_params.footprint.transform.as_affine2();
let blended = pipeline let blended = pipeline.run::<CompositeBackground>(&CompositeBackgroundArgs {
.run::<CompositeBackground>(&CompositeBackgroundArgs { foreground: foreground_texture.as_ref(),
foreground: foreground_texture.as_ref(), backgrounds: &metadata.backgrounds,
backgrounds: &metadata.backgrounds, document_to_screen: doc_to_screen,
document_to_screen: doc_to_screen, zoom: render_params.viewport_zoom.to_f32(),
zoom: render_params.viewport_zoom.to_f32(), });
})
.await;
RenderOutputType::Texture(blended) RenderOutputType::Texture(blended)
} }
@@ -148,7 +146,7 @@ pub struct CompositeBackgroundArgs<'a> {
zoom: f32, zoom: f32,
} }
impl AsyncWgpuPipeline for CompositeBackground { impl WgpuPipeline for CompositeBackground {
type Args<'a> = CompositeBackgroundArgs<'a>; type Args<'a> = CompositeBackgroundArgs<'a>;
type Out = Texture; type Out = Texture;
@@ -331,7 +329,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 { let &CompositeBackgroundArgs {
foreground, foreground,
backgrounds, backgrounds,
@@ -340,7 +338,7 @@ impl AsyncWgpuPipeline for CompositeBackground {
} = args; } = args;
let foreground_size = foreground.size(); 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. { if zoom <= 0. {
return output; return output;
+45 -66
View File
@@ -1,8 +1,9 @@
//! Tile-based render caching for efficient viewport panning. //! Tile-based render caching for efficient viewport panning.
use core_types::gpoll::Interrupt;
use core_types::math::bbox::AxisAlignedBbox; use core_types::math::bbox::AxisAlignedBbox;
use core_types::transform::{Footprint, RenderQuality, Transform}; use core_types::transform::{Footprint, RenderQuality, Transform};
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime, OwnedContextImpl}; use core_types::{Context, Ctx, DeriveCtx, ExtractAll};
use glam::{DAffine2, DVec2, IVec2, UVec2}; use glam::{DAffine2, DVec2, IVec2, UVec2};
use graph_craft::application_io::PlatformEditorApi; use graph_craft::application_io::PlatformEditorApi;
use graph_craft::document::value::{RenderOutput, RenderOutputType}; use graph_craft::document::value::{RenderOutput, RenderOutputType};
@@ -321,25 +322,23 @@ fn flood_fill(start: &TileCoord, tile_set: &HashSet<TileCoord>, visited: &mut Ha
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub async fn render_output_cache<'a>( pub fn render_output_cache<'a>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync, ctx: impl Ctx + ExtractAll + DeriveCtx,
#[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>, #[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, #[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi,
data: impl Node<Context<'_>, Output = RenderOutput> + Send + Sync, data: impl Node<Context<'_>, Output = RenderOutput>,
#[data] tile_cache: TileCache, #[data] tile_cache: TileCache,
) -> RenderOutput { ) -> Result<RenderOutput, Interrupt> {
let footprint = ctx.footprint(); let footprint = *ctx.footprint();
let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()) else { 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"); 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(&ctx.derived());
return data.eval(context.into_context()).await;
}; };
// Fall back to direct render for non-Vello or zero-size viewports // Fall back to direct render for non-Vello or zero-size viewports
let physical_resolution = footprint.resolution; let physical_resolution = footprint.resolution;
if !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || physical_resolution.x == 0 || physical_resolution.y == 0 { 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(&ctx.derived());
return data.eval(context.into_context()).await;
} }
let zoom = footprint.scale_magnitudes().x; let zoom = footprint.scale_magnitudes().x;
@@ -375,8 +374,38 @@ pub async fn render_output_cache<'a>(
if missing_region.tiles.is_empty() { if missing_region.tiles.is_empty() {
continue; continue;
} }
let region = render_missing_region(missing_region, |ctx| data.eval(ctx), ctx.clone(), render_params, &footprint.transform, &device_origin_offset).await; let min_tile = missing_region.tiles.iter().fold(IVec2::new(i32::MAX, i32::MAX), |acc, t| acc.min(IVec2::new(t.x, t.y)));
new_regions.push(region); 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()); tile_cache.store_regions(new_regions.clone());
@@ -385,68 +414,18 @@ pub async fn render_output_cache<'a>(
// If no regions, fall back to direct render // If no regions, fall back to direct render
if all_regions.is_empty() { if all_regions.is_empty() {
let context = OwnedContextImpl::from(ctx.clone()).with_footprint(*footprint).with_vararg(Box::new(render_params.clone())); return data.eval(&ctx.derived());
return data.eval(context.into_context()).await;
} }
let executor = executor.expect("GPU executor not available"); 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), data: RenderOutputType::Texture(output_texture),
metadata: combined_metadata, 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<'_>) -> 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( fn composite_cached_regions(
-1
View File
@@ -131,7 +131,6 @@ fn render<'a>(
let texture = executor let texture = executor
.expect("GPU executor not available") .expect("GPU executor not available")
.render_vello_scene(&transformed_scene, footprint.resolution, context, None) .render_vello_scene(&transformed_scene, footprint.resolution, context, None)
.await
.expect("Failed to render Vello scene"); .expect("Failed to render Vello scene");
RenderOutputType::Texture(texture) RenderOutputType::Texture(texture)
} }
@@ -1,22 +1,22 @@
use core_types::gpoll::Interrupt;
use core_types::transform::{Footprint, Transform}; use core_types::transform::{Footprint, Transform};
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl}; use core_types::{Context, Ctx, DeriveCtx, ExtractAll};
use glam::{DAffine2, DVec2, UVec2, Vec2}; use glam::{DAffine2, DVec2, UVec2, Vec2};
use graph_craft::document::value::{RenderOutput, RenderOutputType}; use graph_craft::document::value::{RenderOutput, RenderOutputType};
use graphic_types::raster_types::Texture; use graphic_types::raster_types::Texture;
use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams}; use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
use vector_types::vector::style::RenderMode; use vector_types::vector::style::RenderMode;
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache}; use wgpu_executor::{WgpuExecutor, WgpuPipeline, WgpuPipelineCache};
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub async fn render_pixel_preview<'a>( pub fn render_pixel_preview(
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync, ctx: impl Ctx + ExtractAll + DeriveCtx,
#[scope(pixel_preview_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache, #[scope(pixel_preview_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
data: impl Node<Context<'_>, Output = RenderOutput> + Send + Sync, data: impl Node<Context<'_>, Output = RenderOutput>,
) -> RenderOutput { ) -> Result<RenderOutput, Interrupt> {
let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()).cloned() else { 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"); log::error!("invalid render params for pixel preview");
let context = OwnedContextImpl::from(ctx).into_context(); return data.eval(&ctx.derived());
return data.eval(context).await;
}; };
let physical_scale = render_params.scale; let physical_scale = render_params.scale;
@@ -24,8 +24,7 @@ pub async fn render_pixel_preview<'a>(
let viewport_zoom = footprint.scale_magnitudes().x; 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. { 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(&ctx.derived());
return data.eval(context).await;
} }
let physical_resolution = footprint.resolution; let physical_resolution = footprint.resolution;
@@ -51,27 +50,25 @@ pub async fn render_pixel_preview<'a>(
quality: footprint.quality, quality: footprint.quality,
}; };
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(upstream_footprint).with_vararg(Box::new(render_params)).into_context(); let scoped = ctx.push_vararg(&render_params);
let mut result = data.eval(new_ctx).await; 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 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 transform = DAffine2::from_translation(-upstream_min) * logical_transform.inverse() * DAffine2::from_scale(logical_resolution);
let resampled = pipeline let resampled = pipeline.run::<PixelPreview>(&PixelPreviewArgs {
.run::<PixelPreview>(&PixelPreviewArgs { source: source_texture.as_ref(),
source: source_texture.as_ref(), transform: &transform,
transform: &transform, size: physical_resolution,
size: physical_resolution, });
})
.await;
result.data = RenderOutputType::Texture(resampled); result.data = RenderOutputType::Texture(resampled);
result.metadata.apply_transform(footprint.transform * DAffine2::from_translation(upstream_min)); result.metadata.apply_transform(footprint.transform * DAffine2::from_translation(upstream_min));
result Ok(result)
} }
#[node_macro::node(category(""), inject_scope)] #[node_macro::node(category(""), inject_scope)]
@@ -97,7 +94,7 @@ pub struct PixelPreviewArgs<'a> {
size: UVec2, size: UVec2,
} }
impl AsyncWgpuPipeline for PixelPreview { impl WgpuPipeline for PixelPreview {
type Args<'a> = PixelPreviewArgs<'a>; type Args<'a> = PixelPreviewArgs<'a>;
type Out = Texture; type Out = Texture;
@@ -169,11 +166,11 @@ impl AsyncWgpuPipeline for PixelPreview {
PixelPreview { pipeline, bind_group_layout } 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 context = &executor.context();
let &PixelPreviewArgs { source, transform, size } = args; 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 source_view = source.create_view(&wgpu::TextureViewDescriptor::default());
let output_view = output.create_view(&wgpu::TextureViewDescriptor::default()); let output_view = output.create_view(&wgpu::TextureViewDescriptor::default());