mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-31 02:07:21 +00:00
Shaders: graster-nodes
no-std fixups (#2984)
* wgpu-executor: remove useless features * wgpu-executor: cleanup copy-pasted code * gcore-shaders: no_std fixups
This commit is contained in:
parent
1742e6000a
commit
caa228a1ec
13 changed files with 84 additions and 129 deletions
|
@ -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" }
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod blending;
|
||||
pub mod choice_type;
|
||||
pub mod color;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue