From cdc605cc4813821338627b84c7aed9f152b0cfff Mon Sep 17 00:00:00 2001 From: arcashka Date: Tue, 28 May 2024 15:09:26 +0300 Subject: [PATCH] add tonemapping LUT bindings for sprite and mesh2d pipelines (#13262) Fixes #13118 If you use `Sprite` or `Mesh2d` and create `Camera` with * hdr=false * any tonemapper You would get ``` wgpu error: Validation Error Caused by: In Device::create_render_pipeline note: label = `sprite_pipeline` Error matching ShaderStages(FRAGMENT) shader requirements against the pipeline Shader global ResourceBinding { group: 0, binding: 19 } is not available in the pipeline layout Binding is missing from the pipeline layout ``` Because of missing tonemapping LUT bindings ## Solution Add missing bindings for tonemapping LUT's to `SpritePipeline` & `Mesh2dPipeline` ## Testing I checked that * `tonemapping` * `color_grading` * `sprite_animations` * `2d_shapes` * `meshlet` * `deferred_rendering` examples are still working 2d cases I checked with this code: ``` use bevy::{ color::palettes::css::PURPLE, core_pipeline::tonemapping::Tonemapping, prelude::*, sprite::MaterialMesh2dBundle, }; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, toggle_tonemapping_method) .run(); } fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, asset_server: Res, ) { commands.spawn(Camera2dBundle { camera: Camera { hdr: false, ..default() }, tonemapping: Tonemapping::BlenderFilmic, ..default() }); commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Rectangle::default()).into(), transform: Transform::default().with_scale(Vec3::splat(128.)), material: materials.add(Color::from(PURPLE)), ..default() }); commands.spawn(SpriteBundle { texture: asset_server.load("asd.png"), ..default() }); } fn toggle_tonemapping_method( keys: Res>, mut tonemapping: Query<&mut Tonemapping>, ) { let mut method = tonemapping.single_mut(); if keys.just_pressed(KeyCode::Digit1) { *method = Tonemapping::None; } else if keys.just_pressed(KeyCode::Digit2) { *method = Tonemapping::Reinhard; } else if keys.just_pressed(KeyCode::Digit3) { *method = Tonemapping::ReinhardLuminance; } else if keys.just_pressed(KeyCode::Digit4) { *method = Tonemapping::AcesFitted; } else if keys.just_pressed(KeyCode::Digit5) { *method = Tonemapping::AgX; } else if keys.just_pressed(KeyCode::Digit6) { *method = Tonemapping::SomewhatBoringDisplayTransform; } else if keys.just_pressed(KeyCode::Digit7) { *method = Tonemapping::TonyMcMapface; } else if keys.just_pressed(KeyCode::Digit8) { *method = Tonemapping::BlenderFilmic; } } ``` --- ## Changelog Fix the bug which led to the crash when user uses any tonemapper without hdr for rendering sprites and 2d meshes. --- .../src/tonemapping/lut_bindings.wgsl | 5 + .../bevy_core_pipeline/src/tonemapping/mod.rs | 19 + .../src/tonemapping/tonemapping.wgsl | 7 +- .../src/tonemapping/tonemapping_shared.wgsl | 19 +- crates/bevy_pbr/src/deferred/mod.rs | 8 + crates/bevy_pbr/src/render/mesh.rs | 8 + .../src/render/mesh_view_bindings.wgsl | 1 - crates/bevy_pbr/src/render/pbr_functions.wgsl | 7 +- .../bevy_pbr/src/render/pbr_transmission.wgsl | 6 +- crates/bevy_render/src/maths.wgsl | 5 + crates/bevy_sprite/src/lib.rs | 11 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 67 +++- .../src/mesh2d/mesh2d_view_bindings.wgsl | 3 + crates/bevy_sprite/src/render/mod.rs | 365 ++++++++++-------- crates/bevy_sprite/src/render/sprite.wgsl | 2 +- .../src/render/sprite_view_bindings.wgsl | 9 + 16 files changed, 342 insertions(+), 200 deletions(-) create mode 100644 crates/bevy_core_pipeline/src/tonemapping/lut_bindings.wgsl create mode 100644 crates/bevy_sprite/src/render/sprite_view_bindings.wgsl diff --git a/crates/bevy_core_pipeline/src/tonemapping/lut_bindings.wgsl b/crates/bevy_core_pipeline/src/tonemapping/lut_bindings.wgsl new file mode 100644 index 0000000000..997f9efd12 --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/lut_bindings.wgsl @@ -0,0 +1,5 @@ +#define_import_path bevy_core_pipeline::tonemapping_lut_bindings + +@group(0) @binding(#TONEMAPPING_LUT_TEXTURE_BINDING_INDEX) var dt_lut_texture: texture_3d; +@group(0) @binding(#TONEMAPPING_LUT_SAMPLER_BINDING_INDEX) var dt_lut_sampler: sampler; + diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 217de82222..f734bfcf6e 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -28,6 +28,9 @@ const TONEMAPPING_SHADER_HANDLE: Handle = Handle::weak_from_u128(1701536 const TONEMAPPING_SHARED_SHADER_HANDLE: Handle = Handle::weak_from_u128(2499430578245347910); +const TONEMAPPING_LUT_BINDINGS_SHADER_HANDLE: Handle = + Handle::weak_from_u128(8392056472189465073); + /// 3D LUT (look up table) textures used for tonemapping #[derive(Resource, Clone, ExtractResource)] pub struct TonemappingLuts { @@ -52,6 +55,12 @@ impl Plugin for TonemappingPlugin { "tonemapping_shared.wgsl", Shader::from_wgsl ); + load_internal_asset!( + app, + TONEMAPPING_LUT_BINDINGS_SHADER_HANDLE, + "lut_bindings.wgsl", + Shader::from_wgsl + ); if !app.world().is_resource_added::() { let mut images = app.world_mut().resource_mut::>(); @@ -208,6 +217,16 @@ impl SpecializedRenderPipeline for TonemappingPipeline { fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let mut shader_defs = Vec::new(); + + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), + 3, + )); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), + 4, + )); + if let DebandDither::Enabled = key.deband_dither { shader_defs.push("DEBAND_DITHER".into()); } diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index a4eee79c74..015cd48c69 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -1,9 +1,12 @@ #define TONEMAPPING_PASS -#import bevy_render::view::View +#import bevy_render::{ + view::View, + maths::powsafe, +} #import bevy_core_pipeline::{ fullscreen_vertex_shader::FullscreenVertexOutput, - tonemapping::{tone_mapping, powsafe, screen_space_dither}, + tonemapping::{tone_mapping, screen_space_dither}, } @group(0) @binding(0) var view: View; diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index cd82738643..52d1ddcb6f 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -3,17 +3,13 @@ #import bevy_render::{ view::ColorGrading, color_operations::{hsv_to_rgb, rgb_to_hsv}, - maths::PI_2 + maths::{PI_2, powsafe}, } -// hack !! not sure what to do with this -#ifdef TONEMAPPING_PASS - @group(0) @binding(3) var dt_lut_texture: texture_3d; - @group(0) @binding(4) var dt_lut_sampler: sampler; -#else - @group(0) @binding(20) var dt_lut_texture: texture_3d; - @group(0) @binding(21) var dt_lut_sampler: sampler; -#endif +#import bevy_core_pipeline::tonemapping_lut_bindings::{ + dt_lut_texture, + dt_lut_sampler, +} // Half the size of the crossfade region between shadows and midtones and // between midtones and highlights. This value, 0.1, corresponds to 10% of the @@ -162,11 +158,6 @@ fn ACESFitted(color: vec3) -> vec3 { // https://github.com/MrLixm/AgXc // https://github.com/sobotka/AgX -// pow() but safe for NaNs/negatives -fn powsafe(color: vec3, power: f32) -> vec3 { - return pow(abs(color), vec3(power)) * sign(color); -} - /* Increase color saturation of the given color data. :param color: expected sRGB primaries input diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index a5be334652..fbb6ab548f 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -254,6 +254,14 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), + 20, + )); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), + 21, + )); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index b6321ad57b..646a72f2ee 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1667,6 +1667,14 @@ impl SpecializedMeshPipeline for MeshPipeline { if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), + 20, + )); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), + 21, + )); let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 4ba2e6c361..de3e5c3b46 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -63,7 +63,6 @@ const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u; @group(0) @binding(19) var irradiance_volume_sampler: sampler; #endif -// NB: If you change these, make sure to update `tonemapping_shared.wgsl` too. @group(0) @binding(20) var dt_lut_texture: texture_3d; @group(0) @binding(21) var dt_lut_sampler: sampler; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 3b90436720..90c8c71ec9 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -14,7 +14,7 @@ irradiance_volume, mesh_types::{MESH_FLAGS_SHADOW_RECEIVER_BIT, MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT}, } -#import bevy_render::maths::E +#import bevy_render::maths::{E, powsafe} #ifdef MESHLET_MESH_MATERIAL_PASS #import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput @@ -28,7 +28,10 @@ #import bevy_pbr::environment_map #endif -#import bevy_core_pipeline::tonemapping::{screen_space_dither, powsafe, tone_mapping} +#ifdef TONEMAP_IN_SHADER +#import bevy_core_pipeline::tonemapping::{tone_mapping, screen_space_dither} +#endif + // Biasing info needed to sample from a texture when calling `sample_texture`. // How this is done depends on whether we're rendering meshlets or regular diff --git a/crates/bevy_pbr/src/render/pbr_transmission.wgsl b/crates/bevy_pbr/src/render/pbr_transmission.wgsl index 4a48260ae6..1e4af0ca89 100644 --- a/crates/bevy_pbr/src/render/pbr_transmission.wgsl +++ b/crates/bevy_pbr/src/render/pbr_transmission.wgsl @@ -10,9 +10,9 @@ #import bevy_render::maths::PI -#import bevy_core_pipeline::tonemapping::{ - approximate_inverse_tone_mapping -}; +#ifdef TONEMAP_IN_SHADER +#import bevy_core_pipeline::tonemapping::approximate_inverse_tone_mapping +#endif fn specular_transmissive_light(world_position: vec4, frag_coord: vec3, view_z: f32, N: vec3, V: vec3, F0: vec3, ior: f32, thickness: f32, perceptual_roughness: f32, specular_transmissive_color: vec3, transmitted_environment_light_specular: vec3) -> vec3 { // Calculate the ratio between refaction indexes. Assume air/vacuum for the space outside the mesh diff --git a/crates/bevy_render/src/maths.wgsl b/crates/bevy_render/src/maths.wgsl index 29ea0a8965..a9cb80c0fc 100644 --- a/crates/bevy_render/src/maths.wgsl +++ b/crates/bevy_render/src/maths.wgsl @@ -88,3 +88,8 @@ fn sphere_intersects_plane_half_space( ) -> bool { return dot(plane, sphere_center) + sphere_radius > 0.0; } + +// pow() but safe for NaNs/negatives +fn powsafe(color: vec3, power: f32) -> vec3 { + return pow(abs(color), vec3(power)) * sign(color); +} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index fcf2a701f6..ecb1ce78c9 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -62,6 +62,8 @@ use bevy_render::{ pub struct SpritePlugin; pub const SPRITE_SHADER_HANDLE: Handle = Handle::weak_from_u128(2763343953151597127); +pub const SPRITE_VIEW_BINDINGS_SHADER_HANDLE: Handle = + Handle::weak_from_u128(8846920112458963210); /// System set for sprite rendering. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] @@ -94,6 +96,12 @@ impl Plugin for SpritePlugin { "render/sprite.wgsl", Shader::from_wgsl ); + load_internal_asset!( + app, + SPRITE_VIEW_BINDINGS_SHADER_HANDLE, + "render/sprite_view_bindings.wgsl", + Shader::from_wgsl + ); app.init_asset::() .register_asset_reflect::() .register_type::() @@ -146,7 +154,8 @@ impl Plugin for SpritePlugin { queue_sprites .in_set(RenderSet::Queue) .ambiguous_with(queue_material2d_meshes::), - prepare_sprites.in_set(RenderSet::PrepareBindGroups), + prepare_sprite_image_bind_groups.in_set(RenderSet::PrepareBindGroups), + prepare_sprite_view_bind_groups.in_set(RenderSet::PrepareBindGroups), ), ); }; diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 0dec7f0cbd..24d574ee0a 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -2,6 +2,9 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::core_2d::Transparent2d; +use bevy_core_pipeline::tonemapping::{ + get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, +}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::{ @@ -16,6 +19,7 @@ use bevy_render::batching::no_gpu_preprocessing::{ BatchedInstanceBuffer, }; use bevy_render::mesh::{GpuMesh, MeshVertexBufferLayoutRef}; +use bevy_render::texture::FallbackImage; use bevy_render::{ batching::{GetBatchData, NoAutomaticBatching}, globals::{GlobalsBuffer, GlobalsUniform}, @@ -263,14 +267,22 @@ impl FromWorld for Mesh2dPipeline { )> = SystemState::new(world); let (render_device, render_queue, default_sampler) = system_state.get_mut(world); let render_device = render_device.into_inner(); + let tonemapping_lut_entries = get_lut_bind_group_layout_entries(); let view_layout = render_device.create_bind_group_layout( "mesh2d_view_layout", - &BindGroupLayoutEntries::sequential( + &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX_FRAGMENT, ( - // View - uniform_buffer::(true), - uniform_buffer::(false), + (0, uniform_buffer::(true)), + (1, uniform_buffer::(false)), + ( + 2, + tonemapping_lut_entries[0].visibility(ShaderStages::FRAGMENT), + ), + ( + 3, + tonemapping_lut_entries[1].visibility(ShaderStages::FRAGMENT), + ), ), ), ); @@ -475,6 +487,14 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), + 2, + )); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), + 3, + )); let method = key.intersection(Mesh2dPipelineKey::TONEMAP_METHOD_RESERVED_BITS); @@ -584,29 +604,42 @@ pub struct Mesh2dViewBindGroup { pub value: BindGroup, } +#[allow(clippy::too_many_arguments)] pub fn prepare_mesh2d_view_bind_groups( mut commands: Commands, render_device: Res, mesh2d_pipeline: Res, view_uniforms: Res, - views: Query>, + views: Query<(Entity, &Tonemapping), With>, globals_buffer: Res, + tonemapping_luts: Res, + images: Res>, + fallback_image: Res, ) { - if let (Some(view_binding), Some(globals)) = ( + let (Some(view_binding), Some(globals)) = ( view_uniforms.uniforms.binding(), globals_buffer.buffer.binding(), - ) { - for entity in &views { - let view_bind_group = render_device.create_bind_group( - "mesh2d_view_bind_group", - &mesh2d_pipeline.view_layout, - &BindGroupEntries::sequential((view_binding.clone(), globals.clone())), - ); + ) else { + return; + }; - commands.entity(entity).insert(Mesh2dViewBindGroup { - value: view_bind_group, - }); - } + for (entity, tonemapping) in &views { + let lut_bindings = + get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image); + let view_bind_group = render_device.create_bind_group( + "mesh2d_view_bind_group", + &mesh2d_pipeline.view_layout, + &BindGroupEntries::with_indices(( + (0, view_binding.clone()), + (1, globals.clone()), + (2, lut_bindings.0), + (3, lut_bindings.1), + )), + ); + + commands.entity(entity).insert(Mesh2dViewBindGroup { + value: view_bind_group, + }); } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl index 8b2f57d6ea..cc43da65ce 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_view_bindings.wgsl @@ -6,3 +6,6 @@ @group(0) @binding(0) var view: View; @group(0) @binding(1) var globals: Globals; + +@group(0) @binding(2) var dt_lut_texture: texture_3d; +@group(0) @binding(3) var dt_lut_sampler: sampler; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 0965b3d3d3..875cd854d3 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -8,9 +8,12 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_color::LinearRgba; use bevy_core_pipeline::{ core_2d::Transparent2d, - tonemapping::{DebandDither, Tonemapping}, + tonemapping::{ + get_lut_bind_group_layout_entries, get_lut_bindings, DebandDither, Tonemapping, + TonemappingLuts, + }, }; -use bevy_ecs::entity::EntityHashMap; +use bevy_ecs::{entity::EntityHashMap, query::ROQueryItem}; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem, SystemState}, @@ -28,7 +31,8 @@ use bevy_render::{ }, renderer::{RenderDevice, RenderQueue}, texture::{ - BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + BevyDefault, DefaultImageSampler, FallbackImage, GpuImage, Image, ImageSampler, + TextureFormatPixelInfo, }, view::{ ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, @@ -57,11 +61,22 @@ impl FromWorld for SpritePipeline { )> = SystemState::new(world); let (render_device, default_sampler, render_queue) = system_state.get_mut(world); + let tonemapping_lut_entries = get_lut_bind_group_layout_entries(); let view_layout = render_device.create_bind_group_layout( "sprite_view_layout", - &BindGroupLayoutEntries::single( + &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX_FRAGMENT, - uniform_buffer::(true), + ( + (0, uniform_buffer::(true)), + ( + 1, + tonemapping_lut_entries[0].visibility(ShaderStages::FRAGMENT), + ), + ( + 2, + tonemapping_lut_entries[1].visibility(ShaderStages::FRAGMENT), + ), + ), ), ); @@ -174,6 +189,14 @@ impl SpecializedRenderPipeline for SpritePipeline { let mut shader_defs = Vec::new(); if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), + 1, + )); + shader_defs.push(ShaderDefVal::UInt( + "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), + 2, + )); let method = key.intersection(SpritePipelineKey::TONEMAP_METHOD_RESERVED_BITS); @@ -412,7 +435,6 @@ impl SpriteInstance { #[derive(Resource)] pub struct SpriteMeta { - view_bind_group: Option, sprite_index_buffer: RawBufferVec, sprite_instance_buffer: RawBufferVec, } @@ -420,13 +442,17 @@ pub struct SpriteMeta { impl Default for SpriteMeta { fn default() -> Self { Self { - view_bind_group: None, sprite_index_buffer: RawBufferVec::::new(BufferUsages::INDEX), sprite_instance_buffer: RawBufferVec::::new(BufferUsages::VERTEX), } } } +#[derive(Component)] +pub struct SpriteViewBindGroup { + pub value: BindGroup, +} + #[derive(Component, PartialEq, Eq, Clone)] pub struct SpriteBatch { image_handle_id: AssetId, @@ -528,13 +554,46 @@ pub fn queue_sprites( } #[allow(clippy::too_many_arguments)] -pub fn prepare_sprites( +pub fn prepare_sprite_view_bind_groups( + mut commands: Commands, + render_device: Res, + sprite_pipeline: Res, + view_uniforms: Res, + views: Query<(Entity, &Tonemapping), With>, + tonemapping_luts: Res, + images: Res>, + fallback_image: Res, +) { + let Some(view_binding) = view_uniforms.uniforms.binding() else { + return; + }; + + for (entity, tonemapping) in &views { + let lut_bindings = + get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image); + let view_bind_group = render_device.create_bind_group( + "mesh2d_view_bind_group", + &sprite_pipeline.view_layout, + &BindGroupEntries::with_indices(( + (0, view_binding.clone()), + (1, lut_bindings.0), + (2, lut_bindings.1), + )), + ); + + commands.entity(entity).insert(SpriteViewBindGroup { + value: view_bind_group, + }); + } +} + +#[allow(clippy::too_many_arguments)] +pub fn prepare_sprite_image_bind_groups( mut commands: Commands, mut previous_len: Local, render_device: Res, render_queue: Res, mut sprite_meta: ResMut, - view_uniforms: Res, sprite_pipeline: Res, mut image_bind_groups: ResMut, gpu_images: Res>, @@ -555,163 +614,155 @@ pub fn prepare_sprites( }; } - if let Some(view_binding) = view_uniforms.uniforms.binding() { - let mut batches: Vec<(Entity, SpriteBatch)> = Vec::with_capacity(*previous_len); + let mut batches: Vec<(Entity, SpriteBatch)> = Vec::with_capacity(*previous_len); - // Clear the sprite instances - sprite_meta.sprite_instance_buffer.clear(); + // Clear the sprite instances + sprite_meta.sprite_instance_buffer.clear(); - sprite_meta.view_bind_group = Some(render_device.create_bind_group( - "sprite_view_bind_group", - &sprite_pipeline.view_layout, - &BindGroupEntries::single(view_binding), - )); + // Index buffer indices + let mut index = 0; - // Index buffer indices - let mut index = 0; + let image_bind_groups = &mut *image_bind_groups; - let image_bind_groups = &mut *image_bind_groups; + for transparent_phase in phases.values_mut() { + let mut batch_item_index = 0; + let mut batch_image_size = Vec2::ZERO; + let mut batch_image_handle = AssetId::invalid(); - for transparent_phase in phases.values_mut() { - let mut batch_item_index = 0; - let mut batch_image_size = Vec2::ZERO; - let mut batch_image_handle = AssetId::invalid(); + // Iterate through the phase items and detect when successive sprites that can be batched. + // Spawn an entity with a `SpriteBatch` component for each possible batch. + // Compatible items share the same entity. + for item_index in 0..transparent_phase.items.len() { + let item = &transparent_phase.items[item_index]; + let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity) else { + // If there is a phase item that is not a sprite, then we must start a new + // batch to draw the other phase item(s) and to respect draw order. This can be + // done by invalidating the batch_image_handle + batch_image_handle = AssetId::invalid(); + continue; + }; - // Iterate through the phase items and detect when successive sprites that can be batched. - // Spawn an entity with a `SpriteBatch` component for each possible batch. - // Compatible items share the same entity. - for item_index in 0..transparent_phase.items.len() { - let item = &transparent_phase.items[item_index]; - let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity) else { - // If there is a phase item that is not a sprite, then we must start a new - // batch to draw the other phase item(s) and to respect draw order. This can be - // done by invalidating the batch_image_handle - batch_image_handle = AssetId::invalid(); + let batch_image_changed = batch_image_handle != extracted_sprite.image_handle_id; + if batch_image_changed { + let Some(gpu_image) = gpu_images.get(extracted_sprite.image_handle_id) else { continue; }; - let batch_image_changed = batch_image_handle != extracted_sprite.image_handle_id; - if batch_image_changed { - let Some(gpu_image) = gpu_images.get(extracted_sprite.image_handle_id) else { - continue; - }; - - batch_image_size = gpu_image.size.as_vec2(); - batch_image_handle = extracted_sprite.image_handle_id; - image_bind_groups - .values - .entry(batch_image_handle) - .or_insert_with(|| { - render_device.create_bind_group( - "sprite_material_bind_group", - &sprite_pipeline.material_layout, - &BindGroupEntries::sequential(( - &gpu_image.texture_view, - &gpu_image.sampler, - )), - ) - }); - } - - // By default, the size of the quad is the size of the texture - let mut quad_size = batch_image_size; - - // Calculate vertex data for this item - let mut uv_offset_scale: Vec4; - - // If a rect is specified, adjust UVs and the size of the quad - if let Some(rect) = extracted_sprite.rect { - let rect_size = rect.size(); - uv_offset_scale = Vec4::new( - rect.min.x / batch_image_size.x, - rect.max.y / batch_image_size.y, - rect_size.x / batch_image_size.x, - -rect_size.y / batch_image_size.y, - ); - quad_size = rect_size; - } else { - uv_offset_scale = Vec4::new(0.0, 1.0, 1.0, -1.0); - } - - if extracted_sprite.flip_x { - uv_offset_scale.x += uv_offset_scale.z; - uv_offset_scale.z *= -1.0; - } - if extracted_sprite.flip_y { - uv_offset_scale.y += uv_offset_scale.w; - uv_offset_scale.w *= -1.0; - } - - // Override the size if a custom one is specified - if let Some(custom_size) = extracted_sprite.custom_size { - quad_size = custom_size; - } - let transform = extracted_sprite.transform.affine() - * Affine3A::from_scale_rotation_translation( - quad_size.extend(1.0), - Quat::IDENTITY, - (quad_size * (-extracted_sprite.anchor - Vec2::splat(0.5))).extend(0.0), - ); - - // Store the vertex data and add the item to the render phase - sprite_meta - .sprite_instance_buffer - .push(SpriteInstance::from( - &transform, - &extracted_sprite.color, - &uv_offset_scale, - )); - - if batch_image_changed { - batch_item_index = item_index; - - batches.push(( - item.entity, - SpriteBatch { - image_handle_id: batch_image_handle, - range: index..index, - }, - )); - } - - transparent_phase.items[batch_item_index] - .batch_range_mut() - .end += 1; - batches.last_mut().unwrap().1.range.end += 1; - index += 1; + batch_image_size = gpu_image.size.as_vec2(); + batch_image_handle = extracted_sprite.image_handle_id; + image_bind_groups + .values + .entry(batch_image_handle) + .or_insert_with(|| { + render_device.create_bind_group( + "sprite_material_bind_group", + &sprite_pipeline.material_layout, + &BindGroupEntries::sequential(( + &gpu_image.texture_view, + &gpu_image.sampler, + )), + ) + }); } - } - sprite_meta - .sprite_instance_buffer - .write_buffer(&render_device, &render_queue); - if sprite_meta.sprite_index_buffer.len() != 6 { - sprite_meta.sprite_index_buffer.clear(); + // By default, the size of the quad is the size of the texture + let mut quad_size = batch_image_size; - // NOTE: This code is creating 6 indices pointing to 4 vertices. - // The vertices form the corners of a quad based on their two least significant bits. - // 10 11 - // - // 00 01 - // The sprite shader can then use the two least significant bits as the vertex index. - // The rest of the properties to transform the vertex positions and UVs (which are - // implicit) are baked into the instance transform, and UV offset and scale. - // See bevy_sprite/src/render/sprite.wgsl for the details. - sprite_meta.sprite_index_buffer.push(2); - sprite_meta.sprite_index_buffer.push(0); - sprite_meta.sprite_index_buffer.push(1); - sprite_meta.sprite_index_buffer.push(1); - sprite_meta.sprite_index_buffer.push(3); - sprite_meta.sprite_index_buffer.push(2); + // Calculate vertex data for this item + let mut uv_offset_scale: Vec4; + // If a rect is specified, adjust UVs and the size of the quad + if let Some(rect) = extracted_sprite.rect { + let rect_size = rect.size(); + uv_offset_scale = Vec4::new( + rect.min.x / batch_image_size.x, + rect.max.y / batch_image_size.y, + rect_size.x / batch_image_size.x, + -rect_size.y / batch_image_size.y, + ); + quad_size = rect_size; + } else { + uv_offset_scale = Vec4::new(0.0, 1.0, 1.0, -1.0); + } + + if extracted_sprite.flip_x { + uv_offset_scale.x += uv_offset_scale.z; + uv_offset_scale.z *= -1.0; + } + if extracted_sprite.flip_y { + uv_offset_scale.y += uv_offset_scale.w; + uv_offset_scale.w *= -1.0; + } + + // Override the size if a custom one is specified + if let Some(custom_size) = extracted_sprite.custom_size { + quad_size = custom_size; + } + let transform = extracted_sprite.transform.affine() + * Affine3A::from_scale_rotation_translation( + quad_size.extend(1.0), + Quat::IDENTITY, + (quad_size * (-extracted_sprite.anchor - Vec2::splat(0.5))).extend(0.0), + ); + + // Store the vertex data and add the item to the render phase sprite_meta - .sprite_index_buffer - .write_buffer(&render_device, &render_queue); - } + .sprite_instance_buffer + .push(SpriteInstance::from( + &transform, + &extracted_sprite.color, + &uv_offset_scale, + )); - *previous_len = batches.len(); - commands.insert_or_spawn_batch(batches); + if batch_image_changed { + batch_item_index = item_index; + + batches.push(( + item.entity, + SpriteBatch { + image_handle_id: batch_image_handle, + range: index..index, + }, + )); + } + + transparent_phase.items[batch_item_index] + .batch_range_mut() + .end += 1; + batches.last_mut().unwrap().1.range.end += 1; + index += 1; + } } + sprite_meta + .sprite_instance_buffer + .write_buffer(&render_device, &render_queue); + + if sprite_meta.sprite_index_buffer.len() != 6 { + sprite_meta.sprite_index_buffer.clear(); + + // NOTE: This code is creating 6 indices pointing to 4 vertices. + // The vertices form the corners of a quad based on their two least significant bits. + // 10 11 + // + // 00 01 + // The sprite shader can then use the two least significant bits as the vertex index. + // The rest of the properties to transform the vertex positions and UVs (which are + // implicit) are baked into the instance transform, and UV offset and scale. + // See bevy_sprite/src/render/sprite.wgsl for the details. + sprite_meta.sprite_index_buffer.push(2); + sprite_meta.sprite_index_buffer.push(0); + sprite_meta.sprite_index_buffer.push(1); + sprite_meta.sprite_index_buffer.push(1); + sprite_meta.sprite_index_buffer.push(3); + sprite_meta.sprite_index_buffer.push(2); + + sprite_meta + .sprite_index_buffer + .write_buffer(&render_device, &render_queue); + } + + *previous_len = batches.len(); + commands.insert_or_spawn_batch(batches); } /// [`RenderCommand`] for sprite rendering. @@ -724,22 +775,18 @@ pub type DrawSprite = ( pub struct SetSpriteViewBindGroup; impl RenderCommand

for SetSpriteViewBindGroup { - type Param = SRes; - type ViewQuery = Read; + type Param = (); + type ViewQuery = (Read, Read); type ItemQuery = (); fn render<'w>( _item: &P, - view_uniform: &'_ ViewUniformOffset, + (view_uniform, sprite_view_bind_group): ROQueryItem<'w, Self::ViewQuery>, _entity: Option<()>, - sprite_meta: SystemParamItem<'w, '_, Self::Param>, + _param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - pass.set_bind_group( - I, - sprite_meta.into_inner().view_bind_group.as_ref().unwrap(), - &[view_uniform.offset], - ); + pass.set_bind_group(I, &sprite_view_bind_group.value, &[view_uniform.offset]); RenderCommandResult::Success } } diff --git a/crates/bevy_sprite/src/render/sprite.wgsl b/crates/bevy_sprite/src/render/sprite.wgsl index 48f0235155..7d820fcc9e 100644 --- a/crates/bevy_sprite/src/render/sprite.wgsl +++ b/crates/bevy_sprite/src/render/sprite.wgsl @@ -7,7 +7,7 @@ view::View, } -@group(0) @binding(0) var view: View; +#import bevy_sprite::sprite_view_bindings::view struct VertexInput { @builtin(vertex_index) index: u32, diff --git a/crates/bevy_sprite/src/render/sprite_view_bindings.wgsl b/crates/bevy_sprite/src/render/sprite_view_bindings.wgsl new file mode 100644 index 0000000000..e3e990538c --- /dev/null +++ b/crates/bevy_sprite/src/render/sprite_view_bindings.wgsl @@ -0,0 +1,9 @@ +#define_import_path bevy_sprite::sprite_view_bindings + +#import bevy_render::view::View + +@group(0) @binding(0) var view: View; + +@group(0) @binding(1) var dt_lut_texture: texture_3d; +@group(0) @binding(2) var dt_lut_sampler: sampler; +