mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
SubGraphs, Views, Shadows, and more
This commit is contained in:
parent
9588bb3243
commit
3400fb4e61
117 changed files with 4715 additions and 1818 deletions
|
@ -29,6 +29,7 @@ default = [
|
|||
"bevy_wgpu2",
|
||||
"bevy_sprite2",
|
||||
"bevy_render2",
|
||||
"bevy_pbr2",
|
||||
"bevy_winit",
|
||||
"render",
|
||||
"png",
|
||||
|
@ -54,6 +55,7 @@ bevy_winit = ["bevy_internal/bevy_winit"]
|
|||
bevy_wgpu2 = ["bevy_internal/bevy_wgpu2"]
|
||||
bevy_render2 = ["bevy_internal/bevy_render2"]
|
||||
bevy_sprite2 = ["bevy_internal/bevy_sprite2"]
|
||||
bevy_pbr2 = ["bevy_internal/bevy_pbr2"]
|
||||
|
||||
trace_chrome = ["bevy_internal/trace_chrome"]
|
||||
trace = ["bevy_internal/trace"]
|
||||
|
@ -142,6 +144,10 @@ path = "examples/2d/texture_atlas.rs"
|
|||
name = "3d_scene"
|
||||
path = "examples/3d/3d_scene.rs"
|
||||
|
||||
[[example]]
|
||||
name = "3d_scene_pipelined"
|
||||
path = "examples/3d/3d_scene_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "load_gltf"
|
||||
path = "examples/3d/load_gltf.rs"
|
||||
|
|
|
@ -25,6 +25,11 @@ impl ArchetypeId {
|
|||
ArchetypeId(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn invalid() -> ArchetypeId {
|
||||
ArchetypeId(usize::MAX)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn resource() -> ArchetypeId {
|
||||
ArchetypeId(1)
|
||||
|
|
|
@ -26,6 +26,12 @@ pub struct Entity {
|
|||
pub(crate) id: u32,
|
||||
}
|
||||
|
||||
pub enum AllocAtWithoutReplacement {
|
||||
Exists(EntityLocation),
|
||||
DidNotExist,
|
||||
ExistsWithWrongGeneration,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
/// Creates a new entity reference with a generation of 0.
|
||||
pub fn new(id: u32) -> Entity {
|
||||
|
@ -242,11 +248,8 @@ impl Entities {
|
|||
}
|
||||
|
||||
/// Allocate an entity ID directly.
|
||||
///
|
||||
/// Location should be written immediately.
|
||||
pub fn alloc(&mut self) -> Entity {
|
||||
self.verify_flushed();
|
||||
|
||||
self.len += 1;
|
||||
if let Some(id) = self.pending.pop() {
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
|
@ -294,6 +297,40 @@ impl Entities {
|
|||
loc
|
||||
}
|
||||
|
||||
/// Allocate a specific entity ID, overwriting its generation.
|
||||
///
|
||||
/// Returns the location of the entity currently using the given ID, if any.
|
||||
pub fn alloc_at_without_replacement(&mut self, entity: Entity) -> AllocAtWithoutReplacement {
|
||||
self.verify_flushed();
|
||||
|
||||
let result = if entity.id as usize >= self.meta.len() {
|
||||
self.pending.extend((self.meta.len() as u32)..entity.id);
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY);
|
||||
self.len += 1;
|
||||
AllocAtWithoutReplacement::DidNotExist
|
||||
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) {
|
||||
self.pending.swap_remove(index);
|
||||
let new_free_cursor = self.pending.len() as i64;
|
||||
*self.free_cursor.get_mut() = new_free_cursor;
|
||||
self.len += 1;
|
||||
AllocAtWithoutReplacement::DidNotExist
|
||||
} else {
|
||||
let current_meta = &mut self.meta[entity.id as usize];
|
||||
if current_meta.location.archetype_id == ArchetypeId::invalid() {
|
||||
AllocAtWithoutReplacement::DidNotExist
|
||||
} else if current_meta.generation == entity.generation {
|
||||
AllocAtWithoutReplacement::Exists(current_meta.location)
|
||||
} else {
|
||||
return AllocAtWithoutReplacement::ExistsWithWrongGeneration;
|
||||
}
|
||||
};
|
||||
|
||||
self.meta[entity.id as usize].generation = entity.generation;
|
||||
result
|
||||
}
|
||||
|
||||
/// Destroy an entity, allowing it to be reused.
|
||||
///
|
||||
/// Must not be called while reserved entities are awaiting `flush()`.
|
||||
|
@ -342,23 +379,13 @@ impl Entities {
|
|||
self.len = 0;
|
||||
}
|
||||
|
||||
/// Access the location storage of an entity.
|
||||
///
|
||||
/// Must not be called on pending entities.
|
||||
pub fn get_mut(&mut self, entity: Entity) -> Option<&mut EntityLocation> {
|
||||
let meta = &mut self.meta[entity.id as usize];
|
||||
if meta.generation == entity.generation {
|
||||
Some(&mut meta.location)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities.
|
||||
/// Returns `Ok(Location { archetype: Archetype::invalid(), index: undefined })` for pending entities.
|
||||
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
|
||||
if (entity.id as usize) < self.meta.len() {
|
||||
let meta = &self.meta[entity.id as usize];
|
||||
if meta.generation != entity.generation {
|
||||
if meta.generation != entity.generation
|
||||
|| meta.location.archetype_id == ArchetypeId::invalid()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(meta.location)
|
||||
|
@ -401,7 +428,7 @@ impl Entities {
|
|||
|
||||
/// Allocates space for entities previously reserved with `reserve_entity` or
|
||||
/// `reserve_entities`, then initializes each one using the supplied function.
|
||||
pub fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
|
||||
pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
|
||||
let free_cursor = self.free_cursor.get_mut();
|
||||
let current_free_cursor = *free_cursor;
|
||||
|
||||
|
@ -439,6 +466,16 @@ impl Entities {
|
|||
}
|
||||
}
|
||||
|
||||
// Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return None
|
||||
// unless they are later populated with a valid archetype.
|
||||
pub fn flush_as_invalid(&mut self) {
|
||||
unsafe {
|
||||
self.flush(|_entity, location| {
|
||||
location.archetype_id = ArchetypeId::invalid();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> u32 {
|
||||
self.len
|
||||
|
@ -460,7 +497,7 @@ impl EntityMeta {
|
|||
const EMPTY: EntityMeta = EntityMeta {
|
||||
generation: 0,
|
||||
location: EntityLocation {
|
||||
archetype_id: ArchetypeId::empty(),
|
||||
archetype_id: ArchetypeId::invalid(),
|
||||
index: usize::max_value(), // dummy value, to be filled in
|
||||
},
|
||||
};
|
||||
|
@ -493,7 +530,24 @@ mod tests {
|
|||
fn reserve_entity_len() {
|
||||
let mut e = Entities::default();
|
||||
e.reserve_entity();
|
||||
e.flush(|_, _| {});
|
||||
unsafe { e.flush(|_, _| {}) };
|
||||
assert_eq!(e.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_reserved_and_invalid() {
|
||||
let mut entities = Entities::default();
|
||||
let e = entities.reserve_entity();
|
||||
assert!(entities.contains(e));
|
||||
assert!(entities.get(e).is_none());
|
||||
|
||||
unsafe {
|
||||
entities.flush(|_entity, _location| {
|
||||
// do nothing ... leaving entity location invalid
|
||||
})
|
||||
};
|
||||
|
||||
assert!(entities.contains(e));
|
||||
assert!(entities.get(e).is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1227,6 +1227,7 @@ mod tests {
|
|||
assert_eq!(dropped2.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_entities() {
|
||||
let mut world = World::default();
|
||||
world
|
||||
|
@ -1266,4 +1267,130 @@ mod tests {
|
|||
"world should still contain resources"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserve_entities_across_worlds() {
|
||||
let mut world_a = World::default();
|
||||
let mut world_b = World::default();
|
||||
|
||||
let e1 = world_a.spawn().insert(1u32).id();
|
||||
let e2 = world_a.spawn().insert(2u32).id();
|
||||
let e3 = world_a.entities().reserve_entity();
|
||||
world_a.flush();
|
||||
|
||||
let world_a_max_entities = world_a.entities().meta.len();
|
||||
world_b
|
||||
.entities
|
||||
.reserve_entities(world_a_max_entities as u32);
|
||||
world_b.entities.flush_as_invalid();
|
||||
|
||||
let e4 = world_b.spawn().insert(4u32).id();
|
||||
assert_eq!(
|
||||
e4,
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 3,
|
||||
},
|
||||
"new entity is created immediately after world_a's max entity"
|
||||
);
|
||||
assert!(world_b.get::<u32>(e1).is_none());
|
||||
assert!(world_b.get_entity(e1).is_none());
|
||||
|
||||
assert!(world_b.get::<u32>(e2).is_none());
|
||||
assert!(world_b.get_entity(e2).is_none());
|
||||
|
||||
assert!(world_b.get::<u32>(e3).is_none());
|
||||
assert!(world_b.get_entity(e3).is_none());
|
||||
|
||||
world_b.get_or_spawn(e1).unwrap().insert(1.0f32);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(e1),
|
||||
Some(&1.0f32),
|
||||
"spawning into 'world_a' entities works"
|
||||
);
|
||||
|
||||
world_b.get_or_spawn(e4).unwrap().insert(4.0f32);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(e4),
|
||||
Some(&4.0f32),
|
||||
"spawning into existing `world_b` entities works"
|
||||
);
|
||||
assert_eq!(
|
||||
world_b.get::<u32>(e4),
|
||||
Some(&4u32),
|
||||
"spawning into existing `world_b` entities works"
|
||||
);
|
||||
|
||||
let e4_mismatched_generation = Entity {
|
||||
generation: 1,
|
||||
id: 3,
|
||||
};
|
||||
assert!(
|
||||
world_b.get_or_spawn(e4_mismatched_generation).is_none(),
|
||||
"attempting to spawn on top of an entity with a mismatched entity generation fails"
|
||||
);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(e4),
|
||||
Some(&4.0f32),
|
||||
"failed mismatched spawn doesn't change existing entity"
|
||||
);
|
||||
assert_eq!(
|
||||
world_b.get::<u32>(e4),
|
||||
Some(&4u32),
|
||||
"failed mismatched spawn doesn't change existing entity"
|
||||
);
|
||||
|
||||
let high_non_existent_entity = Entity {
|
||||
generation: 0,
|
||||
id: 6,
|
||||
};
|
||||
world_b
|
||||
.get_or_spawn(high_non_existent_entity)
|
||||
.unwrap()
|
||||
.insert(10.0f32);
|
||||
assert_eq!(
|
||||
world_b.get::<f32>(high_non_existent_entity),
|
||||
Some(&10.0f32),
|
||||
"inserting into newly allocated high / non-continous entity id works"
|
||||
);
|
||||
|
||||
let high_non_existent_but_reserved_entity = Entity {
|
||||
generation: 0,
|
||||
id: 5,
|
||||
};
|
||||
assert!(
|
||||
world_b.get_entity(high_non_existent_but_reserved_entity).is_none(),
|
||||
"entities between high-newly allocated entity and continuous block of existing entities don't exist"
|
||||
);
|
||||
|
||||
let reserved_entities = vec![
|
||||
world_b.entities().reserve_entity(),
|
||||
world_b.entities().reserve_entity(),
|
||||
world_b.entities().reserve_entity(),
|
||||
world_b.entities().reserve_entity(),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
reserved_entities,
|
||||
vec![
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 5
|
||||
},
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 4
|
||||
},
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 7,
|
||||
},
|
||||
Entity {
|
||||
generation: 0,
|
||||
id: 8,
|
||||
},
|
||||
],
|
||||
"space between original entities and high entities is used for new entity ids"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ where
|
|||
matched_archetypes: Default::default(),
|
||||
archetype_component_access: Default::default(),
|
||||
};
|
||||
state.validate_world_and_update_archetypes(world);
|
||||
state.update_archetypes(world);
|
||||
state
|
||||
}
|
||||
|
||||
|
@ -78,11 +78,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn validate_world_and_update_archetypes(&mut self, world: &World) {
|
||||
if world.id() != self.world_id {
|
||||
panic!("Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.",
|
||||
std::any::type_name::<Self>());
|
||||
}
|
||||
pub fn update_archetypes(&mut self, world: &World) {
|
||||
self.validate_world(world);
|
||||
let archetypes = world.archetypes();
|
||||
let new_generation = archetypes.generation();
|
||||
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
|
||||
|
@ -93,6 +90,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn validate_world(&self, world: &World) {
|
||||
if world.id() != self.world_id {
|
||||
panic!("Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.",
|
||||
std::any::type_name::<Self>());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
if self.fetch_state.matches_archetype(archetype)
|
||||
&& self.filter_state.matches_archetype(archetype)
|
||||
|
@ -129,6 +134,27 @@ where
|
|||
unsafe { self.get_unchecked(world, entity) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_manual<'w>(
|
||||
&self,
|
||||
world: &'w World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w>>::Item, QueryEntityError>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
self.validate_world(world);
|
||||
// SAFETY: query is read only and world is validated
|
||||
unsafe {
|
||||
self.get_unchecked_manual(
|
||||
world,
|
||||
entity,
|
||||
world.last_change_tick(),
|
||||
world.read_change_tick(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_mut<'w>(
|
||||
&mut self,
|
||||
|
@ -149,7 +175,7 @@ where
|
|||
world: &'w World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w>>::Item, QueryEntityError> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.get_unchecked_manual(
|
||||
world,
|
||||
entity,
|
||||
|
@ -202,6 +228,18 @@ where
|
|||
unsafe { self.iter_unchecked(world) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter_manual<'w, 's>(&'s self, world: &'w World) -> QueryIter<'w, 's, Q, F>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
self.validate_world(world);
|
||||
// SAFETY: query is read only and world is validated
|
||||
unsafe {
|
||||
self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, Q, F> {
|
||||
// SAFETY: query has unique world access
|
||||
|
@ -238,7 +276,7 @@ where
|
|||
&'s mut self,
|
||||
world: &'w World,
|
||||
) -> QueryIter<'w, 's, Q, F> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick())
|
||||
}
|
||||
|
||||
|
@ -251,7 +289,7 @@ where
|
|||
&'s mut self,
|
||||
world: &'w World,
|
||||
) -> QueryCombinationIter<'w, 's, Q, F, K> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.iter_combinations_unchecked_manual(
|
||||
world,
|
||||
world.last_change_tick(),
|
||||
|
@ -325,7 +363,7 @@ where
|
|||
world: &'w World,
|
||||
func: impl FnMut(<Q::Fetch as Fetch<'w>>::Item),
|
||||
) {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.for_each_unchecked_manual(
|
||||
world,
|
||||
func,
|
||||
|
@ -376,7 +414,7 @@ where
|
|||
batch_size: usize,
|
||||
func: impl Fn(<Q::Fetch as Fetch<'w>>::Item) + Send + Sync + Clone,
|
||||
) {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.par_for_each_unchecked_manual(
|
||||
world,
|
||||
task_pool,
|
||||
|
|
|
@ -108,7 +108,7 @@ impl ComponentSparseSet {
|
|||
|
||||
pub fn clear(&mut self) {
|
||||
self.dense.clear();
|
||||
self.ticks.get_mut().clear();
|
||||
self.ticks.clear();
|
||||
self.entities.clear();
|
||||
self.sparse.clear();
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ impl Column {
|
|||
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
self.ticks.get_mut().clear();
|
||||
self.ticks.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -58,6 +58,16 @@ impl<'a> Commands<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_or_spawn(&mut self, entity: Entity) -> EntityCommands<'a, '_> {
|
||||
self.add(GetOrSpawn {
|
||||
entity,
|
||||
});
|
||||
EntityCommands {
|
||||
entity,
|
||||
commands: self,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is a hack to work around the "multiple worlds" limitations:
|
||||
// Right now Commands must allocate entities from their "scheduled" world, but Commands might be applied to other worlds,
|
||||
// such as the "render world"
|
||||
|
@ -278,6 +288,17 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub struct GetOrSpawn {
|
||||
entity: Entity,
|
||||
}
|
||||
|
||||
impl Command for GetOrSpawn
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
world.get_or_spawn(self.entity);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpawnBatch<I>
|
||||
where
|
||||
I: IntoIterator,
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::{
|
|||
Component, ComponentDescriptor, ComponentId, ComponentTicks, Components, ComponentsError,
|
||||
StorageType,
|
||||
},
|
||||
entity::{Entities, Entity},
|
||||
entity::{AllocAtWithoutReplacement, Entities, Entity},
|
||||
query::{FilterFetch, QueryState, WorldQuery},
|
||||
storage::{Column, SparseSet, Storages},
|
||||
};
|
||||
|
@ -94,6 +94,13 @@ impl World {
|
|||
&self.entities
|
||||
}
|
||||
|
||||
/// Retrieves this world's [Entities] collection mutably
|
||||
#[inline]
|
||||
pub fn entities_mut(&mut self) -> &mut Entities {
|
||||
&mut self.entities
|
||||
}
|
||||
|
||||
|
||||
/// Retrieves this world's [Archetypes] collection
|
||||
#[inline]
|
||||
pub fn archetypes(&self) -> &Archetypes {
|
||||
|
@ -215,6 +222,24 @@ impl World {
|
|||
self.get_entity_mut(entity).expect("Entity does not exist")
|
||||
}
|
||||
|
||||
/// Returns an EntityMut for an existing entity or creates one if it doesn't exist.
|
||||
/// This will return `None` if the entity exists with a different generation.
|
||||
#[inline]
|
||||
pub fn get_or_spawn(&mut self, entity: Entity) -> Option<EntityMut> {
|
||||
self.flush();
|
||||
match self.entities.alloc_at_without_replacement(entity) {
|
||||
AllocAtWithoutReplacement::Exists(location) => {
|
||||
// SAFE: `entity` exists and `location` is that entity's location
|
||||
Some(unsafe { EntityMut::new(self, entity, location) })
|
||||
}
|
||||
AllocAtWithoutReplacement::DidNotExist => {
|
||||
// SAFE: entity was just allocated
|
||||
Some(unsafe { self.spawn_at_internal(entity) })
|
||||
}
|
||||
AllocAtWithoutReplacement::ExistsWithWrongGeneration => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves an [EntityRef] that exposes read-only operations for the given `entity`.
|
||||
/// Returns [None] if the `entity` does not exist. Use [World::entity] if you don't want
|
||||
/// to unwrap the [EntityRef] yourself.
|
||||
|
@ -293,8 +318,14 @@ impl World {
|
|||
pub fn spawn(&mut self) -> EntityMut {
|
||||
self.flush();
|
||||
let entity = self.entities.alloc();
|
||||
// SAFE: entity was just allocated
|
||||
unsafe { self.spawn_at_internal(entity) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// must be called on an entity that was just allocated
|
||||
unsafe fn spawn_at_internal(&mut self, entity: Entity) -> EntityMut {
|
||||
let archetype = self.archetypes.empty_mut();
|
||||
unsafe {
|
||||
// PERF: consider avoiding allocating entities in the empty archetype unless needed
|
||||
let table_row = self.storages.tables[archetype.table_id()].allocate(entity);
|
||||
// SAFE: no components are allocated by archetype.allocate() because the archetype is
|
||||
|
@ -307,7 +338,6 @@ impl World {
|
|||
.location = location;
|
||||
EntityMut::new(self, entity, location)
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a batch of entities with the same component [Bundle] type. Takes a given [Bundle]
|
||||
/// iterator and returns a corresponding [Entity] iterator.
|
||||
|
@ -878,6 +908,7 @@ impl World {
|
|||
unsafe {
|
||||
let table = &mut self.storages.tables[empty_archetype.table_id()];
|
||||
// PERF: consider pre-allocating space for flushed entities
|
||||
// SAFE: entity is set to a valid location
|
||||
self.entities.flush(|entity, location| {
|
||||
// SAFE: no components are allocated by archetype.allocate() because the archetype
|
||||
// is empty
|
||||
|
|
|
@ -68,6 +68,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" }
|
|||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.5.0" }
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.5.0" }
|
||||
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.5.0" }
|
||||
bevy_pbr2 = { path = "../../pipelined/bevy_pbr2", optional = true, version = "0.5.0" }
|
||||
bevy_render = { path = "../bevy_render", optional = true, version = "0.5.0" }
|
||||
bevy_render2 = { path = "../../pipelined/bevy_render2", optional = true, version = "0.5.0" }
|
||||
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.5.0" }
|
||||
|
|
|
@ -119,7 +119,10 @@ impl PluginGroup for PipelinedDefaultPlugins {
|
|||
group.add(bevy_asset::AssetPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_render2")]
|
||||
{
|
||||
group.add(bevy_render2::RenderPlugin::default());
|
||||
group.add(bevy_render2::core_pipeline::CorePipelinePlugin::default());
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_winit")]
|
||||
group.add(bevy_winit::WinitPlugin::default());
|
||||
|
@ -129,5 +132,8 @@ impl PluginGroup for PipelinedDefaultPlugins {
|
|||
|
||||
#[cfg(feature = "bevy_sprite2")]
|
||||
group.add(bevy_sprite2::SpritePlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_pbr2")]
|
||||
group.add(bevy_pbr2::PbrPlugin::default());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,12 @@ pub mod pbr {
|
|||
pub use bevy_pbr::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_pbr2")]
|
||||
pub mod pbr2 {
|
||||
//! Physically based rendering.
|
||||
pub use bevy_pbr2::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub mod render {
|
||||
//! Cameras, meshes, textures, shaders, and pipelines.
|
||||
|
|
|
@ -33,7 +33,7 @@ use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter};
|
|||
/// # use bevy_log::LogSettings;
|
||||
/// # use bevy_utils::tracing::Level;
|
||||
/// fn main() {
|
||||
/// App::build()
|
||||
/// App::new()
|
||||
/// .insert_resource(LogSettings {
|
||||
/// level: Level::DEBUG,
|
||||
/// filter: "wgpu=error,bevy_render=info".to_string(),
|
||||
|
@ -53,7 +53,7 @@ use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter};
|
|||
/// # use bevy_app::App;
|
||||
/// # use bevy_log::LogPlugin;
|
||||
/// fn main() {
|
||||
/// App::build()
|
||||
/// App::new()
|
||||
/// .add_plugins_with(DefaultPlugins, |group| group.disable::<LogPlugin>())
|
||||
/// .run();
|
||||
/// }
|
||||
|
|
|
@ -102,7 +102,7 @@ impl Default for RenderPlugin {
|
|||
}
|
||||
|
||||
impl Plugin for RenderPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
fn build(&self, app: &mut App) {
|
||||
#[cfg(any(
|
||||
feature = "png",
|
||||
feature = "dds",
|
||||
|
|
|
@ -18,6 +18,7 @@ bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
|||
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.5.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
||||
raw-window-handle = "0.3.0"
|
||||
|
||||
# other
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
mod event;
|
||||
mod raw_window_handle;
|
||||
mod system;
|
||||
mod window;
|
||||
mod windows;
|
||||
|
||||
use bevy_ecs::system::IntoSystem;
|
||||
pub use crate::raw_window_handle::*;
|
||||
pub use event::*;
|
||||
pub use system::*;
|
||||
pub use window::*;
|
||||
|
@ -18,6 +19,7 @@ pub mod prelude {
|
|||
}
|
||||
|
||||
use bevy_app::{prelude::*, Events};
|
||||
use bevy_ecs::system::IntoSystem;
|
||||
|
||||
pub struct WindowPlugin {
|
||||
pub add_primary_window: bool,
|
||||
|
|
37
crates/bevy_window/src/raw_window_handle.rs
Normal file
37
crates/bevy_window/src/raw_window_handle.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
|
||||
/// This wrapper exist to enable safely passing a [`RawWindowHandle`] across threads. Extracting the handle
|
||||
/// is still an unsafe operation, so the caller must still validate that using the raw handle is safe for a given context.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawWindowHandleWrapper(RawWindowHandle);
|
||||
|
||||
impl RawWindowHandleWrapper {
|
||||
pub(crate) fn new(handle: RawWindowHandle) -> Self {
|
||||
Self(handle)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This returns a [`HasRawWindowHandle`] impl, which exposes [`RawWindowHandle`]. Some platforms
|
||||
/// have constraints on where/how this handle can be used. For example, some platforms don't support doing window
|
||||
/// operations off of the main thread. The caller must ensure the [`RawWindowHandle`] is only used in valid contexts.
|
||||
pub unsafe fn get_handle(&self) -> HasRawWindowHandleWrapper {
|
||||
HasRawWindowHandleWrapper(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// SAFE: RawWindowHandle is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only
|
||||
// exposed via an unsafe method that forces the user to make a call for a given platform. (ex: some platforms don't
|
||||
// support doing window operations off of the main thread).
|
||||
// A recommendation for this pattern (and more context) is available here:
|
||||
// https://github.com/rust-windowing/raw-window-handle/issues/59
|
||||
unsafe impl Send for RawWindowHandleWrapper {}
|
||||
unsafe impl Sync for RawWindowHandleWrapper {}
|
||||
|
||||
pub struct HasRawWindowHandleWrapper(RawWindowHandle);
|
||||
|
||||
// SAFE: the caller has validated that this is a valid context to get RawWindowHandle
|
||||
unsafe impl HasRawWindowHandle for HasRawWindowHandleWrapper {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use bevy_math::{IVec2, Vec2};
|
||||
use bevy_utils::{tracing::warn, Uuid};
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct WindowId(Uuid);
|
||||
|
@ -20,6 +21,8 @@ impl WindowId {
|
|||
|
||||
use std::fmt;
|
||||
|
||||
use crate::raw_window_handle::RawWindowHandleWrapper;
|
||||
|
||||
impl fmt::Display for WindowId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.to_simple().fmt(f)
|
||||
|
@ -123,6 +126,7 @@ pub struct Window {
|
|||
cursor_visible: bool,
|
||||
cursor_locked: bool,
|
||||
cursor_position: Option<Vec2>,
|
||||
raw_window_handle: RawWindowHandleWrapper,
|
||||
focused: bool,
|
||||
mode: WindowMode,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -198,6 +202,7 @@ impl Window {
|
|||
physical_height: u32,
|
||||
scale_factor: f64,
|
||||
position: Option<IVec2>,
|
||||
raw_window_handle: RawWindowHandle,
|
||||
) -> Self {
|
||||
Window {
|
||||
id,
|
||||
|
@ -216,6 +221,7 @@ impl Window {
|
|||
cursor_visible: window_descriptor.cursor_visible,
|
||||
cursor_locked: window_descriptor.cursor_locked,
|
||||
cursor_position: None,
|
||||
raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle),
|
||||
focused: true,
|
||||
mode: window_descriptor.mode,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -511,6 +517,10 @@ impl Window {
|
|||
pub fn is_focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandleWrapper {
|
||||
self.raw_window_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -27,6 +27,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
|||
|
||||
# other
|
||||
winit = { version = "0.25.0", default-features = false }
|
||||
raw-window-handle = "0.3.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
winit = { version = "0.25.0", features = ["web-sys"], default-features = false }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use bevy_math::IVec2;
|
||||
use bevy_utils::HashMap;
|
||||
use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode};
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
use winit::dpi::LogicalSize;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -137,6 +138,7 @@ impl WinitWindows {
|
|||
.map(|position| IVec2::new(position.x, position.y));
|
||||
let inner_size = winit_window.inner_size();
|
||||
let scale_factor = winit_window.scale_factor();
|
||||
let raw_window_handle = winit_window.raw_window_handle();
|
||||
self.windows.insert(winit_window.id(), winit_window);
|
||||
Window::new(
|
||||
window_id,
|
||||
|
@ -145,6 +147,7 @@ impl WinitWindows {
|
|||
inner_size.height,
|
||||
scale_factor,
|
||||
position,
|
||||
raw_window_handle,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,15 +13,19 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
[dependencies]
|
||||
crevice-derive = "0.6.0"
|
||||
crevice-derive = { version = "0.6.0", path = "crevice-derive" }
|
||||
|
||||
bytemuck = "1.4.1"
|
||||
mint = { version = "0.5.5", optional = true }
|
||||
mint = "0.5.5"
|
||||
glam = "0.15.1"
|
||||
|
||||
[dev-dependencies]
|
||||
cgmath = { version = "0.17.0", features = ["mint"] }
|
||||
insta = "0.16.1"
|
||||
type-layout = { version = "0.2.0", features = ["serde1"] }
|
||||
crevice-derive = { version = "0.6.0", features = ["test_type_layout"] }
|
||||
crevice-derive = { version = "0.6.0", path = "crevice-derive" }
|
||||
|
|
25
crates/crevice/crevice-derive/Cargo.toml
Normal file
25
crates/crevice/crevice-derive/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "crevice-derive"
|
||||
description = "Derive crate for the 'crevice' crate"
|
||||
version = "0.6.0"
|
||||
edition = "2018"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
documentation = "https://docs.rs/crevice-derive"
|
||||
homepage = "https://github.com/LPGhatguy/crevice"
|
||||
repository = "https://github.com/LPGhatguy/crevice"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
# Feature used for testing; enables type_layout derive on types.
|
||||
# Requires crate using derive to depend on type_layout as well.
|
||||
test_type_layout = []
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
syn = "1.0.40"
|
||||
quote = "1.0.7"
|
||||
proc-macro2 = "1.0.21"
|
301
crates/crevice/crevice-derive/src/lib.rs
Normal file
301
crates/crevice/crevice-derive/src/lib.rs
Normal file
|
@ -0,0 +1,301 @@
|
|||
use proc_macro::TokenStream as CompilerTokenStream;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Ident, Path};
|
||||
|
||||
#[proc_macro_derive(AsStd140)]
|
||||
pub fn derive_as_std140(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let expanded = EmitOptions::new("Std140", "std140", 16).emit(input);
|
||||
|
||||
CompilerTokenStream::from(expanded)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(AsStd430)]
|
||||
pub fn derive_as_std430(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let expanded = EmitOptions::new("Std430", "std430", 0).emit(input);
|
||||
|
||||
CompilerTokenStream::from(expanded)
|
||||
}
|
||||
|
||||
struct EmitOptions {
|
||||
/// The Rust-friendly name of the layout, like Std140.
|
||||
layout_name: Ident,
|
||||
|
||||
/// The minimum alignment for a struct in this layout.
|
||||
min_struct_alignment: usize,
|
||||
|
||||
/// The fully-qualified path to the Crevice module containing everything for
|
||||
/// this layout.
|
||||
mod_path: Path,
|
||||
|
||||
/// The fully-qualified path to the trait defining a type in this layout.
|
||||
trait_path: Path,
|
||||
|
||||
/// The fully-qualified path to the trait implemented for types that can be
|
||||
/// converted into this layout, like AsStd140.
|
||||
as_trait_path: Path,
|
||||
|
||||
/// The name of the associated type contained in AsTrait.
|
||||
as_trait_assoc: Ident,
|
||||
|
||||
/// The name of the method used to convert from AsTrait to Trait.
|
||||
as_trait_method: Ident,
|
||||
|
||||
// The name of the method used to convert from Trait to AsTrait.
|
||||
from_trait_method: Ident,
|
||||
|
||||
/// The name of the struct used for Padded type.
|
||||
padded_name: Ident,
|
||||
}
|
||||
|
||||
impl EmitOptions {
|
||||
fn new(layout_name: &'static str, mod_name: &'static str, min_struct_alignment: usize) -> Self {
|
||||
let mod_name = Ident::new(mod_name, Span::call_site());
|
||||
let layout_name = Ident::new(layout_name, Span::call_site());
|
||||
|
||||
let mod_path = parse_quote!(::crevice::#mod_name);
|
||||
let trait_path = parse_quote!(#mod_path::#layout_name);
|
||||
|
||||
let as_trait_name = format_ident!("As{}", layout_name);
|
||||
let as_trait_path = parse_quote!(#mod_path::#as_trait_name);
|
||||
let as_trait_assoc = format_ident!("{}Type", layout_name);
|
||||
let as_trait_method = format_ident!("as_{}", mod_name);
|
||||
let from_trait_method = format_ident!("from_{}", mod_name);
|
||||
|
||||
let padded_name = format_ident!("{}Padded", layout_name);
|
||||
|
||||
Self {
|
||||
layout_name,
|
||||
min_struct_alignment,
|
||||
|
||||
mod_path,
|
||||
trait_path,
|
||||
as_trait_path,
|
||||
as_trait_assoc,
|
||||
as_trait_method,
|
||||
from_trait_method,
|
||||
|
||||
padded_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(&self, input: DeriveInput) -> TokenStream {
|
||||
let min_struct_alignment = self.min_struct_alignment;
|
||||
let layout_name = &self.layout_name;
|
||||
let mod_path = &self.mod_path;
|
||||
let trait_path = &self.trait_path;
|
||||
let as_trait_path = &self.as_trait_path;
|
||||
let as_trait_assoc = &self.as_trait_assoc;
|
||||
let as_trait_method = &self.as_trait_method;
|
||||
let from_trait_method = &self.from_trait_method;
|
||||
let padded_name = &self.padded_name;
|
||||
|
||||
let visibility = input.vis;
|
||||
|
||||
let name = input.ident;
|
||||
let generated_name = format_ident!("{}{}", layout_name, name);
|
||||
let alignment_mod_name = format_ident!("{}{}Alignment", layout_name, name);
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let fields = match &input.data {
|
||||
Data::Struct(data) => match &data.fields {
|
||||
Fields::Named(fields) => fields,
|
||||
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
|
||||
Fields::Unit => panic!("Unit structs are not supported"),
|
||||
},
|
||||
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
|
||||
};
|
||||
|
||||
// Generate the names we'll use for calculating alignment of each field.
|
||||
// Each name will turn into a const fn that's invoked to compute the
|
||||
// size of a padding array before each field.
|
||||
let align_names: Vec<_> = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| format_ident!("_{}_align", field.ident.as_ref().unwrap()))
|
||||
.collect();
|
||||
|
||||
// Generate one function per field that is used to apply alignment
|
||||
// padding. Each function invokes all previous functions to calculate
|
||||
// the total offset into the struct for the current field, then aligns
|
||||
// up to the nearest multiple of alignment.
|
||||
let alignment_calculators: Vec<_> = fields
|
||||
.named
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, field)| {
|
||||
let align_name = &align_names[index];
|
||||
|
||||
let offset_accumulation =
|
||||
fields
|
||||
.named
|
||||
.iter()
|
||||
.zip(&align_names)
|
||||
.take(index)
|
||||
.map(|(field, align_name)| {
|
||||
let field_ty = &field.ty;
|
||||
quote! {
|
||||
offset += #align_name();
|
||||
offset += ::core::mem::size_of::<<#field_ty as #as_trait_path>::#as_trait_assoc>();
|
||||
}
|
||||
});
|
||||
|
||||
let pad_at_end = index
|
||||
.checked_sub(1)
|
||||
.map_or(quote!{0usize}, |prev_index|{
|
||||
let field = &fields.named[prev_index];
|
||||
let field_ty = &field.ty;
|
||||
quote! {
|
||||
if <<#field_ty as #as_trait_path>::#as_trait_assoc as #mod_path::#layout_name>::PAD_AT_END {
|
||||
<<#field_ty as #as_trait_path>::#as_trait_assoc as #mod_path::#layout_name>::ALIGNMENT
|
||||
}
|
||||
else {
|
||||
0usize
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let field_ty = &field.ty;
|
||||
|
||||
quote! {
|
||||
pub const fn #align_name() -> usize {
|
||||
let mut offset = 0;
|
||||
#( #offset_accumulation )*
|
||||
|
||||
::crevice::internal::align_offset(
|
||||
offset,
|
||||
::crevice::internal::max(
|
||||
<<#field_ty as #as_trait_path>::#as_trait_assoc as #mod_path::#layout_name>::ALIGNMENT,
|
||||
#pad_at_end
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Generate the struct fields that will be present in the generated
|
||||
// struct. Each field in the original struct turns into two fields in
|
||||
// the generated struct:
|
||||
//
|
||||
// * Alignment, a byte array whose size is computed from #align_name().
|
||||
// * Data, the layout-specific version of the original field.
|
||||
let generated_fields: Vec<_> = fields
|
||||
.named
|
||||
.iter()
|
||||
.zip(&align_names)
|
||||
.map(|(field, align_name)| {
|
||||
let field_ty = &field.ty;
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
|
||||
quote! {
|
||||
#align_name: [u8; #alignment_mod_name::#align_name()],
|
||||
#field_name: <#field_ty as #as_trait_path>::#as_trait_assoc,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Generate an initializer for each field in the original struct.
|
||||
// Alignment fields are filled in with zeroes using struct update
|
||||
// syntax.
|
||||
let field_initializers: Vec<_> = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
|
||||
quote!(#field_name: self.#field_name.#as_trait_method())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let field_unwrappers: Vec<_> = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field|{
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let field_ty = &field.ty;
|
||||
quote!(#field_name: <#field_ty as #as_trait_path>::#from_trait_method(value.#field_name))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// This fold builds up an expression that finds the maximum alignment out of
|
||||
// all of the fields in the struct. For this struct:
|
||||
//
|
||||
// struct Foo { a: ty1, b: ty2 }
|
||||
//
|
||||
// ...we should generate an expression like this:
|
||||
//
|
||||
// max(ty2_align, max(ty1_align, min_align))
|
||||
let struct_alignment = fields.named.iter().fold(
|
||||
quote!(#min_struct_alignment),
|
||||
|last, field| {
|
||||
let field_ty = &field.ty;
|
||||
|
||||
quote! {
|
||||
::crevice::internal::max(
|
||||
<<#field_ty as #as_trait_path>::#as_trait_assoc as #trait_path>::ALIGNMENT,
|
||||
#last,
|
||||
)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// For testing purposes, we can optionally generate type layout
|
||||
// information using the type-layout crate.
|
||||
let type_layout_derive = if cfg!(feature = "test_type_layout") {
|
||||
quote!(#[derive(::type_layout::TypeLayout)])
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[allow(non_snake_case)]
|
||||
mod #alignment_mod_name {
|
||||
use super::*;
|
||||
|
||||
#( #alignment_calculators )*
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#type_layout_derive
|
||||
#[repr(C)]
|
||||
#visibility struct #generated_name #ty_generics #where_clause {
|
||||
#( #generated_fields )*
|
||||
}
|
||||
|
||||
unsafe impl #impl_generics ::crevice::internal::bytemuck::Zeroable for #generated_name #ty_generics #where_clause {}
|
||||
unsafe impl #impl_generics ::crevice::internal::bytemuck::Pod for #generated_name #ty_generics #where_clause {}
|
||||
|
||||
unsafe impl #impl_generics #mod_path::#layout_name for #generated_name #ty_generics #where_clause {
|
||||
const ALIGNMENT: usize = #struct_alignment;
|
||||
const PAD_AT_END: bool = true;
|
||||
type Padded = #mod_path::#padded_name<Self, {::crevice::internal::align_offset(
|
||||
::core::mem::size_of::<#generated_name>(),
|
||||
#struct_alignment
|
||||
)}>;
|
||||
}
|
||||
|
||||
impl #impl_generics #as_trait_path for #name #ty_generics #where_clause {
|
||||
type #as_trait_assoc = #generated_name;
|
||||
|
||||
fn #as_trait_method(&self) -> Self::#as_trait_assoc {
|
||||
Self::#as_trait_assoc {
|
||||
#( #field_initializers, )*
|
||||
|
||||
..::crevice::internal::bytemuck::Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
fn #from_trait_method(value: Self::#as_trait_assoc) -> Self {
|
||||
Self {
|
||||
#( #field_unwrappers, )*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,10 @@ macro_rules! glam_vectors {
|
|||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std140(value: Self::Std140Type) -> Self {
|
||||
Self::new($(value.$field,)*)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsStd430 for $glam_ty {
|
||||
|
@ -28,6 +32,10 @@ macro_rules! glam_vectors {
|
|||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std430(value: Self::Std430Type) -> Self {
|
||||
Self::new($(value.$field,)*)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
@ -53,6 +61,14 @@ macro_rules! glam_matrices {
|
|||
..Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std140(value: Self::Std140Type) -> Self {
|
||||
Self::from_cols(
|
||||
$(
|
||||
<_ as AsStd140>::from_std140(value.$field),
|
||||
)*
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsStd430 for $glam_ty {
|
||||
|
@ -66,6 +82,14 @@ macro_rules! glam_matrices {
|
|||
..Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std430(value: Self::Std430Type) -> Self {
|
||||
Self::from_cols(
|
||||
$(
|
||||
<_ as AsStd430>::from_std430(value.$field),
|
||||
)*
|
||||
)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
|
|
@ -129,6 +129,7 @@ Crevice supports Rust 1.46.0 and newer due to use of new `const fn` features.
|
|||
*/
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod std140;
|
||||
pub mod std430;
|
||||
|
@ -136,7 +137,6 @@ pub mod std430;
|
|||
#[doc(hidden)]
|
||||
pub mod internal;
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
mod mint;
|
||||
|
||||
mod glam;
|
|
@ -16,6 +16,14 @@ macro_rules! mint_vectors {
|
|||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std140(value: Self::Std140Type) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: value.$field,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsStd430 for $mint_ty {
|
||||
|
@ -28,6 +36,14 @@ macro_rules! mint_vectors {
|
|||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std430(value: Self::Std430Type) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: value.$field,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
@ -37,6 +53,22 @@ mint_vectors! {
|
|||
mint::Vector2<f32>, Vec2, (x, y),
|
||||
mint::Vector3<f32>, Vec3, (x, y, z),
|
||||
mint::Vector4<f32>, Vec4, (x, y, z, w),
|
||||
|
||||
mint::Vector2<i32>, IVec2, (x, y),
|
||||
mint::Vector3<i32>, IVec3, (x, y, z),
|
||||
mint::Vector4<i32>, IVec4, (x, y, z, w),
|
||||
|
||||
mint::Vector2<u32>, UVec2, (x, y),
|
||||
mint::Vector3<u32>, UVec3, (x, y, z),
|
||||
mint::Vector4<u32>, UVec4, (x, y, z, w),
|
||||
|
||||
mint::Vector2<bool>, BVec2, (x, y),
|
||||
mint::Vector3<bool>, BVec3, (x, y, z),
|
||||
mint::Vector4<bool>, BVec4, (x, y, z, w),
|
||||
|
||||
mint::Vector2<f64>, DVec2, (x, y),
|
||||
mint::Vector3<f64>, DVec3, (x, y, z),
|
||||
mint::Vector4<f64>, DVec4, (x, y, z, w),
|
||||
}
|
||||
|
||||
macro_rules! mint_matrices {
|
||||
|
@ -53,6 +85,14 @@ macro_rules! mint_matrices {
|
|||
..Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std140(value: Self::Std140Type) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: <_ as AsStd140>::from_std140(value.$field),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsStd430 for $mint_ty {
|
||||
|
@ -66,6 +106,14 @@ macro_rules! mint_matrices {
|
|||
..Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_std430(value: Self::Std430Type) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: <_ as AsStd430>::from_std430(value.$field),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
@ -75,4 +123,8 @@ mint_matrices! {
|
|||
mint::ColumnMatrix2<f32>, Mat2, (x, y),
|
||||
mint::ColumnMatrix3<f32>, Mat3, (x, y, z),
|
||||
mint::ColumnMatrix4<f32>, Mat4, (x, y, z, w),
|
||||
|
||||
mint::ColumnMatrix2<f64>, DMat2, (x, y),
|
||||
mint::ColumnMatrix3<f64>, DMat3, (x, y, z),
|
||||
mint::ColumnMatrix4<f64>, DMat4, (x, y, z, w),
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@ mod dynamic_uniform;
|
|||
mod primitives;
|
||||
mod sizer;
|
||||
mod traits;
|
||||
#[cfg(feature = "std")]
|
||||
mod writer;
|
||||
|
||||
pub use self::dynamic_uniform::*;
|
||||
pub use self::primitives::*;
|
||||
pub use self::sizer::*;
|
||||
pub use self::traits::*;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::writer::*;
|
||||
|
||||
pub use crevice_derive::AsStd140;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::internal::max;
|
||||
#[allow(unused_imports)]
|
||||
use crate::internal::{align_offset, max};
|
||||
use crate::std140::{AsStd140, Std140};
|
||||
|
||||
/// Wrapper type that aligns the inner type to at least 256 bytes.
|
||||
|
@ -15,6 +16,10 @@ impl<T: AsStd140> AsStd140 for DynamicUniform<T> {
|
|||
fn as_std140(&self) -> Self::Std140Type {
|
||||
DynamicUniformStd140(self.0.as_std140())
|
||||
}
|
||||
|
||||
fn from_std140(value: Self::Std140Type) -> Self {
|
||||
DynamicUniform(<T as AsStd140>::from_std140(value.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// std140 variant of [`DynamicUniform`].
|
||||
|
@ -24,6 +29,13 @@ pub struct DynamicUniformStd140<T>(T);
|
|||
|
||||
unsafe impl<T: Std140> Std140 for DynamicUniformStd140<T> {
|
||||
const ALIGNMENT: usize = max(256, T::ALIGNMENT);
|
||||
#[cfg(const_evaluatable_checked)]
|
||||
type Padded = crate::std140::Std140Padded<
|
||||
Self,
|
||||
{ align_offset(core::mem::size_of::<T>(), max(256, T::ALIGNMENT)) },
|
||||
>;
|
||||
#[cfg(not(const_evaluatable_checked))]
|
||||
type Padded = crate::std140::InvalidPadded;
|
||||
}
|
||||
|
||||
unsafe impl<T: Zeroable> Zeroable for DynamicUniformStd140<T> {}
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::std140::Std140;
|
||||
use crate::std140::{Std140, Std140Padded};
|
||||
|
||||
use crate::internal::{align_offset, max};
|
||||
use core::mem::size_of;
|
||||
|
||||
unsafe impl Std140 for f32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Std140Padded<Self, 12>;
|
||||
}
|
||||
|
||||
unsafe impl Std140 for f64 {
|
||||
const ALIGNMENT: usize = 8;
|
||||
type Padded = Std140Padded<Self, 8>;
|
||||
}
|
||||
|
||||
unsafe impl Std140 for i32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Std140Padded<Self, 12>;
|
||||
}
|
||||
|
||||
unsafe impl Std140 for u32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Std140Padded<Self, 12>;
|
||||
}
|
||||
|
||||
macro_rules! vectors {
|
||||
|
@ -37,6 +44,7 @@ macro_rules! vectors {
|
|||
|
||||
unsafe impl Std140 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), max(16, $align))}>;
|
||||
}
|
||||
)+
|
||||
};
|
||||
|
@ -87,6 +95,9 @@ macro_rules! matrices {
|
|||
|
||||
unsafe impl Std140 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
/// Matrices are technically arrays of primitives, and as such require pad at end.
|
||||
const PAD_AT_END: bool = true;
|
||||
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), max(16, $align))}>;
|
||||
}
|
||||
)+
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::mem::size_of;
|
||||
use core::mem::size_of;
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use crate::std140::{AsStd140, Std140};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use core::mem::{size_of, MaybeUninit};
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::std140::Writer;
|
||||
|
||||
/// Trait implemented for all `std140` primitives. Generally should not be
|
||||
|
@ -15,6 +17,15 @@ pub unsafe trait Std140: Copy + Zeroable + Pod {
|
|||
/// control and zero their padding bytes, making converting them to and from
|
||||
/// slices safe.
|
||||
const ALIGNMENT: usize;
|
||||
/// Whether this type requires a padding at the end (ie, is a struct or an array
|
||||
/// of primitives).
|
||||
/// See https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159
|
||||
/// (rule 4 and 9)
|
||||
const PAD_AT_END: bool = false;
|
||||
/// Padded type (Std140Padded specialization)
|
||||
/// The usual implementation is
|
||||
/// type Padded = Std140Padded<Self, {align_offset(size_of::<Self>(), max(16, ALIGNMENT))}>;
|
||||
type Padded: Std140Convertible<Self>;
|
||||
|
||||
/// Casts the type to a byte array. Implementors should not override this
|
||||
/// method.
|
||||
|
@ -27,9 +38,38 @@ pub unsafe trait Std140: Copy + Zeroable + Pod {
|
|||
}
|
||||
}
|
||||
|
||||
/// Trait specifically for Std140::Padded, implements conversions between padded type and base type.
|
||||
pub trait Std140Convertible<T: Std140>: Copy {
|
||||
/// Convert from self to Std140
|
||||
fn into_std140(self) -> T;
|
||||
/// Convert from Std140 to self
|
||||
fn from_std140(_: T) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Std140> Std140Convertible<T> for T {
|
||||
fn into_std140(self) -> T {
|
||||
self
|
||||
}
|
||||
fn from_std140(also_self: T) -> Self {
|
||||
also_self
|
||||
}
|
||||
}
|
||||
|
||||
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
|
||||
/// For now, we'll just use this empty enum with no values.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum InvalidPadded {}
|
||||
impl<T: Std140> Std140Convertible<T> for InvalidPadded {
|
||||
fn into_std140(self) -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
fn from_std140(_: T) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
/**
|
||||
Trait implemented for all types that can be turned into `std140` values.
|
||||
|
||||
*
|
||||
This trait can often be `#[derive]`'d instead of manually implementing it. Any
|
||||
struct which contains only fields that also implement `AsStd140` can derive
|
||||
`AsStd140`.
|
||||
|
@ -80,6 +120,9 @@ pub trait AsStd140 {
|
|||
fn std140_size_static() -> usize {
|
||||
size_of::<Self::Std140Type>()
|
||||
}
|
||||
|
||||
/// Converts from `std140` version of self to self.
|
||||
fn from_std140(val: Self::Std140Type) -> Self;
|
||||
}
|
||||
|
||||
impl<T> AsStd140 for T
|
||||
|
@ -91,6 +134,75 @@ where
|
|||
fn as_std140(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
|
||||
fn from_std140(x: Self) -> Self {
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Std140Padded<T: Std140, const PAD: usize> {
|
||||
inner: T,
|
||||
_padding: [u8; PAD],
|
||||
}
|
||||
|
||||
unsafe impl<T: Std140, const PAD: usize> Zeroable for Std140Padded<T, PAD> {}
|
||||
unsafe impl<T: Std140, const PAD: usize> Pod for Std140Padded<T, PAD> {}
|
||||
|
||||
impl<T: Std140, const PAD: usize> Std140Convertible<T> for Std140Padded<T, PAD> {
|
||||
fn into_std140(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
fn from_std140(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
_padding: [0u8; PAD],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Std140Array<T: Std140, const N: usize>([T::Padded; N]);
|
||||
|
||||
unsafe impl<T: Std140, const N: usize> Zeroable for Std140Array<T, N> where T::Padded: Zeroable {}
|
||||
unsafe impl<T: Std140, const N: usize> Pod for Std140Array<T, N> where T::Padded: Pod {}
|
||||
unsafe impl<T: Std140, const N: usize> Std140 for Std140Array<T, N>
|
||||
where
|
||||
T::Padded: Pod,
|
||||
{
|
||||
const ALIGNMENT: usize = crate::internal::max(T::ALIGNMENT, 16);
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
impl<T: AsStd140, const N: usize> AsStd140 for [T; N]
|
||||
where
|
||||
<T::Std140Type as Std140>::Padded: Pod,
|
||||
{
|
||||
type Std140Type = Std140Array<T::Std140Type, N>;
|
||||
fn as_std140(&self) -> Self::Std140Type {
|
||||
let mut res: [MaybeUninit<<T::Std140Type as Std140>::Padded>; N] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(Std140Convertible::from_std140(self[i].as_std140()));
|
||||
}
|
||||
|
||||
unsafe { core::mem::transmute_copy(&res) }
|
||||
}
|
||||
|
||||
fn from_std140(val: Self::Std140Type) -> Self {
|
||||
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(AsStd140::from_std140(val.0[i].into_std140()));
|
||||
}
|
||||
|
||||
unsafe { core::mem::transmute_copy(&res) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented for all types that can be written into a buffer as
|
||||
|
@ -101,6 +213,7 @@ where
|
|||
/// `Std140` trait, `WriteStd140` directly writes bytes using a [`Writer`]. This
|
||||
/// makes `WriteStd140` usable for writing slices or other DSTs that could not
|
||||
/// implement `AsStd140` without allocating new memory on the heap.
|
||||
#[cfg(feature = "std")]
|
||||
pub trait WriteStd140 {
|
||||
/// Writes this value into the given [`Writer`] using `std140` layout rules.
|
||||
///
|
||||
|
@ -118,6 +231,7 @@ pub trait WriteStd140 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd140 for T
|
||||
where
|
||||
T: AsStd140,
|
||||
|
@ -131,6 +245,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd140 for [T]
|
||||
where
|
||||
T: WriteStd140,
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
mod primitives;
|
||||
mod sizer;
|
||||
mod traits;
|
||||
#[cfg(feature = "std")]
|
||||
mod writer;
|
||||
|
||||
pub use self::primitives::*;
|
||||
pub use self::sizer::*;
|
||||
pub use self::traits::*;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::writer::*;
|
||||
|
||||
pub use crevice_derive::AsStd430;
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::std430::Std430;
|
||||
use crate::std430::{Std430, Std430Padded};
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use core::mem::size_of;
|
||||
|
||||
unsafe impl Std430 for f32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
unsafe impl Std430 for f64 {
|
||||
const ALIGNMENT: usize = 8;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
unsafe impl Std430 for i32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
unsafe impl Std430 for u32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
macro_rules! vectors {
|
||||
|
@ -37,6 +44,7 @@ macro_rules! vectors {
|
|||
|
||||
unsafe impl Std430 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $align)}>;
|
||||
}
|
||||
)+
|
||||
};
|
||||
|
@ -87,6 +95,9 @@ macro_rules! matrices {
|
|||
|
||||
unsafe impl Std430 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
/// Matrices are technically arrays of primitives, and as such require pad at end.
|
||||
const PAD_AT_END: bool = true;
|
||||
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $align)}>;
|
||||
}
|
||||
)+
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::mem::size_of;
|
||||
use core::mem::size_of;
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use crate::std430::{AsStd430, Std430};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use core::mem::{size_of, MaybeUninit};
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::std430::Writer;
|
||||
|
||||
/// Trait implemented for all `std430` primitives. Generally should not be
|
||||
|
@ -15,6 +17,15 @@ pub unsafe trait Std430: Copy + Zeroable + Pod {
|
|||
/// control and zero their padding bytes, making converting them to and from
|
||||
/// slices safe.
|
||||
const ALIGNMENT: usize;
|
||||
/// Whether this type requires a padding at the end (ie, is a struct or an array
|
||||
/// of primitives).
|
||||
/// See https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159
|
||||
/// (rule 4 and 9)
|
||||
const PAD_AT_END: bool = false;
|
||||
/// Padded type (Std430Padded specialization)
|
||||
/// The usual implementation is
|
||||
/// type Padded = Std430Padded<Self, {align_offset(size_of::<Self>(), ALIGNMENT)}>;
|
||||
type Padded: Std430Convertible<Self>;
|
||||
|
||||
/// Casts the type to a byte array. Implementors should not override this
|
||||
/// method.
|
||||
|
@ -27,6 +38,35 @@ pub unsafe trait Std430: Copy + Zeroable + Pod {
|
|||
}
|
||||
}
|
||||
|
||||
/// Trait specifically for Std430::Padded, implements conversions between padded type and base type.
|
||||
pub trait Std430Convertible<T: Std430>: Copy {
|
||||
/// Convert from self to Std430
|
||||
fn into_std430(self) -> T;
|
||||
/// Convert from Std430 to self
|
||||
fn from_std430(_: T) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Std430> Std430Convertible<T> for T {
|
||||
fn into_std430(self) -> T {
|
||||
self
|
||||
}
|
||||
fn from_std430(also_self: T) -> Self {
|
||||
also_self
|
||||
}
|
||||
}
|
||||
|
||||
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
|
||||
/// For now, we'll just use this empty enum with no values.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum InvalidPadded {}
|
||||
impl<T: Std430> Std430Convertible<T> for InvalidPadded {
|
||||
fn into_std430(self) -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
fn from_std430(_: T) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
/**
|
||||
Trait implemented for all types that can be turned into `std430` values.
|
||||
|
||||
|
@ -80,6 +120,9 @@ pub trait AsStd430 {
|
|||
fn std430_size_static() -> usize {
|
||||
size_of::<Self::Std430Type>()
|
||||
}
|
||||
|
||||
/// Converts from `std430` version of self to self.
|
||||
fn from_std430(value: Self::Std430Type) -> Self;
|
||||
}
|
||||
|
||||
impl<T> AsStd430 for T
|
||||
|
@ -91,6 +134,75 @@ where
|
|||
fn as_std430(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
|
||||
fn from_std430(value: Self) -> Self {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Std430Padded<T: Std430, const PAD: usize> {
|
||||
inner: T,
|
||||
_padding: [u8; PAD],
|
||||
}
|
||||
|
||||
unsafe impl<T: Std430, const PAD: usize> Zeroable for Std430Padded<T, PAD> {}
|
||||
unsafe impl<T: Std430, const PAD: usize> Pod for Std430Padded<T, PAD> {}
|
||||
|
||||
impl<T: Std430, const PAD: usize> Std430Convertible<T> for Std430Padded<T, PAD> {
|
||||
fn into_std430(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
fn from_std430(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
_padding: [0u8; PAD],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Std430Array<T: Std430, const N: usize>([T::Padded; N]);
|
||||
|
||||
unsafe impl<T: Std430, const N: usize> Zeroable for Std430Array<T, N> where T::Padded: Zeroable {}
|
||||
unsafe impl<T: Std430, const N: usize> Pod for Std430Array<T, N> where T::Padded: Pod {}
|
||||
unsafe impl<T: Std430, const N: usize> Std430 for Std430Array<T, N>
|
||||
where
|
||||
T::Padded: Pod,
|
||||
{
|
||||
const ALIGNMENT: usize = T::ALIGNMENT;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
impl<T: AsStd430, const N: usize> AsStd430 for [T; N]
|
||||
where
|
||||
<T::Std430Type as Std430>::Padded: Pod,
|
||||
{
|
||||
type Std430Type = Std430Array<T::Std430Type, N>;
|
||||
fn as_std430(&self) -> Self::Std430Type {
|
||||
let mut res: [MaybeUninit<<T::Std430Type as Std430>::Padded>; N] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(Std430Convertible::from_std430(self[i].as_std430()));
|
||||
}
|
||||
|
||||
unsafe { core::mem::transmute_copy(&res) }
|
||||
}
|
||||
|
||||
fn from_std430(val: Self::Std430Type) -> Self {
|
||||
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(AsStd430::from_std430(val.0[i].into_std430()));
|
||||
}
|
||||
|
||||
unsafe { core::mem::transmute_copy(&res) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented for all types that can be written into a buffer as
|
||||
|
@ -101,6 +213,7 @@ where
|
|||
/// `Std430` trait, `WriteStd430` directly writes bytes using a [`Writer`]. This
|
||||
/// makes `WriteStd430` usable for writing slices or other DSTs that could not
|
||||
/// implement `AsStd430` without allocating new memory on the heap.
|
||||
#[cfg(feature = "std")]
|
||||
pub trait WriteStd430 {
|
||||
/// Writes this value into the given [`Writer`] using `std430` layout rules.
|
||||
///
|
||||
|
@ -118,6 +231,7 @@ pub trait WriteStd430 {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd430 for T
|
||||
where
|
||||
T: AsStd430,
|
||||
|
@ -131,6 +245,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd430 for [T]
|
||||
where
|
||||
T: WriteStd430,
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
source: tests/std140.rs
|
||||
expression: "<<MoreThan16Alignment as AsStd140>::Std140Type as TypeLayout>::type_layout()"
|
||||
---
|
||||
name: Std140MoreThan16Alignment
|
||||
size: 32
|
||||
alignment: 8
|
||||
fields:
|
||||
- Field:
|
||||
name: _doubles_align
|
||||
ty: "[u8 ; Std140MoreThan16AlignmentAlignment :: _doubles_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: doubles
|
||||
ty: "< DVec4 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 32
|
|
@ -1,40 +0,0 @@
|
|||
---
|
||||
source: tests/std140.rs
|
||||
expression: "<<PointLight as AsStd140>::Std140Type as TypeLayout>::type_layout()"
|
||||
---
|
||||
name: Std140PointLight
|
||||
size: 48
|
||||
alignment: 4
|
||||
fields:
|
||||
- Field:
|
||||
name: _position_align
|
||||
ty: "[u8 ; Std140PointLightAlignment :: _position_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: position
|
||||
ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 12
|
||||
- Field:
|
||||
name: _diffuse_align
|
||||
ty: "[u8 ; Std140PointLightAlignment :: _diffuse_align()]"
|
||||
size: 4
|
||||
- Field:
|
||||
name: diffuse
|
||||
ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 12
|
||||
- Field:
|
||||
name: _specular_align
|
||||
ty: "[u8 ; Std140PointLightAlignment :: _specular_align()]"
|
||||
size: 4
|
||||
- Field:
|
||||
name: specular
|
||||
ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 12
|
||||
- Field:
|
||||
name: _brightness_align
|
||||
ty: "[u8 ; Std140PointLightAlignment :: _brightness_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: brightness
|
||||
ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 4
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
source: tests/std140.rs
|
||||
expression: "<<PrimitiveF32 as AsStd140>::Std140Type as TypeLayout>::layout()"
|
||||
---
|
||||
name: Std140PrimitiveF32
|
||||
size: 8
|
||||
alignment: 4
|
||||
fields:
|
||||
- Field:
|
||||
name: _x_align
|
||||
ty: "[u8 ; Std140PrimitiveF32Alignment :: _x_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: x
|
||||
ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 4
|
||||
- Field:
|
||||
name: _y_align
|
||||
ty: "[u8 ; Std140PrimitiveF32Alignment :: _y_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: y
|
||||
ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 4
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
source: tests/std140.rs
|
||||
expression: "<<UsingVec3Padding as AsStd140>::Std140Type as TypeLayout>::layout()"
|
||||
---
|
||||
name: Std140UsingVec3Padding
|
||||
size: 16
|
||||
alignment: 4
|
||||
fields:
|
||||
- Field:
|
||||
name: _pos_align
|
||||
ty: "[u8 ; Std140UsingVec3PaddingAlignment :: _pos_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: pos
|
||||
ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 12
|
||||
- Field:
|
||||
name: _brightness_align
|
||||
ty: "[u8 ; Std140UsingVec3PaddingAlignment :: _brightness_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: brightness
|
||||
ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 4
|
|
@ -1,24 +0,0 @@
|
|||
---
|
||||
source: tests/std140.rs
|
||||
expression: "<<TestVec3 as AsStd140>::Std140Type as TypeLayout>::layout()"
|
||||
---
|
||||
name: Std140TestVec3
|
||||
size: 28
|
||||
alignment: 4
|
||||
fields:
|
||||
- Field:
|
||||
name: _pos_align
|
||||
ty: "[u8 ; Std140TestVec3Alignment :: _pos_align()]"
|
||||
size: 0
|
||||
- Field:
|
||||
name: pos
|
||||
ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 12
|
||||
- Field:
|
||||
name: _velocity_align
|
||||
ty: "[u8 ; Std140TestVec3Alignment :: _velocity_align()]"
|
||||
size: 4
|
||||
- Field:
|
||||
name: velocity
|
||||
ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type"
|
||||
size: 12
|
|
@ -1,121 +0,0 @@
|
|||
use insta::assert_yaml_snapshot;
|
||||
use type_layout::TypeLayout;
|
||||
|
||||
use crevice::std140::{AsStd140, DVec4, Std140, Vec3};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct PrimitiveF32 {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primitive_f32() {
|
||||
assert_yaml_snapshot!(<<PrimitiveF32 as AsStd140>::Std140Type as TypeLayout>::type_layout());
|
||||
|
||||
assert_eq!(<PrimitiveF32 as AsStd140>::Std140Type::ALIGNMENT, 16);
|
||||
|
||||
let value = PrimitiveF32 { x: 1.0, y: 2.0 };
|
||||
let _value_std140 = value.as_std140();
|
||||
}
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct TestVec3 {
|
||||
pos: Vec3,
|
||||
velocity: Vec3,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec3() {
|
||||
assert_yaml_snapshot!(<<TestVec3 as AsStd140>::Std140Type as TypeLayout>::type_layout());
|
||||
|
||||
assert_eq!(<TestVec3 as AsStd140>::Std140Type::ALIGNMENT, 16);
|
||||
|
||||
let value = TestVec3 {
|
||||
pos: Vec3 {
|
||||
x: 1.0,
|
||||
y: 2.0,
|
||||
z: 3.0,
|
||||
},
|
||||
velocity: Vec3 {
|
||||
x: 4.0,
|
||||
y: 5.0,
|
||||
z: 6.0,
|
||||
},
|
||||
};
|
||||
let _value_std140 = value.as_std140();
|
||||
}
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct UsingVec3Padding {
|
||||
pos: Vec3,
|
||||
brightness: f32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn using_vec3_padding() {
|
||||
assert_yaml_snapshot!(
|
||||
<<UsingVec3Padding as AsStd140>::Std140Type as TypeLayout>::type_layout()
|
||||
);
|
||||
|
||||
assert_eq!(<UsingVec3Padding as AsStd140>::Std140Type::ALIGNMENT, 16);
|
||||
|
||||
let value = UsingVec3Padding {
|
||||
pos: Vec3 {
|
||||
x: 1.0,
|
||||
y: 2.0,
|
||||
z: 3.0,
|
||||
},
|
||||
brightness: 4.0,
|
||||
};
|
||||
let _value_std140 = value.as_std140();
|
||||
}
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct PointLight {
|
||||
position: Vec3,
|
||||
diffuse: Vec3,
|
||||
specular: Vec3,
|
||||
brightness: f32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_light() {
|
||||
assert_yaml_snapshot!(<<PointLight as AsStd140>::Std140Type as TypeLayout>::type_layout());
|
||||
|
||||
assert_eq!(<PointLight as AsStd140>::Std140Type::ALIGNMENT, 16);
|
||||
|
||||
let value = PointLight {
|
||||
position: Vec3 {
|
||||
x: 1.0,
|
||||
y: 2.0,
|
||||
z: 3.0,
|
||||
},
|
||||
diffuse: Vec3 {
|
||||
x: 1.0,
|
||||
y: 2.0,
|
||||
z: 3.0,
|
||||
},
|
||||
specular: Vec3 {
|
||||
x: 1.0,
|
||||
y: 2.0,
|
||||
z: 3.0,
|
||||
},
|
||||
brightness: 4.0,
|
||||
};
|
||||
let _value_std140 = value.as_std140();
|
||||
}
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct MoreThan16Alignment {
|
||||
doubles: DVec4,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_than_16_alignment() {
|
||||
assert_yaml_snapshot!(
|
||||
<<MoreThan16Alignment as AsStd140>::Std140Type as TypeLayout>::type_layout()
|
||||
);
|
||||
|
||||
assert_eq!(<MoreThan16Alignment as AsStd140>::Std140Type::ALIGNMENT, 32);
|
||||
}
|
95
examples/3d/3d_scene_pipelined.rs
Normal file
95
examples/3d/3d_scene_pipelined.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use bevy::{
|
||||
core::Time,
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
ecs::prelude::*,
|
||||
input::Input,
|
||||
math::Vec3,
|
||||
pbr2::{PbrBundle, PointLightBundle, StandardMaterial},
|
||||
prelude::{App, Assets, KeyCode, Transform},
|
||||
render2::{
|
||||
camera::PerspectiveCameraBundle,
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
},
|
||||
wgpu2::diagnostic::WgpuResourceDiagnosticsPlugin,
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.add_plugin(LogDiagnosticsPlugin::default())
|
||||
.add_plugin(WgpuResourceDiagnosticsPlugin::default())
|
||||
.add_startup_system(setup.system())
|
||||
.add_system(movement.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
struct Movable;
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// plane
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
|
||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
||||
..Default::default()
|
||||
});
|
||||
// cube
|
||||
commands
|
||||
.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(Movable);
|
||||
// sphere
|
||||
commands
|
||||
.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::UVSphere { radius: 0.5, ..Default::default() })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
transform: Transform::from_xyz(1.5, 1.0, 1.5),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(Movable);
|
||||
// light
|
||||
commands.spawn_bundle(PointLightBundle {
|
||||
transform: Transform::from_xyz(5.0, 8.0, 2.0),
|
||||
..Default::default()
|
||||
});
|
||||
// camera
|
||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn movement(
|
||||
input: Res<Input<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
mut query: Query<&mut Transform, With<Movable>>,
|
||||
) {
|
||||
for mut transform in query.iter_mut() {
|
||||
let mut direction = Vec3::ZERO;
|
||||
if input.pressed(KeyCode::Up) {
|
||||
direction.y += 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::Down) {
|
||||
direction.y -= 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::Left) {
|
||||
direction.x -= 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::Right) {
|
||||
direction.x += 1.0;
|
||||
}
|
||||
|
||||
transform.translation += time.delta_seconds() * 2.0 * direction;
|
||||
}
|
||||
}
|
|
@ -224,7 +224,7 @@ fn setup(
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let mut app = App::build();
|
||||
let mut app = App::new();
|
||||
app.add_plugins(DefaultPlugins)
|
||||
.add_startup_system(setup.system())
|
||||
.add_system(cube_rotator_system.system())
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
|
|||
/// This example shows how to use the ECS and the AsyncComputeTaskPool
|
||||
/// to spawn, poll, and complete tasks across systems and system ticks.
|
||||
fn main() {
|
||||
App::build()
|
||||
App::new()
|
||||
.insert_resource(Msaa { samples: 4 })
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_startup_system(setup_env.system())
|
||||
|
|
|
@ -7,7 +7,7 @@ struct FixedUpdateStage;
|
|||
const DELTA_TIME: f64 = 0.01;
|
||||
|
||||
fn main() {
|
||||
App::build()
|
||||
App::new()
|
||||
.insert_resource(Msaa { samples: 4 })
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_startup_system(generate_bodies.system())
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use bevy::{log::LogPlugin, prelude::*};
|
||||
|
||||
fn main() {
|
||||
App::build()
|
||||
App::new()
|
||||
.add_plugin(LogPlugin)
|
||||
.add_startup_system(setup.system())
|
||||
.add_system(log_names.system().label(LogNamesSystem))
|
||||
|
|
|
@ -48,7 +48,7 @@ pub enum PhysicsSystem {
|
|||
/// Lastly a system with run criterion _done_ is used to exit the app.
|
||||
/// ```
|
||||
fn main() {
|
||||
App::build()
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.init_resource::<Done>()
|
||||
// Note that the system sets added in this example set their run criteria explicitly.
|
||||
|
|
30
pipelined/bevy_pbr2/Cargo.toml
Normal file
30
pipelined/bevy_pbr2/Cargo.toml
Normal file
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "bevy_pbr2"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
authors = [
|
||||
"Bevy Contributors <bevyengine@gmail.com>",
|
||||
"Carter Anderson <mcanders1@gmail.com>",
|
||||
]
|
||||
description = "Adds PBR rendering to Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT"
|
||||
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_math = { path = "../../crates/bevy_math", version = "0.5.0" }
|
||||
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
||||
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }
|
||||
bevy_transform = { path = "../../crates/bevy_transform", version = "0.5.0" }
|
||||
bevy_utils = { path = "../../crates/bevy_utils", version = "0.5.0" }
|
||||
|
||||
# other
|
||||
# direct dependency required for derive macro
|
||||
bytemuck = { version = "1", features = ["derive"] }
|
||||
crevice = { path = "../../crates/crevice" }
|
32
pipelined/bevy_pbr2/src/bundle.rs
Normal file
32
pipelined/bevy_pbr2/src/bundle.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use crate::{PointLight, StandardMaterial};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_render2::mesh::Mesh;
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
#[derive(Bundle, Clone)]
|
||||
pub struct PbrBundle {
|
||||
pub mesh: Handle<Mesh>,
|
||||
pub material: Handle<StandardMaterial>,
|
||||
pub transform: Transform,
|
||||
pub global_transform: GlobalTransform,
|
||||
}
|
||||
|
||||
impl Default for PbrBundle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mesh: Default::default(),
|
||||
material: Default::default(),
|
||||
transform: Default::default(),
|
||||
global_transform: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A component bundle for "light" entities
|
||||
#[derive(Debug, Bundle, Default)]
|
||||
pub struct PointLightBundle {
|
||||
pub point_light: PointLight,
|
||||
pub transform: Transform,
|
||||
pub global_transform: GlobalTransform,
|
||||
}
|
88
pipelined/bevy_pbr2/src/lib.rs
Normal file
88
pipelined/bevy_pbr2/src/lib.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
mod bundle;
|
||||
mod light;
|
||||
mod material;
|
||||
mod render;
|
||||
|
||||
pub use bundle::*;
|
||||
pub use light::*;
|
||||
pub use material::*;
|
||||
pub use render::*;
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::AddAsset;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_render2::{
|
||||
core_pipeline,
|
||||
render_graph::RenderGraph,
|
||||
render_phase::{sort_phase_system, DrawFunctions},
|
||||
RenderStage,
|
||||
};
|
||||
|
||||
pub mod draw_3d_graph {
|
||||
pub mod node {
|
||||
pub const SHADOW_PASS: &'static str = "shadow_pass";
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PbrPlugin;
|
||||
|
||||
impl Plugin for PbrPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_asset::<StandardMaterial>();
|
||||
|
||||
let render_app = app.sub_app_mut(0);
|
||||
render_app
|
||||
.add_system_to_stage(RenderStage::Extract, render::extract_meshes.system())
|
||||
.add_system_to_stage(RenderStage::Extract, render::extract_lights.system())
|
||||
.add_system_to_stage(RenderStage::Prepare, render::prepare_meshes.system())
|
||||
.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)
|
||||
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
|
||||
render::prepare_lights.exclusive_system(),
|
||||
)
|
||||
.add_system_to_stage(RenderStage::Queue, render::queue_meshes.system())
|
||||
.add_system_to_stage(
|
||||
RenderStage::PhaseSort,
|
||||
sort_phase_system::<ShadowPhase>.system(),
|
||||
)
|
||||
.add_system_to_stage(RenderStage::Cleanup, render::cleanup_view_lights.system())
|
||||
.init_resource::<PbrShaders>()
|
||||
.init_resource::<ShadowShaders>()
|
||||
.init_resource::<MeshMeta>()
|
||||
.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);
|
||||
let render_world = render_app.world.cell();
|
||||
let draw_functions = render_world.get_resource::<DrawFunctions>().unwrap();
|
||||
draw_functions.write().add(draw_pbr);
|
||||
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", core_pipeline::node::MAIN_PASS_DEPENDENCIES)
|
||||
.unwrap();
|
||||
|
||||
let draw_3d_graph = graph
|
||||
.get_sub_graph_mut(core_pipeline::draw_3d_graph::NAME)
|
||||
.unwrap();
|
||||
draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node);
|
||||
draw_3d_graph
|
||||
.add_node_edge(
|
||||
draw_3d_graph::node::SHADOW_PASS,
|
||||
core_pipeline::draw_3d_graph::node::MAIN_PASS,
|
||||
)
|
||||
.unwrap();
|
||||
draw_3d_graph
|
||||
.add_slot_edge(
|
||||
draw_3d_graph.input_node().unwrap().id,
|
||||
core_pipeline::draw_3d_graph::input::VIEW_ENTITY,
|
||||
draw_3d_graph::node::SHADOW_PASS,
|
||||
ShadowPassNode::IN_VIEW,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
24
pipelined/bevy_pbr2/src/light.rs
Normal file
24
pipelined/bevy_pbr2/src/light.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use bevy_ecs::reflect::ReflectComponent;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render2::color::Color;
|
||||
|
||||
/// A point light
|
||||
#[derive(Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct PointLight {
|
||||
pub color: Color,
|
||||
pub intensity: f32,
|
||||
pub range: f32,
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
impl Default for PointLight {
|
||||
fn default() -> Self {
|
||||
PointLight {
|
||||
color: Color::rgb(1.0, 1.0, 1.0),
|
||||
intensity: 200.0,
|
||||
range: 20.0,
|
||||
radius: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
17
pipelined/bevy_pbr2/src/material.rs
Normal file
17
pipelined/bevy_pbr2/src/material.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use bevy_reflect::{Reflect, TypeUuid};
|
||||
use bevy_render2::color::Color;
|
||||
|
||||
#[derive(Debug, Default, Clone, TypeUuid, Reflect)]
|
||||
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
|
||||
pub struct StandardMaterial {
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
impl From<Color> for StandardMaterial {
|
||||
fn from(color: Color) -> Self {
|
||||
StandardMaterial {
|
||||
color,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
424
pipelined/bevy_pbr2/src/render/light.rs
Normal file
424
pipelined/bevy_pbr2/src/render/light.rs
Normal file
|
@ -0,0 +1,424 @@
|
|||
use crate::{render::MeshViewBindGroups, ExtractedMeshes, PointLight};
|
||||
use bevy_ecs::{prelude::*, system::SystemState};
|
||||
use bevy_math::{Mat4, Vec3, Vec4};
|
||||
use bevy_render2::{
|
||||
color::Color,
|
||||
core_pipeline::Transparent3dPhase,
|
||||
pass::*,
|
||||
pipeline::*,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
|
||||
render_resource::{DynamicUniformVec, SamplerId, TextureId, TextureViewId},
|
||||
renderer::{RenderContext, RenderResources},
|
||||
shader::{Shader, ShaderStage, ShaderStages},
|
||||
texture::*,
|
||||
view::{ExtractedView, ViewUniform},
|
||||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use crevice::std140::AsStd140;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
pub struct ExtractedPointLight {
|
||||
color: Color,
|
||||
intensity: f32,
|
||||
range: f32,
|
||||
radius: f32,
|
||||
transform: GlobalTransform,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, AsStd140, Default, Debug)]
|
||||
pub struct GpuLight {
|
||||
color: Vec4,
|
||||
range: f32,
|
||||
radius: f32,
|
||||
position: Vec3,
|
||||
view_proj: Mat4,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, AsStd140)]
|
||||
pub struct GpuLights {
|
||||
len: u32,
|
||||
lights: [GpuLight; MAX_POINT_LIGHTS],
|
||||
}
|
||||
|
||||
// NOTE: this must be kept in sync MAX_POINT_LIGHTS in pbr.frag
|
||||
pub const MAX_POINT_LIGHTS: usize = 10;
|
||||
pub const SHADOW_SIZE: Extent3d = Extent3d {
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
depth_or_array_layers: MAX_POINT_LIGHTS as u32,
|
||||
};
|
||||
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
|
||||
|
||||
pub struct ShadowShaders {
|
||||
pub pipeline: PipelineId,
|
||||
pub pipeline_descriptor: RenderPipelineDescriptor,
|
||||
pub light_sampler: SamplerId,
|
||||
}
|
||||
|
||||
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
|
||||
impl FromWorld for ShadowShaders {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_resources = world.get_resource::<RenderResources>().unwrap();
|
||||
let vertex_shader = Shader::from_glsl(ShaderStage::Vertex, include_str!("pbr.vert"))
|
||||
.get_spirv_shader(None)
|
||||
.unwrap();
|
||||
let vertex_layout = vertex_shader.reflect_layout(true).unwrap();
|
||||
|
||||
let mut pipeline_layout = PipelineLayout::from_shader_layouts(&mut [vertex_layout]);
|
||||
|
||||
let vertex = render_resources.create_shader_module(&vertex_shader);
|
||||
|
||||
pipeline_layout.vertex_buffer_descriptors = vec![VertexBufferLayout {
|
||||
stride: 32,
|
||||
name: "Vertex".into(),
|
||||
step_mode: InputStepMode::Vertex,
|
||||
attributes: vec![
|
||||
// GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)
|
||||
VertexAttribute {
|
||||
name: "Vertex_Position".into(),
|
||||
format: VertexFormat::Float32x3,
|
||||
offset: 12,
|
||||
shader_location: 0,
|
||||
},
|
||||
VertexAttribute {
|
||||
name: "Vertex_Normals".into(),
|
||||
format: VertexFormat::Float32x3,
|
||||
offset: 0,
|
||||
shader_location: 1,
|
||||
},
|
||||
VertexAttribute {
|
||||
name: "Vertex_Uv".into(),
|
||||
format: VertexFormat::Float32x2,
|
||||
offset: 24,
|
||||
shader_location: 2,
|
||||
},
|
||||
],
|
||||
}];
|
||||
|
||||
pipeline_layout.bind_group_mut(0).bindings[0].set_dynamic(true);
|
||||
pipeline_layout.bind_group_mut(1).bindings[0].set_dynamic(true);
|
||||
pipeline_layout.update_bind_group_ids();
|
||||
|
||||
let pipeline_descriptor = RenderPipelineDescriptor {
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: SHADOW_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::LessEqual,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 2,
|
||||
slope_scale: 2.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
primitive: PrimitiveState {
|
||||
topology: PrimitiveTopology::TriangleList,
|
||||
cull_mode: Some(Face::Back),
|
||||
// TODO: detect if this feature is enabled
|
||||
clamp_depth: false,
|
||||
..Default::default()
|
||||
},
|
||||
color_target_states: vec![],
|
||||
..RenderPipelineDescriptor::new(
|
||||
ShaderStages {
|
||||
vertex,
|
||||
fragment: None,
|
||||
},
|
||||
pipeline_layout,
|
||||
)
|
||||
};
|
||||
|
||||
let pipeline = render_resources.create_render_pipeline(&pipeline_descriptor);
|
||||
|
||||
ShadowShaders {
|
||||
pipeline,
|
||||
pipeline_descriptor,
|
||||
light_sampler: render_resources.create_sampler(&SamplerDescriptor {
|
||||
address_mode_u: AddressMode::ClampToEdge,
|
||||
address_mode_v: AddressMode::ClampToEdge,
|
||||
address_mode_w: AddressMode::ClampToEdge,
|
||||
mag_filter: FilterMode::Linear,
|
||||
min_filter: FilterMode::Linear,
|
||||
mipmap_filter: FilterMode::Nearest,
|
||||
compare_function: Some(CompareFunction::LessEqual),
|
||||
..Default::default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ultimately these could be filtered down to lights relevant to actual views
|
||||
pub fn extract_lights(
|
||||
mut commands: Commands,
|
||||
lights: Query<(Entity, &PointLight, &GlobalTransform)>,
|
||||
) {
|
||||
for (entity, light, transform) in lights.iter() {
|
||||
commands.get_or_spawn(entity).insert(ExtractedPointLight {
|
||||
color: light.color,
|
||||
intensity: light.intensity,
|
||||
range: light.range,
|
||||
radius: light.radius,
|
||||
transform: transform.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewLight {
|
||||
pub depth_texture: TextureViewId,
|
||||
}
|
||||
|
||||
pub struct ViewLights {
|
||||
pub light_depth_texture: TextureId,
|
||||
pub light_depth_texture_view: TextureViewId,
|
||||
pub lights: Vec<Entity>,
|
||||
pub gpu_light_binding_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LightMeta {
|
||||
pub view_gpu_lights: DynamicUniformVec<GpuLights>,
|
||||
}
|
||||
|
||||
pub fn prepare_lights(
|
||||
mut commands: Commands,
|
||||
mut texture_cache: ResMut<TextureCache>,
|
||||
render_resources: Res<RenderResources>,
|
||||
mut light_meta: ResMut<LightMeta>,
|
||||
views: Query<Entity, With<RenderPhase<Transparent3dPhase>>>,
|
||||
lights: Query<&ExtractedPointLight>,
|
||||
) {
|
||||
// PERF: view.iter().count() could be views.iter().len() if we implemented ExactSizeIterator for archetype-only filters
|
||||
light_meta
|
||||
.view_gpu_lights
|
||||
.reserve_and_clear(views.iter().count(), &render_resources);
|
||||
|
||||
// set up light data for each view
|
||||
for entity in views.iter() {
|
||||
let light_depth_texture = texture_cache.get(
|
||||
&render_resources,
|
||||
TextureDescriptor {
|
||||
size: SHADOW_SIZE,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: SHADOW_FORMAT,
|
||||
usage: TextureUsage::RENDER_ATTACHMENT | TextureUsage::SAMPLED,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let mut view_lights = Vec::new();
|
||||
|
||||
let mut gpu_lights = GpuLights {
|
||||
len: lights.iter().len() as u32,
|
||||
lights: [GpuLight::default(); MAX_POINT_LIGHTS],
|
||||
};
|
||||
|
||||
// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
|
||||
for (i, light) in lights.iter().enumerate().take(MAX_POINT_LIGHTS) {
|
||||
let depth_texture_view = render_resources.create_texture_view(
|
||||
light_depth_texture.texture,
|
||||
TextureViewDescriptor {
|
||||
format: None,
|
||||
dimension: Some(TextureViewDimension::D2),
|
||||
aspect: TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
level_count: None,
|
||||
base_array_layer: i as u32,
|
||||
array_layer_count: NonZeroU32::new(1),
|
||||
},
|
||||
);
|
||||
|
||||
let view_transform = GlobalTransform::from_translation(light.transform.translation)
|
||||
.looking_at(Vec3::default(), Vec3::Y);
|
||||
// TODO: configure light projection based on light configuration
|
||||
let projection = Mat4::perspective_rh(1.0472, 1.0, 1.0, 20.0);
|
||||
|
||||
gpu_lights.lights[i] = GpuLight {
|
||||
// premultiply color by intensity
|
||||
// we don't use the alpha at all, so no reason to multiply only [0..3]
|
||||
color: (light.color * light.intensity).into(),
|
||||
radius: light.radius.into(),
|
||||
position: light.transform.translation.into(),
|
||||
range: 1.0 / (light.range * light.range),
|
||||
// this could technically be copied to the gpu from the light's ViewUniforms
|
||||
view_proj: projection * view_transform.compute_matrix().inverse(),
|
||||
};
|
||||
|
||||
let view_light_entity = commands
|
||||
.spawn()
|
||||
.insert_bundle((
|
||||
ViewLight {
|
||||
depth_texture: depth_texture_view,
|
||||
},
|
||||
ExtractedView {
|
||||
width: SHADOW_SIZE.width,
|
||||
height: SHADOW_SIZE.height,
|
||||
transform: view_transform.clone(),
|
||||
projection,
|
||||
},
|
||||
RenderPhase::<ShadowPhase>::default(),
|
||||
))
|
||||
.id();
|
||||
view_lights.push(view_light_entity);
|
||||
}
|
||||
|
||||
commands.entity(entity).insert(ViewLights {
|
||||
light_depth_texture: light_depth_texture.texture,
|
||||
light_depth_texture_view: light_depth_texture.default_view,
|
||||
lights: view_lights,
|
||||
gpu_light_binding_index: light_meta.view_gpu_lights.push(gpu_lights),
|
||||
});
|
||||
}
|
||||
|
||||
light_meta
|
||||
.view_gpu_lights
|
||||
.write_to_staging_buffer(&render_resources);
|
||||
}
|
||||
|
||||
// TODO: we can remove this once we move to RAII
|
||||
pub fn cleanup_view_lights(render_resources: Res<RenderResources>, query: Query<&ViewLight>) {
|
||||
for view_light in query.iter() {
|
||||
render_resources.remove_texture_view(view_light.depth_texture);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShadowPhase;
|
||||
|
||||
pub struct ShadowPassNode {
|
||||
main_view_query: QueryState<&'static ViewLights>,
|
||||
view_light_query: QueryState<(&'static ViewLight, &'static RenderPhase<ShadowPhase>)>,
|
||||
}
|
||||
|
||||
impl ShadowPassNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
main_view_query: QueryState::new(world),
|
||||
view_light_query: QueryState::new(world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for ShadowPassNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(ShadowPassNode::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.main_view_query.update_archetypes(world);
|
||||
self.view_light_query.update_archetypes(world);
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut dyn RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_lights = self.main_view_query.get_manual(world, view_entity).unwrap();
|
||||
for view_light_entity in view_lights.lights.iter().copied() {
|
||||
let (view_light, shadow_phase) = self
|
||||
.view_light_query
|
||||
.get_manual(world, view_light_entity)
|
||||
.unwrap();
|
||||
let pass_descriptor = PassDescriptor {
|
||||
color_attachments: Vec::new(),
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||
attachment: TextureAttachment::Id(view_light.depth_texture),
|
||||
depth_ops: Some(Operations {
|
||||
load: LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
sample_count: 1,
|
||||
};
|
||||
|
||||
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
|
||||
|
||||
render_context.begin_render_pass(
|
||||
&pass_descriptor,
|
||||
&mut |render_pass: &mut dyn RenderPass| {
|
||||
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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type DrawShadowMeshParams<'a> = (
|
||||
Res<'a, ShadowShaders>,
|
||||
Res<'a, ExtractedMeshes>,
|
||||
Query<'a, (&'a ViewUniform, &'a MeshViewBindGroups)>,
|
||||
);
|
||||
pub struct DrawShadowMesh {
|
||||
params: SystemState<DrawShadowMeshParams<'static>>,
|
||||
}
|
||||
|
||||
impl DrawShadowMesh {
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
params: SystemState::new(world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw for DrawShadowMesh {
|
||||
fn draw(
|
||||
&mut self,
|
||||
world: &World,
|
||||
pass: &mut TrackedRenderPass,
|
||||
view: Entity,
|
||||
draw_key: usize,
|
||||
_sort_key: usize,
|
||||
) {
|
||||
let (shadow_shaders, extracted_meshes, views) = self.params.get(world);
|
||||
let (view_uniforms, mesh_view_bind_groups) = views.get(view).unwrap();
|
||||
let layout = &shadow_shaders.pipeline_descriptor.layout;
|
||||
let extracted_mesh = &extracted_meshes.meshes[draw_key];
|
||||
pass.set_pipeline(shadow_shaders.pipeline);
|
||||
pass.set_bind_group(
|
||||
0,
|
||||
layout.bind_group(0).id,
|
||||
mesh_view_bind_groups.view_bind_group,
|
||||
Some(&[view_uniforms.view_uniform_offset]),
|
||||
);
|
||||
|
||||
pass.set_bind_group(
|
||||
1,
|
||||
layout.bind_group(1).id,
|
||||
mesh_view_bind_groups.mesh_transform_bind_group,
|
||||
Some(&[extracted_mesh.transform_binding_offset]),
|
||||
);
|
||||
pass.set_vertex_buffer(0, extracted_mesh.vertex_buffer, 0);
|
||||
if let Some(index_info) = &extracted_mesh.index_info {
|
||||
pass.set_index_buffer(index_info.buffer, 0, IndexFormat::Uint32);
|
||||
pass.draw_indexed(0..index_info.count, 0, 0..1);
|
||||
} else {
|
||||
panic!("non-indexed drawing not supported yet")
|
||||
}
|
||||
}
|
||||
}
|
367
pipelined/bevy_pbr2/src/render/mod.rs
Normal file
367
pipelined/bevy_pbr2/src/render/mod.rs
Normal file
|
@ -0,0 +1,367 @@
|
|||
mod light;
|
||||
pub use light::*;
|
||||
|
||||
use crate::StandardMaterial;
|
||||
use bevy_asset::{Assets, Handle};
|
||||
use bevy_ecs::{prelude::*, system::SystemState};
|
||||
use bevy_math::Mat4;
|
||||
use bevy_render2::{
|
||||
core_pipeline::Transparent3dPhase,
|
||||
mesh::Mesh,
|
||||
pipeline::*,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass},
|
||||
render_resource::{BindGroupBuilder, BindGroupId, BufferId, DynamicUniformVec},
|
||||
renderer::{RenderContext, RenderResources},
|
||||
shader::{Shader, ShaderStage, ShaderStages},
|
||||
texture::{TextureFormat, TextureSampleType},
|
||||
view::{ViewMeta, ViewUniform},
|
||||
};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
|
||||
pub struct PbrShaders {
|
||||
pipeline: PipelineId,
|
||||
pipeline_descriptor: RenderPipelineDescriptor,
|
||||
}
|
||||
|
||||
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
|
||||
impl FromWorld for PbrShaders {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_resources = world.get_resource::<RenderResources>().unwrap();
|
||||
let vertex_shader = Shader::from_glsl(ShaderStage::Vertex, include_str!("pbr.vert"))
|
||||
.get_spirv_shader(None)
|
||||
.unwrap();
|
||||
let fragment_shader = Shader::from_glsl(ShaderStage::Fragment, include_str!("pbr.frag"))
|
||||
.get_spirv_shader(None)
|
||||
.unwrap();
|
||||
|
||||
let vertex_layout = vertex_shader.reflect_layout(true).unwrap();
|
||||
let fragment_layout = fragment_shader.reflect_layout(true).unwrap();
|
||||
|
||||
let mut pipeline_layout =
|
||||
PipelineLayout::from_shader_layouts(&mut [vertex_layout, fragment_layout]);
|
||||
|
||||
let vertex = render_resources.create_shader_module(&vertex_shader);
|
||||
let fragment = render_resources.create_shader_module(&fragment_shader);
|
||||
|
||||
pipeline_layout.vertex_buffer_descriptors = vec![VertexBufferLayout {
|
||||
stride: 32,
|
||||
name: "Vertex".into(),
|
||||
step_mode: InputStepMode::Vertex,
|
||||
attributes: vec![
|
||||
// GOTCHA! Vertex_Position isn't first in the buffer due to how Mesh sorts attributes (alphabetically)
|
||||
VertexAttribute {
|
||||
name: "Vertex_Position".into(),
|
||||
format: VertexFormat::Float32x3,
|
||||
offset: 12,
|
||||
shader_location: 0,
|
||||
},
|
||||
VertexAttribute {
|
||||
name: "Vertex_Normals".into(),
|
||||
format: VertexFormat::Float32x3,
|
||||
offset: 0,
|
||||
shader_location: 1,
|
||||
},
|
||||
VertexAttribute {
|
||||
name: "Vertex_Uv".into(),
|
||||
format: VertexFormat::Float32x2,
|
||||
offset: 24,
|
||||
shader_location: 2,
|
||||
},
|
||||
],
|
||||
}];
|
||||
|
||||
pipeline_layout.bind_group_mut(0).bindings[0].set_dynamic(true);
|
||||
pipeline_layout.bind_group_mut(0).bindings[1].set_dynamic(true);
|
||||
if let BindType::Texture { sample_type, .. } =
|
||||
&mut pipeline_layout.bind_group_mut(0).bindings[2].bind_type
|
||||
{
|
||||
*sample_type = TextureSampleType::Depth;
|
||||
}
|
||||
if let BindType::Sampler { comparison, .. } =
|
||||
&mut pipeline_layout.bind_group_mut(0).bindings[3].bind_type
|
||||
{
|
||||
*comparison = true;
|
||||
}
|
||||
pipeline_layout.bind_group_mut(1).bindings[0].set_dynamic(true);
|
||||
|
||||
pipeline_layout.update_bind_group_ids();
|
||||
|
||||
let pipeline_descriptor = RenderPipelineDescriptor {
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::Less,
|
||||
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,
|
||||
},
|
||||
}),
|
||||
color_target_states: vec![ColorTargetState {
|
||||
format: TextureFormat::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,
|
||||
}],
|
||||
..RenderPipelineDescriptor::new(
|
||||
ShaderStages {
|
||||
vertex,
|
||||
fragment: Some(fragment),
|
||||
},
|
||||
pipeline_layout,
|
||||
)
|
||||
};
|
||||
|
||||
let pipeline = render_resources.create_render_pipeline(&pipeline_descriptor);
|
||||
|
||||
PbrShaders {
|
||||
pipeline,
|
||||
pipeline_descriptor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtractedMesh {
|
||||
transform: Mat4,
|
||||
vertex_buffer: BufferId,
|
||||
index_info: Option<IndexInfo>,
|
||||
transform_binding_offset: u32,
|
||||
}
|
||||
|
||||
struct IndexInfo {
|
||||
buffer: BufferId,
|
||||
count: u32,
|
||||
}
|
||||
|
||||
pub struct ExtractedMeshes {
|
||||
meshes: Vec<ExtractedMesh>,
|
||||
}
|
||||
|
||||
pub fn extract_meshes(
|
||||
mut commands: Commands,
|
||||
meshes: Res<Assets<Mesh>>,
|
||||
_materials: Res<Assets<StandardMaterial>>,
|
||||
query: Query<(&GlobalTransform, &Handle<Mesh>, &Handle<StandardMaterial>)>,
|
||||
) {
|
||||
let mut extracted_meshes = Vec::new();
|
||||
for (transform, mesh_handle, _material_handle) in query.iter() {
|
||||
if let Some(mesh) = meshes.get(mesh_handle) {
|
||||
if let Some(gpu_data) = &mesh.gpu_data() {
|
||||
extracted_meshes.push(ExtractedMesh {
|
||||
transform: transform.compute_matrix(),
|
||||
vertex_buffer: gpu_data.vertex_buffer,
|
||||
index_info: gpu_data.index_buffer.map(|i| IndexInfo {
|
||||
buffer: i,
|
||||
count: mesh.indices().unwrap().len() as u32,
|
||||
}),
|
||||
transform_binding_offset: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands.insert_resource(ExtractedMeshes {
|
||||
meshes: extracted_meshes,
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MeshMeta {
|
||||
transform_uniforms: DynamicUniformVec<Mat4>,
|
||||
}
|
||||
|
||||
pub fn prepare_meshes(
|
||||
render_resources: Res<RenderResources>,
|
||||
mut mesh_meta: ResMut<MeshMeta>,
|
||||
mut extracted_meshes: ResMut<ExtractedMeshes>,
|
||||
) {
|
||||
mesh_meta
|
||||
.transform_uniforms
|
||||
.reserve_and_clear(extracted_meshes.meshes.len(), &render_resources);
|
||||
for extracted_mesh in extracted_meshes.meshes.iter_mut() {
|
||||
extracted_mesh.transform_binding_offset =
|
||||
mesh_meta.transform_uniforms.push(extracted_mesh.transform);
|
||||
}
|
||||
|
||||
mesh_meta
|
||||
.transform_uniforms
|
||||
.write_to_staging_buffer(&render_resources);
|
||||
}
|
||||
|
||||
// TODO: This is temporary. Once we expose BindGroupLayouts directly, we can create view bind groups without specific shader context
|
||||
struct MeshViewBindGroups {
|
||||
view_bind_group: BindGroupId,
|
||||
mesh_transform_bind_group: BindGroupId,
|
||||
}
|
||||
|
||||
pub fn queue_meshes(
|
||||
mut commands: Commands,
|
||||
draw_functions: Res<DrawFunctions>,
|
||||
render_resources: Res<RenderResources>,
|
||||
pbr_shaders: Res<PbrShaders>,
|
||||
shadow_shaders: Res<ShadowShaders>,
|
||||
mesh_meta: Res<MeshMeta>,
|
||||
light_meta: Res<LightMeta>,
|
||||
view_meta: Res<ViewMeta>,
|
||||
extracted_meshes: Res<ExtractedMeshes>,
|
||||
mut views: Query<(Entity, &ViewLights, &mut RenderPhase<Transparent3dPhase>)>,
|
||||
mut view_light_shadow_phases: Query<&mut RenderPhase<ShadowPhase>>,
|
||||
) {
|
||||
if extracted_meshes.meshes.is_empty() {
|
||||
return;
|
||||
}
|
||||
for (entity, view_lights, mut transparent_phase) in views.iter_mut() {
|
||||
let layout = &pbr_shaders.pipeline_descriptor.layout;
|
||||
let view_bind_group = BindGroupBuilder::default()
|
||||
.add_binding(0, view_meta.uniforms.binding())
|
||||
.add_binding(1, light_meta.view_gpu_lights.binding())
|
||||
.add_binding(2, view_lights.light_depth_texture_view)
|
||||
.add_binding(3, shadow_shaders.light_sampler)
|
||||
.finish();
|
||||
|
||||
// TODO: this will only create the bind group if it isn't already created. this is a bit nasty
|
||||
render_resources.create_bind_group(layout.bind_group(0).id, &view_bind_group);
|
||||
|
||||
let mesh_transform_bind_group = BindGroupBuilder::default()
|
||||
.add_binding(0, mesh_meta.transform_uniforms.binding())
|
||||
.finish();
|
||||
render_resources.create_bind_group(layout.bind_group(1).id, &mesh_transform_bind_group);
|
||||
|
||||
commands.entity(entity).insert(MeshViewBindGroups {
|
||||
view_bind_group: view_bind_group.id,
|
||||
mesh_transform_bind_group: mesh_transform_bind_group.id,
|
||||
});
|
||||
|
||||
let draw_pbr = draw_functions.read().get_id::<DrawPbr>().unwrap();
|
||||
for i in 0..extracted_meshes.meshes.len() {
|
||||
// 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: 0, // TODO: sort back-to-front
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
let layout = &shadow_shaders.pipeline_descriptor.layout;
|
||||
let shadow_view_bind_group = BindGroupBuilder::default()
|
||||
.add_binding(0, view_meta.uniforms.binding())
|
||||
.finish();
|
||||
|
||||
render_resources.create_bind_group(layout.bind_group(0).id, &shadow_view_bind_group);
|
||||
// TODO: this should only queue up meshes that are actually visible by each "light view"
|
||||
for i in 0..extracted_meshes.meshes.len() {
|
||||
shadow_phase.add(Drawable {
|
||||
draw_function: draw_shadow_mesh,
|
||||
draw_key: i,
|
||||
sort_key: 0, // TODO: sort back-to-front
|
||||
})
|
||||
}
|
||||
|
||||
commands
|
||||
.entity(view_light_entity)
|
||||
.insert(MeshViewBindGroups {
|
||||
view_bind_group: shadow_view_bind_group.id,
|
||||
mesh_transform_bind_group: mesh_transform_bind_group.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 dyn 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(render_context);
|
||||
light_meta
|
||||
.view_gpu_lights
|
||||
.write_to_uniform_buffer(render_context);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type DrawPbrParams<'a> = (
|
||||
Res<'a, PbrShaders>,
|
||||
Res<'a, ExtractedMeshes>,
|
||||
Query<'a, (&'a ViewUniform, &'a MeshViewBindGroups, &'a ViewLights)>,
|
||||
);
|
||||
pub struct DrawPbr {
|
||||
params: SystemState<DrawPbrParams<'static>>,
|
||||
}
|
||||
|
||||
impl DrawPbr {
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
params: SystemState::new(world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Draw for DrawPbr {
|
||||
fn draw(
|
||||
&mut self,
|
||||
world: &World,
|
||||
pass: &mut TrackedRenderPass,
|
||||
view: Entity,
|
||||
draw_key: usize,
|
||||
_sort_key: usize,
|
||||
) {
|
||||
let (pbr_shaders, extracted_meshes, views) = self.params.get(world);
|
||||
let (view_uniforms, mesh_view_bind_groups, view_lights) = views.get(view).unwrap();
|
||||
let layout = &pbr_shaders.pipeline_descriptor.layout;
|
||||
let extracted_mesh = &extracted_meshes.meshes[draw_key];
|
||||
pass.set_pipeline(pbr_shaders.pipeline);
|
||||
pass.set_bind_group(
|
||||
0,
|
||||
layout.bind_group(0).id,
|
||||
mesh_view_bind_groups.view_bind_group,
|
||||
Some(&[
|
||||
view_uniforms.view_uniform_offset,
|
||||
view_lights.gpu_light_binding_index,
|
||||
]),
|
||||
);
|
||||
pass.set_bind_group(
|
||||
1,
|
||||
layout.bind_group(1).id,
|
||||
mesh_view_bind_groups.mesh_transform_bind_group,
|
||||
Some(&[extracted_mesh.transform_binding_offset]),
|
||||
);
|
||||
pass.set_vertex_buffer(0, extracted_mesh.vertex_buffer, 0);
|
||||
if let Some(index_info) = &extracted_mesh.index_info {
|
||||
pass.set_index_buffer(index_info.buffer, 0, IndexFormat::Uint32);
|
||||
pass.draw_indexed(0..index_info.count, 0, 0..1);
|
||||
} else {
|
||||
panic!("non-indexed drawing not supported yet")
|
||||
}
|
||||
}
|
||||
}
|
298
pipelined/bevy_pbr2/src/render/pbr.frag
Normal file
298
pipelined/bevy_pbr2/src/render/pbr.frag
Normal file
|
@ -0,0 +1,298 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec4 v_WorldPosition;
|
||||
layout(location = 1) in vec3 v_WorldNormal;
|
||||
layout(location = 2) in vec2 v_Uv;
|
||||
|
||||
layout(location = 0) out vec4 o_Target;
|
||||
|
||||
struct PointLight {
|
||||
vec4 color;
|
||||
float range;
|
||||
float radius;
|
||||
vec3 position;
|
||||
mat4 projection;
|
||||
};
|
||||
|
||||
// NOTE: this must be kept in sync with lights::MAX_LIGHTS
|
||||
// TODO: this can be removed if we move to storage buffers for light arrays
|
||||
const int MAX_POINT_LIGHTS = 10;
|
||||
|
||||
layout(set = 0, binding = 0) uniform View {
|
||||
mat4 ViewProj;
|
||||
vec3 ViewWorldPosition;
|
||||
};
|
||||
layout(std140, set = 0, binding = 1) uniform Lights {
|
||||
uint NumLights;
|
||||
PointLight PointLights[MAX_POINT_LIGHTS];
|
||||
};
|
||||
layout(set = 0, binding = 2) uniform texture2DArray t_Shadow;
|
||||
layout(set = 0, binding = 3) uniform samplerShadow s_Shadow;
|
||||
|
||||
# define saturate(x) clamp(x, 0.0, 1.0)
|
||||
const float PI = 3.141592653589793;
|
||||
|
||||
float pow5(float x) {
|
||||
float x2 = x * x;
|
||||
return x2 * x2 * x;
|
||||
}
|
||||
|
||||
// distanceAttenuation is simply the square falloff of light intensity
|
||||
// combined with a smooth attenuation at the edge of the light radius
|
||||
//
|
||||
// light radius is a non-physical construct for efficiency purposes,
|
||||
// because otherwise every light affects every fragment in the scene
|
||||
float getDistanceAttenuation(float distanceSquare, float inverseRangeSquared) {
|
||||
float factor = distanceSquare * inverseRangeSquared;
|
||||
float smoothFactor = saturate(1.0 - factor * factor);
|
||||
float attenuation = smoothFactor * smoothFactor;
|
||||
return attenuation * 1.0 / max(distanceSquare, 1e-4);
|
||||
}
|
||||
|
||||
// Normal distribution function (specular D)
|
||||
// Based on https://google.github.io/filament/Filament.html#citation-walter07
|
||||
|
||||
// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 }
|
||||
|
||||
// Simple implementation, has precision problems when using fp16 instead of fp32
|
||||
// see https://google.github.io/filament/Filament.html#listing_speculardfp16
|
||||
float D_GGX(float roughness, float NoH, const vec3 h) {
|
||||
float oneMinusNoHSquared = 1.0 - NoH * NoH;
|
||||
float a = NoH * roughness;
|
||||
float k = roughness / (oneMinusNoHSquared + a * a);
|
||||
float d = k * k * (1.0 / PI);
|
||||
return d;
|
||||
}
|
||||
|
||||
// Visibility function (Specular G)
|
||||
// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) }
|
||||
// such that f_r becomes
|
||||
// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0)
|
||||
// where
|
||||
// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) }
|
||||
// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv
|
||||
float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
|
||||
float a2 = roughness * roughness;
|
||||
float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
|
||||
float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
|
||||
float v = 0.5 / (lambdaV + lambdaL);
|
||||
return v;
|
||||
}
|
||||
|
||||
// Fresnel function
|
||||
// see https://google.github.io/filament/Filament.html#citation-schlick94
|
||||
// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5
|
||||
vec3 F_Schlick(const vec3 f0, float f90, float VoH) {
|
||||
// not using mix to keep the vec3 and float versions identical
|
||||
return f0 + (f90 - f0) * pow5(1.0 - VoH);
|
||||
}
|
||||
|
||||
float F_Schlick(float f0, float f90, float VoH) {
|
||||
// not using mix to keep the vec3 and float versions identical
|
||||
return f0 + (f90 - f0) * pow5(1.0 - VoH);
|
||||
}
|
||||
|
||||
vec3 fresnel(vec3 f0, float LoH) {
|
||||
// f_90 suitable for ambient occlusion
|
||||
// see https://google.github.io/filament/Filament.html#lighting/occlusion
|
||||
float f90 = saturate(dot(f0, vec3(50.0 * 0.33)));
|
||||
return F_Schlick(f0, f90, LoH);
|
||||
}
|
||||
|
||||
// Specular BRDF
|
||||
// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf
|
||||
|
||||
// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m
|
||||
// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) }
|
||||
vec3 specular(vec3 f0, float roughness, const vec3 h, float NoV, float NoL,
|
||||
float NoH, float LoH, float specularIntensity) {
|
||||
float D = D_GGX(roughness, NoH, h);
|
||||
float V = V_SmithGGXCorrelated(roughness, NoV, NoL);
|
||||
vec3 F = fresnel(f0, LoH);
|
||||
|
||||
return (specularIntensity * D * V) * F;
|
||||
}
|
||||
|
||||
// Diffuse BRDF
|
||||
// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf
|
||||
// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm
|
||||
|
||||
// simplest approximation
|
||||
// float Fd_Lambert() {
|
||||
// return 1.0 / PI;
|
||||
// }
|
||||
//
|
||||
// vec3 Fd = diffuseColor * Fd_Lambert();
|
||||
|
||||
// Disney approximation
|
||||
// See https://google.github.io/filament/Filament.html#citation-burley12
|
||||
// minimal quality difference
|
||||
float Fd_Burley(float roughness, float NoV, float NoL, float LoH) {
|
||||
float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
|
||||
float lightScatter = F_Schlick(1.0, f90, NoL);
|
||||
float viewScatter = F_Schlick(1.0, f90, NoV);
|
||||
return lightScatter * viewScatter * (1.0 / PI);
|
||||
}
|
||||
|
||||
// From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
|
||||
vec3 EnvBRDFApprox(vec3 f0, float perceptual_roughness, float NoV) {
|
||||
const vec4 c0 = { -1, -0.0275, -0.572, 0.022 };
|
||||
const vec4 c1 = { 1, 0.0425, 1.04, -0.04 };
|
||||
vec4 r = perceptual_roughness * c0 + c1;
|
||||
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
|
||||
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
|
||||
return f0 * AB.x + AB.y;
|
||||
}
|
||||
|
||||
float perceptualRoughnessToRoughness(float perceptualRoughness) {
|
||||
// clamp perceptual roughness to prevent precision problems
|
||||
// According to Filament design 0.089 is recommended for mobile
|
||||
// Filament uses 0.045 for non-mobile
|
||||
float clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0);
|
||||
return clampedPerceptualRoughness * clampedPerceptualRoughness;
|
||||
}
|
||||
|
||||
// from https://64.github.io/tonemapping/
|
||||
// reinhard on RGB oversaturates colors
|
||||
vec3 reinhard(vec3 color) {
|
||||
return color / (1.0 + color);
|
||||
}
|
||||
|
||||
vec3 reinhard_extended(vec3 color, float max_white) {
|
||||
vec3 numerator = color * (1.0f + (color / vec3(max_white * max_white)));
|
||||
return numerator / (1.0 + color);
|
||||
}
|
||||
|
||||
// luminance coefficients from Rec. 709.
|
||||
// https://en.wikipedia.org/wiki/Rec._709
|
||||
float luminance(vec3 v) {
|
||||
return dot(v, vec3(0.2126, 0.7152, 0.0722));
|
||||
}
|
||||
|
||||
vec3 change_luminance(vec3 c_in, float l_out) {
|
||||
float l_in = luminance(c_in);
|
||||
return c_in * (l_out / l_in);
|
||||
}
|
||||
|
||||
vec3 reinhard_luminance(vec3 color) {
|
||||
float l_old = luminance(color);
|
||||
float l_new = l_old / (1.0f + l_old);
|
||||
return change_luminance(color, l_new);
|
||||
}
|
||||
|
||||
vec3 reinhard_extended_luminance(vec3 color, float max_white_l) {
|
||||
float l_old = luminance(color);
|
||||
float numerator = l_old * (1.0f + (l_old / (max_white_l * max_white_l)));
|
||||
float l_new = numerator / (1.0f + l_old);
|
||||
return change_luminance(color, l_new);
|
||||
}
|
||||
|
||||
vec3 point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) {
|
||||
vec3 light_to_frag = light.position.xyz - v_WorldPosition.xyz;
|
||||
float distance_square = dot(light_to_frag, light_to_frag);
|
||||
float rangeAttenuation =
|
||||
getDistanceAttenuation(distance_square, light.range);
|
||||
|
||||
// Specular.
|
||||
// Representative Point Area Lights.
|
||||
// see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16
|
||||
float a = roughness;
|
||||
vec3 centerToRay = dot(light_to_frag, R) * R - light_to_frag;
|
||||
vec3 closestPoint = light_to_frag + centerToRay * saturate(light.radius * inversesqrt(dot(centerToRay, centerToRay)));
|
||||
float LspecLengthInverse = inversesqrt(dot(closestPoint, closestPoint));
|
||||
float normalizationFactor = a / saturate(a + (light.radius * 0.5 * LspecLengthInverse));
|
||||
float specularIntensity = normalizationFactor * normalizationFactor;
|
||||
|
||||
vec3 L = closestPoint * LspecLengthInverse; // normalize() equivalent?
|
||||
vec3 H = normalize(L + V);
|
||||
float NoL = saturate(dot(N, L));
|
||||
float NoH = saturate(dot(N, H));
|
||||
float LoH = saturate(dot(L, H));
|
||||
|
||||
vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity);
|
||||
|
||||
// Diffuse.
|
||||
// Comes after specular since its NoL is used in the lighting equation.
|
||||
L = normalize(light_to_frag);
|
||||
H = normalize(L + V);
|
||||
NoL = saturate(dot(N, L));
|
||||
NoH = saturate(dot(N, H));
|
||||
LoH = saturate(dot(L, H));
|
||||
|
||||
vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH);
|
||||
|
||||
// Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩
|
||||
// where
|
||||
// f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color
|
||||
// Φ is light intensity
|
||||
|
||||
// our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius
|
||||
// It's not 100% clear where the 1/4π goes in the derivation, but we follow the filament shader and leave it out
|
||||
|
||||
// See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation
|
||||
// TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance
|
||||
// light.color.rgb is premultiplied with light.intensity on the CPU
|
||||
return ((diffuse + specular) * light.color.rgb) * (rangeAttenuation * NoL);
|
||||
}
|
||||
|
||||
float fetch_shadow(int light_id, vec4 homogeneous_coords) {
|
||||
if (homogeneous_coords.w <= 0.0) {
|
||||
return 1.0;
|
||||
}
|
||||
// compensate for the Y-flip difference between the NDC and texture coordinates
|
||||
const vec2 flip_correction = vec2(0.5, -0.5);
|
||||
// compute texture coordinates for shadow lookup
|
||||
vec4 light_local = vec4(
|
||||
homogeneous_coords.xy * flip_correction/homogeneous_coords.w + 0.5,
|
||||
light_id,
|
||||
homogeneous_coords.z / homogeneous_coords.w
|
||||
);
|
||||
// do the lookup, using HW PCF and comparison
|
||||
return texture(sampler2DArrayShadow(t_Shadow, s_Shadow), light_local);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 color = vec4(0.6, 0.6, 0.6, 1.0);
|
||||
float metallic = 0.01;
|
||||
float reflectance = 0.5;
|
||||
float perceptual_roughness = 0.089;
|
||||
vec3 emissive = vec3(0.0, 0.0, 0.0);
|
||||
vec3 ambient_color = vec3(0.1, 0.1, 0.1);
|
||||
float occlusion = 1.0;
|
||||
|
||||
float roughness = perceptualRoughnessToRoughness(perceptual_roughness);
|
||||
vec3 N = normalize(v_WorldNormal);
|
||||
vec3 V = normalize(ViewWorldPosition.xyz - v_WorldPosition.xyz);
|
||||
vec3 R = reflect(-V, N);
|
||||
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
|
||||
float NdotV = max(dot(N, V), 1e-4);
|
||||
|
||||
// Remapping [0,1] reflectance to F0
|
||||
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
|
||||
vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + color.rgb * metallic;
|
||||
|
||||
// Diffuse strength inversely related to metallicity
|
||||
vec3 diffuse_color = color.rgb * (1.0 - metallic);
|
||||
|
||||
vec3 output_color = vec3(0.0);
|
||||
for (int i = 0; i < int(NumLights); ++i) {
|
||||
PointLight light = PointLights[i];
|
||||
vec3 light_contrib = point_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
|
||||
float shadow = fetch_shadow(i, light.projection * v_WorldPosition);
|
||||
output_color += light_contrib * shadow;
|
||||
}
|
||||
|
||||
vec3 diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV);
|
||||
vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV);
|
||||
|
||||
output_color += (diffuse_ambient + specular_ambient) * ambient_color * occlusion;
|
||||
output_color += emissive * color.a;
|
||||
|
||||
// tone_mapping
|
||||
output_color = reinhard_luminance(output_color);
|
||||
// Gamma correction.
|
||||
// Not needed with sRGB buffer
|
||||
// output_color = pow(output_color, vec3(1.0 / 2.2));
|
||||
|
||||
o_Target = vec4(output_color, 1.0);
|
||||
}
|
25
pipelined/bevy_pbr2/src/render/pbr.vert
Normal file
25
pipelined/bevy_pbr2/src/render/pbr.vert
Normal file
|
@ -0,0 +1,25 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 Vertex_Position;
|
||||
layout(location = 1) in vec3 Vertex_Normal;
|
||||
layout(location = 2) in vec2 Vertex_Uv;
|
||||
|
||||
layout(location = 0) out vec4 v_WorldPosition;
|
||||
layout(location = 1) out vec3 v_WorldNormal;
|
||||
layout(location = 2) out vec2 v_Uv;
|
||||
|
||||
layout(set = 0, binding = 0) uniform View {
|
||||
mat4 ViewProj;
|
||||
vec3 ViewWorldPosition;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 0) uniform MeshTransform {
|
||||
mat4 Model;
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_Uv = Vertex_Uv;
|
||||
v_WorldPosition = Model * vec4(Vertex_Position, 1.0);
|
||||
v_WorldNormal = mat3(Model) * Vertex_Normal;
|
||||
gl_Position = ViewProj * v_WorldPosition;
|
||||
}
|
|
@ -39,7 +39,6 @@ anyhow = "1.0"
|
|||
hex = "0.4.2"
|
||||
hexasphere = "3.4"
|
||||
parking_lot = "0.11.0"
|
||||
raw-window-handle = "0.3.0"
|
||||
crevice = { path = "../../crates/crevice" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
|
6
pipelined/bevy_render2/changes.md
Normal file
6
pipelined/bevy_render2/changes.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
* SubGraphs: Graphs can own other named graphs
|
||||
* Graph Inputs: Graphs can now have "inputs". These are represented as a single input node, so inputs can be connected to other node slots using the existing apis.
|
||||
* RenderGraph is now a static specification. No run state is stored
|
||||
* Graph Nodes impls can only read their internal state when "running". This ensures that they can be used multiple times in parallel. State should be stored in World.
|
||||
* RenderGraphContext now handles graph inputs and outputs
|
||||
* Removed RenderGraphStager, RenderGraphExecutor, stager impls, and WgpuRenderGraphExecutor. It is now 100% the render backend's job to decide how to run the RenderGraph ... bevy_render doesn't provide any tools for this.
|
|
@ -1,6 +1,6 @@
|
|||
use crate::camera::{
|
||||
Camera, DepthCalculation, OrthographicProjection, PerspectiveProjection, ScalingMode,
|
||||
CAMERA_2D, CAMERA_3D,
|
||||
Camera, CameraPlugin, DepthCalculation, OrthographicProjection, PerspectiveProjection,
|
||||
ScalingMode,
|
||||
};
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
@ -38,7 +38,7 @@ impl Default for PerspectiveCameraBundle {
|
|||
fn default() -> Self {
|
||||
PerspectiveCameraBundle {
|
||||
camera: Camera {
|
||||
name: Some(CAMERA_3D.to_string()),
|
||||
name: Some(CameraPlugin::CAMERA_3D.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
perspective_projection: Default::default(),
|
||||
|
@ -66,7 +66,7 @@ impl OrthographicCameraBundle {
|
|||
let far = 1000.0;
|
||||
OrthographicCameraBundle {
|
||||
camera: Camera {
|
||||
name: Some(CAMERA_2D.to_string()),
|
||||
name: Some(CameraPlugin::CAMERA_2D.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
orthographic_projection: OrthographicProjection {
|
||||
|
@ -82,7 +82,7 @@ impl OrthographicCameraBundle {
|
|||
pub fn new_3d() -> Self {
|
||||
OrthographicCameraBundle {
|
||||
camera: Camera {
|
||||
name: Some(CAMERA_3D.to_string()),
|
||||
name: Some(CameraPlugin::CAMERA_3D.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
orthographic_projection: OrthographicProjection {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use super::CameraProjection;
|
||||
use crate::camera::CameraProjection;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::EventReader,
|
||||
prelude::DetectChanges,
|
||||
query::Added,
|
||||
reflect::ReflectComponent,
|
||||
system::{Query, QuerySet, Res},
|
||||
|
|
|
@ -7,31 +7,28 @@ mod projection;
|
|||
pub use active_cameras::*;
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_utils::HashMap;
|
||||
use bevy_window::{WindowId, Windows};
|
||||
pub use bundle::*;
|
||||
pub use camera::*;
|
||||
pub use projection::*;
|
||||
|
||||
use crate::{
|
||||
render_graph::{Node, RenderGraph, ResourceSlots},
|
||||
render_resource::{DynamicUniformVec, RenderResourceBinding},
|
||||
renderer::{RenderContext, RenderResources},
|
||||
RenderStage,
|
||||
};
|
||||
use crate::{view::ExtractedView, RenderStage};
|
||||
use bevy_app::{App, CoreStage, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::Mat4;
|
||||
|
||||
pub const CAMERA_2D: &str = "camera_2d";
|
||||
pub const CAMERA_3D: &str = "camera_3d";
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CameraPlugin;
|
||||
|
||||
impl CameraPlugin {
|
||||
pub const CAMERA_2D: &'static str = "camera_2d";
|
||||
pub const CAMERA_3D: &'static str = "camera_3d";
|
||||
}
|
||||
|
||||
impl Plugin for CameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let mut active_cameras = ActiveCameras::default();
|
||||
active_cameras.add(CAMERA_2D);
|
||||
active_cameras.add(CAMERA_3D);
|
||||
active_cameras.add(Self::CAMERA_2D);
|
||||
active_cameras.add(Self::CAMERA_3D);
|
||||
app.register_type::<Camera>()
|
||||
.insert_resource(active_cameras)
|
||||
.add_system_to_stage(
|
||||
|
@ -41,91 +38,56 @@ impl Plugin for CameraPlugin {
|
|||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
crate::camera::camera_system::<OrthographicProjection>.system(),
|
||||
)
|
||||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
crate::camera::camera_system::<PerspectiveProjection>.system(),
|
||||
);
|
||||
let render_app = app.sub_app_mut(0);
|
||||
render_app
|
||||
.init_resource::<Cameras>()
|
||||
.add_system_to_stage(RenderStage::Extract, extract_cameras.system())
|
||||
.add_system_to_stage(RenderStage::Prepare, prepare_cameras.system());
|
||||
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
|
||||
graph.add_node("camera", CameraNode);
|
||||
.init_resource::<ExtractedCameraNames>()
|
||||
.add_system_to_stage(RenderStage::Extract, extract_cameras.system());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Cameras {
|
||||
pub view_proj_uniforms: DynamicUniformVec<Mat4>,
|
||||
pub struct ExtractedCameraNames {
|
||||
pub entities: HashMap<String, Entity>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExtractedCamera {
|
||||
pub projection: Mat4,
|
||||
pub transform: Mat4,
|
||||
pub window_id: WindowId,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
pub struct CameraUniforms {
|
||||
pub view_proj: RenderResourceBinding,
|
||||
}
|
||||
|
||||
fn extract_cameras(
|
||||
mut commands: Commands,
|
||||
active_cameras: Res<ActiveCameras>,
|
||||
query: Query<(&Camera, &GlobalTransform)>,
|
||||
windows: Res<Windows>,
|
||||
query: Query<(Entity, &Camera, &GlobalTransform)>,
|
||||
) {
|
||||
let mut entities = HashMap::default();
|
||||
for camera in active_cameras.iter() {
|
||||
if let Some((camera, transform)) = camera.entity.and_then(|e| query.get(e).ok()) {
|
||||
// TODO: remove "spawn_and_forget" hack in favor of more intelligent multiple world handling
|
||||
commands.spawn_and_forget((ExtractedCamera {
|
||||
projection: camera.projection_matrix,
|
||||
transform: transform.compute_matrix(),
|
||||
let name = &camera.name;
|
||||
if let Some((entity, camera, transform)) = camera.entity.and_then(|e| query.get(e).ok()) {
|
||||
entities.insert(name.clone(), entity);
|
||||
if let Some(window) = windows.get(camera.window) {
|
||||
commands.get_or_spawn(entity).insert_bundle((
|
||||
ExtractedCamera {
|
||||
window_id: camera.window,
|
||||
name: camera.name.clone(),
|
||||
},));
|
||||
},
|
||||
ExtractedView {
|
||||
projection: camera.projection_matrix,
|
||||
transform: transform.clone(),
|
||||
width: window.physical_width(),
|
||||
height: window.physical_height(),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_cameras(
|
||||
mut commands: Commands,
|
||||
render_resources: Res<RenderResources>,
|
||||
mut cameras: ResMut<Cameras>,
|
||||
mut extracted_cameras: Query<(Entity, &ExtractedCamera)>,
|
||||
) {
|
||||
cameras.entities.clear();
|
||||
cameras
|
||||
.view_proj_uniforms
|
||||
.reserve_and_clear(extracted_cameras.iter_mut().len(), &render_resources);
|
||||
for (entity, camera) in extracted_cameras.iter() {
|
||||
let camera_uniforms = CameraUniforms {
|
||||
view_proj: cameras
|
||||
.view_proj_uniforms
|
||||
.push(camera.projection * camera.transform.inverse()),
|
||||
};
|
||||
commands.entity(entity).insert(camera_uniforms);
|
||||
if let Some(name) = camera.name.as_ref() {
|
||||
cameras.entities.insert(name.to_string(), entity);
|
||||
}
|
||||
}
|
||||
|
||||
cameras
|
||||
.view_proj_uniforms
|
||||
.write_to_staging_buffer(&render_resources);
|
||||
}
|
||||
|
||||
pub struct CameraNode;
|
||||
|
||||
impl Node for CameraNode {
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceSlots,
|
||||
_output: &mut ResourceSlots,
|
||||
) {
|
||||
let camera_uniforms = world.get_resource::<Cameras>().unwrap();
|
||||
camera_uniforms
|
||||
.view_proj_uniforms
|
||||
.write_to_uniform_buffer(render_context);
|
||||
}
|
||||
commands.insert_resource(ExtractedCameraNames { entities })
|
||||
}
|
||||
|
|
89
pipelined/bevy_render2/src/core_pipeline/main_pass_2d.rs
Normal file
89
pipelined/bevy_render2/src/core_pipeline/main_pass_2d.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use crate::{
|
||||
color::Color,
|
||||
core_pipeline::Transparent2dPhase,
|
||||
pass::{
|
||||
LoadOp, Operations, PassDescriptor, RenderPass, RenderPassColorAttachment,
|
||||
TextureAttachment,
|
||||
},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
|
||||
renderer::RenderContext,
|
||||
view::ExtractedView,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
pub struct MainPass2dNode {
|
||||
query: QueryState<&'static RenderPhase<Transparent2dPhase>, With<ExtractedView>>,
|
||||
}
|
||||
|
||||
impl MainPass2dNode {
|
||||
pub const IN_COLOR_ATTACHMENT: &'static str = "color_attachment";
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: QueryState::new(world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for MainPass2dNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![
|
||||
SlotInfo::new(MainPass2dNode::IN_COLOR_ATTACHMENT, SlotType::TextureView),
|
||||
SlotInfo::new(MainPass2dNode::IN_VIEW, SlotType::Entity),
|
||||
]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut dyn RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let color_attachment_texture = graph.get_input_texture(Self::IN_COLOR_ATTACHMENT)?;
|
||||
let pass_descriptor = PassDescriptor {
|
||||
color_attachments: vec![RenderPassColorAttachment {
|
||||
attachment: TextureAttachment::Id(color_attachment_texture),
|
||||
resolve_target: None,
|
||||
ops: Operations {
|
||||
load: LoadOp::Clear(Color::rgb(0.4, 0.4, 0.4)),
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
sample_count: 1,
|
||||
};
|
||||
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
|
||||
|
||||
let transparent_phase = self
|
||||
.query
|
||||
.get_manual(world, view_entity)
|
||||
.expect("view entity should exist");
|
||||
|
||||
render_context.begin_render_pass(
|
||||
&pass_descriptor,
|
||||
&mut |render_pass: &mut dyn RenderPass| {
|
||||
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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
99
pipelined/bevy_render2/src/core_pipeline/main_pass_3d.rs
Normal file
99
pipelined/bevy_render2/src/core_pipeline/main_pass_3d.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use crate::{
|
||||
color::Color,
|
||||
core_pipeline::Transparent3dPhase,
|
||||
pass::{
|
||||
LoadOp, Operations, PassDescriptor, RenderPass, RenderPassColorAttachment,
|
||||
RenderPassDepthStencilAttachment, TextureAttachment,
|
||||
},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
|
||||
renderer::RenderContext,
|
||||
view::ExtractedView,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
pub struct MainPass3dNode {
|
||||
query: QueryState<&'static RenderPhase<Transparent3dPhase>, With<ExtractedView>>,
|
||||
}
|
||||
|
||||
impl MainPass3dNode {
|
||||
pub const IN_COLOR_ATTACHMENT: &'static str = "color_attachment";
|
||||
pub const IN_DEPTH: &'static str = "depth";
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: QueryState::new(world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for MainPass3dNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![
|
||||
SlotInfo::new(MainPass3dNode::IN_COLOR_ATTACHMENT, SlotType::TextureView),
|
||||
SlotInfo::new(MainPass3dNode::IN_DEPTH, SlotType::TextureView),
|
||||
SlotInfo::new(MainPass3dNode::IN_VIEW, SlotType::Entity),
|
||||
]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut dyn RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let color_attachment_texture = graph.get_input_texture(Self::IN_COLOR_ATTACHMENT)?;
|
||||
let depth_texture = graph.get_input_texture(Self::IN_DEPTH)?;
|
||||
let pass_descriptor = PassDescriptor {
|
||||
color_attachments: vec![RenderPassColorAttachment {
|
||||
attachment: TextureAttachment::Id(color_attachment_texture),
|
||||
resolve_target: None,
|
||||
ops: Operations {
|
||||
load: LoadOp::Clear(Color::rgb(0.4, 0.4, 0.4)),
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||
attachment: TextureAttachment::Id(depth_texture),
|
||||
depth_ops: Some(Operations {
|
||||
load: LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
sample_count: 1,
|
||||
};
|
||||
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
|
||||
|
||||
let transparent_phase = self
|
||||
.query
|
||||
.get_manual(world, view_entity)
|
||||
.expect("view entity should exist");
|
||||
|
||||
render_context.begin_render_pass(
|
||||
&pass_descriptor,
|
||||
&mut |render_pass: &mut dyn RenderPass| {
|
||||
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,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
52
pipelined/bevy_render2/src/core_pipeline/main_pass_driver.rs
Normal file
52
pipelined/bevy_render2/src/core_pipeline/main_pass_driver.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use crate::{
|
||||
camera::{CameraPlugin, ExtractedCamera, ExtractedCameraNames},
|
||||
core_pipeline::{self, ViewDepthTexture},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
|
||||
renderer::RenderContext,
|
||||
view::ExtractedWindows,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
|
||||
pub struct MainPassDriverNode;
|
||||
|
||||
impl Node for MainPassDriverNode {
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
_render_context: &mut dyn RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let extracted_cameras = world.get_resource::<ExtractedCameraNames>().unwrap();
|
||||
let extracted_windows = world.get_resource::<ExtractedWindows>().unwrap();
|
||||
|
||||
if let Some(camera_2d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_2D) {
|
||||
let extracted_camera = world.entity(*camera_2d).get::<ExtractedCamera>().unwrap();
|
||||
let extracted_window = extracted_windows.get(&extracted_camera.window_id).unwrap();
|
||||
let swap_chain_texture = extracted_window.swap_chain_texture.unwrap();
|
||||
graph.run_sub_graph(
|
||||
core_pipeline::draw_2d_graph::NAME,
|
||||
vec![
|
||||
SlotValue::Entity(*camera_2d),
|
||||
SlotValue::TextureView(swap_chain_texture),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(camera_3d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_3D) {
|
||||
let extracted_camera = world.entity(*camera_3d).get::<ExtractedCamera>().unwrap();
|
||||
let depth_texture = world.entity(*camera_3d).get::<ViewDepthTexture>().unwrap();
|
||||
let extracted_window = extracted_windows.get(&extracted_camera.window_id).unwrap();
|
||||
let swap_chain_texture = extracted_window.swap_chain_texture.unwrap();
|
||||
graph.run_sub_graph(
|
||||
core_pipeline::draw_3d_graph::NAME,
|
||||
vec![
|
||||
SlotValue::Entity(*camera_3d),
|
||||
SlotValue::TextureView(swap_chain_texture),
|
||||
SlotValue::TextureView(depth_texture.view),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
214
pipelined/bevy_render2/src/core_pipeline/mod.rs
Normal file
214
pipelined/bevy_render2/src/core_pipeline/mod.rs
Normal file
|
@ -0,0 +1,214 @@
|
|||
mod main_pass_2d;
|
||||
mod main_pass_3d;
|
||||
mod main_pass_driver;
|
||||
|
||||
pub use main_pass_2d::*;
|
||||
pub use main_pass_3d::*;
|
||||
pub use main_pass_driver::*;
|
||||
|
||||
use crate::{
|
||||
camera::{ActiveCameras, CameraPlugin},
|
||||
render_command::RenderCommandPlugin,
|
||||
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
|
||||
render_phase::{sort_phase_system, RenderPhase},
|
||||
render_resource::{TextureId, TextureViewId},
|
||||
renderer::RenderResources,
|
||||
texture::{
|
||||
Extent3d, TextureCache, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage,
|
||||
},
|
||||
view::{ExtractedView, ViewPlugin},
|
||||
RenderStage,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
// Plugins that contribute to the RenderGraph should use the following label conventions:
|
||||
// 1. Graph modules should have a NAME, input module, and node module (where relevant)
|
||||
// 2. The "top level" graph is the plugin module root. Just add things like `pub mod node` directly under the plugin module
|
||||
// 3. "sub graph" modules should be nested beneath their parent graph module
|
||||
|
||||
pub mod node {
|
||||
pub const MAIN_PASS_DEPENDENCIES: &'static str = "main_pass_dependencies";
|
||||
pub const MAIN_PASS_DRIVER: &'static str = "main_pass_driver";
|
||||
pub const VIEW: &'static str = "view";
|
||||
}
|
||||
|
||||
pub mod draw_2d_graph {
|
||||
pub const NAME: &'static str = "draw_2d";
|
||||
pub mod input {
|
||||
pub const VIEW_ENTITY: &'static str = "view_entity";
|
||||
pub const RENDER_TARGET: &'static str = "render_target";
|
||||
}
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &'static str = "main_pass";
|
||||
}
|
||||
}
|
||||
|
||||
pub mod draw_3d_graph {
|
||||
pub const NAME: &'static str = "draw_3d";
|
||||
pub mod input {
|
||||
pub const VIEW_ENTITY: &'static str = "view_entity";
|
||||
pub const RENDER_TARGET: &'static str = "render_target";
|
||||
pub const DEPTH: &'static str = "depth";
|
||||
}
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &'static str = "main_pass";
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CorePipelinePlugin;
|
||||
|
||||
impl Plugin for CorePipelinePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let render_app = app.sub_app_mut(0);
|
||||
render_app
|
||||
.add_system_to_stage(
|
||||
RenderStage::Extract,
|
||||
extract_core_pipeline_camera_phases.system(),
|
||||
)
|
||||
.add_system_to_stage(RenderStage::Prepare, prepare_core_views_system.system())
|
||||
.add_system_to_stage(
|
||||
RenderStage::PhaseSort,
|
||||
sort_phase_system::<Transparent2dPhase>.system(),
|
||||
)
|
||||
.add_system_to_stage(
|
||||
RenderStage::PhaseSort,
|
||||
sort_phase_system::<Transparent3dPhase>.system(),
|
||||
);
|
||||
|
||||
let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
|
||||
let pass_node_3d = MainPass3dNode::new(&mut render_app.world);
|
||||
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
|
||||
|
||||
let mut draw_2d_graph = RenderGraph::default();
|
||||
draw_2d_graph.add_node(draw_2d_graph::node::MAIN_PASS, pass_node_2d);
|
||||
let input_node_id = draw_2d_graph.set_input(vec![
|
||||
SlotInfo::new(draw_2d_graph::input::VIEW_ENTITY, SlotType::Entity),
|
||||
SlotInfo::new(draw_2d_graph::input::RENDER_TARGET, SlotType::TextureView),
|
||||
]);
|
||||
draw_2d_graph
|
||||
.add_slot_edge(
|
||||
input_node_id,
|
||||
draw_2d_graph::input::VIEW_ENTITY,
|
||||
draw_2d_graph::node::MAIN_PASS,
|
||||
MainPass2dNode::IN_VIEW,
|
||||
)
|
||||
.unwrap();
|
||||
draw_2d_graph
|
||||
.add_slot_edge(
|
||||
input_node_id,
|
||||
draw_2d_graph::input::RENDER_TARGET,
|
||||
draw_2d_graph::node::MAIN_PASS,
|
||||
MainPass2dNode::IN_COLOR_ATTACHMENT,
|
||||
)
|
||||
.unwrap();
|
||||
graph.add_sub_graph(draw_2d_graph::NAME, draw_2d_graph);
|
||||
|
||||
let mut draw_3d_graph = RenderGraph::default();
|
||||
draw_3d_graph.add_node(draw_3d_graph::node::MAIN_PASS, pass_node_3d);
|
||||
let input_node_id = draw_3d_graph.set_input(vec![
|
||||
SlotInfo::new(draw_3d_graph::input::VIEW_ENTITY, SlotType::Entity),
|
||||
SlotInfo::new(draw_3d_graph::input::RENDER_TARGET, SlotType::TextureView),
|
||||
SlotInfo::new(draw_3d_graph::input::DEPTH, SlotType::TextureView),
|
||||
]);
|
||||
draw_3d_graph
|
||||
.add_slot_edge(
|
||||
input_node_id,
|
||||
draw_3d_graph::input::VIEW_ENTITY,
|
||||
draw_3d_graph::node::MAIN_PASS,
|
||||
MainPass3dNode::IN_VIEW,
|
||||
)
|
||||
.unwrap();
|
||||
draw_3d_graph
|
||||
.add_slot_edge(
|
||||
input_node_id,
|
||||
draw_3d_graph::input::RENDER_TARGET,
|
||||
draw_3d_graph::node::MAIN_PASS,
|
||||
MainPass3dNode::IN_COLOR_ATTACHMENT,
|
||||
)
|
||||
.unwrap();
|
||||
draw_3d_graph
|
||||
.add_slot_edge(
|
||||
input_node_id,
|
||||
draw_3d_graph::input::DEPTH,
|
||||
draw_3d_graph::node::MAIN_PASS,
|
||||
MainPass3dNode::IN_DEPTH,
|
||||
)
|
||||
.unwrap();
|
||||
graph.add_sub_graph(draw_3d_graph::NAME, draw_3d_graph);
|
||||
|
||||
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(
|
||||
RenderCommandPlugin::RENDER_COMMAND_QUEUE_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 ViewDepthTexture {
|
||||
pub texture: TextureId,
|
||||
pub view: TextureViewId,
|
||||
}
|
||||
|
||||
pub fn extract_core_pipeline_camera_phases(
|
||||
mut commands: Commands,
|
||||
active_cameras: Res<ActiveCameras>,
|
||||
) {
|
||||
if let Some(camera_2d) = active_cameras.get(CameraPlugin::CAMERA_2D) {
|
||||
if let Some(entity) = camera_2d.entity {
|
||||
commands
|
||||
.get_or_spawn(entity)
|
||||
.insert(RenderPhase::<Transparent2dPhase>::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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_core_views_system(
|
||||
mut commands: Commands,
|
||||
mut texture_cache: ResMut<TextureCache>,
|
||||
render_resources: Res<RenderResources>,
|
||||
views: Query<(Entity, &ExtractedView), With<RenderPhase<Transparent3dPhase>>>,
|
||||
) {
|
||||
for (entity, view) in views.iter() {
|
||||
let cached_texture = texture_cache.get(
|
||||
&render_resources,
|
||||
TextureDescriptor {
|
||||
size: Extent3d {
|
||||
depth_or_array_layers: 1,
|
||||
width: view.width as u32,
|
||||
height: view.height as u32,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24
|
||||
* bit depth for better performance */
|
||||
usage: TextureUsage::RENDER_ATTACHMENT,
|
||||
},
|
||||
);
|
||||
commands.entity(entity).insert(ViewDepthTexture {
|
||||
texture: cached_texture.texture,
|
||||
view: cached_texture.default_view,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,21 +1,29 @@
|
|||
pub mod camera;
|
||||
pub mod color;
|
||||
pub mod main_pass;
|
||||
pub mod core_pipeline;
|
||||
pub mod mesh;
|
||||
pub mod pass;
|
||||
pub mod pipeline;
|
||||
pub mod render_command;
|
||||
pub mod render_graph;
|
||||
pub mod render_phase;
|
||||
pub mod render_resource;
|
||||
pub mod renderer;
|
||||
pub mod shader;
|
||||
pub mod texture;
|
||||
pub mod view;
|
||||
|
||||
pub use once_cell;
|
||||
|
||||
use crate::{
|
||||
render_command::RenderCommandPlugin, render_graph::RenderGraph, renderer::RenderResources,
|
||||
camera::CameraPlugin,
|
||||
mesh::MeshPlugin,
|
||||
render_command::RenderCommandPlugin,
|
||||
render_graph::RenderGraph,
|
||||
render_phase::DrawFunctions,
|
||||
renderer::RenderResources,
|
||||
texture::TexturePlugin,
|
||||
view::{ViewPlugin, WindowRenderPlugin},
|
||||
};
|
||||
use bevy_app::{App, Plugin, StartupStage};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
@ -38,8 +46,15 @@ pub enum RenderStage {
|
|||
/// Create Bind Groups that depend on Prepare data and queue up draw calls to run during the Render stage.
|
||||
Queue,
|
||||
|
||||
// TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue
|
||||
/// Sort RenderPhases here
|
||||
PhaseSort,
|
||||
|
||||
/// Actual rendering happens here. In most cases, only the render backend should insert resources here
|
||||
Render,
|
||||
|
||||
/// Cleanup render resources here.
|
||||
Cleanup,
|
||||
}
|
||||
|
||||
impl Plugin for RenderPlugin {
|
||||
|
@ -57,9 +72,25 @@ impl Plugin for RenderPlugin {
|
|||
.add_stage(RenderStage::Extract, extract_stage)
|
||||
.add_stage(RenderStage::Prepare, SystemStage::parallel())
|
||||
.add_stage(RenderStage::Queue, SystemStage::parallel())
|
||||
.add_stage(RenderStage::Render, SystemStage::parallel());
|
||||
render_app.insert_resource(RenderGraph::default());
|
||||
.add_stage(RenderStage::PhaseSort, SystemStage::parallel())
|
||||
.add_stage(RenderStage::Render, SystemStage::parallel())
|
||||
.add_stage(RenderStage::Cleanup, SystemStage::parallel())
|
||||
.init_resource::<RenderGraph>()
|
||||
.init_resource::<DrawFunctions>();
|
||||
|
||||
app.add_sub_app(render_app, |app_world, render_app| {
|
||||
// reserve all existing app entities for use in render_app
|
||||
// they can only be spawned using `get_or_spawn()`
|
||||
let meta_len = app_world.entities().meta.len();
|
||||
render_app
|
||||
.world
|
||||
.entities()
|
||||
.reserve_entities(meta_len as u32);
|
||||
// flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default
|
||||
// these entities cannot be accessed without spawning directly onto them
|
||||
// this _only_ works as expected because clear_entities() is called at the end of every frame.
|
||||
render_app.world.entities_mut().flush_as_invalid();
|
||||
|
||||
// extract
|
||||
extract(app_world, render_app);
|
||||
|
||||
|
@ -77,6 +108,13 @@ impl Plugin for RenderPlugin {
|
|||
.unwrap();
|
||||
queue.run(&mut render_app.world);
|
||||
|
||||
// phase sort
|
||||
let phase_sort = render_app
|
||||
.schedule
|
||||
.get_stage_mut::<SystemStage>(&RenderStage::PhaseSort)
|
||||
.unwrap();
|
||||
phase_sort.run(&mut render_app.world);
|
||||
|
||||
// render
|
||||
let render = render_app
|
||||
.schedule
|
||||
|
@ -84,10 +122,21 @@ impl Plugin for RenderPlugin {
|
|||
.unwrap();
|
||||
render.run(&mut render_app.world);
|
||||
|
||||
// cleanup
|
||||
let cleanup = render_app
|
||||
.schedule
|
||||
.get_stage_mut::<SystemStage>(&RenderStage::Cleanup)
|
||||
.unwrap();
|
||||
cleanup.run(&mut render_app.world);
|
||||
|
||||
render_app.world.clear_entities();
|
||||
});
|
||||
|
||||
app.add_plugin(RenderCommandPlugin)
|
||||
.add_plugin(WindowRenderPlugin)
|
||||
.add_plugin(CameraPlugin)
|
||||
.add_plugin(ViewPlugin)
|
||||
.add_plugin(MeshPlugin)
|
||||
.add_plugin(TexturePlugin);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
use crate::main_pass::TrackedRenderPass;
|
||||
use bevy_ecs::world::World;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
// 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(
|
||||
&mut self,
|
||||
world: &World,
|
||||
pass: &mut TrackedRenderPass,
|
||||
draw_key: usize,
|
||||
sort_key: usize,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DrawFunctions {
|
||||
pub draw_function: Mutex<Vec<Box<dyn Draw>>>,
|
||||
}
|
||||
|
||||
impl DrawFunctions {
|
||||
pub fn add<D: Draw>(&self, draw_function: D) {
|
||||
self.draw_function.lock().push(Box::new(draw_function));
|
||||
}
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
mod draw;
|
||||
mod draw_state;
|
||||
|
||||
pub use draw::*;
|
||||
pub use draw_state::*;
|
||||
|
||||
use crate::{
|
||||
camera::CameraPlugin,
|
||||
color::Color,
|
||||
pass::{LoadOp, Operations, PassDescriptor, RenderPass, TextureAttachment},
|
||||
render_graph::{Node, RenderGraph, ResourceSlotInfo, ResourceSlots, WindowSwapChainNode},
|
||||
render_resource::RenderResourceType,
|
||||
};
|
||||
use crate::{pass::RenderPassColorAttachment, renderer::RenderContext, RenderStage};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_window::WindowId;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MainPassPlugin;
|
||||
|
||||
impl Plugin for MainPassPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// TODO: this should probably just be a dependency
|
||||
app.add_plugin(CameraPlugin);
|
||||
app.sub_app_mut(0)
|
||||
.add_system_to_stage(
|
||||
RenderStage::Prepare,
|
||||
clear_transparent_phase.exclusive_system().at_start(),
|
||||
)
|
||||
.init_resource::<RenderPhase>()
|
||||
.init_resource::<DrawFunctions>();
|
||||
let render_world = app.sub_app_mut(0).world.cell();
|
||||
let mut graph = render_world.get_resource_mut::<RenderGraph>().unwrap();
|
||||
graph.add_node("main_pass", MainPassNode);
|
||||
graph.add_node_edge("camera", "main_pass").unwrap();
|
||||
graph.add_node(
|
||||
"primary_swap_chain",
|
||||
WindowSwapChainNode::new(WindowId::primary()),
|
||||
);
|
||||
graph
|
||||
.add_slot_edge(
|
||||
"primary_swap_chain",
|
||||
WindowSwapChainNode::OUT_TEXTURE,
|
||||
"main_pass",
|
||||
MainPassNode::IN_COLOR_ATTACHMENT,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: sort out the best place for this
|
||||
fn clear_transparent_phase(mut transparent_phase: ResMut<RenderPhase>) {
|
||||
// TODO: TRANSPARENT PHASE SHOULD NOT BE CLEARED HERE!
|
||||
transparent_phase.drawn_things.clear();
|
||||
}
|
||||
|
||||
pub struct Drawable {
|
||||
pub draw_function: usize,
|
||||
pub draw_key: usize,
|
||||
pub sort_key: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderPhase {
|
||||
drawn_things: Vec<Drawable>,
|
||||
}
|
||||
|
||||
impl RenderPhase {
|
||||
#[inline]
|
||||
pub fn add(&mut self, drawable: Drawable) {
|
||||
self.drawn_things.push(drawable);
|
||||
}
|
||||
|
||||
pub fn sort(&mut self) {
|
||||
self.drawn_things.sort_by_key(|d| d.sort_key);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MainPassNode;
|
||||
|
||||
impl MainPassNode {
|
||||
pub const IN_COLOR_ATTACHMENT: &'static str = "color_attachment";
|
||||
}
|
||||
|
||||
impl Node for MainPassNode {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
static INPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo {
|
||||
name: Cow::Borrowed(MainPassNode::IN_COLOR_ATTACHMENT),
|
||||
resource_type: RenderResourceType::Texture,
|
||||
}];
|
||||
INPUT
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
render_context: &mut dyn RenderContext,
|
||||
input: &ResourceSlots,
|
||||
_output: &mut ResourceSlots,
|
||||
) {
|
||||
// TODO: consider adding shorthand like `get_texture(0)`
|
||||
let color_attachment_texture = input.get(0).unwrap().get_texture().unwrap();
|
||||
let pass_descriptor = PassDescriptor {
|
||||
color_attachments: vec![RenderPassColorAttachment {
|
||||
attachment: TextureAttachment::Id(color_attachment_texture),
|
||||
resolve_target: None,
|
||||
ops: Operations {
|
||||
load: LoadOp::Clear(Color::rgb(0.4, 0.4, 0.4)),
|
||||
store: true,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: None,
|
||||
sample_count: 1,
|
||||
};
|
||||
|
||||
let transparent_phase = world.get_resource::<RenderPhase>().unwrap();
|
||||
let draw_functions = world.get_resource::<DrawFunctions>().unwrap();
|
||||
|
||||
render_context.begin_render_pass(
|
||||
&pass_descriptor,
|
||||
&mut |render_pass: &mut dyn RenderPass| {
|
||||
let mut draw_functions = draw_functions.draw_function.lock();
|
||||
let mut tracked_pass = TrackedRenderPass::new(render_pass);
|
||||
for drawable in transparent_phase.drawn_things.iter() {
|
||||
draw_functions[drawable.draw_function].draw(
|
||||
world,
|
||||
&mut tracked_pass,
|
||||
drawable.draw_key,
|
||||
drawable.sort_key,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,14 @@
|
|||
mod conversions;
|
||||
mod mesh_resource_provider;
|
||||
|
||||
use crate::pipeline::{
|
||||
pub use mesh_resource_provider::*;
|
||||
|
||||
use crate::{
|
||||
pipeline::{
|
||||
IndexFormat, InputStepMode, PrimitiveTopology, VertexAttribute, VertexBufferLayout,
|
||||
VertexFormat,
|
||||
},
|
||||
render_resource::BufferId,
|
||||
};
|
||||
use bevy_core::cast_slice;
|
||||
use bevy_math::*;
|
||||
|
@ -182,6 +188,13 @@ impl Indices {
|
|||
Indices::U32(vec) => IndicesIter::U32(vec.iter()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Indices::U16(vec) => vec.len(),
|
||||
Indices::U32(vec) => vec.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
enum IndicesIter<'a> {
|
||||
U16(std::slice::Iter<'a, u16>),
|
||||
|
@ -207,6 +220,13 @@ impl From<&Indices> for IndexFormat {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: this shouldn't live in the Mesh type
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MeshGpuData {
|
||||
pub vertex_buffer: BufferId,
|
||||
pub index_buffer: Option<BufferId>,
|
||||
}
|
||||
|
||||
// TODO: allow values to be unloaded after been submitting to the GPU to conserve memory
|
||||
#[derive(Debug, TypeUuid, Clone)]
|
||||
#[uuid = "8ecbac0f-f545-4473-ad43-e1f4243af51e"]
|
||||
|
@ -218,6 +238,7 @@ pub struct Mesh {
|
|||
/// which allows easy stable VertexBuffers (i.e. same buffer order)
|
||||
attributes: BTreeMap<Cow<'static, str>, VertexAttributeValues>,
|
||||
indices: Option<Indices>,
|
||||
gpu_data: Option<MeshGpuData>,
|
||||
}
|
||||
|
||||
/// Contains geometry in the form of a mesh.
|
||||
|
@ -264,6 +285,7 @@ impl Mesh {
|
|||
primitive_topology,
|
||||
attributes: Default::default(),
|
||||
indices: None,
|
||||
gpu_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,6 +293,10 @@ impl Mesh {
|
|||
self.primitive_topology
|
||||
}
|
||||
|
||||
pub fn gpu_data(&self) -> Option<&MeshGpuData> {
|
||||
self.gpu_data.as_ref()
|
||||
}
|
||||
|
||||
/// Sets the data for a vertex attribute (position, normal etc.). The name will
|
||||
/// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]
|
||||
pub fn set_attribute(
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
use crate::{
|
||||
mesh::{Mesh, MeshGpuData},
|
||||
render_resource::{BufferInfo, BufferUsage},
|
||||
renderer::{RenderResourceContext, RenderResources},
|
||||
};
|
||||
use bevy_asset::{AssetEvent, Assets, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_utils::HashSet;
|
||||
|
||||
fn remove_current_mesh_resources(
|
||||
render_resource_context: &dyn RenderResourceContext,
|
||||
handle: &Handle<Mesh>,
|
||||
meshes: &mut Assets<Mesh>,
|
||||
) {
|
||||
if let Some(gpu_data) = meshes.get_mut(handle).and_then(|m| m.gpu_data.take()) {
|
||||
render_resource_context.remove_buffer(gpu_data.vertex_buffer);
|
||||
if let Some(index_buffer) = gpu_data.index_buffer {
|
||||
render_resource_context.remove_buffer(index_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mesh_resource_provider_system(
|
||||
render_resource_context: Res<RenderResources>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut mesh_events: EventReader<AssetEvent<Mesh>>,
|
||||
) {
|
||||
let mut changed_meshes = HashSet::default();
|
||||
let render_resource_context = &**render_resource_context;
|
||||
for event in mesh_events.iter() {
|
||||
match event {
|
||||
AssetEvent::Created { ref handle } => {
|
||||
changed_meshes.insert(handle.clone_weak());
|
||||
}
|
||||
AssetEvent::Modified { ref handle } => {
|
||||
changed_meshes.insert(handle.clone_weak());
|
||||
// TODO: uncomment this to support mutated meshes
|
||||
// remove_current_mesh_resources(render_resource_context, handle, &mut meshes);
|
||||
}
|
||||
AssetEvent::Removed { ref handle } => {
|
||||
remove_current_mesh_resources(render_resource_context, handle, &mut meshes);
|
||||
// if mesh was modified and removed in the same update, ignore the modification
|
||||
// events are ordered so future modification events are ok
|
||||
changed_meshes.remove(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update changed mesh data
|
||||
for changed_mesh_handle in changed_meshes.iter() {
|
||||
if let Some(mesh) = meshes.get_mut(changed_mesh_handle) {
|
||||
// TODO: this avoids creating new meshes each frame because storing gpu data in the mesh flags it as
|
||||
// modified. this prevents hot reloading and therefore can't be used in an actual impl.
|
||||
if mesh.gpu_data.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vertex_buffer_data = mesh.get_vertex_buffer_data();
|
||||
let vertex_buffer = render_resource_context.create_buffer_with_data(
|
||||
BufferInfo {
|
||||
buffer_usage: BufferUsage::VERTEX,
|
||||
..Default::default()
|
||||
},
|
||||
&vertex_buffer_data,
|
||||
);
|
||||
|
||||
let index_buffer = mesh.get_index_buffer_bytes().map(|data| {
|
||||
render_resource_context.create_buffer_with_data(
|
||||
BufferInfo {
|
||||
buffer_usage: BufferUsage::INDEX,
|
||||
..Default::default()
|
||||
},
|
||||
&data,
|
||||
)
|
||||
});
|
||||
|
||||
mesh.gpu_data = Some(MeshGpuData {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,4 +3,19 @@ mod mesh;
|
|||
/// Generation for some primitive shape meshes.
|
||||
pub mod shape;
|
||||
|
||||
use bevy_asset::AddAsset;
|
||||
pub use mesh::*;
|
||||
|
||||
use bevy_app::{App, CoreStage, Plugin};
|
||||
use bevy_ecs::system::IntoSystem;
|
||||
|
||||
pub struct MeshPlugin;
|
||||
|
||||
impl Plugin for MeshPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_asset::<Mesh>().add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
mesh_resource_provider_system.system(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::{color::Color, pass::Operations, render_resource::TextureId};
|
||||
use crate::{color::Color, pass::Operations, render_resource::TextureViewId};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TextureAttachment {
|
||||
Id(TextureId),
|
||||
Id(TextureViewId),
|
||||
Input(String),
|
||||
}
|
||||
|
||||
impl TextureAttachment {
|
||||
pub fn get_texture_id(&self) -> Option<TextureId> {
|
||||
pub fn get_texture_view_id(&self) -> Option<TextureViewId> {
|
||||
if let TextureAttachment::Id(texture_id) = self {
|
||||
Some(*texture_id)
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::BindingDescriptor;
|
||||
use bevy_utils::AHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use bevy_utils::FixedState;
|
||||
use std::hash::{BuildHasher, Hash, Hasher};
|
||||
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
pub struct BindGroupDescriptor {
|
||||
|
@ -25,7 +25,7 @@ impl BindGroupDescriptor {
|
|||
}
|
||||
|
||||
pub fn update_id(&mut self) {
|
||||
let mut hasher = AHasher::default();
|
||||
let mut hasher = FixedState::default().build_hasher();
|
||||
self.hash(&mut hasher);
|
||||
self.id = BindGroupDescriptorId(hasher.finish());
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
mod bind_group;
|
||||
mod binding;
|
||||
mod compute_pipeline;
|
||||
mod pipeline_layout;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod render_pipeline;
|
||||
mod pipeline_layout;
|
||||
mod state_descriptors;
|
||||
mod vertex_buffer_descriptor;
|
||||
mod vertex_format;
|
||||
|
@ -11,8 +11,8 @@ mod vertex_format;
|
|||
pub use bind_group::*;
|
||||
pub use binding::*;
|
||||
pub use compute_pipeline::*;
|
||||
pub use render_pipeline::*;
|
||||
pub use pipeline_layout::*;
|
||||
pub use render_pipeline::*;
|
||||
pub use state_descriptors::*;
|
||||
pub use vertex_buffer_descriptor::*;
|
||||
pub use vertex_format::*;
|
||||
|
|
|
@ -11,12 +11,29 @@ pub struct PipelineLayout {
|
|||
}
|
||||
|
||||
impl PipelineLayout {
|
||||
// TODO: make direct indexing work to avoid a full scan
|
||||
pub fn get_bind_group(&self, index: u32) -> Option<&BindGroupDescriptor> {
|
||||
self.bind_groups
|
||||
.iter()
|
||||
.find(|bind_group| bind_group.index == index)
|
||||
}
|
||||
|
||||
pub fn get_bind_group_mut(&mut self, index: u32) -> Option<&mut BindGroupDescriptor> {
|
||||
self.bind_groups
|
||||
.iter_mut()
|
||||
.find(|bind_group| bind_group.index == index)
|
||||
}
|
||||
|
||||
pub fn bind_group(&self, index: u32) -> &BindGroupDescriptor {
|
||||
self.get_bind_group(index)
|
||||
.expect("bind group exists")
|
||||
}
|
||||
|
||||
pub fn bind_group_mut(&mut self, index: u32) -> &mut BindGroupDescriptor {
|
||||
self.get_bind_group_mut(index)
|
||||
.expect("bind group exists")
|
||||
}
|
||||
|
||||
pub fn from_shader_layouts(shader_layouts: &mut [ShaderLayout]) -> Self {
|
||||
let mut bind_groups = HashMap::<u32, BindGroupDescriptor>::default();
|
||||
let mut vertex_buffer_descriptors = Vec::new();
|
||||
|
@ -68,6 +85,12 @@ impl PipelineLayout {
|
|||
vertex_buffer_descriptors,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_bind_group_ids(&mut self) {
|
||||
for bind_group in self.bind_groups.iter_mut() {
|
||||
bind_group.update_id();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
|
|
@ -2,7 +2,7 @@ mod render_command_queue;
|
|||
pub use render_command_queue::*;
|
||||
|
||||
use crate::{
|
||||
render_graph::{Node, RenderGraph, ResourceSlots},
|
||||
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext},
|
||||
renderer::RenderContext,
|
||||
RenderStage,
|
||||
};
|
||||
|
@ -12,13 +12,17 @@ use bevy_ecs::prelude::*;
|
|||
#[derive(Default)]
|
||||
pub struct RenderCommandPlugin;
|
||||
|
||||
impl RenderCommandPlugin {
|
||||
pub const RENDER_COMMAND_QUEUE_NODE: &'static str = "render_command_queue";
|
||||
}
|
||||
|
||||
impl Plugin for RenderCommandPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<RenderCommandQueue>();
|
||||
let render_app = app.sub_app_mut(0);
|
||||
render_app.add_system_to_stage(RenderStage::Extract, extract_render_commands.system());
|
||||
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
|
||||
graph.add_node("render_command_queue", RenderCommandQueueNode);
|
||||
graph.add_node(Self::RENDER_COMMAND_QUEUE_NODE, RenderCommandQueueNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,14 +38,14 @@ fn extract_render_commands(
|
|||
pub struct RenderCommandQueueNode;
|
||||
|
||||
impl Node for RenderCommandQueueNode {
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceSlots,
|
||||
_output: &mut ResourceSlots,
|
||||
) {
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let queue = world.get_resource::<RenderCommandQueue>().unwrap();
|
||||
queue.execute(render_context);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
234
pipelined/bevy_render2/src/render_graph/context.rs
Normal file
234
pipelined/bevy_render2/src/render_graph/context.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::{
|
||||
render_graph::{NodeState, RenderGraph, SlotInfos, SlotLabel, SlotType, SlotValue},
|
||||
render_resource::{BufferId, SamplerId, TextureViewId},
|
||||
};
|
||||
use bevy_ecs::entity::Entity;
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct RunSubGraph {
|
||||
pub name: Cow<'static, str>,
|
||||
pub inputs: Vec<SlotValue>,
|
||||
}
|
||||
|
||||
pub struct RenderGraphContext<'a> {
|
||||
graph: &'a RenderGraph,
|
||||
node: &'a NodeState,
|
||||
inputs: &'a [SlotValue],
|
||||
outputs: &'a mut [Option<SlotValue>],
|
||||
run_sub_graphs: Vec<RunSubGraph>,
|
||||
}
|
||||
|
||||
impl<'a> RenderGraphContext<'a> {
|
||||
pub fn new(
|
||||
graph: &'a RenderGraph,
|
||||
node: &'a NodeState,
|
||||
inputs: &'a [SlotValue],
|
||||
outputs: &'a mut [Option<SlotValue>],
|
||||
) -> Self {
|
||||
Self {
|
||||
graph,
|
||||
node,
|
||||
inputs,
|
||||
outputs,
|
||||
run_sub_graphs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inputs(&self) -> &[SlotValue] {
|
||||
self.inputs
|
||||
}
|
||||
|
||||
pub fn input_info(&self) -> &SlotInfos {
|
||||
&self.node.input_slots
|
||||
}
|
||||
|
||||
pub fn output_info(&self) -> &SlotInfos {
|
||||
&self.node.output_slots
|
||||
}
|
||||
|
||||
pub fn get_input(&self, label: impl Into<SlotLabel>) -> Result<SlotValue, InputSlotError> {
|
||||
let label = label.into();
|
||||
let index = self
|
||||
.input_info()
|
||||
.get_slot_index(label.clone())
|
||||
.ok_or(InputSlotError::InvalidSlot(label))?;
|
||||
Ok(self.inputs[index])
|
||||
}
|
||||
|
||||
pub fn get_input_texture(
|
||||
&self,
|
||||
label: impl Into<SlotLabel>,
|
||||
) -> Result<TextureViewId, InputSlotError> {
|
||||
let label = label.into();
|
||||
match self.get_input(label.clone())? {
|
||||
SlotValue::TextureView(value) => Ok(value),
|
||||
value @ _ => Err(InputSlotError::MismatchedSlotType {
|
||||
label,
|
||||
actual: value.slot_type(),
|
||||
expected: SlotType::TextureView,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_sampler(
|
||||
&self,
|
||||
label: impl Into<SlotLabel>,
|
||||
) -> Result<SamplerId, InputSlotError> {
|
||||
let label = label.into();
|
||||
match self.get_input(label.clone())? {
|
||||
SlotValue::Sampler(value) => Ok(value),
|
||||
value @ _ => Err(InputSlotError::MismatchedSlotType {
|
||||
label,
|
||||
actual: value.slot_type(),
|
||||
expected: SlotType::Sampler,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_buffer(
|
||||
&self,
|
||||
label: impl Into<SlotLabel>,
|
||||
) -> Result<BufferId, InputSlotError> {
|
||||
let label = label.into();
|
||||
match self.get_input(label.clone())? {
|
||||
SlotValue::Buffer(value) => Ok(value),
|
||||
value @ _ => Err(InputSlotError::MismatchedSlotType {
|
||||
label,
|
||||
actual: value.slot_type(),
|
||||
expected: SlotType::Buffer,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input_entity(&self, label: impl Into<SlotLabel>) -> Result<Entity, InputSlotError> {
|
||||
let label = label.into();
|
||||
match self.get_input(label.clone())? {
|
||||
SlotValue::Entity(value) => Ok(value),
|
||||
value @ _ => Err(InputSlotError::MismatchedSlotType {
|
||||
label,
|
||||
actual: value.slot_type(),
|
||||
expected: SlotType::Entity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_output(
|
||||
&mut self,
|
||||
label: impl Into<SlotLabel>,
|
||||
value: impl Into<SlotValue>,
|
||||
) -> Result<(), OutputSlotError> {
|
||||
let label = label.into();
|
||||
let value = value.into();
|
||||
let slot_index = self
|
||||
.output_info()
|
||||
.get_slot_index(label.clone())
|
||||
.ok_or(OutputSlotError::InvalidSlot(label.clone()))?;
|
||||
let slot = self
|
||||
.output_info()
|
||||
.get_slot(slot_index)
|
||||
.expect("slot is valid");
|
||||
if value.slot_type() != slot.slot_type {
|
||||
return Err(OutputSlotError::MismatchedSlotType {
|
||||
label,
|
||||
actual: slot.slot_type,
|
||||
expected: value.slot_type(),
|
||||
});
|
||||
}
|
||||
self.outputs[slot_index] = Some(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_sub_graph(
|
||||
&mut self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
inputs: Vec<SlotValue>,
|
||||
) -> Result<(), RunSubGraphError> {
|
||||
let name = name.into();
|
||||
let sub_graph = self
|
||||
.graph
|
||||
.get_sub_graph(&name)
|
||||
.ok_or(RunSubGraphError::MissingSubGraph(name.clone()))?;
|
||||
if let Some(input_node) = sub_graph.input_node() {
|
||||
for (i, input_slot) in input_node.input_slots.iter().enumerate() {
|
||||
if let Some(input_value) = inputs.get(i) {
|
||||
if input_slot.slot_type != input_value.slot_type() {
|
||||
return Err(RunSubGraphError::MismatchedInputSlotType {
|
||||
graph_name: name,
|
||||
slot_index: i,
|
||||
actual: input_value.slot_type(),
|
||||
expected: input_slot.slot_type,
|
||||
label: input_slot.name.clone().into(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Err(RunSubGraphError::MissingInput {
|
||||
slot_index: i,
|
||||
slot_name: input_slot.name.clone(),
|
||||
graph_name: name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !inputs.is_empty() {
|
||||
return Err(RunSubGraphError::SubGraphHasNoInputs(name));
|
||||
}
|
||||
}
|
||||
|
||||
self.run_sub_graphs.push(RunSubGraph { name, inputs });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finish(self) -> Vec<RunSubGraph> {
|
||||
self.run_sub_graphs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Eq, PartialEq)]
|
||||
pub enum RunSubGraphError {
|
||||
#[error("tried to run a non-existent sub-graph")]
|
||||
MissingSubGraph(Cow<'static, str>),
|
||||
#[error("passed in inputs, but this sub-graph doesn't have any")]
|
||||
SubGraphHasNoInputs(Cow<'static, str>),
|
||||
#[error("sub graph (name: '{graph_name:?}') could not be run because slot '{slot_name}' at index {slot_index} has no value")]
|
||||
MissingInput {
|
||||
slot_index: usize,
|
||||
slot_name: Cow<'static, str>,
|
||||
graph_name: Cow<'static, str>,
|
||||
},
|
||||
#[error("attempted to use the wrong type for input slot")]
|
||||
MismatchedInputSlotType {
|
||||
graph_name: Cow<'static, str>,
|
||||
slot_index: usize,
|
||||
label: SlotLabel,
|
||||
expected: SlotType,
|
||||
actual: SlotType,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Eq, PartialEq)]
|
||||
pub enum OutputSlotError {
|
||||
#[error("slot does not exist")]
|
||||
InvalidSlot(SlotLabel),
|
||||
#[error("attempted to assign the wrong type to slot")]
|
||||
MismatchedSlotType {
|
||||
label: SlotLabel,
|
||||
expected: SlotType,
|
||||
actual: SlotType,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Eq, PartialEq)]
|
||||
pub enum InputSlotError {
|
||||
#[error("slot does not exist")]
|
||||
InvalidSlot(SlotLabel),
|
||||
#[error("attempted to retrieve the wrong type from input slot")]
|
||||
MismatchedSlotType {
|
||||
label: SlotLabel,
|
||||
expected: SlotType,
|
||||
actual: SlotType,
|
||||
},
|
||||
}
|
|
@ -1,4 +1,11 @@
|
|||
use super::{Edge, Node, NodeId, NodeLabel, NodeState, RenderGraphError, SlotLabel};
|
||||
use crate::{
|
||||
render_graph::{
|
||||
Edge, Node, NodeId, NodeLabel, NodeRunError, NodeState, RenderGraphContext,
|
||||
RenderGraphError, SlotInfo, SlotLabel,
|
||||
},
|
||||
renderer::RenderContext,
|
||||
};
|
||||
use bevy_ecs::prelude::World;
|
||||
use bevy_utils::HashMap;
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
|
@ -6,9 +13,38 @@ use std::{borrow::Cow, fmt::Debug};
|
|||
pub struct RenderGraph {
|
||||
nodes: HashMap<NodeId, NodeState>,
|
||||
node_names: HashMap<Cow<'static, str>, NodeId>,
|
||||
sub_graphs: HashMap<Cow<'static, str>, RenderGraph>,
|
||||
input_node: Option<NodeId>,
|
||||
}
|
||||
|
||||
impl RenderGraph {
|
||||
pub const INPUT_NODE_NAME: &'static str = "GraphInputNode";
|
||||
|
||||
pub fn update(&mut self, world: &mut World) {
|
||||
for node in self.nodes.values_mut() {
|
||||
node.node.update(world);
|
||||
}
|
||||
|
||||
for sub_graph in self.sub_graphs.values_mut() {
|
||||
sub_graph.update(world);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_input(&mut self, inputs: Vec<SlotInfo>) -> NodeId {
|
||||
if self.input_node.is_some() {
|
||||
panic!("Graph already has an input node");
|
||||
}
|
||||
|
||||
let id = self.add_node("GraphInputNode", GraphInputNode { inputs });
|
||||
self.input_node = Some(id);
|
||||
id
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn input_node(&self) -> Option<&NodeState> {
|
||||
self.input_node.and_then(|id| self.get_node_state(id).ok())
|
||||
}
|
||||
|
||||
pub fn add_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
|
||||
where
|
||||
T: Node,
|
||||
|
@ -80,17 +116,21 @@ impl RenderGraph {
|
|||
input_node: impl Into<NodeLabel>,
|
||||
input_slot: impl Into<SlotLabel>,
|
||||
) -> Result<(), RenderGraphError> {
|
||||
let output_slot = output_slot.into();
|
||||
let input_slot = input_slot.into();
|
||||
let output_node_id = self.get_node_id(output_node)?;
|
||||
let input_node_id = self.get_node_id(input_node)?;
|
||||
|
||||
let output_index = self
|
||||
.get_node_state(output_node_id)?
|
||||
.output_slots
|
||||
.get_slot_index(output_slot)?;
|
||||
.get_slot_index(output_slot.clone())
|
||||
.ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?;
|
||||
let input_index = self
|
||||
.get_node_state(input_node_id)?
|
||||
.input_slots
|
||||
.get_slot_index(input_slot)?;
|
||||
.get_slot_index(input_slot.clone())
|
||||
.ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?;
|
||||
|
||||
let edge = Edge::SlotEdge {
|
||||
output_node: output_node_id,
|
||||
|
@ -151,8 +191,18 @@ impl RenderGraph {
|
|||
let output_node_state = self.get_node_state(output_node)?;
|
||||
let input_node_state = self.get_node_state(input_node)?;
|
||||
|
||||
let output_slot = output_node_state.output_slots.get_slot(output_index)?;
|
||||
let input_slot = input_node_state.input_slots.get_slot(input_index)?;
|
||||
let output_slot = output_node_state
|
||||
.output_slots
|
||||
.get_slot(output_index)
|
||||
.ok_or_else(|| {
|
||||
RenderGraphError::InvalidOutputNodeSlot(SlotLabel::Index(output_index))
|
||||
})?;
|
||||
let input_slot = input_node_state
|
||||
.input_slots
|
||||
.get_slot(input_index)
|
||||
.ok_or_else(|| {
|
||||
RenderGraphError::InvalidInputNodeSlot(SlotLabel::Index(input_index))
|
||||
})?;
|
||||
|
||||
if let Some(Edge::SlotEdge {
|
||||
output_node: current_output_node,
|
||||
|
@ -175,7 +225,7 @@ impl RenderGraph {
|
|||
});
|
||||
}
|
||||
|
||||
if output_slot.resource_type != input_slot.resource_type {
|
||||
if output_slot.slot_type != input_slot.slot_type {
|
||||
return Err(RenderGraphError::MismatchedNodeSlots {
|
||||
output_node,
|
||||
output_slot: output_index,
|
||||
|
@ -241,6 +291,18 @@ impl RenderGraph {
|
|||
.map(|edge| (edge, edge.get_input_node()))
|
||||
.map(move |(edge, input_node_id)| (edge, self.get_node_state(input_node_id).unwrap())))
|
||||
}
|
||||
|
||||
pub fn add_sub_graph(&mut self, name: impl Into<Cow<'static, str>>, sub_graph: RenderGraph) {
|
||||
self.sub_graphs.insert(name.into(), sub_graph);
|
||||
}
|
||||
|
||||
pub fn get_sub_graph(&self, name: impl AsRef<str>) -> Option<&RenderGraph> {
|
||||
self.sub_graphs.get(name.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_sub_graph_mut(&mut self, name: impl AsRef<str>) -> Option<&mut RenderGraph> {
|
||||
self.sub_graphs.get_mut(name.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RenderGraph {
|
||||
|
@ -255,13 +317,40 @@ impl Debug for RenderGraph {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct GraphInputNode {
|
||||
inputs: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
impl Node for GraphInputNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
self.inputs.clone()
|
||||
}
|
||||
|
||||
fn output(&self) -> Vec<SlotInfo> {
|
||||
self.inputs.clone()
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
_render_context: &mut dyn RenderContext,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
for i in 0..graph.inputs().len() {
|
||||
let input = graph.inputs()[i];
|
||||
graph.set_output(i, input)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
render_graph::{
|
||||
Edge, Node, NodeId, RenderGraph, RenderGraphError, ResourceSlotInfo, ResourceSlots,
|
||||
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
|
||||
SlotInfo, SlotType,
|
||||
},
|
||||
render_resource::RenderResourceType,
|
||||
renderer::RenderContext,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
|
@ -270,45 +359,39 @@ mod tests {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct TestNode {
|
||||
inputs: Vec<ResourceSlotInfo>,
|
||||
outputs: Vec<ResourceSlotInfo>,
|
||||
inputs: Vec<SlotInfo>,
|
||||
outputs: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
impl TestNode {
|
||||
pub fn new(inputs: usize, outputs: usize) -> Self {
|
||||
TestNode {
|
||||
inputs: (0..inputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("in_{}", i).into(),
|
||||
resource_type: RenderResourceType::Texture,
|
||||
})
|
||||
.map(|i| SlotInfo::new(format!("in_{}", i), SlotType::TextureView))
|
||||
.collect(),
|
||||
outputs: (0..outputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("out_{}", i).into(),
|
||||
resource_type: RenderResourceType::Texture,
|
||||
})
|
||||
.map(|i| SlotInfo::new(format!("out_{}", i), SlotType::TextureView))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for TestNode {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
&self.inputs
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
self.inputs.clone()
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
&self.outputs
|
||||
fn output(&self) -> Vec<SlotInfo> {
|
||||
self.outputs.clone()
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_: &World,
|
||||
fn run(
|
||||
&self,
|
||||
_: &mut RenderGraphContext,
|
||||
_: &mut dyn RenderContext,
|
||||
_: &ResourceSlots,
|
||||
_: &mut ResourceSlots,
|
||||
) {
|
||||
_: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,13 +458,13 @@ mod tests {
|
|||
}
|
||||
|
||||
impl Node for MyNode {
|
||||
fn update(
|
||||
&mut self,
|
||||
_: &World,
|
||||
fn run(
|
||||
&self,
|
||||
_: &mut RenderGraphContext,
|
||||
_: &mut dyn RenderContext,
|
||||
_: &ResourceSlots,
|
||||
_: &mut ResourceSlots,
|
||||
) {
|
||||
_: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
mod context;
|
||||
mod edge;
|
||||
mod graph;
|
||||
mod node;
|
||||
mod node_slot;
|
||||
mod nodes;
|
||||
mod schedule;
|
||||
|
||||
pub use context::*;
|
||||
pub use edge::*;
|
||||
pub use graph::*;
|
||||
pub use node::*;
|
||||
pub use node_slot::*;
|
||||
pub use nodes::*;
|
||||
pub use schedule::*;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -18,8 +16,10 @@ use thiserror::Error;
|
|||
pub enum RenderGraphError {
|
||||
#[error("node does not exist")]
|
||||
InvalidNode(NodeLabel),
|
||||
#[error("node slot does not exist")]
|
||||
InvalidNodeSlot(SlotLabel),
|
||||
#[error("output node slot does not exist")]
|
||||
InvalidOutputNodeSlot(SlotLabel),
|
||||
#[error("input node slot does not exist")]
|
||||
InvalidInputNodeSlot(SlotLabel),
|
||||
#[error("node does not match the given type")]
|
||||
WrongNodeType,
|
||||
#[error("attempted to connect a node output slot to an incompatible input node slot")]
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
use super::{Edge, RenderGraphError, ResourceSlotInfo, ResourceSlots};
|
||||
use crate::renderer::RenderContext;
|
||||
use crate::{
|
||||
render_graph::{
|
||||
Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError,
|
||||
RunSubGraphError, SlotInfo, SlotInfos,
|
||||
},
|
||||
renderer::RenderContext,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_utils::Uuid;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct NodeId(Uuid);
|
||||
|
@ -20,27 +26,38 @@ impl NodeId {
|
|||
}
|
||||
|
||||
pub trait Node: Downcast + Send + Sync + 'static {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
&[]
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
&[]
|
||||
fn output(&self) -> Vec<SlotInfo> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Run the graph node logic. This runs once per graph run after [Node::prepare] has been called
|
||||
/// on all nodes.
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
/// Update internal node state using the current render [`World`].
|
||||
fn update(&mut self, _world: &mut World) {}
|
||||
|
||||
/// Run the graph node logic
|
||||
fn run(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut dyn RenderContext,
|
||||
input: &ResourceSlots,
|
||||
output: &mut ResourceSlots,
|
||||
);
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError>;
|
||||
}
|
||||
|
||||
impl_downcast!(Node);
|
||||
|
||||
#[derive(Error, Debug, Eq, PartialEq)]
|
||||
pub enum NodeRunError {
|
||||
#[error("encountered an input slot error")]
|
||||
InputSlotError(#[from] InputSlotError),
|
||||
#[error("encountered an output slot error")]
|
||||
OutputSlotError(#[from] OutputSlotError),
|
||||
#[error("encountered an error when running a sub-graph")]
|
||||
RunSubGraphError(#[from] RunSubGraphError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Edges {
|
||||
pub id: NodeId,
|
||||
|
@ -111,8 +128,8 @@ pub struct NodeState {
|
|||
pub name: Option<Cow<'static, str>>,
|
||||
pub type_name: &'static str,
|
||||
pub node: Box<dyn Node>,
|
||||
pub input_slots: ResourceSlots,
|
||||
pub output_slots: ResourceSlots,
|
||||
pub input_slots: SlotInfos,
|
||||
pub output_slots: SlotInfos,
|
||||
pub edges: Edges,
|
||||
}
|
||||
|
||||
|
@ -130,8 +147,8 @@ impl NodeState {
|
|||
NodeState {
|
||||
id,
|
||||
name: None,
|
||||
input_slots: ResourceSlots::from(node.input()),
|
||||
output_slots: ResourceSlots::from(node.output()),
|
||||
input_slots: node.input().into(),
|
||||
output_slots: node.output().into(),
|
||||
node: Box::new(node),
|
||||
type_name: std::any::type_name::<T>(),
|
||||
edges: Edges {
|
||||
|
@ -206,3 +223,15 @@ impl From<NodeId> for NodeLabel {
|
|||
NodeLabel::Id(value)
|
||||
}
|
||||
}
|
||||
pub struct EmptyNode;
|
||||
|
||||
impl Node for EmptyNode {
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
_render_context: &mut dyn RenderContext,
|
||||
_world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,56 @@
|
|||
use crate::render_resource::{RenderResourceId, RenderResourceType};
|
||||
|
||||
use super::RenderGraphError;
|
||||
use crate::render_resource::{BufferId, SamplerId, TextureViewId};
|
||||
use bevy_ecs::entity::Entity;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResourceSlot {
|
||||
pub resource: Option<RenderResourceId>,
|
||||
pub name: Cow<'static, str>,
|
||||
pub resource_type: RenderResourceType,
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum SlotValue {
|
||||
Buffer(BufferId),
|
||||
TextureView(TextureViewId),
|
||||
Sampler(SamplerId),
|
||||
Entity(Entity),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ResourceSlots {
|
||||
slots: Vec<ResourceSlot>,
|
||||
impl SlotValue {
|
||||
pub fn slot_type(self) -> SlotType {
|
||||
match self {
|
||||
SlotValue::Buffer(_) => SlotType::Buffer,
|
||||
SlotValue::TextureView(_) => SlotType::TextureView,
|
||||
SlotValue::Sampler(_) => SlotType::Sampler,
|
||||
SlotValue::Entity(_) => SlotType::Entity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BufferId> for SlotValue {
|
||||
fn from(value: BufferId) -> Self {
|
||||
SlotValue::Buffer(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextureViewId> for SlotValue {
|
||||
fn from(value: TextureViewId) -> Self {
|
||||
SlotValue::TextureView(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SamplerId> for SlotValue {
|
||||
fn from(value: SamplerId) -> Self {
|
||||
SlotValue::Sampler(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Entity> for SlotValue {
|
||||
fn from(value: Entity) -> Self {
|
||||
SlotValue::Entity(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum SlotType {
|
||||
Buffer,
|
||||
TextureView,
|
||||
Sampler,
|
||||
Entity,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
|
@ -39,105 +77,83 @@ impl From<&'static str> for SlotLabel {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for SlotLabel {
|
||||
fn from(value: Cow<'static, str>) -> Self {
|
||||
SlotLabel::Name(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for SlotLabel {
|
||||
fn from(value: usize) -> Self {
|
||||
SlotLabel::Index(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSlots {
|
||||
pub fn set(&mut self, label: impl Into<SlotLabel>, resource: RenderResourceId) {
|
||||
let mut slot = self.get_slot_mut(label).unwrap();
|
||||
slot.resource = Some(resource);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SlotInfo {
|
||||
pub name: Cow<'static, str>,
|
||||
pub slot_type: SlotType,
|
||||
}
|
||||
|
||||
pub fn get(&self, label: impl Into<SlotLabel>) -> Option<RenderResourceId> {
|
||||
let slot = self.get_slot(label).unwrap();
|
||||
slot.resource.clone()
|
||||
impl SlotInfo {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, slot_type: SlotType) -> Self {
|
||||
SlotInfo {
|
||||
name: name.into(),
|
||||
slot_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Result<&ResourceSlot, RenderGraphError> {
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SlotInfos {
|
||||
slots: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
impl<T: IntoIterator<Item = SlotInfo>> From<T> for SlotInfos {
|
||||
fn from(slots: T) -> Self {
|
||||
SlotInfos {
|
||||
slots: slots.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SlotInfos {
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.slots.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.slots.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Option<&SlotInfo> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots
|
||||
.get(index)
|
||||
.ok_or(RenderGraphError::InvalidNodeSlot(label))
|
||||
self.slots.get(index)
|
||||
}
|
||||
|
||||
pub fn get_slot_mut(
|
||||
&mut self,
|
||||
label: impl Into<SlotLabel>,
|
||||
) -> Result<&mut ResourceSlot, RenderGraphError> {
|
||||
pub fn get_slot_mut(&mut self, label: impl Into<SlotLabel>) -> Option<&mut SlotInfo> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots
|
||||
.get_mut(index)
|
||||
.ok_or(RenderGraphError::InvalidNodeSlot(label))
|
||||
self.slots.get_mut(index)
|
||||
}
|
||||
|
||||
pub fn get_slot_index(&self, label: impl Into<SlotLabel>) -> Result<usize, RenderGraphError> {
|
||||
pub fn get_slot_index(&self, label: impl Into<SlotLabel>) -> Option<usize> {
|
||||
let label = label.into();
|
||||
match label {
|
||||
SlotLabel::Index(index) => Ok(index),
|
||||
SlotLabel::Index(index) => Some(index),
|
||||
SlotLabel::Name(ref name) => self
|
||||
.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_i, s)| s.name == *name)
|
||||
.map(|(i, _s)| i)
|
||||
.ok_or(RenderGraphError::InvalidNodeSlot(label)),
|
||||
.map(|(i, _s)| i),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &ResourceSlot> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &SlotInfo> {
|
||||
self.slots.iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ResourceSlot> {
|
||||
self.slots.iter_mut()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.slots.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.slots.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ResourceSlotInfo> for ResourceSlot {
|
||||
fn from(slot: &ResourceSlotInfo) -> Self {
|
||||
ResourceSlot {
|
||||
resource: None,
|
||||
name: slot.name.clone(),
|
||||
resource_type: slot.resource_type.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[ResourceSlotInfo]> for ResourceSlots {
|
||||
fn from(slots: &[ResourceSlotInfo]) -> Self {
|
||||
ResourceSlots {
|
||||
slots: slots
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<ResourceSlot>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceSlotInfo {
|
||||
pub name: Cow<'static, str>,
|
||||
pub resource_type: RenderResourceType,
|
||||
}
|
||||
|
||||
impl ResourceSlotInfo {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, resource_type: RenderResourceType) -> Self {
|
||||
ResourceSlotInfo {
|
||||
name: name.into(),
|
||||
resource_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
mod window_swap_chain_node;
|
||||
|
||||
pub use window_swap_chain_node::*;
|
|
@ -1,99 +0,0 @@
|
|||
use crate::{
|
||||
render_graph::{Node, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::{RenderResourceId, RenderResourceType, SwapChainDescriptor},
|
||||
renderer::RenderContext,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_utils::HashMap;
|
||||
use bevy_window::WindowId;
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub struct ExtractedWindow {
|
||||
pub id: WindowId,
|
||||
pub handle: RawWindowHandleWrapper,
|
||||
pub physical_width: u32,
|
||||
pub physical_height: u32,
|
||||
pub vsync: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtractedWindows {
|
||||
pub windows: HashMap<WindowId, ExtractedWindow>,
|
||||
}
|
||||
|
||||
impl Deref for ExtractedWindows {
|
||||
type Target = HashMap<WindowId, ExtractedWindow>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.windows
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ExtractedWindows {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.windows
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RawWindowHandleWrapper(pub RawWindowHandle);
|
||||
|
||||
// TODO: THIS IS NOT SAFE ... ONLY FOR PROTOTYPING
|
||||
unsafe impl Send for RawWindowHandleWrapper {}
|
||||
unsafe impl Sync for RawWindowHandleWrapper {}
|
||||
|
||||
// TODO: safe?
|
||||
unsafe impl HasRawWindowHandle for RawWindowHandleWrapper {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowSwapChainNode {
|
||||
window_id: WindowId,
|
||||
}
|
||||
|
||||
impl WindowSwapChainNode {
|
||||
pub const OUT_TEXTURE: &'static str = "texture";
|
||||
|
||||
pub fn new(window_id: WindowId) -> Self {
|
||||
WindowSwapChainNode { window_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for WindowSwapChainNode {
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
static OUTPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo {
|
||||
name: Cow::Borrowed(WindowSwapChainNode::OUT_TEXTURE),
|
||||
resource_type: RenderResourceType::Texture,
|
||||
}];
|
||||
OUTPUT
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceSlots,
|
||||
output: &mut ResourceSlots,
|
||||
) {
|
||||
let windows = world.get_resource::<ExtractedWindows>().unwrap();
|
||||
let window = windows
|
||||
.get(&self.window_id)
|
||||
.expect("Window swapchain node refers to a non-existent window.");
|
||||
|
||||
let render_resource_context = render_context.resources_mut();
|
||||
let swap_chain_descriptor = SwapChainDescriptor {
|
||||
window_id: window.id,
|
||||
format: crate::texture::TextureFormat::Bgra8UnormSrgb,
|
||||
width: window.physical_width,
|
||||
height: window.physical_height,
|
||||
vsync: window.vsync,
|
||||
};
|
||||
|
||||
let swap_chain_texture =
|
||||
render_resource_context.next_swap_chain_texture(&swap_chain_descriptor);
|
||||
output.set(0, RenderResourceId::Texture(swap_chain_texture));
|
||||
}
|
||||
}
|
|
@ -1,552 +0,0 @@
|
|||
use super::{NodeId, NodeState, RenderGraph, RenderGraphError};
|
||||
use bevy_utils::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StagerError {
|
||||
// This might have to be `:` tagged at the end.
|
||||
#[error("encountered a `RenderGraphError`")]
|
||||
RenderGraphError(#[from] RenderGraphError),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct Stage {
|
||||
pub jobs: Vec<OrderedJob>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct OrderedJob {
|
||||
pub nodes: Vec<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StageBorrow<'a> {
|
||||
pub jobs: Vec<OrderedJobBorrow<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct OrderedJobBorrow<'a> {
|
||||
pub node_states: Vec<&'a mut NodeState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct NodeIndices {
|
||||
stage: usize,
|
||||
job: usize,
|
||||
node: usize,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Stages {
|
||||
stages: Vec<Stage>,
|
||||
/// a collection of node indices that are used to efficiently borrow render graph nodes
|
||||
node_indices: HashMap<NodeId, NodeIndices>,
|
||||
}
|
||||
|
||||
impl Stages {
|
||||
pub fn new(stages: Vec<Stage>) -> Self {
|
||||
let mut node_indices = HashMap::default();
|
||||
for (stage_index, stage) in stages.iter().enumerate() {
|
||||
for (job_index, job) in stage.jobs.iter().enumerate() {
|
||||
for (node_index, node) in job.nodes.iter().enumerate() {
|
||||
node_indices.insert(
|
||||
*node,
|
||||
NodeIndices {
|
||||
stage: stage_index,
|
||||
job: job_index,
|
||||
node: node_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stages {
|
||||
stages,
|
||||
node_indices,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow<'a>(&self, render_graph: &'a mut RenderGraph) -> Vec<StageBorrow<'a>> {
|
||||
// unfortunately borrowing render graph nodes in a specific order takes a little bit of
|
||||
// gymnastics
|
||||
let mut stage_borrows = Vec::with_capacity(self.stages.len());
|
||||
|
||||
let mut node_borrows = Vec::new();
|
||||
for node in render_graph.iter_nodes_mut() {
|
||||
let indices = self.node_indices.get(&node.id).unwrap();
|
||||
node_borrows.push((node, indices));
|
||||
}
|
||||
|
||||
node_borrows.sort_by_key(|(_node, indices)| <&NodeIndices>::clone(indices));
|
||||
let mut last_stage = usize::MAX;
|
||||
let mut last_job = usize::MAX;
|
||||
for (node, indices) in node_borrows.drain(..) {
|
||||
if last_stage != indices.stage {
|
||||
stage_borrows.push(StageBorrow::default());
|
||||
last_job = usize::MAX;
|
||||
}
|
||||
|
||||
let stage = &mut stage_borrows[indices.stage];
|
||||
if last_job != indices.job {
|
||||
stage.jobs.push(OrderedJobBorrow::default());
|
||||
}
|
||||
|
||||
let job = &mut stage.jobs[indices.job];
|
||||
job.node_states.push(node);
|
||||
|
||||
last_stage = indices.stage;
|
||||
last_job = indices.job;
|
||||
}
|
||||
|
||||
stage_borrows
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a collection of `Stages`, which are sets of OrderedJobs that must be run before moving
|
||||
/// on to the next stage
|
||||
pub trait RenderGraphStager {
|
||||
fn get_stages(&mut self, render_graph: &RenderGraph) -> Result<Stages, RenderGraphError>;
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
/// This scheduler ignores dependencies and puts everything in one stage. It shouldn't be used for
|
||||
/// anything :)
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LinearStager;
|
||||
|
||||
impl RenderGraphStager for LinearStager {
|
||||
fn get_stages(&mut self, render_graph: &RenderGraph) -> Result<Stages, RenderGraphError> {
|
||||
let mut stage = Stage::default();
|
||||
let mut job = OrderedJob::default();
|
||||
for node in render_graph.iter_nodes() {
|
||||
job.nodes.push(node.id);
|
||||
}
|
||||
|
||||
stage.jobs.push(job);
|
||||
|
||||
Ok(Stages::new(vec![stage]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
/// Determines the grouping strategy used when constructing graph stages
|
||||
pub enum JobGrouping {
|
||||
/// Default to adding the current node to a new job in its assigned stage. This results
|
||||
/// in a "loose" pack that is easier to parallelize but has more jobs
|
||||
Loose,
|
||||
/// Default to adding the current node into the first job in its assigned stage. This results
|
||||
/// in a "tight" pack that is harder to parallelize but results in fewer jobs
|
||||
Tight,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Produces Render Graph stages and jobs in a way that ensures node dependencies are respected.
|
||||
pub struct DependentNodeStager {
|
||||
job_grouping: JobGrouping,
|
||||
}
|
||||
|
||||
impl DependentNodeStager {
|
||||
pub fn loose_grouping() -> Self {
|
||||
DependentNodeStager {
|
||||
job_grouping: JobGrouping::Loose,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tight_grouping() -> Self {
|
||||
DependentNodeStager {
|
||||
job_grouping: JobGrouping::Tight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderGraphStager for DependentNodeStager {
|
||||
fn get_stages<'a>(&mut self, render_graph: &RenderGraph) -> Result<Stages, RenderGraphError> {
|
||||
// get all nodes without input. this intentionally includes nodes with no outputs
|
||||
let output_only_nodes = render_graph
|
||||
.iter_nodes()
|
||||
.filter(|node| node.input_slots.is_empty());
|
||||
let mut stages = vec![Stage::default()];
|
||||
let mut node_stages = HashMap::default();
|
||||
for output_only_node in output_only_nodes {
|
||||
// each "output only" node should start a new job on the first stage
|
||||
stage_node(
|
||||
render_graph,
|
||||
&mut stages,
|
||||
&mut node_stages,
|
||||
output_only_node,
|
||||
self.job_grouping,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Stages::new(stages))
|
||||
}
|
||||
}
|
||||
|
||||
fn stage_node(
|
||||
graph: &RenderGraph,
|
||||
stages: &mut Vec<Stage>,
|
||||
node_stages_and_jobs: &mut HashMap<NodeId, (usize, usize)>,
|
||||
node: &NodeState,
|
||||
job_grouping: JobGrouping,
|
||||
) {
|
||||
// don't re-visit nodes or visit them before all of their parents have been visited
|
||||
if node_stages_and_jobs.contains_key(&node.id)
|
||||
|| node
|
||||
.edges
|
||||
.input_edges
|
||||
.iter()
|
||||
.any(|e| !node_stages_and_jobs.contains_key(&e.get_output_node()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// by default assume we are creating a new job on a new stage
|
||||
let mut stage_index = 0;
|
||||
let mut job_index = match job_grouping {
|
||||
JobGrouping::Tight => Some(0),
|
||||
JobGrouping::Loose => None,
|
||||
};
|
||||
|
||||
// check to see if the current node has a parent. if so, grab the parent with the highest stage
|
||||
if let Some((max_parent_stage, max_parent_job)) = node
|
||||
.edges
|
||||
.input_edges
|
||||
.iter()
|
||||
.map(|e| {
|
||||
node_stages_and_jobs
|
||||
.get(&e.get_output_node())
|
||||
.expect("Already checked that parents were visited.")
|
||||
})
|
||||
.max()
|
||||
{
|
||||
// count the number of parents that are in the highest stage
|
||||
let max_stage_parent_count = node
|
||||
.edges
|
||||
.input_edges
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
let (max_stage, _) = node_stages_and_jobs
|
||||
.get(&e.get_output_node())
|
||||
.expect("Already checked that parents were visited.");
|
||||
max_stage == max_parent_stage
|
||||
})
|
||||
.count();
|
||||
|
||||
// if the current node has more than one parent on the highest stage (aka requires
|
||||
// synchronization), then move it to the next stage and start a new job on that
|
||||
// stage
|
||||
if max_stage_parent_count > 1 {
|
||||
stage_index = max_parent_stage + 1;
|
||||
} else {
|
||||
stage_index = *max_parent_stage;
|
||||
job_index = Some(*max_parent_job);
|
||||
}
|
||||
}
|
||||
|
||||
if stage_index == stages.len() {
|
||||
stages.push(Stage::default());
|
||||
}
|
||||
|
||||
let stage = &mut stages[stage_index];
|
||||
|
||||
let job_index = job_index.unwrap_or_else(|| stage.jobs.len());
|
||||
if job_index == stage.jobs.len() {
|
||||
stage.jobs.push(OrderedJob::default());
|
||||
}
|
||||
|
||||
let job = &mut stage.jobs[job_index];
|
||||
job.nodes.push(node.id);
|
||||
|
||||
node_stages_and_jobs.insert(node.id, (stage_index, job_index));
|
||||
|
||||
for (_edge, node) in graph.iter_node_outputs(node.id).unwrap() {
|
||||
stage_node(graph, stages, node_stages_and_jobs, node, job_grouping);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::renderer::RenderContext;
|
||||
use crate::{
|
||||
render_graph::{
|
||||
DependentNodeStager, Node, NodeId, OrderedJob, RenderGraph, RenderGraphStager,
|
||||
ResourceSlotInfo, ResourceSlots, Stage,
|
||||
},
|
||||
render_resource::RenderResourceType,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
|
||||
struct TestNode {
|
||||
inputs: Vec<ResourceSlotInfo>,
|
||||
outputs: Vec<ResourceSlotInfo>,
|
||||
}
|
||||
|
||||
impl TestNode {
|
||||
pub fn new(inputs: usize, outputs: usize) -> Self {
|
||||
TestNode {
|
||||
inputs: (0..inputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("in_{}", i).into(),
|
||||
resource_type: RenderResourceType::Texture,
|
||||
})
|
||||
.collect(),
|
||||
outputs: (0..outputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("out_{}", i).into(),
|
||||
resource_type: RenderResourceType::Texture,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for TestNode {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
&self.inputs
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
&self.outputs
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_: &World,
|
||||
_: &mut dyn RenderContext,
|
||||
_: &ResourceSlots,
|
||||
_: &mut ResourceSlots,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_graph_dependency_stager_loose() {
|
||||
let mut graph = RenderGraph::default();
|
||||
|
||||
// Setup graph to look like this:
|
||||
//
|
||||
// A -> B -> C -> D
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
let a_id = graph.add_node("A", TestNode::new(0, 1));
|
||||
let b_id = graph.add_node("B", TestNode::new(2, 1));
|
||||
let c_id = graph.add_node("C", TestNode::new(2, 1));
|
||||
let d_id = graph.add_node("D", TestNode::new(1, 0));
|
||||
let e_id = graph.add_node("E", TestNode::new(0, 1));
|
||||
let f_id = graph.add_node("F", TestNode::new(0, 2));
|
||||
let g_id = graph.add_node("G", TestNode::new(1, 0));
|
||||
let h_id = graph.add_node("H", TestNode::new(0, 1));
|
||||
let i_id = graph.add_node("I", TestNode::new(1, 1));
|
||||
let j_id = graph.add_node("J", TestNode::new(1, 0));
|
||||
|
||||
graph.add_node_edge("A", "B").unwrap();
|
||||
graph.add_node_edge("B", "C").unwrap();
|
||||
graph.add_node_edge("C", "D").unwrap();
|
||||
graph.add_node_edge("E", "B").unwrap();
|
||||
graph.add_node_edge("F", "C").unwrap();
|
||||
graph.add_node_edge("F", "G").unwrap();
|
||||
graph.add_node_edge("H", "I").unwrap();
|
||||
graph.add_node_edge("I", "J").unwrap();
|
||||
|
||||
let mut stager = DependentNodeStager::loose_grouping();
|
||||
let mut stages = stager.get_stages(&graph).unwrap();
|
||||
|
||||
// Expected Stages:
|
||||
// (X indicates nodes that are not part of that stage)
|
||||
|
||||
// Stage 1
|
||||
// A -> X -> X -> X
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
// Stage 2
|
||||
// X -> B -> C -> D
|
||||
// / /
|
||||
// X X -> X
|
||||
//
|
||||
// X -> X -> X
|
||||
|
||||
let mut expected_stages = vec![
|
||||
Stage {
|
||||
jobs: vec![
|
||||
OrderedJob {
|
||||
nodes: vec![f_id, g_id],
|
||||
},
|
||||
OrderedJob { nodes: vec![a_id] },
|
||||
OrderedJob { nodes: vec![e_id] },
|
||||
OrderedJob {
|
||||
nodes: vec![h_id, i_id, j_id],
|
||||
},
|
||||
],
|
||||
},
|
||||
Stage {
|
||||
jobs: vec![OrderedJob {
|
||||
nodes: vec![b_id, c_id, d_id],
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
// ensure job order lines up within stages (this can vary due to hash maps)
|
||||
// jobs within a stage are unordered conceptually so this is ok
|
||||
expected_stages
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0]));
|
||||
|
||||
stages
|
||||
.stages
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0]));
|
||||
|
||||
assert_eq!(
|
||||
stages.stages, expected_stages,
|
||||
"stages should be loosely grouped"
|
||||
);
|
||||
|
||||
let mut borrowed = stages.borrow(&mut graph);
|
||||
// ensure job order lines up within stages (this can vary due to hash maps)
|
||||
// jobs within a stage are unordered conceptually so this is ok
|
||||
borrowed
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.node_states[0].id));
|
||||
|
||||
assert_eq!(
|
||||
borrowed.len(),
|
||||
expected_stages.len(),
|
||||
"same number of stages"
|
||||
);
|
||||
for (stage_index, borrowed_stage) in borrowed.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_stage.jobs.len(),
|
||||
stages.stages[stage_index].jobs.len(),
|
||||
"job length matches"
|
||||
);
|
||||
for (job_index, borrowed_job) in borrowed_stage.jobs.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_job.node_states.len(),
|
||||
stages.stages[stage_index].jobs[job_index].nodes.len(),
|
||||
"node length matches"
|
||||
);
|
||||
for (node_index, borrowed_node) in borrowed_job.node_states.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_node.id,
|
||||
stages.stages[stage_index].jobs[job_index].nodes[node_index]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_graph_dependency_stager_tight() {
|
||||
let mut graph = RenderGraph::default();
|
||||
|
||||
// Setup graph to look like this:
|
||||
//
|
||||
// A -> B -> C -> D
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
let _a_id = graph.add_node("A", TestNode::new(0, 1));
|
||||
let b_id = graph.add_node("B", TestNode::new(2, 1));
|
||||
let c_id = graph.add_node("C", TestNode::new(2, 1));
|
||||
let d_id = graph.add_node("D", TestNode::new(1, 0));
|
||||
let _e_id = graph.add_node("E", TestNode::new(0, 1));
|
||||
let f_id = graph.add_node("F", TestNode::new(0, 2));
|
||||
let g_id = graph.add_node("G", TestNode::new(1, 0));
|
||||
let h_id = graph.add_node("H", TestNode::new(0, 1));
|
||||
let i_id = graph.add_node("I", TestNode::new(1, 1));
|
||||
let j_id = graph.add_node("J", TestNode::new(1, 0));
|
||||
|
||||
graph.add_node_edge("A", "B").unwrap();
|
||||
graph.add_node_edge("B", "C").unwrap();
|
||||
graph.add_node_edge("C", "D").unwrap();
|
||||
graph.add_node_edge("E", "B").unwrap();
|
||||
graph.add_node_edge("F", "C").unwrap();
|
||||
graph.add_node_edge("F", "G").unwrap();
|
||||
graph.add_node_edge("H", "I").unwrap();
|
||||
graph.add_node_edge("I", "J").unwrap();
|
||||
|
||||
let mut stager = DependentNodeStager::tight_grouping();
|
||||
let mut stages = stager.get_stages(&graph).unwrap();
|
||||
|
||||
// Expected Stages:
|
||||
// (X indicates nodes that are not part of that stage)
|
||||
|
||||
// Stage 1
|
||||
// A -> X -> X -> X
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
// Stage 2
|
||||
// X -> B -> C -> D
|
||||
// / /
|
||||
// X X -> X
|
||||
//
|
||||
// X -> X -> X
|
||||
|
||||
assert_eq!(stages.stages[0].jobs.len(), 1, "expect exactly 1 job");
|
||||
|
||||
let job = &stages.stages[0].jobs[0];
|
||||
|
||||
assert_eq!(job.nodes.len(), 7, "expect exactly 7 nodes in the job");
|
||||
|
||||
// its hard to test the exact order of this job's nodes because of hashing, so instead we'll
|
||||
// test the constraints that must hold true
|
||||
let index =
|
||||
|node_id: NodeId| -> usize { job.nodes.iter().position(|id| *id == node_id).unwrap() };
|
||||
|
||||
assert!(index(f_id) < index(g_id));
|
||||
assert!(index(h_id) < index(i_id));
|
||||
assert!(index(i_id) < index(j_id));
|
||||
|
||||
let expected_stage_1 = Stage {
|
||||
jobs: vec![OrderedJob {
|
||||
nodes: vec![b_id, c_id, d_id],
|
||||
}],
|
||||
};
|
||||
|
||||
assert_eq!(stages.stages[1], expected_stage_1,);
|
||||
|
||||
let mut borrowed = stages.borrow(&mut graph);
|
||||
// ensure job order lines up within stages (this can vary due to hash maps)
|
||||
// jobs within a stage are unordered conceptually so this is ok
|
||||
stages
|
||||
.stages
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0]));
|
||||
borrowed
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.node_states[0].id));
|
||||
|
||||
assert_eq!(borrowed.len(), 2, "same number of stages");
|
||||
for (stage_index, borrowed_stage) in borrowed.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_stage.jobs.len(),
|
||||
stages.stages[stage_index].jobs.len(),
|
||||
"job length matches"
|
||||
);
|
||||
for (job_index, borrowed_job) in borrowed_stage.jobs.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_job.node_states.len(),
|
||||
stages.stages[stage_index].jobs[job_index].nodes.len(),
|
||||
"node length matches"
|
||||
);
|
||||
for (node_index, borrowed_node) in borrowed_job.node_states.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_node.id,
|
||||
stages.stages[stage_index].jobs[job_index].nodes[node_index]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
59
pipelined/bevy_render2/src/render_phase/draw.rs
Normal file
59
pipelined/bevy_render2/src/render_phase/draw.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use crate::render_phase::TrackedRenderPass;
|
||||
use bevy_ecs::{entity::Entity, 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(
|
||||
&mut self,
|
||||
world: &World,
|
||||
pass: &mut TrackedRenderPass,
|
||||
view: Entity,
|
||||
draw_key: usize,
|
||||
sort_key: usize,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct DrawFunctionId(usize);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DrawFunctionsInternal {
|
||||
pub draw_functions: Vec<Box<dyn Draw>>,
|
||||
pub indices: HashMap<TypeId, DrawFunctionId>,
|
||||
}
|
||||
|
||||
impl DrawFunctionsInternal {
|
||||
pub fn add<D: Draw>(&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);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DrawFunctions {
|
||||
internal: RwLock<DrawFunctionsInternal>,
|
||||
}
|
||||
|
||||
impl DrawFunctions {
|
||||
pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal> {
|
||||
self.internal.read()
|
||||
}
|
||||
|
||||
pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal> {
|
||||
self.internal.write()
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
use bevy_utils::tracing::debug;
|
||||
|
||||
use crate::{
|
||||
pass::RenderPass,
|
||||
pipeline::{BindGroupDescriptorId, IndexFormat, PipelineId},
|
||||
|
@ -102,6 +104,7 @@ impl<'a> TrackedRenderPass<'a> {
|
|||
}
|
||||
}
|
||||
pub fn set_pipeline(&mut self, pipeline: PipelineId) {
|
||||
debug!("set pipeline: {:?}", pipeline);
|
||||
if self.state.is_pipeline_set(pipeline) {
|
||||
return;
|
||||
}
|
||||
|
@ -120,7 +123,16 @@ impl<'a> TrackedRenderPass<'a> {
|
|||
.state
|
||||
.is_bind_group_set(index as usize, bind_group, dynamic_uniform_indices)
|
||||
{
|
||||
debug!(
|
||||
"set bind_group {} (already set): {:?} ({:?})",
|
||||
index, bind_group, dynamic_uniform_indices
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
debug!(
|
||||
"set bind_group {}: {:?} ({:?})",
|
||||
index, bind_group, dynamic_uniform_indices
|
||||
);
|
||||
}
|
||||
self.pass.set_bind_group(
|
||||
index as u32,
|
||||
|
@ -134,7 +146,13 @@ impl<'a> TrackedRenderPass<'a> {
|
|||
|
||||
pub fn set_vertex_buffer(&mut self, index: usize, buffer: BufferId, offset: u64) {
|
||||
if self.state.is_vertex_buffer_set(index, buffer, offset) {
|
||||
debug!(
|
||||
"set vertex buffer {} (already set): {:?} ({})",
|
||||
index, buffer, offset
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
debug!("set vertex buffer {}: {:?} ({})", index, buffer, offset);
|
||||
}
|
||||
self.pass.set_vertex_buffer(index as u32, buffer, offset);
|
||||
self.state.set_vertex_buffer(index, buffer, offset);
|
||||
|
@ -142,13 +160,20 @@ impl<'a> TrackedRenderPass<'a> {
|
|||
|
||||
pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) {
|
||||
if self.state.is_index_buffer_set(buffer, offset, index_format) {
|
||||
debug!("set index buffer (already set): {:?} ({})", buffer, offset);
|
||||
return;
|
||||
} else {
|
||||
debug!("set index buffer: {:?} ({})", buffer, offset);
|
||||
}
|
||||
self.pass.set_index_buffer(buffer, offset, index_format);
|
||||
self.state.set_index_buffer(buffer, offset, index_format);
|
||||
}
|
||||
|
||||
pub fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
|
||||
debug!(
|
||||
"draw indexed: {:?} {} {:?}",
|
||||
indices, base_vertex, instances
|
||||
);
|
||||
self.pass.draw_indexed(indices, base_vertex, instances);
|
||||
}
|
||||
}
|
47
pipelined/bevy_render2/src/render_phase/mod.rs
Normal file
47
pipelined/bevy_render2/src/render_phase/mod.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
mod draw;
|
||||
mod draw_state;
|
||||
|
||||
pub use draw::*;
|
||||
pub use draw_state::*;
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use bevy_ecs::prelude::Query;
|
||||
|
||||
// TODO: make this configurable per phase?
|
||||
pub struct Drawable {
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub draw_key: usize,
|
||||
pub sort_key: usize,
|
||||
}
|
||||
|
||||
pub struct RenderPhase<T> {
|
||||
pub drawn_things: Vec<Drawable>,
|
||||
marker: PhantomData<fn() -> T>,
|
||||
}
|
||||
|
||||
impl<T> Default for RenderPhase<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
drawn_things: Vec::new(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RenderPhase<T> {
|
||||
#[inline]
|
||||
pub fn add(&mut self, drawable: Drawable) {
|
||||
self.drawn_things.push(drawable);
|
||||
}
|
||||
|
||||
pub fn sort(&mut self) {
|
||||
self.drawn_things.sort_by_key(|d| d.sort_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn sort_phase_system<T: 'static>(mut render_phases: Query<&mut RenderPhase<T>>) {
|
||||
for mut phase in render_phases.iter_mut() {
|
||||
phase.sort();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use crate::render_resource::RenderResourceId;
|
||||
|
||||
use super::{BufferId, RenderResourceBinding, SamplerId, TextureId};
|
||||
use crate::render_resource::{
|
||||
BufferId, RenderResourceBinding, RenderResourceId, SamplerId, TextureViewId,
|
||||
};
|
||||
use bevy_utils::AHasher;
|
||||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
|
@ -21,7 +21,6 @@ pub struct IndexedBindGroupEntry {
|
|||
pub struct BindGroup {
|
||||
pub id: BindGroupId,
|
||||
pub indexed_bindings: Arc<[IndexedBindGroupEntry]>,
|
||||
pub dynamic_uniform_indices: Option<Arc<[u32]>>,
|
||||
}
|
||||
|
||||
impl BindGroup {
|
||||
|
@ -33,21 +32,12 @@ impl BindGroup {
|
|||
#[derive(Debug, Default)]
|
||||
pub struct BindGroupBuilder {
|
||||
pub indexed_bindings: Vec<IndexedBindGroupEntry>,
|
||||
pub dynamic_uniform_indices: Vec<u32>,
|
||||
pub hasher: AHasher,
|
||||
}
|
||||
|
||||
impl BindGroupBuilder {
|
||||
pub fn add_binding<T: Into<RenderResourceBinding>>(mut self, index: u32, binding: T) -> Self {
|
||||
let binding = binding.into();
|
||||
if let RenderResourceBinding::Buffer {
|
||||
dynamic_index: Some(dynamic_index),
|
||||
..
|
||||
} = binding
|
||||
{
|
||||
self.dynamic_uniform_indices.push(dynamic_index);
|
||||
}
|
||||
|
||||
self.hash_binding(&binding);
|
||||
self.indexed_bindings.push(IndexedBindGroupEntry {
|
||||
index,
|
||||
|
@ -56,8 +46,8 @@ impl BindGroupBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn add_texture(self, index: u32, texture: TextureId) -> Self {
|
||||
self.add_binding(index, RenderResourceBinding::Texture(texture))
|
||||
pub fn add_texture_view(self, index: u32, texture: TextureViewId) -> Self {
|
||||
self.add_binding(index, RenderResourceBinding::TextureView(texture))
|
||||
}
|
||||
|
||||
pub fn add_sampler(self, index: u32, sampler: SamplerId) -> Self {
|
||||
|
@ -70,24 +60,6 @@ impl BindGroupBuilder {
|
|||
RenderResourceBinding::Buffer {
|
||||
buffer,
|
||||
range,
|
||||
dynamic_index: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_dynamic_buffer(
|
||||
self,
|
||||
index: u32,
|
||||
buffer: BufferId,
|
||||
range: Range<u64>,
|
||||
dynamic_index: u32,
|
||||
) -> Self {
|
||||
self.add_binding(
|
||||
index,
|
||||
RenderResourceBinding::Buffer {
|
||||
buffer,
|
||||
range,
|
||||
dynamic_index: Some(dynamic_index),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -98,11 +70,6 @@ impl BindGroupBuilder {
|
|||
BindGroup {
|
||||
id: BindGroupId(self.hasher.finish()),
|
||||
indexed_bindings: self.indexed_bindings.into(),
|
||||
dynamic_uniform_indices: if self.dynamic_uniform_indices.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.dynamic_uniform_indices.into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,13 +78,12 @@ impl BindGroupBuilder {
|
|||
RenderResourceBinding::Buffer {
|
||||
buffer,
|
||||
range,
|
||||
dynamic_index: _, // dynamic_index is not a part of the binding
|
||||
} => {
|
||||
RenderResourceId::Buffer(*buffer).hash(&mut self.hasher);
|
||||
range.hash(&mut self.hasher);
|
||||
}
|
||||
RenderResourceBinding::Texture(texture) => {
|
||||
RenderResourceId::Texture(*texture).hash(&mut self.hasher);
|
||||
RenderResourceBinding::TextureView(texture) => {
|
||||
RenderResourceId::TextureView(*texture).hash(&mut self.hasher);
|
||||
}
|
||||
RenderResourceBinding::Sampler(sampler) => {
|
||||
RenderResourceId::Sampler(*sampler).hash(&mut self.hasher);
|
||||
|
|
|
@ -106,7 +106,7 @@ impl<T: Pod> BufferVec<T> {
|
|||
render_resources.unmap_buffer(staging_buffer);
|
||||
}
|
||||
}
|
||||
pub fn write_to_uniform_buffer(&self, render_context: &mut dyn RenderContext) {
|
||||
pub fn write_to_buffer(&self, render_context: &mut dyn RenderContext) {
|
||||
if let (Some(staging_buffer), Some(uniform_buffer)) = (self.staging_buffer, self.buffer) {
|
||||
render_context.copy_buffer_to_buffer(
|
||||
staging_buffer,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{BufferId, SamplerId, TextureId};
|
||||
use crate::render_resource::{BufferId, SamplerId, TextureViewId};
|
||||
use std::ops::Range;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -6,15 +6,14 @@ pub enum RenderResourceBinding {
|
|||
Buffer {
|
||||
buffer: BufferId,
|
||||
range: Range<u64>,
|
||||
dynamic_index: Option<u32>,
|
||||
},
|
||||
Texture(TextureId),
|
||||
TextureView(TextureViewId),
|
||||
Sampler(SamplerId),
|
||||
}
|
||||
|
||||
impl RenderResourceBinding {
|
||||
pub fn get_texture(&self) -> Option<TextureId> {
|
||||
if let RenderResourceBinding::Texture(texture) = self {
|
||||
pub fn get_texture_view(&self) -> Option<TextureViewId> {
|
||||
if let RenderResourceBinding::TextureView(texture) = self {
|
||||
Some(*texture)
|
||||
} else {
|
||||
None
|
||||
|
@ -29,16 +28,6 @@ impl RenderResourceBinding {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_dynamic_buffer(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
RenderResourceBinding::Buffer {
|
||||
dynamic_index: Some(_),
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_sampler(&self) -> Option<SamplerId> {
|
||||
if let RenderResourceBinding::Sampler(sampler) = self {
|
||||
Some(*sampler)
|
||||
|
@ -48,9 +37,9 @@ impl RenderResourceBinding {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TextureId> for RenderResourceBinding {
|
||||
fn from(id: TextureId) -> Self {
|
||||
RenderResourceBinding::Texture(id)
|
||||
impl From<TextureViewId> for RenderResourceBinding {
|
||||
fn from(id: TextureViewId) -> Self {
|
||||
RenderResourceBinding::TextureView(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
use crate::render_resource::{BufferId, SamplerId, TextureId};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum RenderResourceType {
|
||||
Buffer,
|
||||
Texture,
|
||||
Sampler,
|
||||
}
|
||||
use crate::render_resource::{BufferId, SamplerId, TextureViewId};
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub enum RenderResourceId {
|
||||
Buffer(BufferId),
|
||||
Texture(TextureId),
|
||||
TextureView(TextureViewId),
|
||||
Sampler(SamplerId),
|
||||
}
|
||||
|
||||
impl RenderResourceId {
|
||||
pub fn get_texture(&self) -> Option<TextureId> {
|
||||
if let RenderResourceId::Texture(id) = self {
|
||||
pub fn get_texture_view(&self) -> Option<TextureViewId> {
|
||||
if let RenderResourceId::TextureView(id) = self {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -10,6 +10,16 @@ impl TextureId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct TextureViewId(Uuid);
|
||||
|
||||
impl TextureViewId {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
TextureViewId(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct SamplerId(Uuid);
|
||||
|
||||
|
|
|
@ -41,15 +41,18 @@ impl<T: AsStd140> UniformVec<T> {
|
|||
self.capacity
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> RenderResourceBinding {
|
||||
if self.values.len() < self.capacity {
|
||||
let binding = RenderResourceBinding::Buffer {
|
||||
pub fn binding(&self) -> RenderResourceBinding {
|
||||
RenderResourceBinding::Buffer {
|
||||
buffer: self.uniform_buffer.unwrap(),
|
||||
dynamic_index: Some((self.values.len() * self.item_size) as u32),
|
||||
range: 0..self.item_size as u64,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> usize {
|
||||
let len = self.values.len();
|
||||
if len < self.capacity {
|
||||
self.values.push(value);
|
||||
binding
|
||||
len
|
||||
} else {
|
||||
panic!(
|
||||
"Cannot push value because capacity of {} has been reached",
|
||||
|
@ -145,14 +148,19 @@ impl<T: AsStd140> DynamicUniformVec<T> {
|
|||
self.uniform_vec.uniform_buffer()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn binding(&self) -> RenderResourceBinding {
|
||||
self.uniform_vec.binding()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.uniform_vec.capacity()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push(&mut self, value: T) -> RenderResourceBinding {
|
||||
self.uniform_vec.push(DynamicUniform(value))
|
||||
pub fn push(&mut self, value: T) -> u32 {
|
||||
(self.uniform_vec.push(DynamicUniform(value)) * self.uniform_vec.item_size) as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use super::RenderResourceContext;
|
||||
use crate::{
|
||||
pipeline::{BindGroupDescriptorId, ComputePipelineDescriptor, RenderPipelineDescriptor, PipelineId},
|
||||
pipeline::{
|
||||
BindGroupDescriptorId, ComputePipelineDescriptor, PipelineId, RenderPipelineDescriptor,
|
||||
},
|
||||
render_resource::{
|
||||
BindGroup, BufferId, BufferInfo, BufferMapMode, SamplerId, SwapChainDescriptor, TextureId,
|
||||
TextureViewId,
|
||||
},
|
||||
renderer::RenderResourceContext,
|
||||
shader::{Shader, ShaderId},
|
||||
texture::{SamplerDescriptor, TextureDescriptor},
|
||||
texture::{SamplerDescriptor, TextureDescriptor, TextureViewDescriptor},
|
||||
};
|
||||
use bevy_utils::HashMap;
|
||||
use bevy_window::Window;
|
||||
use parking_lot::RwLock;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
|
@ -16,6 +18,7 @@ use std::{ops::Range, sync::Arc};
|
|||
pub struct HeadlessRenderResourceContext {
|
||||
buffer_info: Arc<RwLock<HashMap<BufferId, BufferInfo>>>,
|
||||
texture_descriptors: Arc<RwLock<HashMap<TextureId, TextureDescriptor>>>,
|
||||
texture_view_descriptors: Arc<RwLock<HashMap<TextureViewId, TextureViewDescriptor>>>,
|
||||
}
|
||||
|
||||
impl HeadlessRenderResourceContext {
|
||||
|
@ -29,9 +32,7 @@ impl HeadlessRenderResourceContext {
|
|||
}
|
||||
|
||||
impl RenderResourceContext for HeadlessRenderResourceContext {
|
||||
fn create_swap_chain(&self, _window: &Window) {}
|
||||
|
||||
fn drop_swap_chain_texture(&self, _render_resource: TextureId) {}
|
||||
fn drop_swap_chain_texture(&self, _texture_view: TextureViewId) {}
|
||||
|
||||
fn drop_all_swap_chain_textures(&self) {}
|
||||
|
||||
|
@ -45,6 +46,18 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
|
|||
texture
|
||||
}
|
||||
|
||||
fn create_texture_view(
|
||||
&self,
|
||||
_texture_id: TextureId,
|
||||
texture_view_descriptor: TextureViewDescriptor,
|
||||
) -> TextureViewId {
|
||||
let texture_view_id = TextureViewId::new();
|
||||
self.texture_view_descriptors
|
||||
.write()
|
||||
.insert(texture_view_id, texture_view_descriptor);
|
||||
texture_view_id
|
||||
}
|
||||
|
||||
fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId {
|
||||
let buffer = BufferId::new();
|
||||
self.add_buffer_info(buffer, buffer_info);
|
||||
|
@ -97,7 +110,14 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
|
|||
|
||||
fn remove_sampler(&self, _sampler: SamplerId) {}
|
||||
|
||||
fn create_render_pipeline(&self, _pipeline_descriptor: &RenderPipelineDescriptor) -> PipelineId {
|
||||
fn remove_texture_view(&self, texture_view: TextureViewId) {
|
||||
self.texture_view_descriptors.write().remove(&texture_view);
|
||||
}
|
||||
|
||||
fn create_render_pipeline(
|
||||
&self,
|
||||
_pipeline_descriptor: &RenderPipelineDescriptor,
|
||||
) -> PipelineId {
|
||||
PipelineId::new()
|
||||
}
|
||||
|
||||
|
@ -138,7 +158,7 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
|
|||
|
||||
fn remove_stale_bind_groups(&self) {}
|
||||
|
||||
fn next_swap_chain_texture(&self, _descriptor: &SwapChainDescriptor) -> TextureId {
|
||||
TextureId::new()
|
||||
fn next_swap_chain_texture(&self, _descriptor: &SwapChainDescriptor) -> TextureViewId {
|
||||
TextureViewId::new()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use crate::{
|
||||
pipeline::{BindGroupDescriptorId, ComputePipelineDescriptor, RenderPipelineDescriptor, PipelineId},
|
||||
pipeline::{
|
||||
BindGroupDescriptorId, ComputePipelineDescriptor, PipelineId, RenderPipelineDescriptor,
|
||||
},
|
||||
render_resource::{
|
||||
BindGroup, BufferId, BufferInfo, BufferMapMode, SamplerId, SwapChainDescriptor, TextureId,
|
||||
TextureViewId,
|
||||
},
|
||||
shader::{Shader, ShaderId},
|
||||
texture::{SamplerDescriptor, TextureDescriptor},
|
||||
texture::{SamplerDescriptor, TextureDescriptor, TextureViewDescriptor},
|
||||
};
|
||||
use bevy_window::Window;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use std::ops::{Deref, DerefMut, Range};
|
||||
|
||||
|
@ -33,13 +35,16 @@ impl DerefMut for RenderResources {
|
|||
}
|
||||
|
||||
pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
|
||||
// TODO: remove me
|
||||
fn create_swap_chain(&self, window: &Window);
|
||||
fn next_swap_chain_texture(&self, descriptor: &SwapChainDescriptor) -> TextureId;
|
||||
fn drop_swap_chain_texture(&self, resource: TextureId);
|
||||
fn next_swap_chain_texture(&self, descriptor: &SwapChainDescriptor) -> TextureViewId;
|
||||
fn drop_swap_chain_texture(&self, resource: TextureViewId);
|
||||
fn drop_all_swap_chain_textures(&self);
|
||||
fn create_sampler(&self, sampler_descriptor: &SamplerDescriptor) -> SamplerId;
|
||||
fn create_texture(&self, texture_descriptor: TextureDescriptor) -> TextureId;
|
||||
fn create_texture_view(
|
||||
&self,
|
||||
texture_id: TextureId,
|
||||
texture_view_descriptor: TextureViewDescriptor,
|
||||
) -> TextureViewId;
|
||||
fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId;
|
||||
// TODO: remove RenderResourceContext here
|
||||
fn write_mapped_buffer(
|
||||
|
@ -61,6 +66,7 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
|
|||
fn remove_buffer(&self, buffer: BufferId);
|
||||
fn remove_texture(&self, texture: TextureId);
|
||||
fn remove_sampler(&self, sampler: SamplerId);
|
||||
fn remove_texture_view(&self, texture_view: TextureViewId);
|
||||
fn get_buffer_info(&self, buffer: BufferId) -> Option<BufferInfo>;
|
||||
fn get_aligned_uniform_size(&self, size: usize, dynamic: bool) -> usize;
|
||||
fn get_aligned_texture_size(&self, data_size: usize) -> usize;
|
||||
|
|
|
@ -105,11 +105,24 @@ fn reflect_bind_group(
|
|||
}
|
||||
|
||||
fn reflect_dimension(type_description: &ReflectTypeDescription) -> TextureViewDimension {
|
||||
let arrayed = type_description.traits.image.arrayed > 0;
|
||||
match type_description.traits.image.dim {
|
||||
ReflectDimension::Type1d => TextureViewDimension::D1,
|
||||
ReflectDimension::Type2d => TextureViewDimension::D2,
|
||||
ReflectDimension::Type2d => {
|
||||
if arrayed {
|
||||
TextureViewDimension::D2Array
|
||||
} else {
|
||||
TextureViewDimension::D2
|
||||
}
|
||||
}
|
||||
ReflectDimension::Type3d => TextureViewDimension::D3,
|
||||
ReflectDimension::Cube => TextureViewDimension::Cube,
|
||||
ReflectDimension::Cube => {
|
||||
if arrayed {
|
||||
TextureViewDimension::CubeArray
|
||||
} else {
|
||||
TextureViewDimension::Cube
|
||||
}
|
||||
}
|
||||
dimension => panic!("Unsupported image dimension: {:?}.", dimension),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ mod image_texture_loader;
|
|||
mod sampler_descriptor;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod texture;
|
||||
mod texture_cache;
|
||||
mod texture_descriptor;
|
||||
mod texture_dimension;
|
||||
|
||||
|
@ -14,6 +15,7 @@ pub use hdr_texture_loader::*;
|
|||
pub use image_texture_loader::*;
|
||||
pub use sampler_descriptor::*;
|
||||
pub use texture::*;
|
||||
pub use texture_cache::*;
|
||||
pub use texture_descriptor::*;
|
||||
pub use texture_dimension::*;
|
||||
|
||||
|
@ -21,6 +23,7 @@ use crate::{
|
|||
render_command::RenderCommandQueue,
|
||||
render_resource::{BufferInfo, BufferUsage},
|
||||
renderer::{RenderResourceContext, RenderResources},
|
||||
RenderStage,
|
||||
};
|
||||
use bevy_app::{App, CoreStage, Plugin};
|
||||
use bevy_asset::{AddAsset, AssetEvent, Assets, Handle};
|
||||
|
@ -38,10 +41,14 @@ impl Plugin for TexturePlugin {
|
|||
|
||||
app.add_system_to_stage(CoreStage::PostUpdate, texture_resource_system.system())
|
||||
.add_asset::<Texture>();
|
||||
|
||||
let render_app = app.sub_app_mut(0);
|
||||
render_app
|
||||
.init_resource::<TextureCache>()
|
||||
.add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system.system());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove old system
|
||||
pub fn texture_resource_system(
|
||||
render_resource_context: Res<RenderResources>,
|
||||
mut render_command_queue: ResMut<RenderCommandQueue>,
|
||||
|
@ -57,7 +64,8 @@ pub fn texture_resource_system(
|
|||
}
|
||||
AssetEvent::Modified { handle } => {
|
||||
changed_textures.insert(handle);
|
||||
remove_current_texture_resources(render_resource_context, handle, &mut textures);
|
||||
// TODO: uncomment this to support mutated textures
|
||||
// remove_current_texture_resources(render_resource_context, handle, &mut textures);
|
||||
}
|
||||
AssetEvent::Removed { handle } => {
|
||||
remove_current_texture_resources(render_resource_context, handle, &mut textures);
|
||||
|
@ -109,9 +117,13 @@ pub fn texture_resource_system(
|
|||
},
|
||||
&aligned_data,
|
||||
);
|
||||
texture.gpu_data = Some(GpuData {
|
||||
texture_id,
|
||||
sampler_id,
|
||||
|
||||
let texture_view_id = render_resource_context
|
||||
.create_texture_view(texture_id, TextureViewDescriptor::default());
|
||||
texture.gpu_data = Some(TextureGpuData {
|
||||
texture: texture_id,
|
||||
texture_view: texture_view_id,
|
||||
sampler: sampler_id,
|
||||
});
|
||||
|
||||
render_command_queue.copy_buffer_to_texture(
|
||||
|
@ -134,7 +146,7 @@ fn remove_current_texture_resources(
|
|||
textures: &mut Assets<Texture>,
|
||||
) {
|
||||
if let Some(gpu_data) = textures.get_mut(handle).and_then(|t| t.gpu_data.take()) {
|
||||
render_resource_context.remove_texture(gpu_data.texture_id);
|
||||
render_resource_context.remove_sampler(gpu_data.sampler_id);
|
||||
render_resource_context.remove_texture(gpu_data.texture);
|
||||
render_resource_context.remove_sampler(gpu_data.sampler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::{
|
|||
image_texture_conversion::image_to_texture, Extent3d, SamplerDescriptor, TextureDimension,
|
||||
TextureFormat,
|
||||
};
|
||||
use crate::render_resource::{SamplerId, TextureId};
|
||||
use crate::render_resource::{SamplerId, TextureId, TextureViewId};
|
||||
use bevy_reflect::TypeUuid;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -11,16 +11,17 @@ pub const SAMPLER_ASSET_INDEX: u64 = 1;
|
|||
|
||||
// TODO: this shouldn't live in the Texture type
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GpuData {
|
||||
pub texture_id: TextureId,
|
||||
pub sampler_id: SamplerId,
|
||||
pub struct TextureGpuData {
|
||||
pub texture: TextureId,
|
||||
pub texture_view: TextureViewId,
|
||||
pub sampler: SamplerId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TypeUuid)]
|
||||
#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"]
|
||||
pub struct Texture {
|
||||
pub data: Vec<u8>,
|
||||
pub gpu_data: Option<GpuData>,
|
||||
pub gpu_data: Option<TextureGpuData>,
|
||||
pub size: Extent3d,
|
||||
pub format: TextureFormat,
|
||||
pub dimension: TextureDimension,
|
||||
|
|
101
pipelined/bevy_render2/src/texture/texture_cache.rs
Normal file
101
pipelined/bevy_render2/src/texture/texture_cache.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use crate::{
|
||||
render_resource::{TextureId, TextureViewId},
|
||||
renderer::RenderResources,
|
||||
texture::{TextureDescriptor, TextureViewDescriptor},
|
||||
};
|
||||
use bevy_ecs::prelude::{Res, ResMut};
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
struct CachedTextureMeta {
|
||||
texture: TextureId,
|
||||
default_view: TextureViewId,
|
||||
taken: bool,
|
||||
frames_since_last_use: usize,
|
||||
}
|
||||
|
||||
pub struct CachedTexture {
|
||||
pub texture: TextureId,
|
||||
pub default_view: TextureViewId,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TextureCache {
|
||||
textures: HashMap<TextureDescriptor, Vec<CachedTextureMeta>>,
|
||||
}
|
||||
|
||||
impl TextureCache {
|
||||
pub fn get(
|
||||
&mut self,
|
||||
render_resources: &RenderResources,
|
||||
descriptor: TextureDescriptor,
|
||||
) -> CachedTexture {
|
||||
match self.textures.entry(descriptor) {
|
||||
std::collections::hash_map::Entry::Occupied(mut entry) => {
|
||||
for texture in entry.get_mut().iter_mut() {
|
||||
if !texture.taken {
|
||||
texture.frames_since_last_use = 0;
|
||||
texture.taken = true;
|
||||
return CachedTexture {
|
||||
texture: texture.texture,
|
||||
default_view: texture.default_view,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let texture_id = render_resources.create_texture(entry.key().clone());
|
||||
let view_id = render_resources
|
||||
.create_texture_view(texture_id, TextureViewDescriptor::default());
|
||||
entry.get_mut().push(CachedTextureMeta {
|
||||
texture: texture_id,
|
||||
default_view: view_id,
|
||||
frames_since_last_use: 0,
|
||||
taken: true,
|
||||
});
|
||||
CachedTexture {
|
||||
texture: texture_id,
|
||||
default_view: view_id,
|
||||
}
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||
let texture_id = render_resources.create_texture(entry.key().clone());
|
||||
let view_id = render_resources
|
||||
.create_texture_view(texture_id, TextureViewDescriptor::default());
|
||||
entry.insert(vec![CachedTextureMeta {
|
||||
texture: texture_id,
|
||||
default_view: view_id,
|
||||
taken: true,
|
||||
frames_since_last_use: 0,
|
||||
}]);
|
||||
CachedTexture {
|
||||
texture: texture_id,
|
||||
default_view: view_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, render_resources: &RenderResources) {
|
||||
for textures in self.textures.values_mut() {
|
||||
for texture in textures.iter_mut() {
|
||||
texture.frames_since_last_use += 1;
|
||||
texture.taken = false;
|
||||
}
|
||||
|
||||
textures.retain(|texture| {
|
||||
let should_keep = texture.frames_since_last_use < 3;
|
||||
if !should_keep {
|
||||
render_resources.remove_texture_view(texture.default_view);
|
||||
render_resources.remove_texture(texture.texture);
|
||||
}
|
||||
should_keep
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_texture_cache_system(
|
||||
mut texture_cache: ResMut<TextureCache>,
|
||||
render_resources: Res<RenderResources>,
|
||||
) {
|
||||
texture_cache.update(&render_resources);
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
use std::num::NonZeroU32;
|
||||
|
||||
use crate::texture::TextureViewDimension;
|
||||
|
||||
use super::{Extent3d, Texture, TextureDimension, TextureFormat, TextureUsage};
|
||||
|
||||
/// Describes a texture
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct TextureDescriptor {
|
||||
pub size: Extent3d,
|
||||
pub mip_level_count: u32,
|
||||
|
@ -67,3 +71,42 @@ pub enum StorageTextureAccess {
|
|||
/// ```
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum TextureAspect {
|
||||
/// Depth, Stencil, and Color.
|
||||
All,
|
||||
/// Stencil.
|
||||
StencilOnly,
|
||||
/// Depth.
|
||||
DepthOnly,
|
||||
}
|
||||
|
||||
impl Default for TextureAspect {
|
||||
fn default() -> Self {
|
||||
Self::All
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct TextureViewDescriptor {
|
||||
/// Format of the texture view. At this time, it must be the same as the underlying format of the texture.
|
||||
pub format: Option<TextureFormat>,
|
||||
/// The dimension of the texture view. For 1D textures, this must be `1D`. For 2D textures it must be one of
|
||||
/// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `3D`
|
||||
pub dimension: Option<TextureViewDimension>,
|
||||
/// Aspect of the texture. Color textures must be [`TextureAspect::All`].
|
||||
pub aspect: TextureAspect,
|
||||
/// Base mip level.
|
||||
pub base_mip_level: u32,
|
||||
/// Mip level count.
|
||||
/// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count.
|
||||
/// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total.
|
||||
pub level_count: Option<NonZeroU32>,
|
||||
/// Base array layer.
|
||||
pub base_array_layer: u32,
|
||||
/// Layer count.
|
||||
/// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count.
|
||||
/// If `None`, considered to include the rest of the array layers, but at least 1 in total.
|
||||
pub array_layer_count: Option<NonZeroU32>,
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue