diff --git a/.nix/flake.nix b/.nix/flake.nix index ba9bd62714..eac77dd2c8 100644 --- a/.nix/flake.nix +++ b/.nix/flake.nix @@ -34,11 +34,52 @@ inherit system overlays; }; - rustc-wasm = pkgs.rust-bin.stable.latest.default.override { + rustExtensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ]; + rust = pkgs.rust-bin.stable.latest.default.override { targets = [ "wasm32-unknown-unknown" ]; - extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ]; + extensions = rustExtensions; }; + rustGPUToolchainPkg = pkgs.rust-bin.nightly."2025-06-23".default.override { + extensions = rustExtensions ++ [ "rustc-dev" "llvm-tools" ]; + }; + rustGPUToolchainRustPlatform = pkgs.makeRustPlatform { + cargo = rustGPUToolchainPkg; + rustc = rustGPUToolchainPkg; + }; + rustc_codegen_spirv = rustGPUToolchainRustPlatform.buildRustPackage (finalAttrs: { + pname = "rustc_codegen_spirv"; + version = "0-unstable-2025-08-04"; + src = pkgs.fetchFromGitHub { + owner = "Rust-GPU"; + repo = "rust-gpu"; + rev = "df1628a032d22c864397417c2871b74d602af986"; + hash = "sha256-AFt3Nc+NqK8DxNUhDBcOUmk3XDVcoToVeFIMYNszdbY="; + }; + cargoHash = "sha256-en3BYJWQabH064xeAwYQrvcr6EuWg/QjvsG+Jd6HHCk"; + cargoBuildFlags = [ "-p" "rustc_codegen_spirv" "--features=use-installed-tools" "--no-default-features" ]; + doCheck = false; + }); + + # Wrapper script for running rust commands with the rust toolchain used by rust-gpu. + # For example `rust-gpu cargo --version` or `rust-gpu rustc --version`. + execWithRustGPUEnvironment = pkgs.writeShellScriptBin "rust-gpu" '' + #!${pkgs.lib.getExe pkgs.bash} + + filtered_args=() + for arg in "$@"; do + case "$arg" in + +nightly|+nightly-*) ;; + *) filtered_args+=("$arg") ;; + esac + done + + export PATH="${pkgs.lib.makeBinPath [ rustGPUToolchainPkg pkgs.spirv-tools ]}:$PATH" + export RUSTC_CODEGEN_SPIRV_PATH="${rustc_codegen_spirv}/lib/librustc_codegen_spirv.so" + + exec ${"\${filtered_args[@]}"} + ''; + libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: { version = "139.0.17"; gitRevision = "6c347eb"; @@ -51,7 +92,6 @@ strip $out/lib/* ''; }); - libcefPath = pkgs.runCommand "libcef-path" {} '' mkdir -p $out @@ -85,7 +125,7 @@ # Development tools that don't need to be in LD_LIBRARY_PATH buildTools = [ - rustc-wasm + rust pkgs.nodejs pkgs.nodePackages.npm pkgs.binaryen @@ -97,6 +137,8 @@ # Linker pkgs.mold + + execWithRustGPUEnvironment ]; # Development tools that don't need to be in LD_LIBRARY_PATH devTools = with pkgs; [ diff --git a/Cargo.lock b/Cargo.lock index e38f273a25..f23a90db8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1777,6 +1777,7 @@ version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" dependencies = [ + "bytemuck", "libm", "serde", ] @@ -4128,6 +4129,7 @@ dependencies = [ "graph-craft", "graphene-std", "interpreted-executor", + "log", ] [[package]] diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index bdc63e4a11..17a2a9bcfd 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -147,9 +147,9 @@ pub(crate) fn property_from_type( Type::Concrete(concrete_type) => { match concrete_type.alias.as_ref().map(|x| x.as_ref()) { // Aliased types (ambiguous values) - Some("Percentage") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(), - Some("SignedPercentage") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(), - Some("Angle") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(), + Some("Percentage") | Some("PercentageF32") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(), + Some("SignedPercentage") | Some("SignedPercentageF32") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(), + Some("Angle") | Some("AngleF32") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(), Some("Multiplier") => number_widget(default_info, number_input.unit(unit.unwrap_or("x"))).into(), Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(), Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(), @@ -166,13 +166,15 @@ pub(crate) fn property_from_type( // =============== // PRIMITIVE TYPES // =============== - Some(x) if x == TypeId::of::() => number_widget(default_info, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY))).into(), + Some(x) if x == TypeId::of::() || x == TypeId::of::() => number_widget(default_info, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY))).into(), Some(x) if x == TypeId::of::() => number_widget(default_info, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX)))).into(), Some(x) if x == TypeId::of::() => number_widget(default_info, number_input.int().min(min(0.))).into(), Some(x) if x == TypeId::of::() => bool_widget(default_info, CheckboxInput::default()).into(), Some(x) if x == TypeId::of::() => text_widget(default_info).into(), Some(x) if x == TypeId::of::() => vec2_widget(default_info, "X", "Y", "", None, false), Some(x) if x == TypeId::of::() => transform_widget(default_info, &mut extra_widgets), + Some(x) if x == TypeId::of::() => color_widget(default_info, ColorInput::default()), + Some(x) if x == TypeId::of::>() => color_widget(default_info, ColorInput::default()), // ========================== // PRIMITIVE COLLECTION TYPES // ========================== @@ -805,6 +807,14 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: .on_commit(commit_value) .widget_holder(), ]), + Some(&TaggedValue::F32(x)) => widgets.extend_from_slice(&[ + Separator::new(SeparatorType::Unrelated).widget_holder(), + number_props + .value(Some(x as f64)) + .on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index)) + .on_commit(commit_value) + .widget_holder(), + ]), Some(&TaggedValue::U32(x)) => widgets.extend_from_slice(&[ Separator::new(SeparatorType::Unrelated).widget_holder(), number_props @@ -851,6 +861,15 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: .on_commit(commit_value) .widget_holder(), ]), + Some(&TaggedValue::Vec2(vec2)) => widgets.extend_from_slice(&[ + Separator::new(SeparatorType::Unrelated).widget_holder(), + number_props + // We use an arbitrary `y` instead of an arbitrary `x` here because the "Grid" node's "Spacing" value's height should be used from rectangular mode when transferred to "Y Spacing" in isometric mode + .value(Some(vec2.y as f64)) + .on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index)) + .on_commit(commit_value) + .widget_holder(), + ]), _ => {} } @@ -909,6 +928,22 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: // Add the color input match &**tagged_value { + TaggedValue::ColorNotInTable(color) => widgets.push( + color_button + .value(FillChoice::Solid(*color)) + .allow_none(false) + .on_update(update_value(|input: &ColorInput| TaggedValue::ColorNotInTable(input.value.as_solid().unwrap()), node_id, index)) + .on_commit(commit_value) + .widget_holder(), + ), + TaggedValue::OptionalColorNotInTable(color) => widgets.push( + color_button + .value(color.map_or(FillChoice::None, FillChoice::Solid)) + .allow_none(true) + .on_update(update_value(|input: &ColorInput| TaggedValue::OptionalColorNotInTable(input.value.as_solid()), node_id, index)) + .on_commit(commit_value) + .widget_holder(), + ), TaggedValue::Color(color_table) => widgets.push( color_button .value(match color_table.iter().next() { @@ -948,7 +983,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: .on_commit(commit_value) .widget_holder(), ), - _ => {} + x => warn!("Colour {x:?}"), } LayoutGroup::Row { widgets } diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 8067ef2758..80308b2920 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -23,6 +23,7 @@ impl FrontendGraphDataType { match TaggedValue::from_type_or_none(input) { TaggedValue::U32(_) | TaggedValue::U64(_) + | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Array4(_) diff --git a/node-graph/gbrush/src/brush.rs b/node-graph/gbrush/src/brush.rs index e832335f8a..b1837cfb13 100644 --- a/node-graph/gbrush/src/brush.rs +++ b/node-graph/gbrush/src/brush.rs @@ -137,7 +137,7 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster { } pub fn blend_with_mode(background: TableRow>, foreground: TableRow>, blend_mode: BlendMode, opacity: f64) -> TableRow> { - let opacity = opacity / 100.; + let opacity = opacity as f32 / 100.; match std::hint::black_box(blend_mode) { // Normal group BlendMode::Normal => blend_image_closure(foreground, background, |a, b| blend_colors(a, b, BlendMode::Normal, opacity)), diff --git a/node-graph/gcore-shaders/src/registry.rs b/node-graph/gcore-shaders/src/registry.rs index 69ef59753c..d1957c8d29 100644 --- a/node-graph/gcore-shaders/src/registry.rs +++ b/node-graph/gcore-shaders/src/registry.rs @@ -1,10 +1,16 @@ pub mod types { /// 0% - 100% pub type Percentage = f64; + /// 0% - 100% + pub type PercentageF32 = f32; /// -100% - 100% pub type SignedPercentage = f64; + /// -100% - 100% + pub type SignedPercentageF32 = f32; /// -180° - 180° pub type Angle = f64; + /// -180° - 180° + pub type AngleF32 = f32; /// Ends in the unit of x pub type Multiplier = f64; /// Non-negative integer with px unit diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index e5d3844a44..4c2b535687 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -3,6 +3,7 @@ use crate::proto::{Any as DAny, FutureAny}; use crate::wasm_application_io::WasmEditorApi; use dyn_any::DynAny; pub use dyn_any::StaticType; +use glam::{Affine2, Vec2}; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; use graphene_application_io::{ImageTexture, SurfaceFrame}; use graphene_brush::brush_cache::BrushCache; @@ -163,13 +164,15 @@ tagged_value! { // =============== // PRIMITIVE TYPES // =============== - #[serde(alias = "F32")] // TODO: Eventually remove this alias document upgrade code + F32(f32), F64(f64), U32(u32), U64(u64), Bool(bool), String(String), OptionalF64(Option), + ColorNotInTable(Color), + OptionalColorNotInTable(Option), // ======================== // LISTS OF PRIMITIVE TYPES // ======================== @@ -201,6 +204,8 @@ tagged_value! { // ============ // STRUCT TYPES // ============ + Vec2(Vec2), + Affine2(Affine2), #[serde(alias = "IVec2", alias = "UVec2")] DVec2(DVec2), DAffine2(DAffine2), @@ -257,6 +262,7 @@ impl TaggedValue { TaggedValue::String(x) => format!("\"{x}\""), TaggedValue::U32(x) => x.to_string() + "_u32", TaggedValue::U64(x) => x.to_string() + "_u64", + TaggedValue::F32(x) => x.to_string() + "_f32", TaggedValue::F64(x) => x.to_string() + "_f64", TaggedValue::Bool(x) => x.to_string(), TaggedValue::BlendMode(x) => "BlendMode::".to_string() + &x.to_string(), @@ -348,11 +354,14 @@ impl TaggedValue { x if x == TypeId::of::<()>() => TaggedValue::None, x if x == TypeId::of::() => TaggedValue::String(string.into()), x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::F64).ok()?, + x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::F32).ok()?, x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::U64).ok()?, x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::U32).ok()?, x if x == TypeId::of::() => to_dvec2(string).map(TaggedValue::DVec2)?, x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, x if x == TypeId::of::>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, + x if x == TypeId::of::() => to_color(string).map(|color| TaggedValue::ColorNotInTable(color))?, + x if x == TypeId::of::>() => TaggedValue::ColorNotInTable(to_color(string)?), x if x == TypeId::of::() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, x if x == TypeId::of::() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, _ => return None, @@ -378,6 +387,7 @@ impl Display for TaggedValue { TaggedValue::String(x) => f.write_str(x), TaggedValue::U32(x) => f.write_fmt(format_args!("{x}")), TaggedValue::U64(x) => f.write_fmt(format_args!("{x}")), + TaggedValue::F32(x) => f.write_fmt(format_args!("{x}")), TaggedValue::F64(x) => f.write_fmt(format_args!("{x}")), TaggedValue::Bool(x) => f.write_fmt(format_args!("{x}")), _ => panic!("Cannot convert to string"), @@ -463,6 +473,21 @@ mod fake_hash { self.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) } } + impl FakeHash for f32 { + fn hash(&self, state: &mut H) { + self.to_bits().hash(state) + } + } + impl FakeHash for Vec2 { + fn hash(&self, state: &mut H) { + self.to_array().iter().for_each(|x| x.to_bits().hash(state)) + } + } + impl FakeHash for Affine2 { + fn hash(&self, state: &mut H) { + self.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)) + } + } impl FakeHash for Option { fn hash(&self, state: &mut H) { if let Some(x) = self { @@ -491,3 +516,8 @@ mod fake_hash { } } } + +#[test] +fn can_construct_color() { + assert_eq!(TaggedValue::from_type(&concrete!(Color)).unwrap(), TaggedValue::ColorNotInTable(Color::default())); +} diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 8b9ca44c08..9fefb03572 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -805,9 +805,11 @@ mod test { construction_network.generate_stable_node_ids(); assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value"); let ids: Vec<_> = construction_network.nodes.iter().map(|(id, _)| *id).collect(); + + // If this assert fails: These NodeIds seem to be changing when you modify TaggedValue, just update them. assert_eq!( ids, - vec![NodeId(13743208144182721472), NodeId(4607569396187877965), NodeId(16950305885390329527), NodeId(15151181027373658932)] + vec![NodeId(2791689253855410677), NodeId(11246167042277902310), NodeId(1014827049498980779), NodeId(4864562752646903491)] ); } diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 18a9a22ba8..1274ef5fe4 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -11,7 +11,7 @@ use graphene_core::raster_types::{CPU, Raster}; use graphene_core::table::Table; use graphene_core_shaders::color::Color; use graphene_core_shaders::context::Ctx; -use graphene_core_shaders::registry::types::{Angle, Percentage, SignedPercentage}; +use graphene_core_shaders::registry::types::{AngleF32, PercentageF32, SignedPercentageF32}; #[cfg(not(feature = "std"))] use num_traits::float::Float; @@ -81,11 +81,11 @@ fn gamma_correction>( #[default(2.2)] #[range((0.01, 10.))] #[hard_min(0.0001)] - gamma: f64, + gamma: f32, inverse: bool, ) -> T { let exponent = if inverse { 1. / gamma } else { gamma }; - input.adjust(|color| color.gamma(exponent as f32)); + input.adjust(|color| color.gamma(exponent)); input } @@ -149,14 +149,14 @@ fn brightness_contrast>( GradientStops, )] mut input: T, - brightness: SignedPercentage, - contrast: SignedPercentage, + brightness: SignedPercentageF32, + contrast: SignedPercentageF32, use_classic: bool, ) -> T { if use_classic { - let brightness = brightness as f32 / 255.; + let brightness = brightness / 255.; - let contrast = contrast as f32 / 100.; + let contrast = contrast / 100.; let contrast = if contrast > 0. { (contrast * core::f32::consts::FRAC_PI_2 - 0.01).tan() } else { contrast }; let offset = brightness * contrast + brightness - contrast / 2.; @@ -173,7 +173,7 @@ fn brightness_contrast>( // We clamp the brightness before the two curve X-axis points `130 - brightness * 26` and `233 - brightness * 48` intersect. // Beyond the point of intersection, the cubic spline fitting becomes invalid and fails an assertion, which we need to avoid. // See the intersection of the red lines at x = 103/22*100 = 468.18182 in the graph: https://www.desmos.com/calculator/ekvz4zyd9c - let brightness = (brightness.abs() / 100.).min(103. / 22. - 0.00001) as f32; + let brightness = (brightness.abs() / 100.).min(103. / 22. - 0.00001); let brightness_curve_points = CubicSplines { x: [0., 130. - brightness * 26., 233. - brightness * 48., 255.].map(|x| x / 255.), y: [0., 130. + brightness * 51., 233. + brightness * 10., 255.].map(|x| x / 255.), @@ -198,7 +198,7 @@ fn brightness_contrast>( // Unlike with brightness, the X-axis points `64` and `192` don't intersect at any contrast value, because they are constants. // So we don't have to worry about clamping the contrast value to avoid invalid cubic spline fitting. // See the graph: https://www.desmos.com/calculator/iql9vsca56 - let contrast = contrast as f32 / 100.; + let contrast = contrast / 100.; let contrast_curve_points = CubicSplines { x: [0., 64., 192., 255.].map(|x| x / 255.), y: [0., 64. - contrast * 30., 192. + contrast * 30., 255.].map(|x| x / 255.), @@ -239,23 +239,23 @@ fn levels>( GradientStops, )] mut image: T, - #[default(0.)] shadows: Percentage, - #[default(50.)] midtones: Percentage, - #[default(100.)] highlights: Percentage, - #[default(0.)] output_minimums: Percentage, - #[default(100.)] output_maximums: Percentage, + #[default(0.)] shadows: PercentageF32, + #[default(50.)] midtones: PercentageF32, + #[default(100.)] highlights: PercentageF32, + #[default(0.)] output_minimums: PercentageF32, + #[default(100.)] output_maximums: PercentageF32, ) -> T { image.adjust(|color| { let color = color.to_gamma_srgb(); // Input Range (Range: 0-1) - let input_shadows = (shadows / 100.) as f32; - let input_midtones = (midtones / 100.) as f32; - let input_highlights = (highlights / 100.) as f32; + let input_shadows = shadows / 100.; + let input_midtones = midtones / 100.; + let input_highlights = highlights / 100.; // Output Range (Range: 0-1) - let output_minimums = (output_minimums / 100.) as f32; - let output_maximums = (output_maximums / 100.) as f32; + let output_minimums = output_minimums / 100.; + let output_maximums = output_maximums / 100.; // Midtones interpolation factor between minimums and maximums (Range: 0-1) let midtones = output_minimums + (output_maximums - output_minimums) * input_midtones; @@ -307,38 +307,35 @@ fn black_and_white>( GradientStops, )] mut image: T, - #[default(Color::BLACK)] tint: Table, + #[default(Color::BLACK)] tint: Color, #[default(40.)] #[range((-200., 300.))] - reds: Percentage, + reds: PercentageF32, #[default(60.)] #[range((-200., 300.))] - yellows: Percentage, + yellows: PercentageF32, #[default(40.)] #[range((-200., 300.))] - greens: Percentage, + greens: PercentageF32, #[default(60.)] #[range((-200., 300.))] - cyans: Percentage, + cyans: PercentageF32, #[default(20.)] #[range((-200., 300.))] - blues: Percentage, + blues: PercentageF32, #[default(80.)] #[range((-200., 300.))] - magentas: Percentage, + magentas: PercentageF32, ) -> T { - let tint: Option = tint.into(); - let tint = tint.unwrap_or(Color::BLACK); - image.adjust(|color| { let color = color.to_gamma_srgb(); - let reds = reds as f32 / 100.; - let yellows = yellows as f32 / 100.; - let greens = greens as f32 / 100.; - let cyans = cyans as f32 / 100.; - let blues = blues as f32 / 100.; - let magentas = magentas as f32 / 100.; + let reds = reds / 100.; + let yellows = yellows / 100.; + let greens = greens / 100.; + let cyans = cyans / 100.; + let blues = blues / 100.; + let magentas = magentas / 100.; let gray_base = color.r().min(color.g()).min(color.b()); @@ -383,9 +380,9 @@ fn hue_saturation>( GradientStops, )] mut input: T, - hue_shift: Angle, - saturation_shift: SignedPercentage, - lightness_shift: SignedPercentage, + hue_shift: AngleF32, + saturation_shift: SignedPercentageF32, + lightness_shift: SignedPercentageF32, ) -> T { input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -393,11 +390,11 @@ fn hue_saturation>( let [hue, saturation, lightness, alpha] = color.to_hsla(); let color = Color::from_hsla( - (hue + hue_shift as f32 / 360.) % 1., + (hue + hue_shift / 360.) % 1., // TODO: Improve the way saturation works (it's slightly off) - (saturation + saturation_shift as f32 / 100.).clamp(0., 1.), + (saturation + saturation_shift / 100.).clamp(0., 1.), // TODO: Fix the way lightness works (it's very off) - (lightness + lightness_shift as f32 / 100.).clamp(0., 1.), + (lightness + lightness_shift / 100.).clamp(0., 1.), alpha, ); @@ -441,13 +438,13 @@ fn threshold>( GradientStops, )] mut image: T, - #[default(50.)] min_luminance: Percentage, - #[default(100.)] max_luminance: Percentage, + #[default(50.)] min_luminance: PercentageF32, + #[default(100.)] max_luminance: PercentageF32, luminance_calc: LuminanceCalculation, ) -> T { image.adjust(|color| { - let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.); - let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.); + let min_luminance = Color::srgb_to_linear(min_luminance / 100.); + let max_luminance = Color::srgb_to_linear(max_luminance / 100.); let luminance = match luminance_calc { LuminanceCalculation::SRGB => color.luminance_srgb(), @@ -487,10 +484,10 @@ fn vibrance>( GradientStops, )] mut image: T, - vibrance: SignedPercentage, + vibrance: SignedPercentageF32, ) -> T { image.adjust(|color| { - let vibrance = vibrance as f32 / 100.; + let vibrance = vibrance / 100.; // Slow the effect down by half when it's negative, since artifacts begin appearing past -50%. // So this scales the 0% to -50% range to 0% to -100%. let slowed_vibrance = if vibrance >= 0. { vibrance } else { vibrance * 0.5 }; @@ -658,55 +655,55 @@ fn channel_mixer>( #[default(40.)] #[name("Red")] - monochrome_r: f64, + monochrome_r: f32, #[default(40.)] #[name("Green")] - monochrome_g: f64, + monochrome_g: f32, #[default(20.)] #[name("Blue")] - monochrome_b: f64, + monochrome_b: f32, #[default(0.)] #[name("Constant")] - monochrome_c: f64, + monochrome_c: f32, #[default(100.)] #[name("(Red) Red")] - red_r: f64, + red_r: f32, #[default(0.)] #[name("(Red) Green")] - red_g: f64, + red_g: f32, #[default(0.)] #[name("(Red) Blue")] - red_b: f64, + red_b: f32, #[default(0.)] #[name("(Red) Constant")] - red_c: f64, + red_c: f32, #[default(0.)] #[name("(Green) Red")] - green_r: f64, + green_r: f32, #[default(100.)] #[name("(Green) Green")] - green_g: f64, + green_g: f32, #[default(0.)] #[name("(Green) Blue")] - green_b: f64, + green_b: f32, #[default(0.)] #[name("(Green) Constant")] - green_c: f64, + green_c: f32, #[default(0.)] #[name("(Blue) Red")] - blue_r: f64, + blue_r: f32, #[default(0.)] #[name("(Blue) Green")] - blue_g: f64, + blue_g: f32, #[default(100.)] #[name("(Blue) Blue")] - blue_b: f64, + blue_b: f32, #[default(0.)] #[name("(Blue) Constant")] - blue_c: f64, + blue_c: f32, // Display-only properties (not used within the node) _output_channel: RedGreenBlue, @@ -717,15 +714,15 @@ fn channel_mixer>( let (r, g, b, a) = color.components(); let color = if monochrome { - let (monochrome_r, monochrome_g, monochrome_b, monochrome_c) = (monochrome_r as f32 / 100., monochrome_g as f32 / 100., monochrome_b as f32 / 100., monochrome_c as f32 / 100.); + let (monochrome_r, monochrome_g, monochrome_b, monochrome_c) = (monochrome_r / 100., monochrome_g / 100., monochrome_b / 100., monochrome_c / 100.); let gray = (r * monochrome_r + g * monochrome_g + b * monochrome_b + monochrome_c).clamp(0., 1.); Color::from_rgbaf32_unchecked(gray, gray, gray, a) } else { - let (red_r, red_g, red_b, red_c) = (red_r as f32 / 100., red_g as f32 / 100., red_b as f32 / 100., red_c as f32 / 100.); - let (green_r, green_g, green_b, green_c) = (green_r as f32 / 100., green_g as f32 / 100., green_b as f32 / 100., green_c as f32 / 100.); - let (blue_r, blue_g, blue_b, blue_c) = (blue_r as f32 / 100., blue_g as f32 / 100., blue_b as f32 / 100., blue_c as f32 / 100.); + let (red_r, red_g, red_b, red_c) = (red_r / 100., red_g / 100., red_b / 100., red_c / 100.); + let (green_r, green_g, green_b, green_c) = (green_r / 100., green_g / 100., green_b / 100., green_c / 100.); + let (blue_r, blue_g, blue_b, blue_c) = (blue_r / 100., blue_g / 100., blue_b / 100., blue_c / 100.); let red = (r * red_r + g * red_g + b * red_b + red_c).clamp(0., 1.); let green = (r * green_r + g * green_g + b * green_b + green_c).clamp(0., 1.); @@ -785,50 +782,50 @@ fn selective_color>( mode: RelativeAbsolute, - #[name("(Reds) Cyan")] r_c: f64, - #[name("(Reds) Magenta")] r_m: f64, - #[name("(Reds) Yellow")] r_y: f64, - #[name("(Reds) Black")] r_k: f64, - - #[name("(Yellows) Cyan")] y_c: f64, - #[name("(Yellows) Magenta")] y_m: f64, - #[name("(Yellows) Yellow")] y_y: f64, - #[name("(Yellows) Black")] y_k: f64, - - #[name("(Greens) Cyan")] g_c: f64, - #[name("(Greens) Magenta")] g_m: f64, - #[name("(Greens) Yellow")] g_y: f64, - #[name("(Greens) Black")] g_k: f64, - - #[name("(Cyans) Cyan")] c_c: f64, - #[name("(Cyans) Magenta")] c_m: f64, - #[name("(Cyans) Yellow")] c_y: f64, - #[name("(Cyans) Black")] c_k: f64, - - #[name("(Blues) Cyan")] b_c: f64, - #[name("(Blues) Magenta")] b_m: f64, - #[name("(Blues) Yellow")] b_y: f64, - #[name("(Blues) Black")] b_k: f64, - - #[name("(Magentas) Cyan")] m_c: f64, - #[name("(Magentas) Magenta")] m_m: f64, - #[name("(Magentas) Yellow")] m_y: f64, - #[name("(Magentas) Black")] m_k: f64, - - #[name("(Whites) Cyan")] w_c: f64, - #[name("(Whites) Magenta")] w_m: f64, - #[name("(Whites) Yellow")] w_y: f64, - #[name("(Whites) Black")] w_k: f64, - - #[name("(Neutrals) Cyan")] n_c: f64, - #[name("(Neutrals) Magenta")] n_m: f64, - #[name("(Neutrals) Yellow")] n_y: f64, - #[name("(Neutrals) Black")] n_k: f64, - - #[name("(Blacks) Cyan")] k_c: f64, - #[name("(Blacks) Magenta")] k_m: f64, - #[name("(Blacks) Yellow")] k_y: f64, - #[name("(Blacks) Black")] k_k: f64, + #[name("(Reds) Cyan")] r_c: f32, + #[name("(Reds) Magenta")] r_m: f32, + #[name("(Reds) Yellow")] r_y: f32, + #[name("(Reds) Black")] r_k: f32, + + #[name("(Yellows) Cyan")] y_c: f32, + #[name("(Yellows) Magenta")] y_m: f32, + #[name("(Yellows) Yellow")] y_y: f32, + #[name("(Yellows) Black")] y_k: f32, + + #[name("(Greens) Cyan")] g_c: f32, + #[name("(Greens) Magenta")] g_m: f32, + #[name("(Greens) Yellow")] g_y: f32, + #[name("(Greens) Black")] g_k: f32, + + #[name("(Cyans) Cyan")] c_c: f32, + #[name("(Cyans) Magenta")] c_m: f32, + #[name("(Cyans) Yellow")] c_y: f32, + #[name("(Cyans) Black")] c_k: f32, + + #[name("(Blues) Cyan")] b_c: f32, + #[name("(Blues) Magenta")] b_m: f32, + #[name("(Blues) Yellow")] b_y: f32, + #[name("(Blues) Black")] b_k: f32, + + #[name("(Magentas) Cyan")] m_c: f32, + #[name("(Magentas) Magenta")] m_m: f32, + #[name("(Magentas) Yellow")] m_y: f32, + #[name("(Magentas) Black")] m_k: f32, + + #[name("(Whites) Cyan")] w_c: f32, + #[name("(Whites) Magenta")] w_m: f32, + #[name("(Whites) Yellow")] w_y: f32, + #[name("(Whites) Black")] w_k: f32, + + #[name("(Neutrals) Cyan")] n_c: f32, + #[name("(Neutrals) Magenta")] n_m: f32, + #[name("(Neutrals) Yellow")] n_y: f32, + #[name("(Neutrals) Black")] n_k: f32, + + #[name("(Blacks) Cyan")] k_c: f32, + #[name("(Blacks) Magenta")] k_m: f32, + #[name("(Blacks) Yellow")] k_y: f32, + #[name("(Blacks) Black")] k_k: f32, _colors: SelectiveColorChoice, ) -> T { @@ -866,15 +863,15 @@ fn selective_color>( }; let (sum_r, sum_g, sum_b) = [ - (SelectiveColorChoice::Reds, (r_c as f32, r_m as f32, r_y as f32, r_k as f32)), - (SelectiveColorChoice::Yellows, (y_c as f32, y_m as f32, y_y as f32, y_k as f32)), - (SelectiveColorChoice::Greens, (g_c as f32, g_m as f32, g_y as f32, g_k as f32)), - (SelectiveColorChoice::Cyans, (c_c as f32, c_m as f32, c_y as f32, c_k as f32)), - (SelectiveColorChoice::Blues, (b_c as f32, b_m as f32, b_y as f32, b_k as f32)), - (SelectiveColorChoice::Magentas, (m_c as f32, m_m as f32, m_y as f32, m_k as f32)), - (SelectiveColorChoice::Whites, (w_c as f32, w_m as f32, w_y as f32, w_k as f32)), - (SelectiveColorChoice::Neutrals, (n_c as f32, n_m as f32, n_y as f32, n_k as f32)), - (SelectiveColorChoice::Blacks, (k_c as f32, k_m as f32, k_y as f32, k_k as f32)), + (SelectiveColorChoice::Reds, (r_c, r_m, r_y, r_k)), + (SelectiveColorChoice::Yellows, (y_c, y_m, y_y, y_k)), + (SelectiveColorChoice::Greens, (g_c, g_m, g_y, g_k)), + (SelectiveColorChoice::Cyans, (c_c, c_m, c_y, c_k)), + (SelectiveColorChoice::Blues, (b_c, b_m, b_y, b_k)), + (SelectiveColorChoice::Magentas, (m_c, m_m, m_y, m_k)), + (SelectiveColorChoice::Whites, (w_c, w_m, w_y, w_k)), + (SelectiveColorChoice::Neutrals, (n_c, n_m, n_y, n_k)), + (SelectiveColorChoice::Blacks, (k_c, k_m, k_y, k_k)), ] .into_iter() .fold((0., 0., 0.), |acc, (color_parameter_group, (c, m, y, k))| { @@ -959,21 +956,21 @@ fn exposure>( GradientStops, )] mut input: T, - exposure: f64, - offset: f64, + exposure: f32, + offset: f32, #[default(1.)] #[range((0.01, 10.))] #[hard_min(0.0001)] - gamma_correction: f64, + gamma_correction: f32, ) -> T { input.adjust(|color| { let adjusted = color - // Exposure - .map_rgb(|c: f32| c * 2_f32.powf(exposure as f32)) - // Offset - .map_rgb(|c: f32| c + offset as f32) - // Gamma correction - .gamma(gamma_correction as f32); + // Exposure + .map_rgb(|c: f32| c * 2_f32.powf(exposure)) + // Offset + .map_rgb(|c: f32| c + offset) + // Gamma correction + .gamma(gamma_correction); adjusted.map_rgb(|c: f32| c.clamp(0., 1.)) }); diff --git a/node-graph/graster-nodes/src/blending_nodes.rs b/node-graph/graster-nodes/src/blending_nodes.rs index 06c145fe3a..6efc335fbb 100644 --- a/node-graph/graster-nodes/src/blending_nodes.rs +++ b/node-graph/graster-nodes/src/blending_nodes.rs @@ -8,7 +8,7 @@ use graphene_core::table::Table; use graphene_core_shaders::Ctx; use graphene_core_shaders::blending::BlendMode; use graphene_core_shaders::color::{Color, Pixel}; -use graphene_core_shaders::registry::types::Percentage; +use graphene_core_shaders::registry::types::PercentageF32; pub trait Blend { fn blend(&self, under: &Self, blend_fn: impl Fn(P, P) -> P) -> Self; @@ -81,7 +81,7 @@ mod blend_std { } #[inline(always)] -pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f64) -> Color { +pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, opacity: f32) -> Color { let target_color = match blend_mode { // Other utility blend modes (hidden from the normal list) - do not have alpha blend BlendMode::Erase => return background.alpha_subtract(foreground), @@ -151,7 +151,7 @@ fn blend + Send>( )] under: T, blend_mode: BlendMode, - #[default(100.)] opacity: Percentage, + #[default(100.)] opacity: PercentageF32, ) -> T { over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) } @@ -166,15 +166,12 @@ fn color_overlay>( GradientStops, )] mut image: T, - #[default(Color::BLACK)] color: Table, + #[default(Color::BLACK)] color: Color, blend_mode: BlendMode, - #[default(100.)] opacity: Percentage, + #[default(100.)] opacity: PercentageF32, ) -> T { let opacity = (opacity as f32 / 100.).clamp(0., 1.); - let color: Option = color.into(); - let color = color.unwrap_or(Color::BLACK); - image.adjust(|pixel| { let image = pixel.map_rgb(|channel| channel * (1. - opacity)); @@ -204,15 +201,9 @@ mod test { let overlay_color = Color::GREEN; // 100% of the output should come from the multiplied value - let opacity = 100_f64; - - let result = super::color_overlay( - (), - Table::new_from_element(Raster::new_cpu(image.clone())), - Table::new_from_element(overlay_color), - BlendMode::Multiply, - opacity, - ); + let opacity = 100.; + + let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity); let result = result.iter().next().unwrap().element; // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) diff --git a/node-graph/gstd/src/text.rs b/node-graph/gstd/src/text.rs index ae9c684628..d1a094cc73 100644 --- a/node-graph/gstd/src/text.rs +++ b/node-graph/gstd/src/text.rs @@ -17,12 +17,8 @@ fn text<'i: 'n>( #[unit(" px")] #[default(0.)] character_spacing: f64, - #[unit(" px")] - #[default(None)] - max_width: Option, - #[unit(" px")] - #[default(None)] - max_height: Option, + #[unit(" px")] max_width: Option, + #[unit(" px")] max_height: Option, /// Faux italic. #[unit("°")] #[default(0.)] diff --git a/node-graph/preprocessor/Cargo.toml b/node-graph/preprocessor/Cargo.toml index e39fef365c..08d03c8524 100644 --- a/node-graph/preprocessor/Cargo.toml +++ b/node-graph/preprocessor/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT OR Apache-2.0" [features] [dependencies] +log = { workspace = true } # Workspace dependencies graphene-std = { workspace = true, features = ["gpu"] } diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 7b5681e206..b440f570d9 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate log; + use graph_craft::document::value::*; use graph_craft::document::*; use graph_craft::proto::RegistryValueSource; @@ -150,7 +153,14 @@ pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTyp match field.value_source { RegistryValueSource::None => {} - RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), + RegistryValueSource::Default(data) => { + if let Some(custom_default) = TaggedValue::from_primitive_string(data, ty) { + return NodeInput::value(custom_default, exposed); + } else { + // It is incredibly useful to get a warning when the default type cannot be parsed rather than defaulting to `()`. + warn!("Failed to parse default value for type {ty:?} with data {data}"); + } + } RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), };