diff --git a/Cargo.toml b/Cargo.toml index 89fee5170c..09ad47689d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,7 +152,7 @@ kurbo = { version = "0.11.0", features = ["serde"] } petgraph = { version = "0.7.1", default-features = false, features = [ "graphmap", ] } -half = { version = "2.4.1", default-features = false, features = ["bytemuck", "serde"] } +half = { version = "2.4.1", default-features = false, features = ["bytemuck"] } tinyvec = { version = "1", features = ["std"] } criterion = { version = "0.5", features = ["html_reports"] } iai-callgrind = { version = "0.12.3" } diff --git a/node-graph/gcore-shaders/Cargo.toml b/node-graph/gcore-shaders/Cargo.toml index 7e6b57444d..0fefb577af 100644 --- a/node-graph/gcore-shaders/Cargo.toml +++ b/node-graph/gcore-shaders/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Graphite Authors "] license = "MIT OR Apache-2.0" [features] -std = ["dep:dyn-any", "dep:serde", "dep:specta", "dep:log"] +std = ["dep:dyn-any", "dep:serde", "dep:specta", "dep:log", "half/std", "half/serde"] [dependencies] # Local std dependencies @@ -16,7 +16,7 @@ dyn-any = { workspace = true, optional = true } # Workspace dependencies bytemuck = { workspace = true } glam = { version = "0.29", default-features = false, features = ["nostd-libm", "scalar-math"] } -half = { workspace = true } +half = { workspace = true, default-features = false } num-derive = { workspace = true } num-traits = { workspace = true } diff --git a/node-graph/gcore-shaders/src/blending.rs b/node-graph/gcore-shaders/src/blending.rs index 844185514e..0d57bd0aad 100644 --- a/node-graph/gcore-shaders/src/blending.rs +++ b/node-graph/gcore-shaders/src/blending.rs @@ -1,5 +1,7 @@ use core::fmt::Display; use core::hash::{Hash, Hasher}; +#[cfg(target_arch = "spirv")] +use num_traits::float::Float; #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] @@ -24,7 +26,7 @@ impl Hash for AlphaBlending { } } impl Display for AlphaBlending { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let round = |x: f32| (x * 1e3).round() / 1e3; write!( f, @@ -203,7 +205,7 @@ impl BlendMode { } impl Display for BlendMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { // Normal group BlendMode::Normal => write!(f, "Normal"), diff --git a/node-graph/gcore-shaders/src/color/color.rs b/node-graph/gcore-shaders/src/color/color.rs index 91fa57dcbe..b745d56547 100644 --- a/node-graph/gcore-shaders/src/color/color.rs +++ b/node-graph/gcore-shaders/src/color/color.rs @@ -1,15 +1,17 @@ use super::color_traits::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGB, RGBMut, Rec709Primaries, SRGB}; use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float}; use bytemuck::{Pod, Zeroable}; +use core::fmt::Debug; use core::hash::Hash; use half::f16; #[cfg(target_arch = "spirv")] -use spirv_std::num_traits::Euclid; +use num_traits::Euclid; #[cfg(target_arch = "spirv")] -use spirv_std::num_traits::float::Float; +use num_traits::float::Float; #[repr(C)] -#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)] +#[derive(Default, Clone, Copy, PartialEq, Pod, Zeroable)] +#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] pub struct RGBA16F { red: f16, @@ -18,6 +20,14 @@ pub struct RGBA16F { alpha: f16, } +/// hack around half still masking out impl Debug for f16 on spirv +#[cfg(target_arch = "spirv")] +impl core::fmt::Debug for RGBA16F { + fn fmt(&self, _f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + Ok(()) + } +} + impl From for RGBA16F { #[inline(always)] fn from(c: Color) -> Self { @@ -215,7 +225,7 @@ pub struct Color { #[allow(clippy::derived_hash_with_manual_eq)] impl Hash for Color { - fn hash(&self, state: &mut H) { + fn hash(&self, state: &mut H) { self.red.to_bits().hash(state); self.green.to_bits().hash(state); self.blue.to_bits().hash(state); @@ -256,7 +266,7 @@ impl AlphaMut for Color { } impl Pixel for Color { - #[cfg(not(target_arch = "spirv"))] + #[cfg(feature = "std")] fn to_bytes(&self) -> Vec { self.to_rgba8_srgb().to_vec() } @@ -793,6 +803,7 @@ impl Color { /// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61); // Premultiplied alpha /// assert_eq!("3240a261", color.to_rgba_hex_srgb()); // Equivalent hex incorporating premultiplied alpha /// ``` + #[cfg(feature = "std")] pub fn to_rgba_hex_srgb(&self) -> String { let gamma = self.to_gamma_srgb(); format!( @@ -810,6 +821,7 @@ impl Color { /// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61); // Premultiplied alpha /// assert_eq!("3240a2", color.to_rgb_hex_srgb()); // Equivalent hex incorporating premultiplied alpha /// ``` + #[cfg(feature = "std")] pub fn to_rgb_hex_srgb(&self) -> String { self.to_gamma_srgb().to_rgb_hex_srgb_from_gamma() } @@ -820,6 +832,7 @@ impl Color { /// let color = Color::from_rgba8_srgb(0x52, 0x67, 0xFA, 0x61); // Premultiplied alpha /// assert_eq!("3240a2", color.to_rgb_hex_srgb()); // Equivalent hex incorporating premultiplied alpha /// ``` + #[cfg(feature = "std")] pub fn to_rgb_hex_srgb_from_gamma(&self) -> String { format!("{:02x?}{:02x?}{:02x?}", (self.r() * 255.) as u8, (self.g() * 255.) as u8, (self.b() * 255.) as u8) } diff --git a/node-graph/gcore-shaders/src/color/color_traits.rs b/node-graph/gcore-shaders/src/color/color_traits.rs index 3f0e69b4ff..08e6895b37 100644 --- a/node-graph/gcore-shaders/src/color/color_traits.rs +++ b/node-graph/gcore-shaders/src/color/color_traits.rs @@ -14,9 +14,9 @@ pub trait Linear { fn lerp(self, other: Self, value: Self) -> Self where Self: Sized + Copy, - Self: std::ops::Sub, - Self: std::ops::Mul, - Self: std::ops::Add, + Self: core::ops::Sub, + Self: core::ops::Mul, + Self: core::ops::Add, { self + (other - self) * value } @@ -97,7 +97,7 @@ pub trait SRGB: Rec709Primaries {} // TODO: Come up with a better name for this trait pub trait Pixel: Clone + Pod + Zeroable + Default { - #[cfg(not(target_arch = "spirv"))] + #[cfg(feature = "std")] fn to_bytes(&self) -> Vec { bytemuck::bytes_of(self).to_vec() } diff --git a/node-graph/gcore-shaders/src/color/discrete_srgb.rs b/node-graph/gcore-shaders/src/color/discrete_srgb.rs index 28981e0976..13a06e30ab 100644 --- a/node-graph/gcore-shaders/src/color/discrete_srgb.rs +++ b/node-graph/gcore-shaders/src/color/discrete_srgb.rs @@ -69,7 +69,7 @@ pub fn float_to_srgb_u8(mut f: f32) -> u8 { // We clamped f to [0, 1], and the integer representations // of the positive finite non-NaN floats are monotonic. // This makes the later LUT lookup panicless. - unsafe { std::hint::unreachable_unchecked() } + unsafe { core::hint::unreachable_unchecked() } } // Compute a piecewise linear interpolation that is always diff --git a/node-graph/gcore-shaders/src/lib.rs b/node-graph/gcore-shaders/src/lib.rs index 1933fedeb2..9b310ea9d7 100644 --- a/node-graph/gcore-shaders/src/lib.rs +++ b/node-graph/gcore-shaders/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "std"), no_std)] + pub mod blending; pub mod choice_type; pub mod color; diff --git a/node-graph/gcore-shaders/src/registry.rs b/node-graph/gcore-shaders/src/registry.rs index 84b4877ffd..69ef59753c 100644 --- a/node-graph/gcore-shaders/src/registry.rs +++ b/node-graph/gcore-shaders/src/registry.rs @@ -20,5 +20,6 @@ pub mod types { /// DVec2 with px unit pub type PixelSize = glam::DVec2; /// String with one or more than one line + #[cfg(feature = "std")] pub type TextArea = String; } diff --git a/node-graph/graphene-cli/Cargo.toml b/node-graph/graphene-cli/Cargo.toml index 84bf96e7cd..d8402e133e 100644 --- a/node-graph/graphene-cli/Cargo.toml +++ b/node-graph/graphene-cli/Cargo.toml @@ -10,8 +10,6 @@ license = "MIT OR Apache-2.0" default = ["wgpu"] wgpu = ["wgpu-executor", "gpu", "graphene-std/wgpu"] wayland = ["graphene-std/wayland"] -profiling = ["wgpu-executor/profiling"] -passthrough = ["wgpu-executor/passthrough"] gpu = ["interpreted-executor/gpu", "graphene-std/gpu", "wgpu-executor"] [dependencies] diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 37de4fc7aa..a4b58ddda5 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -16,7 +16,6 @@ use graphene_svg_renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRende #[cfg(target_family = "wasm")] use base64::Engine; -#[cfg(target_family = "wasm")] use glam::DAffine2; use std::sync::Arc; #[cfg(target_family = "wasm")] @@ -169,7 +168,8 @@ fn render_svg(data: impl Render, mut render: SvgRender, render_params: RenderPar async fn render_canvas(render_config: RenderConfig, data: impl Render, editor: &WasmEditorApi, surface_handle: Option, render_params: RenderParams) -> RenderOutputType { use graphene_application_io::{ImageTexture, SurfaceFrame}; - let footprint = render_config.viewport; + let mut footprint = render_config.viewport; + footprint.resolution = footprint.resolution.max(glam::UVec2::splat(1)); let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else { unreachable!("Attempted to render with Vello when no GPU executor is available"); }; @@ -196,7 +196,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl Render, editor: & let frame = SurfaceFrame { surface_id: surface_handle.window_id, resolution: render_config.viewport.resolution, - transform: glam::DAffine2::IDENTITY, + transform: DAffine2::IDENTITY, }; RenderOutputType::CanvasFrame(frame) diff --git a/node-graph/wgpu-executor/Cargo.toml b/node-graph/wgpu-executor/Cargo.toml index bed1adbd6d..388b8a7eec 100644 --- a/node-graph/wgpu-executor/Cargo.toml +++ b/node-graph/wgpu-executor/Cargo.toml @@ -4,11 +4,6 @@ version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" -[features] -default = [] -profiling = [] -passthrough = [] - [dependencies] # Local dependencies graphene-core = { workspace = true, features = ["wgpu"] } diff --git a/node-graph/wgpu-executor/src/context.rs b/node-graph/wgpu-executor/src/context.rs index f5408f36c2..06da16e0ac 100644 --- a/node-graph/wgpu-executor/src/context.rs +++ b/node-graph/wgpu-executor/src/context.rs @@ -32,15 +32,10 @@ impl Context { let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: None, - // #[cfg(not(feature = "passthrough"))] #[cfg(target_family = "wasm")] required_features: wgpu::Features::empty(), #[cfg(not(target_family = "wasm"))] required_features: wgpu::Features::PUSH_CONSTANTS, - // Currently disabled because not all backend support passthrough. - // TODO: reenable only when vulkan adapter is available - // #[cfg(feature = "passthrough")] - // required_features: wgpu::Features::SPIRV_SHADER_PASSTHROUGH, required_limits, memory_hints: Default::default(), trace: wgpu::Trace::Off, diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index fbae5a77f1..920a002c4e 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -42,6 +42,7 @@ pub struct Surface { } pub struct TargetTexture { + texture: wgpu::Texture, view: wgpu::TextureView, size: UVec2, } @@ -60,29 +61,6 @@ const VELLO_SURFACE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unor impl WgpuExecutor { pub async fn render_vello_scene(&self, scene: &Scene, surface: &WgpuSurface, size: UVec2, context: &RenderContext, background: Color) -> Result<()> { let mut guard = surface.surface.target_texture.lock().await; - let target_texture = if let Some(target_texture) = &*guard - && target_texture.size == size - { - target_texture - } else { - let texture = self.context.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: size.x, - height: size.y, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, - format: VELLO_SURFACE_FORMAT, - view_formats: &[], - }); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - *guard = Some(TargetTexture { size, view }); - guard.as_ref().unwrap() - }; let surface_inner = &surface.surface.inner; let surface_caps = surface_inner.get_capabilities(&self.context.adapter); @@ -100,39 +78,14 @@ impl WgpuExecutor { }, ); - let [r, g, b, _] = background.to_rgba8_srgb(); - let render_params = RenderParams { - // We are using an explicit opaque color here to eliminate the alpha premultiplication step - // which would be required to support a transparent webgpu canvas - base_color: vello::peniko::Color::from_rgba8(r, g, b, 0xff), - width: size.x, - height: size.y, - antialiasing_method: AaConfig::Msaa16, - }; - - { - let mut renderer = self.vello_renderer.lock().await; - for (image, texture) in context.resource_overrides.iter() { - let texture_view = wgpu::TexelCopyTextureInfoBase { - texture: texture.clone(), - mip_level: 0, - origin: Origin3d::ZERO, - aspect: TextureAspect::All, - }; - renderer.override_image(image, Some(texture_view)); - } - renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?; - for (image, _) in context.resource_overrides.iter() { - renderer.override_image(image, None); - } - } + self.render_vello_scene_to_target_texture(scene, size, context, background, &mut guard).await?; let surface_texture = surface_inner.get_current_texture()?; let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Surface Blit") }); surface.surface.blitter.copy( &self.context.device, &mut encoder, - &target_texture.view, + &guard.as_ref().unwrap().view, &surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()), ); self.context.queue.submit([encoder.finish()]); @@ -142,21 +95,35 @@ impl WgpuExecutor { } pub async fn render_vello_scene_to_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color) -> Result { - let texture = self.context.device.create_texture(&wgpu::TextureDescriptor { - label: None, - size: wgpu::Extent3d { - width: size.x.max(1), - height: size.y.max(1), - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, - format: VELLO_SURFACE_FORMAT, - view_formats: &[], - }); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let mut output = None; + self.render_vello_scene_to_target_texture(scene, size, context, background, &mut output).await?; + Ok(output.unwrap().texture) + } + + async fn render_vello_scene_to_target_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color, output: &mut Option) -> Result<()> { + let target_texture = if let Some(target_texture) = output + && target_texture.size == size + { + target_texture + } else { + let texture = self.context.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: size.x, + height: size.y, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, + format: VELLO_SURFACE_FORMAT, + view_formats: &[], + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + *output = Some(TargetTexture { texture, view, size }); + output.as_mut().unwrap() + }; let [r, g, b, a] = background.to_rgba8_srgb(); let render_params = RenderParams { @@ -177,13 +144,12 @@ impl WgpuExecutor { }; renderer.override_image(image, Some(texture_view)); } - renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &view, &render_params)?; + renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?; for (image, _) in context.resource_overrides.iter() { renderer.override_image(image, None); } } - - Ok(texture) + Ok(()) } #[cfg(target_family = "wasm")] @@ -212,26 +178,9 @@ impl WgpuExecutor { impl WgpuExecutor { pub async fn new() -> Option { - let context = Context::new().await?; - - let vello_renderer = Renderer::new( - &context.device, - RendererOptions { - // surface_format: Some(wgpu::TextureFormat::Rgba8Unorm), - pipeline_cache: None, - use_cpu: false, - antialiasing_support: AaSupport::all(), - num_init_threads: std::num::NonZeroUsize::new(1), - }, - ) - .map_err(|e| anyhow::anyhow!("Failed to create Vello renderer: {:?}", e)) - .ok()?; - - Some(Self { - context, - vello_renderer: vello_renderer.into(), - }) + Self::with_context(Context::new().await?) } + pub fn with_context(context: Context) -> Option { let vello_renderer = Renderer::new( &context.device,