mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +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> =
|
const TONEMAPPING_SHARED_SHADER_HANDLE: Handle<Shader> =
|
||||||
Handle::weak_from_u128(2499430578245347910);
|
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
|
/// 3D LUT (look up table) textures used for tonemapping
|
||||||
#[derive(Resource, Clone, ExtractResource)]
|
#[derive(Resource, Clone, ExtractResource)]
|
||||||
pub struct TonemappingLuts {
|
pub struct TonemappingLuts {
|
||||||
|
@ -52,6 +55,12 @@ impl Plugin for TonemappingPlugin {
|
||||||
"tonemapping_shared.wgsl",
|
"tonemapping_shared.wgsl",
|
||||||
Shader::from_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>() {
|
if !app.world().is_resource_added::<TonemappingLuts>() {
|
||||||
let mut images = app.world_mut().resource_mut::<Assets<Image>>();
|
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 {
|
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||||
let mut shader_defs = Vec::new();
|
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 {
|
if let DebandDither::Enabled = key.deband_dither {
|
||||||
shader_defs.push("DEBAND_DITHER".into());
|
shader_defs.push("DEBAND_DITHER".into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
#define TONEMAPPING_PASS
|
#define TONEMAPPING_PASS
|
||||||
|
|
||||||
#import bevy_render::view::View
|
#import bevy_render::{
|
||||||
|
view::View,
|
||||||
|
maths::powsafe,
|
||||||
|
}
|
||||||
#import bevy_core_pipeline::{
|
#import bevy_core_pipeline::{
|
||||||
fullscreen_vertex_shader::FullscreenVertexOutput,
|
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;
|
@group(0) @binding(0) var<uniform> view: View;
|
||||||
|
|
|
@ -3,17 +3,13 @@
|
||||||
#import bevy_render::{
|
#import bevy_render::{
|
||||||
view::ColorGrading,
|
view::ColorGrading,
|
||||||
color_operations::{hsv_to_rgb, rgb_to_hsv},
|
color_operations::{hsv_to_rgb, rgb_to_hsv},
|
||||||
maths::PI_2
|
maths::{PI_2, powsafe},
|
||||||
}
|
}
|
||||||
|
|
||||||
// hack !! not sure what to do with this
|
#import bevy_core_pipeline::tonemapping_lut_bindings::{
|
||||||
#ifdef TONEMAPPING_PASS
|
dt_lut_texture,
|
||||||
@group(0) @binding(3) var dt_lut_texture: texture_3d<f32>;
|
dt_lut_sampler,
|
||||||
@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
|
|
||||||
|
|
||||||
// Half the size of the crossfade region between shadows and midtones and
|
// 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
|
// 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/MrLixm/AgXc
|
||||||
// https://github.com/sobotka/AgX
|
// 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.
|
Increase color saturation of the given color data.
|
||||||
:param color: expected sRGB primaries input
|
:param color: expected sRGB primaries input
|
||||||
|
|
|
@ -254,6 +254,14 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
|
||||||
|
|
||||||
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
|
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
|
||||||
shader_defs.push("TONEMAP_IN_SHADER".into());
|
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);
|
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
|
||||||
|
|
||||||
|
|
|
@ -1667,6 +1667,14 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||||
|
|
||||||
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
|
if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
|
||||||
shader_defs.push("TONEMAP_IN_SHADER".into());
|
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);
|
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;
|
@group(0) @binding(19) var irradiance_volume_sampler: sampler;
|
||||||
#endif
|
#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(20) var dt_lut_texture: texture_3d<f32>;
|
||||||
@group(0) @binding(21) var dt_lut_sampler: sampler;
|
@group(0) @binding(21) var dt_lut_sampler: sampler;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
irradiance_volume,
|
irradiance_volume,
|
||||||
mesh_types::{MESH_FLAGS_SHADOW_RECEIVER_BIT, MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT},
|
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
|
#ifdef MESHLET_MESH_MATERIAL_PASS
|
||||||
#import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput
|
#import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput
|
||||||
|
@ -28,7 +28,10 @@
|
||||||
#import bevy_pbr::environment_map
|
#import bevy_pbr::environment_map
|
||||||
#endif
|
#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`.
|
// 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
|
// How this is done depends on whether we're rendering meshlets or regular
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
|
|
||||||
#import bevy_render::maths::PI
|
#import bevy_render::maths::PI
|
||||||
|
|
||||||
#import bevy_core_pipeline::tonemapping::{
|
#ifdef TONEMAP_IN_SHADER
|
||||||
approximate_inverse_tone_mapping
|
#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> {
|
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
|
// 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 {
|
) -> bool {
|
||||||
return dot(plane, sphere_center) + sphere_radius > 0.0;
|
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 struct SpritePlugin;
|
||||||
|
|
||||||
pub const SPRITE_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(2763343953151597127);
|
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.
|
/// System set for sprite rendering.
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
|
@ -94,6 +96,12 @@ impl Plugin for SpritePlugin {
|
||||||
"render/sprite.wgsl",
|
"render/sprite.wgsl",
|
||||||
Shader::from_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>()
|
app.init_asset::<TextureAtlasLayout>()
|
||||||
.register_asset_reflect::<TextureAtlasLayout>()
|
.register_asset_reflect::<TextureAtlasLayout>()
|
||||||
.register_type::<Sprite>()
|
.register_type::<Sprite>()
|
||||||
|
@ -146,7 +154,8 @@ impl Plugin for SpritePlugin {
|
||||||
queue_sprites
|
queue_sprites
|
||||||
.in_set(RenderSet::Queue)
|
.in_set(RenderSet::Queue)
|
||||||
.ambiguous_with(queue_material2d_meshes::<ColorMaterial>),
|
.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_asset::{load_internal_asset, AssetId, Handle};
|
||||||
|
|
||||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
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_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::entity::EntityHashMap;
|
use bevy_ecs::entity::EntityHashMap;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
@ -16,6 +19,7 @@ use bevy_render::batching::no_gpu_preprocessing::{
|
||||||
BatchedInstanceBuffer,
|
BatchedInstanceBuffer,
|
||||||
};
|
};
|
||||||
use bevy_render::mesh::{GpuMesh, MeshVertexBufferLayoutRef};
|
use bevy_render::mesh::{GpuMesh, MeshVertexBufferLayoutRef};
|
||||||
|
use bevy_render::texture::FallbackImage;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
batching::{GetBatchData, NoAutomaticBatching},
|
batching::{GetBatchData, NoAutomaticBatching},
|
||||||
globals::{GlobalsBuffer, GlobalsUniform},
|
globals::{GlobalsBuffer, GlobalsUniform},
|
||||||
|
@ -263,14 +267,22 @@ impl FromWorld for Mesh2dPipeline {
|
||||||
)> = SystemState::new(world);
|
)> = SystemState::new(world);
|
||||||
let (render_device, render_queue, default_sampler) = system_state.get_mut(world);
|
let (render_device, render_queue, default_sampler) = system_state.get_mut(world);
|
||||||
let render_device = render_device.into_inner();
|
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(
|
let view_layout = render_device.create_bind_group_layout(
|
||||||
"mesh2d_view_layout",
|
"mesh2d_view_layout",
|
||||||
&BindGroupLayoutEntries::sequential(
|
&BindGroupLayoutEntries::with_indices(
|
||||||
ShaderStages::VERTEX_FRAGMENT,
|
ShaderStages::VERTEX_FRAGMENT,
|
||||||
(
|
(
|
||||||
// View
|
(0, uniform_buffer::<ViewUniform>(true)),
|
||||||
uniform_buffer::<ViewUniform>(true),
|
(1, uniform_buffer::<GlobalsUniform>(false)),
|
||||||
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) {
|
if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) {
|
||||||
shader_defs.push("TONEMAP_IN_SHADER".into());
|
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);
|
let method = key.intersection(Mesh2dPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
|
||||||
|
|
||||||
|
@ -584,29 +604,42 @@ pub struct Mesh2dViewBindGroup {
|
||||||
pub value: BindGroup,
|
pub value: BindGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn prepare_mesh2d_view_bind_groups(
|
pub fn prepare_mesh2d_view_bind_groups(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
mesh2d_pipeline: Res<Mesh2dPipeline>,
|
||||||
view_uniforms: Res<ViewUniforms>,
|
view_uniforms: Res<ViewUniforms>,
|
||||||
views: Query<Entity, With<ExtractedView>>,
|
views: Query<(Entity, &Tonemapping), With<ExtractedView>>,
|
||||||
globals_buffer: Res<GlobalsBuffer>,
|
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(),
|
view_uniforms.uniforms.binding(),
|
||||||
globals_buffer.buffer.binding(),
|
globals_buffer.buffer.binding(),
|
||||||
) {
|
) else {
|
||||||
for entity in &views {
|
return;
|
||||||
let view_bind_group = render_device.create_bind_group(
|
};
|
||||||
"mesh2d_view_bind_group",
|
|
||||||
&mesh2d_pipeline.view_layout,
|
|
||||||
&BindGroupEntries::sequential((view_binding.clone(), globals.clone())),
|
|
||||||
);
|
|
||||||
|
|
||||||
commands.entity(entity).insert(Mesh2dViewBindGroup {
|
for (entity, tonemapping) in &views {
|
||||||
value: view_bind_group,
|
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(0) var<uniform> view: View;
|
||||||
|
|
||||||
@group(0) @binding(1) var<uniform> globals: Globals;
|
@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_color::LinearRgba;
|
||||||
use bevy_core_pipeline::{
|
use bevy_core_pipeline::{
|
||||||
core_2d::Transparent2d,
|
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::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{lifetimeless::*, SystemParamItem, SystemState},
|
system::{lifetimeless::*, SystemParamItem, SystemState},
|
||||||
|
@ -28,7 +31,8 @@ use bevy_render::{
|
||||||
},
|
},
|
||||||
renderer::{RenderDevice, RenderQueue},
|
renderer::{RenderDevice, RenderQueue},
|
||||||
texture::{
|
texture::{
|
||||||
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
|
BevyDefault, DefaultImageSampler, FallbackImage, GpuImage, Image, ImageSampler,
|
||||||
|
TextureFormatPixelInfo,
|
||||||
},
|
},
|
||||||
view::{
|
view::{
|
||||||
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
|
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
|
||||||
|
@ -57,11 +61,22 @@ impl FromWorld for SpritePipeline {
|
||||||
)> = SystemState::new(world);
|
)> = SystemState::new(world);
|
||||||
let (render_device, default_sampler, render_queue) = system_state.get_mut(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(
|
let view_layout = render_device.create_bind_group_layout(
|
||||||
"sprite_view_layout",
|
"sprite_view_layout",
|
||||||
&BindGroupLayoutEntries::single(
|
&BindGroupLayoutEntries::with_indices(
|
||||||
ShaderStages::VERTEX_FRAGMENT,
|
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();
|
let mut shader_defs = Vec::new();
|
||||||
if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) {
|
if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) {
|
||||||
shader_defs.push("TONEMAP_IN_SHADER".into());
|
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);
|
let method = key.intersection(SpritePipelineKey::TONEMAP_METHOD_RESERVED_BITS);
|
||||||
|
|
||||||
|
@ -412,7 +435,6 @@ impl SpriteInstance {
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct SpriteMeta {
|
pub struct SpriteMeta {
|
||||||
view_bind_group: Option<BindGroup>,
|
|
||||||
sprite_index_buffer: RawBufferVec<u32>,
|
sprite_index_buffer: RawBufferVec<u32>,
|
||||||
sprite_instance_buffer: RawBufferVec<SpriteInstance>,
|
sprite_instance_buffer: RawBufferVec<SpriteInstance>,
|
||||||
}
|
}
|
||||||
|
@ -420,13 +442,17 @@ pub struct SpriteMeta {
|
||||||
impl Default for SpriteMeta {
|
impl Default for SpriteMeta {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_bind_group: None,
|
|
||||||
sprite_index_buffer: RawBufferVec::<u32>::new(BufferUsages::INDEX),
|
sprite_index_buffer: RawBufferVec::<u32>::new(BufferUsages::INDEX),
|
||||||
sprite_instance_buffer: RawBufferVec::<SpriteInstance>::new(BufferUsages::VERTEX),
|
sprite_instance_buffer: RawBufferVec::<SpriteInstance>::new(BufferUsages::VERTEX),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SpriteViewBindGroup {
|
||||||
|
pub value: BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component, PartialEq, Eq, Clone)]
|
#[derive(Component, PartialEq, Eq, Clone)]
|
||||||
pub struct SpriteBatch {
|
pub struct SpriteBatch {
|
||||||
image_handle_id: AssetId<Image>,
|
image_handle_id: AssetId<Image>,
|
||||||
|
@ -528,13 +554,46 @@ pub fn queue_sprites(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[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 commands: Commands,
|
||||||
mut previous_len: Local<usize>,
|
mut previous_len: Local<usize>,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
render_queue: Res<RenderQueue>,
|
render_queue: Res<RenderQueue>,
|
||||||
mut sprite_meta: ResMut<SpriteMeta>,
|
mut sprite_meta: ResMut<SpriteMeta>,
|
||||||
view_uniforms: Res<ViewUniforms>,
|
|
||||||
sprite_pipeline: Res<SpritePipeline>,
|
sprite_pipeline: Res<SpritePipeline>,
|
||||||
mut image_bind_groups: ResMut<ImageBindGroups>,
|
mut image_bind_groups: ResMut<ImageBindGroups>,
|
||||||
gpu_images: Res<RenderAssets<GpuImage>>,
|
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
|
// Clear the sprite instances
|
||||||
sprite_meta.sprite_instance_buffer.clear();
|
sprite_meta.sprite_instance_buffer.clear();
|
||||||
|
|
||||||
sprite_meta.view_bind_group = Some(render_device.create_bind_group(
|
// Index buffer indices
|
||||||
"sprite_view_bind_group",
|
let mut index = 0;
|
||||||
&sprite_pipeline.view_layout,
|
|
||||||
&BindGroupEntries::single(view_binding),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Index buffer indices
|
let image_bind_groups = &mut *image_bind_groups;
|
||||||
let mut index = 0;
|
|
||||||
|
|
||||||
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() {
|
// Iterate through the phase items and detect when successive sprites that can be batched.
|
||||||
let mut batch_item_index = 0;
|
// Spawn an entity with a `SpriteBatch` component for each possible batch.
|
||||||
let mut batch_image_size = Vec2::ZERO;
|
// Compatible items share the same entity.
|
||||||
let mut batch_image_handle = AssetId::invalid();
|
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.
|
let batch_image_changed = batch_image_handle != extracted_sprite.image_handle_id;
|
||||||
// Spawn an entity with a `SpriteBatch` component for each possible batch.
|
if batch_image_changed {
|
||||||
// Compatible items share the same entity.
|
let Some(gpu_image) = gpu_images.get(extracted_sprite.image_handle_id) else {
|
||||||
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;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let batch_image_changed = batch_image_handle != extracted_sprite.image_handle_id;
|
batch_image_size = gpu_image.size.as_vec2();
|
||||||
if batch_image_changed {
|
batch_image_handle = extracted_sprite.image_handle_id;
|
||||||
let Some(gpu_image) = gpu_images.get(extracted_sprite.image_handle_id) else {
|
image_bind_groups
|
||||||
continue;
|
.values
|
||||||
};
|
.entry(batch_image_handle)
|
||||||
|
.or_insert_with(|| {
|
||||||
batch_image_size = gpu_image.size.as_vec2();
|
render_device.create_bind_group(
|
||||||
batch_image_handle = extracted_sprite.image_handle_id;
|
"sprite_material_bind_group",
|
||||||
image_bind_groups
|
&sprite_pipeline.material_layout,
|
||||||
.values
|
&BindGroupEntries::sequential((
|
||||||
.entry(batch_image_handle)
|
&gpu_image.texture_view,
|
||||||
.or_insert_with(|| {
|
&gpu_image.sampler,
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
sprite_meta
|
|
||||||
.sprite_instance_buffer
|
|
||||||
.write_buffer(&render_device, &render_queue);
|
|
||||||
|
|
||||||
if sprite_meta.sprite_index_buffer.len() != 6 {
|
// By default, the size of the quad is the size of the texture
|
||||||
sprite_meta.sprite_index_buffer.clear();
|
let mut quad_size = batch_image_size;
|
||||||
|
|
||||||
// NOTE: This code is creating 6 indices pointing to 4 vertices.
|
// Calculate vertex data for this item
|
||||||
// The vertices form the corners of a quad based on their two least significant bits.
|
let mut uv_offset_scale: Vec4;
|
||||||
// 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);
|
|
||||||
|
|
||||||
|
// 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_meta
|
||||||
.sprite_index_buffer
|
.sprite_instance_buffer
|
||||||
.write_buffer(&render_device, &render_queue);
|
.push(SpriteInstance::from(
|
||||||
}
|
&transform,
|
||||||
|
&extracted_sprite.color,
|
||||||
|
&uv_offset_scale,
|
||||||
|
));
|
||||||
|
|
||||||
*previous_len = batches.len();
|
if batch_image_changed {
|
||||||
commands.insert_or_spawn_batch(batches);
|
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.
|
/// [`RenderCommand`] for sprite rendering.
|
||||||
|
@ -724,22 +775,18 @@ pub type DrawSprite = (
|
||||||
|
|
||||||
pub struct SetSpriteViewBindGroup<const I: usize>;
|
pub struct SetSpriteViewBindGroup<const I: usize>;
|
||||||
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteViewBindGroup<I> {
|
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteViewBindGroup<I> {
|
||||||
type Param = SRes<SpriteMeta>;
|
type Param = ();
|
||||||
type ViewQuery = Read<ViewUniformOffset>;
|
type ViewQuery = (Read<ViewUniformOffset>, Read<SpriteViewBindGroup>);
|
||||||
type ItemQuery = ();
|
type ItemQuery = ();
|
||||||
|
|
||||||
fn render<'w>(
|
fn render<'w>(
|
||||||
_item: &P,
|
_item: &P,
|
||||||
view_uniform: &'_ ViewUniformOffset,
|
(view_uniform, sprite_view_bind_group): ROQueryItem<'w, Self::ViewQuery>,
|
||||||
_entity: Option<()>,
|
_entity: Option<()>,
|
||||||
sprite_meta: SystemParamItem<'w, '_, Self::Param>,
|
_param: SystemParamItem<'w, '_, Self::Param>,
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
pass.set_bind_group(
|
pass.set_bind_group(I, &sprite_view_bind_group.value, &[view_uniform.offset]);
|
||||||
I,
|
|
||||||
sprite_meta.into_inner().view_bind_group.as_ref().unwrap(),
|
|
||||||
&[view_uniform.offset],
|
|
||||||
);
|
|
||||||
RenderCommandResult::Success
|
RenderCommandResult::Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
view::View,
|
view::View,
|
||||||
}
|
}
|
||||||
|
|
||||||
@group(0) @binding(0) var<uniform> view: View;
|
#import bevy_sprite::sprite_view_bindings::view
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
@builtin(vertex_index) index: u32,
|
@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…
Add table
Reference in a new issue