Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add 2d meshes and materials #3460

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ path = "examples/2d/contributors.rs"
name = "many_sprites"
path = "examples/2d/many_sprites.rs"

[[example]]
name = "mesh2d"
path = "examples/2d/mesh2d.rs"

[[example]]
name = "mesh2d_manual"
path = "examples/2d/mesh2d_manual.rs"

[[example]]
name = "rect"
path = "examples/2d/rect.rs"
Expand Down
48 changes: 45 additions & 3 deletions crates/bevy_core_pipeline/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub use main_pass_2d::*;
pub use main_pass_3d::*;
pub use main_pass_driver::*;

use std::ops::Range;

use bevy_app::{App, Plugin};
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
Expand All @@ -23,8 +25,8 @@ use bevy_render::{
color::Color,
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
render_phase::{
sort_phase_system, CachedPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem,
PhaseItem, RenderPhase,
batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedPipelinePhaseItem,
DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase,
},
render_resource::*,
renderer::RenderDevice,
Expand Down Expand Up @@ -84,6 +86,11 @@ pub mod clear_graph {
#[derive(Default)]
pub struct CorePipelinePlugin;

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum CorePipelineRenderSystems {
SortTransparent2d,
}

impl Plugin for CorePipelinePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ClearColor>();
Expand All @@ -97,7 +104,16 @@ impl Plugin for CorePipelinePlugin {
.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::<Transparent2d>)
.add_system_to_stage(
RenderStage::PhaseSort,
sort_phase_system::<Transparent2d>
.label(CorePipelineRenderSystems::SortTransparent2d),
)
.add_system_to_stage(
RenderStage::PhaseSort,
batch_phase_system::<Transparent2d>
.after(CorePipelineRenderSystems::SortTransparent2d),
)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
Expand Down Expand Up @@ -160,6 +176,8 @@ pub struct Transparent2d {
pub entity: Entity,
pub pipeline: CachedPipelineId,
pub draw_function: DrawFunctionId,
/// Range in the vertex buffer of this item
pub batch_range: Option<Range<u32>>,
}

impl PhaseItem for Transparent2d {
Expand All @@ -176,6 +194,30 @@ impl PhaseItem for Transparent2d {
}
}

impl EntityPhaseItem for Transparent2d {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}

impl CachedPipelinePhaseItem for Transparent2d {
#[inline]
fn cached_pipeline(&self) -> CachedPipelineId {
self.pipeline
}
}

impl BatchedPhaseItem for Transparent2d {
fn batch_range(&self) -> &Option<Range<u32>> {
&self.batch_range
}

fn batch_range_mut(&mut self) -> &mut Option<Range<u32>> {
&mut self.batch_range
}
}

pub struct Opaque3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
Expand Down
14 changes: 5 additions & 9 deletions crates/bevy_core_pipeline/src/main_pass_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bevy_ecs::prelude::*;
use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor},
render_resource::{LoadOp, Operations, RenderPassDescriptor},
renderer::RenderContext,
view::{ExtractedView, ViewTarget},
};
Expand Down Expand Up @@ -46,14 +46,10 @@ impl Node for MainPass2dNode {

let pass_descriptor = RenderPassDescriptor {
label: Some("main_pass_2d"),
color_attachments: &[RenderPassColorAttachment {
view: &target.view,
resolve_target: None,
ops: Operations {
load: LoadOp::Load,
store: true,
},
}],
color_attachments: &[target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
})],
depth_stencil_attachment: None,
};

Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
};
use bevy_math::Mat4;
use bevy_math::{Mat4, Size};
use bevy_reflect::TypeUuid;
use bevy_render::{
mesh::{GpuBufferInfo, Mesh},
Expand Down Expand Up @@ -328,6 +328,10 @@ impl FromWorld for MeshPipeline {
texture,
texture_view,
sampler,
size: Size::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
),
}
};
MeshPipeline {
Expand Down
40 changes: 39 additions & 1 deletion crates/bevy_render/src/render_phase/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use bevy_ecs::{
};
use bevy_utils::HashMap;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, fmt::Debug, hash::Hash};
use std::{any::TypeId, fmt::Debug, hash::Hash, ops::Range};

/// A draw function which is used to draw a specific [`PhaseItem`].
///
Expand Down Expand Up @@ -166,6 +166,44 @@ pub trait CachedPipelinePhaseItem: PhaseItem {
fn cached_pipeline(&self) -> CachedPipelineId;
}

/// A [`PhaseItem`] that can be batched dynamically.
///
/// Batching is an optimization that regroups multiple items in the same vertex buffer
/// to render them in a single draw call.
pub trait BatchedPhaseItem: EntityPhaseItem {
/// Range in the vertex buffer of this item
fn batch_range(&self) -> &Option<Range<u32>>;

/// Range in the vertex buffer of this item
fn batch_range_mut(&mut self) -> &mut Option<Range<u32>>;

/// Batches another item within this item if they are compatible.
/// Items can be batched together if they have the same entity, and consecutive ranges.
/// If batching is successful, the `other` item should be discarded from the render pass.
#[inline]
fn add_to_batch(&mut self, other: &Self) -> BatchResult {
let self_entity = self.entity();
if let (Some(self_batch_range), Some(other_batch_range)) = (
self.batch_range_mut().as_mut(),
other.batch_range().as_ref(),
) {
if self_entity == other.entity() && self_batch_range.end == other_batch_range.start {
Davier marked this conversation as resolved.
Show resolved Hide resolved
// If the items are compatible, join their range into `self`
self_batch_range.end = other_batch_range.end;
return BatchResult::Success;
}
}
BatchResult::IncompatibleItems
}
}

pub enum BatchResult {
/// The `other` item was batched into `self`
Success,
/// `self` and `other` cannot be batched together
IncompatibleItems,
}

impl<P: EntityPhaseItem, E: EntityRenderCommand> RenderCommand<P> for E {
type Param = E::Param;

Expand Down
157 changes: 157 additions & 0 deletions crates/bevy_render/src/render_phase/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,166 @@ impl<I: PhaseItem> RenderPhase<I> {
}
}

impl<I: BatchedPhaseItem> RenderPhase<I> {
/// Batches the compatible [`BatchedPhaseItem`]s of this render phase
pub fn batch(&mut self) {
// TODO: this could be done in-place
let mut items = std::mem::take(&mut self.items);
let mut items = items.drain(..);

self.items.reserve(items.len());

// Start the first batch from the first item
if let Some(mut current_batch) = items.next() {
// Batch following items until we find an incompatible item
for next_item in items {
if matches!(
current_batch.add_to_batch(&next_item),
BatchResult::IncompatibleItems
) {
// Store the completed batch, and start a new one from the incompatible item
self.items.push(current_batch);
current_batch = next_item;
}
}
// Store the last batch
self.items.push(current_batch);
}
}
}

/// This system sorts all [`RenderPhases`](RenderPhase) for the [`PhaseItem`] type.
pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
for mut phase in render_phases.iter_mut() {
phase.sort();
}
}

/// This system batches the [`PhaseItem`]s of all [`RenderPhase`]s of this type.
pub fn batch_phase_system<I: BatchedPhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
for mut phase in render_phases.iter_mut() {
phase.batch();
}
}

#[cfg(test)]
mod tests {
use std::ops::Range;

use bevy_ecs::entity::Entity;

use super::*;

#[test]
fn batching() {
#[derive(Debug, PartialEq)]
struct TestPhaseItem {
entity: Entity,
batch_range: Option<Range<u32>>,
}
impl PhaseItem for TestPhaseItem {
type SortKey = ();

fn sort_key(&self) -> Self::SortKey {}

fn draw_function(&self) -> DrawFunctionId {
unimplemented!();
}
}
impl EntityPhaseItem for TestPhaseItem {
fn entity(&self) -> bevy_ecs::entity::Entity {
self.entity
}
}
impl BatchedPhaseItem for TestPhaseItem {
fn batch_range(&self) -> &Option<std::ops::Range<u32>> {
&self.batch_range
}

fn batch_range_mut(&mut self) -> &mut Option<std::ops::Range<u32>> {
&mut self.batch_range
}
}
let mut render_phase = RenderPhase::<TestPhaseItem>::default();
let items = [
TestPhaseItem {
entity: Entity::from_raw(0),
batch_range: Some(0..5),
},
// This item should be batched
TestPhaseItem {
entity: Entity::from_raw(0),
batch_range: Some(5..10),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(0..5),
},
TestPhaseItem {
entity: Entity::from_raw(0),
batch_range: Some(10..15),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(5..10),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: None,
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(10..15),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(20..25),
},
// This item should be batched
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(25..30),
},
// This item should be batched
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(30..35),
},
];
for item in items {
render_phase.add(item);
}
render_phase.batch();
let items_batched = [
TestPhaseItem {
entity: Entity::from_raw(0),
batch_range: Some(0..10),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(0..5),
},
TestPhaseItem {
entity: Entity::from_raw(0),
batch_range: Some(10..15),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(5..10),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: None,
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(10..15),
},
TestPhaseItem {
entity: Entity::from_raw(1),
batch_range: Some(20..35),
},
];
assert_eq!(&*render_phase.items, items_batched);
}
}
Loading