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:
arcashka 2024-05-28 15:09:26 +03:00 committed by GitHub
parent f45eddfe82
commit cdc605cc48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 342 additions and 200 deletions

View file

@ -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;

View file

@ -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());
}

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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),
),
);
};

View file

@ -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,
});
}
}

View file

@ -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;

View file

@ -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
}
}

View file

@ -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,

View 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;