From 2042edaf38e97e1f258ef02396f4ccadae33a552 Mon Sep 17 00:00:00 2001 From: Johan Klokkhammer Helsing Date: Sat, 20 Jun 2026 01:12:22 +0200 Subject: [PATCH 1/3] Port to bevy 0.19 --- Cargo.toml | 10 ++-- examples/bevy_on_fire.rs | 2 +- examples/bloom.rs | 2 +- examples/ui_button.rs | 4 +- src/lib.rs | 125 ++++++++++++++++++++++++++++----------- src/ui.rs | 20 +++++-- 6 files changed, 112 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0cd8c37..72d1c8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ bevy_ui = ["bevy/bevy_ui_render"] bevy_primitives = [] [dependencies] -bevy = { version = "0.18", default-features = false, features = [ +bevy = { version = "0.19", default-features = false, features = [ "bevy_asset", # needed for handle ids "bevy_core_pipeline", "bevy_log", @@ -30,7 +30,7 @@ fixedbitset = "0.5" uuid = "1.10.0" [dev-dependencies] -bevy = { version = "0.18", default-features = false, features = [ +bevy = { version = "0.19", default-features = false, features = [ "bevy_post_process", "bevy_sprite_render", "bevy_state", @@ -39,9 +39,9 @@ bevy = { version = "0.18", default-features = false, features = [ "file_watcher", "x11", # github actions runners don't have libxkbcommon installed, so can't use wayland ] } -bevy_asset_loader = "0.25" -bevy_lospec = "0.12" -bevy_pancam = "0.20" +bevy_asset_loader = "0.27.0-rc.1" +bevy_lospec = "0.13" +bevy_pancam = "0.21" rand = "0.9" [profile.dev] diff --git a/examples/bevy_on_fire.rs b/examples/bevy_on_fire.rs index a488429..05ce0b8 100644 --- a/examples/bevy_on_fire.rs +++ b/examples/bevy_on_fire.rs @@ -1,4 +1,4 @@ -use bevy::{prelude::*, render::view::Hdr}; +use bevy::{camera::Hdr, prelude::*}; use bevy_pancam::*; use bevy_smud::*; diff --git a/examples/bloom.rs b/examples/bloom.rs index 2a4475a..f9d30af 100644 --- a/examples/bloom.rs +++ b/examples/bloom.rs @@ -3,10 +3,10 @@ //! Note that you could probably achieve cheaper and higher quality bloom-like //! effects by creating a custom fill. +use bevy::camera::Hdr; use bevy::color::palettes::css; use bevy::post_process::bloom::Bloom; use bevy::prelude::*; -use bevy::render::view::Hdr; // The prelude contains the basic things needed to create shapes use bevy_smud::prelude::*; diff --git a/examples/ui_button.rs b/examples/ui_button.rs index 90cdc97..152408f 100644 --- a/examples/ui_button.rs +++ b/examples/ui_button.rs @@ -71,7 +71,7 @@ return vec4(input.color.rgb, a * input.color.a); children![( Text::new("Click Me!"), TextFont { - font_size: 20.0, + font_size: FontSize::Px(20.0), ..default() }, TextColor(css::WHITE.into()), @@ -96,7 +96,7 @@ return vec4(input.color.rgb, a * input.color.a); children![( Text::new("Click Me!"), TextFont { - font_size: 20.0, + font_size: FontSize::Px(20.0), ..default() }, TextColor(css::WHITE.into()), diff --git a/src/lib.rs b/src/lib.rs index 0711cff..d24c9b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use std::ops::Range; use bevy::{ + camera::CompositingSpace, core_pipeline::{ core_2d::{CORE_2D_DEPTH_FORMAT, Transparent2d}, tonemapping::{ @@ -25,6 +26,7 @@ use bevy::{ prelude::*, render::{ Extract, MainWorld, Render, RenderApp, RenderSystems, + camera::ExtractedCamera, globals::{GlobalsBuffer, GlobalsUniform}, render_asset::RenderAssets, render_phase::{ @@ -45,8 +47,9 @@ use bevy::{ sync_world::{MainEntity, RenderEntity}, texture::{FallbackImage, GpuImage}, view::{ - ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewTarget, ViewUniform, - ViewUniformOffset, ViewUniforms, + COLOR_TARGET_FORMAT_MASK_BITS, ExtractedView, RenderVisibleEntities, + RetainedViewEntity, ViewUniform, ViewUniformOffset, ViewUniforms, + texture_format_from_code, texture_format_to_code, }, }, shader::{ShaderDefVal, ShaderImport}, @@ -243,7 +246,6 @@ struct SmudPipelineKey { /// Mix of bevy_render Mesh2DPipelineKey and SpritePipelineKey mesh: PipelineKey, shader: Handle, - hdr: bool, } impl SpecializedRenderPipeline for SmudPipeline { @@ -292,6 +294,9 @@ impl SpecializedRenderPipeline for SmudPipeline { PipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE => { shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); } + PipelineKey::TONEMAP_METHOD_KHRONOS_PBR_NEUTRAL => { + shader_defs.push("TONEMAP_METHOD_PBR_NEUTRAL".into()); + } _ => {} } // Debanding is tied to tonemapping in the shader, cannot run without it. @@ -300,6 +305,13 @@ impl SpecializedRenderPipeline for SmudPipeline { } } + if key.mesh.contains(PipelineKey::SRGB_COMPOSITING) { + shader_defs.push("SRGB_OUTPUT".into()); + } + if key.mesh.contains(PipelineKey::OKLAB_COMPOSITING) { + shader_defs.push("OKLAB_OUTPUT".into()); + } + debug!("shader_defs: {shader_defs:?}"); // Customize how to store the meshes' vertex attributes in the vertex buffer @@ -363,11 +375,7 @@ impl SpecializedRenderPipeline for SmudPipeline { entry_point: Some("fragment".into()), shader_defs, targets: vec![Some(ColorTargetState { - format: if key.hdr { - ViewTarget::TEXTURE_FORMAT_HDR - } else { - TextureFormat::bevy_default() - }, + format: key.mesh.target_format(), blend: Some(if key.mesh.contains(PipelineKey::BLEND_ADDITIVE) { BlendState { color: BlendComponent { @@ -402,8 +410,8 @@ impl SpecializedRenderPipeline for SmudPipeline { }, depth_stencil: Some(DepthStencilState { format: CORE_2D_DEPTH_FORMAT, - depth_write_enabled: false, - depth_compare: CompareFunction::GreaterEqual, + depth_write_enabled: Some(false), + depth_compare: Some(CompareFunction::GreaterEqual), stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, @@ -422,7 +430,7 @@ impl SpecializedRenderPipeline for SmudPipeline { alpha_to_coverage_enabled: false, // what is this? }, label: Some("bevy_smud_pipeline".into()), - push_constant_ranges: Vec::new(), + immediate_size: 0, zero_initialize_workgroup_memory: false, } } @@ -448,12 +456,12 @@ impl GeneratedShaders { } let sdf_import_path = match shaders.get_mut(sdf) { - Some(shader) => match shader.import_path() { + Some(mut shader) => match &shader.import_path { ShaderImport::Custom(p) => p.to_owned(), _ => { let id = generate_shader_id(); let path = format!("smud::generated::{id}"); - shader.set_import_path(&path); + shader.import_path = ShaderImport::Custom(path.clone()); path } }, @@ -464,12 +472,12 @@ impl GeneratedShaders { }; let fill_import_path = match shaders.get_mut(fill) { - Some(shader) => match shader.import_path() { + Some(mut shader) => match &shader.import_path { ShaderImport::Custom(p) => p.to_owned(), _ => { let id = generate_shader_id(); let path = format!("smud::generated::{id}"); - shader.set_import_path(&path); + shader.import_path = ShaderImport::Custom(path.clone()); path } }, @@ -486,6 +494,12 @@ impl GeneratedShaders { #ifdef TONEMAP_IN_SHADER #import bevy_core_pipeline::tonemapping #endif +#ifdef SRGB_OUTPUT +#import bevy_render::color_operations::linear_to_srgb +#endif +#ifdef OKLAB_OUTPUT +#import bevy_render::color_operations::linear_rgb_to_oklab +#endif #import bevy_smud::view_bindings::view #import smud @@ -516,6 +530,14 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 {{ color = tonemapping::tone_mapping(color, view.color_grading); #endif +#ifdef SRGB_OUTPUT + color = vec4(linear_to_srgb(color.rgb), color.a); +#endif + +#ifdef OKLAB_OUTPUT + color = vec4(linear_rgb_to_oklab(color.rgb), color.a); +#endif + return color; }} "# @@ -617,11 +639,13 @@ bitflags::bitflags! { // FIXME: make normals optional? pub(crate) struct PipelineKey: u32 { const NONE = 0; - const HDR = 1 << 0; - const TONEMAP_IN_SHADER = 1 << 1; - const DEBAND_DITHER = 1 << 2; - const BLEND_ADDITIVE = 1 << 3; - const MAY_DISCARD = 1 << 4; + const TONEMAP_IN_SHADER = 1 << 0; + const DEBAND_DITHER = 1 << 1; + const SRGB_COMPOSITING = 1 << 2; + const OKLAB_COMPOSITING = 1 << 3; + const BLEND_ADDITIVE = 1 << 4; + const MAY_DISCARD = 1 << 5; + const COLOR_TARGET_FORMAT_RESERVED_BITS = Self::COLOR_TARGET_FORMAT_MASK_BITS << Self::COLOR_TARGET_FORMAT_SHIFT_BITS; const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS; @@ -633,6 +657,7 @@ bitflags::bitflags! { const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS; + const TONEMAP_METHOD_KHRONOS_PBR_NEUTRAL = 8 << Self::TONEMAP_METHOD_SHIFT_BITS; } } @@ -641,9 +666,12 @@ impl PipelineKey { const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3; - const TONEMAP_METHOD_MASK_BITS: u32 = 0b111; + const TONEMAP_METHOD_MASK_BITS: u32 = 0b1111; const TONEMAP_METHOD_SHIFT_BITS: u32 = Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones(); + const COLOR_TARGET_FORMAT_MASK_BITS: u32 = COLOR_TARGET_FORMAT_MASK_BITS; + const COLOR_TARGET_FORMAT_SHIFT_BITS: u32 = + Self::TONEMAP_METHOD_SHIFT_BITS - Self::COLOR_TARGET_FORMAT_MASK_BITS.count_ones(); pub fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = @@ -651,8 +679,21 @@ impl PipelineKey { Self::from_bits_retain(msaa_bits) } - pub fn from_hdr(hdr: bool) -> Self { - if hdr { Self::HDR } else { Self::NONE } + /// Create a pipeline key from the view's color target format. + pub fn from_target_format(format: TextureFormat) -> Self { + let code = texture_format_to_code(format) + .expect("Texture format is not supported by the pipeline") as u32; + Self::from_bits_retain( + (code & Self::COLOR_TARGET_FORMAT_MASK_BITS) << Self::COLOR_TARGET_FORMAT_SHIFT_BITS, + ) + } + + /// Color target format of the main pass for this pipeline key. + pub fn target_format(&self) -> TextureFormat { + let code = ((self.bits() >> Self::COLOR_TARGET_FORMAT_SHIFT_BITS) + & Self::COLOR_TARGET_FORMAT_MASK_BITS) as u8; + texture_format_from_code(code) + .expect("Unknown bits in `COLOR_TARGET_FORMAT_MASK_BITS` of the pipeline key") } pub fn msaa_samples(&self) -> u32 { @@ -679,10 +720,10 @@ impl PipelineKey { } } - pub fn from_blend_mode(blend_mode: crate::BlendMode) -> Self { + pub fn from_blend_mode(blend_mode: BlendMode) -> Self { match blend_mode { - crate::BlendMode::Alpha => Self::NONE, - crate::BlendMode::Additive => Self::BLEND_ADDITIVE, + BlendMode::Alpha => Self::NONE, + BlendMode::Additive => Self::BLEND_ADDITIVE, } } } @@ -698,6 +739,7 @@ fn queue_shapes( mut transparent_render_phases: ResMut>, mut views: Query<( &RenderVisibleEntities, + &ExtractedCamera, &ExtractedView, &Msaa, Option<&Tonemapping>, @@ -708,7 +750,7 @@ fn queue_shapes( let draw_smud_shape_function = draw_functions.read().get_id::().unwrap(); // Iterate over each view (a camera is a view) - for (visible_entities, view, msaa, tonemapping, dither) in &mut views { + for (visible_entities, camera, view, msaa, tonemapping, dither) in &mut views { let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity) else { continue; @@ -717,9 +759,16 @@ fn queue_shapes( let mesh_key = PipelineKey::from_msaa_samples(msaa.samples()) | PipelineKey::from_primitive_topology(PrimitiveTopology::TriangleStrip); - let mut view_key = PipelineKey::from_hdr(view.hdr) | mesh_key; + let mut view_key = PipelineKey::from_target_format(view.target_format) | mesh_key; + + if camera.compositing_space == Some(CompositingSpace::Srgb) { + view_key |= PipelineKey::SRGB_COMPOSITING; + } + if camera.compositing_space == Some(CompositingSpace::Oklab) { + view_key |= PipelineKey::OKLAB_COMPOSITING; + } - if !view.hdr { + if !camera.hdr { if let Some(tonemapping) = tonemapping { view_key |= PipelineKey::TONEMAP_IN_SHADER; view_key |= match tonemapping { @@ -735,6 +784,9 @@ fn queue_shapes( } Tonemapping::TonyMcMapface => PipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, Tonemapping::BlenderFilmic => PipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, + Tonemapping::KhronosPbrNeutral => { + PipelineKey::TONEMAP_METHOD_KHRONOS_PBR_NEUTRAL + } }; } if let Some(DebandDither::Enabled) = dither { @@ -743,11 +795,13 @@ fn queue_shapes( } view_entities.clear(); - view_entities.extend( - visible_entities - .iter::() - .map(|(_, e)| e.index_u32() as usize), - ); + if let Some(visible_entities) = visible_entities.get::() { + view_entities.extend( + visible_entities + .iter_visible() + .map(|(_, e)| e.index_u32() as usize), + ); + } transparent_phase .items @@ -761,7 +815,6 @@ fn queue_shapes( let specialize_key = SmudPipelineKey { mesh: shape_key, shader: extracted_shape.shader.clone(), - hdr: view.hdr, }; let pipeline = pipelines.specialize(&pipeline_cache, &smud_pipeline, specialize_key); @@ -774,7 +827,7 @@ fn queue_shapes( let sort_key = FloatOrd(extracted_shape.transform.translation().z); // Add the item to the render phase - transparent_phase.add(Transparent2d { + transparent_phase.add_transient(Transparent2d { draw_function: draw_smud_shape_function, pipeline, entity: ( diff --git a/src/ui.rs b/src/ui.rs index 88b52fa..7850281 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -27,7 +27,7 @@ use bevy::{ sync_world::{MainEntity, TemporaryRenderEntity}, view::{ViewUniform, ViewUniformOffset, ViewUniforms}, }, - ui::{ComputedNode, Node, UiGlobalTransform}, + ui::{ComputedNode, ComputedStackIndex, Node, UiGlobalTransform}, ui_render::{TransparentUi, stack_z_offsets}, }; use bytemuck::{Pod, Zeroable}; @@ -151,11 +151,19 @@ fn extract_ui_shapes( mut commands: Commands, mut extracted_nodes: ResMut, generated_shaders: Res, - ui_shapes: Extract>, + ui_shapes: Extract< + Query<( + Entity, + &UiShape, + &ComputedNode, + &ComputedStackIndex, + &UiGlobalTransform, + )>, + >, ) { extracted_nodes.nodes.clear(); - for (entity, ui_shape, computed_node, transform) in ui_shapes.iter() { + for (entity, ui_shape, computed_node, stack_index, transform) in ui_shapes.iter() { let render_entity = commands.spawn(TemporaryRenderEntity).id(); let Some(shader) = generated_shaders @@ -170,7 +178,7 @@ fn extract_ui_shapes( extracted_nodes.nodes.push(ExtractedUiShape { main_entity: entity.into(), render_entity, - stack_index: computed_node.stack_index, + stack_index: stack_index.0, transform: transform.into(), rect: Rect { min: Vec2::ZERO, @@ -227,7 +235,7 @@ impl SpecializedRenderPipeline for UiShapePipeline { RenderPipelineDescriptor { label: Some("ui_shape_pipeline".into()), layout: vec![self.view_layout.clone()], - push_constant_ranges: vec![], + immediate_size: 0, vertex: VertexState { shader: VERTEX_SHADER_HANDLE, shader_defs: vec!["Y_DOWN".into()], @@ -419,7 +427,7 @@ fn queue_ui_shapes( // We use a value slightly after MATERIAL (0.05) so UiShapes render in proper layer order let sort_key = FloatOrd(node.stack_index as f32 + stack_z_offsets::MATERIAL + 0.01); - transparent_phase.add(TransparentUi { + transparent_phase.add_transient(TransparentUi { entity: (node.render_entity, node.main_entity), draw_function, pipeline: pipeline_id, From 12ba2037bba5a9a6d65035b063376fb0ed9ae523 Mon Sep 17 00:00:00 2001 From: Johan Klokkhammer Helsing Date: Sat, 20 Jun 2026 01:15:01 +0200 Subject: [PATCH 2/3] Update version support table --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 864932d..eca4bfb 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,8 @@ The `main` branch targets the latest bevy release. |bevy|bevy_smud| |----|---------| -|0.18|0.13, main| +|0.19|0.14, main| +|0.18|0.13 | |0.17|0.12 | |0.16|0.11 | |0.15|no support| From c165b5691eff1017f689f1f7f5882d3d30f2c885 Mon Sep 17 00:00:00 2001 From: Johan Klokkhammer Helsing Date: Sat, 20 Jun 2026 01:16:01 +0200 Subject: [PATCH 3/3] Silence clippy warning --- src/ui.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui.rs b/src/ui.rs index 7850281..993d10f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -147,6 +147,7 @@ fn generate_shaders( } /// Extract UiShape components to render world +#[allow(clippy::type_complexity)] fn extract_ui_shapes( mut commands: Commands, mut extracted_nodes: ResMut,