Modular Rendering (#2831)

This changes how render logic is composed to make it much more modular. Previously, all extraction logic was centralized for a given "type" of rendered thing. For example, we extracted meshes into a vector of ExtractedMesh, which contained the mesh and material asset handles, the transform, etc. We looked up bindings for "drawn things" using their index in the `Vec<ExtractedMesh>`. This worked fine for built in rendering, but made it hard to reuse logic for "custom" rendering. It also prevented us from reusing things like "extracted transforms" across contexts.

To make rendering more modular, I made a number of changes:

* Entities now drive rendering:
  * We extract "render components" from "app components" and store them _on_ entities. No more centralized uber lists! We now have true "ECS-driven rendering"
  * To make this perform well, I implemented #2673 in upstream Bevy for fast batch insertions into specific entities. This was merged into the `pipelined-rendering` branch here: #2815
* Reworked the `Draw` abstraction:
  * Generic `PhaseItems`: each draw phase can define its own type of "rendered thing", which can define its own "sort key"
  * Ported the 2d, 3d, and shadow phases to the new PhaseItem impl (currently Transparent2d, Transparent3d, and Shadow PhaseItems)
  * `Draw` trait and and `DrawFunctions` are now generic on PhaseItem
  * Modular / Ergonomic `DrawFunctions` via `RenderCommands`
    * RenderCommand is a trait that runs an ECS query and produces one or more RenderPass calls. Types implementing this trait can be composed to create a final DrawFunction. For example the DrawPbr DrawFunction is created from the following DrawCommand tuple. Const generics are used to set specific bind group locations:
        ```rust
         pub type DrawPbr = (
            SetPbrPipeline,
            SetMeshViewBindGroup<0>,
            SetStandardMaterialBindGroup<1>,
            SetTransformBindGroup<2>,
            DrawMesh,
        );
        ```
    * The new `custom_shader_pipelined` example illustrates how the commands above can be reused to create a custom draw function:
       ```rust
       type DrawCustom = (
           SetCustomMaterialPipeline,
           SetMeshViewBindGroup<0>,
           SetTransformBindGroup<2>,
           DrawMesh,
       );
       ``` 
* ExtractComponentPlugin and UniformComponentPlugin:
  * Simple, standardized ways to easily extract individual components and write them to GPU buffers
* Ported PBR and Sprite rendering to the new primitives above.
* Removed staging buffer from UniformVec in favor of direct Queue usage
  * Makes UniformVec much easier to use and more ergonomic. Completely removes the need for custom render graph nodes in these contexts (see the PbrNode and view Node removals and the much simpler call patterns in the relevant Prepare systems).
* Added a many_cubes_pipelined example to benchmark baseline 3d rendering performance and ensure there were no major regressions during this port. Avoiding regressions was challenging given that the old approach of extracting into centralized vectors is basically the "optimal" approach. However thanks to a various ECS optimizations and render logic rephrasing, we pretty much break even on this benchmark!
* Lifetimeless SystemParams: this will be a bit divisive, but as we continue to embrace "trait driven systems" (ex: ExtractComponentPlugin, UniformComponentPlugin, DrawCommand), the ergonomics of `(Query<'static, 'static, (&'static A, &'static B, &'static)>, Res<'static, C>)` were getting very hard to bear. As a compromise, I added "static type aliases" for the relevant SystemParams. The previous example can now be expressed like this: `(SQuery<(Read<A>, Read<B>)>, SRes<C>)`. If anyone has better ideas / conflicting opinions, please let me know!
* RunSystem trait: a way to define Systems via a trait with a SystemParam associated type. This is used to implement the various plugins mentioned above. I also added SystemParamItem and QueryItem type aliases to make "trait stye" ecs interactions nicer on the eyes (and fingers).
* RenderAsset retrying: ensures that render assets are only created when they are "ready" and allows us to create bind groups directly inside render assets (which significantly simplified the StandardMaterial code). I think ultimately we should swap this out on "asset dependency" events to wait for dependencies to load, but this will require significant asset system changes.
* Updated some built in shaders to account for missing MeshUniform fields
This commit is contained in:
Carter Anderson 2021-09-23 06:16:11 +00:00
parent 1e03a97e73
commit 08969a24b8
36 changed files with 1656 additions and 930 deletions

View file

@ -108,6 +108,7 @@ ron = "0.6.2"
serde = { version = "1", features = ["derive"] }
# Needed to poll Task examples
futures-lite = "1.11.3"
crevice = {path = "crates/crevice"}
[[example]]
name = "hello_world"
@ -159,6 +160,10 @@ path = "examples/3d/3d_scene.rs"
name = "3d_scene_pipelined"
path = "examples/3d/3d_scene_pipelined.rs"
[[example]]
name = "many_cubes_pipelined"
path = "examples/3d/many_cubes_pipelined.rs"
[[example]]
name = "cornell_box_pipelined"
path = "examples/3d/cornell_box_pipelined.rs"
@ -462,6 +467,10 @@ path = "examples/shader/shader_custom_material.rs"
name = "shader_defs"
path = "examples/shader/shader_defs.rs"
[[example]]
name = "custom_shader_pipelined"
path = "examples/shader/custom_shader_pipelined.rs"
# Tools
[[example]]
name = "bevymark"

View file

@ -0,0 +1,46 @@
[[block]]
struct View {
view_proj: mat4x4<f32>;
projection: mat4x4<f32>;
world_position: vec3<f32>;
};
[[group(0), binding(0)]]
var view: View;
[[block]]
struct Mesh {
transform: mat4x4<f32>;
};
[[group(2), binding(0)]]
var mesh: Mesh;
struct Vertex {
[[location(0)]] position: vec3<f32>;
[[location(1)]] normal: vec3<f32>;
[[location(2)]] uv: vec2<f32>;
};
struct VertexOutput {
[[builtin(position)]] clip_position: vec4<f32>;
};
[[stage(vertex)]]
fn vertex(vertex: Vertex) -> VertexOutput {
let world_position = mesh.transform * vec4<f32>(vertex.position, 1.0);
var out: VertexOutput;
out.clip_position = view.view_proj * world_position;
return out;
}
[[block]]
struct CustomMaterial {
color: vec4<f32>;
};
[[group(1), binding(0)]]
var material: CustomMaterial;
[[stage(fragment)]]
fn fragment() -> [[location(0)]] vec4<f32> {
return material.color;
}

View file

@ -96,6 +96,7 @@ impl<T: Asset> Handle<T> {
}
}
#[inline]
pub fn weak(id: HandleId) -> Self {
Self {
id,
@ -129,6 +130,7 @@ impl<T: Asset> Handle<T> {
self.handle_type = HandleType::Strong(sender);
}
#[inline]
pub fn clone_weak(&self) -> Self {
Handle::weak(self.id)
}

View file

@ -36,6 +36,8 @@ pub mod prelude {
};
}
pub use bevy_ecs_macros::all_tuples;
#[cfg(test)]
mod tests {
use crate as bevy_ecs;

View file

@ -45,6 +45,8 @@ pub trait WorldQuery {
type State: FetchState;
}
pub type QueryItem<'w, 's, Q> = <<Q as WorldQuery>::Fetch as Fetch<'w, 's>>::Item;
pub trait Fetch<'world, 'state>: Sized {
type Item;
type State: FetchState;

View file

@ -4,7 +4,7 @@ use crate::{
query::{Access, FilteredAccessSet},
system::{
check_system_change_tick, ReadOnlySystemParamFetch, System, SystemId, SystemParam,
SystemParamFetch, SystemParamState,
SystemParamFetch, SystemParamItem, SystemParamState,
},
world::{World, WorldId},
};
@ -48,6 +48,11 @@ impl SystemMeta {
pub fn set_non_send(&mut self) {
self.is_send = false;
}
#[inline]
pub(crate) fn check_change_tick(&mut self, change_tick: u32) {
check_system_change_tick(&mut self.last_change_tick, change_tick, self.name.as_ref());
}
}
// TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference
@ -123,6 +128,10 @@ impl<Param: SystemParam> SystemState<Param> {
self.world_id == world.id()
}
pub(crate) fn new_archetype(&mut self, archetype: &Archetype) {
self.param_state.new_archetype(archetype, &mut self.meta);
}
fn validate_world_and_update_archetypes(&mut self, world: &World) {
assert!(self.matches_world(world), "Encountered a mismatched World. A SystemState cannot be used with Worlds other than the one it was created with.");
let archetypes = world.archetypes();
@ -161,6 +170,69 @@ impl<Param: SystemParam> SystemState<Param> {
}
}
pub trait RunSystem: Send + Sync + 'static {
type Param: SystemParam;
fn run(param: SystemParamItem<Self::Param>);
fn system(world: &mut World) -> ParamSystem<Self::Param> {
ParamSystem {
run: Self::run,
state: SystemState::new(world),
}
}
}
pub struct ParamSystem<P: SystemParam> {
state: SystemState<P>,
run: fn(SystemParamItem<P>),
}
impl<P: SystemParam + 'static> System for ParamSystem<P> {
type In = ();
type Out = ();
fn name(&self) -> Cow<'static, str> {
self.state.meta().name.clone()
}
fn id(&self) -> SystemId {
self.state.meta().id
}
fn new_archetype(&mut self, archetype: &Archetype) {
self.state.new_archetype(archetype);
}
fn component_access(&self) -> &Access<ComponentId> {
self.state.meta().component_access_set.combined_access()
}
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.state.meta().archetype_component_access
}
fn is_send(&self) -> bool {
self.state.meta().is_send()
}
unsafe fn run_unsafe(&mut self, _input: Self::In, world: &World) -> Self::Out {
let param = self.state.get_unchecked_manual(world);
(self.run)(param);
}
fn apply_buffers(&mut self, world: &mut World) {
self.state.apply(world);
}
fn initialize(&mut self, _world: &mut World) {
// already initialized by nature of the SystemState being constructed
}
fn check_change_tick(&mut self, change_tick: u32) {
self.state.meta.check_change_tick(change_tick);
}
}
/// Conversion trait to turn something into a [`System`].
///
/// Use this to get a system from a function. Also note that every system implements this trait as

View file

@ -47,6 +47,8 @@ pub trait SystemParam: Sized {
type Fetch: for<'w, 's> SystemParamFetch<'w, 's>;
}
pub type SystemParamItem<'w, 's, P> = <<P as SystemParam>::Fetch as SystemParamFetch<'w, 's>>::Item;
/// The state of a [`SystemParam`].
///
/// # Safety
@ -1220,3 +1222,12 @@ macro_rules! impl_system_param_tuple {
}
all_tuples!(impl_system_param_tuple, 0, 16, P);
pub mod lifetimeless {
pub type SQuery<Q, F = ()> = super::Query<'static, 'static, Q, F>;
pub type Read<T> = &'static T;
pub type Write<T> = &'static mut T;
pub type SRes<T> = super::Res<'static, T>;
pub type SResMut<T> = super::ResMut<'static, T>;
pub type SCommands = crate::system::Commands<'static, 'static>;
}

View file

@ -0,0 +1,50 @@
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
ecs::prelude::*,
pbr2::{PbrBundle, StandardMaterial},
prelude::{App, Assets, Transform},
render2::{
camera::PerspectiveCameraBundle,
color::Color,
mesh::{shape, Mesh},
},
PipelinedDefaultPlugins,
};
fn main() {
App::new()
.add_plugins(PipelinedDefaultPlugins)
.add_plugin(FrameTimeDiagnosticsPlugin::default())
.add_plugin(LogDiagnosticsPlugin::default())
.add_startup_system(setup.system())
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
const WIDTH: usize = 100;
const HEIGHT: usize = 100;
for x in 0..WIDTH {
for y in 0..HEIGHT {
// cube
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(StandardMaterial {
base_color: Color::PINK,
..Default::default()
}),
transform: Transform::from_xyz((x as f32) * 2.0, (y as f32) * 2.0, 0.0),
..Default::default()
});
}
}
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(80.0, 80.0, 300.0),
..Default::default()
});
}

View file

@ -101,6 +101,7 @@ Example | File | Description
`cornell_box_pipelined` | [`3d/cornell_box_pipelined.rs`](./3d/cornell_box_pipelined.rs) | Re-production of the cornell box
`load_gltf` | [`3d/load_gltf.rs`](./3d/load_gltf.rs) | Loads and renders a gltf file as a scene
`load_gltf_pipelined` | [`3d/load_gltf_pipelined.rs`](./3d/load_gltf_pipelined.rs) | Loads and renders a gltf file as a scene
`many_cubes_pipelined` | [`3d/many_cubes_pipelined.rs`](./3d/many_cubes_pipelined.rs) | Simple benchmark to test per-entity draw overhead
`msaa` | [`3d/msaa.rs`](./3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges
`orthographic` | [`3d/orthographic.rs`](./3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications)
`orthographic_pipelined` | [`3d/orthographic_pipelined.rs`](./3d/orthographic_pipelined.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications)
@ -222,6 +223,7 @@ Example | File | Description
--- | --- | ---
`animate_shader` | [`shader/animate_shader.rs`](./shader/animate_shader.rs) | Shows how to animate a shader by accessing a time uniform variable
`array_texture` | [`shader/array_texture.rs`](./shader/array_texture.rs) | Illustrates how to create a texture for use with a texture2DArray shader uniform variable
`custom_shader_pipelined` | [`shader/custom_shader_pipelined.rs`](./shader/custom_shader_pipelined.rs) | Illustrates how to create custom shaders
`hot_shader_reloading` | [`shader/hot_shader_reloading.rs`](./shader/hot_shader_reloading.rs) | Illustrates how to load shaders such that they can be edited while the example is still running
`mesh_custom_attribute` | [`shader/mesh_custom_attribute.rs`](./shader/mesh_custom_attribute.rs) | Illustrates how to add a custom attribute to a mesh and use it in a custom shader
`shader_custom_material` | [`shader/shader_custom_material.rs`](./shader/shader_custom_material.rs) | Illustrates creating a custom material and a shader that uses it

View file

@ -0,0 +1,297 @@
use bevy::{
core_pipeline::Transparent3d,
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
},
math::{Vec3, Vec4},
pbr2::{DrawMesh, MeshUniform, PbrShaders, SetMeshViewBindGroup, SetTransformBindGroup},
prelude::{AddAsset, App, Assets, GlobalTransform, Handle, Plugin, Transform},
reflect::TypeUuid,
render2::{
camera::PerspectiveCameraBundle,
color::Color,
mesh::{shape, Mesh},
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_component::ExtractComponentPlugin,
render_phase::{
AddRenderCommand, DrawFunctions, RenderCommand, RenderPhase, TrackedRenderPass,
},
render_resource::*,
renderer::RenderDevice,
shader::Shader,
texture::BevyDefault,
view::ExtractedView,
RenderApp, RenderStage,
},
PipelinedDefaultPlugins,
};
use crevice::std140::{AsStd140, Std140};
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "4ee9c363-1124-4113-890e-199d81b00281"]
pub struct CustomMaterial {
color: Color,
}
#[derive(Clone)]
pub struct GpuCustomMaterial {
_buffer: Buffer,
bind_group: BindGroup,
}
impl RenderAsset for CustomMaterial {
type ExtractedAsset = CustomMaterial;
type PreparedAsset = GpuCustomMaterial;
type Param = (SRes<RenderDevice>, SRes<CustomPipeline>);
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
fn prepare_asset(
extracted_asset: Self::ExtractedAsset,
(render_device, custom_pipeline): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let color: Vec4 = extracted_asset.color.as_rgba_linear().into();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
contents: color.as_std140().as_bytes(),
label: None,
usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
});
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
}],
label: None,
layout: &custom_pipeline.material_layout,
});
Ok(GpuCustomMaterial {
_buffer: buffer,
bind_group,
})
}
}
pub struct CustomMaterialPlugin;
impl Plugin for CustomMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_asset::<CustomMaterial>()
.add_plugin(ExtractComponentPlugin::<Handle<CustomMaterial>>::default())
.add_plugin(RenderAssetPlugin::<CustomMaterial>::default());
app.sub_app(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>()
.init_resource::<CustomPipeline>()
.add_system_to_stage(RenderStage::Queue, queue_custom);
}
}
fn main() {
App::new()
.add_plugins(PipelinedDefaultPlugins)
.add_plugin(FrameTimeDiagnosticsPlugin::default())
.add_plugin(LogDiagnosticsPlugin::default())
.add_plugin(CustomMaterialPlugin)
.add_startup_system(setup)
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<CustomMaterial>>,
) {
// cube
commands.spawn().insert_bundle((
meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
Transform::from_xyz(0.0, 0.5, 0.0),
GlobalTransform::default(),
materials.add(CustomMaterial {
color: Color::GREEN,
}),
));
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
}
pub struct CustomPipeline {
material_layout: BindGroupLayout,
pipeline: RenderPipeline,
}
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
impl FromWorld for CustomPipeline {
fn from_world(world: &mut World) -> Self {
let render_device = world.get_resource::<RenderDevice>().unwrap();
let shader = Shader::from_wgsl(include_str!("../../assets/shaders/custom.wgsl"));
let shader_module = render_device.create_shader_module(&shader);
let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStage::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64),
},
count: None,
}],
label: None,
});
let pbr_pipeline = world.get_resource::<PbrShaders>().unwrap();
let pipeline_layout = render_device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: None,
push_constant_ranges: &[],
bind_group_layouts: &[
&pbr_pipeline.view_layout,
&material_layout,
&pbr_pipeline.mesh_layout,
],
});
let pipeline = render_device.create_render_pipeline(&RenderPipelineDescriptor {
label: None,
vertex: VertexState {
buffers: &[VertexBufferLayout {
array_stride: 32,
step_mode: InputStepMode::Vertex,
attributes: &[
// Position (GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically))
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 12,
shader_location: 0,
},
// Normal
VertexAttribute {
format: VertexFormat::Float32x3,
offset: 0,
shader_location: 1,
},
// Uv
VertexAttribute {
format: VertexFormat::Float32x2,
offset: 24,
shader_location: 2,
},
],
}],
module: &shader_module,
entry_point: "vertex",
},
fragment: Some(FragmentState {
module: &shader_module,
entry_point: "fragment",
targets: &[ColorTargetState {
format: TextureFormat::bevy_default(),
blend: Some(BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrite::ALL,
}],
}),
depth_stencil: Some(DepthStencilState {
format: TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
layout: Some(&pipeline_layout),
multisample: MultisampleState::default(),
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
polygon_mode: PolygonMode::Fill,
clamp_depth: false,
conservative: false,
},
});
CustomPipeline {
pipeline,
material_layout,
}
}
}
pub fn queue_custom(
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
materials: Res<RenderAssets<CustomMaterial>>,
material_meshes: Query<(Entity, &Handle<CustomMaterial>, &MeshUniform), With<Handle<Mesh>>>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
) {
let draw_custom = transparent_3d_draw_functions
.read()
.get_id::<DrawCustom>()
.unwrap();
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, material_handle, mesh_uniform) in material_meshes.iter() {
if materials.contains_key(material_handle) {
transparent_phase.add(Transparent3d {
entity,
draw_function: draw_custom,
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
});
}
}
}
}
type DrawCustom = (
SetCustomMaterialPipeline,
SetMeshViewBindGroup<0>,
SetTransformBindGroup<2>,
DrawMesh,
);
struct SetCustomMaterialPipeline;
impl RenderCommand<Transparent3d> for SetCustomMaterialPipeline {
type Param = (
SRes<RenderAssets<CustomMaterial>>,
SRes<CustomPipeline>,
SQuery<Read<Handle<CustomMaterial>>>,
);
fn render<'w>(
_view: Entity,
item: &Transparent3d,
(materials, custom_pipeline, query): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) {
let material_handle = query.get(item.entity).unwrap();
let material = materials.into_inner().get(material_handle).unwrap();
pass.set_render_pipeline(&custom_pipeline.into_inner().pipeline);
pass.set_bind_group(1, &material.bind_group, &[]);
}
}

View file

@ -66,11 +66,23 @@ struct BirdTexture(Handle<Image>);
fn setup(
mut commands: Commands,
_window: Res<WindowDescriptor>,
_counter: ResMut<BevyCounter>,
window: Res<WindowDescriptor>,
mut counter: ResMut<BevyCounter>,
asset_server: Res<AssetServer>,
) {
// spawn_birds(&mut commands, &window, &mut counter, 10);
let texture = asset_server.load("branding/icon.png");
if let Some(initial_count) = std::env::args()
.nth(1)
.and_then(|arg| arg.parse::<u128>().ok())
{
spawn_birds(
&mut commands,
&window,
&mut counter,
initial_count,
texture.clone_weak(),
);
}
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
// commands.spawn_bundle(UiCameraBundle::default());
// commands.spawn_bundle(TextBundle {
@ -123,7 +135,7 @@ fn setup(
// ..Default::default()
// });
commands.insert_resource(BirdTexture(asset_server.load("branding/icon.png")));
commands.insert_resource(BirdTexture(texture));
}
#[allow(clippy::too_many_arguments)]

View file

@ -15,6 +15,8 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../../crates/bevy_app", version = "0.5.0" }
bevy_asset = { path = "../../crates/bevy_asset", version = "0.5.0" }
bevy_core = { path = "../../crates/bevy_core", version = "0.5.0" }
bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" }
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }

View file

@ -7,19 +7,18 @@ pub use main_pass_3d::*;
pub use main_pass_driver::*;
use bevy_app::{App, Plugin};
use bevy_asset::Handle;
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
use bevy_render2::{
camera::{ActiveCameras, CameraPlugin},
color::Color,
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
render_phase::{sort_phase_system, RenderPhase},
render_resource::{
Extent3d, Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage,
TextureView,
},
render_phase::{sort_phase_system, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase},
render_resource::*,
renderer::RenderDevice,
texture::TextureCache,
view::{ExtractedView, ViewPlugin},
texture::{Image, TextureCache},
view::ExtractedView,
RenderApp, RenderStage, RenderWorld,
};
@ -76,17 +75,13 @@ impl Plugin for CorePipelinePlugin {
let render_app = app.sub_app(RenderApp);
render_app
.init_resource::<DrawFunctions<Transparent2d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_system_to_stage(RenderStage::Extract, extract_clear_color)
.add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases)
.add_system_to_stage(RenderStage::Prepare, prepare_core_views_system)
.add_system_to_stage(
RenderStage::PhaseSort,
sort_phase_system::<Transparent2dPhase>,
)
.add_system_to_stage(
RenderStage::PhaseSort,
sort_phase_system::<Transparent3dPhase>,
);
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
let pass_node_3d = MainPass3dNode::new(&mut render_app.world);
@ -151,17 +146,51 @@ impl Plugin for CorePipelinePlugin {
graph.add_node(node::MAIN_PASS_DEPENDENCIES, EmptyNode);
graph.add_node(node::MAIN_PASS_DRIVER, MainPassDriverNode);
graph
.add_node_edge(ViewPlugin::VIEW_NODE, node::MAIN_PASS_DEPENDENCIES)
.unwrap();
graph
.add_node_edge(node::MAIN_PASS_DEPENDENCIES, node::MAIN_PASS_DRIVER)
.unwrap();
}
}
pub struct Transparent3dPhase;
pub struct Transparent2dPhase;
pub struct Transparent2d {
pub sort_key: Handle<Image>,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for Transparent2d {
type SortKey = Handle<Image>;
#[inline]
fn sort_key(&self) -> Self::SortKey {
self.sort_key.clone_weak()
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
}
pub struct Transparent3d {
pub distance: f32,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for Transparent3d {
type SortKey = FloatOrd;
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
}
pub struct ViewDepthTexture {
pub texture: Texture,
@ -184,14 +213,14 @@ pub fn extract_core_pipeline_camera_phases(
if let Some(entity) = camera_2d.entity {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<Transparent2dPhase>::default());
.insert(RenderPhase::<Transparent2d>::default());
}
}
if let Some(camera_3d) = active_cameras.get(CameraPlugin::CAMERA_3D) {
if let Some(entity) = camera_3d.entity {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<Transparent3dPhase>::default());
.insert(RenderPhase::<Transparent3d>::default());
}
}
}
@ -200,7 +229,7 @@ pub fn prepare_core_views_system(
mut commands: Commands,
mut texture_cache: ResMut<TextureCache>,
render_device: Res<RenderDevice>,
views: Query<(Entity, &ExtractedView), With<RenderPhase<Transparent3dPhase>>>,
views: Query<(Entity, &ExtractedView), With<RenderPhase<Transparent3d>>>,
) {
for (entity, view) in views.iter() {
let cached_texture = texture_cache.get(

View file

@ -1,4 +1,4 @@
use crate::{ClearColor, Transparent2dPhase};
use crate::{ClearColor, Transparent2d};
use bevy_ecs::prelude::*;
use bevy_render2::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
@ -9,7 +9,7 @@ use bevy_render2::{
};
pub struct MainPass2dNode {
query: QueryState<&'static RenderPhase<Transparent2dPhase>, With<ExtractedView>>,
query: QueryState<&'static RenderPhase<Transparent2d>, With<ExtractedView>>,
}
impl MainPass2dNode {
@ -57,7 +57,9 @@ impl Node for MainPass2dNode {
};
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
let draw_functions = world
.get_resource::<DrawFunctions<Transparent2d>>()
.unwrap();
let transparent_phase = self
.query
@ -70,15 +72,9 @@ impl Node for MainPass2dNode {
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for drawable in transparent_phase.drawn_things.iter() {
let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap();
draw_function.draw(
world,
&mut tracked_pass,
view_entity,
drawable.draw_key,
drawable.sort_key,
);
for item in transparent_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
Ok(())
}

View file

@ -1,4 +1,4 @@
use crate::{ClearColor, Transparent3dPhase};
use crate::{ClearColor, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_render2::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
@ -12,7 +12,7 @@ use bevy_render2::{
};
pub struct MainPass3dNode {
query: QueryState<&'static RenderPhase<Transparent3dPhase>, With<ExtractedView>>,
query: QueryState<&'static RenderPhase<Transparent3d>, With<ExtractedView>>,
}
impl MainPass3dNode {
@ -70,7 +70,9 @@ impl Node for MainPass3dNode {
};
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
let draw_functions = world
.get_resource::<DrawFunctions<Transparent3d>>()
.unwrap();
let transparent_phase = self
.query
@ -82,15 +84,9 @@ impl Node for MainPass3dNode {
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for drawable in transparent_phase.drawn_things.iter() {
let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap();
draw_function.draw(
world,
&mut tracked_pass,
view_entity,
drawable.draw_key,
drawable.sort_key,
);
for item in transparent_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
Ok(())
}

View file

@ -9,10 +9,13 @@ pub use material::*;
pub use render::*;
use bevy_app::prelude::*;
use bevy_asset::Handle;
use bevy_core_pipeline::Transparent3d;
use bevy_ecs::prelude::*;
use bevy_render2::{
render_component::{ExtractComponentPlugin, UniformComponentPlugin},
render_graph::RenderGraph,
render_phase::{sort_phase_system, DrawFunctions},
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
RenderApp, RenderStage,
};
@ -28,15 +31,17 @@ pub struct PbrPlugin;
impl Plugin for PbrPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(StandardMaterialPlugin)
.add_plugin(ExtractComponentPlugin::<Handle<StandardMaterial>>::default())
.add_plugin(UniformComponentPlugin::<MeshUniform>::default())
.init_resource::<AmbientLight>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>();
.init_resource::<PointLightShadowMap>()
.init_resource::<AmbientLight>();
let render_app = app.sub_app(RenderApp);
render_app
.add_system_to_stage(RenderStage::Extract, render::extract_meshes)
.add_system_to_stage(RenderStage::Extract, render::extract_lights)
.add_system_to_stage(RenderStage::Prepare, render::prepare_meshes)
.add_system_to_stage(
RenderStage::Prepare,
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
@ -44,27 +49,24 @@ impl Plugin for PbrPlugin {
render::prepare_lights.exclusive_system(),
)
.add_system_to_stage(RenderStage::Queue, render::queue_meshes)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<ShadowPhase>)
// FIXME: Hack to ensure RenderCommandQueue is initialized when PbrShaders is being initialized
// .init_resource::<RenderCommandQueue>()
.add_system_to_stage(RenderStage::Queue, render::queue_shadows)
.add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group)
.add_system_to_stage(RenderStage::Queue, render::queue_transform_bind_group)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
.init_resource::<PbrShaders>()
.init_resource::<ShadowShaders>()
.init_resource::<MeshMeta>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<LightMeta>();
let draw_pbr = DrawPbr::new(&mut render_app.world);
let draw_shadow_mesh = DrawShadowMesh::new(&mut render_app.world);
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Transparent3d, DrawPbr>();
let render_world = render_app.world.cell();
let draw_functions = render_world.get_resource::<DrawFunctions>().unwrap();
draw_functions.write().add(draw_pbr);
let draw_functions = render_world
.get_resource::<DrawFunctions<Shadow>>()
.unwrap();
draw_functions.write().add(draw_shadow_mesh);
let mut graph = render_world.get_resource_mut::<RenderGraph>().unwrap();
graph.add_node("pbr", PbrNode);
graph
.add_node_edge("pbr", bevy_core_pipeline::node::MAIN_PASS_DEPENDENCIES)
.unwrap();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy_core_pipeline::draw_3d_graph::NAME)
.unwrap();

View file

@ -1,15 +1,19 @@
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Handle};
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_math::Vec4;
use bevy_reflect::TypeUuid;
use bevy_render2::{
color::Color,
render_asset::{RenderAsset, RenderAssetPlugin},
render_resource::{Buffer, BufferInitDescriptor, BufferUsage},
renderer::{RenderDevice, RenderQueue},
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_resource::{BindGroup, Buffer, BufferInitDescriptor, BufferUsage, Sampler, TextureView},
renderer::RenderDevice,
texture::Image,
};
use crevice::std140::{AsStd140, Std140};
use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource};
use crate::PbrShaders;
// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag!
bitflags::bitflags! {
@ -134,16 +138,17 @@ impl Plugin for StandardMaterialPlugin {
#[derive(Debug, Clone)]
pub struct GpuStandardMaterial {
pub buffer: Buffer,
// FIXME: image handles feel unnecessary here but the extracted asset is discarded
pub base_color_texture: Option<Handle<Image>>,
pub emissive_texture: Option<Handle<Image>>,
pub metallic_roughness_texture: Option<Handle<Image>>,
pub occlusion_texture: Option<Handle<Image>>,
pub bind_group: BindGroup,
}
impl RenderAsset for StandardMaterial {
type ExtractedAsset = StandardMaterial;
type PreparedAsset = GpuStandardMaterial;
type Param = (
SRes<RenderDevice>,
SRes<PbrShaders>,
SRes<RenderAssets<Image>>,
);
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
@ -151,9 +156,41 @@ impl RenderAsset for StandardMaterial {
fn prepare_asset(
material: Self::ExtractedAsset,
render_device: &RenderDevice,
_render_queue: &RenderQueue,
) -> Self::PreparedAsset {
(render_device, pbr_shaders, gpu_images): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let (base_color_texture_view, base_color_sampler) = if let Some(result) =
image_handle_to_view_sampler(pbr_shaders, gpu_images, &material.base_color_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (emissive_texture_view, emissive_sampler) = if let Some(result) =
image_handle_to_view_sampler(pbr_shaders, gpu_images, &material.emissive_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) =
image_handle_to_view_sampler(
pbr_shaders,
gpu_images,
&material.metallic_roughness_texture,
) {
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (occlusion_texture_view, occlusion_sampler) = if let Some(result) =
image_handle_to_view_sampler(pbr_shaders, gpu_images, &material.occlusion_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let mut flags = StandardMaterialFlags::NONE;
if material.base_color_texture.is_some() {
flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE;
@ -188,12 +225,65 @@ impl RenderAsset for StandardMaterial {
usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
contents: value_std140.as_bytes(),
});
GpuStandardMaterial {
buffer,
base_color_texture: material.base_color_texture,
emissive_texture: material.emissive_texture,
metallic_roughness_texture: material.metallic_roughness_texture,
occlusion_texture: material.occlusion_texture,
}
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(base_color_texture_view),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::Sampler(base_color_sampler),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureView(emissive_texture_view),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::Sampler(emissive_sampler),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::TextureView(metallic_roughness_texture_view),
},
BindGroupEntry {
binding: 6,
resource: BindingResource::Sampler(metallic_roughness_sampler),
},
BindGroupEntry {
binding: 7,
resource: BindingResource::TextureView(occlusion_texture_view),
},
BindGroupEntry {
binding: 8,
resource: BindingResource::Sampler(occlusion_sampler),
},
],
label: None,
layout: &pbr_shaders.material_layout,
});
Ok(GpuStandardMaterial { buffer, bind_group })
}
}
fn image_handle_to_view_sampler<'a>(
pbr_pipeline: &'a PbrShaders,
gpu_images: &'a RenderAssets<Image>,
handle_option: &Option<Handle<Image>>,
) -> Option<(&'a TextureView, &'a Sampler)> {
if let Some(handle) = handle_option {
let gpu_image = gpu_images.get(handle)?;
Some((&gpu_image.texture_view, &gpu_image.sampler))
} else {
Some((
&pbr_pipeline.dummy_white_gpu_image.texture_view,
&pbr_pipeline.dummy_white_gpu_image.sampler,
))
}
}

View file

@ -12,7 +12,11 @@ var view: View;
[[block]]
struct Mesh {
model: mat4x4<f32>;
inverse_transpose_model: mat4x4<f32>;
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
flags: u32;
};
[[group(1), binding(0)]]
var mesh: Mesh;

View file

@ -1,22 +1,30 @@
use crate::{
AmbientLight, DirectionalLight, DirectionalLightShadowMap, ExtractedMeshes, MeshMeta,
PbrShaders, PointLight, PointLightShadowMap,
AmbientLight, DirectionalLight, DirectionalLightShadowMap, MeshUniform, NotShadowCaster,
PbrShaders, PointLight, PointLightShadowMap, TransformBindGroup,
};
use bevy_asset::Handle;
use bevy_core::FloatOrd;
use bevy_core_pipeline::Transparent3d;
use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemState},
};
use bevy_core_pipeline::Transparent3dPhase;
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_math::{const_vec3, Mat4, Vec3, Vec4};
use bevy_render2::{
camera::CameraProjection,
color::Color,
mesh::Mesh,
render_asset::RenderAssets,
render_component::DynamicUniformIndex,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
render_phase::{
Draw, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, TrackedRenderPass,
},
render_resource::*,
renderer::{RenderContext, RenderDevice},
renderer::{RenderContext, RenderDevice, RenderQueue},
shader::Shader,
texture::*,
view::{ExtractedView, ViewUniformOffset},
view::{ExtractedView, ViewUniformOffset, ViewUniforms},
};
use bevy_transform::components::GlobalTransform;
use crevice::std140::AsStd140;
@ -374,8 +382,9 @@ pub fn prepare_lights(
mut commands: Commands,
mut texture_cache: ResMut<TextureCache>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut light_meta: ResMut<LightMeta>,
views: Query<Entity, With<RenderPhase<Transparent3dPhase>>>,
views: Query<Entity, With<RenderPhase<Transparent3d>>>,
ambient_light: Res<ExtractedAmbientLight>,
point_light_shadow_map: Res<ExtractedPointLightShadowMap>,
directional_light_shadow_map: Res<ExtractedDirectionalLightShadowMap>,
@ -477,7 +486,7 @@ pub fn prepare_lights(
transform: view_translation * view_rotation,
projection,
},
RenderPhase::<ShadowPhase>::default(),
RenderPhase::<Shadow>::default(),
))
.id();
view_lights.push(view_light_entity);
@ -563,7 +572,7 @@ pub fn prepare_lights(
transform: GlobalTransform::from_matrix(view.inverse()),
projection,
},
RenderPhase::<ShadowPhase>::default(),
RenderPhase::<Shadow>::default(),
))
.id();
view_lights.push(view_light_entity);
@ -604,16 +613,77 @@ pub fn prepare_lights(
});
}
light_meta
.view_gpu_lights
.write_to_staging_buffer(&render_device);
light_meta.view_gpu_lights.write_buffer(&render_queue);
}
pub struct ShadowPhase;
pub fn queue_shadow_view_bind_group(
render_device: Res<RenderDevice>,
shadow_shaders: Res<ShadowShaders>,
mut light_meta: ResMut<LightMeta>,
view_uniforms: Res<ViewUniforms>,
) {
if let Some(view_binding) = view_uniforms.uniforms.binding() {
light_meta.shadow_view_bind_group =
Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: view_binding,
}],
label: None,
layout: &shadow_shaders.view_layout,
}));
}
}
pub fn queue_shadows(
shadow_draw_functions: Res<DrawFunctions<Shadow>>,
casting_meshes: Query<Entity, (With<Handle<Mesh>>, Without<NotShadowCaster>)>,
mut view_lights: Query<&ViewLights>,
mut view_light_shadow_phases: Query<&mut RenderPhase<Shadow>>,
) {
for view_lights in view_lights.iter_mut() {
// ultimately lights should check meshes for relevancy (ex: light views can "see" different meshes than the main view can)
let draw_shadow_mesh = shadow_draw_functions
.read()
.get_id::<DrawShadowMesh>()
.unwrap();
for view_light_entity in view_lights.lights.iter().copied() {
let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap();
// TODO: this should only queue up meshes that are actually visible by each "light view"
for entity in casting_meshes.iter() {
shadow_phase.add(Shadow {
draw_function: draw_shadow_mesh,
entity,
distance: 0.0, // TODO: sort back-to-front
})
}
}
}
}
pub struct Shadow {
pub distance: f32,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for Shadow {
type SortKey = FloatOrd;
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
}
pub struct ShadowPassNode {
main_view_query: QueryState<&'static ViewLights>,
view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase<ShadowPhase>)>,
view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase<Shadow>)>,
}
impl ShadowPassNode {
@ -663,22 +733,16 @@ impl Node for ShadowPassNode {
}),
};
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
let draw_functions = world.get_resource::<DrawFunctions<Shadow>>().unwrap();
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for drawable in shadow_phase.drawn_things.iter() {
let draw_function = draw_functions.get_mut(drawable.draw_function).unwrap();
draw_function.draw(
world,
&mut tracked_pass,
view_light_entity,
drawable.draw_key,
drawable.sort_key,
);
for item in shadow_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_light_entity, item);
}
}
}
@ -687,16 +751,15 @@ impl Node for ShadowPassNode {
}
}
type DrawShadowMeshParams<'s, 'w> = (
Res<'w, ShadowShaders>,
Res<'w, ExtractedMeshes>,
Res<'w, LightMeta>,
Res<'w, MeshMeta>,
Res<'w, RenderAssets<Mesh>>,
Query<'w, 's, &'w ViewUniformOffset>,
);
pub struct DrawShadowMesh {
params: SystemState<DrawShadowMeshParams<'static, 'static>>,
params: SystemState<(
SRes<ShadowShaders>,
SRes<LightMeta>,
SRes<TransformBindGroup>,
SRes<RenderAssets<Mesh>>,
SQuery<(Read<DynamicUniformIndex<MeshUniform>>, Read<Handle<Mesh>>)>,
SQuery<Read<ViewUniformOffset>>,
)>,
}
impl DrawShadowMesh {
@ -707,21 +770,19 @@ impl DrawShadowMesh {
}
}
impl Draw for DrawShadowMesh {
impl Draw<Shadow> for DrawShadowMesh {
fn draw<'w>(
&mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
view: Entity,
draw_key: usize,
_sort_key: usize,
item: &Shadow,
) {
let (shadow_shaders, extracted_meshes, light_meta, mesh_meta, meshes, views) =
let (shadow_shaders, light_meta, transform_bind_group, meshes, items, views) =
self.params.get(world);
let (transform_index, mesh_handle) = items.get(item.entity).unwrap();
let view_uniform_offset = views.get(view).unwrap();
let extracted_mesh = &extracted_meshes.into_inner().meshes[draw_key];
let shadow_shaders = shadow_shaders.into_inner();
pass.set_render_pipeline(&shadow_shaders.pipeline);
pass.set_render_pipeline(&shadow_shaders.into_inner().pipeline);
pass.set_bind_group(
0,
light_meta
@ -732,18 +793,13 @@ impl Draw for DrawShadowMesh {
&[view_uniform_offset.offset],
);
let transform_bindgroup_key = mesh_meta.mesh_transform_bind_group_key.unwrap();
pass.set_bind_group(
1,
mesh_meta
.into_inner()
.mesh_transform_bind_group
.get_value(transform_bindgroup_key)
.unwrap(),
&[extracted_mesh.transform_binding_offset],
&transform_bind_group.into_inner().value,
&[transform_index.index()],
);
let gpu_mesh = meshes.into_inner().get(&extracted_mesh.mesh).unwrap();
let gpu_mesh = meshes.into_inner().get(mesh_handle).unwrap();
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
if let Some(index_info) = &gpu_mesh.index_info {
pass.set_index_buffer(index_info.buffer.slice(..), 0, IndexFormat::Uint32);

View file

@ -1,37 +1,127 @@
mod light;
pub use light::*;
use crate::{NotShadowCaster, NotShadowReceiver, StandardMaterial, StandardMaterialUniformData};
use bevy_asset::{Assets, Handle};
use bevy_core_pipeline::Transparent3dPhase;
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_asset::Handle;
use bevy_core_pipeline::Transparent3d;
use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
};
use bevy_math::Mat4;
use bevy_render2::{
mesh::Mesh,
render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass},
render_component::{ComponentUniforms, DynamicUniformIndex},
render_phase::{DrawFunctions, RenderCommand, RenderPhase, TrackedRenderPass},
render_resource::*,
renderer::{RenderContext, RenderDevice, RenderQueue},
renderer::{RenderDevice, RenderQueue},
shader::Shader,
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
view::{ExtractedView, ViewMeta, ViewUniformOffset},
view::{ExtractedView, ViewUniformOffset, ViewUniforms},
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey};
use crevice::std140::AsStd140;
use wgpu::{
Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat,
TextureViewDescriptor,
};
#[derive(AsStd140, Clone)]
pub struct MeshUniform {
pub transform: Mat4,
pub inverse_transpose_model: Mat4,
pub flags: u32,
}
// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl!
bitflags::bitflags! {
#[repr(transparent)]
struct MeshFlags: u32 {
const SHADOW_RECEIVER = (1 << 0);
const NONE = 0;
const UNINITIALIZED = 0xFFFF;
}
}
pub fn extract_meshes(
mut commands: Commands,
mut previous_caster_len: Local<usize>,
mut previous_not_caster_len: Local<usize>,
caster_query: Query<
(
Entity,
&GlobalTransform,
&Handle<Mesh>,
Option<&NotShadowReceiver>,
),
Without<NotShadowCaster>,
>,
not_caster_query: Query<
(
Entity,
&GlobalTransform,
&Handle<Mesh>,
Option<&NotShadowReceiver>,
),
With<NotShadowCaster>,
>,
) {
let mut caster_values = Vec::with_capacity(*previous_caster_len);
for (entity, transform, handle, not_receiver) in caster_query.iter() {
let transform = transform.compute_matrix();
caster_values.push((
entity,
(
handle.clone_weak(),
MeshUniform {
flags: if not_receiver.is_some() {
MeshFlags::empty().bits
} else {
MeshFlags::SHADOW_RECEIVER.bits
},
transform,
inverse_transpose_model: transform.inverse().transpose(),
},
),
));
}
*previous_caster_len = caster_values.len();
commands.insert_or_spawn_batch(caster_values);
let mut not_caster_values = Vec::with_capacity(*previous_not_caster_len);
for (entity, transform, handle, not_receiver) in not_caster_query.iter() {
let transform = transform.compute_matrix();
not_caster_values.push((
entity,
(
handle.clone_weak(),
MeshUniform {
flags: if not_receiver.is_some() {
MeshFlags::empty().bits
} else {
MeshFlags::SHADOW_RECEIVER.bits
},
transform,
inverse_transpose_model: transform.inverse().transpose(),
},
NotShadowCaster,
),
));
}
*previous_not_caster_len = not_caster_values.len();
commands.insert_or_spawn_batch(not_caster_values);
}
pub struct PbrShaders {
pipeline: RenderPipeline,
view_layout: BindGroupLayout,
material_layout: BindGroupLayout,
mesh_layout: BindGroupLayout,
pub pipeline: RenderPipeline,
pub shader_module: ShaderModule,
pub view_layout: BindGroupLayout,
pub material_layout: BindGroupLayout,
pub mesh_layout: BindGroupLayout,
// This dummy white texture is to be used in place of optional StandardMaterial textures
dummy_white_gpu_image: GpuImage,
pub dummy_white_gpu_image: GpuImage,
}
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
@ -41,7 +131,6 @@ impl FromWorld for PbrShaders {
let shader = Shader::from_wgsl(include_str!("pbr.wgsl"));
let shader_module = render_device.create_shader_module(&shader);
// TODO: move this into ViewMeta?
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
// View
@ -361,6 +450,7 @@ impl FromWorld for PbrShaders {
};
PbrShaders {
pipeline,
shader_module,
view_layout,
material_layout,
mesh_layout,
@ -369,476 +459,227 @@ impl FromWorld for PbrShaders {
}
}
struct ExtractedMesh {
transform: Mat4,
mesh: Handle<Mesh>,
transform_binding_offset: u32,
material_handle: Handle<StandardMaterial>,
casts_shadows: bool,
receives_shadows: bool,
pub struct TransformBindGroup {
pub value: BindGroup,
}
pub struct ExtractedMeshes {
meshes: Vec<ExtractedMesh>,
}
pub fn extract_meshes(
pub fn queue_transform_bind_group(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
materials: Res<Assets<StandardMaterial>>,
images: Res<Assets<Image>>,
query: Query<(
&GlobalTransform,
&Handle<Mesh>,
&Handle<StandardMaterial>,
Option<&NotShadowCaster>,
Option<&NotShadowReceiver>,
)>,
) {
let mut extracted_meshes = Vec::new();
for (
transform,
mesh_handle,
material_handle,
maybe_not_shadow_caster,
maybe_not_shadow_receiver,
) in query.iter()
{
if !meshes.contains(mesh_handle) {
continue;
}
if let Some(material) = materials.get(material_handle) {
if let Some(ref image) = material.base_color_texture {
if !images.contains(image) {
continue;
}
}
if let Some(ref image) = material.emissive_texture {
if !images.contains(image) {
continue;
}
}
if let Some(ref image) = material.metallic_roughness_texture {
if !images.contains(image) {
continue;
}
}
if let Some(ref image) = material.occlusion_texture {
if !images.contains(image) {
continue;
}
}
extracted_meshes.push(ExtractedMesh {
transform: transform.compute_matrix(),
mesh: mesh_handle.clone_weak(),
transform_binding_offset: 0,
material_handle: material_handle.clone_weak(),
// NOTE: Double-negative is so that meshes cast and receive shadows by default
// Not not shadow caster means that this mesh is a shadow caster
casts_shadows: maybe_not_shadow_caster.is_none(),
receives_shadows: maybe_not_shadow_receiver.is_none(),
});
} else {
continue;
}
}
commands.insert_resource(ExtractedMeshes {
meshes: extracted_meshes,
});
}
struct MeshDrawInfo {
// TODO: compare cost of doing this vs cloning the BindGroup?
material_bind_group_key: FrameSlabMapKey<BufferId, BindGroup>,
}
#[derive(Debug, AsStd140)]
pub struct MeshUniform {
model: Mat4,
inverse_transpose_model: Mat4,
flags: u32,
}
// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.wgsl!
bitflags::bitflags! {
#[repr(transparent)]
struct MeshFlags: u32 {
const SHADOW_RECEIVER = (1 << 0);
const NONE = 0;
const UNINITIALIZED = 0xFFFF;
}
}
#[derive(Default)]
pub struct MeshMeta {
transform_uniforms: DynamicUniformVec<MeshUniform>,
material_bind_groups: FrameSlabMap<BufferId, BindGroup>,
mesh_transform_bind_group: FrameSlabMap<BufferId, BindGroup>,
mesh_transform_bind_group_key: Option<FrameSlabMapKey<BufferId, BindGroup>>,
mesh_draw_info: Vec<MeshDrawInfo>,
}
pub fn prepare_meshes(
pbr_shaders: Res<PbrShaders>,
render_device: Res<RenderDevice>,
mut mesh_meta: ResMut<MeshMeta>,
mut extracted_meshes: ResMut<ExtractedMeshes>,
transform_uniforms: Res<ComponentUniforms<MeshUniform>>,
) {
mesh_meta
.transform_uniforms
.reserve_and_clear(extracted_meshes.meshes.len(), &render_device);
for extracted_mesh in extracted_meshes.meshes.iter_mut() {
let model = extracted_mesh.transform;
let inverse_transpose_model = model.inverse().transpose();
let flags = if extracted_mesh.receives_shadows {
MeshFlags::SHADOW_RECEIVER
} else {
MeshFlags::NONE
};
extracted_mesh.transform_binding_offset = mesh_meta.transform_uniforms.push(MeshUniform {
model,
inverse_transpose_model,
flags: flags.bits,
if let Some(binding) = transform_uniforms.uniforms().binding() {
commands.insert_resource(TransformBindGroup {
value: render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: binding,
}],
label: None,
layout: &pbr_shaders.mesh_layout,
}),
});
}
mesh_meta
.transform_uniforms
.write_to_staging_buffer(&render_device);
}
pub struct MeshViewBindGroups {
view: BindGroup,
}
fn image_handle_to_view_sampler<'a>(
pbr_shaders: &'a PbrShaders,
gpu_images: &'a RenderAssets<Image>,
image_option: &Option<Handle<Image>>,
) -> (&'a TextureView, &'a Sampler) {
image_option.as_ref().map_or(
(
&pbr_shaders.dummy_white_gpu_image.texture_view,
&pbr_shaders.dummy_white_gpu_image.sampler,
),
|image_handle| {
let gpu_image = gpu_images
.get(image_handle)
.expect("only materials with valid textures should be drawn");
(&gpu_image.texture_view, &gpu_image.sampler)
},
)
pub struct PbrViewBindGroup {
pub value: BindGroup,
}
#[allow(clippy::too_many_arguments)]
pub fn queue_meshes(
mut commands: Commands,
draw_functions: Res<DrawFunctions>,
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
render_device: Res<RenderDevice>,
pbr_shaders: Res<PbrShaders>,
shadow_shaders: Res<ShadowShaders>,
mesh_meta: ResMut<MeshMeta>,
mut light_meta: ResMut<LightMeta>,
view_meta: Res<ViewMeta>,
mut extracted_meshes: ResMut<ExtractedMeshes>,
gpu_images: Res<RenderAssets<Image>>,
light_meta: Res<LightMeta>,
view_uniforms: Res<ViewUniforms>,
render_materials: Res<RenderAssets<StandardMaterial>>,
standard_material_meshes: Query<
(Entity, &Handle<StandardMaterial>, &MeshUniform),
With<Handle<Mesh>>,
>,
mut views: Query<(
Entity,
&ExtractedView,
&ViewLights,
&mut RenderPhase<Transparent3dPhase>,
&mut RenderPhase<Transparent3d>,
)>,
mut view_light_shadow_phases: Query<&mut RenderPhase<ShadowPhase>>,
) {
let mesh_meta = mesh_meta.into_inner();
if view_meta.uniforms.is_empty() {
return;
}
light_meta.shadow_view_bind_group.get_or_insert_with(|| {
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: view_meta.uniforms.binding(),
}],
label: None,
layout: &shadow_shaders.view_layout,
})
});
if extracted_meshes.meshes.is_empty() {
return;
}
let transform_uniforms = &mesh_meta.transform_uniforms;
mesh_meta.mesh_transform_bind_group.next_frame();
mesh_meta.mesh_transform_bind_group_key =
Some(mesh_meta.mesh_transform_bind_group.get_or_insert_with(
transform_uniforms.uniform_buffer().unwrap().id(),
|| {
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
if let (Some(view_binding), Some(light_binding)) = (
view_uniforms.uniforms.binding(),
light_meta.view_gpu_lights.binding(),
) {
for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() {
let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: transform_uniforms.binding(),
}],
label: None,
layout: &pbr_shaders.mesh_layout,
})
},
));
for (entity, view, view_lights, mut transparent_phase) in views.iter_mut() {
// TODO: cache this?
let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: view_meta.uniforms.binding(),
},
BindGroupEntry {
binding: 1,
resource: light_meta.view_gpu_lights.binding(),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(
&view_lights.point_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&shadow_shaders.point_light_sampler),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::TextureView(
&view_lights.directional_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::Sampler(&shadow_shaders.directional_light_sampler),
},
],
label: None,
layout: &pbr_shaders.view_layout,
});
commands.entity(entity).insert(MeshViewBindGroups {
view: view_bind_group,
});
let draw_pbr = draw_functions.read().get_id::<DrawPbr>().unwrap();
mesh_meta.mesh_draw_info.clear();
mesh_meta.material_bind_groups.next_frame();
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
for (i, mesh) in extracted_meshes.meshes.iter_mut().enumerate() {
let gpu_material = &render_materials
.get(&mesh.material_handle)
.expect("Failed to get StandardMaterial PreparedAsset");
let material_bind_group_key =
mesh_meta
.material_bind_groups
.get_or_insert_with(gpu_material.buffer.id(), || {
let (base_color_texture_view, base_color_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.base_color_texture,
);
let (emissive_texture_view, emissive_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.emissive_texture,
);
let (metallic_roughness_texture_view, metallic_roughness_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.metallic_roughness_texture,
);
let (occlusion_texture_view, occlusion_sampler) =
image_handle_to_view_sampler(
&pbr_shaders,
&gpu_images,
&gpu_material.occlusion_texture,
);
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: gpu_material.buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(base_color_texture_view),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::Sampler(base_color_sampler),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureView(emissive_texture_view),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::Sampler(emissive_sampler),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::TextureView(
metallic_roughness_texture_view,
),
},
BindGroupEntry {
binding: 6,
resource: BindingResource::Sampler(metallic_roughness_sampler),
},
BindGroupEntry {
binding: 7,
resource: BindingResource::TextureView(occlusion_texture_view),
},
BindGroupEntry {
binding: 8,
resource: BindingResource::Sampler(occlusion_sampler),
},
],
label: None,
layout: &pbr_shaders.material_layout,
})
});
mesh_meta.mesh_draw_info.push(MeshDrawInfo {
material_bind_group_key,
resource: view_binding.clone(),
},
BindGroupEntry {
binding: 1,
resource: light_binding.clone(),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::TextureView(
&view_lights.point_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::Sampler(&shadow_shaders.point_light_sampler),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::TextureView(
&view_lights.directional_light_depth_texture_view,
),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::Sampler(
&shadow_shaders.directional_light_sampler,
),
},
],
label: None,
layout: &pbr_shaders.view_layout,
});
// NOTE: row 2 of the view matrix dotted with column 3 of the model matrix
// gives the z component of translation of the mesh in view space
let mesh_z = view_row_2.dot(mesh.transform.col(3));
// FIXME: Switch from usize to u64 for portability and use sort key encoding
// similar to https://realtimecollisiondetection.net/blog/?p=86 as appropriate
// FIXME: What is the best way to map from view space z to a number of bits of unsigned integer?
let sort_key = (((mesh_z * 1000.0) as usize) << 10)
| (material_bind_group_key.index() & ((1 << 10) - 1));
// TODO: currently there is only "transparent phase". this should pick transparent vs opaque according to the mesh material
transparent_phase.add(Drawable {
draw_function: draw_pbr,
draw_key: i,
sort_key,
commands.entity(entity).insert(PbrViewBindGroup {
value: view_bind_group,
});
}
// ultimately lights should check meshes for relevancy (ex: light views can "see" different meshes than the main view can)
let draw_shadow_mesh = draw_functions.read().get_id::<DrawShadowMesh>().unwrap();
for view_light_entity in view_lights.lights.iter().copied() {
let mut shadow_phase = view_light_shadow_phases.get_mut(view_light_entity).unwrap();
// TODO: this should only queue up meshes that are actually visible by each "light view"
for (i, mesh) in extracted_meshes.meshes.iter().enumerate() {
if mesh.casts_shadows {
shadow_phase.add(Drawable {
draw_function: draw_shadow_mesh,
draw_key: i,
sort_key: 0, // TODO: sort back-to-front
});
let draw_pbr = transparent_3d_draw_functions
.read()
.get_id::<DrawPbr>()
.unwrap();
let view_matrix = view.transform.compute_matrix();
let view_row_2 = view_matrix.row(2);
for (entity, material_handle, mesh_uniform) in standard_material_meshes.iter() {
if !render_materials.contains_key(material_handle) {
continue;
}
// NOTE: row 2 of the view matrix dotted with column 3 of the model matrix
// gives the z component of translation of the mesh in view space
let mesh_z = view_row_2.dot(mesh_uniform.transform.col(3));
// TODO: currently there is only "transparent phase". this should pick transparent vs opaque according to the mesh material
transparent_phase.add(Transparent3d {
entity,
draw_function: draw_pbr,
distance: mesh_z,
});
}
}
}
}
// TODO: this logic can be moved to prepare_meshes once wgpu::Queue is exposed directly
pub struct PbrNode;
impl Node for PbrNode {
fn run(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let mesh_meta = world.get_resource::<MeshMeta>().unwrap();
let light_meta = world.get_resource::<LightMeta>().unwrap();
mesh_meta
.transform_uniforms
.write_to_uniform_buffer(&mut render_context.command_encoder);
light_meta
.view_gpu_lights
.write_to_uniform_buffer(&mut render_context.command_encoder);
Ok(())
}
}
type DrawPbrParams<'s, 'w> = (
Res<'w, PbrShaders>,
Res<'w, MeshMeta>,
Res<'w, ExtractedMeshes>,
Res<'w, RenderAssets<Mesh>>,
Query<
'w,
's,
(
&'w ViewUniformOffset,
&'w ViewLights,
&'w MeshViewBindGroups,
),
>,
pub type DrawPbr = (
SetPbrPipeline,
SetMeshViewBindGroup<0>,
SetStandardMaterialBindGroup<1>,
SetTransformBindGroup<2>,
DrawMesh,
);
pub struct DrawPbr {
params: SystemState<DrawPbrParams<'static, 'static>>,
}
impl DrawPbr {
pub fn new(world: &mut World) -> Self {
Self {
params: SystemState::new(world),
}
pub struct SetPbrPipeline;
impl RenderCommand<Transparent3d> for SetPbrPipeline {
type Param = SRes<PbrShaders>;
#[inline]
fn render<'w>(
_view: Entity,
_item: &Transparent3d,
pbr_shaders: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) {
pass.set_render_pipeline(&pbr_shaders.into_inner().pipeline);
}
}
impl Draw for DrawPbr {
fn draw<'w, 's>(
&'s mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
pub struct SetMeshViewBindGroup<const I: usize>;
impl<const I: usize> RenderCommand<Transparent3d> for SetMeshViewBindGroup<I> {
type Param = SQuery<(
Read<ViewUniformOffset>,
Read<ViewLights>,
Read<PbrViewBindGroup>,
)>;
#[inline]
fn render<'w>(
view: Entity,
draw_key: usize,
_sort_key: usize,
_item: &Transparent3d,
view_query: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) {
let (pbr_shaders, mesh_meta, extracted_meshes, meshes, views) = self.params.get(world);
let (view_uniforms, view_lights, mesh_view_bind_groups) = views.get(view).unwrap();
let extracted_mesh = &extracted_meshes.into_inner().meshes[draw_key];
let mesh_meta = mesh_meta.into_inner();
pass.set_render_pipeline(&pbr_shaders.into_inner().pipeline);
let (view_uniform, view_lights, pbr_view_bind_group) = view_query.get(view).unwrap();
pass.set_bind_group(
0,
&mesh_view_bind_groups.view,
&[view_uniforms.offset, view_lights.gpu_light_binding_index],
);
let mesh_draw_info = &mesh_meta.mesh_draw_info[draw_key];
pass.set_bind_group(
1,
// &mesh_meta.material_bind_groups[sort_key & ((1 << 10) - 1)],
&mesh_meta.material_bind_groups[mesh_draw_info.material_bind_group_key],
&[],
);
pass.set_bind_group(
2,
mesh_meta
.mesh_transform_bind_group
.get_value(mesh_meta.mesh_transform_bind_group_key.unwrap())
.unwrap(),
&[extracted_mesh.transform_binding_offset],
I,
&pbr_view_bind_group.value,
&[view_uniform.offset, view_lights.gpu_light_binding_index],
);
}
}
let gpu_mesh = meshes.into_inner().get(&extracted_mesh.mesh).unwrap();
pub struct SetTransformBindGroup<const I: usize>;
impl<const I: usize> RenderCommand<Transparent3d> for SetTransformBindGroup<I> {
type Param = (
SRes<TransformBindGroup>,
SQuery<Read<DynamicUniformIndex<MeshUniform>>>,
);
#[inline]
fn render<'w>(
_view: Entity,
item: &Transparent3d,
(transform_bind_group, mesh_query): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) {
let transform_index = mesh_query.get(item.entity).unwrap();
pass.set_bind_group(
I,
&transform_bind_group.into_inner().value,
&[transform_index.index()],
);
}
}
pub struct SetStandardMaterialBindGroup<const I: usize>;
impl<const I: usize> RenderCommand<Transparent3d> for SetStandardMaterialBindGroup<I> {
type Param = (
SRes<RenderAssets<StandardMaterial>>,
SQuery<Read<Handle<StandardMaterial>>>,
);
#[inline]
fn render<'w>(
_view: Entity,
item: &Transparent3d,
(materials, handle_query): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) {
let handle = handle_query.get(item.entity).unwrap();
let materials = materials.into_inner();
let material = materials.get(handle).unwrap();
pass.set_bind_group(I, &material.bind_group, &[]);
}
}
pub struct DrawMesh;
impl RenderCommand<Transparent3d> for DrawMesh {
type Param = (SRes<RenderAssets<Mesh>>, SQuery<Read<Handle<Mesh>>>);
#[inline]
fn render<'w>(
_view: Entity,
item: &Transparent3d,
(meshes, mesh_query): SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) {
let mesh_handle = mesh_query.get(item.entity).unwrap();
let gpu_mesh = meshes.into_inner().get(mesh_handle).unwrap();
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
if let Some(index_info) = &gpu_mesh.index_info {
pass.set_index_buffer(index_info.buffer.slice(..), 0, IndexFormat::Uint32);

View file

@ -1,4 +1,3 @@
// TODO: try merging this block with the binding?
// NOTE: Keep in sync with depth.wgsl
[[block]]
struct View {

View file

@ -2,6 +2,7 @@ pub mod camera;
pub mod color;
pub mod mesh;
pub mod render_asset;
pub mod render_component;
pub mod render_graph;
pub mod render_phase;
pub mod render_resource;
@ -10,22 +11,21 @@ pub mod shader;
pub mod texture;
pub mod view;
use std::ops::{Deref, DerefMut};
pub use once_cell;
use wgpu::BackendBit;
use crate::{
camera::CameraPlugin,
mesh::MeshPlugin,
render_graph::RenderGraph,
render_phase::DrawFunctions,
renderer::render_system,
texture::ImagePlugin,
view::{ViewPlugin, WindowRenderPlugin},
};
use bevy_app::{App, AppLabel, Plugin};
use bevy_asset::AssetServer;
use bevy_ecs::prelude::*;
use std::ops::{Deref, DerefMut};
use wgpu::BackendBit;
#[derive(Default)]
pub struct RenderPlugin;
@ -96,6 +96,7 @@ impl Plugin for RenderPlugin {
app.insert_resource(device.clone())
.insert_resource(queue.clone())
.init_resource::<ScratchRenderWorld>();
let asset_server = app.world.get_resource::<AssetServer>().unwrap().clone();
let mut render_app = App::empty();
let mut extract_stage = SystemStage::parallel();
@ -115,8 +116,8 @@ impl Plugin for RenderPlugin {
.insert_resource(instance)
.insert_resource(device)
.insert_resource(queue)
.init_resource::<RenderGraph>()
.init_resource::<DrawFunctions>();
.insert_resource(asset_server)
.init_resource::<RenderGraph>();
app.add_sub_app(RenderApp, render_app, move |app_world, render_app| {
// reserve all existing app entities for use in render_app

View file

@ -1,11 +1,12 @@
mod conversions;
use crate::{
render_asset::RenderAsset,
render_asset::{PrepareAssetError, RenderAsset},
render_resource::Buffer,
renderer::{RenderDevice, RenderQueue},
renderer::RenderDevice,
};
use bevy_core::cast_slice;
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_math::*;
use bevy_reflect::TypeUuid;
use bevy_utils::EnumVariantMeta;
@ -540,6 +541,7 @@ pub struct GpuIndexInfo {
impl RenderAsset for Mesh {
type ExtractedAsset = Mesh;
type PreparedAsset = GpuMesh;
type Param = SRes<RenderDevice>;
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
@ -547,9 +549,8 @@ impl RenderAsset for Mesh {
fn prepare_asset(
mesh: Self::ExtractedAsset,
render_device: &RenderDevice,
_render_queue: &RenderQueue,
) -> Self::PreparedAsset {
render_device: &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let vertex_buffer_data = mesh.get_vertex_buffer_data();
let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsage::VERTEX,
@ -566,9 +567,9 @@ impl RenderAsset for Mesh {
count: mesh.indices().unwrap().len() as u32,
});
GpuMesh {
Ok(GpuMesh {
vertex_buffer,
index_info,
}
})
}
}

View file

@ -1,23 +1,26 @@
use std::marker::PhantomData;
use crate::{
renderer::{RenderDevice, RenderQueue},
RenderApp, RenderStage,
};
use crate::{RenderApp, RenderStage};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetEvent, Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, RunSystem, SystemParam, SystemParamItem},
};
use bevy_utils::{HashMap, HashSet};
use std::marker::PhantomData;
pub enum PrepareAssetError<E: Send + Sync + 'static> {
RetryNextUpdate(E),
}
pub trait RenderAsset: Asset {
type ExtractedAsset: Send + Sync + 'static;
type PreparedAsset: Send + Sync + 'static;
type Param: SystemParam;
fn extract_asset(&self) -> Self::ExtractedAsset;
fn prepare_asset(
extracted_asset: Self::ExtractedAsset,
render_device: &RenderDevice,
render_queue: &RenderQueue,
) -> Self::PreparedAsset;
param: &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>>;
}
/// Extracts assets into gpu-usable data
@ -31,15 +34,18 @@ impl<A: RenderAsset> Default for RenderAssetPlugin<A> {
impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
fn build(&self, app: &mut App) {
app.sub_app(RenderApp)
let render_app = app.sub_app(RenderApp);
let prepare_asset_system = PrepareAssetSystem::<A>::system(&mut render_app.world);
render_app
.init_resource::<ExtractedAssets<A>>()
.init_resource::<RenderAssets<A>>()
.init_resource::<PrepareNextFrameAssets<A>>()
.add_system_to_stage(RenderStage::Extract, extract_render_asset::<A>)
.add_system_to_stage(RenderStage::Prepare, prepare_render_asset::<A>);
.add_system_to_stage(RenderStage::Prepare, prepare_asset_system);
}
}
struct ExtractedAssets<A: RenderAsset> {
pub struct ExtractedAssets<A: RenderAsset> {
extracted: Vec<(Handle<A>, A::ExtractedAsset)>,
removed: Vec<Handle<A>>,
}
@ -91,18 +97,58 @@ fn extract_render_asset<A: RenderAsset>(
})
}
fn prepare_render_asset<R: RenderAsset>(
mut extracted_assets: ResMut<ExtractedAssets<R>>,
mut render_assets: ResMut<RenderAssets<R>>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
for removed in extracted_assets.removed.iter() {
render_assets.remove(removed);
}
pub type RenderAssetParams<R> = (
SResMut<ExtractedAssets<R>>,
SResMut<RenderAssets<R>>,
SResMut<PrepareNextFrameAssets<R>>,
<R as RenderAsset>::Param,
);
for (handle, extracted_asset) in extracted_assets.extracted.drain(..) {
let prepared_asset = R::prepare_asset(extracted_asset, &render_device, &render_queue);
render_assets.insert(handle, prepared_asset);
// TODO: consider storing inside system?
pub struct PrepareNextFrameAssets<A: RenderAsset> {
assets: Vec<(Handle<A>, A::ExtractedAsset)>,
}
impl<A: RenderAsset> Default for PrepareNextFrameAssets<A> {
fn default() -> Self {
Self {
assets: Default::default(),
}
}
}
pub struct PrepareAssetSystem<R: RenderAsset>(PhantomData<R>);
impl<R: RenderAsset> RunSystem for PrepareAssetSystem<R> {
type Param = RenderAssetParams<R>;
fn run(
(mut extracted_assets, mut render_assets, mut prepare_next_frame, mut param): SystemParamItem<Self::Param>,
) {
let mut queued_assets = std::mem::take(&mut prepare_next_frame.assets);
for (handle, extracted_asset) in queued_assets.drain(..) {
match R::prepare_asset(extracted_asset, &mut param) {
Ok(prepared_asset) => {
render_assets.insert(handle, prepared_asset);
}
Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
prepare_next_frame.assets.push((handle, extracted_asset));
}
}
}
for removed in std::mem::take(&mut extracted_assets.removed) {
render_assets.remove(&removed);
}
for (handle, extracted_asset) in std::mem::take(&mut extracted_assets.extracted) {
match R::prepare_asset(extracted_asset, &mut param) {
Ok(prepared_asset) => {
render_assets.insert(handle, prepared_asset);
}
Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
prepare_next_frame.assets.push((handle, extracted_asset));
}
}
}
}
}

View file

@ -0,0 +1,162 @@
use crate::{
render_resource::DynamicUniformVec,
renderer::{RenderDevice, RenderQueue},
RenderApp, RenderStage,
};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, Handle};
use bevy_ecs::{
component::Component,
prelude::*,
query::{FilterFetch, QueryItem, ReadOnlyFetch, WorldQuery},
system::{
lifetimeless::{Read, SCommands, SQuery},
RunSystem, SystemParamItem,
},
};
use crevice::std140::AsStd140;
use std::{marker::PhantomData, ops::Deref};
pub struct DynamicUniformIndex<C: Component> {
index: u32,
marker: PhantomData<C>,
}
impl<C: Component> DynamicUniformIndex<C> {
#[inline]
pub fn index(&self) -> u32 {
self.index
}
}
pub trait ExtractComponent: Component {
type Query: WorldQuery;
type Filter: WorldQuery;
fn extract_component(item: QueryItem<Self::Query>) -> Self;
}
/// Extracts assets into gpu-usable data
pub struct UniformComponentPlugin<C>(PhantomData<fn() -> C>);
impl<C> Default for UniformComponentPlugin<C> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<C: Component + AsStd140 + Clone> Plugin for UniformComponentPlugin<C> {
fn build(&self, app: &mut App) {
app.sub_app(RenderApp)
.insert_resource(ComponentUniforms::<C>::default())
.add_system_to_stage(
RenderStage::Prepare,
prepare_uniform_components::<C>.system(),
);
}
}
pub struct ComponentUniforms<C: Component + AsStd140> {
uniforms: DynamicUniformVec<C>,
}
impl<C: Component + AsStd140> Deref for ComponentUniforms<C> {
type Target = DynamicUniformVec<C>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.uniforms
}
}
impl<C: Component + AsStd140> ComponentUniforms<C> {
#[inline]
pub fn uniforms(&self) -> &DynamicUniformVec<C> {
&self.uniforms
}
}
impl<C: Component + AsStd140> Default for ComponentUniforms<C> {
fn default() -> Self {
Self {
uniforms: Default::default(),
}
}
}
fn prepare_uniform_components<C: Component>(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut component_uniforms: ResMut<ComponentUniforms<C>>,
components: Query<(Entity, &C)>,
) where
C: AsStd140 + Clone,
{
let len = components.iter().len();
component_uniforms
.uniforms
.reserve_and_clear(len, &render_device);
for (entity, component) in components.iter() {
commands
.get_or_spawn(entity)
.insert(DynamicUniformIndex::<C> {
index: component_uniforms.uniforms.push(component.clone()),
marker: PhantomData,
});
}
component_uniforms.uniforms.write_buffer(&render_queue);
}
pub struct ExtractComponentPlugin<C, F = ()>(PhantomData<fn() -> (C, F)>);
impl<C, F> Default for ExtractComponentPlugin<C, F> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<C: ExtractComponent> Plugin for ExtractComponentPlugin<C>
where
<C::Query as WorldQuery>::Fetch: ReadOnlyFetch,
<C::Filter as WorldQuery>::Fetch: FilterFetch,
{
fn build(&self, app: &mut App) {
let system = ExtractComponentSystem::<C>::system(&mut app.world);
let render_app = app.sub_app(RenderApp);
render_app.add_system_to_stage(RenderStage::Extract, system);
}
}
impl<T: Asset> ExtractComponent for Handle<T> {
type Query = Read<Handle<T>>;
type Filter = ();
#[inline]
fn extract_component(handle: QueryItem<Self::Query>) -> Self {
handle.clone_weak()
}
}
pub struct ExtractComponentSystem<C: ExtractComponent>(PhantomData<C>);
impl<C: ExtractComponent> RunSystem for ExtractComponentSystem<C>
where
<C::Filter as WorldQuery>::Fetch: FilterFetch,
<C::Query as WorldQuery>::Fetch: ReadOnlyFetch,
{
type Param = (
SCommands,
Local<'static, usize>,
SQuery<(Entity, C::Query), C::Filter>,
);
fn run((mut commands, mut previous_len, query): SystemParamItem<Self::Param>) {
let mut values = Vec::with_capacity(*previous_len);
for (entity, query_item) in query.iter() {
values.push((entity, (C::extract_component(query_item),)));
}
*previous_len = values.len();
commands.insert_or_spawn_batch(values);
}
}

View file

@ -1,59 +1,161 @@
use crate::render_phase::TrackedRenderPass;
use bevy_ecs::{entity::Entity, world::World};
use bevy_app::App;
use bevy_ecs::{
all_tuples,
entity::Entity,
system::{ReadOnlySystemParamFetch, SystemParam, SystemParamItem, SystemState},
world::World,
};
use bevy_utils::HashMap;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, fmt::Debug, hash::Hash};
// TODO: should this be generic on "drawn thing"? would provide more flexibility and explicitness
// instead of hard coded draw key and sort key
pub trait Draw: Send + Sync + 'static {
fn draw<'w, 's>(
&'s mut self,
pub trait Draw<P: PhaseItem>: Send + Sync + 'static {
fn draw<'w>(
&mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
view: Entity,
draw_key: usize,
sort_key: usize,
item: &P,
);
}
pub trait PhaseItem: Send + Sync + 'static {
type SortKey: Ord;
fn sort_key(&self) -> Self::SortKey;
fn draw_function(&self) -> DrawFunctionId;
}
// TODO: make this generic?
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct DrawFunctionId(usize);
#[derive(Default)]
pub struct DrawFunctionsInternal {
pub draw_functions: Vec<Box<dyn Draw>>,
pub struct DrawFunctionsInternal<P: PhaseItem> {
pub draw_functions: Vec<Box<dyn Draw<P>>>,
pub indices: HashMap<TypeId, DrawFunctionId>,
}
impl DrawFunctionsInternal {
pub fn add<D: Draw>(&mut self, draw_function: D) -> DrawFunctionId {
impl<P: PhaseItem> DrawFunctionsInternal<P> {
pub fn add<T: Draw<P>>(&mut self, draw_function: T) -> DrawFunctionId {
self.add_with::<T, T>(draw_function)
}
pub fn add_with<T: 'static, D: Draw<P>>(&mut self, draw_function: D) -> DrawFunctionId {
self.draw_functions.push(Box::new(draw_function));
let id = DrawFunctionId(self.draw_functions.len() - 1);
self.indices.insert(TypeId::of::<D>(), id);
self.indices.insert(TypeId::of::<T>(), id);
id
}
pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw> {
pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw<P>> {
self.draw_functions.get_mut(id.0).map(|f| &mut **f)
}
pub fn get_id<D: Draw>(&self) -> Option<DrawFunctionId> {
self.indices.get(&TypeId::of::<D>()).copied()
pub fn get_id<T: 'static>(&self) -> Option<DrawFunctionId> {
self.indices.get(&TypeId::of::<T>()).copied()
}
}
#[derive(Default)]
pub struct DrawFunctions {
internal: RwLock<DrawFunctionsInternal>,
pub struct DrawFunctions<P: PhaseItem> {
internal: RwLock<DrawFunctionsInternal<P>>,
}
impl DrawFunctions {
pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal> {
impl<P: PhaseItem> Default for DrawFunctions<P> {
fn default() -> Self {
Self {
internal: RwLock::new(DrawFunctionsInternal {
draw_functions: Vec::new(),
indices: HashMap::default(),
}),
}
}
}
impl<P: PhaseItem> DrawFunctions<P> {
pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal<P>> {
self.internal.read()
}
pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal> {
pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal<P>> {
self.internal.write()
}
}
pub trait RenderCommand<P: PhaseItem> {
type Param: SystemParam;
fn render<'w>(
view: Entity,
item: &P,
param: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
);
}
macro_rules! render_command_tuple_impl {
($($name: ident),*) => {
impl<P: PhaseItem, $($name: RenderCommand<P>),*> RenderCommand<P> for ($($name,)*) {
type Param = ($($name::Param,)*);
#[allow(non_snake_case)]
fn render<'w>(
_view: Entity,
_item: &P,
($($name,)*): SystemParamItem<'w, '_, Self::Param>,
_pass: &mut TrackedRenderPass<'w>,
) {
$($name::render(_view, _item, $name, _pass);)*
}
}
};
}
all_tuples!(render_command_tuple_impl, 0, 15, C);
pub struct RenderCommandState<P: PhaseItem, C: RenderCommand<P>> {
state: SystemState<C::Param>,
}
impl<P: PhaseItem, C: RenderCommand<P>> RenderCommandState<P, C> {
pub fn new(world: &mut World) -> Self {
Self {
state: SystemState::new(world),
}
}
}
impl<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static> Draw<P> for RenderCommandState<P, C>
where
<C::Param as SystemParam>::Fetch: ReadOnlySystemParamFetch,
{
fn draw<'w>(
&mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
view: Entity,
item: &P,
) {
let param = self.state.get(world);
C::render(view, item, param, pass);
}
}
pub trait AddRenderCommand {
fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
&mut self,
) -> &mut Self
where
<C::Param as SystemParam>::Fetch: ReadOnlySystemParamFetch;
}
impl AddRenderCommand for App {
fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
&mut self,
) -> &mut Self
where
<C::Param as SystemParam>::Fetch: ReadOnlySystemParamFetch,
{
let draw_function = RenderCommandState::<P, C>::new(&mut self.world);
let draw_functions = self.world.get_resource::<DrawFunctions<P>>().unwrap();
draw_functions.write().add_with::<C, _>(draw_function);
self
}
}

View file

@ -5,41 +5,29 @@ pub use draw::*;
pub use draw_state::*;
use bevy_ecs::prelude::Query;
use std::marker::PhantomData;
// TODO: make this configurable per phase?
pub struct Drawable {
pub draw_function: DrawFunctionId,
pub draw_key: usize,
pub sort_key: usize,
pub struct RenderPhase<I: PhaseItem> {
pub items: Vec<I>,
}
pub struct RenderPhase<T> {
pub drawn_things: Vec<Drawable>,
marker: PhantomData<fn() -> T>,
}
impl<T> Default for RenderPhase<T> {
impl<I: PhaseItem> Default for RenderPhase<I> {
fn default() -> Self {
Self {
drawn_things: Vec::new(),
marker: PhantomData,
}
Self { items: Vec::new() }
}
}
impl<T> RenderPhase<T> {
impl<I: PhaseItem> RenderPhase<I> {
#[inline]
pub fn add(&mut self, drawable: Drawable) {
self.drawn_things.push(drawable);
pub fn add(&mut self, item: I) {
self.items.push(item);
}
pub fn sort(&mut self) {
self.drawn_things.sort_by_key(|d| d.sort_key);
self.items.sort_by_key(|d| d.sort_key());
}
}
pub fn sort_phase_system<T: 'static>(mut render_phases: Query<&mut RenderPhase<T>>) {
pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
for mut phase in render_phases.iter_mut() {
phase.sort();
}

View file

@ -15,15 +15,14 @@ pub use texture::*;
pub use uniform_vec::*;
// TODO: decide where re-exports should go
pub use wgpu::util::BufferInitDescriptor;
pub use wgpu::{
AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BlendFactor,
BlendOperation, BlendState, BufferAddress, BufferBindingType, BufferSize, BufferUsage,
ColorTargetState, ColorWrite, CompareFunction, ComputePassDescriptor,
util::BufferInitDescriptor, AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent,
BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBindingType, BufferSize,
BufferUsage, ColorTargetState, ColorWrite, CompareFunction, ComputePassDescriptor,
ComputePipelineDescriptor, DepthBiasState, DepthStencilState, Extent3d, Face, FilterMode,
FragmentState, FrontFace, IndexFormat, InputStepMode, LoadOp, MultisampleState, Operations,
PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology,
PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology,
RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor,
RenderPipelineDescriptor, SamplerDescriptor, ShaderFlags, ShaderModule, ShaderModuleDescriptor,
ShaderSource, ShaderStage, StencilFaceState, StencilOperation, StencilState,

View file

@ -1,11 +1,14 @@
use crate::{render_resource::Buffer, renderer::RenderDevice};
use crate::{
render_resource::Buffer,
renderer::{RenderDevice, RenderQueue},
};
use crevice::std140::{self, AsStd140, DynamicUniform, Std140};
use std::{num::NonZeroU64, ops::DerefMut};
use wgpu::{BindingResource, BufferBinding, BufferDescriptor, BufferUsage, CommandEncoder};
use std::num::NonZeroU64;
use wgpu::{BindingResource, BufferBinding, BufferDescriptor, BufferUsage};
pub struct UniformVec<T: AsStd140> {
values: Vec<T>,
staging_buffer: Option<Buffer>,
scratch: Vec<u8>,
uniform_buffer: Option<Buffer>,
capacity: usize,
item_size: usize,
@ -15,7 +18,7 @@ impl<T: AsStd140> Default for UniformVec<T> {
fn default() -> Self {
Self {
values: Vec::new(),
staging_buffer: None,
scratch: Vec::new(),
uniform_buffer: None,
capacity: 0,
item_size: (T::std140_size_static() + <T as AsStd140>::Std140Type::ALIGNMENT - 1)
@ -25,23 +28,18 @@ impl<T: AsStd140> Default for UniformVec<T> {
}
impl<T: AsStd140> UniformVec<T> {
#[inline]
pub fn staging_buffer(&self) -> Option<&Buffer> {
self.staging_buffer.as_ref()
}
#[inline]
pub fn uniform_buffer(&self) -> Option<&Buffer> {
self.uniform_buffer.as_ref()
}
#[inline]
pub fn binding(&self) -> BindingResource {
BindingResource::Buffer(BufferBinding {
buffer: self.uniform_buffer().expect("uniform buffer should exist"),
pub fn binding(&self) -> Option<BindingResource> {
Some(BindingResource::Buffer(BufferBinding {
buffer: self.uniform_buffer()?,
offset: 0,
size: Some(NonZeroU64::new(self.item_size as u64).unwrap()),
})
}))
}
#[inline]
@ -75,16 +73,11 @@ impl<T: AsStd140> UniformVec<T> {
pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) {
if capacity > self.capacity {
self.capacity = capacity;
let size = (self.item_size * capacity) as wgpu::BufferAddress;
self.staging_buffer = Some(device.create_buffer(&BufferDescriptor {
label: None,
size,
usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: false,
}));
let size = self.item_size * capacity;
self.scratch.resize(size, 0);
self.uniform_buffer = Some(device.create_buffer(&BufferDescriptor {
label: None,
size,
size: size as wgpu::BufferAddress,
usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
mapped_at_creation: false,
}));
@ -96,29 +89,12 @@ impl<T: AsStd140> UniformVec<T> {
self.reserve(capacity, device);
}
pub fn write_to_staging_buffer(&self, device: &RenderDevice) {
if let Some(staging_buffer) = &self.staging_buffer {
let slice = staging_buffer.slice(..);
device.map_buffer(&slice, wgpu::MapMode::Write);
{
let mut data = slice.get_mapped_range_mut();
let mut writer = std140::Writer::new(data.deref_mut());
writer.write(self.values.as_slice()).unwrap();
}
staging_buffer.unmap()
}
}
pub fn write_to_uniform_buffer(&self, command_encoder: &mut CommandEncoder) {
if let (Some(staging_buffer), Some(uniform_buffer)) =
(&self.staging_buffer, &self.uniform_buffer)
{
command_encoder.copy_buffer_to_buffer(
staging_buffer,
0,
uniform_buffer,
0,
(self.values.len() * self.item_size) as u64,
);
pub fn write_buffer(&mut self, queue: &RenderQueue) {
if let Some(uniform_buffer) = &self.uniform_buffer {
let range = 0..self.item_size * self.values.len();
let mut writer = std140::Writer::new(&mut self.scratch[range.clone()]);
writer.write(self.values.as_slice()).unwrap();
queue.write_buffer(uniform_buffer, 0, &self.scratch[range]);
}
}
@ -140,18 +116,13 @@ impl<T: AsStd140> Default for DynamicUniformVec<T> {
}
impl<T: AsStd140> DynamicUniformVec<T> {
#[inline]
pub fn staging_buffer(&self) -> Option<&Buffer> {
self.uniform_vec.staging_buffer()
}
#[inline]
pub fn uniform_buffer(&self) -> Option<&Buffer> {
self.uniform_vec.uniform_buffer()
}
#[inline]
pub fn binding(&self) -> BindingResource {
pub fn binding(&self) -> Option<BindingResource> {
self.uniform_vec.binding()
}
@ -186,13 +157,8 @@ impl<T: AsStd140> DynamicUniformVec<T> {
}
#[inline]
pub fn write_to_staging_buffer(&self, device: &RenderDevice) {
self.uniform_vec.write_to_staging_buffer(device);
}
#[inline]
pub fn write_to_uniform_buffer(&self, command_encoder: &mut CommandEncoder) {
self.uniform_vec.write_to_uniform_buffer(command_encoder);
pub fn write_buffer(&mut self, queue: &RenderQueue) {
self.uniform_vec.write_buffer(queue);
}
#[inline]

View file

@ -1,10 +1,11 @@
use super::image_texture_conversion::image_to_texture;
use crate::{
render_asset::RenderAsset,
render_asset::{PrepareAssetError, RenderAsset},
render_resource::{Sampler, Texture, TextureView},
renderer::{RenderDevice, RenderQueue},
texture::BevyDefault,
};
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_reflect::TypeUuid;
use thiserror::Error;
use wgpu::{
@ -349,6 +350,7 @@ pub struct GpuImage {
impl RenderAsset for Image {
type ExtractedAsset = Image;
type PreparedAsset = GpuImage;
type Param = (SRes<RenderDevice>, SRes<RenderQueue>);
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
@ -356,9 +358,8 @@ impl RenderAsset for Image {
fn prepare_asset(
image: Self::ExtractedAsset,
render_device: &RenderDevice,
render_queue: &RenderQueue,
) -> Self::PreparedAsset {
(render_device, render_queue): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let texture = render_device.create_texture(&image.texture_descriptor);
let sampler = render_device.create_sampler(&image.sampler_descriptor);
@ -388,10 +389,10 @@ impl RenderAsset for Image {
);
let texture_view = texture.create_view(&TextureViewDescriptor::default());
GpuImage {
Ok(GpuImage {
texture,
texture_view,
sampler,
}
})
}
}

View file

@ -1,34 +1,25 @@
pub mod window;
use bevy_transform::components::GlobalTransform;
pub use window::*;
use crate::{
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext},
render_resource::DynamicUniformVec,
renderer::{RenderContext, RenderDevice},
renderer::{RenderDevice, RenderQueue},
RenderApp, RenderStage,
};
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use bevy_math::{Mat4, Vec3};
use bevy_transform::components::GlobalTransform;
use crevice::std140::AsStd140;
pub struct ViewPlugin;
impl ViewPlugin {
pub const VIEW_NODE: &'static str = "view";
}
impl Plugin for ViewPlugin {
fn build(&self, app: &mut App) {
let render_app = app.sub_app(RenderApp);
render_app
.init_resource::<ViewMeta>()
app.sub_app(RenderApp)
.init_resource::<ViewUniforms>()
.add_system_to_stage(RenderStage::Prepare, prepare_views);
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
graph.add_node(ViewPlugin::VIEW_NODE, ViewNode);
}
}
@ -47,7 +38,7 @@ pub struct ViewUniform {
}
#[derive(Default)]
pub struct ViewMeta {
pub struct ViewUniforms {
pub uniforms: DynamicUniformVec<ViewUniform>,
}
@ -57,17 +48,18 @@ pub struct ViewUniformOffset {
fn prepare_views(
mut commands: Commands,
render_resources: Res<RenderDevice>,
mut view_meta: ResMut<ViewMeta>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut view_uniforms: ResMut<ViewUniforms>,
mut extracted_views: Query<(Entity, &ExtractedView)>,
) {
view_meta
view_uniforms
.uniforms
.reserve_and_clear(extracted_views.iter_mut().len(), &render_resources);
.reserve_and_clear(extracted_views.iter_mut().len(), &render_device);
for (entity, camera) in extracted_views.iter() {
let projection = camera.projection;
let view_uniforms = ViewUniformOffset {
offset: view_meta.uniforms.push(ViewUniform {
offset: view_uniforms.uniforms.push(ViewUniform {
view_proj: projection * camera.transform.compute_matrix().inverse(),
projection,
world_position: camera.transform.translation,
@ -77,24 +69,5 @@ fn prepare_views(
commands.entity(entity).insert(view_uniforms);
}
view_meta
.uniforms
.write_to_staging_buffer(&render_resources);
}
pub struct ViewNode;
impl Node for ViewNode {
fn run(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let view_meta = world.get_resource::<ViewMeta>().unwrap();
view_meta
.uniforms
.write_to_uniform_buffer(&mut render_context.command_encoder);
Ok(())
}
view_uniforms.uniforms.write_buffer(&render_queue);
}

View file

@ -6,7 +6,6 @@ mod sprite;
mod texture_atlas;
mod texture_atlas_builder;
use bevy_asset::AddAsset;
pub use bundle::*;
pub use dynamic_texture_atlas_builder::*;
pub use rect::*;
@ -16,6 +15,8 @@ pub use texture_atlas::*;
pub use texture_atlas_builder::*;
use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_core_pipeline::Transparent2d;
use bevy_render2::{
render_graph::RenderGraph, render_phase::DrawFunctions, RenderApp, RenderStage,
};
@ -28,17 +29,18 @@ impl Plugin for SpritePlugin {
app.add_asset::<TextureAtlas>().register_type::<Sprite>();
let render_app = app.sub_app(RenderApp);
render_app
.init_resource::<ExtractedSprites>()
.init_resource::<ImageBindGroups>()
.init_resource::<SpriteShaders>()
.init_resource::<SpriteMeta>()
.add_system_to_stage(RenderStage::Extract, render::extract_atlases)
.add_system_to_stage(RenderStage::Extract, render::extract_sprites)
.add_system_to_stage(RenderStage::Prepare, render::prepare_sprites)
.add_system_to_stage(RenderStage::Queue, queue_sprites)
.init_resource::<SpriteShaders>()
.init_resource::<SpriteMeta>();
.add_system_to_stage(RenderStage::Queue, queue_sprites);
let draw_sprite = DrawSprite::new(&mut render_app.world);
render_app
.world
.get_resource::<DrawFunctions>()
.get_resource::<DrawFunctions<Transparent2d>>()
.unwrap()
.write()
.add(draw_sprite);

View file

@ -3,23 +3,25 @@ use crate::{
Rect, Sprite,
};
use bevy_asset::{Assets, Handle};
use bevy_core_pipeline::Transparent2dPhase;
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_core_pipeline::Transparent2d;
use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemState},
};
use bevy_math::{Mat4, Vec2, Vec3, Vec4Swizzles};
use bevy_render2::{
mesh::{shape::Quad, Indices, Mesh, VertexAttributeValues},
render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass},
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::*,
renderer::{RenderContext, RenderDevice},
shader::Shader,
texture::{BevyDefault, Image},
view::{ViewMeta, ViewUniformOffset},
RenderWorld,
view::{ViewUniformOffset, ViewUniforms},
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey};
use bevy_utils::HashMap;
use bytemuck::{Pod, Zeroable};
pub struct SpriteShaders {
@ -146,75 +148,71 @@ impl FromWorld for SpriteShaders {
}
}
struct ExtractedSprite {
pub struct ExtractedSprite {
transform: Mat4,
rect: Rect,
handle: Handle<Image>,
atlas_size: Option<Vec2>,
}
#[derive(Default)]
pub struct ExtractedSprites {
sprites: Vec<ExtractedSprite>,
vertex_index: usize,
}
pub fn extract_atlases(
mut commands: Commands,
texture_atlases: Res<Assets<TextureAtlas>>,
atlas_query: Query<(&TextureAtlasSprite, &GlobalTransform, &Handle<TextureAtlas>)>,
mut render_world: ResMut<RenderWorld>,
atlas_query: Query<(
Entity,
&TextureAtlasSprite,
&GlobalTransform,
&Handle<TextureAtlas>,
)>,
) {
let mut extracted_sprites = Vec::new();
for (atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
if !texture_atlases.contains(texture_atlas_handle) {
continue;
}
let mut sprites = Vec::new();
for (entity, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
let rect = texture_atlas.textures[atlas_sprite.index as usize];
extracted_sprites.push(ExtractedSprite {
atlas_size: Some(texture_atlas.size),
transform: transform.compute_matrix(),
rect,
handle: texture_atlas.texture.clone_weak(),
});
sprites.push((
entity,
(ExtractedSprite {
atlas_size: Some(texture_atlas.size),
transform: transform.compute_matrix(),
rect,
handle: texture_atlas.texture.clone_weak(),
vertex_index: 0,
},),
));
}
}
if let Some(mut extracted_sprites_res) = render_world.get_resource_mut::<ExtractedSprites>() {
extracted_sprites_res.sprites.extend(extracted_sprites);
}
commands.insert_or_spawn_batch(sprites);
}
pub fn extract_sprites(
mut commands: Commands,
images: Res<Assets<Image>>,
sprite_query: Query<(&Sprite, &GlobalTransform, &Handle<Image>)>,
mut render_world: ResMut<RenderWorld>,
sprite_query: Query<(Entity, &Sprite, &GlobalTransform, &Handle<Image>)>,
) {
let mut extracted_sprites = Vec::new();
for (sprite, transform, handle) in sprite_query.iter() {
let image = if let Some(image) = images.get(handle) {
image
} else {
continue;
let mut sprites = Vec::new();
for (entity, sprite, transform, handle) in sprite_query.iter() {
if let Some(image) = images.get(handle) {
let size = image.texture_descriptor.size;
sprites.push((
entity,
(ExtractedSprite {
atlas_size: None,
transform: transform.compute_matrix(),
rect: Rect {
min: Vec2::ZERO,
max: sprite
.custom_size
.unwrap_or_else(|| Vec2::new(size.width as f32, size.height as f32)),
},
handle: handle.clone_weak(),
vertex_index: 0,
},),
));
};
let size = image.texture_descriptor.size;
extracted_sprites.push(ExtractedSprite {
atlas_size: None,
transform: transform.compute_matrix(),
rect: Rect {
min: Vec2::ZERO,
max: sprite
.custom_size
.unwrap_or_else(|| Vec2::new(size.width as f32, size.height as f32)),
},
handle: handle.clone_weak(),
});
}
if let Some(mut extracted_sprites_res) = render_world.get_resource_mut::<ExtractedSprites>() {
extracted_sprites_res.sprites.extend(extracted_sprites);
}
commands.insert_or_spawn_batch(sprites);
}
#[repr(C)]
@ -229,8 +227,6 @@ pub struct SpriteMeta {
indices: BufferVec<u32>,
quad: Mesh,
view_bind_group: Option<BindGroup>,
texture_bind_group_keys: Vec<FrameSlabMapKey<Handle<Image>, BindGroup>>,
texture_bind_groups: FrameSlabMap<Handle<Image>, BindGroup>,
}
impl Default for SpriteMeta {
@ -238,8 +234,6 @@ impl Default for SpriteMeta {
Self {
vertices: BufferVec::new(BufferUsage::VERTEX),
indices: BufferVec::new(BufferUsage::INDEX),
texture_bind_groups: Default::default(),
texture_bind_group_keys: Default::default(),
view_bind_group: None,
quad: Quad {
size: Vec2::new(1.0, 1.0),
@ -253,10 +247,11 @@ impl Default for SpriteMeta {
pub fn prepare_sprites(
render_device: Res<RenderDevice>,
mut sprite_meta: ResMut<SpriteMeta>,
extracted_sprites: Res<ExtractedSprites>,
mut extracted_sprites: Query<&mut ExtractedSprite>,
) {
let extracted_sprite_len = extracted_sprites.iter_mut().len();
// dont create buffers when there are no sprites
if extracted_sprites.sprites.is_empty() {
if extracted_sprite_len == 0 {
return;
}
@ -279,15 +274,14 @@ pub fn prepare_sprites(
};
sprite_meta.vertices.reserve_and_clear(
extracted_sprites.sprites.len() * quad_vertex_positions.len(),
&render_device,
);
sprite_meta.indices.reserve_and_clear(
extracted_sprites.sprites.len() * quad_indices.len(),
extracted_sprite_len * quad_vertex_positions.len(),
&render_device,
);
sprite_meta
.indices
.reserve_and_clear(extracted_sprite_len * quad_indices.len(), &render_device);
for (i, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() {
for (i, mut extracted_sprite) in extracted_sprites.iter_mut().enumerate() {
let sprite_rect = extracted_sprite.rect;
// Specify the corners of the sprite
@ -298,6 +292,7 @@ pub fn prepare_sprites(
let atlas_positions: [Vec2; 4] = [bottom_left, top_left, top_right, bottom_right];
extracted_sprite.vertex_index = i;
for (index, vertex_position) in quad_vertex_positions.iter().enumerate() {
let mut final_position =
Vec3::from(*vertex_position) * extracted_sprite.rect.size().extend(1.0);
@ -321,71 +316,63 @@ pub fn prepare_sprites(
sprite_meta.indices.write_to_staging_buffer(&render_device);
}
#[derive(Default)]
pub struct ImageBindGroups {
values: HashMap<Handle<Image>, BindGroup>,
}
#[allow(clippy::too_many_arguments)]
pub fn queue_sprites(
draw_functions: Res<DrawFunctions>,
draw_functions: Res<DrawFunctions<Transparent2d>>,
render_device: Res<RenderDevice>,
mut sprite_meta: ResMut<SpriteMeta>,
view_meta: Res<ViewMeta>,
view_uniforms: Res<ViewUniforms>,
sprite_shaders: Res<SpriteShaders>,
mut extracted_sprites: ResMut<ExtractedSprites>,
mut image_bind_groups: ResMut<ImageBindGroups>,
gpu_images: Res<RenderAssets<Image>>,
mut views: Query<&mut RenderPhase<Transparent2dPhase>>,
mut extracted_sprites: Query<(Entity, &ExtractedSprite)>,
mut views: Query<&mut RenderPhase<Transparent2d>>,
) {
if view_meta.uniforms.is_empty() {
return;
}
// TODO: define this without needing to check every frame
sprite_meta.view_bind_group.get_or_insert_with(|| {
render_device.create_bind_group(&BindGroupDescriptor {
if let Some(view_binding) = view_uniforms.uniforms.binding() {
sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[BindGroupEntry {
binding: 0,
resource: view_meta.uniforms.binding(),
resource: view_binding,
}],
label: None,
layout: &sprite_shaders.view_layout,
})
});
let sprite_meta = &mut *sprite_meta;
let draw_sprite_function = draw_functions.read().get_id::<DrawSprite>().unwrap();
sprite_meta.texture_bind_groups.next_frame();
sprite_meta.texture_bind_group_keys.clear();
for mut transparent_phase in views.iter_mut() {
for (i, sprite) in extracted_sprites.sprites.iter().enumerate() {
let texture_bind_group_key = sprite_meta.texture_bind_groups.get_or_insert_with(
sprite.handle.clone_weak(),
|| {
let gpu_image = gpu_images.get(&sprite.handle).unwrap();
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&gpu_image.texture_view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&gpu_image.sampler),
},
],
label: None,
layout: &sprite_shaders.material_layout,
})
},
);
sprite_meta
.texture_bind_group_keys
.push(texture_bind_group_key);
transparent_phase.add(Drawable {
draw_function: draw_sprite_function,
draw_key: i,
sort_key: texture_bind_group_key.index(),
});
}));
let draw_sprite_function = draw_functions.read().get_id::<DrawSprite>().unwrap();
for mut transparent_phase in views.iter_mut() {
for (entity, sprite) in extracted_sprites.iter_mut() {
image_bind_groups
.values
.entry(sprite.handle.clone_weak())
.or_insert_with(|| {
let gpu_image = gpu_images.get(&sprite.handle).unwrap();
render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&gpu_image.texture_view),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::Sampler(&gpu_image.sampler),
},
],
label: None,
layout: &sprite_shaders.material_layout,
})
});
transparent_phase.add(Transparent2d {
draw_function: draw_sprite_function,
entity,
sort_key: sprite.handle.clone_weak(),
});
}
}
}
extracted_sprites.sprites.clear();
}
// TODO: this logic can be moved to prepare_sprites once wgpu::Queue is exposed directly
@ -409,13 +396,14 @@ impl Node for SpriteNode {
}
}
type DrawSpriteQuery<'s, 'w> = (
Res<'w, SpriteShaders>,
Res<'w, SpriteMeta>,
Query<'w, 's, &'w ViewUniformOffset>,
);
pub struct DrawSprite {
params: SystemState<DrawSpriteQuery<'static, 'static>>,
params: SystemState<(
SRes<SpriteShaders>,
SRes<SpriteMeta>,
SRes<ImageBindGroups>,
SQuery<Read<ViewUniformOffset>>,
SQuery<Read<ExtractedSprite>>,
)>,
}
impl DrawSprite {
@ -426,19 +414,21 @@ impl DrawSprite {
}
}
impl Draw for DrawSprite {
impl Draw<Transparent2d> for DrawSprite {
fn draw<'w>(
&mut self,
world: &'w World,
pass: &mut TrackedRenderPass<'w>,
view: Entity,
draw_key: usize,
_sort_key: usize,
item: &Transparent2d,
) {
const INDICES: usize = 6;
let (sprite_shaders, sprite_meta, views) = self.params.get(world);
let (sprite_shaders, sprite_meta, image_bind_groups, views, sprites) =
self.params.get(world);
let view_uniform = views.get(view).unwrap();
let sprite_meta = sprite_meta.into_inner();
let image_bind_groups = image_bind_groups.into_inner();
let extracted_sprite = sprites.get(item.entity).unwrap();
pass.set_render_pipeline(&sprite_shaders.into_inner().pipeline);
pass.set_vertex_buffer(0, sprite_meta.vertices.buffer().unwrap().slice(..));
pass.set_index_buffer(
@ -453,12 +443,16 @@ impl Draw for DrawSprite {
);
pass.set_bind_group(
1,
&sprite_meta.texture_bind_groups[sprite_meta.texture_bind_group_keys[draw_key]],
image_bind_groups
.values
.get(&extracted_sprite.handle)
.unwrap(),
&[],
);
pass.draw_indexed(
(draw_key * INDICES) as u32..(draw_key * INDICES + INDICES) as u32,
(extracted_sprite.vertex_index * INDICES) as u32
..(extracted_sprite.vertex_index * INDICES + INDICES) as u32,
0,
0..1,
);

View file

@ -1,11 +0,0 @@
#version 450
layout(location = 0) in vec2 v_Uv;
layout(location = 0) out vec4 o_Target;
layout(set = 1, binding = 0) uniform texture2D sprite_texture;
layout(set = 1, binding = 1) uniform sampler sprite_sampler;
void fragment() {
o_Target = texture(sampler2D(sprite_texture, sprite_sampler), v_Uv);
}

View file

@ -1,17 +0,0 @@
#version 450
layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec2 Vertex_Uv;
layout(location = 0) out vec2 v_Uv;
layout(set = 0, binding = 0) uniform View {
mat4 ViewProj;
vec4 ViewWorldPosition;
// vec3 ViewWorldPosition;
};
void vertex() {
v_Uv = Vertex_Uv;
gl_Position = ViewProj * vec4(Vertex_Position, 1.0);
}

View file

@ -1,4 +1,3 @@
// TODO: try merging this block with the binding?
[[block]]
struct View {
view_proj: mat4x4<f32>;