mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
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<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>>, asset_server: Res<AssetServer>, ) { 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<ButtonInput<KeyCode>>, 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.
This commit is contained in:
parent
f45eddfe82
commit
cdc605cc48
16 changed files with 342 additions and 200 deletions
|
@ -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<f32>;
|
||||
@group(0) @binding(#TONEMAPPING_LUT_SAMPLER_BINDING_INDEX) var dt_lut_sampler: sampler;
|
||||
|
|
@ -28,6 +28,9 @@ const TONEMAPPING_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(1701536
|
|||
const TONEMAPPING_SHARED_SHADER_HANDLE: Handle<Shader> =
|
||||
Handle::weak_from_u128(2499430578245347910);
|
||||
|
||||
const TONEMAPPING_LUT_BINDINGS_SHADER_HANDLE: Handle<Shader> =
|
||||
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::<TonemappingLuts>() {
|
||||
let mut images = app.world_mut().resource_mut::<Assets<Image>>();
|
||||
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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<uniform> view: View;
|
||||
|
|
|
@ -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<f32>;
|
||||
@group(0) @binding(4) var dt_lut_sampler: sampler;
|
||||
#else
|
||||
@group(0) @binding(20) var dt_lut_texture: texture_3d<f32>;
|
||||
@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<f32>) -> vec3<f32> {
|
|||
// https://github.com/MrLixm/AgXc
|
||||
// https://github.com/sobotka/AgX
|
||||
|
||||
// pow() but safe for NaNs/negatives
|
||||
fn powsafe(color: vec3<f32>, power: f32) -> vec3<f32> {
|
||||
return pow(abs(color), vec3(power)) * sign(color);
|
||||
}
|
||||
|
||||
/*
|
||||
Increase color saturation of the given color data.
|
||||
:param color: expected sRGB primaries input
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<f32>;
|
||||
@group(0) @binding(21) var dt_lut_sampler: sampler;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<f32>, frag_coord: vec3<f32>, view_z: f32, N: vec3<f32>, V: vec3<f32>, F0: vec3<f32>, ior: f32, thickness: f32, perceptual_roughness: f32, specular_transmissive_color: vec3<f32>, transmitted_environment_light_specular: vec3<f32>) -> vec3<f32> {
|
||||
// Calculate the ratio between refaction indexes. Assume air/vacuum for the space outside the mesh
|
||||
|
|
|
@ -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<f32>, power: f32) -> vec3<f32> {
|
||||
return pow(abs(color), vec3(power)) * sign(color);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ use bevy_render::{
|
|||
pub struct SpritePlugin;
|
||||
|
||||
pub const SPRITE_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(2763343953151597127);
|
||||
pub const SPRITE_VIEW_BINDINGS_SHADER_HANDLE: Handle<Shader> =
|
||||
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::<TextureAtlasLayout>()
|
||||
.register_asset_reflect::<TextureAtlasLayout>()
|
||||
.register_type::<Sprite>()
|
||||
|
@ -146,7 +154,8 @@ impl Plugin for SpritePlugin {
|
|||
queue_sprites
|
||||
.in_set(RenderSet::Queue)
|
||||
.ambiguous_with(queue_material2d_meshes::<ColorMaterial>),
|
||||
prepare_sprites.in_set(RenderSet::PrepareBindGroups),
|
||||
prepare_sprite_image_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
prepare_sprite_view_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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::<ViewUniform>(true),
|
||||
uniform_buffer::<GlobalsUniform>(false),
|
||||
(0, uniform_buffer::<ViewUniform>(true)),
|
||||
(1, uniform_buffer::<GlobalsUniform>(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<RenderDevice>,
|
||||
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
views: Query<Entity, With<ExtractedView>>,
|
||||
views: Query<(Entity, &Tonemapping), With<ExtractedView>>,
|
||||
globals_buffer: Res<GlobalsBuffer>,
|
||||
tonemapping_luts: Res<TonemappingLuts>,
|
||||
images: Res<RenderAssets<GpuImage>>,
|
||||
fallback_image: Res<FallbackImage>,
|
||||
) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,3 +6,6 @@
|
|||
@group(0) @binding(0) var<uniform> view: View;
|
||||
|
||||
@group(0) @binding(1) var<uniform> globals: Globals;
|
||||
|
||||
@group(0) @binding(2) var dt_lut_texture: texture_3d<f32>;
|
||||
@group(0) @binding(3) var dt_lut_sampler: sampler;
|
||||
|
|
|
@ -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::<ViewUniform>(true),
|
||||
(
|
||||
(0, uniform_buffer::<ViewUniform>(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<BindGroup>,
|
||||
sprite_index_buffer: RawBufferVec<u32>,
|
||||
sprite_instance_buffer: RawBufferVec<SpriteInstance>,
|
||||
}
|
||||
|
@ -420,13 +442,17 @@ pub struct SpriteMeta {
|
|||
impl Default for SpriteMeta {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
view_bind_group: None,
|
||||
sprite_index_buffer: RawBufferVec::<u32>::new(BufferUsages::INDEX),
|
||||
sprite_instance_buffer: RawBufferVec::<SpriteInstance>::new(BufferUsages::VERTEX),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct SpriteViewBindGroup {
|
||||
pub value: BindGroup,
|
||||
}
|
||||
|
||||
#[derive(Component, PartialEq, Eq, Clone)]
|
||||
pub struct SpriteBatch {
|
||||
image_handle_id: AssetId<Image>,
|
||||
|
@ -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<RenderDevice>,
|
||||
sprite_pipeline: Res<SpritePipeline>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
views: Query<(Entity, &Tonemapping), With<ExtractedView>>,
|
||||
tonemapping_luts: Res<TonemappingLuts>,
|
||||
images: Res<RenderAssets<GpuImage>>,
|
||||
fallback_image: Res<FallbackImage>,
|
||||
) {
|
||||
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<usize>,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut sprite_meta: ResMut<SpriteMeta>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
sprite_pipeline: Res<SpritePipeline>,
|
||||
mut image_bind_groups: ResMut<ImageBindGroups>,
|
||||
gpu_images: Res<RenderAssets<GpuImage>>,
|
||||
|
@ -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<const I: usize>;
|
||||
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteViewBindGroup<I> {
|
||||
type Param = SRes<SpriteMeta>;
|
||||
type ViewQuery = Read<ViewUniformOffset>;
|
||||
type Param = ();
|
||||
type ViewQuery = (Read<ViewUniformOffset>, Read<SpriteViewBindGroup>);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
view::View,
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> view: View;
|
||||
#import bevy_sprite::sprite_view_bindings::view
|
||||
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) index: u32,
|
||||
|
|
9
crates/bevy_sprite/src/render/sprite_view_bindings.wgsl
Normal file
9
crates/bevy_sprite/src/render/sprite_view_bindings.wgsl
Normal file
|
@ -0,0 +1,9 @@
|
|||
#define_import_path bevy_sprite::sprite_view_bindings
|
||||
|
||||
#import bevy_render::view::View
|
||||
|
||||
@group(0) @binding(0) var<uniform> view: View;
|
||||
|
||||
@group(0) @binding(1) var dt_lut_texture: texture_3d<f32>;
|
||||
@group(0) @binding(2) var dt_lut_sampler: sampler;
|
||||
|
Loading…
Reference in a new issue