SubGraphs, Views, Shadows, and more

This commit is contained in:
Carter Anderson 2021-06-01 19:59:17 -07:00
parent 9588bb3243
commit 3400fb4e61
117 changed files with 4715 additions and 1818 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -181,7 +181,7 @@ impl Column {
pub fn clear(&mut self) {
self.data.clear();
self.ticks.get_mut().clear();
self.ticks.clear();
}
#[inline]

View file

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

View file

@ -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,20 +318,25 @@ 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
// empty
let location = archetype.allocate(entity, table_row);
// SAFE: entity index was just allocated
self.entities
.meta
.get_unchecked_mut(entity.id() as usize)
.location = location;
EntityMut::new(self, entity, location)
}
// 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
// empty
let location = archetype.allocate(entity, table_row);
// SAFE: entity index was just allocated
self.entities
.meta
.get_unchecked_mut(entity.id() as usize)
.location = location;
EntityMut::new(self, entity, location)
}
/// Spawns a batch of entities with the same component [Bundle] type. Takes a given [Bundle]
@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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()
}
}

View file

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

View file

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

View file

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

View file

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

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

View 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, )*
}
}
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use std::mem::size_of;
use core::mem::size_of;
use crate::internal::align_offset;
use crate::std140::{AsStd140, Std140};

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use std::mem::size_of;
use core::mem::size_of;
use crate::internal::align_offset;
use crate::std430::{AsStd430, Std430};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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" }

View 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,
}

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

View 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,
}
}
}

View 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()
}
}
}

View 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")
}
}
}

View 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")
}
}
}

View 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 (α21) + 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);
}

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

View file

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

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

View file

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

View file

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

View file

@ -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(),
name: camera.name.clone(),
},));
}
}
}
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);
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(),
},
));
}
}
}
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 })
}

View 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(())
}
}

View 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(())
}
}

View 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(())
}
}

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,14 @@
mod conversions;
mod mesh_resource_provider;
use crate::pipeline::{
IndexFormat, InputStepMode, PrimitiveTopology, VertexAttribute, VertexBufferLayout,
VertexFormat,
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(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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,
},
}

View file

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

View file

@ -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")]

View file

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

View file

@ -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,
}
impl SlotInfo {
pub fn new(name: impl Into<Cow<'static, str>>, slot_type: SlotType) -> Self {
SlotInfo {
name: name.into(),
slot_type,
}
}
}
#[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()
}
pub fn get(&self, label: impl Into<SlotLabel>) -> Option<RenderResourceId> {
let slot = self.get_slot(label).unwrap();
slot.resource.clone()
#[inline]
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Result<&ResourceSlot, RenderGraphError> {
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,
}
}
}

View file

@ -1,3 +0,0 @@
mod window_swap_chain_node;
pub use window_swap_chain_node::*;

View file

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

View file

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

View 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()
}
}

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {
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 binding(&self) -> RenderResourceBinding {
RenderResourceBinding::Buffer {
buffer: self.uniform_buffer.unwrap(),
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]

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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