mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Make the GPU render and dispatch pipelines synchronous
This commit is contained in:
@@ -10,6 +10,8 @@ use core_types::math::bbox::Bbox;
|
||||
use core_types::transform::Footprint;
|
||||
#[cfg(target_family = "wasm")]
|
||||
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};
|
||||
pub use graph_craft::application_io::resource::{Resource, ResourceHash};
|
||||
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(""))]
|
||||
pub async fn resource<'a>(_: 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<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a 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"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ 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(""))]
|
||||
fn render_background<'a>(
|
||||
@@ -35,14 +35,12 @@ fn render_background<'a>(
|
||||
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)
|
||||
}
|
||||
@@ -148,7 +146,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 +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 {
|
||||
foreground,
|
||||
backgrounds,
|
||||
@@ -340,7 +338,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;
|
||||
|
||||
@@ -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::{Context, Ctx, DeriveCtx, ExtractAll};
|
||||
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use graph_craft::application_io::PlatformEditorApi;
|
||||
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(""))]
|
||||
pub async fn render_output_cache<'a>(
|
||||
ctx: impl Ctx + ExtractAll + CloneVarArgs + ExtractRealTime + ExtractAnimationTime + ExtractPointerPosition + Sync,
|
||||
pub fn render_output_cache<'a>(
|
||||
ctx: impl Ctx + ExtractAll + DeriveCtx,
|
||||
#[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<'_>, Output = RenderOutput> + Send + Sync,
|
||||
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 +374,38 @@ pub async fn render_output_cache<'a>(
|
||||
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(®ion_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 +414,18 @@ pub async fn render_output_cache<'a>(
|
||||
|
||||
// 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);
|
||||
|
||||
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<'_>) -> 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(
|
||||
|
||||
@@ -131,7 +131,6 @@ fn render<'a>(
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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::{Context, 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>(
|
||||
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<'_>, 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>(
|
||||
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,27 +50,25 @@ pub async fn render_pixel_preview<'a>(
|
||||
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)]
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user