diff --git a/crates/processing_core/src/constants.rs b/crates/processing_core/src/constants.rs index f8081bce..d155448b 100644 --- a/crates/processing_core/src/constants.rs +++ b/crates/processing_core/src/constants.rs @@ -55,6 +55,15 @@ pub const LAB: &str = "lab"; pub const LCH: &str = "lch"; pub const XYZ: &str = "xyz"; +pub const PBR_NEUTRAL: &str = "pbr_neutral"; // tonemapping +pub const AGX: &str = "agx"; +pub const ACES: &str = "aces"; +pub const TONY_MC_MAPFACE: &str = "tony_mc_mapface"; +pub const BLENDER_FILMIC: &str = "blender_filmic"; +pub const REINHARD: &str = "reinhard"; +pub const REINHARD_LUMINANCE: &str = "reinhard_luminance"; +pub const SOMEWHAT_BORING_DISPLAY_TRANSFORM: &str = "somewhat_boring_display_transform"; + pub const MAP: &str = "map"; pub const COMBINE: &str = "combine"; pub const MIX: &str = "mix"; diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index 297202a3..c81d3c6f 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -3897,13 +3897,33 @@ pub unsafe extern "C" fn processing_graphics_world_from_screen( #[unsafe(no_mangle)] pub extern "C" fn processing_graphics_set_bloom(graphics_id: u64, intensity: f32, threshold: f32) { error::clear_error(); - error::check(|| graphics_set_bloom(Entity::from_bits(graphics_id), intensity, threshold)); + error::check(|| graphics_bloom(Entity::from_bits(graphics_id), intensity, threshold)); } #[unsafe(no_mangle)] pub extern "C" fn processing_graphics_remove_bloom(graphics_id: u64) { error::clear_error(); - error::check(|| graphics_remove_bloom(Entity::from_bits(graphics_id))); + error::check(|| graphics_no_bloom(Entity::from_bits(graphics_id))); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_graphics_tonemapping(graphics_id: u64, mode: u8) { + error::clear_error(); + error::check(|| { + let tonemapping = + processing::prelude::graphics::tonemapping_from_u8(mode).ok_or_else(|| { + processing::prelude::error::ProcessingError::InvalidArgument(format!( + "unknown tonemapping: {mode}" + )) + })?; + graphics_tonemapping(Entity::from_bits(graphics_id), tonemapping) + }); +} + +#[unsafe(no_mangle)] +pub extern "C" fn processing_graphics_no_tonemapping(graphics_id: u64) { + error::clear_error(); + error::check(|| graphics_no_tonemapping(Entity::from_bits(graphics_id))); } // Mouse buttons diff --git a/crates/processing_pyo3/examples/curves.py b/crates/processing_pyo3/examples/curves.py index accbbb8a..60fce365 100644 --- a/crates/processing_pyo3/examples/curves.py +++ b/crates/processing_pyo3/examples/curves.py @@ -19,7 +19,7 @@ def draw(): amp = 30 + sin(i * 0.5) * 20 v = 0.3 + (i / 20.0) * 0.5 - stroke(v * 0.6 * 255, v * 255, min(v * 1.2, 1.0) * 255) + stroke(v * 0.6, v, min(v * 1.2, 1.0)) bezier( 0, y_base, diff --git a/crates/processing_pyo3/examples/particles_lissajous.py b/crates/processing_pyo3/examples/particles_lissajous.py index 6bfa291c..ae8e4220 100644 --- a/crates/processing_pyo3/examples/particles_lissajous.py +++ b/crates/processing_pyo3/examples/particles_lissajous.py @@ -33,7 +33,6 @@ def setup(): size(1000, 800) window_title(f"Lissajous — all points connected — {N:,} pts") mode_3d() - bloom(0.0) p = create_particles(capacity=N, attributes=[Attribute.position(), Attribute.color()]) # One line = 2 vertices; up to N*MAX_LINKS lines. @@ -52,7 +51,6 @@ def setup(): def draw(): - bloom(0.0) # re-assert each frame background(255, 255, 255) t = elapsed_time diff --git a/crates/processing_pyo3/examples/primitives_2d.py b/crates/processing_pyo3/examples/primitives_2d.py index e6e7f277..ee8f9a40 100644 --- a/crates/processing_pyo3/examples/primitives_2d.py +++ b/crates/processing_pyo3/examples/primitives_2d.py @@ -20,7 +20,7 @@ def draw(): r = 40 + i * 18 offset = t + i * 0.15 v = 0.2 + (i / 12.0) * 0.4 - stroke(v * 255, v * 0.8 * 255, v * 0.6 * 255) + stroke(v, v * 0.8, v * 0.6) arc(cx, cy, r * 2, r * 2, offset, offset + pi * 1.2, OPEN) no_stroke() @@ -29,7 +29,7 @@ def draw(): angle = t + i * 0.15 x = cx + r * cos(angle) y = cy + r * sin(angle) - fill(217, (0.3 + i / 24.0) * 255, 51) + fill(0.85, 0.3 + i / 24.0, 0.2) ellipse(x, y, 6, 6) fill(25, 25, 25, 38) diff --git a/crates/processing_pyo3/examples/style_stack.py b/crates/processing_pyo3/examples/style_stack.py index 0023faea..a2639820 100644 --- a/crates/processing_pyo3/examples/style_stack.py +++ b/crates/processing_pyo3/examples/style_stack.py @@ -28,7 +28,7 @@ def draw(): rotate(spin) no_stroke() - fill(235, 90 + 130 * pulse, 60) + fill(0.92, 0.35 + 0.51 * pulse, 0.24) rect_mode(CENTER) rect(0, 0, cell * (0.3 + 0.15 * pulse), cell * 0.3) diff --git a/crates/processing_pyo3/examples/text.py b/crates/processing_pyo3/examples/text.py index fcad5e91..6bbc5fe4 100644 --- a/crates/processing_pyo3/examples/text.py +++ b/crates/processing_pyo3/examples/text.py @@ -17,8 +17,8 @@ def draw(): text("hello, processing", width / 2, height / 2) # Pulsing subtitle. - pulse = 150 + 105 * sin(frame_count * 0.05) - fill(120, pulse, 255) + pulse = 0.59 + 0.41 * sin(frame_count * 0.05) + fill(0.47, pulse, 1.0) text_size(20) text("global text now works", width / 2, height / 2 + 60) diff --git a/crates/processing_pyo3/src/constants.rs b/crates/processing_pyo3/src/constants.rs index 10b44594..999a1904 100644 --- a/crates/processing_pyo3/src/constants.rs +++ b/crates/processing_pyo3/src/constants.rs @@ -44,6 +44,17 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { add!(m, WORD, CHAR); add!(m, NEAREST, CLAMP, REPEAT, MIRROR); add!(m, SRGB, LINEAR, HSL, HSV, HWB, OKLAB, OKLCH, LAB, LCH, XYZ); + add!( + m, + PBR_NEUTRAL, + AGX, + ACES, + TONY_MC_MAPFACE, + BLENDER_FILMIC, + REINHARD, + REINHARD_LUMINANCE, + SOMEWHAT_BORING_DISPLAY_TRANSFORM + ); add!( m, PI, TWO_PI, HALF_PI, QUARTER_PI, TAU, DEG_TO_RAD, RAD_TO_DEG ); diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 30e71403..e4474170 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -2291,14 +2291,26 @@ impl Graphics { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } - #[pyo3(signature = (intensity, threshold=0.0))] + #[pyo3(signature = (intensity=processing_render::graphics::DEFAULT_BLOOM_INTENSITY, threshold=0.0))] pub fn bloom(&self, intensity: f32, threshold: f32) -> PyResult<()> { - if intensity <= 0.0 { - graphics_remove_bloom(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) - } else { - graphics_set_bloom(self.entity, intensity, threshold) - .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) - } + graphics_bloom(self.entity, intensity, threshold) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn no_bloom(&self) -> PyResult<()> { + graphics_no_bloom(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + #[pyo3(signature = (mode=processing::prelude::constants::PBR_NEUTRAL))] + pub fn tonemapping(&self, mode: &str) -> PyResult<()> { + let tonemapping = processing_render::graphics::parse_tonemapping(mode) + .ok_or_else(|| PyValueError::new_err(format!("unknown tonemapping {mode:?}")))?; + graphics_tonemapping(self.entity, tonemapping) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn no_tonemapping(&self) -> PyResult<()> { + graphics_no_tonemapping(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } /// Composites a source onto this graphics with a blend mode (Processing diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index 0ddc4e09..97deda89 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -1485,11 +1485,29 @@ pub mod mewnala { } #[pyfunction] - #[pyo3(pass_module, signature = (intensity, threshold=0.0))] + #[pyo3(pass_module, signature = (intensity=processing_render::graphics::DEFAULT_BLOOM_INTENSITY, threshold=0.0))] fn bloom(module: &Bound<'_, PyModule>, intensity: f32, threshold: f32) -> PyResult<()> { graphics!(module).bloom(intensity, threshold) } + #[pyfunction] + #[pyo3(pass_module)] + fn no_bloom(module: &Bound<'_, PyModule>) -> PyResult<()> { + graphics!(module).no_bloom() + } + + #[pyfunction] + #[pyo3(pass_module, signature = (mode=processing::prelude::constants::PBR_NEUTRAL))] + fn tonemapping(module: &Bound<'_, PyModule>, mode: &str) -> PyResult<()> { + graphics!(module).tonemapping(mode) + } + + #[pyfunction] + #[pyo3(pass_module)] + fn no_tonemapping(module: &Bound<'_, PyModule>) -> PyResult<()> { + graphics!(module).no_tonemapping() + } + #[pyfunction] #[pyo3(pass_module, signature = (*args))] fn rect(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult<()> { diff --git a/crates/processing_render/src/graphics.rs b/crates/processing_render/src/graphics.rs index 0f0dea8a..c8005e79 100644 --- a/crates/processing_render/src/graphics.rs +++ b/crates/processing_render/src/graphics.rs @@ -11,7 +11,6 @@ use bevy::{ core_pipeline::tonemapping::Tonemapping, ecs::query::QueryEntityError, math::{Mat4, Vec3A}, - post_process::bloom::Bloom, prelude::*, render::{ RenderApp, @@ -210,7 +209,6 @@ pub fn create( ..default() }, target, - // overridden below for hdr targets Tonemapping::None, // we need to be able to write to the texture CameraMainTextureUsages::default().with(TextureUsages::COPY_DST), @@ -229,7 +227,7 @@ pub fn create( )); if is_hdr { - entity_commands.insert((Hdr, Bloom::NATURAL, Tonemapping::TonyMcMapface)); + entity_commands.insert(Hdr); } let entity = entity_commands.id(); @@ -444,10 +442,11 @@ pub fn world_from_screen( Ok(world) } -pub fn set_bloom( +pub const DEFAULT_BLOOM_INTENSITY: f32 = bevy::post_process::bloom::Bloom::NATURAL.intensity; + +pub fn bloom( In((entity, intensity, threshold)): In<(Entity, f32, f32)>, mut commands: Commands, - mut tonemapping_query: Query<&mut Tonemapping>, ) -> Result<()> { use bevy::post_process::bloom::{Bloom, BloomCompositeMode, BloomPrefilter}; @@ -462,29 +461,56 @@ pub fn set_bloom( } commands.entity(entity).insert((bloom, Hdr)); - - if let Ok(mut tm) = tonemapping_query.get_mut(entity) - && *tm == Tonemapping::None - { - *tm = Tonemapping::TonyMcMapface; - } - Ok(()) } -pub fn remove_bloom( - In(entity): In, - mut commands: Commands, - mut tonemapping_query: Query<&mut Tonemapping>, -) -> Result<()> { +pub fn no_bloom(In(entity): In, mut commands: Commands) -> Result<()> { use bevy::post_process::bloom::Bloom; commands.entity(entity).remove::(); + Ok(()) +} - if let Ok(mut tm) = tonemapping_query.get_mut(entity) { - *tm = Tonemapping::None; +pub fn parse_tonemapping(s: &str) -> Option { + use processing_core::constants as consts; + [ + (consts::PBR_NEUTRAL, Tonemapping::KhronosPbrNeutral), + (consts::AGX, Tonemapping::AgX), + (consts::ACES, Tonemapping::AcesFitted), + (consts::TONY_MC_MAPFACE, Tonemapping::TonyMcMapface), + (consts::BLENDER_FILMIC, Tonemapping::BlenderFilmic), + (consts::REINHARD, Tonemapping::Reinhard), + (consts::REINHARD_LUMINANCE, Tonemapping::ReinhardLuminance), + ( + consts::SOMEWHAT_BORING_DISPLAY_TRANSFORM, + Tonemapping::SomewhatBoringDisplayTransform, + ), + ] + .into_iter() + .find_map(|(name, tm)| s.eq_ignore_ascii_case(name).then_some(tm)) +} + +pub fn tonemapping_from_u8(v: u8) -> Option { + match v { + 0 => Some(Tonemapping::KhronosPbrNeutral), + 1 => Some(Tonemapping::AgX), + 2 => Some(Tonemapping::AcesFitted), + 3 => Some(Tonemapping::TonyMcMapface), + 4 => Some(Tonemapping::BlenderFilmic), + 5 => Some(Tonemapping::Reinhard), + 6 => Some(Tonemapping::ReinhardLuminance), + 7 => Some(Tonemapping::SomewhatBoringDisplayTransform), + _ => None, } +} +pub fn set_tonemapping( + In((entity, tonemapping)): In<(Entity, Tonemapping)>, + mut query: Query<&mut Tonemapping>, +) -> Result<()> { + *query + .get_mut(entity) + .map_err(|_| ProcessingError::GraphicsNotFound)? = tonemapping; Ok(()) } diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index 8634690c..5b0cc9b2 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -948,26 +948,44 @@ pub fn graphics_world_from_screen( }) } -pub fn graphics_set_bloom( +pub fn graphics_bloom( graphics_entity: Entity, intensity: f32, threshold: f32, ) -> error::Result<()> { app_mut(|app| { app.world_mut() - .run_system_cached_with(graphics::set_bloom, (graphics_entity, intensity, threshold)) + .run_system_cached_with(graphics::bloom, (graphics_entity, intensity, threshold)) .unwrap() }) } -pub fn graphics_remove_bloom(graphics_entity: Entity) -> error::Result<()> { +pub fn graphics_no_bloom(graphics_entity: Entity) -> error::Result<()> { app_mut(|app| { app.world_mut() - .run_system_cached_with(graphics::remove_bloom, graphics_entity) + .run_system_cached_with(graphics::no_bloom, graphics_entity) .unwrap() }) } +pub fn graphics_tonemapping( + graphics_entity: Entity, + tonemapping: bevy::core_pipeline::tonemapping::Tonemapping, +) -> error::Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(graphics::set_tonemapping, (graphics_entity, tonemapping)) + .unwrap() + }) +} + +pub fn graphics_no_tonemapping(graphics_entity: Entity) -> error::Result<()> { + graphics_tonemapping( + graphics_entity, + bevy::core_pipeline::tonemapping::Tonemapping::None, + ) +} + pub fn transform_set_position(entity: Entity, position: Vec3) -> error::Result<()> { app_mut(|app| { app.world_mut()