bevy/examples/shader/shader_instancing.rs
Boxy 024d98457c yeet unsound lifetime annotations on Query methods (#4243)
# Objective
Continuation of #2964 (I really should have checked other methods when I made that PR)

yeet unsound lifetime annotations on `Query` methods.
Example unsoundness:
```rust
use bevy::prelude::*;

fn main() {
    App::new().add_startup_system(bar).add_system(foo).run();
}

pub fn bar(mut cmds: Commands) {
    let e = cmds.spawn().insert(Foo { a: 10 }).id();
    cmds.insert_resource(e);
}

#[derive(Component, Debug, PartialEq, Eq)]
pub struct Foo {
    a: u32,
}
pub fn foo(mut query: Query<&mut Foo>, e: Res<Entity>) {
    dbg!("hi");
    {
        let data: &Foo = query.get(*e).unwrap();
        let data2: Mut<Foo> = query.get_mut(*e).unwrap();
        assert_eq!(data, &*data2); // oops UB
    }

    {
        let data: &Foo = query.single();
        let data2: Mut<Foo> = query.single_mut();
        assert_eq!(data, &*data2); // oops UB
    }

    {
        let data: &Foo = query.get_single().unwrap();
        let data2: Mut<Foo> = query.get_single_mut().unwrap();
        assert_eq!(data, &*data2); // oops UB
    }

    {
        let data: &Foo = query.iter().next().unwrap();
        let data2: Mut<Foo> = query.iter_mut().next().unwrap();
        assert_eq!(data, &*data2); // oops UB
    }

    {
        let mut opt_data: Option<&Foo> = None;
        let mut opt_data_2: Option<Mut<Foo>> = None;
        query.for_each(|data| opt_data = Some(data));
        query.for_each_mut(|data| opt_data_2 = Some(data));
        assert_eq!(opt_data.unwrap(), &*opt_data_2.unwrap()); // oops UB
    }
    dbg!("bye");
}

```

## Solution
yeet unsound lifetime annotations on `Query` methods

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-03-22 02:49:41 +00:00

268 lines
9.2 KiB
Rust

use bevy::{
core_pipeline::Transparent3d,
ecs::system::{lifetimeless::*, SystemParamItem},
math::prelude::*,
pbr::{MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup},
prelude::*,
render::{
mesh::{GpuBufferInfo, MeshVertexBufferLayout},
render_asset::RenderAssets,
render_component::{ExtractComponent, ExtractComponentPlugin},
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,
},
render_resource::*,
renderer::RenderDevice,
view::{ComputedVisibility, ExtractedView, Msaa, NoFrustumCulling, Visibility},
RenderApp, RenderStage,
},
};
use bytemuck::{Pod, Zeroable};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(CustomMaterialPlugin)
.add_startup_system(setup)
.run();
}
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
commands.spawn().insert_bundle((
meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
Transform::from_xyz(0.0, 0.0, 0.0),
GlobalTransform::default(),
InstanceMaterialData(
(1..=10)
.flat_map(|x| (1..=10).map(move |y| (x as f32 / 10.0, y as f32 / 10.0)))
.map(|(x, y)| InstanceData {
position: Vec3::new(x * 10.0 - 5.0, y * 10.0 - 5.0, 0.0),
scale: 1.0,
color: Color::hsla(x * 360., y, 0.5, 1.0).as_rgba_f32(),
})
.collect(),
),
Visibility::default(),
ComputedVisibility::default(),
// NOTE: Frustum culling is done based on the Aabb of the Mesh and the GlobalTransform.
// As the cube is at the origin, if its Aabb moves outside the view frustum, all the
// instanced cubes will be culled.
// The InstanceMaterialData contains the 'GlobalTransform' information for this custom
// instancing, and that is not taken into account with the built-in frustum culling.
// We must disable the built-in frustum culling by adding the `NoFrustumCulling` marker
// component to avoid incorrect culling.
NoFrustumCulling,
));
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}
#[derive(Component)]
struct InstanceMaterialData(Vec<InstanceData>);
impl ExtractComponent for InstanceMaterialData {
type Query = &'static InstanceMaterialData;
type Filter = ();
fn extract_component(item: bevy::ecs::query::QueryItem<Self::Query>) -> Self {
InstanceMaterialData(item.0.clone())
}
}
pub struct CustomMaterialPlugin;
impl Plugin for CustomMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(ExtractComponentPlugin::<InstanceMaterialData>::default());
app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>()
.init_resource::<CustomPipeline>()
.init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
.add_system_to_stage(RenderStage::Queue, queue_custom)
.add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers);
}
}
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
struct InstanceData {
position: Vec3,
scale: f32,
color: [f32; 4],
}
#[allow(clippy::too_many_arguments)]
fn queue_custom(
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
custom_pipeline: Res<CustomPipeline>,
msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedMeshPipelines<CustomPipeline>>,
mut pipeline_cache: ResMut<RenderPipelineCache>,
meshes: Res<RenderAssets<Mesh>>,
material_meshes: Query<
(Entity, &MeshUniform, &Handle<Mesh>),
(With<Handle<Mesh>>, With<InstanceMaterialData>),
>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
) {
let draw_custom = transparent_3d_draw_functions
.read()
.get_id::<DrawCustom>()
.unwrap();
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
for (view, mut transparent_phase) in views.iter_mut() {
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() {
if let Some(mesh) = meshes.get(mesh_handle) {
let key =
msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
let pipeline = pipelines
.specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout)
.unwrap();
transparent_phase.add(Transparent3d {
entity,
pipeline,
draw_function: draw_custom,
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
});
}
}
}
}
#[derive(Component)]
pub struct InstanceBuffer {
buffer: Buffer,
length: usize,
}
fn prepare_instance_buffers(
mut commands: Commands,
query: Query<(Entity, &InstanceMaterialData)>,
render_device: Res<RenderDevice>,
) {
for (entity, instance_data) in query.iter() {
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("instance data buffer"),
contents: bytemuck::cast_slice(instance_data.0.as_slice()),
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
});
commands.entity(entity).insert(InstanceBuffer {
buffer,
length: instance_data.0.len(),
});
}
}
pub struct CustomPipeline {
shader: Handle<Shader>,
mesh_pipeline: MeshPipeline,
}
impl FromWorld for CustomPipeline {
fn from_world(world: &mut World) -> Self {
let world = world.cell();
let asset_server = world.get_resource::<AssetServer>().unwrap();
asset_server.watch_for_changes().unwrap();
let shader = asset_server.load("shaders/instancing.wgsl");
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap();
CustomPipeline {
shader,
mesh_pipeline: mesh_pipeline.clone(),
}
}
}
impl SpecializedMeshPipeline for CustomPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
descriptor.vertex.shader = self.shader.clone();
descriptor.vertex.buffers.push(VertexBufferLayout {
array_stride: std::mem::size_of::<InstanceData>() as u64,
step_mode: VertexStepMode::Instance,
attributes: vec![
VertexAttribute {
format: VertexFormat::Float32x4,
offset: 0,
shader_location: 3, // shader locations 0-2 are taken up by Position, Normal and UV attributes
},
VertexAttribute {
format: VertexFormat::Float32x4,
offset: VertexFormat::Float32x4.size(),
shader_location: 4,
},
],
});
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone();
descriptor.layout = Some(vec![
self.mesh_pipeline.view_layout.clone(),
self.mesh_pipeline.mesh_layout.clone(),
]);
Ok(descriptor)
}
}
type DrawCustom = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshBindGroup<1>,
DrawMeshInstanced,
);
pub struct DrawMeshInstanced;
impl EntityRenderCommand for DrawMeshInstanced {
type Param = (
SRes<RenderAssets<Mesh>>,
SQuery<Read<Handle<Mesh>>>,
SQuery<Read<InstanceBuffer>>,
);
#[inline]
fn render<'w>(
_view: Entity,
item: Entity,
(meshes, mesh_query, instance_buffer_query): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let mesh_handle = mesh_query.get(item).unwrap();
let instance_buffer = instance_buffer_query.get_inner(item).unwrap();
let gpu_mesh = match meshes.into_inner().get(mesh_handle) {
Some(gpu_mesh) => gpu_mesh,
None => return RenderCommandResult::Failure,
};
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
pass.set_vertex_buffer(1, instance_buffer.buffer.slice(..));
match &gpu_mesh.buffer_info {
GpuBufferInfo::Indexed {
buffer,
index_format,
count,
} => {
pass.set_index_buffer(buffer.slice(..), 0, *index_format);
pass.draw_indexed(0..*count, 0, 0..instance_buffer.length as u32);
}
GpuBufferInfo::NonIndexed { vertex_count } => {
pass.draw(0..*vertex_count, 0..instance_buffer.length as u32);
}
}
RenderCommandResult::Success
}
}