Shaders: graster-nodes no-std fixups (#2984)
Some checks are pending
Editor: Dev & CI / build (push) Waiting to run
Editor: Dev & CI / cargo-deny (push) Waiting to run

* wgpu-executor: remove useless features

* wgpu-executor: cleanup copy-pasted code

* gcore-shaders: no_std fixups
This commit is contained in:
Firestar99 2025-08-06 16:10:08 +02:00 committed by GitHub
parent 1742e6000a
commit caa228a1ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 84 additions and 129 deletions

View file

@ -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" }

View file

@ -7,7 +7,7 @@ authors = ["Graphite Authors <contact@graphite.rs>"]
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 }

View file

@ -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"),

View file

@ -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<Color> 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<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: core::hash::Hasher>(&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<u8> {
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)
}

View file

@ -14,9 +14,9 @@ pub trait Linear {
fn lerp(self, other: Self, value: Self) -> Self
where
Self: Sized + Copy,
Self: std::ops::Sub<Self, Output = Self>,
Self: std::ops::Mul<Self, Output = Self>,
Self: std::ops::Add<Self, Output = Self>,
Self: core::ops::Sub<Self, Output = Self>,
Self: core::ops::Mul<Self, Output = Self>,
Self: core::ops::Add<Self, Output = Self>,
{
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<u8> {
bytemuck::bytes_of(self).to_vec()
}

View file

@ -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

View file

@ -1,3 +1,5 @@
#![cfg_attr(not(feature = "std"), no_std)]
pub mod blending;
pub mod choice_type;
pub mod color;

View file

@ -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;
}

View file

@ -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]

View file

@ -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<wgpu_executor::WgpuSurface>, 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)

View file

@ -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"] }

View file

@ -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,

View file

@ -42,6 +42,7 @@ pub struct Surface {
}
pub struct TargetTexture {
texture: wgpu::Texture,
view: wgpu::TextureView,
size: UVec2,
}
@ -60,7 +61,47 @@ 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
let surface_inner = &surface.surface.inner;
let surface_caps = surface_inner.get_capabilities(&self.context.adapter);
surface_inner.configure(
&self.context.device,
&SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::STORAGE_BINDING,
format: VELLO_SURFACE_FORMAT,
width: size.x,
height: size.y,
present_mode: surface_caps.present_modes[0],
alpha_mode: wgpu::CompositeAlphaMode::Opaque,
view_formats: vec![],
desired_maximum_frame_latency: 2,
},
);
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,
&guard.as_ref().unwrap().view,
&surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()),
);
self.context.queue.submit([encoder.finish()]);
surface_texture.present();
Ok(())
}
pub async fn render_vello_scene_to_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color) -> Result<wgpu::Texture> {
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<TargetTexture>) -> Result<()> {
let target_texture = if let Some(target_texture) = output
&& target_texture.size == size
{
target_texture
@ -80,31 +121,13 @@ impl WgpuExecutor {
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
*guard = Some(TargetTexture { size, view });
guard.as_ref().unwrap()
*output = Some(TargetTexture { texture, view, size });
output.as_mut().unwrap()
};
let surface_inner = &surface.surface.inner;
let surface_caps = surface_inner.get_capabilities(&self.context.adapter);
surface_inner.configure(
&self.context.device,
&SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::STORAGE_BINDING,
format: VELLO_SURFACE_FORMAT,
width: size.x,
height: size.y,
present_mode: surface_caps.present_modes[0],
alpha_mode: wgpu::CompositeAlphaMode::Opaque,
view_formats: vec![],
desired_maximum_frame_latency: 2,
},
);
let [r, g, b, _] = background.to_rgba8_srgb();
let [r, g, b, a] = 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),
base_color: vello::peniko::Color::from_rgba8(r, g, b, a),
width: size.x,
height: size.y,
antialiasing_method: AaConfig::Msaa16,
@ -126,66 +149,9 @@ impl WgpuExecutor {
renderer.override_image(image, None);
}
}
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,
&surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()),
);
self.context.queue.submit([encoder.finish()]);
surface_texture.present();
Ok(())
}
pub async fn render_vello_scene_to_texture(&self, scene: &Scene, size: UVec2, context: &RenderContext, background: Color) -> Result<wgpu::Texture> {
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 [r, g, b, a] = background.to_rgba8_srgb();
let render_params = RenderParams {
base_color: vello::peniko::Color::from_rgba8(r, g, b, a),
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, &view, &render_params)?;
for (image, _) in context.resource_overrides.iter() {
renderer.override_image(image, None);
}
}
Ok(texture)
}
#[cfg(target_family = "wasm")]
pub fn create_surface(&self, canvas: graphene_application_io::WasmSurfaceHandle) -> Result<SurfaceHandle<Surface>> {
let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?;
@ -212,26 +178,9 @@ impl WgpuExecutor {
impl WgpuExecutor {
pub async fn new() -> Option<Self> {
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<Self> {
let vello_renderer = Renderer::new(
&context.device,