Compare commits
104 Commits
debug/sig-
...
main
Author | SHA1 | Date |
---|---|---|
SeanOMik | 62adcf2b50 | |
SeanOMik | 4816b7333e | |
SeanOMik | 5542467d7e | |
SeanOMik | 3ce9ab6fb3 | |
SeanOMik | f02d3c6b2f | |
SeanOMik | 7ae0eae6ac | |
SeanOMik | fae2cdfadc | |
SeanOMik | 076676e486 | |
SeanOMik | 0e613bd216 | |
SeanOMik | 964c4ec423 | |
SeanOMik | 23a215ba46 | |
SeanOMik | 42112c2cf1 | |
SeanOMik | f2ff2a9855 | |
SeanOMik | 7c2efe3c6f | |
SeanOMik | cb3c3a601f | |
SeanOMik | 8072ec1c7e | |
SeanOMik | 4dbd96832f | |
SeanOMik | 2e33de5da2 | |
SeanOMik | 74465ce614 | |
SeanOMik | 380b15e560 | |
SeanOMik | 2ffdd4085b | |
SeanOMik | 156cbf25a4 | |
SeanOMik | b2d259ac71 | |
SeanOMik | d001e136d0 | |
SeanOMik | d0e6fc6ecd | |
SeanOMik | 6a47cd2671 | |
SeanOMik | 8e56ee1f0f | |
SeanOMik | 9e9478966b | |
SeanOMik | 624cd5362f | |
SeanOMik | eff6b221e0 | |
SeanOMik | 77ec620adb | |
SeanOMik | 6f65e2ce35 | |
SeanOMik | b90e19161d | |
SeanOMik | e9cbb48653 | |
SeanOMik | 49dfb38da3 | |
SeanOMik | 140ca506d6 | |
SeanOMik | 06a4301c23 | |
SeanOMik | de14b6211b | |
SeanOMik | a2c52a0bb8 | |
SeanOMik | 76b7cac699 | |
SeanOMik | 64099f598c | |
SeanOMik | 958c86cf73 | |
SeanOMik | ef2b0bf326 | |
SeanOMik | fa22a0310c | |
SeanOMik | 02f0c93aa2 | |
SeanOMik | 8fb686b7fe | |
SeanOMik | 798719a7a2 | |
SeanOMik | d6d6b2df72 | |
SeanOMik | f5aca87ede | |
SeanOMik | 9b1cc8c364 | |
SeanOMik | 9125b91977 | |
SeanOMik | eb43fad6c7 | |
SeanOMik | 7219013593 | |
SeanOMik | 33ddf689be | |
SeanOMik | 393b4206d3 | |
SeanOMik | 782d64f6cf | |
SeanOMik | 2107b8f7b0 | |
SeanOMik | 8b1077cab7 | |
SeanOMik | 45fd190409 | |
SeanOMik | 2b44d7f354 | |
SeanOMik | 60c139f9b2 | |
SeanOMik | 256025849e | |
SeanOMik | 8545e7e27d | |
SeanOMik | a85178eeea | |
SeanOMik | 8c1738334c | |
SeanOMik | fefcf58765 | |
SeanOMik | b0a6d30afc | |
SeanOMik | fef709d5f1 | |
SeanOMik | c91ee67961 | |
SeanOMik | c961568b96 | |
SeanOMik | 54b47c2178 | |
SeanOMik | 4449172c2b | |
SeanOMik | 4c6c6c4dd5 | |
SeanOMik | 27bc88c5a7 | |
SeanOMik | ff06bd55f3 | |
SeanOMik | d02258224a | |
SeanOMik | b45c2f4fab | |
SeanOMik | 40fa9c09da | |
SeanOMik | 87aa440691 | |
SeanOMik | cc1c482c40 | |
SeanOMik | a4ce4cb432 | |
SeanOMik | e2b554b4ef | |
SeanOMik | 6d57b40629 | |
SeanOMik | fd65f754cf | |
SeanOMik | 6c6893149a | |
SeanOMik | 1c649b2eb6 | |
SeanOMik | 7b2d2424a3 | |
SeanOMik | e8974bbd44 | |
SeanOMik | 3a80c069c9 | |
SeanOMik | 7ff67a194b | |
SeanOMik | c4aebdb25d | |
SeanOMik | 5ebbec8cf9 | |
SeanOMik | 2eeca335e2 | |
SeanOMik | 96dea5b1f9 | |
SeanOMik | 007b1047ef | |
SeanOMik | 545da71cda | |
SeanOMik | 0e71c5734f | |
SeanOMik | 6c1bff5768 | |
SeanOMik | 5f1a61ef52 | |
SeanOMik | f755a4c53b | |
SeanOMik | 9d3de88c50 | |
SeanOMik | f440f306be | |
SeanOMik | f17bf40c77 | |
SeanOMik | 5fc1a0f134 |
|
@ -0,0 +1,36 @@
|
|||
name: CI
|
||||
|
||||
env:
|
||||
# Runners don't expose the TSC but we want to make sure these tests work, so we
|
||||
# can ignore it.
|
||||
TRACY_NO_INVARIANT_CHECK: 1
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
container: git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
apt update
|
||||
apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo test --all
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "lyra-scripting/elua"]
|
||||
path = lyra-scripting/elua
|
||||
path = crates/lyra-scripting/elua
|
||||
url = ../elua.git # git@git.seanomik.net:SeanOMik/elua.git
|
||||
[submodule "wgsl-preprocessor"]
|
||||
path = crates/wgsl-preprocessor
|
||||
url = git@git.seanomik.net:SeanOMik/wgsl-preprocessor.git
|
||||
|
|
|
@ -4,6 +4,24 @@
|
|||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug lyra lua-scripting",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path", "${workspaceFolder}/examples/lua-scripting/Cargo.toml"
|
||||
//"--bin=testbed",
|
||||
],
|
||||
"filter": {
|
||||
"name": "lua-scripting",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/lua-scripting"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
@ -22,6 +40,24 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/testbed"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug lyra shadows",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path", "${workspaceFolder}/examples/shadows/Cargo.toml"
|
||||
//"--bin=shadows",
|
||||
],
|
||||
"filter": {
|
||||
"name": "shadows",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/shadows"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
variables:
|
||||
- &rust_image 'git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm'
|
||||
|
||||
when:
|
||||
event: [push, manual, pull_request]
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
Build - Debug:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo build
|
||||
|
||||
Test - Debug:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo test --all
|
|
@ -1,20 +0,0 @@
|
|||
variables:
|
||||
- &rust_image 'git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm'
|
||||
|
||||
when:
|
||||
event: [release, pull_request, manual]
|
||||
|
||||
steps:
|
||||
Build - Release:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo build --release
|
||||
|
||||
Test - Release:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo test --all --release
|
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
26
Cargo.toml
|
@ -5,19 +5,15 @@ edition = "2021"
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"examples/testbed",
|
||||
"lyra-resource",
|
||||
"lyra-ecs",
|
||||
"lyra-reflect",
|
||||
"lyra-scripting",
|
||||
"lyra-game",
|
||||
"lyra-math",
|
||||
"lyra-scene",
|
||||
|
||||
"examples/many-lights",
|
||||
"crates/*",
|
||||
|
||||
"examples/2d",
|
||||
"examples/fixed-timestep-rotating-model",
|
||||
"examples/lua-scripting",
|
||||
"examples/simple_scene"
|
||||
"examples/many-lights",
|
||||
"examples/shadows",
|
||||
"examples/simple_scene",
|
||||
"examples/testbed",
|
||||
]
|
||||
|
||||
[features]
|
||||
|
@ -26,11 +22,11 @@ lua_scripting = ["scripting", "lyra-scripting/lua"]
|
|||
tracy = ["lyra-game/tracy"]
|
||||
|
||||
[dependencies]
|
||||
lyra-game = { path = "lyra-game" }
|
||||
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||
lyra-game = { path = "crates/lyra-game" }
|
||||
lyra-scripting = { path = "crates/lyra-scripting", optional = true }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
#[profile.dev]
|
||||
#opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
|
@ -1,6 +1,16 @@
|
|||
use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::DerefMut};
|
||||
use std::{
|
||||
alloc::{self, alloc, dealloc, Layout},
|
||||
cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
mem,
|
||||
ops::DerefMut,
|
||||
ptr::{self, NonNull},
|
||||
};
|
||||
|
||||
use crate::{bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entity, Tick};
|
||||
use crate::{
|
||||
bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entity,
|
||||
Tick,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentColumn {
|
||||
|
@ -32,8 +42,9 @@ impl ComponentColumn {
|
|||
pub unsafe fn alloc(component_layout: Layout, capacity: usize) -> NonNull<u8> {
|
||||
let new_layout = Layout::from_size_align(
|
||||
component_layout.size().checked_mul(capacity).unwrap(),
|
||||
component_layout.align()
|
||||
).unwrap();
|
||||
component_layout.align(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(data) = NonNull::new(alloc(new_layout)) {
|
||||
data
|
||||
|
@ -44,7 +55,7 @@ impl ComponentColumn {
|
|||
|
||||
pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self {
|
||||
let data = ComponentColumn::alloc(info.layout(), capacity);
|
||||
|
||||
|
||||
Self {
|
||||
data: RefCell::new(data),
|
||||
capacity,
|
||||
|
@ -55,7 +66,7 @@ impl ComponentColumn {
|
|||
}
|
||||
|
||||
/// Set a component from pointer at an entity index.
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
/// This column must have space to fit the component, if it does not have room it will panic.
|
||||
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||
|
@ -63,7 +74,7 @@ impl ComponentColumn {
|
|||
|
||||
let mut data = self.data.borrow_mut();
|
||||
let data = data.deref_mut();
|
||||
|
||||
|
||||
let size = self.info.layout().size();
|
||||
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||
|
@ -83,7 +94,7 @@ impl ComponentColumn {
|
|||
|
||||
let mut data = self.data.borrow_mut();
|
||||
let data = data.deref_mut();
|
||||
|
||||
|
||||
let size = self.info.layout().size();
|
||||
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||
|
@ -95,25 +106,25 @@ impl ComponentColumn {
|
|||
}
|
||||
|
||||
/// Get a component at an entities index.
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
///
|
||||
/// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB.
|
||||
pub unsafe fn get<T>(&self, entity_index: usize) -> Ref<T> {
|
||||
let data = self.data.borrow();
|
||||
|
||||
Ref::map(data, |data| {
|
||||
let ptr = NonNull::new_unchecked(data.as_ptr()
|
||||
.add(entity_index * self.info.layout().size()))
|
||||
.cast();
|
||||
let ptr =
|
||||
NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout().size()))
|
||||
.cast();
|
||||
&*ptr.as_ptr()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a mutable borrow to the component at an entities index, ticking the entity.
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
///
|
||||
/// This column must have the entity.
|
||||
pub unsafe fn get_mut<T>(&mut self, entity_index: usize, tick: &Tick) -> RefMut<T> {
|
||||
self.entity_ticks[entity_index].tick_to(tick);
|
||||
|
@ -121,22 +132,22 @@ impl ComponentColumn {
|
|||
let data = self.data.borrow_mut();
|
||||
|
||||
RefMut::map(data, |data| {
|
||||
let ptr = NonNull::new_unchecked(data.as_ptr()
|
||||
.add(entity_index * self.info.layout().size()))
|
||||
.cast();
|
||||
let ptr =
|
||||
NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout().size()))
|
||||
.cast();
|
||||
&mut *ptr.as_ptr()
|
||||
})
|
||||
}
|
||||
|
||||
/// Grow the column to fit `new_capacity` amount of components.
|
||||
///
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `new_capacity` - The new capacity of components that can fit in this column.
|
||||
///
|
||||
///
|
||||
/// Note: This does not modify the Tick of this column, since no components were actually modified.
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
///
|
||||
/// Will panic if `new_capacity` is less than the current capacity of the column.
|
||||
pub unsafe fn grow(&mut self, new_capacity: usize) {
|
||||
assert!(new_capacity > self.capacity);
|
||||
|
@ -155,7 +166,7 @@ impl ComponentColumn {
|
|||
// create a layout with the same alignment, but expand the size of the buffer.
|
||||
let old_layout = Layout::from_size_align_unchecked(
|
||||
layout.size().checked_mul(self.capacity).unwrap(),
|
||||
layout.align()
|
||||
layout.align(),
|
||||
);
|
||||
|
||||
mem::swap(data.deref_mut(), &mut new_ptr);
|
||||
|
@ -163,7 +174,7 @@ impl ComponentColumn {
|
|||
} else {
|
||||
*data = new_ptr;
|
||||
}
|
||||
|
||||
|
||||
self.capacity = new_capacity;
|
||||
}
|
||||
|
||||
|
@ -171,19 +182,20 @@ impl ComponentColumn {
|
|||
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
||||
let _ = tick; // may be used at some point
|
||||
|
||||
debug_assert!(self.len > 0, "There are no entities in the Archetype to remove from!");
|
||||
|
||||
debug_assert!(
|
||||
self.len > 0,
|
||||
"There are no entities in the Archetype to remove from!"
|
||||
);
|
||||
|
||||
let mut data = self.data.borrow_mut();
|
||||
let data = data.deref_mut();
|
||||
|
||||
let size = self.info.layout().size();
|
||||
let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr()
|
||||
.add(entity_index * size));
|
||||
let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||
|
||||
let moved_index = if entity_index != self.len - 1 {
|
||||
let moved_index = self.len - 1;
|
||||
let mut new_comp_ptr = NonNull::new_unchecked(data.as_ptr()
|
||||
.add(moved_index * size));
|
||||
let mut new_comp_ptr = NonNull::new_unchecked(data.as_ptr().add(moved_index * size));
|
||||
|
||||
ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), size);
|
||||
|
||||
|
@ -193,13 +205,43 @@ impl ComponentColumn {
|
|||
self.entity_ticks.swap_remove(entity_index);
|
||||
|
||||
Some(moved_index)
|
||||
} else { None };
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.len -= 1;
|
||||
|
||||
moved_index
|
||||
}
|
||||
|
||||
/// Get the pointer of the component for an entity.
|
||||
///
|
||||
/// It is assumed that the component will be mutated, meaning the component's tick will be
|
||||
/// updated.
|
||||
pub fn component_ptr(&mut self, entity_index: usize, tick: &Tick) -> NonNull<u8> {
|
||||
self.entity_ticks[entity_index] = *tick;
|
||||
let size = self.info.layout().size();
|
||||
unsafe { NonNull::new_unchecked(self.borrow_ptr().as_ptr().add(entity_index * size)) }
|
||||
}
|
||||
|
||||
/// Get the pointer of the component for an entity without ticking.
|
||||
///
|
||||
/// Since this does not tick, only use this if you know the pointer will not be mutated.
|
||||
pub fn component_ptr_non_tick(&self, entity_index: usize) -> NonNull<u8> {
|
||||
let size = self.info.layout().size();
|
||||
unsafe { NonNull::new_unchecked(self.borrow_ptr().as_ptr().add(entity_index * size)) }
|
||||
}
|
||||
|
||||
/// Get the tick of a component for an entity.
|
||||
pub fn component_tick(&self, entity_index: usize) -> Option<Tick> {
|
||||
self.entity_ticks.get(entity_index).cloned()
|
||||
}
|
||||
|
||||
pub fn component_has_changed(&self, entity_index: usize, world_tick: Tick) -> Option<bool> {
|
||||
self.component_tick(entity_index)
|
||||
.map(|tick| *tick >= *world_tick - 1)
|
||||
}
|
||||
|
||||
pub fn borrow_ptr(&self) -> Ref<NonNull<u8>> {
|
||||
self.data.borrow()
|
||||
}
|
||||
|
@ -226,13 +268,13 @@ impl ArchetypeId {
|
|||
pub(crate) fn increment(&mut self) -> Self {
|
||||
let v = self.0;
|
||||
self.0 += 1;
|
||||
|
||||
|
||||
ArchetypeId(v)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores a group of entities with matching components.
|
||||
///
|
||||
///
|
||||
/// An Archetype can be thought of as a table, with entities as the rows and the entity's
|
||||
/// components as each column. This means you can have tightly packed components of entities and
|
||||
/// quickly iterate through entities with the same components.
|
||||
|
@ -248,7 +290,7 @@ pub struct Archetype {
|
|||
/// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has
|
||||
/// the index that the entity is stored at.
|
||||
pub(crate) entities: Vec<Entity>,
|
||||
pub(crate) columns: Vec<ComponentColumn>,
|
||||
pub columns: Vec<ComponentColumn>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
|
@ -267,9 +309,10 @@ impl Archetype {
|
|||
}
|
||||
|
||||
pub fn from_bundle_info(new_id: ArchetypeId, bundle_info: Vec<ComponentInfo>) -> Archetype {
|
||||
let columns = bundle_info.into_iter().map(|i| {
|
||||
unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) }
|
||||
}).collect();
|
||||
let columns = bundle_info
|
||||
.into_iter()
|
||||
.map(|i| unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) })
|
||||
.collect();
|
||||
|
||||
Archetype {
|
||||
id: new_id,
|
||||
|
@ -281,13 +324,18 @@ impl Archetype {
|
|||
}
|
||||
|
||||
/// Add an entity and its component bundle to the Archetype
|
||||
///
|
||||
///
|
||||
/// # Safety:
|
||||
///
|
||||
///
|
||||
/// Archetype must contain all of the components
|
||||
pub(crate) fn add_entity<B>(&mut self, entity: Entity, bundle: B, tick: &Tick) -> ArchetypeEntityId
|
||||
pub(crate) fn add_entity<B>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
bundle: B,
|
||||
tick: &Tick,
|
||||
) -> ArchetypeEntityId
|
||||
where
|
||||
B: Bundle
|
||||
B: Bundle,
|
||||
{
|
||||
if self.capacity == self.entity_ids.len() {
|
||||
let new_cap = self.capacity * 2;
|
||||
|
@ -301,28 +349,49 @@ impl Archetype {
|
|||
self.entities.push(entity);
|
||||
|
||||
bundle.take(|data, type_id, info| {
|
||||
self.put_component_at(tick, data, type_id, info.layout().size(), entity_index.0 as _);
|
||||
self.put_component_at(
|
||||
tick,
|
||||
data,
|
||||
type_id,
|
||||
info.layout().size(),
|
||||
entity_index.0 as _,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
entity_index
|
||||
}
|
||||
|
||||
pub(crate) fn put_component_at(&mut self, tick: &Tick, ptr: NonNull<u8>, type_id: DynTypeId, size: usize, index: usize) {
|
||||
pub(crate) fn put_component_at(
|
||||
&mut self,
|
||||
tick: &Tick,
|
||||
ptr: NonNull<u8>,
|
||||
type_id: DynTypeId,
|
||||
size: usize,
|
||||
index: usize,
|
||||
) {
|
||||
let _ = size;
|
||||
|
||||
let col = self.get_column_mut(type_id).unwrap();
|
||||
//unsafe { col.set_at(index, ptr, *tick) };
|
||||
unsafe { col.insert_entity(index, ptr, *tick); }
|
||||
unsafe {
|
||||
col.insert_entity(index, ptr, *tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes an entity from the Archetype and frees its components.
|
||||
///
|
||||
///
|
||||
/// Inside the component columns, the entities are swap-removed. Meaning that the last
|
||||
/// entity in the column is moved in the position of the entity that was removed.
|
||||
/// If there was an entity that was swapped, this function returns the entity, and its
|
||||
/// new index in the archetype that was put in place of the removed entity.
|
||||
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
||||
let entity_index = self.entity_ids.remove(&entity)
|
||||
pub(crate) fn remove_entity(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
tick: &Tick,
|
||||
) -> Option<(Entity, ArchetypeEntityId)> {
|
||||
let entity_index = self
|
||||
.entity_ids
|
||||
.remove(&entity)
|
||||
.expect("The entity is not in this Archetype!");
|
||||
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
||||
|
||||
|
@ -339,19 +408,19 @@ impl Archetype {
|
|||
removed_entity = Some((just_removed, ArchetypeEntityId(moved_idx as u64)));
|
||||
}
|
||||
} else {
|
||||
// If there wasn't a moved entity, make sure no other columns moved something.
|
||||
// If there wasn't a moved entity, make sure no other columns moved something.
|
||||
assert!(removed_entity.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
// safe from the .expect at the start of this method.
|
||||
//self.entity_ids.remove(&entity).unwrap();
|
||||
|
||||
|
||||
// update the archetype index of the moved entity
|
||||
if let Some((moved, _old_idx)) = removed_entity {
|
||||
self.entity_ids.insert(moved, entity_index);
|
||||
}
|
||||
|
||||
|
||||
let removed = self.entities.swap_remove(entity_index.0 as _);
|
||||
assert_eq!(removed, entity);
|
||||
|
||||
|
@ -362,8 +431,12 @@ impl Archetype {
|
|||
/// Returns a boolean indicating whether this archetype can store the TypeIds given
|
||||
pub fn is_archetype_for(&self, types: &Vec<DynTypeId>) -> bool {
|
||||
if types.len() == self.columns.len() {
|
||||
self.columns.iter().all(|c| types.contains(&c.info.type_id()))
|
||||
} else { false }
|
||||
self.columns
|
||||
.iter()
|
||||
.all(|c| types.contains(&c.info.type_id()))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating whether this archetype has a column for `comp_type`
|
||||
|
@ -383,18 +456,20 @@ impl Archetype {
|
|||
}
|
||||
|
||||
/// Grows columns in the archetype
|
||||
///
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `new_capacity` - The new capacity of components that can fit in this column.
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
///
|
||||
/// Will panic if new_capacity is less than the current capacity
|
||||
fn grow_columns(&mut self, new_capacity: usize) {
|
||||
assert!(new_capacity > self.capacity);
|
||||
|
||||
for c in self.columns.iter_mut() {
|
||||
unsafe { c.grow(new_capacity); }
|
||||
unsafe {
|
||||
c.grow(new_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
self.capacity = new_capacity;
|
||||
|
@ -412,11 +487,16 @@ impl Archetype {
|
|||
}
|
||||
|
||||
/// Returns a mutable borrow to a component column for `type_id`.
|
||||
///
|
||||
///
|
||||
/// Note: This does not modify the tick for the column!
|
||||
pub fn get_column_mut<I: Into<DynTypeId>>(&mut self, type_id: I) -> Option<&mut ComponentColumn> {
|
||||
pub fn get_column_mut<I: Into<DynTypeId>>(
|
||||
&mut self,
|
||||
type_id: I,
|
||||
) -> Option<&mut ComponentColumn> {
|
||||
let type_id = type_id.into();
|
||||
self.columns.iter_mut().find(|c| c.info.type_id() == type_id)
|
||||
self.columns
|
||||
.iter_mut()
|
||||
.find(|c| c.info.type_id() == type_id)
|
||||
}
|
||||
|
||||
/// Reserves a slot in the columns for an entity and returns the index of that reserved spot
|
||||
|
@ -427,8 +507,11 @@ impl Archetype {
|
|||
self.capacity = new_cap;
|
||||
}
|
||||
|
||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||
"Somehow the Archetype's entity storage got unsynced");
|
||||
debug_assert_eq!(
|
||||
self.entity_ids.len(),
|
||||
self.entities.len(),
|
||||
"Somehow the Archetype's entity storage got unsynced"
|
||||
);
|
||||
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||
self.entity_ids.insert(entity, entity_index);
|
||||
self.entities.push(entity);
|
||||
|
@ -442,12 +525,15 @@ impl Archetype {
|
|||
|
||||
/// Ensure that the internal entity lists are synced in length
|
||||
pub(crate) fn ensure_synced(&self) {
|
||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||
"Somehow the Archetype's entity storage got unsynced");
|
||||
debug_assert_eq!(
|
||||
self.entity_ids.len(),
|
||||
self.entities.len(),
|
||||
"Somehow the Archetype's entity storage got unsynced"
|
||||
);
|
||||
}
|
||||
|
||||
/// Moves the entity from this archetype into another one.
|
||||
///
|
||||
///
|
||||
/// # Safety
|
||||
/// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype).
|
||||
/// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World)
|
||||
|
@ -463,7 +549,7 @@ impl Archetype {
|
|||
// move the existing components into the new archetype
|
||||
for col in self.columns.iter() {
|
||||
let into_col = into_arch.get_column_mut(col.info.type_id()).unwrap();
|
||||
|
||||
|
||||
// copy from the old column into the new column, then remove it from the old one
|
||||
unsafe {
|
||||
let ptr = col.borrow_ptr();
|
||||
|
@ -514,19 +600,20 @@ impl Archetype {
|
|||
}
|
||||
|
||||
/// Extend the Archetype by adding more columns.
|
||||
///
|
||||
///
|
||||
/// In order to extend the Archetype, the archetype needs the components for the entities
|
||||
/// it already has. These are provided through the `new_columns` parameter. **If the Vec
|
||||
/// does not have the same amount of bundles in it as the amount of entities in the
|
||||
/// Archetype, it will panic!**
|
||||
pub fn extend<B: Bundle>(&mut self, tick: &Tick, new_columns: Vec<B>) {
|
||||
debug_assert_eq!(new_columns.len(), self.len(), "The amount of provided column does not \
|
||||
match the amount of entities");
|
||||
|
||||
let column_info = new_columns.iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.info();
|
||||
debug_assert_eq!(
|
||||
new_columns.len(),
|
||||
self.len(),
|
||||
"The amount of provided column does not \
|
||||
match the amount of entities"
|
||||
);
|
||||
|
||||
let column_info = new_columns.iter().next().unwrap().info();
|
||||
|
||||
for coli in column_info.into_iter() {
|
||||
let col = unsafe { ComponentColumn::new(coli, self.capacity) };
|
||||
|
@ -534,11 +621,9 @@ impl Archetype {
|
|||
}
|
||||
|
||||
for (eid, bundle) in new_columns.into_iter().enumerate() {
|
||||
bundle.take(|ptr, tyid, _size| {
|
||||
unsafe {
|
||||
let col = self.get_column_mut(tyid).unwrap();
|
||||
col.insert_entity(eid, ptr, tick.clone());
|
||||
}
|
||||
bundle.take(|ptr, tyid, _size| unsafe {
|
||||
let col = self.get_column_mut(tyid).unwrap();
|
||||
col.insert_entity(eid, ptr, tick.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -550,7 +635,11 @@ mod tests {
|
|||
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick};
|
||||
use crate::{
|
||||
bundle::Bundle,
|
||||
tests::{Vec2, Vec3},
|
||||
ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick,
|
||||
};
|
||||
|
||||
use super::Archetype;
|
||||
|
||||
|
@ -559,7 +648,7 @@ mod tests {
|
|||
let bundle = (Vec2::new(10.0, 20.0),);
|
||||
let entity = Entity {
|
||||
id: EntityId(0),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
};
|
||||
|
||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info());
|
||||
|
@ -572,10 +661,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn one_entity_two_component() {
|
||||
let bundle = (Vec2::new(10.0, 20.0),Vec3::new(15.0, 54.0, 84.0));
|
||||
let bundle = (Vec2::new(10.0, 20.0), Vec3::new(15.0, 54.0, 84.0));
|
||||
let entity = Entity {
|
||||
id: EntityId(0),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
};
|
||||
|
||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info());
|
||||
|
@ -595,12 +684,12 @@ mod tests {
|
|||
let b1 = (Vec2::new(10.0, 20.0),);
|
||||
let e1 = Entity {
|
||||
id: EntityId(0),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
};
|
||||
let b2 = (Vec2::new(19.0, 43.0),);
|
||||
let e2 = Entity {
|
||||
id: EntityId(1),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
};
|
||||
|
||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info());
|
||||
|
@ -619,12 +708,12 @@ mod tests {
|
|||
let b1 = (Vec2::new(10.0, 20.0), Vec3::new(84.0, 283.0, 28.0));
|
||||
let e1 = Entity {
|
||||
id: EntityId(0),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
};
|
||||
let b2 = (Vec2::new(19.0, 43.0), Vec3::new(74.0, 28.0, 93.0));
|
||||
let e2 = Entity {
|
||||
id: EntityId(1),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
};
|
||||
|
||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info());
|
||||
|
@ -659,7 +748,7 @@ mod tests {
|
|||
fn auto_archetype_growth() {
|
||||
let mut rng = rand::thread_rng();
|
||||
let bundle_count = rng.gen_range(50..150);
|
||||
|
||||
|
||||
let mut bundles: Vec<(Vec2,)> = vec![];
|
||||
bundles.reserve(bundle_count);
|
||||
|
||||
|
@ -669,14 +758,14 @@ mod tests {
|
|||
for i in 0..bundle_count {
|
||||
let c = (Vec2::rand(),);
|
||||
bundles.push(c);
|
||||
|
||||
|
||||
a.add_entity(
|
||||
Entity {
|
||||
id: EntityId(i as u64),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
},
|
||||
c,
|
||||
&Tick::default()
|
||||
&Tick::default(),
|
||||
);
|
||||
}
|
||||
println!("Inserted {} entities", bundle_count);
|
||||
|
@ -701,25 +790,27 @@ mod tests {
|
|||
a.add_entity(
|
||||
Entity {
|
||||
id: EntityId(i as u64),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
},
|
||||
(bundles[i],),
|
||||
&Tick::default()
|
||||
&Tick::default(),
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the 'middle' entity in the column
|
||||
let moved_entity = a.remove_entity(
|
||||
Entity {
|
||||
id: EntityId(1u64),
|
||||
generation: 0,
|
||||
},
|
||||
&Tick::default()
|
||||
).expect("No entity was moved");
|
||||
let moved_entity = a
|
||||
.remove_entity(
|
||||
Entity {
|
||||
id: EntityId(1u64),
|
||||
generation: 0,
|
||||
},
|
||||
&Tick::default(),
|
||||
)
|
||||
.expect("No entity was moved");
|
||||
|
||||
// The last entity in the column should have been moved
|
||||
assert!(moved_entity.0.id.0 == 2);
|
||||
assert!(moved_entity.1.0 == 1);
|
||||
assert!(moved_entity.1 .0 == 1);
|
||||
|
||||
// make sure that the entities' component was actually moved in the column
|
||||
let col = &a.columns[0];
|
||||
|
@ -731,7 +822,8 @@ mod tests {
|
|||
#[test]
|
||||
fn dynamic_archetype() {
|
||||
let layout = Layout::new::<u32>();
|
||||
let info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout);
|
||||
let info =
|
||||
ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout);
|
||||
let infos = vec![info.clone()];
|
||||
|
||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), infos);
|
||||
|
@ -744,10 +836,10 @@ mod tests {
|
|||
a.add_entity(
|
||||
Entity {
|
||||
id: EntityId(0),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
},
|
||||
dynamic_bundle,
|
||||
&Tick::default()
|
||||
&Tick::default(),
|
||||
);
|
||||
|
||||
let col = a.columns.iter().next().unwrap();
|
||||
|
@ -764,15 +856,11 @@ mod tests {
|
|||
|
||||
let ae = Entity {
|
||||
id: EntityId(0),
|
||||
generation: 0
|
||||
generation: 0,
|
||||
};
|
||||
|
||||
a.add_entity(
|
||||
ae,
|
||||
Vec2::new(10.0, 50.0),
|
||||
&Tick::default()
|
||||
);
|
||||
a.add_entity(ae, Vec2::new(10.0, 50.0), &Tick::default());
|
||||
|
||||
a.remove_entity(ae, &Tick::default());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,13 +42,20 @@ impl DynTypeId {
|
|||
*self == id
|
||||
}
|
||||
|
||||
/// Force self into a rust TypeId, will panic if this type is not a Rust type.
|
||||
/// Force self into a rust TypeId, will panic if this type is not a Rust type.
|
||||
pub fn as_rust(&self) -> TypeId {
|
||||
match self {
|
||||
DynTypeId::Rust(t) => *t,
|
||||
DynTypeId::Unknown(_) => panic!("This type is unknown to rust, cannot construct a TypeId from it!"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_unknown(&self) -> Option<u128> {
|
||||
match self {
|
||||
DynTypeId::Rust(_) => None,
|
||||
DynTypeId::Unknown(id) => Some(*id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Some information about a component.
|
|
@ -12,6 +12,13 @@ pub struct Entity {
|
|||
}
|
||||
|
||||
impl Entity {
|
||||
pub fn new(id: EntityId, gen: u64) -> Self {
|
||||
Self {
|
||||
id,
|
||||
generation: gen,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> EntityId {
|
||||
self.id
|
||||
}
|
||||
|
@ -70,4 +77,4 @@ impl Entities {
|
|||
pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) {
|
||||
self.arch_index.insert(entity.id, record);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
|
||||
extern crate self as lyra_ecs;
|
||||
|
||||
#[allow(unused_imports)]
|
|
@ -5,6 +5,9 @@ use crate::{World, ComponentColumn, ComponentInfo};
|
|||
mod view;
|
||||
pub use view::*;
|
||||
|
||||
mod view_one;
|
||||
pub use view_one::*;
|
||||
|
||||
use super::Fetch;
|
||||
|
||||
/// Data that rust does not know the type of
|
|
@ -24,6 +24,10 @@ impl DynamicViewState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn queries_num(&self) -> usize {
|
||||
self.queries.len()
|
||||
}
|
||||
|
||||
pub fn push(&mut self, dyn_query: QueryDynamicType) {
|
||||
self.queries.push(dyn_query);
|
||||
}
|
||||
|
@ -57,11 +61,17 @@ pub struct DynamicViewStateIter {
|
|||
}
|
||||
|
||||
impl DynamicViewStateIter {
|
||||
pub fn next(&mut self, world: &World) -> Option<DynamicViewItem> {
|
||||
pub fn next(&mut self, world: &World) -> Option<(Entity, Vec<DynamicType>)> {
|
||||
let archetypes = world.archetypes.values().collect::<Vec<_>>();
|
||||
|
||||
loop {
|
||||
if let Some(entity_index) = self.component_indices.next() {
|
||||
let entity = {
|
||||
let arch_id = self.next_archetype - 1;
|
||||
let arch = unsafe { archetypes.get_unchecked(arch_id) };
|
||||
arch.entity_at_index(ArchetypeEntityId(entity_index)).unwrap()
|
||||
};
|
||||
|
||||
let mut fetch_res = vec![];
|
||||
|
||||
for fetcher in self.fetchers.iter_mut() {
|
||||
|
@ -78,11 +88,7 @@ impl DynamicViewStateIter {
|
|||
continue;
|
||||
}
|
||||
|
||||
let arch = archetypes.get(self.next_archetype-1).unwrap();
|
||||
return Some(DynamicViewItem {
|
||||
row: fetch_res,
|
||||
entity: arch.entity_at_index(ArchetypeEntityId(entity_index)).unwrap()
|
||||
})
|
||||
return Some((entity, fetch_res));
|
||||
} else {
|
||||
if self.next_archetype >= archetypes.len() {
|
||||
return None; // ran out of archetypes to go through
|
||||
|
@ -138,7 +144,7 @@ impl<'a> DynamicView<'a> {
|
|||
/// This works great for a embedding with a scripting language (*cough* *cough* WASM) since
|
||||
/// Rust doesn't actually need to know the types of what its iterating over.
|
||||
impl<'a> IntoIterator for DynamicView<'a> {
|
||||
type Item = DynamicViewItem;
|
||||
type Item = (Entity, Vec<DynamicType>);
|
||||
|
||||
type IntoIter = DynamicViewIter<'a>;
|
||||
|
||||
|
@ -160,7 +166,7 @@ pub struct DynamicViewIter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for DynamicViewIter<'a> {
|
||||
type Item = DynamicViewItem;
|
||||
type Item = (Entity, Vec<DynamicType>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next(&self.world)
|
||||
|
@ -193,13 +199,11 @@ mod tests {
|
|||
view.push(query);
|
||||
|
||||
let mut view_iter = view.into_iter();
|
||||
while let Some(view_row) = view_iter.next(&world) {
|
||||
assert_eq!(view_row.row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.row.iter();
|
||||
while let Some((_e, view_row)) = view_iter.next(&world) {
|
||||
assert_eq!(view_row.len(), 1);
|
||||
let mut row_iter = view_row.iter();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
||||
|
@ -222,13 +226,11 @@ mod tests {
|
|||
let mut view = DynamicView::new(&world);
|
||||
view.push(query);
|
||||
|
||||
for view_row in view.into_iter() {
|
||||
assert_eq!(view_row.row.len(), 1);
|
||||
for (_e, view_row) in view.into_iter() {
|
||||
assert_eq!(view_row.len(), 1);
|
||||
let mut row_iter = view_row.iter();
|
||||
|
||||
let mut row_iter = view_row.row.iter();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::{query::Fetch, Entity, World};
|
||||
|
||||
use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType};
|
||||
|
||||
/// A view of dynamic types (types that are not known to Rust).
|
||||
///
|
||||
/// This view gives you the ability to iterate over types that are unknown to Rust, which we call
|
||||
/// dynamic types. This is great for embedding with a scripting language (*cough* *cough* WASM)
|
||||
/// since Rust doesn't actually need to know the types of what its iterating over.
|
||||
pub struct DynamicViewOne<'a> {
|
||||
world: &'a World,
|
||||
inner: DynamicViewOneOwned,
|
||||
}
|
||||
|
||||
impl<'a> Deref for DynamicViewOne<'a> {
|
||||
type Target = DynamicViewOneOwned;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for DynamicViewOne<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DynamicViewOne<'a> {
|
||||
pub fn new(world: &'a World, entity: Entity) -> Self {
|
||||
Self {
|
||||
world,
|
||||
inner: DynamicViewOneOwned::new(entity)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicViewOne`] with queries.
|
||||
pub fn new_with(world: &'a World, entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
|
||||
Self {
|
||||
world,
|
||||
inner: DynamicViewOneOwned::new_with(entity, queries)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(self) -> Option<Vec<DynamicType>> {
|
||||
self.inner.get(&self.world)
|
||||
}
|
||||
}
|
||||
|
||||
/// A variant of [`DynamicViewOne`] that doesn't store a borrow of the world.
|
||||
#[derive(Clone)]
|
||||
pub struct DynamicViewOneOwned {
|
||||
pub entity: Entity,
|
||||
pub queries: Vec<QueryDynamicType>
|
||||
}
|
||||
|
||||
impl DynamicViewOneOwned {
|
||||
pub fn new(entity: Entity) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
queries: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicViewOne`] with queries.
|
||||
pub fn new_with(entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
queries
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(self, world: &World) -> Option<Vec<DynamicType>> {
|
||||
dynamic_view_one_get_impl(world, &self.queries, self.entity)
|
||||
}
|
||||
}
|
||||
|
||||
fn dynamic_view_one_get_impl(world: &World, queries: &Vec<QueryDynamicType>, entity: Entity) -> Option<Vec<DynamicType>> {
|
||||
let arch = world.entity_archetype(entity)?;
|
||||
let aid = arch.entity_indexes().get(&entity)?;
|
||||
|
||||
// get all fetchers for the queries
|
||||
let mut fetchers: Vec<FetchDynamicTypeUnsafe> = queries.iter()
|
||||
.map(|q| unsafe { q.fetch(world, arch.id(), arch) } )
|
||||
.collect();
|
||||
|
||||
let mut fetch_res = vec![];
|
||||
for fetcher in fetchers.iter_mut() {
|
||||
if !fetcher.can_visit_item(*aid) {
|
||||
return None;
|
||||
} else {
|
||||
let i = unsafe { fetcher.get_item(*aid) };
|
||||
fetch_res.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if fetch_res.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(fetch_res)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{alloc::Layout, ptr::NonNull};
|
||||
|
||||
use crate::{World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType};
|
||||
|
||||
use super::DynamicViewOne;
|
||||
|
||||
#[test]
|
||||
fn single_dynamic_view_one_state() {
|
||||
let comp_layout = Layout::new::<u32>();
|
||||
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
|
||||
|
||||
let mut dynamic_bundle = DynamicBundle::default();
|
||||
let comp = 50u32;
|
||||
let ptr = NonNull::from(&comp).cast::<u8>();
|
||||
dynamic_bundle.push_unknown(ptr, comp_info.clone());
|
||||
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(dynamic_bundle);
|
||||
|
||||
let query = QueryDynamicType::from_info(comp_info);
|
||||
let view = DynamicViewOne::new_with(&world, e, vec![query]);
|
||||
|
||||
let view_row = view.get()
|
||||
.expect("failed to get entity row");
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.iter();
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_dynamic_view_one() {
|
||||
let comp_layout = Layout::new::<u32>();
|
||||
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
|
||||
|
||||
let mut dynamic_bundle = DynamicBundle::default();
|
||||
let comp = 50u32;
|
||||
let ptr = NonNull::from(&comp).cast::<u8>();
|
||||
dynamic_bundle.push_unknown(ptr, comp_info.clone());
|
||||
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(dynamic_bundle);
|
||||
|
||||
let query = QueryDynamicType::from_info(comp_info);
|
||||
let view = DynamicViewOne::new_with(&world, e, vec![query]);
|
||||
|
||||
let view_row = view.get()
|
||||
.expect("failed to get entity row");
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.iter();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{query::{AsFilter, AsQuery, Fetch, Filter, Query}, Component, ComponentColumn, DynTypeId, Tick, World};
|
||||
|
||||
pub struct ChangedFetcher<'a, T> {
|
||||
col: &'a ComponentColumn,
|
||||
tick: Tick,
|
||||
_phantom: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Fetch<'a> for ChangedFetcher<'a, T>
|
||||
where
|
||||
T: 'a,
|
||||
{
|
||||
type Item = bool;
|
||||
|
||||
fn dangling() -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||
let tick = self.col.entity_ticks[entity.0 as usize];
|
||||
*tick >= (*self.tick) - 1
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter that fetches components that have changed.
|
||||
///
|
||||
/// Since [`AsQuery`] is implemented for `&T`, you can use this query like this:
|
||||
/// ```nobuild
|
||||
/// for ts in world.view::<&T>() {
|
||||
/// println!("Got a &T!");
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Changed<T> {
|
||||
type_id: DynTypeId,
|
||||
_phantom: PhantomData<T>
|
||||
}
|
||||
|
||||
impl<T: Component> Default for Changed<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
type_id: DynTypeId::of::<T>(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// manually implemented to avoid a Copy bound on T
|
||||
impl<T> Copy for Changed<T> {}
|
||||
|
||||
// manually implemented to avoid a Clone bound on T
|
||||
impl<T> Clone for Changed<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Changed<T> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Query for Changed<T>
|
||||
where
|
||||
T: 'static
|
||||
{
|
||||
type Item<'a> = bool;
|
||||
type Fetch<'a> = ChangedFetcher<'a, T>;
|
||||
|
||||
fn new() -> Self {
|
||||
Changed::<T>::new()
|
||||
}
|
||||
|
||||
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||
archetype.has_column(self.type_id)
|
||||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, w: &'a World, a: &'a crate::archetype::Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||
ChangedFetcher {
|
||||
col: a.get_column(self.type_id).unwrap(),
|
||||
tick: w.current_tick(),
|
||||
_phantom: PhantomData::<&T>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> AsQuery for Changed<T> {
|
||||
type Query = Self;
|
||||
}
|
||||
|
||||
impl<T: Component> Filter for Changed<T> { }
|
||||
|
||||
impl<T: Component> AsFilter for Changed<T> {
|
||||
type Filter = Self;
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{query::{AsQuery, Query}, Archetype, Component, DynTypeId, World};
|
||||
use crate::{query::{AsFilter, AsQuery, Filter, Query}, Archetype, Component, DynTypeId, World};
|
||||
|
||||
use super::StaticFetcher;
|
||||
|
||||
/// A filter query that fetches when the entity has the component `C`.
|
||||
///
|
||||
|
@ -20,9 +22,8 @@ impl<C: Component> Clone for Has<C> {
|
|||
}
|
||||
|
||||
impl<C: Component> Query for Has<C> {
|
||||
type Item<'a> = ();
|
||||
|
||||
type Fetch<'a> = ();
|
||||
type Item<'a> = bool;
|
||||
type Fetch<'a> = StaticFetcher<bool>;
|
||||
|
||||
fn new() -> Self {
|
||||
Has {
|
||||
|
@ -35,10 +36,17 @@ impl<C: Component> Query for Has<C> {
|
|||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||
()
|
||||
// if fetch is called, it means that 'can_visit_archetype' returned true
|
||||
StaticFetcher::new(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component> AsQuery for Has<C> {
|
||||
type Query = Self;
|
||||
}
|
||||
|
||||
impl<C: Component> Filter for Has<C> { }
|
||||
|
||||
impl<C: Component> AsFilter for Has<C> {
|
||||
type Filter = Self;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
mod has;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub use has::*;
|
||||
|
||||
mod or;
|
||||
pub use or::*;
|
||||
|
||||
mod not;
|
||||
pub use not::*;
|
||||
|
||||
mod changed;
|
||||
pub use changed::*;
|
||||
|
||||
use super::Fetch;
|
||||
|
||||
/// A fetcher that just returns a provided value
|
||||
pub struct StaticFetcher<T: Clone> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
impl<'a, T: Clone> StaticFetcher<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a, T> Fetch<'a> for StaticFetcher<T>
|
||||
where
|
||||
T: Clone + 'a,
|
||||
{
|
||||
type Item = T;
|
||||
|
||||
fn dangling() -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, _: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||
self.value.clone()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use crate::{query::{AsQuery, Query}, Archetype, World};
|
||||
use crate::{query::{AsFilter, AsQuery, Filter, Query}, Archetype, World};
|
||||
|
||||
use super::StaticFetcher;
|
||||
|
||||
/// A filter query that fetches the inverse of `Q`.
|
||||
///
|
||||
|
@ -20,9 +22,8 @@ pub struct Not<Q: Query> {
|
|||
}
|
||||
|
||||
impl<Q: Query> Query for Not<Q> {
|
||||
type Item<'a> = ();
|
||||
|
||||
type Fetch<'a> = ();
|
||||
type Item<'a> = bool;
|
||||
type Fetch<'a> = StaticFetcher<bool>;
|
||||
|
||||
fn new() -> Self {
|
||||
Not {
|
||||
|
@ -35,10 +36,17 @@ impl<Q: Query> Query for Not<Q> {
|
|||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||
()
|
||||
// if fetch is called, it means that 'can_visit_archetype' returned true
|
||||
StaticFetcher::new(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Query> AsQuery for Not<Q> {
|
||||
type Query = Self;
|
||||
}
|
||||
|
||||
impl<Q: Query> Filter for Not<Q> { }
|
||||
|
||||
impl<Q: Query> AsFilter for Not<Q> {
|
||||
type Filter = Self;
|
||||
}
|
|
@ -31,6 +31,10 @@ mod optional;
|
|||
#[allow(unused_imports)]
|
||||
pub use optional::*;
|
||||
|
||||
mod world_tick;
|
||||
#[allow(unused_imports)]
|
||||
pub use world_tick::*;
|
||||
|
||||
pub mod dynamic;
|
||||
|
||||
pub mod filter;
|
||||
|
@ -89,6 +93,12 @@ pub trait AsQuery {
|
|||
type Query: Query;
|
||||
}
|
||||
|
||||
/// A trait for getting the filter of a type.
|
||||
pub trait AsFilter {
|
||||
/// The query for this type
|
||||
type Filter: Filter;
|
||||
}
|
||||
|
||||
pub trait IntoQuery {
|
||||
fn into_query(self) -> Self;
|
||||
}
|
||||
|
@ -125,10 +135,22 @@ impl Query for () {
|
|||
}
|
||||
}
|
||||
|
||||
impl Filter for () {
|
||||
type Item<'a> = bool;
|
||||
}
|
||||
|
||||
impl AsQuery for () {
|
||||
type Query = ();
|
||||
}
|
||||
|
||||
pub trait Filter: Query {
|
||||
type Item<'a> = bool;
|
||||
}
|
||||
|
||||
impl AsFilter for () {
|
||||
type Filter = ();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{World, archetype::Archetype, tests::Vec2};
|
|
@ -2,7 +2,7 @@ use std::marker::PhantomData;
|
|||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||
|
||||
use crate::{World, resource::ResourceObject};
|
||||
use crate::{resource::ResourceObject, Tick, TrackedResource, World};
|
||||
|
||||
use super::{Query, Fetch, AsQuery};
|
||||
|
||||
|
@ -22,12 +22,18 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResource<'a, T> {
|
|||
}
|
||||
|
||||
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
|
||||
true
|
||||
let w = self.world.unwrap();
|
||||
w.has_resource::<T>()
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||
let w = self.world.unwrap();
|
||||
Res(w.get_resource::<T>())
|
||||
Res {
|
||||
// this unwrap is safe since `can_visit_item` ensures the resource exists
|
||||
inner: w.get_tracked_resource::<T>().unwrap(),
|
||||
world_tick: w.current_tick(),
|
||||
_marker: PhantomData::<T>,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -81,13 +87,37 @@ impl<R: ResourceObject> AsQuery for QueryResource<R> {
|
|||
}
|
||||
|
||||
/// A struct used for querying resources from the World.
|
||||
pub struct Res<'a, T: ResourceObject>(pub(crate) AtomicRef<'a, T>);
|
||||
pub struct Res<'a, T: ResourceObject> {
|
||||
pub(crate) inner: AtomicRef<'a, TrackedResource<dyn ResourceObject>>,
|
||||
pub(crate) world_tick: Tick,
|
||||
pub(crate) _marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.deref()
|
||||
self.inner.res.as_any().downcast_ref::<T>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ResourceObject> Res<'a, T> {
|
||||
/// Get the inner [`AtomicRef`].
|
||||
///
|
||||
/// This inner type does not have change tracking. If you `DerefMut` it, the change will not be tracked!
|
||||
#[inline]
|
||||
pub fn get_inner(self) -> atomic_refcell::AtomicRef<'a, T> {
|
||||
atomic_refcell::AtomicRef::map(self.inner, |r| r.res.as_any().downcast_ref().unwrap())
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if the resource changed.
|
||||
pub fn changed(&self) -> bool {
|
||||
*self.inner.tick >= *self.world_tick - 1
|
||||
}
|
||||
|
||||
/// The tick that this resource was last modified at
|
||||
pub fn tick(&self) -> Tick {
|
||||
self.inner.tick
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,12 +141,18 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResourceMut<'a, T> {
|
|||
}
|
||||
|
||||
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
|
||||
true
|
||||
let w = self.world.unwrap();
|
||||
w.has_resource::<T>()
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||
let w = self.world.unwrap();
|
||||
ResMut(w.get_resource_mut::<T>())
|
||||
ResMut {
|
||||
// this is safe since `can_visit_item` ensures that the resource exists.
|
||||
inner: w.get_tracked_resource_mut::<T>().unwrap(),
|
||||
world_tick: w.current_tick(),
|
||||
_marker: PhantomData::<T>,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -169,20 +205,51 @@ impl<R: ResourceObject> AsQuery for QueryResourceMut<R> {
|
|||
}
|
||||
|
||||
/// A struct used for querying resources from the World.
|
||||
pub struct ResMut<'a, T: ResourceObject>(pub(crate) AtomicRefMut<'a, T>);
|
||||
//pub struct ResMut<'a, T: ResourceObject>(pub(crate) AtomicRefMut<'a, T>);
|
||||
pub struct ResMut<'a, T: ResourceObject> {
|
||||
pub(crate) inner: AtomicRefMut<'a, TrackedResource<dyn ResourceObject>>,
|
||||
pub(crate) world_tick: Tick,
|
||||
pub(crate) _marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.deref()
|
||||
self.inner.res.as_any().downcast_ref::<T>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ResourceObject> std::ops::DerefMut for ResMut<'a, T> {
|
||||
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.0.deref_mut()
|
||||
self.mark_changed();
|
||||
self.inner.res.as_any_mut().downcast_mut::<T>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ResourceObject> ResMut<'a, T> {
|
||||
/// Get the inner [`AtomicRefMut`].
|
||||
///
|
||||
/// This inner type does not have change tracking. If you `DerefMut` it, the change will not be tracked!
|
||||
pub fn get_inner(self) -> atomic_refcell::AtomicRefMut<'a, T> {
|
||||
atomic_refcell::AtomicRefMut::map(self.inner, |r| r.res.as_any_mut().downcast_mut().unwrap())
|
||||
}
|
||||
|
||||
pub fn changed(&self) -> bool {
|
||||
*self.inner.tick - 1 >= *self.world_tick - 1
|
||||
}
|
||||
|
||||
/// The tick that this resource was last modified at
|
||||
pub fn tick(&self) -> Tick {
|
||||
self.inner.tick
|
||||
}
|
||||
|
||||
/// Manually mark the resource as changed.
|
||||
///
|
||||
/// This is useful if you have a resource with interior mutability and you want to
|
||||
/// mark this resource as changed.
|
||||
pub fn mark_changed(&mut self) {
|
||||
self.inner.tick = self.world_tick
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::World;
|
||||
|
||||
use super::{Query, Fetch, AsQuery};
|
||||
use super::{Query, Fetch, AsQuery, Filter, AsFilter};
|
||||
|
||||
impl<'a, F1> Fetch<'a> for (F1,)
|
||||
/* impl<'a, F1> Fetch<'a> for (F1,)
|
||||
where
|
||||
F1: Fetch<'a>,
|
||||
{
|
||||
|
@ -102,7 +102,7 @@ where
|
|||
Q2: AsQuery,
|
||||
{
|
||||
type Query = (Q1::Query, Q2::Query);
|
||||
}
|
||||
} */
|
||||
|
||||
macro_rules! impl_bundle_tuple {
|
||||
( $($name: ident),+ ) => (
|
||||
|
@ -154,10 +154,39 @@ macro_rules! impl_bundle_tuple {
|
|||
impl<$($name: AsQuery),+> AsQuery for ($($name,)+) {
|
||||
type Query = ($($name::Query,)+);
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl<$($name: Filter),+> Filter for ($($name,)+) {
|
||||
//type Item<'a> = ($(<$name as Query>::Item<'a>,)+);
|
||||
//type Fetch<'a> = ($(<$name as Query>::Fetch<'a>,)+);
|
||||
|
||||
/* fn new() -> Self {
|
||||
( $($name::new(),)+ )
|
||||
}
|
||||
|
||||
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||
let ( $($name,)+ ) = self;
|
||||
|
||||
// this is the only way I could figure out how to do an 'and'
|
||||
let bools = vec![$($name.can_visit_archetype(archetype),)+];
|
||||
bools.iter().all(|b| *b)
|
||||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||
let ( $($name,)+ ) = self;
|
||||
( $($name.fetch(world, archetype, tick),)+ )
|
||||
} */
|
||||
}
|
||||
|
||||
impl<$($name: AsFilter),+> AsFilter for ($($name,)+) {
|
||||
type Filter = ($($name::Filter,)+);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Hopefully up to 16 queries in a SINGLE view is enough
|
||||
impl_bundle_tuple! { Q1 }
|
||||
impl_bundle_tuple! { Q1, Q2 }
|
||||
impl_bundle_tuple! { Q1, Q2, Q3 }
|
||||
impl_bundle_tuple! { Q1, Q2, Q3, Q4 }
|
||||
impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5 }
|
|
@ -2,11 +2,11 @@ use std::ops::Range;
|
|||
|
||||
use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick};
|
||||
|
||||
use super::{Query, Fetch, AsQuery};
|
||||
use super::{AsFilter, AsQuery, Fetch, Filter, Query};
|
||||
|
||||
pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
|
||||
|
||||
pub struct ViewState<'a, Q: Query, F: Query> {
|
||||
pub struct ViewState<'a, Q: Query, F: Filter> {
|
||||
world: &'a World,
|
||||
query: Q,
|
||||
filter: F,
|
||||
|
@ -16,7 +16,7 @@ pub struct ViewState<'a, Q: Query, F: Query> {
|
|||
impl<'a, Q, F> ViewState<'a, Q, F>
|
||||
where
|
||||
Q: Query,
|
||||
F: Query,
|
||||
F: Filter,
|
||||
{
|
||||
pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self {
|
||||
Self {
|
||||
|
@ -38,7 +38,7 @@ where
|
|||
}
|
||||
|
||||
/// Consumes `self`, adding a filter to the view.
|
||||
pub fn with<U: AsQuery>(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> {
|
||||
pub fn with<U: AsFilter>(self, filter: U::Filter) -> ViewState<'a, Q, (F, U::Filter)> {
|
||||
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
|
||||
}
|
||||
}
|
||||
|
@ -46,18 +46,18 @@ where
|
|||
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
|
||||
where
|
||||
Q: Query,
|
||||
F: Query,
|
||||
F: Filter,
|
||||
{
|
||||
type Item = Q::Item<'a>;
|
||||
|
||||
type IntoIter = ViewIter<'a, Q, F>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let tick = self.world.tick_tracker().tick_when(Q::MUTATES);
|
||||
//let tick = self.world.tick_tracker().tick_when(Q::MUTATES);
|
||||
|
||||
ViewIter {
|
||||
world: self.world,
|
||||
tick,
|
||||
tick: self.world.current_tick(),
|
||||
query: self.query,
|
||||
filter: self.filter,
|
||||
fetcher: None,
|
||||
|
@ -69,7 +69,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ViewIter<'a, Q: Query, F: Query> {
|
||||
pub struct ViewIter<'a, Q: Query, F: Filter> {
|
||||
world: &'a World,
|
||||
tick: Tick,
|
||||
query: Q,
|
||||
|
@ -84,7 +84,7 @@ pub struct ViewIter<'a, Q: Query, F: Query> {
|
|||
impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
|
||||
where
|
||||
Q: Query,
|
||||
F: Query,
|
||||
F: Filter,
|
||||
{
|
||||
type Item = Q::Item<'a>;
|
||||
|
||||
|
@ -147,17 +147,17 @@ pub struct ViewOne<'a, Q: Query> {
|
|||
|
||||
impl<'a, Q: Query> ViewOne<'a, Q> {
|
||||
pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self {
|
||||
let tick = world.tick_tracker().tick_when(Q::MUTATES);
|
||||
//let tick = world.tick_tracker().tick_when(Q::MUTATES);
|
||||
|
||||
Self {
|
||||
world,
|
||||
tick,
|
||||
tick: world.current_tick(),
|
||||
entity,
|
||||
query
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<Q::Item<'a>> {
|
||||
pub fn get(self) -> Option<Q::Item<'a>> {
|
||||
if let Some(record) = self.world.entities.arch_index.get(&self.entity) {
|
||||
let arch = self.world.archetypes.get(&record.id)
|
||||
.expect("An invalid record was specified for an entity");
|
|
@ -0,0 +1,102 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crate::{system::FnArgFetcher, Tick, World};
|
||||
use super::{Fetch, Query, AsQuery};
|
||||
|
||||
/// Fetcher used to fetch the current tick of the world.
|
||||
pub struct FetchWorldTick {
|
||||
tick: Tick
|
||||
}
|
||||
|
||||
impl<'a> Fetch<'a> for FetchWorldTick {
|
||||
type Item = WorldTick;
|
||||
|
||||
fn dangling() -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn can_visit_item(&mut self, _entity: crate::ArchetypeEntityId) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||
WorldTick(self.tick)
|
||||
}
|
||||
}
|
||||
|
||||
/// Query used to query the current tick of the world.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct QueryWorldTick;
|
||||
|
||||
impl Default for QueryWorldTick {
|
||||
fn default() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Query for QueryWorldTick {
|
||||
type Item<'a> = WorldTick;
|
||||
|
||||
type Fetch<'a> = FetchWorldTick;
|
||||
|
||||
const ALWAYS_FETCHES: bool = true;
|
||||
|
||||
fn new() -> Self {
|
||||
QueryWorldTick
|
||||
}
|
||||
|
||||
fn can_visit_archetype(&self, _archetype: &crate::archetype::Archetype) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, world: &'a World, _archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> {
|
||||
FetchWorldTick {
|
||||
tick: world.current_tick()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn fetch_world<'a>(&self, world: &'a World) -> Option<Self::Fetch<'a>> {
|
||||
Some(FetchWorldTick {
|
||||
tick: world.current_tick()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsQuery for QueryWorldTick {
|
||||
type Query = Self;
|
||||
}
|
||||
|
||||
/// Type that can be used in an fn system for fetching the current world tick.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct WorldTick(Tick);
|
||||
|
||||
impl Deref for WorldTick {
|
||||
type Target = Tick;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsQuery for WorldTick {
|
||||
type Query = QueryWorldTick;
|
||||
}
|
||||
|
||||
impl FnArgFetcher for WorldTick {
|
||||
type State = ();
|
||||
|
||||
type Arg<'a, 'state> = WorldTick;
|
||||
|
||||
fn create_state(_: std::ptr::NonNull<World>) -> Self::State {
|
||||
()
|
||||
}
|
||||
|
||||
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: std::ptr::NonNull<World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
WorldTick(world.current_tick())
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: std::ptr::NonNull<World>) {
|
||||
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ use std::marker::PhantomData;
|
|||
|
||||
use lyra_ecs_derive::Component;
|
||||
|
||||
use crate::query::Filter;
|
||||
use crate::query::Query;
|
||||
use crate::query::ViewState;
|
||||
use crate::Entity;
|
||||
|
@ -98,7 +99,7 @@ impl World {
|
|||
impl<'a, Q, F> ViewState<'a, Q, F>
|
||||
where
|
||||
Q: Query,
|
||||
F: Query,
|
||||
F: Filter,
|
||||
{
|
||||
/// Consumes `self` to return a view that fetches the relation to a specific target entity.
|
||||
pub fn relates_to<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F>
|
|
@ -2,6 +2,8 @@ use std::{any::{Any, TypeId}, sync::Arc};
|
|||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||
|
||||
use crate::{Tick, TickTracker};
|
||||
|
||||
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
|
||||
pub trait ResourceObject: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
@ -18,18 +20,23 @@ impl<T: Send + Sync + Any> ResourceObject for T {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct TrackedResource<T: ?Sized> {
|
||||
pub tick: Tick,
|
||||
pub res: T,
|
||||
}
|
||||
|
||||
/// A type erased storage for a Resource.
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceData {
|
||||
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
|
||||
pub(crate) data: Arc<AtomicRefCell<TrackedResource<dyn ResourceObject>>>,
|
||||
type_id: TypeId,
|
||||
}
|
||||
|
||||
impl ResourceData {
|
||||
pub fn new<T: ResourceObject>(data: T) -> Self {
|
||||
pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
|
||||
|
||||
Self {
|
||||
data: Arc::new(AtomicRefCell::new(data)),
|
||||
data: Arc::new(AtomicRefCell::new(TrackedResource { tick, res: data })),
|
||||
type_id: TypeId::of::<T>(),
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +53,7 @@ impl ResourceData {
|
|||
/// * If the data is already borrowed mutably, this will panic.
|
||||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn get<T: ResourceObject>(&self) -> AtomicRef<T> {
|
||||
AtomicRef::map(self.data.borrow(), |a| a.as_any().downcast_ref().unwrap())
|
||||
AtomicRef::map(self.data.borrow(), |a| a.res.as_any().downcast_ref().unwrap())
|
||||
}
|
||||
|
||||
/// Mutably borrow the data inside of the resource.
|
||||
|
@ -56,7 +63,7 @@ impl ResourceData {
|
|||
/// * If the data is already borrowed mutably, this will panic.
|
||||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn get_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
||||
AtomicRefMut::map(self.data.borrow_mut(), |a| a.as_any_mut().downcast_mut().unwrap())
|
||||
AtomicRefMut::map(self.data.borrow_mut(), |a| a.res.as_any_mut().downcast_mut().unwrap())
|
||||
}
|
||||
|
||||
/// Borrow the data inside of the resource.
|
||||
|
@ -66,7 +73,7 @@ impl ResourceData {
|
|||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
|
||||
self.data.try_borrow()
|
||||
.map(|r| AtomicRef::map(r, |a| a.as_any().downcast_ref().unwrap()))
|
||||
.map(|r| AtomicRef::map(r, |a| a.res.as_any().downcast_ref().unwrap()))
|
||||
.ok()
|
||||
}
|
||||
|
||||
|
@ -77,7 +84,11 @@ impl ResourceData {
|
|||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
|
||||
self.data.try_borrow_mut()
|
||||
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
|
||||
.map(|r| AtomicRefMut::map(r, |a| a.res.as_any_mut().downcast_mut().unwrap()))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn changed(&self, tick: Tick) -> bool {
|
||||
*self.data.borrow().tick >= *tick - 1
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use std::{any::Any, marker::PhantomData, ptr::NonNull};
|
||||
|
||||
use paste::paste;
|
||||
use crate::{World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}};
|
||||
use crate::{World, Access, ResourceObject, query::{Query, Filter, ViewState, ResMut, Res}};
|
||||
|
||||
use super::{System, IntoSystem};
|
||||
|
||||
|
@ -20,6 +20,7 @@ pub trait FnArgFetcher {
|
|||
Access::Read
|
||||
}
|
||||
|
||||
// TODO: check if the fetcher can fetch before getting.
|
||||
/// Get the arg from the world
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -130,6 +131,7 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G }
|
|||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H }
|
||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I }
|
||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J }
|
||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K }
|
||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L }
|
||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M }
|
||||
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N }
|
||||
|
@ -140,7 +142,7 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
|
|||
impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F>
|
||||
where
|
||||
Q: Query + 'static,
|
||||
F: Query + 'static,
|
||||
F: Filter + 'static,
|
||||
{
|
||||
type State = (Q, F);
|
||||
type Arg<'a, 'state> = ViewState<'a, Q, F>;
|
||||
|
@ -213,7 +215,9 @@ impl<R: ResourceObject> FnArgFetcher for Res<'_, R> {
|
|||
|
||||
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
Res(world.get_resource::<R>())
|
||||
world.get_resource::<R>()
|
||||
// TODO: check if the resource exists before attempting to fetch
|
||||
.unwrap_or_else(|| panic!("world is missing resource: {}", std::any::type_name::<R>()))
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
|
||||
|
@ -227,7 +231,9 @@ impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
|
|||
|
||||
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
ResMut(world.get_resource_mut::<R>())
|
||||
world.get_resource_mut::<R>()
|
||||
// TODO: check if the resource exists before attempting to fetch
|
||||
.unwrap_or_else(|| panic!("world is missing resource: {}", std::any::type_name::<R>()))
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: NonNull<World>) { }
|
||||
|
@ -306,7 +312,8 @@ mod tests {
|
|||
world.add_resource(SomeCounter(0));
|
||||
|
||||
let test_system = |world: &World| -> anyhow::Result<()> {
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>();
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
counter.0 += 10;
|
||||
|
||||
Ok(())
|
||||
|
@ -315,7 +322,8 @@ mod tests {
|
|||
test_system.into_system().execute(NonNull::from(&world)).unwrap();
|
||||
|
||||
let test_system = |world: &mut World| -> anyhow::Result<()> {
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>();
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
counter.0 += 10;
|
||||
|
||||
Ok(())
|
||||
|
@ -323,7 +331,8 @@ mod tests {
|
|||
|
||||
test_system.into_system().execute(NonNull::from(&world)).unwrap();
|
||||
|
||||
let counter = world.get_resource::<SomeCounter>();
|
||||
let counter = world.get_resource::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 20);
|
||||
}
|
||||
|
||||
|
@ -335,7 +344,8 @@ mod tests {
|
|||
world.add_resource(SomeCounter(0));
|
||||
|
||||
let test_system = |world: &World| -> anyhow::Result<()> {
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>();
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
counter.0 += 10;
|
||||
|
||||
Ok(())
|
||||
|
@ -345,7 +355,8 @@ mod tests {
|
|||
|
||||
#[allow(dead_code)]
|
||||
fn test_system(world: &mut World) -> anyhow::Result<()> {
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>();
|
||||
let mut counter = world.get_resource_mut::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
counter.0 += 10;
|
||||
|
||||
Ok(())
|
||||
|
@ -353,7 +364,8 @@ mod tests {
|
|||
|
||||
test_system.into_system().execute(NonNull::from(&world)).unwrap();
|
||||
|
||||
let counter = world.get_resource::<SomeCounter>();
|
||||
let counter = world.get_resource::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 20);
|
||||
}
|
||||
|
||||
|
@ -365,16 +377,15 @@ mod tests {
|
|||
world.add_resource(SomeCounter(0));
|
||||
|
||||
let test_system = |mut counter: ResMut<SomeCounter>| -> anyhow::Result<()> {
|
||||
// .0 is twice here since ResMut's tuple field is pub(crate).
|
||||
// Users wont need to do this
|
||||
counter.0.0 += 10;
|
||||
counter.0 += 10;
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
test_system.into_system().execute(NonNull::from(&world)).unwrap();
|
||||
|
||||
let counter = world.get_resource::<SomeCounter>();
|
||||
let counter = world.get_resource::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 10);
|
||||
}
|
||||
|
||||
|
@ -388,9 +399,7 @@ mod tests {
|
|||
let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
|
||||
for v2 in view.into_iter() {
|
||||
println!("Got v2 at '{:?}'", v2);
|
||||
// .0 is twice here since ResMut's tuple field is pub(crate).
|
||||
// Users wont need to do this
|
||||
counter.0.0 += 1;
|
||||
counter.0 += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -398,7 +407,8 @@ mod tests {
|
|||
|
||||
test_system.into_system().execute(NonNull::from(&world)).unwrap();
|
||||
|
||||
let counter = world.get_resource::<SomeCounter>();
|
||||
let counter = world.get_resource::<SomeCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 2);
|
||||
}
|
||||
}
|
|
@ -103,7 +103,7 @@ impl GraphExecutor {
|
|||
}
|
||||
|
||||
let world = unsafe { world_ptr.as_mut() };
|
||||
if let Some(mut queue) = world.try_get_resource_mut::<CommandQueue>() {
|
||||
if let Some(mut queue) = world.get_resource_mut::<CommandQueue>() {
|
||||
// Safety: Commands only borrows world.entities when adding commands
|
||||
let world = unsafe { world_ptr.as_mut() };
|
||||
let mut commands = Commands::new(&mut queue, world);
|
||||
|
@ -189,7 +189,8 @@ mod tests {
|
|||
exec.execute(NonNull::from(&world), true).unwrap();
|
||||
println!("Executed systems");
|
||||
|
||||
let order = world.get_resource::<Vec<String>>();
|
||||
let order = world.get_resource::<Vec<String>>()
|
||||
.expect("missing Vec<String> resource");
|
||||
let mut order_iter = order.iter();
|
||||
|
||||
// ... but still executed in order
|
|
@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
|||
/// TickTracker is used for tracking changes of [`Component`](crate::Component)s and entities.
|
||||
///
|
||||
/// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well.
|
||||
/// Note that [`Tick::clone`] only clones the inner value of atomic, and not the atomic itself.
|
||||
/// Note that [`TickTracker::clone`] only clones the inner value of atomic, and not the atomic itself.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TickTracker {
|
||||
tick: AtomicU64,
|
||||
|
@ -72,6 +72,12 @@ impl TickTracker {
|
|||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default, PartialOrd, Ord)]
|
||||
pub struct Tick(u64);
|
||||
|
||||
impl From<u64> for Tick {
|
||||
fn from(value: u64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Tick {
|
||||
type Target = u64;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
use std::{any::TypeId, collections::HashMap, ptr::NonNull};
|
||||
use std::{any::TypeId, collections::HashMap, ops::Deref, ptr::NonNull};
|
||||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||
|
||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
|
||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, Res, ResMut, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker, TrackedResource};
|
||||
|
||||
/// The id of the entity for the Archetype.
|
||||
///
|
||||
|
@ -10,6 +10,14 @@ use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic
|
|||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ArchetypeEntityId(pub u64);
|
||||
|
||||
impl Deref for ArchetypeEntityId {
|
||||
type Target = u64;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Record {
|
||||
pub id: ArchetypeId,
|
||||
|
@ -18,11 +26,11 @@ pub struct Record {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct World {
|
||||
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>,
|
||||
pub archetypes: HashMap<ArchetypeId, Archetype>,
|
||||
next_archetype_id: ArchetypeId,
|
||||
resources: HashMap<TypeId, ResourceData>,
|
||||
tracker: TickTracker,
|
||||
pub(crate) entities: Entities,
|
||||
pub entities: Entities,
|
||||
}
|
||||
|
||||
impl Default for World {
|
||||
|
@ -65,10 +73,9 @@ impl World {
|
|||
where
|
||||
B: Bundle
|
||||
{
|
||||
let tick = self.current_tick();
|
||||
let bundle_types = bundle.type_ids();
|
||||
|
||||
let tick = self.tick();
|
||||
|
||||
// try to find an archetype
|
||||
let archetype = self.archetypes
|
||||
.values_mut()
|
||||
|
@ -112,14 +119,8 @@ impl World {
|
|||
|
||||
/// Despawn an entity from the World
|
||||
pub fn despawn(&mut self, entity: Entity) {
|
||||
// Tick the tracker if the entity is spawned. This is done here instead of the `if let`
|
||||
// below due to the borrow checker complaining about multiple mutable borrows to self.
|
||||
let tick = if self.entities.arch_index.contains_key(&entity.id) {
|
||||
Some(self.tick())
|
||||
} else { None };
|
||||
|
||||
let tick = self.current_tick();
|
||||
if let Some(record) = self.entities.arch_index.get_mut(&entity.id) {
|
||||
let tick = tick.unwrap();
|
||||
let arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||
|
||||
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
|
||||
|
@ -138,9 +139,34 @@ impl World {
|
|||
where
|
||||
B: Bundle
|
||||
{
|
||||
let tick = self.tick();
|
||||
let tick = self.current_tick();
|
||||
let record = self.entities.entity_record(entity);
|
||||
|
||||
if record.is_none() {
|
||||
//let mut combined_column_infos: Vec<ComponentInfo> = bundle.info().columns.iter().map(|c| c.info).collect();
|
||||
let new_arch_id = self.next_archetype_id.increment();
|
||||
let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info());
|
||||
|
||||
let mut dbun = DynamicBundle::new();
|
||||
dbun.push_bundle(bundle);
|
||||
|
||||
let entity_arch_id = archetype.add_entity(entity, dbun, &tick);
|
||||
|
||||
self.archetypes.insert(new_arch_id, archetype);
|
||||
|
||||
// Create entity record and store it
|
||||
let record = Record {
|
||||
id: new_arch_id,
|
||||
index: entity_arch_id,
|
||||
};
|
||||
|
||||
self.entities.insert_entity_record(entity, record);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let record = record.unwrap();
|
||||
|
||||
let record = self.entities.entity_record(entity).unwrap();
|
||||
let current_arch = self.archetypes.get(&record.id).unwrap();
|
||||
let current_arch_len = current_arch.len();
|
||||
|
||||
|
@ -175,6 +201,7 @@ impl World {
|
|||
.map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) })
|
||||
.collect();
|
||||
|
||||
// try to find an archetype that this entity and its new components can fit into
|
||||
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&combined_column_types)) {
|
||||
let mut dbun = DynamicBundle::new();
|
||||
// move old entity components into new archetype columns
|
||||
|
@ -347,9 +374,9 @@ impl World {
|
|||
}
|
||||
|
||||
/// View into the world for a set of entities that satisfy the query and the filter.
|
||||
pub fn filtered_view<Q: AsQuery, F: AsQuery>(&self) -> ViewState<Q::Query, F::Query> {
|
||||
pub fn filtered_view<Q: AsQuery, F: AsFilter>(&self) -> ViewState<Q::Query, F::Filter> {
|
||||
let archetypes = self.archetypes.values().collect();
|
||||
ViewState::<Q::Query, F::Query>::new(self, Q::Query::new(), F::Query::new(), archetypes)
|
||||
ViewState::<Q::Query, F::Filter>::new(self, Q::Query::new(), F::Filter::new(), archetypes)
|
||||
}
|
||||
|
||||
/// View into the world for a set of entities that satisfy the queries.
|
||||
|
@ -360,9 +387,9 @@ impl World {
|
|||
}
|
||||
|
||||
/// View into the world for a set of entities that satisfy the queries.
|
||||
pub fn filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> {
|
||||
pub fn filtered_view_iter<Q: AsQuery, F: AsFilter>(&self) -> ViewIter<Q::Query, F::Filter> {
|
||||
let archetypes = self.archetypes.values().collect();
|
||||
let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes);
|
||||
let v = ViewState::new(self, Q::Query::new(), F::Filter::new(), archetypes);
|
||||
v.into_iter()
|
||||
}
|
||||
|
||||
|
@ -374,43 +401,111 @@ impl World {
|
|||
ViewOne::new(self, entity.id, T::Query::new())
|
||||
}
|
||||
|
||||
//pub fn view_one(&self, entity: EntityId) ->
|
||||
|
||||
/// Add a resource to the world.
|
||||
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data));
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, self.current_tick()));
|
||||
}
|
||||
|
||||
/// Add the default value of a resource.
|
||||
///
|
||||
/// > Note: This will replace existing values.
|
||||
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default()));
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), self.current_tick()));
|
||||
}
|
||||
|
||||
/// Add the default value of a resource if it does not already exist.
|
||||
///
|
||||
/// Returns a boolean indicating if the resource was added.
|
||||
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
|
||||
let id = TypeId::of::<T>();
|
||||
if !self.resources.contains_key(&id) {
|
||||
self.resources.insert(id, ResourceData::new(T::default(), self.current_tick()));
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a resource from the world, or insert it into the world with the provided
|
||||
/// `fn` and return it.
|
||||
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
|
||||
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> ResMut<T>
|
||||
where
|
||||
F: Fn() -> T + 'static
|
||||
{
|
||||
self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(f()))
|
||||
.get_mut()
|
||||
let tick = self.current_tick();
|
||||
let res = self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(f(), tick));
|
||||
|
||||
ResMut {
|
||||
inner: res.data.borrow_mut(),
|
||||
world_tick: tick,
|
||||
_marker: std::marker::PhantomData::<T>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a resource from the world, or insert it into the world as its default.
|
||||
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
|
||||
/// Get a resource from the world, or insert its default value.
|
||||
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> ResMut<T>
|
||||
{
|
||||
self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(T::default()))
|
||||
.get_mut()
|
||||
let tick = self.current_tick();
|
||||
let res = self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(T::default(), tick));
|
||||
|
||||
ResMut {
|
||||
inner: res.data.borrow_mut(),
|
||||
world_tick: tick,
|
||||
_marker: std::marker::PhantomData::<T>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a resource from the World.
|
||||
pub fn get_resource<T: ResourceObject>(&self) -> Option<Res<T>> {
|
||||
self.get_tracked_resource::<T>().map(|r| Res {
|
||||
inner: r,
|
||||
world_tick: self.current_tick(),
|
||||
_marker: std::marker::PhantomData::<T>,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the tick of a resource.
|
||||
///
|
||||
/// Will panic if the resource is not in the world. See [`World::try_get_resource`] for
|
||||
/// a function that returns an option.
|
||||
pub fn get_resource<T: ResourceObject>(&self) -> AtomicRef<T> {
|
||||
/// This tick represents the last time the resource was mutated.
|
||||
pub fn get_resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
|
||||
self.get_tracked_resource::<T>().map(|r| r.tick)
|
||||
}
|
||||
|
||||
/// Gets a reference to a change tracked resource.
|
||||
///
|
||||
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
||||
/// [`World::get_resource`].
|
||||
pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
.get()
|
||||
.map(|r| r.data.borrow())
|
||||
}
|
||||
|
||||
/// Gets a mutable borrow to a change tracked resource.
|
||||
///
|
||||
/// You will have to manually downcast the inner resource. Most people don't need this, see
|
||||
/// [`World::get_resource_mut`].
|
||||
pub fn get_tracked_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<TrackedResource<dyn ResourceObject>>> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.data.borrow_mut())
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if the resource changed.
|
||||
///
|
||||
/// This will return false if the resource doesn't exist.
|
||||
pub fn has_resource_changed<T: ResourceObject>(&self) -> bool {
|
||||
let tick = self.current_tick();
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.changed(tick))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns the [`Tick`] that the resource was last modified at.
|
||||
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.data.borrow().tick)
|
||||
}
|
||||
|
||||
/// Returns boolean indicating if the World contains a resource of type `T`.
|
||||
|
@ -418,42 +513,33 @@ impl World {
|
|||
self.resources.contains_key(&TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Attempts to get a resource from the World.
|
||||
///
|
||||
/// Returns `None` if the resource was not found.
|
||||
pub fn try_get_resource<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.and_then(|r| r.try_get())
|
||||
}
|
||||
|
||||
/// Gets a mutable borrow of a resource from the World.
|
||||
///
|
||||
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
|
||||
/// a function that returns an option.
|
||||
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
.get_mut()
|
||||
pub fn get_resource_mut<T: ResourceObject>(&self) -> Option<ResMut<T>> {
|
||||
self.get_tracked_resource_mut::<T>().map(|r| ResMut {
|
||||
inner: r,
|
||||
world_tick: self.current_tick(),
|
||||
_marker: std::marker::PhantomData::<T>,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to get a mutable borrow of a resource from the World.
|
||||
///
|
||||
/// Returns `None` if the resource was not found.
|
||||
pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
|
||||
/// Get the corresponding [`ResourceData`].
|
||||
pub fn get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.and_then(|r| r.try_get_mut())
|
||||
.map(|r| r.clone())
|
||||
}
|
||||
|
||||
/// Increments the TickTracker which is used for tracking changes to components.
|
||||
/// Increments the world current tick for tracking changes to components and resources.
|
||||
///
|
||||
/// Most users wont need to call this manually, its done for you through queries and views.
|
||||
/// # Note:
|
||||
/// For change tracking to work correctly, this must be ran each loop before you run world
|
||||
/// systems.
|
||||
pub fn tick(&self) -> Tick {
|
||||
self.tracker.tick()
|
||||
}
|
||||
|
||||
/// Gets the current tick that the world is at.
|
||||
///
|
||||
/// See [`TickTracker`]
|
||||
/// See [`World::tick`].
|
||||
pub fn current_tick(&self) -> Tick {
|
||||
self.tracker.current()
|
||||
}
|
||||
|
@ -463,9 +549,13 @@ impl World {
|
|||
}
|
||||
|
||||
/// Attempts to find a resource in the world and returns a NonNull pointer to it
|
||||
pub unsafe fn try_get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
|
||||
pub unsafe fn get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) })
|
||||
.map(|d| unsafe {
|
||||
let data = d.data.borrow();
|
||||
let ptr = NonNull::from(&data.res);
|
||||
NonNull::new_unchecked(ptr.as_ptr() as *mut T)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn archetype_count(&self) -> usize {
|
||||
|
@ -539,15 +629,17 @@ mod tests {
|
|||
world.add_resource(counter);
|
||||
}
|
||||
|
||||
let counter = world.get_resource::<SimpleCounter>();
|
||||
let counter = world.get_resource::<SimpleCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 0);
|
||||
drop(counter);
|
||||
|
||||
let mut counter = world.get_resource_mut::<SimpleCounter>();
|
||||
let mut counter = world.get_resource_mut::<SimpleCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
counter.0 += 4582;
|
||||
drop(counter);
|
||||
|
||||
assert!(world.try_get_resource::<u32>().is_none());
|
||||
assert!(world.get_resource::<u32>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -557,9 +649,11 @@ mod tests {
|
|||
world.add_resource(counter);
|
||||
|
||||
// test multiple borrows at the same time
|
||||
let counter = world.get_resource::<SimpleCounter>();
|
||||
let counter = world.get_resource::<SimpleCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 4582);
|
||||
let counter2 = world.get_resource::<SimpleCounter>();
|
||||
let counter2 = world.get_resource::<SimpleCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 4582);
|
||||
assert_eq!(counter2.0, 4582);
|
||||
}
|
||||
|
@ -573,9 +667,10 @@ mod tests {
|
|||
}
|
||||
|
||||
// test that its only possible to get a single mutable borrow
|
||||
let counter = world.get_resource_mut::<SimpleCounter>();
|
||||
let counter = world.get_resource_mut::<SimpleCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
assert_eq!(counter.0, 4582);
|
||||
assert!(world.try_get_resource_mut::<SimpleCounter>().is_none());
|
||||
assert!(world.get_resource_mut::<SimpleCounter>().is_none());
|
||||
assert_eq!(counter.0, 4582);
|
||||
}
|
||||
|
||||
|
@ -688,4 +783,23 @@ mod tests {
|
|||
let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
|
||||
assert_eq!(*pos, Vec2::new(5.0, 5.0));
|
||||
}
|
||||
|
||||
/// Tests resource change checks
|
||||
#[test]
|
||||
fn resource_changed() {
|
||||
let mut world = World::new();
|
||||
world.add_resource(SimpleCounter(50));
|
||||
|
||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||
|
||||
world.spawn(Vec2::new(50.0, 50.0));
|
||||
|
||||
assert!(!world.has_resource_changed::<SimpleCounter>());
|
||||
|
||||
let mut counter = world.get_resource_mut::<SimpleCounter>()
|
||||
.expect("Counter resource is missing");
|
||||
counter.0 += 100;
|
||||
|
||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||
}
|
||||
}
|
|
@ -10,34 +10,38 @@ lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
|||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
lyra-math = { path = "../lyra-math" }
|
||||
lyra-scene = { path = "../lyra-scene" }
|
||||
lyra-gltf = { path = "../lyra-gltf" }
|
||||
wgsl_preprocessor = { path = "../wgsl-preprocessor" }
|
||||
|
||||
winit = "0.28.1"
|
||||
wgpu = "0.15.1"
|
||||
winit = "0.30.5"
|
||||
wgpu = { version = "22.1.0" }
|
||||
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
|
||||
tracing-log = "0.1.3"
|
||||
tracing-log = "0.2.0"
|
||||
tracing-appender = "0.2.2"
|
||||
tracing-tracy = { version = "0.11.0", optional = true }
|
||||
|
||||
async-std = { version = "1.12.0", features = [ "unstable", "attributes" ] }
|
||||
cfg-if = "1"
|
||||
bytemuck = { version = "1.12", features = [ "derive", "min_const_generics" ] }
|
||||
image = { version = "0.24", default-features = false, features = ["png", "jpeg"] }
|
||||
image = "0.25.2"
|
||||
anyhow = "1.0"
|
||||
instant = "0.1"
|
||||
async-trait = "0.1.65"
|
||||
glam = { version = "0.24.0", features = ["bytemuck", "debug-glam-assert"] }
|
||||
gilrs-core = "0.5.6"
|
||||
glam = { version = "0.29.0", features = ["bytemuck", "debug-glam-assert"] }
|
||||
syn = "2.0.26"
|
||||
quote = "1.0.29"
|
||||
uuid = { version = "1.5.0", features = ["v4", "fast-rng"] }
|
||||
itertools = "0.11.0"
|
||||
itertools = "0.13.0"
|
||||
thiserror = "1.0.56"
|
||||
unique = "0.9.1"
|
||||
rustc-hash = "1.1.0"
|
||||
rustc-hash = "2.0.0"
|
||||
petgraph = { version = "0.6.5", features = ["matrix_graph"] }
|
||||
bind_match = "0.1.2"
|
||||
round_mult = "0.1.3"
|
||||
fast_poisson = { version = "1.0.0", features = ["single_precision"] }
|
||||
atomic_refcell = "0.1.13"
|
||||
|
||||
[features]
|
||||
tracy = ["dep:tracing-tracy"]
|
||||
tracy = ["dep:tracing-tracy"]
|
|
@ -1,5 +1,5 @@
|
|||
use instant::Instant;
|
||||
use lyra_ecs::{Component, World};
|
||||
use lyra_ecs::{query::ResMut, Component};
|
||||
use lyra_reflect::Reflect;
|
||||
|
||||
use crate::{plugin::Plugin, game::GameStages};
|
||||
|
@ -30,9 +30,8 @@ impl std::ops::DerefMut for DeltaTime {
|
|||
/// A system that updates the [`DeltaTime``] resource.
|
||||
///
|
||||
/// The resource is updated in the [`GameStages::First`] stage.
|
||||
pub fn delta_time_system(world: &mut World) -> anyhow::Result<()> {
|
||||
pub fn delta_time_system(mut delta: ResMut<DeltaTime>) -> anyhow::Result<()> {
|
||||
let now = Instant::now();
|
||||
let mut delta = world.get_resource_mut::<DeltaTime>();
|
||||
delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32();
|
||||
delta.1 = Some(now);
|
||||
|
||||
|
@ -42,8 +41,8 @@ pub fn delta_time_system(world: &mut World) -> anyhow::Result<()> {
|
|||
pub struct DeltaTimePlugin;
|
||||
|
||||
impl Plugin for DeltaTimePlugin {
|
||||
fn setup(&self, game: &mut crate::game::Game) {
|
||||
game.world_mut().add_resource(DeltaTime(0.0, None));
|
||||
game.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]);
|
||||
fn setup(&mut self, app: &mut crate::game::App) {
|
||||
app.world.add_resource(DeltaTime(0.0, None));
|
||||
app.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick};
|
||||
|
||||
pub trait Event: Clone + Send + Sync + 'static {}
|
||||
impl<T: Clone + Send + Sync + 'static> Event for T {}
|
||||
|
||||
/// A Vec with other Vecs in it to track relative age of items.
|
||||
///
|
||||
/// The vec has 3 levels, a `newest`, `medium` and `old`. Items are pushed to the `newest`
|
||||
/// internal vec. When [`WaterfallVec::waterfall`] is called the items in `newest` are
|
||||
/// put into `medium`, and items in `medium` goes to `old`.
|
||||
///
|
||||
/// By checking the items in each internal vec, you can see a relative age between the items.
|
||||
/// The event system uses this to clear the `old` vec to ensure keep events for only two
|
||||
/// frames at a time.
|
||||
struct WaterfallVec<T> {
|
||||
newest: Vec<T>,
|
||||
medium: Vec<T>,
|
||||
old: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for WaterfallVec<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
newest: Default::default(),
|
||||
medium: Default::default(),
|
||||
old: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WaterfallVec<T> {
|
||||
fn total_len(&self) -> usize {
|
||||
self.newest.len() + self.medium.len() + self.old.len()
|
||||
}
|
||||
|
||||
fn get(&self, mut i: usize) -> Option<&T> {
|
||||
if i >= self.old.len() {
|
||||
i -= self.old.len();
|
||||
|
||||
if i >= self.medium.len() {
|
||||
i -= self.medium.len();
|
||||
self.newest.get(i)
|
||||
} else {
|
||||
self.medium.get(i)
|
||||
}
|
||||
} else {
|
||||
self.old.get(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// Age elements.
|
||||
///
|
||||
/// This moves elements in `newest` to `medium` and elements in `medium` to `old`.
|
||||
/// This is what drives the relative age of the [`WaterfallVec`].
|
||||
fn waterfall(&mut self) {
|
||||
self.old.append(&mut self.medium);
|
||||
self.medium.append(&mut self.newest);
|
||||
}
|
||||
|
||||
/// Push a new element to the newest queue.
|
||||
fn push(&mut self, event: T) {
|
||||
self.newest.push(event);
|
||||
}
|
||||
|
||||
/// Clear oldest items.
|
||||
fn clear_oldest(&mut self) {
|
||||
self.old.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Events<T: Event> {
|
||||
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
|
||||
/// Used to track when the old events were last cleared.
|
||||
last_cleared_at: Tick,
|
||||
/// Used to indicate when the cursor in readers should be reset to zero.
|
||||
/// This becomes true after the old events are cleared.
|
||||
reset_cursor: bool,
|
||||
}
|
||||
|
||||
impl<T: Event> Default for Events<T> {
|
||||
fn default() -> Self {
|
||||
Self { events: Default::default(), last_cleared_at: Default::default(), reset_cursor: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Event> Events<T> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn push_event(&mut self, event: T) {
|
||||
let mut events = self.events.borrow_mut();
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
pub fn reader(&self) -> EventReader<T> {
|
||||
EventReader {
|
||||
events: self.events.clone(),
|
||||
cursor: Arc::new(AtomicRefCell::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writer(&self) -> EventWriter<T> {
|
||||
EventWriter {
|
||||
events: self.events.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventReader<T: Event> {
|
||||
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
|
||||
cursor: Arc<AtomicRefCell<usize>>,
|
||||
}
|
||||
|
||||
impl<T: Event> EventReader<T> {
|
||||
pub fn read(&self) -> Option<atomic_refcell::AtomicRef<T>> {
|
||||
let events = self.events.borrow();
|
||||
|
||||
let mut cursor = self.cursor.borrow_mut();
|
||||
if *cursor >= events.total_len() {
|
||||
None
|
||||
} else {
|
||||
let e = atomic_refcell::AtomicRef::map(events,
|
||||
|e| e.get(*cursor).unwrap());
|
||||
*cursor += 1;
|
||||
Some(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventWriter<T: Event> {
|
||||
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Event> EventWriter<T> {
|
||||
pub fn write(&self, event: T) {
|
||||
let mut events = self.events.borrow_mut();
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean events of event type `T` every 2 ticks.
|
||||
pub fn event_cleaner_system<T>(tick: WorldTick, mut events: ResMut<Events<T>>) -> anyhow::Result<()>
|
||||
where
|
||||
T: Event
|
||||
{
|
||||
let last_tick = *events.last_cleared_at;
|
||||
let world_tick = **tick;
|
||||
|
||||
if last_tick + 2 < world_tick {
|
||||
events.last_cleared_at = *tick;
|
||||
events.reset_cursor = true;
|
||||
|
||||
let mut events = events.events.borrow_mut();
|
||||
events.clear_oldest();
|
||||
} else {
|
||||
events.reset_cursor = false;
|
||||
}
|
||||
|
||||
let mut events = events.events.borrow_mut();
|
||||
events.waterfall();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<T: Event> FnArgFetcher for EventReader<T> {
|
||||
type State = Arc<AtomicRefCell<usize>>;
|
||||
|
||||
type Arg<'a, 'state> = EventReader<T>;
|
||||
|
||||
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State {
|
||||
Arc::new(AtomicRefCell::new(0))
|
||||
}
|
||||
|
||||
unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
let events = world.get_resource::<Events<T>>()
|
||||
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
|
||||
|
||||
if events.reset_cursor {
|
||||
let mut state_num = state.borrow_mut();
|
||||
*state_num = 0;
|
||||
}
|
||||
|
||||
let reader = EventReader {
|
||||
events: events.events.clone(),
|
||||
cursor: state.clone(),
|
||||
};
|
||||
|
||||
reader
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: std::ptr::NonNull<lyra_ecs::World>) { }
|
||||
}
|
||||
|
||||
impl<T: Event> FnArgFetcher for EventWriter<T> {
|
||||
type State = ();
|
||||
|
||||
type Arg<'a, 'state> = EventWriter<T>;
|
||||
|
||||
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State {
|
||||
()
|
||||
}
|
||||
|
||||
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
let events = world.get_resource::<Events<T>>()
|
||||
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
|
||||
events.writer()
|
||||
}
|
||||
|
||||
fn apply_deferred(_: Self::State, _: std::ptr::NonNull<lyra_ecs::World>) { }
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull};
|
||||
|
||||
use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World};
|
||||
use lyra_math::IVec2;
|
||||
use tracing::{error, info, Level};
|
||||
use tracing_appender::non_blocking;
|
||||
use tracing_subscriber::{
|
||||
layer::SubscriberExt,
|
||||
filter,
|
||||
util::SubscriberInitExt, fmt,
|
||||
};
|
||||
|
||||
use crate::{event_cleaner_system, plugin::Plugin, render::renderer::Renderer, Event, Events, Stage, StagedExecutor};
|
||||
|
||||
#[derive(Clone, Copy, Hash, Debug)]
|
||||
pub enum GameStages {
|
||||
/// This stage runs before all other stages.
|
||||
First,
|
||||
/// This stage runs before `Update`.
|
||||
PreUpdate,
|
||||
/// This stage is where most game logic would be.
|
||||
Update,
|
||||
/// This stage is ran after `Update`.
|
||||
PostUpdate,
|
||||
/// This stage runs after all other stages.
|
||||
Last,
|
||||
}
|
||||
|
||||
impl Stage for GameStages {}
|
||||
|
||||
pub struct Controls<'a> {
|
||||
pub world: &'a mut World,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct WindowState {
|
||||
/// Indicates if the window is currently focused.
|
||||
pub focused: bool,
|
||||
/// Indicates if the window is currently occluded.
|
||||
pub occluded: bool,
|
||||
/// Indicates if the cursor is inside of the window.
|
||||
pub cursor_inside_window: bool,
|
||||
pub position: IVec2,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub(crate) renderer: OnceCell<Box<dyn Renderer>>,
|
||||
pub world: World,
|
||||
plugins: VecDeque<Box<dyn Plugin>>,
|
||||
startup_systems: VecDeque<Box<dyn System>>,
|
||||
staged_exec: StagedExecutor,
|
||||
run_fn: OnceCell<Box<dyn FnOnce(App)>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
// init logging
|
||||
let (stdout_layer, stdout_nb) = non_blocking(std::io::stdout());
|
||||
{
|
||||
let t = tracing_subscriber::registry()
|
||||
.with(fmt::layer().with_writer(stdout_layer));
|
||||
|
||||
#[cfg(feature = "tracy")]
|
||||
let t = t.with(tracing_tracy::TracyLayer::default());
|
||||
|
||||
t.with(filter::Targets::new()
|
||||
// done by prefix, so it includes all lyra subpackages
|
||||
.with_target("lyra", Level::DEBUG)
|
||||
.with_target("wgsl_preprocessor", Level::INFO)
|
||||
.with_target("wgpu", Level::WARN)
|
||||
.with_target("winit", Level::DEBUG)
|
||||
.with_default(Level::INFO))
|
||||
.init();
|
||||
}
|
||||
|
||||
// store the logger worker guard to ensure logging still happens
|
||||
let mut world = World::new();
|
||||
world.add_resource(stdout_nb);
|
||||
|
||||
// initialize ecs system stages
|
||||
let mut staged = StagedExecutor::new();
|
||||
staged.add_stage(GameStages::First);
|
||||
staged.add_stage_after(GameStages::First, GameStages::PreUpdate);
|
||||
staged.add_stage_after(GameStages::PreUpdate, GameStages::Update);
|
||||
staged.add_stage_after(GameStages::Update, GameStages::PostUpdate);
|
||||
staged.add_stage_after(GameStages::PostUpdate, GameStages::Last);
|
||||
|
||||
Self {
|
||||
renderer: OnceCell::new(),
|
||||
world,
|
||||
plugins: Default::default(),
|
||||
startup_systems: Default::default(),
|
||||
staged_exec: staged,
|
||||
run_fn: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.world.tick();
|
||||
let wptr = NonNull::from(&self.world);
|
||||
|
||||
if let Err(e) = self.staged_exec.execute(wptr, true) {
|
||||
error!("Error when executing staged systems: '{}'", e);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
self.renderer.get_mut()
|
||||
.expect("renderer was not initialized")
|
||||
.on_resize(&mut self.world, new_size);
|
||||
}
|
||||
|
||||
pub(crate) fn on_exit(&mut self) {
|
||||
info!("On exit!");
|
||||
}
|
||||
|
||||
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
|
||||
self.world.add_resource(data);
|
||||
}
|
||||
|
||||
/// Add a system to the ecs world
|
||||
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
|
||||
where
|
||||
S: IntoSystem<A>,
|
||||
<S as IntoSystem<A>>::System: 'static
|
||||
{
|
||||
self.staged_exec.add_system_to_stage(GameStages::Update, name, system.into_system(), depends);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a stage.
|
||||
///
|
||||
/// This stage could run at any moment if nothing is dependent on it.
|
||||
pub fn add_stage<T: Stage>(&mut self, stage: T) -> &mut Self {
|
||||
self.staged_exec.add_stage(stage);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a stage that executes after another one.
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `before` - The stage that will run before `after`.
|
||||
/// * `after` - The stage that will run after `before`.
|
||||
pub fn add_stage_after<T: Stage, U: Stage>(&mut self, before: T, after: U) -> &mut Self {
|
||||
self.staged_exec.add_stage_after(before, after);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a system to an already existing stage.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the stage was not already added to the executor
|
||||
pub fn add_system_to_stage<T, S, A>(&mut self, stage: T,
|
||||
name: &str, system: S, depends: &[&str]) -> &mut Self
|
||||
where
|
||||
T: Stage,
|
||||
S: IntoSystem<A>,
|
||||
<S as IntoSystem<A>>::System: 'static
|
||||
{
|
||||
self.staged_exec.add_system_to_stage(stage, name, system.into_system(), depends);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a startup system that will be ran right after plugins are setup.
|
||||
/// They will only be ran once
|
||||
pub fn with_startup_system<S>(&mut self, system: S) -> &mut Self
|
||||
where
|
||||
S: System + 'static
|
||||
{
|
||||
self.startup_systems.push_back(Box::new(system));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a plugin to the game. These are executed as they are added.
|
||||
pub fn with_plugin<P>(&mut self, mut plugin: P) -> &mut Self
|
||||
where
|
||||
P: Plugin + 'static
|
||||
{
|
||||
plugin.setup(self);
|
||||
let plugin = Box::new(plugin);
|
||||
self.plugins.push_back(plugin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the default (empty) world
|
||||
///
|
||||
/// This isn't recommended, you should create a startup system and add it to `with_startup_system`
|
||||
pub fn with_world(&mut self, world: World) -> &mut Self {
|
||||
self.world = world;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_run_fn<F>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(App) + 'static
|
||||
{
|
||||
// ignore if a runner function was already set
|
||||
let _ = self.run_fn.set(Box::new(f));
|
||||
}
|
||||
|
||||
pub fn run(mut self) {
|
||||
let f = self.run_fn.take()
|
||||
.expect("No run function set");
|
||||
f(self);
|
||||
}
|
||||
|
||||
pub fn register_event<T: Event>(&mut self) {
|
||||
let world = &mut self.world;
|
||||
// only register the event if it isn't already registered.
|
||||
if !world.has_resource::<Events<T>>() {
|
||||
world.add_resource_default::<Events<T>>();
|
||||
let sys_name = format!("{}_event_cleaner_system", std::any::type_name::<T>().to_lowercase());
|
||||
self.add_system_to_stage(GameStages::First, &sys_name, event_cleaner_system::<T>, &[]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_event<T: Event>(&mut self, event: T) {
|
||||
let world = &mut self.world;
|
||||
let mut events = world.get_resource_mut::<Events<T>>()
|
||||
.expect("missing events for event type! Must use `App::register_event` first");
|
||||
events.push_event(event);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher}, fmt::Debug};
|
||||
use std::{collections::HashMap, hash::{Hash, DefaultHasher, Hasher}, fmt::Debug};
|
||||
|
||||
use glam::Vec2;
|
||||
use lyra_ecs::World;
|
||||
use lyra_ecs::query::{Res, ResMut};
|
||||
use lyra_reflect::Reflect;
|
||||
|
||||
use crate::{plugin::Plugin, game::GameStages, EventQueue};
|
||||
use crate::{game::GameStages, plugin::Plugin, EventReader};
|
||||
|
||||
use super::{Button, InputButtons, KeyCode, MouseButton, MouseMotion};
|
||||
|
||||
|
@ -358,66 +358,61 @@ impl ActionHandler {
|
|||
|
||||
/// Returns true if the action is pressed (or was just pressed).
|
||||
///
|
||||
/// This will panic if the action name does not correspond to an action.
|
||||
pub fn is_action_pressed<L>(&self, action: L) -> bool
|
||||
/// Returns `None` if the action was not found.
|
||||
pub fn is_action_pressed<L>(&self, action: L) -> Option<bool>
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
let action = self.actions.get(&action.label_hash())
|
||||
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
|
||||
let action = self.actions.get(&action.label_hash())?;
|
||||
|
||||
matches!(action.state, ActionState::Pressed(_) | ActionState::JustPressed(_))
|
||||
Some(matches!(action.state, ActionState::Pressed(_) | ActionState::JustPressed(_)))
|
||||
}
|
||||
|
||||
/// Returns true if the action was just pressed.
|
||||
///
|
||||
/// This will panic if the action name does not correspond to an action.
|
||||
pub fn was_action_just_pressed<L>(&self, action: L) -> bool
|
||||
/// Returns `None` if the action was not found.
|
||||
pub fn was_action_just_pressed<L>(&self, action: L) -> Option<bool>
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
let action = self.actions.get(&action.label_hash())
|
||||
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
|
||||
let action = self.actions.get(&action.label_hash())?;
|
||||
|
||||
matches!(action.state, ActionState::JustPressed(_))
|
||||
Some(matches!(action.state, ActionState::JustPressed(_)))
|
||||
}
|
||||
|
||||
/// Returns true if the action was just released.
|
||||
///
|
||||
/// This will panic if the action name does not correspond to an action.
|
||||
pub fn was_action_just_released<L>(&self, action: L) -> bool
|
||||
/// Returns `None` if the action was not found.
|
||||
pub fn was_action_just_released<L>(&self, action: L) -> Option<bool>
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
let action = self.actions.get(&action.label_hash())
|
||||
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
|
||||
let action = self.actions.get(&action.label_hash())?;
|
||||
|
||||
matches!(action.state, ActionState::JustReleased)
|
||||
Some(matches!(action.state, ActionState::JustReleased))
|
||||
}
|
||||
|
||||
/// Returns an action's state.
|
||||
///
|
||||
/// This will panic if the action name does not correspond to an action.
|
||||
pub fn get_action_state<L>(&self, action: L) -> ActionState
|
||||
/// Returns `None` if the action was not found.
|
||||
pub fn get_action_state<L>(&self, action: L) -> Option<ActionState>
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
let action = self.actions.get(&action.label_hash())
|
||||
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
|
||||
let action = self.actions.get(&action.label_hash())?;
|
||||
|
||||
action.state
|
||||
Some(action.state)
|
||||
}
|
||||
|
||||
/// Returns the action's modifier if it is pressed (or was just pressed).
|
||||
/// Returns `None` if the action's state is not `ActionState::Pressed` or `ActionState::JustPressed`.
|
||||
///
|
||||
/// This will panic if the action name does not correspond to an action.
|
||||
/// Returns `None` if the action's state is not `ActionState::Pressed`, `ActionState::JustPressed`,
|
||||
/// or if the action was not found.
|
||||
pub fn get_pressed_modifier<L>(&self, action: L) -> Option<f32>
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
let action = self.actions.get(&action.label_hash())
|
||||
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
|
||||
let action = self.actions.get(&action.label_hash())?;
|
||||
|
||||
match action.state {
|
||||
ActionState::Pressed(v) | ActionState::JustPressed(v) => Some(v),
|
||||
|
@ -426,15 +421,14 @@ impl ActionHandler {
|
|||
}
|
||||
|
||||
/// Returns the action's modifier if it was just pressed.
|
||||
/// Returns `None` if the action's state is not `ActionState::JustPressed`.
|
||||
///
|
||||
/// This will panic if the action name does not correspond to an action.
|
||||
/// Returns `None` if the action's state is not `ActionState::JustPressed`,
|
||||
/// or if the action was not found.
|
||||
pub fn get_just_pressed_modifier<L>(&self, action: L) -> Option<f32>
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
let action = self.actions.get(&action.label_hash())
|
||||
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
|
||||
let action = self.actions.get(&action.label_hash())?;
|
||||
|
||||
match action.state {
|
||||
ActionState::JustPressed(v) => Some(v),
|
||||
|
@ -443,15 +437,14 @@ impl ActionHandler {
|
|||
}
|
||||
|
||||
/// Returns the action's modifier if its an updated axis.
|
||||
/// Returns `None` if the action's state is not `ActionState::Axis`.
|
||||
///
|
||||
/// This will panic if the action name does not correspond to an action.
|
||||
/// Returns `None` if the action's state is not `ActionState::Axis`,
|
||||
/// or if the action was not found.
|
||||
pub fn get_axis_modifier<L>(&self, action: L) -> Option<f32>
|
||||
where
|
||||
L: ActionLabel
|
||||
{
|
||||
let action = self.actions.get(&action.label_hash())
|
||||
.unwrap_or_else(|| panic!("Action {action:?} was not found"));
|
||||
let action = self.actions.get(&action.label_hash())?;
|
||||
|
||||
match action.state {
|
||||
ActionState::Axis(v) => Some(v),
|
||||
|
@ -497,64 +490,64 @@ impl ActionHandlerBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn actions_system(world: &mut World) -> anyhow::Result<()> {
|
||||
let keys = world.try_get_resource::<InputButtons<KeyCode>>()
|
||||
.map(|r| r.deref().clone());
|
||||
let mouse_events = world
|
||||
.try_get_resource::<EventQueue>()
|
||||
.and_then(|q| q.read_events::<MouseMotion>());
|
||||
//let mouse = world.try_get_resource()
|
||||
|
||||
//fn actions_system(world: &mut World) -> anyhow::Result<()> {
|
||||
fn actions_system(
|
||||
input_btns: Res<InputButtons<KeyCode>>,
|
||||
mut mouse_ev: EventReader<MouseMotion>,
|
||||
mut handler: ResMut<ActionHandler>,
|
||||
) -> anyhow::Result<()> {
|
||||
// clear the states of all axises each frame
|
||||
{
|
||||
let mut handler = world.try_get_resource_mut::<ActionHandler>()
|
||||
.expect("No Input Action handler was created in the world!");
|
||||
|
||||
let layout = handler.layouts.get(&handler.current_layout).expect("No active layout");
|
||||
let mapping = layout.mappings.get(&layout.active_mapping).expect("No active mapping");
|
||||
|
||||
for (action, _) in mapping.action_binds.clone().iter() {
|
||||
let action = handler.actions.get_mut(action).expect("Action name for binding is invalid!");
|
||||
if action.kind == ActionKind::Axis {
|
||||
action.state = ActionState::Axis(0.0);
|
||||
}
|
||||
let layout = handler.layouts.get(&handler.current_layout).expect("No active layout");
|
||||
let mapping = layout.mappings.get(&layout.active_mapping).expect("No active mapping");
|
||||
for (action, _) in mapping.action_binds.clone().iter() {
|
||||
let action = handler.actions.get_mut(action).expect("Action name for binding is invalid!");
|
||||
if action.kind == ActionKind::Axis {
|
||||
action.state = ActionState::Axis(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
let motion_avg = if let Some(mouse_events) = mouse_events {
|
||||
// collect all events to a list
|
||||
let mouse_events = {
|
||||
let mut evs = vec![];
|
||||
while let Some(ev) = mouse_ev.read() {
|
||||
evs.push(ev.clone());
|
||||
}
|
||||
evs
|
||||
};
|
||||
|
||||
// get the average motion from the events that were collected last frame
|
||||
let motion_avg = if !mouse_events.is_empty() {
|
||||
let count = mouse_events.len();
|
||||
let mut sum = Vec2::ZERO;
|
||||
|
||||
for mm in mouse_events {
|
||||
sum += mm.delta;
|
||||
}
|
||||
Some(sum / count as f32)
|
||||
} else { None };
|
||||
|
||||
let mut handler = world.try_get_resource_mut::<ActionHandler>()
|
||||
.expect("No Input Action handler was created in the world!");
|
||||
sum / count as f32
|
||||
} else { Vec2::new(0.0, 0.0) };
|
||||
|
||||
let layout = handler.layouts.get(&handler.current_layout).expect("No active layout");
|
||||
let mapping = layout.mappings.get(&layout.active_mapping).expect("No active mapping");
|
||||
|
||||
for (action_lbl, binds) in mapping.action_binds.clone().iter() {
|
||||
let action = handler.actions.get_mut(action_lbl).expect("Action name for binding is invalid!");
|
||||
let mut new_state = None;
|
||||
|
||||
for bind in binds.iter() {
|
||||
match bind.source {
|
||||
ActionSource::Keyboard(key) => if let Some(keys) = &keys {
|
||||
ActionSource::Keyboard(key) => {
|
||||
// JustPressed needs to be first, since is_pressed includes buttons that
|
||||
// were just pressed.
|
||||
match action.kind {
|
||||
ActionKind::Button => {
|
||||
if keys.was_just_pressed(key) {
|
||||
if input_btns.was_just_pressed(key) {
|
||||
new_state = Some(ActionState::JustPressed(bind.modifier));
|
||||
} else if keys.is_pressed(key) {
|
||||
} else if input_btns.is_pressed(key) {
|
||||
new_state = Some(ActionState::Pressed(bind.modifier));
|
||||
}
|
||||
},
|
||||
ActionKind::Axis => {
|
||||
if keys.is_pressed(key) {
|
||||
if input_btns.is_pressed(key) {
|
||||
new_state = Some(ActionState::Axis(bind.modifier));
|
||||
}
|
||||
}
|
||||
|
@ -564,7 +557,7 @@ fn actions_system(world: &mut World) -> anyhow::Result<()> {
|
|||
ActionSource::Gamepad(_, _) => todo!(),
|
||||
ActionSource::Mouse(m) => match m {
|
||||
MouseInput::Button(_) => todo!(),
|
||||
MouseInput::Axis(a) => if let Some(motion_avg) = motion_avg {
|
||||
MouseInput::Axis(a) => {
|
||||
match a {
|
||||
MouseAxis::X => {
|
||||
new_state = Some(ActionState::Axis(motion_avg.x));
|
||||
|
@ -597,7 +590,7 @@ fn actions_system(world: &mut World) -> anyhow::Result<()> {
|
|||
pub struct InputActionPlugin;
|
||||
|
||||
impl Plugin for InputActionPlugin {
|
||||
fn setup(&self, game: &mut crate::game::Game) {
|
||||
game.add_system_to_stage(GameStages::PreUpdate, "input_actions", actions_system, &[]);
|
||||
fn setup(&mut self, app: &mut crate::game::App) {
|
||||
app.add_system_to_stage(GameStages::PreUpdate, "input_actions", actions_system, &[]);
|
||||
}
|
||||
}
|
|
@ -92,10 +92,7 @@ impl<T: Button> InputButtons<T> {
|
|||
pub fn was_just_pressed(&self, button: T) -> bool {
|
||||
let hash = Self::get_button_hash(&button);
|
||||
match self.button_events.get(&hash) {
|
||||
Some(button_event) => match button_event {
|
||||
ButtonEvent::JustPressed(b) if button == *b => true,
|
||||
_ => false,
|
||||
},
|
||||
Some(button_event) => matches!(button_event, ButtonEvent::JustPressed(b) if button == *b),
|
||||
None => false
|
||||
}
|
||||
}
|
||||
|
@ -105,11 +102,8 @@ impl<T: Button> InputButtons<T> {
|
|||
/// This must be done so that a key does not stay as JustPressed between multiple ticks
|
||||
pub fn update(&mut self) {
|
||||
for bev in self.button_events.values_mut() {
|
||||
match bev {
|
||||
ButtonEvent::JustPressed(btn) => {
|
||||
*bev = ButtonEvent::Pressed(btn.clone());
|
||||
},
|
||||
_ => {},
|
||||
if let ButtonEvent::JustPressed(btn) = bev {
|
||||
*bev = ButtonEvent::Pressed(btn.clone());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -88,6 +88,8 @@ pub enum MouseButton {
|
|||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Back,
|
||||
Forward,
|
||||
Other(u16),
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,138 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use glam::Vec2;
|
||||
use lyra_ecs::query::ResMut;
|
||||
use winit::{event::{MouseScrollDelta, WindowEvent}, keyboard::PhysicalKey};
|
||||
|
||||
use crate::{game::GameStages, plugin::Plugin, winit::DeviceEventPair, EventReader, EventWriter};
|
||||
|
||||
use super::{events::*, InputButtons, KeyCode};
|
||||
|
||||
fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter<MouseScroll>, delta: &MouseScrollDelta) {
|
||||
let event = match delta {
|
||||
MouseScrollDelta::LineDelta(x, y) => MouseScroll {
|
||||
unit: MouseScrollUnit::Line(Vec2::new(*x, *y)),
|
||||
},
|
||||
MouseScrollDelta::PixelDelta(delta) => MouseScroll {
|
||||
unit: MouseScrollUnit::Pixel(Vec2::new(delta.x as f32, delta.y as f32)),
|
||||
},
|
||||
};
|
||||
|
||||
mouse_scroll_ev.write(event);
|
||||
}
|
||||
|
||||
fn write_key_event(key_buttons: &mut ResMut<InputButtons<KeyCode>>, physical_key: PhysicalKey, state: winit::event::ElementState) {
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
key_buttons.add_input_from_winit(KeyCode::from(code), state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input_system(
|
||||
mut key_code_res: ResMut<InputButtons<KeyCode>>,
|
||||
mut mouse_btn_res: ResMut<InputButtons<MouseButton>>,
|
||||
mut touches_res: ResMut<Touches>,
|
||||
window_ev: EventReader<WindowEvent>,
|
||||
device_ev: EventReader<DeviceEventPair>,
|
||||
mut mouse_scroll_ev: EventWriter<MouseScroll>,
|
||||
mouse_btn_ev: EventWriter<MouseButton>,
|
||||
mouse_exact_ev: EventWriter<MouseExact>,
|
||||
mouse_entered_ev: EventWriter<CursorEnteredWindow>,
|
||||
mouse_left_ev: EventWriter<CursorLeftWindow>,
|
||||
mouse_motion_ev: EventWriter<MouseMotion>,
|
||||
) -> anyhow::Result<()> {
|
||||
while let Some(event) = window_ev.read() {
|
||||
match event.deref() {
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
write_key_event(&mut key_code_res, event.physical_key, event.state);
|
||||
},
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
let exact = MouseExact {
|
||||
pos: Vec2::new(position.x as f32, position.y as f32)
|
||||
};
|
||||
|
||||
mouse_exact_ev.write(exact);
|
||||
},
|
||||
WindowEvent::CursorEntered { .. } => {
|
||||
mouse_entered_ev.write(CursorEnteredWindow);
|
||||
},
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
mouse_left_ev.write(CursorLeftWindow);
|
||||
},
|
||||
WindowEvent::MouseWheel { delta, .. } => {
|
||||
write_scroll_delta(&mut mouse_scroll_ev, delta);
|
||||
},
|
||||
WindowEvent::MouseInput { button, state, .. } => {
|
||||
let button_event = match button {
|
||||
winit::event::MouseButton::Left => MouseButton::Left,
|
||||
winit::event::MouseButton::Right => MouseButton::Right,
|
||||
winit::event::MouseButton::Middle => MouseButton::Middle,
|
||||
winit::event::MouseButton::Back => MouseButton::Back,
|
||||
winit::event::MouseButton::Forward => MouseButton::Forward,
|
||||
winit::event::MouseButton::Other(v) => MouseButton::Other(*v),
|
||||
};
|
||||
|
||||
mouse_btn_ev.write(button_event);
|
||||
mouse_btn_res.add_input_from_winit(button_event, *state);
|
||||
},
|
||||
WindowEvent::Touch(t) => {
|
||||
let touch = Touch {
|
||||
phase: TouchPhase::from(t.phase),
|
||||
location: Vec2::new(t.location.x as f32, t.location.y as f32),
|
||||
force: t.force.map(Force::from),
|
||||
finger_id: t.id,
|
||||
};
|
||||
|
||||
touches_res.touches.push(touch);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(device) = device_ev.read() {
|
||||
match &device.event {
|
||||
winit::event::DeviceEvent::Motion { .. } => {
|
||||
// TODO: handle device motion events
|
||||
// A todo! isn't used since these are triggered alongside MouseMotion events
|
||||
}
|
||||
winit::event::DeviceEvent::MouseMotion { delta } => {
|
||||
let delta = MouseMotion {
|
||||
delta: Vec2::new(delta.0 as f32, delta.1 as f32)
|
||||
};
|
||||
|
||||
mouse_motion_ev.write(delta);
|
||||
},
|
||||
winit::event::DeviceEvent::MouseWheel { delta } => {
|
||||
write_scroll_delta(&mut mouse_scroll_ev, delta);
|
||||
},
|
||||
winit::event::DeviceEvent::Key(key) => {
|
||||
write_key_event(&mut key_code_res, key.physical_key, key.state);
|
||||
},
|
||||
_ => {
|
||||
todo!("unhandled device event: {:?}", device.event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Plugin that runs InputSystem
|
||||
#[derive(Default)]
|
||||
pub struct InputPlugin;
|
||||
|
||||
impl Plugin for InputPlugin {
|
||||
fn setup(&mut self, app: &mut crate::game::App) {
|
||||
app.add_resource(InputButtons::<KeyCode>::default());
|
||||
app.add_resource(InputButtons::<MouseButton>::default());
|
||||
app.add_resource(Touches::default());
|
||||
|
||||
app.register_event::<MouseScroll>();
|
||||
app.register_event::<MouseButton>();
|
||||
app.register_event::<MouseMotion>();
|
||||
app.register_event::<MouseExact>();
|
||||
app.register_event::<CursorEnteredWindow>();
|
||||
app.register_event::<CursorLeftWindow>();
|
||||
|
||||
app.add_system_to_stage(GameStages::PreUpdate, "input", input_system, &[]);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
#![feature(hash_extract_if)]
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(trait_alias)]
|
||||
#![feature(map_many_mut)]
|
||||
|
||||
|
@ -9,12 +8,14 @@ pub mod game;
|
|||
pub mod render;
|
||||
pub mod resources;
|
||||
pub mod input;
|
||||
pub mod winit;
|
||||
pub mod as_any;
|
||||
pub mod plugin;
|
||||
pub mod change_tracker;
|
||||
|
||||
pub mod events;
|
||||
pub use events::*;
|
||||
pub mod sprite;
|
||||
|
||||
mod event;
|
||||
pub use event::*;
|
||||
|
||||
pub mod stage;
|
||||
pub use stage::*;
|
||||
|
@ -28,8 +29,6 @@ pub use lyra_resource as assets;
|
|||
pub use lyra_ecs as ecs;
|
||||
pub use lyra_math as math;
|
||||
pub use lyra_reflect as reflect;
|
||||
|
||||
#[cfg(feature = "scripting")]
|
||||
pub use lyra_scripting as script;
|
||||
pub use lyra_gltf as gltf;
|
||||
|
||||
pub use plugin::DefaultPlugins;
|
|
@ -1,35 +1,37 @@
|
|||
use lyra_ecs::query::ResMut;
|
||||
use lyra_ecs::CommandQueue;
|
||||
use lyra_gltf::GltfLoader;
|
||||
use lyra_resource::ResourceManager;
|
||||
|
||||
use crate::EventsPlugin;
|
||||
use crate::game::App;
|
||||
use crate::winit::{WinitPlugin, WindowPlugin};
|
||||
use crate::DeltaTimePlugin;
|
||||
use crate::game::Game;
|
||||
use crate::input::InputPlugin;
|
||||
use crate::render::window::WindowPlugin;
|
||||
|
||||
/// A Plugin is something you can add to a `Game` that can be used to define systems, or spawn initial entities.
|
||||
pub trait Plugin {
|
||||
/// Setup this plugin. This runs before the game has started
|
||||
fn setup(&self, game: &mut Game);
|
||||
/// Setup this plugin. This runs before the app has started
|
||||
fn setup(&mut self, app: &mut App);
|
||||
|
||||
fn is_ready(&self, _game: &mut Game) -> bool {
|
||||
fn is_ready(&self, app: &mut App) -> bool {
|
||||
let _ = app;
|
||||
true
|
||||
}
|
||||
|
||||
fn complete(&self, _game: &mut Game) {
|
||||
|
||||
fn complete(&self, app: &mut App) {
|
||||
let _ = app;
|
||||
}
|
||||
|
||||
fn cleanup(&self, _game: &mut Game) {
|
||||
|
||||
fn cleanup(&self, app: &mut App) {
|
||||
let _ = app;
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Plugin for P
|
||||
where P: Fn(&mut Game)
|
||||
where P: Fn(&mut App)
|
||||
{
|
||||
fn setup(&self, game: &mut Game) {
|
||||
self(game);
|
||||
fn setup(&mut self, app: &mut App) {
|
||||
self(app);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,9 +58,9 @@ impl PluginSet {
|
|||
}
|
||||
|
||||
impl Plugin for PluginSet {
|
||||
fn setup(&self, game: &mut Game) {
|
||||
for plugin in self.plugins.iter() {
|
||||
plugin.setup(game);
|
||||
fn setup(&mut self, app: &mut App) {
|
||||
for plugin in self.plugins.iter_mut() {
|
||||
plugin.setup(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,8 +100,8 @@ impl_tuple_plugin_set! { (C0, 0) (C1, 1) (C2, 2) (C3, 3) (C4, 4) (C5, 5) (C6, 6)
|
|||
pub struct ResourceManagerPlugin;
|
||||
|
||||
impl Plugin for ResourceManagerPlugin {
|
||||
fn setup(&self, game: &mut Game) {
|
||||
game.world_mut().add_resource(ResourceManager::new());
|
||||
fn setup(&mut self, app: &mut App) {
|
||||
app.world.add_resource(ResourceManager::new());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,13 +110,14 @@ impl Plugin for ResourceManagerPlugin {
|
|||
pub struct DefaultPlugins;
|
||||
|
||||
impl Plugin for DefaultPlugins {
|
||||
fn setup(&self, game: &mut Game) {
|
||||
CommandQueuePlugin.setup(game);
|
||||
EventsPlugin.setup(game);
|
||||
InputPlugin.setup(game);
|
||||
ResourceManagerPlugin.setup(game);
|
||||
WindowPlugin::default().setup(game);
|
||||
DeltaTimePlugin.setup(game);
|
||||
fn setup(&mut self, app: &mut App) {
|
||||
WinitPlugin::default().setup(app);
|
||||
CommandQueuePlugin.setup(app);
|
||||
InputPlugin.setup(app);
|
||||
ResourceManagerPlugin.setup(app);
|
||||
GltfPlugin.setup(app);
|
||||
WindowPlugin::default().setup(app);
|
||||
DeltaTimePlugin.setup(app);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +127,17 @@ impl Plugin for DefaultPlugins {
|
|||
pub struct CommandQueuePlugin;
|
||||
|
||||
impl Plugin for CommandQueuePlugin {
|
||||
fn setup(&self, game: &mut Game) {
|
||||
game.world_mut().add_resource(CommandQueue::default());
|
||||
fn setup(&mut self, app: &mut App) {
|
||||
app.world.add_resource(CommandQueue::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GltfPlugin;
|
||||
|
||||
impl Plugin for GltfPlugin {
|
||||
fn setup(&mut self, app: &mut App) {
|
||||
let man: ResMut<ResourceManager> = app.world.get_resource_or_default();
|
||||
man.register_loader::<GltfLoader>();
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ impl<T> AVec<T> {
|
|||
#[inline(always)]
|
||||
fn slot_size(&self) -> usize {
|
||||
let a = self.align - 1;
|
||||
mem::align_of::<T>() + (a) & !a
|
||||
(mem::align_of::<T>() + (a)) & !a
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
|
@ -240,6 +240,11 @@ impl<T> AVec<T> {
|
|||
self.len
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Returns the capacity of the vector.
|
||||
///
|
||||
/// The capacity is the amount of elements that the vector can store without reallocating.
|
|
@ -1,8 +1,9 @@
|
|||
use lyra_reflect::Reflect;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
use crate::{math::{Angle, OPENGL_TO_WGPU_MATRIX}, scene::CameraComponent};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
|
||||
pub enum CameraProjectionMode {
|
||||
/// 3d camera projection
|
||||
Perspective,
|
Before Width: | Height: | Size: 545 B After Width: | Height: | Size: 545 B |
|
@ -0,0 +1,101 @@
|
|||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
use tracing::instrument;
|
||||
|
||||
use super::{RenderGraphLabel, RenderGraphLabelValue};
|
||||
|
||||
/// A queued write to a GPU buffer targeting a graph slot.
|
||||
pub(crate) struct GraphBufferWrite {
|
||||
/// The name of the slot that has the resource that will be written
|
||||
pub(crate) target_slot: RenderGraphLabelValue,
|
||||
pub(crate) offset: u64,
|
||||
pub(crate) bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct RenderGraphContext<'a> {
|
||||
/// The [`wgpu::CommandEncoder`] used to encode GPU operations.
|
||||
///
|
||||
/// This is `None` during the `prepare` stage.
|
||||
pub encoder: Option<wgpu::CommandEncoder>,
|
||||
/// The gpu device that is being used.
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub(crate) buffer_writes: VecDeque<GraphBufferWrite>,
|
||||
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a>>,
|
||||
/// The label of this Node.
|
||||
pub label: RenderGraphLabelValue,
|
||||
}
|
||||
|
||||
impl<'a> RenderGraphContext<'a> {
|
||||
pub(crate) fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, encoder: Option<wgpu::CommandEncoder>, label: RenderGraphLabelValue) -> Self {
|
||||
Self {
|
||||
encoder,
|
||||
device,
|
||||
queue,
|
||||
buffer_writes: Default::default(),
|
||||
renderpass_desc: vec![],
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_render_pass(
|
||||
&'a mut self,
|
||||
desc: wgpu::RenderPassDescriptor<'a>,
|
||||
) -> wgpu::RenderPass<'a> {
|
||||
self.encoder
|
||||
.as_mut()
|
||||
.expect(
|
||||
"RenderGraphContext is missing a command encoder. This is likely \
|
||||
because you are trying to run render commands in the prepare stage.",
|
||||
)
|
||||
.begin_render_pass(&desc)
|
||||
}
|
||||
|
||||
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
|
||||
self.encoder
|
||||
.as_mut()
|
||||
.expect(
|
||||
"RenderGraphContext is missing a command encoder. This is likely \
|
||||
because you are trying to run render commands in the prepare stage.",
|
||||
)
|
||||
.begin_compute_pass(desc)
|
||||
}
|
||||
|
||||
/// Queue a data write to a buffer at that is contained in `target_slot`.
|
||||
///
|
||||
/// This does not submit the data to the GPU immediately, or add it to the `wgpu::Queue`. The
|
||||
/// data will be submitted to the GPU queue right after the prepare stage for all passes
|
||||
/// is ran.
|
||||
#[instrument(skip(self, bytes), level="trace", fields(size = bytes.len()))]
|
||||
pub fn queue_buffer_write(&mut self, target_slot: impl RenderGraphLabel, offset: u64, bytes: &[u8]) {
|
||||
self.buffer_writes.push_back(GraphBufferWrite {
|
||||
target_slot: target_slot.into(),
|
||||
offset,
|
||||
bytes: bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Queue a data write of a type that to a buffer at that is contained in `target_slot`.
|
||||
#[instrument(skip(self, bytes), level="trace", fields(size = std::mem::size_of::<T>()))]
|
||||
pub fn queue_buffer_write_with<T: bytemuck::NoUninit>(
|
||||
&mut self,
|
||||
target_slot: impl RenderGraphLabel,
|
||||
offset: u64,
|
||||
bytes: T,
|
||||
) {
|
||||
self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes));
|
||||
}
|
||||
|
||||
/// Submit the encoder to the gpu queue.
|
||||
///
|
||||
/// The `encoder` of this context will be `None` until the next node is executed, then another
|
||||
/// one will be made. You likely don't need to run this yourself until you are manually
|
||||
/// presenting a surface texture.
|
||||
pub fn submit_encoder(&mut self) {
|
||||
let en = self.encoder.take()
|
||||
.unwrap()
|
||||
.finish();
|
||||
self.queue.submit(std::iter::once(en));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,572 @@
|
|||
mod node;
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut}, collections::VecDeque, fmt::Debug, hash::Hash, rc::Rc, sync::Arc
|
||||
};
|
||||
|
||||
use lyra_ecs::World;
|
||||
pub use node::*;
|
||||
|
||||
mod passes;
|
||||
pub use passes::*;
|
||||
|
||||
mod slot_desc;
|
||||
pub use slot_desc::*;
|
||||
|
||||
mod context;
|
||||
pub use context::*;
|
||||
|
||||
mod render_target;
|
||||
pub use render_target::*;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug_span, instrument, trace, warn};
|
||||
use wgpu::CommandEncoder;
|
||||
|
||||
use super::resource::{ComputePipeline, Pass, Pipeline, RenderPipeline};
|
||||
|
||||
/// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`].
|
||||
pub trait RenderGraphLabel: Debug + 'static {
|
||||
fn rc_clone(&self) -> Rc<dyn RenderGraphLabel>;
|
||||
fn as_label_hash(&self) -> u64;
|
||||
|
||||
fn label_eq_rc(&self, other: &Rc<dyn RenderGraphLabel>) -> bool {
|
||||
self.as_label_hash() == other.as_label_hash()
|
||||
}
|
||||
|
||||
fn label_eq(&self, other: &dyn RenderGraphLabel) -> bool {
|
||||
self.as_label_hash() == other.as_label_hash()
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned [`RenderGraphLabel`].
|
||||
#[derive(Clone)]
|
||||
pub struct RenderGraphLabelValue(Rc<dyn RenderGraphLabel>);
|
||||
|
||||
impl Debug for RenderGraphLabelValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: RenderGraphLabel> From<L> for RenderGraphLabelValue {
|
||||
fn from(value: L) -> Self {
|
||||
Self(Rc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
||||
fn from(value: Rc<dyn RenderGraphLabel>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
||||
fn from(value: &Rc<dyn RenderGraphLabel>) -> Self {
|
||||
Self(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for RenderGraphLabelValue {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.0.as_label_hash());
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for RenderGraphLabelValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.label_eq_rc(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for RenderGraphLabelValue {}
|
||||
|
||||
struct NodeEntry {
|
||||
/// The Node
|
||||
inner: Arc<RefCell<dyn Node>>,
|
||||
/// The Node descriptor
|
||||
desc: Rc<RefCell<NodeDesc>>,
|
||||
/// The index of the node in the execution graph
|
||||
graph_index: petgraph::matrix_graph::NodeIndex<usize>,
|
||||
/// The Node's optional pipeline
|
||||
pipeline: Rc<RefCell<Option<Pipeline>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BindGroupEntry {
|
||||
label: RenderGraphLabelValue,
|
||||
/// BindGroup
|
||||
bg: Arc<wgpu::BindGroup>,
|
||||
/// BindGroupLayout
|
||||
layout: Option<Arc<wgpu::BindGroupLayout>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
struct ResourceSlot {
|
||||
label: RenderGraphLabelValue,
|
||||
ty: SlotType,
|
||||
value: SlotValue,
|
||||
}
|
||||
|
||||
pub struct RenderGraph {
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
slots: FxHashMap<RenderGraphLabelValue, ResourceSlot>,
|
||||
nodes: FxHashMap<RenderGraphLabelValue, NodeEntry>,
|
||||
sub_graphs: FxHashMap<RenderGraphLabelValue, RenderGraph>,
|
||||
bind_groups: FxHashMap<RenderGraphLabelValue, BindGroupEntry>,
|
||||
/// A directed graph used to determine dependencies of nodes.
|
||||
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
|
||||
view_target: Rc<RefCell<ViewTarget>>,
|
||||
shader_prepoc: wgsl_preprocessor::Processor,
|
||||
}
|
||||
|
||||
impl RenderGraph {
|
||||
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>) -> Self {
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
slots: Default::default(),
|
||||
nodes: Default::default(),
|
||||
sub_graphs: Default::default(),
|
||||
bind_groups: Default::default(),
|
||||
node_graph: Default::default(),
|
||||
view_target,
|
||||
shader_prepoc: wgsl_preprocessor::Processor::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &wgpu::Device {
|
||||
&self.device
|
||||
}
|
||||
|
||||
/// Add a [`Node`] to the RenderGraph.
|
||||
///
|
||||
/// When the node is added, its [`Node::desc`] method will be executed.
|
||||
///
|
||||
/// Additionally, all [`Slot`](node::NodeSlot)s of the node will be iterated,
|
||||
/// 1. Ensuring that there are no two slots of the same name, with different value types
|
||||
/// 2. Changing the id of insert slots to match the id of the output slot of the same name.
|
||||
/// * This means that the id of insert slots **ARE NOT STABLE**. **DO NOT** rely on them to
|
||||
/// not change. The IDs of output slots do stay the same.
|
||||
/// 3. Ensuring that no two slots share the same ID when the names do not match.
|
||||
#[instrument(skip(self, node), level = "debug")]
|
||||
pub fn add_node<P: Node>(&mut self, label: impl RenderGraphLabel, mut node: P) {
|
||||
let mut desc = node.desc(self);
|
||||
|
||||
// collect all the slots of the node
|
||||
for slot in &mut desc.slots {
|
||||
if let Some(other) = self
|
||||
.slots
|
||||
.get_mut(&slot.label)
|
||||
{
|
||||
debug_assert_eq!(
|
||||
slot.ty, other.ty,
|
||||
"slot {:?} in node {:?} does not match existing slot of same name",
|
||||
slot.label, label
|
||||
);
|
||||
} else {
|
||||
debug_assert!(!self.slots.contains_key(&slot.label),
|
||||
"Reuse of id detected in render graph! Node: {:?}, slot: {:?}",
|
||||
label, slot.label,
|
||||
);
|
||||
|
||||
let res_slot = ResourceSlot {
|
||||
label: slot.label.clone(),
|
||||
ty: slot.ty,
|
||||
value: slot.value.clone().unwrap_or(SlotValue::None),
|
||||
};
|
||||
|
||||
self.slots.insert(slot.label.clone(), res_slot);
|
||||
}
|
||||
}
|
||||
|
||||
// get clones of the bind groups and layouts
|
||||
for (label, bg, bgl) in &desc.bind_groups {
|
||||
self.bind_groups.insert(label.clone(), BindGroupEntry {
|
||||
label: label.clone(),
|
||||
bg: bg.clone(),
|
||||
layout: bgl.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let label: RenderGraphLabelValue = label.into();
|
||||
let index = self.node_graph.add_node(label.clone());
|
||||
|
||||
self.nodes.insert(
|
||||
label,
|
||||
NodeEntry {
|
||||
inner: Arc::new(RefCell::new(node)),
|
||||
desc: Rc::new(RefCell::new(desc)),
|
||||
graph_index: index,
|
||||
pipeline: Rc::new(RefCell::new(None)),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates all buffers required for the nodes.
|
||||
///
|
||||
/// This only needs to be ran when the [`Node`]s in the graph change, or they are removed or
|
||||
/// added.
|
||||
#[instrument(skip(self, device))]
|
||||
pub fn setup(&mut self, device: &wgpu::Device) {
|
||||
// For all nodes, create their pipelines
|
||||
for node in self.nodes.values_mut() {
|
||||
let desc = (*node.desc).borrow();
|
||||
if let Some(pipeline_desc) = &desc.pipeline_desc {
|
||||
let pipeline = match desc.ty {
|
||||
NodeType::Render => Pipeline::Render(RenderPipeline::create(
|
||||
device,
|
||||
pipeline_desc
|
||||
.as_render_pipeline_descriptor()
|
||||
.expect("got compute pipeline descriptor in a render node"),
|
||||
)),
|
||||
NodeType::Compute => Pipeline::Compute(ComputePipeline::create(
|
||||
device,
|
||||
pipeline_desc
|
||||
.as_compute_pipeline_descriptor()
|
||||
.expect("got render pipeline descriptor in a compute node"),
|
||||
)),
|
||||
NodeType::Presenter | NodeType::Node | NodeType::Graph => {
|
||||
panic!("Present or Node RenderGraph nodes should not have a pipeline descriptor!");
|
||||
},
|
||||
};
|
||||
|
||||
drop(desc);
|
||||
|
||||
let mut node_pipeline = node.pipeline.borrow_mut();
|
||||
*node_pipeline = Some(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
for sub in self.sub_graphs.values_mut() {
|
||||
sub.setup(device);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, world))]
|
||||
pub fn prepare(&mut self, world: &mut World) {
|
||||
let mut buffer_writes = VecDeque::<GraphBufferWrite>::new();
|
||||
// reserve some buffer writes. not all nodes write so half the amount of them is probably
|
||||
// fine.
|
||||
buffer_writes.reserve(self.nodes.len() / 2);
|
||||
|
||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.node_graph, None)
|
||||
.expect("RenderGraph had cycled!")
|
||||
.iter()
|
||||
.map(|i| self.node_graph[*i].clone())
|
||||
.collect();
|
||||
|
||||
while let Some(node_label) = sorted.pop_front() {
|
||||
let node = self.nodes.get(&node_label).unwrap();
|
||||
let device = self.device.clone();
|
||||
let queue = self.queue.clone();
|
||||
|
||||
let inner = node.inner.clone();
|
||||
let mut inner = inner.borrow_mut();
|
||||
|
||||
let mut context = RenderGraphContext::new(device, queue, None, node_label.clone());
|
||||
inner.prepare(self, world, &mut context);
|
||||
buffer_writes.append(&mut context.buffer_writes);
|
||||
}
|
||||
|
||||
{
|
||||
// Queue all buffer writes to the gpu
|
||||
let s = debug_span!("queue_buffer_writes");
|
||||
let _e = s.enter();
|
||||
|
||||
while let Some(bufwr) = buffer_writes.pop_front() {
|
||||
let slot = self
|
||||
.slots
|
||||
.get(&bufwr.target_slot)
|
||||
.unwrap_or_else(|| panic!("Failed to find slot '{:?}' for buffer write",
|
||||
bufwr.target_slot));
|
||||
let buf = slot
|
||||
.value
|
||||
.as_buffer()
|
||||
.unwrap_or_else(|| panic!("Slot '{:?}' is not a buffer", bufwr.target_slot));
|
||||
|
||||
self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_encoder(&self) -> CommandEncoder {
|
||||
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("graph encoder"),
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub fn render(&mut self) {
|
||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.node_graph, None)
|
||||
.expect("RenderGraph had cycled!")
|
||||
.iter()
|
||||
.map(|i| self.node_graph[*i].clone())
|
||||
.collect();
|
||||
|
||||
// A bit of 'encoder hot potato' is played using this.
|
||||
// Although the encoder is an option, its only an option so ownership of it can be given
|
||||
// to the context for the time of the node execution.
|
||||
// After the node is executed, the encoder is taken back. If the node is a presenter node,
|
||||
// the encoder will be submitted and a new one will be made.
|
||||
let mut encoder = Some(self.create_encoder());
|
||||
|
||||
while let Some(node_label) = sorted.pop_front() {
|
||||
let node = self.nodes.get(&node_label).unwrap();
|
||||
let node_inn = node.inner.clone();
|
||||
|
||||
let node_desc = node.desc.clone();
|
||||
let node_desc = (*node_desc).borrow();
|
||||
|
||||
// clone of the Rc's is required to appease the borrow checker
|
||||
let device = self.device.clone();
|
||||
let queue = self.queue.clone();
|
||||
|
||||
// create a new encoder if the last one was submitted
|
||||
if encoder.is_none() {
|
||||
encoder = Some(self.create_encoder());
|
||||
}
|
||||
|
||||
let mut context = RenderGraphContext::new(device, queue, encoder.take(), node_label.clone());
|
||||
|
||||
trace!("Executing {:?}", node_label.0);
|
||||
let mut inner = node_inn.borrow_mut();
|
||||
inner.execute(self, &node_desc, &mut context);
|
||||
|
||||
// take back the encoder from the context
|
||||
encoder = context.encoder;
|
||||
}
|
||||
|
||||
if let Some(encoder) = encoder {
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slot_value<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&SlotValue> {
|
||||
self.slots.get(&label.into()).map(|s| &s.value)
|
||||
}
|
||||
|
||||
pub fn slot_value_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut SlotValue> {
|
||||
self.slots.get_mut(&label.into()).map(|s| &mut s.value)
|
||||
}
|
||||
|
||||
pub fn node_desc<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<NodeDesc>> {
|
||||
self.nodes.get(&label.into()).map(|s| (*s.desc).borrow())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn pipeline<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<Pipeline>> {
|
||||
self.nodes.get(&label.into())
|
||||
.and_then(|p| {
|
||||
let v = p.pipeline.borrow();
|
||||
|
||||
#[allow(clippy::manual_map)]
|
||||
match &*v {
|
||||
Some(_) => Some(Ref::map(v, |p| p.as_ref().unwrap())),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroup>> {
|
||||
self.bind_groups.get(&label.into()).map(|e| &e.bg)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroup> {
|
||||
let l = label.into();
|
||||
self.try_bind_group(l.clone()).unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroupLayout>> {
|
||||
self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroupLayout> {
|
||||
let l = label.into();
|
||||
self.try_bind_group_layout(l.clone())
|
||||
.unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))
|
||||
}
|
||||
|
||||
pub fn add_edge(&mut self, from: impl RenderGraphLabel, to: impl RenderGraphLabel)
|
||||
{
|
||||
let from = RenderGraphLabelValue::from(from);
|
||||
let to = RenderGraphLabelValue::from(to);
|
||||
|
||||
let from_idx = self
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|p| *p.0 == from)
|
||||
.map(|p| p.1.graph_index)
|
||||
.expect("Failed to find from node");
|
||||
let to_idx = self
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|p| *p.0 == to)
|
||||
.map(|p| p.1.graph_index)
|
||||
.expect("Failed to find to node");
|
||||
|
||||
debug_assert_ne!(from_idx, to_idx, "cannot add edges between the same node");
|
||||
|
||||
self.node_graph.add_edge(from_idx, to_idx, ());
|
||||
}
|
||||
|
||||
/// Utility method for setting the bind groups for a node.
|
||||
///
|
||||
/// The parameter `bind_groups` can be used to specify the labels of a bind group, and the
|
||||
/// index of the bind group in the pipeline for the node. If a bind group of the provided
|
||||
/// name is not found in the graph, a panic will occur.
|
||||
///
|
||||
/// # Example:
|
||||
/// ```nobuild
|
||||
/// graph.set_bind_groups(
|
||||
/// &mut pass,
|
||||
/// &[
|
||||
/// // retrieves the `BasePassSlots::DepthTexture` bind group and sets the index 0 in the
|
||||
/// // node to it.
|
||||
/// (&BaseNodeSlots::DepthTexture, 0),
|
||||
/// (&BaseNodeSlots::Camera, 1),
|
||||
/// (&LightBaseNodeSlots::Lights, 2),
|
||||
/// (&LightCullComputeNodeSlots::LightIndicesGridGroup, 3),
|
||||
/// (&BaseNodeSlots::ScreenSize, 4),
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a bind group of a provided name is not found.
|
||||
pub fn set_bind_groups<'a, P: Pass<'a>>(
|
||||
&'a self,
|
||||
pass: &mut P,
|
||||
bind_groups: &[(&dyn RenderGraphLabel, u32)],
|
||||
) {
|
||||
for (label, index) in bind_groups {
|
||||
let bg = self
|
||||
.bind_group(label.rc_clone());
|
||||
|
||||
pass.set_bind_group(*index, bg, &[]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub_graph_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut RenderGraph> {
|
||||
self.sub_graphs.get_mut(&label.into())
|
||||
}
|
||||
|
||||
/// Add a sub graph.
|
||||
///
|
||||
/// > Note: the sub graph is not ran unless you add a node that executes it. See [`SubGraphNode`].
|
||||
pub fn add_sub_graph<L: Into<RenderGraphLabelValue>>(&mut self, label: L, sub: RenderGraph) {
|
||||
self.sub_graphs.insert(label.into(), sub);
|
||||
}
|
||||
|
||||
/// Clone rendering resources (slots, bind groups, etc.) to a sub graph.
|
||||
fn clone_resources_to_sub(&mut self, sub_graph: RenderGraphLabelValue, slots: Vec<RenderGraphLabelValue>) {
|
||||
// instead of inserting the slots to the sub graph as they are extracted from the parent graph,
|
||||
// they are done separately to make the borrow checker happy. If this is not done,
|
||||
// the borrow checker complains about multiple mutable borrows (or an inmutable borrow
|
||||
// while mutable borrowing) to self; caused by borrowing the sub graph from self, and
|
||||
// self.slots.
|
||||
let mut collected_slots = VecDeque::new();
|
||||
let mut collected_bind_groups = VecDeque::new();
|
||||
|
||||
for slot in slots.iter() {
|
||||
let mut found_res = false;
|
||||
|
||||
// Since slots and bind groups may go by the same label,
|
||||
// there must be a way to collect both of them. A flag variable is used to detect
|
||||
// if neither was found.
|
||||
|
||||
if let Some(slot_res) = self.slots.get(slot) {
|
||||
collected_slots.push_back(slot_res.clone());
|
||||
found_res = true;
|
||||
}
|
||||
|
||||
if let Some(bg_res) = self.bind_groups.get(slot) {
|
||||
collected_bind_groups.push_back(bg_res.clone());
|
||||
found_res = true;
|
||||
}
|
||||
|
||||
if !found_res {
|
||||
panic!("sub graph is missing {:?} input slot or bind group", slot);
|
||||
}
|
||||
}
|
||||
|
||||
let sg = self.sub_graph_mut(sub_graph.clone()).unwrap();
|
||||
while let Some(res) = collected_slots.pop_front() {
|
||||
sg.slots.insert(res.label.clone(), res);
|
||||
}
|
||||
|
||||
while let Some(bg) = collected_bind_groups.pop_front() {
|
||||
sg.bind_groups.insert(bg.label.clone(), bg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_target(&self) -> Ref<ViewTarget> {
|
||||
self.view_target.borrow()
|
||||
}
|
||||
|
||||
pub fn view_target_mut(&self) -> RefMut<ViewTarget> {
|
||||
self.view_target.borrow_mut()
|
||||
}
|
||||
|
||||
/// Register a shader with the preprocessor.
|
||||
///
|
||||
/// This step also parses the shader and will return errors if it failed to parse.
|
||||
///
|
||||
/// Returns: The shader module import path if the module specified one.
|
||||
#[inline(always)]
|
||||
pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> {
|
||||
self.shader_prepoc.parse_module(shader_src)
|
||||
}
|
||||
|
||||
/// Preprocess a shader, returning the source.
|
||||
#[inline(always)]
|
||||
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
|
||||
self.shader_prepoc.preprocess_module(shader_path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubGraphNode {
|
||||
subg: RenderGraphLabelValue,
|
||||
slots: Vec<RenderGraphLabelValue>,
|
||||
}
|
||||
|
||||
impl SubGraphNode {
|
||||
pub fn new<L: Into<RenderGraphLabelValue>>(sub_label: L, slot_labels: Vec<RenderGraphLabelValue>) -> Self {
|
||||
Self {
|
||||
subg: sub_label.into(),
|
||||
slots: slot_labels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for SubGraphNode {
|
||||
fn desc(&mut self, _: &mut RenderGraph) -> NodeDesc {
|
||||
NodeDesc::new(NodeType::Graph, None, vec![])
|
||||
}
|
||||
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, _: &mut RenderGraphContext) {
|
||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
||||
|
||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
||||
sg.prepare(world);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut RenderGraph,
|
||||
_: &NodeDesc,
|
||||
_: &mut RenderGraphContext,
|
||||
) {
|
||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
||||
|
||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
||||
sg.render();
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc};
|
||||
use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc, sync::Arc};
|
||||
|
||||
use bind_match::bind_match;
|
||||
use lyra_ecs::World;
|
||||
|
||||
use crate::render::resource::PipelineDescriptor;
|
||||
|
||||
use super::{RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget};
|
||||
use super::{Frame, RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
pub enum NodeType {
|
||||
|
@ -18,6 +18,8 @@ pub enum NodeType {
|
|||
Render,
|
||||
/// A node that presents render results to a render target.
|
||||
Presenter,
|
||||
/// A node that represents a sub-graph.
|
||||
Graph,
|
||||
}
|
||||
|
||||
impl NodeType {
|
||||
|
@ -28,6 +30,7 @@ impl NodeType {
|
|||
NodeType::Compute => true,
|
||||
NodeType::Render => true,
|
||||
NodeType::Presenter => false,
|
||||
NodeType::Graph => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,25 +43,35 @@ pub enum SlotType {
|
|||
Texture,
|
||||
Buffer,
|
||||
RenderTarget,
|
||||
Frame,
|
||||
}
|
||||
|
||||
/// The value of a slot in a [`Node`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub enum SlotValue {
|
||||
/// This slot doesn't have any value
|
||||
None,
|
||||
/// The value will be set during a later phase of the render graph. To see the type of value
|
||||
/// this will be set to, see the slots type.
|
||||
Lazy,
|
||||
TextureView(Rc<wgpu::TextureView>),
|
||||
TextureView(Arc<wgpu::TextureView>),
|
||||
Sampler(Rc<wgpu::Sampler>),
|
||||
Texture(Rc<wgpu::Texture>),
|
||||
Buffer(Rc<wgpu::Buffer>),
|
||||
Texture(Arc<wgpu::Texture>),
|
||||
Buffer(Arc<wgpu::Buffer>),
|
||||
RenderTarget(Rc<RefCell<RenderTarget>>),
|
||||
Frame(Rc<RefCell<Option<Frame>>>),
|
||||
}
|
||||
|
||||
impl SlotValue {
|
||||
pub fn as_texture_view(&self) -> Option<&Rc<wgpu::TextureView>> {
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub fn is_lazy(&self) -> bool {
|
||||
matches!(self, Self::Lazy)
|
||||
}
|
||||
|
||||
pub fn as_texture_view(&self) -> Option<&Arc<wgpu::TextureView>> {
|
||||
bind_match!(self, Self::TextureView(v) => v)
|
||||
}
|
||||
|
||||
|
@ -66,11 +79,11 @@ impl SlotValue {
|
|||
bind_match!(self, Self::Sampler(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_texture(&self) -> Option<&Rc<wgpu::Texture>> {
|
||||
pub fn as_texture(&self) -> Option<&Arc<wgpu::Texture>> {
|
||||
bind_match!(self, Self::Texture(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_buffer(&self) -> Option<&Rc<wgpu::Buffer>> {
|
||||
pub fn as_buffer(&self) -> Option<&Arc<wgpu::Buffer>> {
|
||||
bind_match!(self, Self::Buffer(v) => v)
|
||||
}
|
||||
|
||||
|
@ -81,6 +94,14 @@ impl SlotValue {
|
|||
pub fn as_render_target_mut(&mut self) -> Option<RefMut<RenderTarget>> {
|
||||
bind_match!(self, Self::RenderTarget(v) => v.borrow_mut())
|
||||
}
|
||||
|
||||
pub fn as_frame(&self) -> Option<Ref<Option<Frame>>> {
|
||||
bind_match!(self, Self::Frame(v) => v.borrow())
|
||||
}
|
||||
|
||||
pub fn as_frame_mut(&mut self) -> Option<RefMut<Option<Frame>>> {
|
||||
bind_match!(self, Self::Frame(v) => v.borrow_mut())
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlotAttribute {
|
||||
|
@ -132,6 +153,7 @@ pub struct RenderGraphPipelineInfo {
|
|||
pub multiview: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl RenderGraphPipelineInfo {
|
||||
pub fn new(
|
||||
label: &str,
|
||||
|
@ -147,7 +169,7 @@ impl RenderGraphPipelineInfo {
|
|||
label: Some(label.to_string()),
|
||||
bind_group_layouts: bind_group_layouts
|
||||
.into_iter()
|
||||
.map(|bgl| Rc::new(bgl))
|
||||
.map(Rc::new)
|
||||
.collect(),
|
||||
vertex,
|
||||
primitive,
|
||||
|
@ -175,8 +197,8 @@ pub struct NodeDesc {
|
|||
/// This makes the bind groups accessible to other Nodes.
|
||||
pub bind_groups: Vec<(
|
||||
RenderGraphLabelValue,
|
||||
Rc<wgpu::BindGroup>,
|
||||
Option<Rc<wgpu::BindGroupLayout>>,
|
||||
Arc<wgpu::BindGroup>,
|
||||
Option<Arc<wgpu::BindGroupLayout>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
|
@ -185,7 +207,7 @@ impl NodeDesc {
|
|||
pub fn new(
|
||||
pass_type: NodeType,
|
||||
pipeline_desc: Option<PipelineDescriptor>,
|
||||
bind_groups: Vec<(&dyn RenderGraphLabel, Rc<wgpu::BindGroup>, Option<Rc<wgpu::BindGroupLayout>>)>,
|
||||
bind_groups: Vec<(&dyn RenderGraphLabel, Arc<wgpu::BindGroup>, Option<Arc<wgpu::BindGroupLayout>>)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ty: pass_type,
|
||||
|
@ -342,13 +364,13 @@ impl NodeDesc {
|
|||
/// describes all resources the node requires for execution during the `execute` phase.
|
||||
pub trait Node: 'static {
|
||||
/// Retrieve a descriptor of the Node.
|
||||
fn desc<'a, 'b>(&'a mut self, graph: &'b mut RenderGraph) -> NodeDesc;
|
||||
fn desc(&mut self, graph: &mut RenderGraph) -> NodeDesc;
|
||||
|
||||
/// Prepare the node for rendering.
|
||||
///
|
||||
/// This phase runs before `execute` and is meant to be used to collect data from the World
|
||||
/// and write to GPU buffers.
|
||||
fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext);
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext);
|
||||
|
||||
/// Execute the node.
|
||||
///
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::UVec2;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
@ -9,8 +9,7 @@ use crate::{
|
|||
render::{
|
||||
camera::{CameraUniform, RenderCamera},
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeSlot,
|
||||
NodeType, RenderTarget, SlotAttribute, SlotType, SlotValue,
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
},
|
||||
render_buffer::BufferWrapper, texture::RenderTexture,
|
||||
},
|
||||
|
@ -25,8 +24,6 @@ pub enum BasePassSlots {
|
|||
DepthTexture,
|
||||
ScreenSize,
|
||||
Camera,
|
||||
MainRenderTarget,
|
||||
WindowTextureView,
|
||||
DepthTextureView,
|
||||
}
|
||||
|
||||
|
@ -35,27 +32,12 @@ pub enum BasePassSlots {
|
|||
/// screen size buffer, camera buffer,
|
||||
#[derive(Default)]
|
||||
pub struct BasePass {
|
||||
/// Temporary storage for the main render target
|
||||
///
|
||||
/// This should be Some when the pass is first created then after its added to
|
||||
/// the render graph it will be None and stay None.
|
||||
temp_render_target: Option<RenderTarget>,
|
||||
screen_size: glam::UVec2,
|
||||
screen_size: UVec2,
|
||||
}
|
||||
|
||||
impl BasePass {
|
||||
pub fn new(surface: wgpu::Surface, surface_config: wgpu::SurfaceConfiguration) -> Self {
|
||||
let size = glam::UVec2::new(surface_config.width, surface_config.height);
|
||||
|
||||
Self {
|
||||
temp_render_target: Some(RenderTarget {
|
||||
surface,
|
||||
surface_config,
|
||||
current_texture: None,
|
||||
}),
|
||||
screen_size: size,
|
||||
..Default::default()
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,11 +46,8 @@ impl Node for BasePass {
|
|||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let render_target = self.temp_render_target.take().unwrap();
|
||||
self.screen_size = UVec2::new(
|
||||
render_target.surface_config.width,
|
||||
render_target.surface_config.height,
|
||||
);
|
||||
let vt = graph.view_target();
|
||||
self.screen_size = vt.size();
|
||||
|
||||
let (screen_size_bgl, screen_size_bg, screen_size_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
|
@ -76,9 +55,9 @@ impl Node for BasePass {
|
|||
.visibility(wgpu::ShaderStages::COMPUTE)
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[self.screen_size])
|
||||
.finish_parts(&graph.device());
|
||||
let screen_size_bgl = Rc::new(screen_size_bgl);
|
||||
let screen_size_bg = Rc::new(screen_size_bg);
|
||||
.finish_parts(graph.device());
|
||||
let screen_size_bgl = Arc::new(screen_size_bgl);
|
||||
let screen_size_bg = Arc::new(screen_size_bg);
|
||||
|
||||
let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
|
@ -86,18 +65,18 @@ impl Node for BasePass {
|
|||
.visibility(wgpu::ShaderStages::all())
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[CameraUniform::default()])
|
||||
.finish_parts(&graph.device());
|
||||
let camera_bgl = Rc::new(camera_bgl);
|
||||
let camera_bg = Rc::new(camera_bg);
|
||||
.finish_parts(graph.device());
|
||||
let camera_bgl = Arc::new(camera_bgl);
|
||||
let camera_bg = Arc::new(camera_bg);
|
||||
|
||||
// create the depth texture using the utility struct, then take all the required fields
|
||||
let mut depth_texture = RenderTexture::create_depth_texture(&graph.device(), &render_target.surface_config, "depth_texture");
|
||||
let mut depth_texture = RenderTexture::create_depth_texture(graph.device(), self.screen_size, "depth_texture");
|
||||
depth_texture.create_bind_group(&graph.device);
|
||||
|
||||
let dt_bg_pair = depth_texture.bindgroup_pair.unwrap();
|
||||
let depth_texture_bg = Rc::new(dt_bg_pair.bindgroup);
|
||||
let depth_texture_bg = Arc::new(dt_bg_pair.bindgroup);
|
||||
let depth_texture_bgl = dt_bg_pair.layout;
|
||||
let depth_texture_view = Rc::new(depth_texture.view);
|
||||
let depth_texture_view = Arc::new(depth_texture.view);
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Node,
|
||||
|
@ -115,19 +94,6 @@ impl Node for BasePass {
|
|||
],
|
||||
);
|
||||
|
||||
desc.add_slot(NodeSlot {
|
||||
ty: SlotType::RenderTarget,
|
||||
attribute: SlotAttribute::Output,
|
||||
label: BasePassSlots::MainRenderTarget.into(),
|
||||
value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(
|
||||
render_target,
|
||||
)))),
|
||||
});
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::WindowTextureView,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Lazy),
|
||||
);
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::DepthTextureView,
|
||||
SlotAttribute::Output,
|
||||
|
@ -136,21 +102,23 @@ impl Node for BasePass {
|
|||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(screen_size_buf))),
|
||||
Some(SlotValue::Buffer(Arc::new(screen_size_buf))),
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::Camera,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(camera_buf))),
|
||||
Some(SlotValue::Buffer(Arc::new(camera_buf))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
if let Some(camera) = world.view_iter::<&mut CameraComponent>().next() {
|
||||
let screen_size = graph.view_target().size();
|
||||
|
||||
let mut render_cam =
|
||||
RenderCamera::new(PhysicalSize::new(self.screen_size.x, self.screen_size.y));
|
||||
RenderCamera::new(PhysicalSize::new(screen_size.x, screen_size.y));
|
||||
let uniform = render_cam.calc_view_projection(&camera);
|
||||
|
||||
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform)
|
||||
|
@ -165,35 +133,19 @@ impl Node for BasePass {
|
|||
_desc: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let tv_slot = graph
|
||||
.slot_value_mut(BasePassSlots::MainRenderTarget)
|
||||
.expect("somehow the main render target slot is missing");
|
||||
let mut rt = tv_slot.as_render_target_mut().unwrap();
|
||||
debug_assert!(
|
||||
let mut vt = graph.view_target_mut();
|
||||
vt.primary.create_frame();
|
||||
vt.primary.create_frame_view();
|
||||
/* debug_assert!(
|
||||
!rt.current_texture.is_some(),
|
||||
"main render target surface was not presented!"
|
||||
);
|
||||
); */
|
||||
|
||||
// update the screen size buffer if the size changed.
|
||||
if rt.surface_config.width != self.screen_size.x
|
||||
|| rt.surface_config.height != self.screen_size.y
|
||||
{
|
||||
self.screen_size = UVec2::new(rt.surface_config.width, rt.surface_config.height);
|
||||
let rt_size = vt.size();
|
||||
if rt_size != self.screen_size {
|
||||
self.screen_size = rt_size;
|
||||
context.queue_buffer_write_with(BasePassSlots::ScreenSize, 0, self.screen_size)
|
||||
}
|
||||
|
||||
let surface_tex = rt.surface.get_current_texture().unwrap();
|
||||
let view = surface_tex
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
rt.current_texture = Some(surface_tex);
|
||||
drop(rt); // must be manually dropped for borrow checker when getting texture view slot
|
||||
|
||||
// store the surface texture to the slot
|
||||
let tv_slot = graph
|
||||
.slot_value_mut(BasePassSlots::WindowTextureView)
|
||||
.expect("somehow the window texture view slot is missing");
|
||||
*tv_slot = SlotValue::TextureView(Rc::new(view));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
use std::{collections::HashMap, rc::Rc, sync::Arc};
|
||||
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::{
|
||||
graph::{Node, NodeDesc, NodeType},
|
||||
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct FxaaPassLabel;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FxaaPass {
|
||||
target_sampler: Option<wgpu::Sampler>,
|
||||
bgl: Option<Arc<wgpu::BindGroupLayout>>,
|
||||
/// Store bind groups for the input textures.
|
||||
/// The texture may change due to resizes, or changes to the view target chain
|
||||
/// from other nodes.
|
||||
bg_cache: HashMap<wgpu::Id<wgpu::TextureView>, wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
impl FxaaPass {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for FxaaPass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let device = &graph.device;
|
||||
|
||||
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("fxaa_bgl"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let bgl = Arc::new(bgl);
|
||||
self.bgl = Some(bgl.clone());
|
||||
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("fxaa sampler"),
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("fxaa_shader".into()),
|
||||
source: include_str!("../../shaders/fxaa.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let vt = graph.view_target();
|
||||
|
||||
NodeDesc::new(
|
||||
NodeType::Render,
|
||||
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
|
||||
label: Some("fxaa_pass".into()),
|
||||
layouts: vec![bgl.clone()],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: VertexState {
|
||||
module: shader.clone(),
|
||||
entry_point: "vs_main".into(),
|
||||
buffers: vec![],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: shader,
|
||||
entry_point: "fs_main".into(),
|
||||
targets: vec![Some(wgpu::ColorTargetState {
|
||||
format: vt.format(),
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
depth_stencil: None,
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
})),
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &mut lyra_ecs::World,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
//todo!()
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let pipeline = graph
|
||||
.pipeline(context.label.clone())
|
||||
.expect("Failed to find pipeline for FxaaPass");
|
||||
|
||||
let mut vt = graph.view_target_mut();
|
||||
let chain = vt.get_chain();
|
||||
let source_view = chain.source.frame_view.as_ref().unwrap();
|
||||
let dest_view = chain.dest.frame_view.as_ref().unwrap();
|
||||
|
||||
let bg = self
|
||||
.bg_cache
|
||||
.entry(source_view.global_id())
|
||||
.or_insert_with(|| {
|
||||
graph
|
||||
.device()
|
||||
.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("fxaa_bg"),
|
||||
layout: self.bgl.as_ref().unwrap(),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(source_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(
|
||||
self.target_sampler.as_ref().unwrap(),
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
{
|
||||
let encoder = context.encoder.as_mut().unwrap();
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("fxaa_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: dest_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None, // TODO: occlusion queries
|
||||
});
|
||||
pass.set_pipeline(pipeline.as_render());
|
||||
|
||||
pass.set_bind_group(0, bg, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::graph::{Node, NodeDesc, NodeSlot, NodeType};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct InitNodeLabel;
|
||||
|
||||
pub struct InitNode {
|
||||
slots: Vec<NodeSlot>,
|
||||
}
|
||||
|
||||
impl Node for InitNode {
|
||||
fn desc(&mut self, _: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc {
|
||||
let mut desc = NodeDesc::new(NodeType::Node, None, vec![]);
|
||||
// the slots can just be cloned since the slot attribute doesn't really matter much.
|
||||
desc.slots = self.slots.clone();
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _: &mut crate::render::graph::RenderGraph, _: &mut lyra_ecs::World, _: &mut crate::render::graph::RenderGraphContext) {
|
||||
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
|
@ -2,8 +2,7 @@ use lyra_game_derive::RenderGraphLabel;
|
|||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute,
|
||||
SlotValue,
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
},
|
||||
light::LightUniformBuffers,
|
||||
};
|
||||
|
@ -58,10 +57,10 @@ impl Node for LightBasePass {
|
|||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
fn prepare(&mut self, _graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
let tick = world.current_tick();
|
||||
let lights = self.light_buffers.as_mut().unwrap();
|
||||
lights.update_lights(context.queue, tick, world);
|
||||
lights.update_lights(&context.queue, tick, world);
|
||||
}
|
||||
|
||||
fn execute(
|
|
@ -1,15 +1,14 @@
|
|||
use std::{mem, rc::Rc};
|
||||
use std::{mem, rc::Rc, sync::Arc};
|
||||
|
||||
use glam::Vec2Swizzles;
|
||||
use lyra_ecs::World;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute,
|
||||
SlotValue,
|
||||
},
|
||||
resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader},
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor, Shader}
|
||||
};
|
||||
|
||||
use super::{BasePassSlots, LightBasePassSlots};
|
||||
|
@ -27,12 +26,14 @@ pub enum LightCullComputePassSlots {
|
|||
|
||||
pub struct LightCullComputePass {
|
||||
workgroup_size: glam::UVec2,
|
||||
pipeline: Option<ComputePipeline>,
|
||||
}
|
||||
|
||||
impl LightCullComputePass {
|
||||
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
|
||||
Self {
|
||||
workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height),
|
||||
pipeline: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,19 +43,6 @@ impl Node for LightCullComputePass {
|
|||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("light_cull_comp_shader".into()),
|
||||
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
|
||||
});
|
||||
|
||||
// get the size of the work group for the grid
|
||||
let main_rt = graph
|
||||
.slot_value(BasePassSlots::MainRenderTarget)
|
||||
.and_then(|s| s.as_render_target())
|
||||
.expect("missing main render target");
|
||||
self.workgroup_size =
|
||||
glam::UVec2::new(main_rt.surface_config.width, main_rt.surface_config.height);
|
||||
|
||||
// initialize some buffers with empty data
|
||||
let mut contents = Vec::<u8>::new();
|
||||
let contents_len =
|
||||
|
@ -71,11 +59,11 @@ impl Node for LightCullComputePass {
|
|||
let light_index_counter_buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("light_index_counter_buffer"),
|
||||
contents: &bytemuck::cast_slice(&[0]),
|
||||
contents: bytemuck::cast_slice(&[0]),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let light_indices_bg_layout = Rc::new(device.create_bind_group_layout(
|
||||
let light_indices_bg_layout = Arc::new(device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
|
@ -140,7 +128,7 @@ impl Node for LightCullComputePass {
|
|||
array_layer_count: None,
|
||||
});
|
||||
|
||||
let light_indices_bg = Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
let light_indices_bg = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_indices_bg_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
|
@ -167,16 +155,16 @@ impl Node for LightCullComputePass {
|
|||
label: Some("light_indices_grid_bind_group"),
|
||||
}));
|
||||
|
||||
drop(main_rt);
|
||||
//drop(main_rt);
|
||||
|
||||
let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
/* let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize); */
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Compute,
|
||||
Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
|
||||
/* Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
|
||||
label: Some("light_cull_pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
layouts: vec![
|
||||
|
@ -188,7 +176,8 @@ impl Node for LightCullComputePass {
|
|||
],
|
||||
shader,
|
||||
shader_entry_point: "cs_main".into(),
|
||||
})),
|
||||
})), */
|
||||
None,
|
||||
vec![(
|
||||
&LightCullComputePassSlots::LightIndicesGridGroup,
|
||||
light_indices_bg,
|
||||
|
@ -196,11 +185,6 @@ impl Node for LightCullComputePass {
|
|||
)],
|
||||
);
|
||||
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::WindowTextureView,
|
||||
SlotAttribute::Input,
|
||||
None,
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Input,
|
||||
|
@ -210,14 +194,54 @@ impl Node for LightCullComputePass {
|
|||
desc.add_buffer_slot(
|
||||
LightCullComputePassSlots::IndexCounterBuffer,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))),
|
||||
Some(SlotValue::Buffer(Arc::new(light_index_counter_buffer))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _world: &mut World, context: &mut RenderGraphContext) {
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext) {
|
||||
context.queue_buffer_write_with(LightCullComputePassSlots::IndexCounterBuffer, 0, 0);
|
||||
|
||||
let screen_size = world.get_resource::<ScreenSize>()
|
||||
.expect("world missing ScreenSize resource");
|
||||
if screen_size.xy() != self.workgroup_size {
|
||||
self.workgroup_size = screen_size.xy();
|
||||
todo!("Resize buffers and other resources");
|
||||
}
|
||||
|
||||
if self.pipeline.is_none() {
|
||||
let device = graph.device();
|
||||
|
||||
let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
||||
let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("light_cull_comp_shader".into()),
|
||||
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor {
|
||||
label: Some("light_cull_pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
layouts: vec![
|
||||
depth_tex_bgl.clone(),
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
light_indices_bg_layout.clone(),
|
||||
screen_size_bgl.clone(),
|
||||
],
|
||||
shader,
|
||||
shader_entry_point: "cs_main".into(),
|
||||
cache: None,
|
||||
compilation_options: Default::default(),
|
||||
});
|
||||
|
||||
self.pipeline = Some(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
|
@ -226,14 +250,11 @@ impl Node for LightCullComputePass {
|
|||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut RenderGraphContext,
|
||||
) {
|
||||
let label = context.label.clone();
|
||||
|
||||
let pipeline = graph.pipeline(label)
|
||||
.expect("Failed to find Pipeline for LightCullComputePass");
|
||||
let pipeline = pipeline.as_compute();
|
||||
let pipeline = self.pipeline.as_mut().unwrap();
|
||||
|
||||
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||
label: Some("light_cull_pass"),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
pass.set_pipeline(pipeline);
|
|
@ -0,0 +1,667 @@
|
|||
use std::{
|
||||
collections::{HashSet, VecDeque},
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use glam::{UVec2, Vec3};
|
||||
use image::GenericImageView;
|
||||
use itertools::izip;
|
||||
use lyra_ecs::{
|
||||
query::{filter::Or, Entities, ResMut, TickOf},
|
||||
Entity, ResourceObject, World,
|
||||
};
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use lyra_resource::ResHandle;
|
||||
use lyra_gltf::Mesh;
|
||||
use lyra_scene::SceneGraph;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug, instrument};
|
||||
use uuid::Uuid;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::render::{
|
||||
graph::{Node, NodeDesc, NodeType},
|
||||
render_buffer::BufferStorage,
|
||||
render_job::RenderJob,
|
||||
texture::{res_filter_to_wgpu, res_wrap_to_wgpu},
|
||||
transform_buffer_storage::TransformIndex,
|
||||
vertex::Vertex,
|
||||
};
|
||||
|
||||
type MeshHandle = ResHandle<Mesh>;
|
||||
type SceneHandle = ResHandle<SceneGraph>;
|
||||
|
||||
pub struct MeshBufferStorage {
|
||||
pub buffer_vertex: BufferStorage,
|
||||
pub buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>,
|
||||
|
||||
// maybe this should just be a Uuid and the material can be retrieved though
|
||||
// MeshPass's `material_buffers` field?
|
||||
pub material: Option<Arc<GpuMaterial>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct MeshPrepNodeLabel;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MeshPrepNode {
|
||||
pub material_bgl: Arc<wgpu::BindGroupLayout>,
|
||||
}
|
||||
|
||||
impl MeshPrepNode {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
let bgl = GpuMaterial::create_bind_group_layout(device);
|
||||
|
||||
Self { material_bgl: bgl }
|
||||
}
|
||||
|
||||
/// Checks if the mesh buffers in the GPU need to be updated.
|
||||
#[instrument(skip(self, device, mesh_buffers, queue, mesh_han))]
|
||||
fn check_mesh_buffers(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
mesh_buffers: &mut FxHashMap<uuid::Uuid, MeshBufferStorage>,
|
||||
mesh_han: &ResHandle<Mesh>,
|
||||
) {
|
||||
let mesh_uuid = mesh_han.uuid();
|
||||
|
||||
if let (Some(mesh), Some(buffers)) = (mesh_han.data_ref(), mesh_buffers.get_mut(&mesh_uuid))
|
||||
{
|
||||
// check if the buffer sizes dont match. If they dont, completely remake the buffers
|
||||
let vertices = mesh.position().unwrap();
|
||||
if buffers.buffer_vertex.count() != vertices.len() {
|
||||
debug!("Recreating buffers for mesh {}", mesh_uuid.to_string());
|
||||
let (vert, idx) = self.create_vertex_index_buffers(device, &mesh);
|
||||
|
||||
// have to re-get buffers because of borrow checker
|
||||
let buffers = mesh_buffers.get_mut(&mesh_uuid).unwrap();
|
||||
buffers.buffer_indices = idx;
|
||||
buffers.buffer_vertex = vert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// update vertices
|
||||
let vertex_buffer = buffers.buffer_vertex.buffer();
|
||||
let vertices = vertices.as_slice();
|
||||
// align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT)
|
||||
let (_, vertices, _) = bytemuck::pod_align_to::<Vec3, u32>(vertices);
|
||||
queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices));
|
||||
|
||||
// update the indices if they're given
|
||||
if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
|
||||
let aligned_indices = match mesh.indices.as_ref().unwrap() {
|
||||
// U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size.
|
||||
lyra_gltf::MeshIndices::U16(v) => {
|
||||
bytemuck::pod_align_to::<u16, u32>(v).1
|
||||
}
|
||||
lyra_gltf::MeshIndices::U32(v) => {
|
||||
bytemuck::pod_align_to::<u32, u32>(v).1
|
||||
}
|
||||
};
|
||||
|
||||
let index_buffer = index_buffer.1.buffer();
|
||||
queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, device, mesh))]
|
||||
fn create_vertex_index_buffers(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
mesh: &Mesh,
|
||||
) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
|
||||
let positions = mesh.position().unwrap();
|
||||
let tex_coords: Vec<glam::Vec2> = mesh
|
||||
.tex_coords()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
|
||||
let normals = mesh.normals().unwrap();
|
||||
|
||||
assert!(positions.len() == tex_coords.len() && positions.len() == normals.len());
|
||||
|
||||
let mut vertex_inputs = vec![];
|
||||
for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) {
|
||||
vertex_inputs.push(Vertex::new(*v, *t, *n));
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(vertex_inputs.as_slice()), //vertex_combined.as_slice(),
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len());
|
||||
|
||||
let indices = match mesh.indices.as_ref() {
|
||||
Some(indices) => {
|
||||
let (idx_type, len, contents) = match indices {
|
||||
lyra_gltf::MeshIndices::U16(v) => {
|
||||
(wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v))
|
||||
}
|
||||
lyra_gltf::MeshIndices::U32(v) => {
|
||||
(wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v))
|
||||
}
|
||||
};
|
||||
|
||||
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents,
|
||||
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let buffer_indices = BufferStorage::new(index_buffer, 0, len);
|
||||
|
||||
Some((idx_type, buffer_indices))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
(vertex_buffer, indices)
|
||||
}
|
||||
|
||||
#[instrument(skip(self, device, queue, material_buffers, mesh))]
|
||||
fn create_mesh_buffers(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
material_buffers: &mut RenderAssets<Arc<GpuMaterial>>,
|
||||
mesh: &Mesh,
|
||||
) -> MeshBufferStorage {
|
||||
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(device, mesh);
|
||||
|
||||
let material = mesh
|
||||
.material
|
||||
.as_ref()
|
||||
.expect("Material resource not loaded yet");
|
||||
let material_ref = material.data_ref().unwrap();
|
||||
|
||||
let material = material_buffers.entry(material.uuid()).or_insert_with(|| {
|
||||
debug!(
|
||||
uuid = material.uuid().to_string(),
|
||||
"Sending material to gpu"
|
||||
);
|
||||
Arc::new(GpuMaterial::from_resource(
|
||||
device,
|
||||
queue,
|
||||
&self.material_bgl,
|
||||
&material_ref,
|
||||
))
|
||||
});
|
||||
|
||||
MeshBufferStorage {
|
||||
buffer_vertex: vertex_buffer,
|
||||
buffer_indices,
|
||||
material: Some(material.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
|
||||
#[instrument(skip(
|
||||
self,
|
||||
device,
|
||||
queue,
|
||||
mesh_buffers,
|
||||
material_buffers,
|
||||
entity_meshes,
|
||||
mesh,
|
||||
entity
|
||||
))]
|
||||
fn process_mesh(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
mesh_buffers: &mut RenderAssets<MeshBufferStorage>,
|
||||
material_buffers: &mut RenderAssets<Arc<GpuMaterial>>,
|
||||
entity_meshes: &mut FxHashMap<Entity, uuid::Uuid>,
|
||||
entity: Entity,
|
||||
mesh: &Mesh,
|
||||
mesh_uuid: Uuid,
|
||||
) -> bool {
|
||||
#[allow(clippy::map_entry)]
|
||||
if !mesh_buffers.contains_key(&mesh_uuid) {
|
||||
// create the mesh's buffers
|
||||
let buffers = self.create_mesh_buffers(device, queue, material_buffers, mesh);
|
||||
mesh_buffers.insert(mesh_uuid, buffers);
|
||||
entity_meshes.insert(entity, mesh_uuid);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// If the resource does not exist in the world, add the default
|
||||
fn try_init_resource<T: ResourceObject + Default>(world: &mut World) {
|
||||
if !world.has_resource::<T>() {
|
||||
world.add_resource_default::<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for MeshPrepNode {
|
||||
fn desc(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
NodeDesc::new(NodeType::Node, None, vec![])
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
world: &mut lyra_ecs::World,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let device = &context.device;
|
||||
let queue = &context.queue;
|
||||
|
||||
let last_epoch = world.current_tick();
|
||||
let mut alive_entities = HashSet::new();
|
||||
|
||||
{
|
||||
// prepare the world with resources
|
||||
Self::try_init_resource::<RenderMeshes>(world);
|
||||
Self::try_init_resource::<RenderAssets<MeshBufferStorage>>(world);
|
||||
Self::try_init_resource::<RenderAssets<Arc<GpuMaterial>>>(world);
|
||||
Self::try_init_resource::<FxHashMap<Entity, uuid::Uuid>>(world);
|
||||
|
||||
let mut render_meshes = world
|
||||
.get_resource_mut::<RenderMeshes>()
|
||||
.expect("world missing RenderMeshes resource");
|
||||
render_meshes.clear();
|
||||
}
|
||||
|
||||
let view = world.view_iter::<(
|
||||
Entities,
|
||||
&TransformIndex,
|
||||
Or<(&MeshHandle, TickOf<MeshHandle>), (&SceneHandle, TickOf<SceneHandle>)>,
|
||||
ResMut<RenderMeshes>,
|
||||
ResMut<RenderAssets<MeshBufferStorage>>,
|
||||
ResMut<RenderAssets<Arc<GpuMaterial>>>,
|
||||
ResMut<FxHashMap<Entity, uuid::Uuid>>,
|
||||
)>();
|
||||
|
||||
// used to store InterpTransform components to add to entities later
|
||||
for (
|
||||
entity,
|
||||
transform_index,
|
||||
(mesh_pair, scene_pair),
|
||||
mut render_meshes,
|
||||
mut mesh_buffers,
|
||||
mut material_buffers,
|
||||
mut entity_meshes,
|
||||
) in view
|
||||
{
|
||||
alive_entities.insert(entity);
|
||||
|
||||
if let Some((mesh_han, mesh_epoch)) = mesh_pair {
|
||||
if let Some(mesh) = mesh_han.data_ref() {
|
||||
// if process mesh did not just create a new mesh, and the epoch
|
||||
// shows that the scene has changed, verify that the mesh buffers
|
||||
// dont need to be resent to the gpu.
|
||||
if !self.process_mesh(
|
||||
device,
|
||||
queue,
|
||||
&mut mesh_buffers,
|
||||
&mut material_buffers,
|
||||
&mut entity_meshes,
|
||||
entity,
|
||||
&mesh,
|
||||
mesh_han.uuid(),
|
||||
) && mesh_epoch == last_epoch
|
||||
{
|
||||
self.check_mesh_buffers(device, queue, &mut mesh_buffers, &mesh_han);
|
||||
}
|
||||
|
||||
let material = mesh.material.as_ref().unwrap().data_ref().unwrap();
|
||||
let shader = material.shader_uuid.unwrap_or(0);
|
||||
let job = RenderJob::new(entity, shader, mesh_han.uuid(), *transform_index);
|
||||
render_meshes.push_back(job);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((scene_han, scene_epoch)) = scene_pair {
|
||||
if let Some(scene) = scene_han.data_ref() {
|
||||
for (mesh_han, transform_index) in
|
||||
scene.world().view_iter::<(&MeshHandle, &TransformIndex)>()
|
||||
{
|
||||
if let Some(mesh) = mesh_han.data_ref() {
|
||||
// if process mesh did not just create a new mesh, and the epoch
|
||||
// shows that the scene has changed, verify that the mesh buffers
|
||||
// dont need to be resent to the gpu.
|
||||
if !self.process_mesh(
|
||||
device,
|
||||
queue,
|
||||
&mut mesh_buffers,
|
||||
&mut material_buffers,
|
||||
&mut entity_meshes,
|
||||
entity,
|
||||
&mesh,
|
||||
mesh_han.uuid(),
|
||||
) && scene_epoch == last_epoch
|
||||
{
|
||||
self.check_mesh_buffers(
|
||||
device,
|
||||
queue,
|
||||
&mut mesh_buffers,
|
||||
&mesh_han,
|
||||
);
|
||||
}
|
||||
|
||||
let material = mesh.material.as_ref().unwrap().data_ref().unwrap();
|
||||
let shader = material.shader_uuid.unwrap_or(0);
|
||||
let job =
|
||||
RenderJob::new(entity, shader, mesh_han.uuid(), *transform_index);
|
||||
render_meshes.push_back(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct RenderAssets<T>(FxHashMap<Uuid, T>);
|
||||
|
||||
impl<T> Deref for RenderAssets<T> {
|
||||
type Target = FxHashMap<Uuid, T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for RenderAssets<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for RenderAssets<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RenderAssets<T> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct GpuMaterial {
|
||||
pub bind_group: Arc<wgpu::BindGroup>,
|
||||
bind_group_layout: Arc<wgpu::BindGroupLayout>,
|
||||
material_properties_buffer: wgpu::Buffer,
|
||||
diffuse_texture: wgpu::Texture,
|
||||
diffuse_texture_sampler: wgpu::Sampler,
|
||||
/* specular_texture: wgpu::Texture,
|
||||
specular_texture_sampler: wgpu::Sampler, */
|
||||
}
|
||||
|
||||
impl GpuMaterial {
|
||||
fn create_bind_group_layout(device: &wgpu::Device) -> Arc<wgpu::BindGroupLayout> {
|
||||
Arc::new(
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("bgl_material"),
|
||||
entries: &[
|
||||
// material properties
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None, /* Some(
|
||||
NonZeroU64::new(mem::size_of::<MaterialPropertiesUniform>() as _)
|
||||
.unwrap(),
|
||||
) */
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// diffuse texture
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// diffuse texture sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
// specular texture
|
||||
/* wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
// specular texture sampler
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
}, */
|
||||
],
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn texture_desc(label: &str, size: UVec2) -> wgpu::TextureDescriptor {
|
||||
//debug!("Texture desc size: {:?}", size);
|
||||
wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size: wgpu::Extent3d {
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1, // TODO
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
}
|
||||
}
|
||||
|
||||
fn write_texture(queue: &wgpu::Queue, texture: &wgpu::Texture, img: &lyra_resource::Image) {
|
||||
let dim = img.dimensions();
|
||||
//debug!("Write texture size: {:?}", dim);
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
&img.to_rgba8(),
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * dim.0),
|
||||
rows_per_image: Some(dim.1),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: dim.0,
|
||||
height: dim.1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn from_resource(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
layout: &Arc<wgpu::BindGroupLayout>,
|
||||
mat: &lyra_gltf::Material,
|
||||
) -> Self {
|
||||
//let specular = mat.specular.as_ref().unwrap_or_default();
|
||||
//let specular_
|
||||
|
||||
let prop = MaterialPropertiesUniform {
|
||||
ambient: Vec3::ONE,
|
||||
_padding1: 0,
|
||||
diffuse: Vec3::ONE,
|
||||
shininess: 32.0,
|
||||
specular_factor: 0.0,
|
||||
_padding2: [0; 3],
|
||||
specular_color_factor: Vec3::ZERO,
|
||||
_padding3: 0,
|
||||
};
|
||||
|
||||
let properties_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("buffer_material"),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
contents: bytemuck::bytes_of(&prop),
|
||||
});
|
||||
|
||||
let diffuse_tex = mat.base_color_texture.as_ref().unwrap();
|
||||
let diffuse_tex = diffuse_tex.data_ref().unwrap();
|
||||
let diffuse_tex_img = diffuse_tex.image.data_ref().unwrap();
|
||||
let diffuse_tex_dim = diffuse_tex_img.dimensions();
|
||||
let diffuse_texture = device.create_texture(&Self::texture_desc(
|
||||
"material_diffuse_texture",
|
||||
UVec2::new(diffuse_tex_dim.0, diffuse_tex_dim.1),
|
||||
));
|
||||
let diffuse_tex_view = diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let sampler_desc = match &diffuse_tex.sampler {
|
||||
Some(sampler) => {
|
||||
let magf = res_filter_to_wgpu(
|
||||
sampler
|
||||
.mag_filter
|
||||
.unwrap_or(lyra_resource::FilterMode::Linear),
|
||||
);
|
||||
let minf = res_filter_to_wgpu(
|
||||
sampler
|
||||
.min_filter
|
||||
.unwrap_or(lyra_resource::FilterMode::Nearest),
|
||||
);
|
||||
let mipf = res_filter_to_wgpu(
|
||||
sampler
|
||||
.mipmap_filter
|
||||
.unwrap_or(lyra_resource::FilterMode::Nearest),
|
||||
);
|
||||
|
||||
let wrap_u = res_wrap_to_wgpu(sampler.wrap_u);
|
||||
let wrap_v = res_wrap_to_wgpu(sampler.wrap_v);
|
||||
let wrap_w = res_wrap_to_wgpu(sampler.wrap_w);
|
||||
|
||||
wgpu::SamplerDescriptor {
|
||||
address_mode_u: wrap_u,
|
||||
address_mode_v: wrap_v,
|
||||
address_mode_w: wrap_w,
|
||||
mag_filter: magf,
|
||||
min_filter: minf,
|
||||
mipmap_filter: mipf,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
None => wgpu::SamplerDescriptor {
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let diffuse_sampler = device.create_sampler(&sampler_desc);
|
||||
|
||||
Self::write_texture(queue, &diffuse_texture, &diffuse_tex_img);
|
||||
|
||||
debug!("TODO: specular texture");
|
||||
|
||||
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("bg_material"),
|
||||
layout: &layout,
|
||||
entries: &[
|
||||
// material properties
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &properties_buffer,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
// diffuse texture
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_tex_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
|
||||
},
|
||||
// TODO: specular textures
|
||||
],
|
||||
});
|
||||
|
||||
Self {
|
||||
bind_group: Arc::new(bg),
|
||||
bind_group_layout: layout.clone(),
|
||||
material_properties_buffer: properties_buffer,
|
||||
diffuse_texture,
|
||||
diffuse_texture_sampler: diffuse_sampler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Uniform for MaterialProperties in a shader
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct MaterialPropertiesUniform {
|
||||
ambient: glam::Vec3,
|
||||
_padding1: u32,
|
||||
diffuse: glam::Vec3,
|
||||
shininess: f32,
|
||||
specular_factor: f32,
|
||||
_padding2: [u32; 3],
|
||||
specular_color_factor: glam::Vec3,
|
||||
_padding3: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderMeshes(VecDeque<RenderJob>);
|
||||
|
||||
impl Deref for RenderMeshes {
|
||||
type Target = VecDeque<RenderJob>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for RenderMeshes {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,507 @@
|
|||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use lyra_ecs::{AtomicRef, ResourceData};
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use tracing::{instrument, warn};
|
||||
|
||||
use crate::render::{
|
||||
desc_buf_lay::DescVertexBufferLayout,
|
||||
graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext},
|
||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
|
||||
texture::RenderTexture,
|
||||
transform_buffer_storage::TransformBuffers,
|
||||
vertex::Vertex,
|
||||
};
|
||||
|
||||
use super::{
|
||||
BasePassSlots, LightBasePassSlots, LightCullComputePassSlots, MeshBufferStorage, RenderAssets, RenderMeshes, ShadowMapsPassSlots
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct MeshesPassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum MeshesPassSlots {
|
||||
Material,
|
||||
}
|
||||
|
||||
/// Stores the bind group and bind group layout for the shadow atlas texture
|
||||
struct ShadowsAtlasBgPair {
|
||||
layout: Arc<wgpu::BindGroupLayout>,
|
||||
bg: Arc<wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
//#[derive(Default)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MeshPass {
|
||||
default_texture: Option<RenderTexture>,
|
||||
|
||||
pipeline: Option<RenderPipeline>,
|
||||
material_bgl: Arc<wgpu::BindGroupLayout>,
|
||||
|
||||
// TODO: find a better way to extract these resources from the main world to be used in the
|
||||
// render stage.
|
||||
transform_buffers: Option<ResourceData>,
|
||||
render_meshes: Option<ResourceData>,
|
||||
mesh_buffers: Option<ResourceData>,
|
||||
|
||||
shadows_atlas: Option<ShadowsAtlasBgPair>,
|
||||
}
|
||||
|
||||
impl MeshPass {
|
||||
pub fn new(material_bgl: Arc<wgpu::BindGroupLayout>) -> Self {
|
||||
Self {
|
||||
default_texture: None,
|
||||
pipeline: None,
|
||||
material_bgl,
|
||||
|
||||
transform_buffers: None,
|
||||
render_meshes: None,
|
||||
mesh_buffers: None,
|
||||
|
||||
shadows_atlas: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_buffers(&self) -> AtomicRef<TransformBuffers> {
|
||||
self.transform_buffers.as_ref().unwrap().get()
|
||||
}
|
||||
|
||||
fn render_meshes(&self) -> AtomicRef<RenderMeshes> {
|
||||
self.render_meshes.as_ref().unwrap().get()
|
||||
}
|
||||
|
||||
fn mesh_buffers(&self) -> AtomicRef<RenderAssets<MeshBufferStorage>> {
|
||||
self.mesh_buffers.as_ref().unwrap().get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for MeshPass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
// load the default texture
|
||||
//let bytes = include_bytes!("../../default_texture.png");
|
||||
//self.default_texture = Some(RenderTexture::from_bytes(device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap());
|
||||
|
||||
NodeDesc::new(
|
||||
NodeType::Render,
|
||||
None,
|
||||
vec![
|
||||
//(&MeshesPassSlots::Material, material_bg, Some(material_bgl)),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip(self, graph, world))]
|
||||
fn prepare(
|
||||
&mut self,
|
||||
graph: &mut RenderGraph,
|
||||
world: &mut lyra_ecs::World,
|
||||
_: &mut RenderGraphContext,
|
||||
) {
|
||||
if self.pipeline.is_none() {
|
||||
let shader_mod = graph.register_shader(include_str!("../../shaders/base.wgsl"))
|
||||
.expect("failed to register shader").expect("base shader missing module");
|
||||
let shader_src = graph.preprocess_shader(&shader_mod)
|
||||
.expect("failed to preprocess shader");
|
||||
|
||||
let device = graph.device();
|
||||
let surface_config_format = graph.view_target().format();
|
||||
|
||||
let atlas_view = graph
|
||||
.slot_value(ShadowMapsPassSlots::ShadowAtlasTextureView)
|
||||
.expect("missing ShadowMapsPassSlots::ShadowAtlasTextureView")
|
||||
.as_texture_view()
|
||||
.unwrap();
|
||||
let atlas_sampler = graph
|
||||
.slot_value(ShadowMapsPassSlots::ShadowAtlasSampler)
|
||||
.expect("missing ShadowMapsPassSlots::ShadowAtlasSampler")
|
||||
.as_sampler()
|
||||
.unwrap();
|
||||
let atlas_sampler_compare = graph
|
||||
.slot_value(ShadowMapsPassSlots::ShadowAtlasSamplerComparison)
|
||||
.expect("missing ShadowMapsPassSlots::ShadowAtlasSamplerComparison")
|
||||
.as_sampler()
|
||||
.unwrap();
|
||||
let shadow_settings_buf = graph
|
||||
.slot_value(ShadowMapsPassSlots::ShadowSettingsUniform)
|
||||
.expect("missing ShadowMapsPassSlots::ShadowSettingsUniform")
|
||||
.as_buffer()
|
||||
.unwrap();
|
||||
let light_uniform_buf = graph
|
||||
.slot_value(ShadowMapsPassSlots::ShadowLightUniformsBuffer)
|
||||
.expect("missing ShadowMapsPassSlots::ShadowLightUniformsBuffer")
|
||||
.as_buffer()
|
||||
.unwrap();
|
||||
let pcf_poisson_disc = graph
|
||||
.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer)
|
||||
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer")
|
||||
.as_buffer()
|
||||
.unwrap();
|
||||
let pcf_poisson_disc_3d = graph
|
||||
.slot_value(ShadowMapsPassSlots::PcfPoissonDiscBuffer3d)
|
||||
.expect("missing ShadowMapsPassSlots::PcfPoissonDiscBuffer3d")
|
||||
.as_buffer()
|
||||
.unwrap();
|
||||
let pcss_poisson_disc = graph
|
||||
.slot_value(ShadowMapsPassSlots::PcssPoissonDiscBuffer)
|
||||
.expect("missing ShadowMapsPassSlots::PcssPoissonDiscBuffer")
|
||||
.as_buffer()
|
||||
.unwrap();
|
||||
|
||||
let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("bgl_shadows_atlas"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Depth,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 5,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 6,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 7,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let atlas_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("bg_shadows_atlas"),
|
||||
layout: &atlas_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(atlas_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(atlas_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(atlas_sampler_compare),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: shadow_settings_buf,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 4,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: light_uniform_buf,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 5,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: pcf_poisson_disc,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 6,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: pcf_poisson_disc_3d,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 7,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: pcss_poisson_disc,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
self.shadows_atlas = Some(ShadowsAtlasBgPair {
|
||||
layout: Arc::new(atlas_layout),
|
||||
bg: Arc::new(atlas_bg),
|
||||
});
|
||||
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let light_grid_bgl =
|
||||
graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
let atlas_bgl = self.shadows_atlas.as_ref().unwrap().layout.clone();
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some(shader_mod.into()),
|
||||
source: shader_src,
|
||||
});
|
||||
|
||||
let transforms = world
|
||||
.get_resource_data::<TransformBuffers>()
|
||||
.expect("Missing transform buffers");
|
||||
self.transform_buffers = Some(transforms.clone());
|
||||
|
||||
let render_meshes = world
|
||||
.get_resource_data::<RenderMeshes>()
|
||||
.expect("Missing transform buffers");
|
||||
self.render_meshes = Some(render_meshes.clone());
|
||||
|
||||
let mesh_buffers = world
|
||||
.get_resource_data::<RenderAssets<MeshBufferStorage>>()
|
||||
.expect("Missing render meshes");
|
||||
self.mesh_buffers = Some(mesh_buffers.clone());
|
||||
|
||||
let transforms = transforms.get::<TransformBuffers>();
|
||||
|
||||
self.pipeline = Some(RenderPipeline::create(
|
||||
device,
|
||||
&RenderPipelineDescriptor {
|
||||
label: Some("meshes".into()),
|
||||
layouts: vec![
|
||||
self.material_bgl.clone(),
|
||||
transforms.bindgroup_layout.clone(),
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
light_grid_bgl.clone(),
|
||||
atlas_bgl,
|
||||
],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: VertexState {
|
||||
module: shader.clone(),
|
||||
entry_point: "vs_main".into(),
|
||||
buffers: vec![Vertex::desc().into()],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: shader,
|
||||
entry_point: "fs_main".into(),
|
||||
targets: vec![Some(wgpu::ColorTargetState {
|
||||
format: surface_config_format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: RenderTexture::DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::Less,
|
||||
stencil: wgpu::StencilState::default(), // TODO: stencil buffer
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
cull_mode: Some(wgpu::Face::Back),
|
||||
..Default::default()
|
||||
},
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let encoder = context.encoder.as_mut().unwrap();
|
||||
|
||||
/* let view = graph
|
||||
.slot_value(BasePassSlots::WindowTextureView)
|
||||
.unwrap()
|
||||
.as_texture_view()
|
||||
.expect("BasePassSlots::WindowTextureView was not a TextureView slot"); */
|
||||
|
||||
let vt = graph.view_target();
|
||||
let view = vt.render_view();
|
||||
|
||||
let depth_view = graph
|
||||
.slot_value(BasePassSlots::DepthTextureView)
|
||||
.unwrap()
|
||||
.as_texture_view()
|
||||
.expect("BasePassSlots::DepthTextureView was not a TextureView slot");
|
||||
|
||||
let camera_bg = graph.bind_group(BasePassSlots::Camera);
|
||||
|
||||
let lights_bg = graph.bind_group(LightBasePassSlots::Lights);
|
||||
|
||||
let light_grid_bg = graph.bind_group(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
|
||||
let shadows_atlas_bg = &self.shadows_atlas.as_ref().unwrap().bg;
|
||||
|
||||
//let material_bg = graph.bind_group(MeshesPassSlots::Material);
|
||||
|
||||
/* let pipeline = graph.pipeline(context.label.clone())
|
||||
.expect("Failed to find pipeline for MeshPass"); */
|
||||
let pipeline = self.pipeline.as_ref().unwrap();
|
||||
|
||||
let transforms = self.transform_buffers();
|
||||
let render_meshes = self.render_meshes();
|
||||
let mesh_buffers = self.mesh_buffers();
|
||||
|
||||
{
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
}),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
// enable depth buffer
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
pass.set_pipeline(pipeline);
|
||||
|
||||
//let default_texture = self.default_texture.as_ref().unwrap();
|
||||
|
||||
for job in render_meshes.iter() {
|
||||
// get the mesh (containing vertices) and the buffers from storage
|
||||
let buffers = mesh_buffers.get(&job.mesh_uuid);
|
||||
if buffers.is_none() {
|
||||
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
|
||||
continue;
|
||||
}
|
||||
let buffers = buffers.unwrap();
|
||||
|
||||
// Bind the optional texture
|
||||
/* if let Some(tex) = buffers.material.as_ref()
|
||||
.and_then(|m| m.diffuse_texture.as_ref()) {
|
||||
pass.set_bind_group(0, tex.bind_group(), &[]);
|
||||
} else {
|
||||
pass.set_bind_group(0, default_texture.bind_group(), &[]);
|
||||
}
|
||||
|
||||
if let Some(tex) = buffers.material.as_ref()
|
||||
.and_then(|m| m.specular.as_ref())
|
||||
.and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) {
|
||||
pass.set_bind_group(5, tex.bind_group(), &[]);
|
||||
} else {
|
||||
pass.set_bind_group(5, default_texture.bind_group(), &[]);
|
||||
} */
|
||||
if let Some(mat) = buffers.material.as_ref() {
|
||||
pass.set_bind_group(0, &mat.bind_group, &[]);
|
||||
} else {
|
||||
todo!("cannot render mesh without material");
|
||||
}
|
||||
|
||||
// Get the bindgroup for job's transform and bind to it using an offset.
|
||||
let bindgroup = transforms.bind_group(job.transform_id);
|
||||
let offset = transforms.buffer_offset(job.transform_id);
|
||||
pass.set_bind_group(1, bindgroup, &[offset]);
|
||||
|
||||
pass.set_bind_group(2, camera_bg, &[]);
|
||||
pass.set_bind_group(3, lights_bg, &[]);
|
||||
//pass.set_bind_group(4, material_bg, &[]);
|
||||
|
||||
pass.set_bind_group(4, light_grid_bg, &[]);
|
||||
|
||||
pass.set_bind_group(5, shadows_atlas_bg, &[]);
|
||||
|
||||
// if this mesh uses indices, use them to draw the mesh
|
||||
if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() {
|
||||
let indices_len = indices.count() as u32;
|
||||
|
||||
pass.set_vertex_buffer(
|
||||
buffers.buffer_vertex.slot(),
|
||||
buffers.buffer_vertex.buffer().slice(..),
|
||||
);
|
||||
pass.set_index_buffer(indices.buffer().slice(..), *idx_type);
|
||||
pass.draw_indexed(0..indices_len, 0, 0..1);
|
||||
} else {
|
||||
let vertex_count = buffers.buffer_vertex.count();
|
||||
|
||||
pass.set_vertex_buffer(
|
||||
buffers.buffer_vertex.slot(),
|
||||
buffers.buffer_vertex.buffer().slice(..),
|
||||
);
|
||||
pass.draw(0..vertex_count as u32, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
mod light_cull_compute;
|
||||
pub use light_cull_compute::*;
|
||||
|
||||
mod base;
|
||||
pub use base::*;
|
||||
|
||||
mod meshes;
|
||||
pub use meshes::*;
|
||||
|
||||
mod light_base;
|
||||
pub use light_base::*;
|
||||
|
||||
mod present_pass;
|
||||
pub use present_pass::*;
|
||||
|
||||
mod init;
|
||||
pub use init::*;
|
||||
|
||||
mod tint;
|
||||
pub use tint::*;
|
||||
|
||||
mod fxaa;
|
||||
pub use fxaa::*;
|
||||
|
||||
mod shadows;
|
||||
pub use shadows::*;
|
||||
|
||||
mod mesh_prepare;
|
||||
pub use mesh_prepare::*;
|
||||
|
||||
mod transform;
|
||||
pub use transform::*;
|
|
@ -0,0 +1,44 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext};
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
||||
pub struct PresentPassLabel;
|
||||
|
||||
/// Supplies some basic things other passes needs.
|
||||
///
|
||||
/// screen size buffer, camera buffer,
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PresentPass;
|
||||
|
||||
impl PresentPass {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for PresentPass {
|
||||
fn desc(&mut self, _graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc {
|
||||
NodeDesc::new(
|
||||
NodeType::Presenter,
|
||||
None,
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _graph: &mut RenderGraph, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) {
|
||||
|
||||
}
|
||||
|
||||
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::NodeDesc, context: &mut crate::render::graph::RenderGraphContext) {
|
||||
let mut vt = graph.view_target_mut();
|
||||
vt.copy_to_primary(context.encoder.as_mut().unwrap());
|
||||
context.submit_encoder();
|
||||
|
||||
let frame = vt.primary.frame.take()
|
||||
.expect("ViewTarget.primary was already presented");
|
||||
frame.present();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,168 @@
|
|||
use std::{collections::HashMap, rc::Rc, sync::Arc};
|
||||
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::{
|
||||
graph::{Node, NodeDesc, NodeType},
|
||||
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct TintPassLabel;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TintPass {
|
||||
target_sampler: Option<wgpu::Sampler>,
|
||||
bgl: Option<Arc<wgpu::BindGroupLayout>>,
|
||||
/// Store bind groups for the input textures.
|
||||
/// The texture may change due to resizes, or changes to the view target chain
|
||||
/// from other nodes.
|
||||
bg_cache: HashMap<wgpu::Id<wgpu::TextureView>, wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
impl TintPass {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for TintPass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let device = &graph.device;
|
||||
|
||||
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("tint_bgl"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let bgl = Arc::new(bgl);
|
||||
self.bgl = Some(bgl.clone());
|
||||
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default()));
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("tint_shader".into()),
|
||||
source: include_str!("../../shaders/tint.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let vt = graph.view_target();
|
||||
|
||||
|
||||
NodeDesc::new(
|
||||
NodeType::Render,
|
||||
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
|
||||
label: Some("tint_pass".into()),
|
||||
layouts: vec![bgl.clone()],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: VertexState {
|
||||
module: shader.clone(),
|
||||
entry_point: "vs_main".into(),
|
||||
buffers: vec![],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: shader,
|
||||
entry_point: "fs_main".into(),
|
||||
targets: vec![Some(wgpu::ColorTargetState {
|
||||
format: vt.format(),
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
depth_stencil: None,
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
})),
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &mut lyra_ecs::World,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
//todo!()
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let pipeline = graph
|
||||
.pipeline(context.label.clone())
|
||||
.expect("Failed to find pipeline for TintPass");
|
||||
|
||||
let mut vt = graph.view_target_mut();
|
||||
let chain = vt.get_chain();
|
||||
let source_view = chain.source.frame_view.as_ref().unwrap();
|
||||
let dest_view = chain.dest.frame_view.as_ref().unwrap();
|
||||
|
||||
let bg = self
|
||||
.bg_cache
|
||||
.entry(source_view.global_id())
|
||||
.or_insert_with(|| {
|
||||
graph
|
||||
.device()
|
||||
.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("tint_bg"),
|
||||
layout: self.bgl.as_ref().unwrap(),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(source_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(
|
||||
self.target_sampler.as_ref().unwrap(),
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
{
|
||||
let encoder = context.encoder.as_mut().unwrap();
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("tint_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: dest_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_pipeline(pipeline.as_render());
|
||||
|
||||
pass.set_bind_group(0, bg, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
use lyra_ecs::{
|
||||
query::{
|
||||
filter::Or,
|
||||
Entities,
|
||||
},
|
||||
Component, Entity,
|
||||
};
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use lyra_math::Transform;
|
||||
use lyra_resource::ResHandle;
|
||||
use lyra_scene::{SceneGraph, WorldTransform};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
graph::{Node, NodeDesc, NodeType},
|
||||
transform_buffer_storage::{TransformBuffers, TransformIndex},
|
||||
},
|
||||
DeltaTime,
|
||||
};
|
||||
|
||||
/// An interpolated transform.
|
||||
///
|
||||
/// This transform is interpolated between frames to make movement appear smoother when the
|
||||
/// transform is updated less often than rendering.
|
||||
#[derive(Clone, Debug, Component)]
|
||||
pub struct InterpTransform {
|
||||
last_transform: Transform,
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct TransformsNodeLabel;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TransformsNode {}
|
||||
|
||||
impl TransformsNode {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_component_queue(world: &mut lyra_ecs::World, component_queue: Vec<(Entity, Option<InterpTransform>, Option<TransformIndex>)>) {
|
||||
for (en, interp, index) in component_queue {
|
||||
println!("writing index {:?} for entity {}", index, en.id().0);
|
||||
|
||||
match (interp, index) {
|
||||
(None, None) => unreachable!(),
|
||||
(None, Some(index)) => world.insert(en, index),
|
||||
(Some(interp), None) => world.insert(en, interp),
|
||||
(Some(interp), Some(index)) => world.insert(en, (interp, index)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_transforms(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
limits: &wgpu::Limits,
|
||||
world: &mut lyra_ecs::World,
|
||||
delta_time: DeltaTime,
|
||||
buffers: &mut TransformBuffers,
|
||||
parent_transform: Transform,
|
||||
) {
|
||||
let mut component_queue = vec![];
|
||||
|
||||
let view = world.view_iter::<(
|
||||
Entities,
|
||||
Or<&WorldTransform, &Transform>,
|
||||
Option<&mut InterpTransform>,
|
||||
Option<&TransformIndex>,
|
||||
Option<&ResHandle<SceneGraph>>,
|
||||
)>();
|
||||
|
||||
for (entity, transform, interp_tran, transform_index, scene_graph) in view {
|
||||
// expand the transform buffers if they need to be.
|
||||
if buffers.needs_expand() {
|
||||
debug!("Expanding transform buffers");
|
||||
buffers.expand_buffers(device);
|
||||
}
|
||||
|
||||
// Get the world transform of the entity, else fall back to the transform
|
||||
let transform = match transform {
|
||||
(None, None) => unreachable!(),
|
||||
(None, Some(t)) => *t,
|
||||
(Some(wt), None) => **wt,
|
||||
// Assume world transform since it *should* be updated by world systems
|
||||
(Some(wt), Some(_)) => **wt,
|
||||
};
|
||||
// offset this transform by its parent
|
||||
let transform = transform + parent_transform;
|
||||
|
||||
// Interpolate the transform for this entity using a component.
|
||||
// If the entity does not have the component then it will be queued to be added
|
||||
// to it after all the entities are prepared for rendering.
|
||||
let transform = match interp_tran {
|
||||
Some(mut interp_transform) => {
|
||||
// found in https://youtu.be/YJB1QnEmlTs?t=472
|
||||
interp_transform.alpha = 1.0 - interp_transform.alpha.powf(*delta_time);
|
||||
|
||||
interp_transform.last_transform = interp_transform
|
||||
.last_transform
|
||||
.lerp(transform, interp_transform.alpha);
|
||||
interp_transform.last_transform
|
||||
}
|
||||
None => {
|
||||
let interp = InterpTransform {
|
||||
last_transform: transform,
|
||||
alpha: 0.5,
|
||||
};
|
||||
component_queue.push((entity, Some(interp), None));
|
||||
transform
|
||||
}
|
||||
};
|
||||
|
||||
// Get the TransformIndex from the entity, or reserve a new one if the entity doesn't have
|
||||
// the component.
|
||||
let index = match transform_index {
|
||||
Some(i) => *i,
|
||||
None => {
|
||||
let i = buffers.reserve_transform(&device);
|
||||
debug!(
|
||||
"Reserved transform index {:?} for entity {}",
|
||||
i,
|
||||
entity.id().0
|
||||
);
|
||||
|
||||
component_queue.push((entity, None, Some(i)));
|
||||
i
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: only update if the transform changed.
|
||||
buffers.update(
|
||||
&queue,
|
||||
index,
|
||||
transform.calculate_mat4(),
|
||||
glam::Mat3::from_quat(transform.rotation),
|
||||
);
|
||||
|
||||
if let Some(scene) = scene_graph {
|
||||
if let Some(mut scene) = scene.data_mut() {
|
||||
|
||||
update_transforms(
|
||||
device,
|
||||
queue,
|
||||
limits,
|
||||
scene.world_mut(),
|
||||
delta_time,
|
||||
buffers,
|
||||
transform,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process_component_queue(world, component_queue);
|
||||
}
|
||||
|
||||
impl Node for TransformsNode {
|
||||
fn desc(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
NodeDesc::new(NodeType::Node, None, vec![])
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
world: &mut lyra_ecs::World,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let device = &context.device;
|
||||
let queue = &context.queue;
|
||||
let render_limits = device.limits();
|
||||
|
||||
// prepare the world with resources
|
||||
if !world.has_resource::<TransformBuffers>() {
|
||||
let buffers = TransformBuffers::new(device);
|
||||
world.add_resource(buffers);
|
||||
}
|
||||
|
||||
// I have to do this weird garbage to borrow the `TransformBuffers`
|
||||
// without running into a borrow checker error from passing `world` as mutable.
|
||||
// This is safe since I know that the recursive function isn't accessing this
|
||||
// TransformBuffers, or any other ones in other worlds.
|
||||
let buffers = world.get_resource_data::<TransformBuffers>()
|
||||
.map(|r| r.clone()).unwrap();
|
||||
let mut buffers = buffers.get_mut();
|
||||
let dt = world.get_resource::<DeltaTime>().unwrap().clone();
|
||||
|
||||
update_transforms(
|
||||
&device,
|
||||
&queue,
|
||||
&render_limits,
|
||||
world,
|
||||
dt,
|
||||
&mut buffers,
|
||||
Transform::default(),
|
||||
);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use crate::math;
|
||||
|
||||
enum RenderTargetInner {
|
||||
Surface {
|
||||
/// The surface that will be rendered to.
|
||||
///
|
||||
/// You can create a new surface with a `'static` lifetime if you have an `Arc<Window>`:
|
||||
/// ```nobuild
|
||||
/// let window = Arc::new(window);
|
||||
/// let surface = instance.create_surface(Arc::clone(&window))?;
|
||||
/// ```
|
||||
surface: wgpu::Surface<'static>,
|
||||
/// the configuration of the surface render target..
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
},
|
||||
Texture {
|
||||
/// The texture that will be rendered to.
|
||||
texture: Arc<wgpu::Texture>,
|
||||
}
|
||||
}
|
||||
|
||||
/// A render target that is a surface or a texture.
|
||||
#[repr(transparent)]
|
||||
pub struct RenderTarget(RenderTargetInner);
|
||||
|
||||
impl From<wgpu::Texture> for RenderTarget {
|
||||
fn from(value: wgpu::Texture) -> Self {
|
||||
Self(RenderTargetInner::Texture { texture: Arc::new(value) })
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderTarget {
|
||||
pub fn from_surface(surface: wgpu::Surface<'static>, config: wgpu::SurfaceConfiguration) -> Self {
|
||||
Self(RenderTargetInner::Surface { surface, config })
|
||||
}
|
||||
|
||||
pub fn new_texture(device: &wgpu::Device, format: wgpu::TextureFormat, size: math::UVec2) -> Self {
|
||||
let tex = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: None,
|
||||
size: wgpu::Extent3d {
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
Self(RenderTargetInner::Texture { texture: Arc::new(tex) })
|
||||
}
|
||||
|
||||
pub fn format(&self) -> wgpu::TextureFormat {
|
||||
match &self.0 {
|
||||
RenderTargetInner::Surface { config, .. } => config.format,
|
||||
RenderTargetInner::Texture { texture } => texture.format(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> math::UVec2 {
|
||||
match &self.0 {
|
||||
RenderTargetInner::Surface { config, .. } => math::UVec2::new(config.width, config.height),
|
||||
RenderTargetInner::Texture { texture } => {
|
||||
let s = texture.size();
|
||||
math::UVec2::new(s.width, s.height)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the frame texture of the [`RenderTarget`]
|
||||
///
|
||||
/// If this is target is a surface and the frame texture was already retrieved from the swap
|
||||
/// chain, a [`wgpu::SurfaceError`] error will be returned.
|
||||
pub fn frame_texture(&self) -> Result<FrameTexture, wgpu::SurfaceError> {
|
||||
match &self.0 {
|
||||
RenderTargetInner::Surface { surface, .. } => Ok(FrameTexture::Surface(surface.get_current_texture()?)),
|
||||
RenderTargetInner::Texture { texture } => Ok(FrameTexture::Texture(texture.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, device: &wgpu::Device, new_size: math::UVec2) {
|
||||
match &mut self.0 {
|
||||
RenderTargetInner::Surface { surface, config } => {
|
||||
config.width = new_size.x;
|
||||
config.height = new_size.y;
|
||||
surface.configure(device, config);
|
||||
},
|
||||
RenderTargetInner::Texture { texture } => {
|
||||
let format = texture.format();
|
||||
let size = self.size();
|
||||
|
||||
*self = Self::new_texture(device, format, size);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create the frame of the RenderTarget.
|
||||
///
|
||||
/// If this is target is a surface and the frame texture was already retrieved from the
|
||||
/// swap chain, a [`wgpu::SurfaceError`] error will be returned.
|
||||
pub fn create_frame(&self) -> Frame {
|
||||
let texture = self.frame_texture()
|
||||
.expect("failed to create frame texture"); // TODO: should be returned to the user
|
||||
let size = self.size();
|
||||
|
||||
Frame {
|
||||
size,
|
||||
texture,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FrameTexture {
|
||||
Surface(wgpu::SurfaceTexture),
|
||||
Texture(Arc<wgpu::Texture>),
|
||||
}
|
||||
|
||||
/// Represents the current frame that is being rendered to.
|
||||
//#[allow(dead_code)]
|
||||
pub struct Frame {
|
||||
pub(crate) size: math::UVec2,
|
||||
pub(crate) texture: FrameTexture,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
pub fn texture(&self) -> &wgpu::Texture {
|
||||
match &self.texture {
|
||||
FrameTexture::Surface(s) => &s.texture,
|
||||
FrameTexture::Texture(t) => t,
|
||||
}
|
||||
}
|
||||
|
||||
/// Present the frame
|
||||
///
|
||||
/// If this frame is from a surface, it will be present, else nothing will happen.
|
||||
pub fn present(self) {
|
||||
match self.texture {
|
||||
FrameTexture::Surface(s) => s.present(),
|
||||
FrameTexture::Texture(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of the frame
|
||||
pub fn size(&self) -> math::UVec2 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the current frame, and the render target it came from.
|
||||
pub struct FrameTarget {
|
||||
pub render_target: RenderTarget,
|
||||
/// None when a frame has not been created yet
|
||||
pub frame: Option<Frame>,
|
||||
/// The view to use to render to the frame.
|
||||
pub frame_view: Option<wgpu::TextureView>,
|
||||
}
|
||||
|
||||
impl FrameTarget {
|
||||
pub fn new(render_target: RenderTarget) -> Self {
|
||||
Self {
|
||||
render_target,
|
||||
frame: None,
|
||||
frame_view: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the size of the [`RenderTarget`].
|
||||
pub fn size(&self) -> math::UVec2 {
|
||||
self.render_target.size()
|
||||
}
|
||||
|
||||
/// Returns the [`wgpu::TextureFormat`] of the [`RenderTarget`].
|
||||
pub fn format(&self) -> wgpu::TextureFormat {
|
||||
self.render_target.format()
|
||||
}
|
||||
|
||||
/// Create the frame using the inner [`RenderTarget`].
|
||||
pub fn create_frame(&mut self) -> &mut Frame {
|
||||
self.frame = Some(self.render_target.create_frame());
|
||||
self.frame.as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Create the [`wgpu::TextureView`] for the [`Frame`], storing it in self and returning a reference to it.
|
||||
pub fn create_frame_view(&mut self) -> &wgpu::TextureView {
|
||||
let frame = self.frame.as_ref().expect("frame was not created, cannot create view");
|
||||
|
||||
self.frame_view = Some(frame.texture().create_view(&wgpu::TextureViewDescriptor::default()));
|
||||
self.frame_view.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TargetViewChain<'a> {
|
||||
pub source: &'a mut FrameTarget,
|
||||
pub dest: &'a mut FrameTarget,
|
||||
}
|
||||
|
||||
struct ViewChain {
|
||||
source: FrameTarget,
|
||||
dest: FrameTarget,
|
||||
/// tracks the target that is currently being presented
|
||||
active: u8,
|
||||
}
|
||||
|
||||
impl ViewChain {
|
||||
/// Returns the currently active [`FrameTarget`].
|
||||
fn active(&self) -> &FrameTarget {
|
||||
if self.active == 0 {
|
||||
&self.source
|
||||
} else if self.active == 1 {
|
||||
&self.dest
|
||||
} else {
|
||||
panic!("active chain index became invalid! ({})", self.active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ViewTarget {
|
||||
device: Arc<wgpu::Device>,
|
||||
/// The primary RenderTarget, likely a Surface
|
||||
pub primary: FrameTarget,
|
||||
chain: Option<ViewChain>,
|
||||
}
|
||||
|
||||
impl ViewTarget {
|
||||
pub fn new(device: Arc<wgpu::Device>, primary: RenderTarget) -> Self {
|
||||
let mut s = Self {
|
||||
device,
|
||||
primary: FrameTarget::new(primary),
|
||||
chain: None,
|
||||
};
|
||||
|
||||
s.create_chain(s.primary.format(), s.primary.size());
|
||||
s
|
||||
}
|
||||
|
||||
/// Returns the size of the target.
|
||||
pub fn size(&self) -> math::UVec2 {
|
||||
self.primary.size()
|
||||
}
|
||||
|
||||
/// Returns the [`wgpu::TextureFormat`]
|
||||
pub fn format(&self) -> wgpu::TextureFormat {
|
||||
self.primary.format()
|
||||
}
|
||||
|
||||
/// Resize all the targets, causes the chain to be recreated.
|
||||
pub fn resize(&mut self, device: &wgpu::Device, size: math::UVec2) {
|
||||
if size != self.primary.size() {
|
||||
self.primary.render_target.resize(device, size);
|
||||
self.create_chain(self.primary.format(), size);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_chain(&mut self, format: wgpu::TextureFormat, size: math::UVec2) {
|
||||
debug!("Creating chain with {:?} format and {:?} size", format, size);
|
||||
|
||||
let mut source = FrameTarget::new(RenderTarget::new_texture(&self.device, format, size));
|
||||
source.create_frame();
|
||||
source.create_frame_view();
|
||||
|
||||
let mut dest = FrameTarget::new(RenderTarget::new_texture(&self.device, format, size));
|
||||
dest.create_frame();
|
||||
dest.create_frame_view();
|
||||
|
||||
self.chain = Some(ViewChain {
|
||||
source,
|
||||
dest,
|
||||
active: 0,
|
||||
});
|
||||
}
|
||||
|
||||
/// Cycle the target view chain, storing it in self, and returning a mutable borrow to it.
|
||||
pub fn get_chain(&mut self) -> TargetViewChain {
|
||||
let format = self.primary.format();
|
||||
let size = self.primary.size();
|
||||
|
||||
if let Some(chain) = &self.chain {
|
||||
// check if the chain needs to be recreated
|
||||
if chain.source.format() != format || chain.source.size() != size {
|
||||
self.create_chain(format, size);
|
||||
}
|
||||
} else {
|
||||
self.create_chain(format, size);
|
||||
}
|
||||
|
||||
let chain = self.chain.as_mut().unwrap();
|
||||
|
||||
if chain.active == 0 {
|
||||
chain.active = 1;
|
||||
TargetViewChain {
|
||||
source: &mut chain.source,
|
||||
dest: &mut chain.dest,
|
||||
}
|
||||
} else if chain.active == 1 {
|
||||
chain.active = 0;
|
||||
TargetViewChain {
|
||||
source: &mut chain.dest,
|
||||
dest: &mut chain.source,
|
||||
}
|
||||
} else {
|
||||
panic!("active chain index became invalid! ({})", chain.active);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [`wgpu::TextureView`] to render to.
|
||||
pub fn render_view(&self) -> &wgpu::TextureView {
|
||||
let chain = self.chain.as_ref().unwrap();
|
||||
chain.active().frame_view.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Copy the chain target to the primary target
|
||||
///
|
||||
/// The primary target must have `wgpu::TextureUsages::COPY_DST`. This also resets the active
|
||||
/// chain texture.
|
||||
pub fn copy_to_primary(&mut self, encoder: &mut wgpu::CommandEncoder) {
|
||||
let chain = self.chain.as_mut().unwrap();
|
||||
let active_tex = chain.active().frame.as_ref().unwrap().texture();
|
||||
|
||||
let active_copy = wgpu::ImageCopyTexture {
|
||||
texture: active_tex,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
};
|
||||
|
||||
let dest_tex = self.primary.frame.as_ref().unwrap().texture();
|
||||
let dest_copy = wgpu::ImageCopyTexture {
|
||||
texture: dest_tex,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
};
|
||||
|
||||
let size = self.primary.size();
|
||||
let size = wgpu::Extent3d {
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
encoder.copy_texture_to_texture(active_copy, dest_copy, size);
|
||||
|
||||
// reset active texture after a render
|
||||
// must get the chain again because of the borrow checker
|
||||
let chain = self.chain.as_mut().unwrap();
|
||||
chain.active = 0;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use std::{mem, num::{NonZeroU32, NonZeroU8}};
|
||||
use std::mem;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TextureViewDescriptor {
|
||||
|
@ -16,13 +16,13 @@ pub struct TextureViewDescriptor {
|
|||
/// 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 mip_level_count: Option<NonZeroU32>,
|
||||
pub mip_level_count: Option<u32>,
|
||||
/// 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>,
|
||||
pub array_layer_count: Option<u32>,
|
||||
}
|
||||
|
||||
impl TextureViewDescriptor {
|
||||
|
@ -79,7 +79,7 @@ pub struct SamplerDescriptor {
|
|||
/// If this is enabled, this is a comparison sampler using the given comparison function.
|
||||
pub compare: Option<wgpu::CompareFunction>,
|
||||
/// Valid values: 1, 2, 4, 8, and 16.
|
||||
pub anisotropy_clamp: Option<NonZeroU8>,
|
||||
pub anisotropy_clamp: u16,
|
||||
/// Border color to use when address_mode is [`AddressMode::ClampToBorder`]
|
||||
pub border_color: Option<wgpu::SamplerBorderColor>,
|
||||
}
|
|
@ -1,17 +1,24 @@
|
|||
pub mod point;
|
||||
pub mod directional;
|
||||
pub mod point;
|
||||
pub mod spotlight;
|
||||
|
||||
use lyra_ecs::{Entity, Tick, World};
|
||||
pub use point::*;
|
||||
pub use spotlight::*;
|
||||
|
||||
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, rc::Rc};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::math::Transform;
|
||||
|
||||
use self::directional::DirectionalLight;
|
||||
|
||||
use super::graph::LightShadowMapId;
|
||||
|
||||
const MAX_LIGHT_COUNT: usize = 16;
|
||||
|
||||
/// A struct that stores a list of lights in a wgpu::Buffer.
|
||||
|
@ -20,7 +27,7 @@ pub struct LightBuffer<U: Default + bytemuck::Pod + bytemuck::Zeroable> {
|
|||
/// The max amount of light casters that could fit in this buffer.
|
||||
pub max_count: usize,
|
||||
/// The amount of light casters that are taking up space in the buffer.
|
||||
///
|
||||
///
|
||||
/// This means that a light may be inactive in the buffer, by being replaced
|
||||
/// with a default caster as to not affect lighting. Its easier this way than
|
||||
/// to recreate the array and remove the gaps.
|
||||
|
@ -47,15 +54,27 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
}
|
||||
|
||||
/// Update an existing light in the light buffer.
|
||||
pub fn update_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) {
|
||||
let buffer_idx = *self.used_indexes.get(&entity)
|
||||
pub fn update_light(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
light: U,
|
||||
) {
|
||||
let buffer_idx = *self
|
||||
.used_indexes
|
||||
.get(&entity)
|
||||
.expect("Entity for Light is not in buffer!");
|
||||
|
||||
lights_buffer[buffer_idx] = light;
|
||||
}
|
||||
|
||||
/// Add a new light to the light buffer.
|
||||
pub fn add_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) {
|
||||
pub fn add_light(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
light: U,
|
||||
) {
|
||||
let buffer_idx = match self.dead_indexes.pop_front() {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
|
@ -67,15 +86,20 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
assert!(self.buffer_count <= self.max_count);
|
||||
|
||||
i
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.used_indexes.insert(entity, buffer_idx);
|
||||
self.update_light(lights_buffer, entity, light);
|
||||
}
|
||||
|
||||
/// Update, or add a new caster, to the light buffer.
|
||||
pub fn update_or_add(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) {
|
||||
pub fn update_or_add(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
light: U,
|
||||
) {
|
||||
if self.used_indexes.contains_key(&entity) {
|
||||
self.update_light(lights_buffer, entity, light);
|
||||
} else {
|
||||
|
@ -84,7 +108,11 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
}
|
||||
|
||||
/// Remove a caster from the buffer, returns true if it was removed.
|
||||
pub fn remove_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity) -> bool {
|
||||
pub fn remove_light(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
) -> bool {
|
||||
if let Some(removed_idx) = self.used_indexes.remove(&entity) {
|
||||
self.dead_indexes.push_back(removed_idx);
|
||||
//self.current_count -= 1;
|
||||
|
@ -98,9 +126,9 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
}
|
||||
|
||||
pub(crate) struct LightUniformBuffers {
|
||||
pub buffer: Rc<wgpu::Buffer>,
|
||||
pub bind_group: Rc<wgpu::BindGroup>,
|
||||
pub bind_group_layout: Rc<wgpu::BindGroupLayout>,
|
||||
pub buffer: Arc<wgpu::Buffer>,
|
||||
pub bind_group: Arc<wgpu::BindGroup>,
|
||||
pub bind_group_layout: Arc<wgpu::BindGroupLayout>,
|
||||
max_light_count: u64,
|
||||
}
|
||||
|
||||
|
@ -110,54 +138,44 @@ impl LightUniformBuffers {
|
|||
// TODO: ensure we dont write over this limit
|
||||
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2;
|
||||
|
||||
let buffer = device.create_buffer(
|
||||
&wgpu::BufferDescriptor {
|
||||
label: Some("UBO_Lights"),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
size: max_buffer_sizes,
|
||||
mapped_at_creation: false,
|
||||
}
|
||||
);
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("UBO_Lights"),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
size: max_buffer_sizes,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage {
|
||||
read_only: true
|
||||
},
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
],
|
||||
count: None,
|
||||
}],
|
||||
label: Some("BGL_Lights"),
|
||||
});
|
||||
|
||||
let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &bindgroup_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &buffer,
|
||||
offset: 0,
|
||||
size: None, // use the full buffer
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &buffer,
|
||||
offset: 0,
|
||||
size: None, // use the full buffer
|
||||
}),
|
||||
}],
|
||||
label: Some("BG_Lights"),
|
||||
});
|
||||
|
||||
Self {
|
||||
buffer: Rc::new(buffer),
|
||||
bind_group: Rc::new(bindgroup),
|
||||
bind_group_layout: Rc::new(bindgroup_layout),
|
||||
buffer: Arc::new(buffer),
|
||||
bind_group: Arc::new(bindgroup),
|
||||
bind_group_layout: Arc::new(bindgroup_layout),
|
||||
max_light_count: max_buffer_sizes / mem::size_of::<LightUniform>() as u64,
|
||||
}
|
||||
}
|
||||
|
@ -166,27 +184,43 @@ impl LightUniformBuffers {
|
|||
let _ = world_tick;
|
||||
let mut lights = vec![];
|
||||
|
||||
for (point_light, transform) in world.view_iter::<(&PointLight, &Transform)>() {
|
||||
let uniform = LightUniform::from_point_light_bundle(&point_light, &transform);
|
||||
for (point_light, transform, shadow_map_id) in
|
||||
world.view_iter::<(&PointLight, &Transform, Option<&LightShadowMapId>)>()
|
||||
{
|
||||
let shadow_map_id = shadow_map_id.map(|m| m.clone());
|
||||
let uniform =
|
||||
LightUniform::from_point_light_bundle(&point_light, &transform, shadow_map_id);
|
||||
lights.push(uniform);
|
||||
}
|
||||
|
||||
for (spot_light, transform) in world.view_iter::<(&SpotLight, &Transform)>() {
|
||||
let uniform = LightUniform::from_spot_light_bundle(&spot_light, &transform);
|
||||
for (spot_light, transform, shadow_map_id) in
|
||||
world.view_iter::<(&SpotLight, &Transform, Option<&LightShadowMapId>)>()
|
||||
{
|
||||
let shadow_map_id = shadow_map_id.map(|m| m.clone());
|
||||
let uniform =
|
||||
LightUniform::from_spot_light_bundle(&spot_light, &transform, shadow_map_id);
|
||||
lights.push(uniform);
|
||||
}
|
||||
|
||||
for (dir_light, transform) in world.view_iter::<(&DirectionalLight, &Transform)>() {
|
||||
let uniform = LightUniform::from_directional_bundle(&dir_light, &transform);
|
||||
for (dir_light, transform, shadow_map_id) in
|
||||
world.view_iter::<(&DirectionalLight, &Transform, Option<&LightShadowMapId>)>()
|
||||
{
|
||||
let shadow_map_id = shadow_map_id.map(|m| m.clone());
|
||||
let uniform =
|
||||
LightUniform::from_directional_bundle(&dir_light, &transform, shadow_map_id);
|
||||
lights.push(uniform);
|
||||
}
|
||||
|
||||
assert!(lights.len() < self.max_light_count as _); // ensure we dont overwrite the buffer
|
||||
assert!(lights.len() < self.max_light_count as usize); // ensure we dont overwrite the buffer
|
||||
|
||||
// write the amount of lights to the buffer, and right after that the list of lights.
|
||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights.len()]));
|
||||
// the size of u32 is multiplied by 4 because of gpu alignment requirements
|
||||
queue.write_buffer(&self.buffer, mem::size_of::<u32>() as u64 * 4, bytemuck::cast_slice(lights.as_slice()));
|
||||
queue.write_buffer(
|
||||
&self.buffer,
|
||||
mem::size_of::<u32>() as u64 * 4,
|
||||
bytemuck::cast_slice(lights.as_slice()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,17 +243,22 @@ pub(crate) struct LightUniform {
|
|||
pub color: glam::Vec3,
|
||||
// no padding is needed here since range acts as the padding
|
||||
// that would usually be needed for the vec3
|
||||
|
||||
pub range: f32,
|
||||
pub intensity: f32,
|
||||
pub smoothness: f32,
|
||||
|
||||
pub spot_cutoff_rad: f32,
|
||||
pub spot_outer_cutoff_rad: f32,
|
||||
pub light_shadow_uniform_index: [i32; 6],
|
||||
_padding: [u32; 2],
|
||||
}
|
||||
|
||||
impl LightUniform {
|
||||
pub fn from_point_light_bundle(light: &PointLight, transform: &Transform) -> Self {
|
||||
pub fn from_point_light_bundle(
|
||||
light: &PointLight,
|
||||
transform: &Transform,
|
||||
map_id: Option<LightShadowMapId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type: LightType::Point as u32,
|
||||
enabled: light.enabled as u32,
|
||||
|
@ -233,11 +272,27 @@ impl LightUniform {
|
|||
|
||||
spot_cutoff_rad: 0.0,
|
||||
spot_outer_cutoff_rad: 0.0,
|
||||
|
||||
light_shadow_uniform_index: map_id
|
||||
.map(|m| {
|
||||
[
|
||||
m.uniform_index(0) as i32,
|
||||
m.uniform_index(1) as i32,
|
||||
m.uniform_index(2) as i32,
|
||||
m.uniform_index(3) as i32,
|
||||
m.uniform_index(4) as i32,
|
||||
m.uniform_index(5) as i32,
|
||||
]
|
||||
})
|
||||
.unwrap_or([-1; 6]),
|
||||
_padding: [0; 2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_directional_bundle(light: &DirectionalLight, transform: &Transform) -> Self {
|
||||
pub fn from_directional_bundle(
|
||||
light: &DirectionalLight,
|
||||
transform: &Transform,
|
||||
map_id: Option<LightShadowMapId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type: LightType::Directional as u32,
|
||||
enabled: light.enabled as u32,
|
||||
|
@ -251,11 +306,28 @@ impl LightUniform {
|
|||
|
||||
spot_cutoff_rad: 0.0,
|
||||
spot_outer_cutoff_rad: 0.0,
|
||||
light_shadow_uniform_index: map_id
|
||||
.map(|m| {
|
||||
[
|
||||
m.uniform_index(0) as i32,
|
||||
m.uniform_index(1) as i32,
|
||||
m.uniform_index(2) as i32,
|
||||
m.uniform_index(3) as i32,
|
||||
m.uniform_index(4) as i32,
|
||||
m.uniform_index(5) as i32,
|
||||
]
|
||||
})
|
||||
.unwrap_or([-1; 6]),
|
||||
_padding: [0; 2],
|
||||
}
|
||||
}
|
||||
|
||||
// Create the SpotLightUniform from an ECS bundle
|
||||
pub fn from_spot_light_bundle(light: &SpotLight, transform: &Transform) -> Self {
|
||||
pub fn from_spot_light_bundle(
|
||||
light: &SpotLight,
|
||||
transform: &Transform,
|
||||
map_id: Option<LightShadowMapId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type: LightType::Spotlight as u32,
|
||||
enabled: light.enabled as u32,
|
||||
|
@ -269,7 +341,19 @@ impl LightUniform {
|
|||
|
||||
spot_cutoff_rad: light.cutoff.to_radians(),
|
||||
spot_outer_cutoff_rad: light.outer_cutoff.to_radians(),
|
||||
light_shadow_uniform_index: map_id
|
||||
.map(|m| {
|
||||
[
|
||||
m.uniform_index(0) as i32,
|
||||
m.uniform_index(1) as i32,
|
||||
m.uniform_index(2) as i32,
|
||||
m.uniform_index(3) as i32,
|
||||
m.uniform_index(4) as i32,
|
||||
m.uniform_index(5) as i32,
|
||||
]
|
||||
})
|
||||
.unwrap_or([-1; 6]),
|
||||
_padding: [0; 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,8 +17,8 @@ pub(crate) struct LightIndicesGridBuffer {
|
|||
}
|
||||
|
||||
pub(crate) struct LightCullCompute {
|
||||
device: Rc<wgpu::Device>,
|
||||
queue: Rc<wgpu::Queue>,
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
pipeline: ComputePipeline,
|
||||
pub light_indices_grid: LightIndicesGridBuffer,
|
||||
screen_size_buffer: BufferWrapper,
|
||||
|
@ -153,7 +153,7 @@ impl LightCullCompute {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new(device: Rc<wgpu::Device>, queue: Rc<wgpu::Queue>, screen_size: PhysicalSize<u32>, lights_buffers: &LightUniformBuffers, camera_buffers: &BufferWrapper, depth_texture: &mut RenderTexture) -> Self {
|
||||
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, screen_size: PhysicalSize<u32>, lights_buffers: &LightUniformBuffers, camera_buffers: &BufferWrapper, depth_texture: &mut RenderTexture) -> Self {
|
||||
let screen_size_buffer = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.label_prefix("ScreenSize")
|
|
@ -1,9 +1,10 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use lyra_resource::{ResHandle, Texture};
|
||||
|
||||
use super::texture::RenderTexture;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MaterialSpecular {
|
||||
pub factor: f32,
|
||||
pub color_factor: glam::Vec3,
|
||||
|
@ -11,7 +12,7 @@ pub struct MaterialSpecular {
|
|||
pub color_texture: Option<RenderTexture>,
|
||||
}
|
||||
|
||||
fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> {
|
||||
fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> {
|
||||
if let Some(tex) = i {
|
||||
RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok()
|
||||
} else {
|
||||
|
@ -20,7 +21,7 @@ fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc<
|
|||
}
|
||||
|
||||
impl MaterialSpecular {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Specular) -> Self {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_gltf::Specular) -> Self {
|
||||
let tex = texture_to_render(device, queue, &bg_layout, &value.texture);
|
||||
let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture);
|
||||
|
||||
|
@ -45,7 +46,7 @@ pub struct Material {
|
|||
}
|
||||
|
||||
impl Material {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_gltf::Material) -> Self {
|
||||
let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture);
|
||||
|
||||
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));
|
||||
|
@ -57,7 +58,7 @@ impl Material {
|
|||
//diffuse: glam::Vec3::new(value.base_color.x, value.base_color.y, value.base_color.z),
|
||||
//diffuse: glam::Vec3::new(1.0, 0.5, 0.31),
|
||||
//specular: glam::Vec3::new(0.5, 0.5, 0.5),
|
||||
ambient: glam::Vec3::new(1.0, 1.0, 1.0),
|
||||
ambient: glam::Vec3::new(1.0, 1.0, 1.0) * 0.5,
|
||||
diffuse: glam::Vec3::new(1.0, 1.0, 1.0),
|
||||
shininess: 32.0,
|
||||
|
|
@ -9,9 +9,14 @@ pub mod texture;
|
|||
pub mod shader_loader;
|
||||
pub mod material;
|
||||
pub mod camera;
|
||||
pub mod window;
|
||||
pub mod transform_buffer_storage;
|
||||
pub mod light;
|
||||
//pub mod light_cull_compute;
|
||||
pub mod avec;
|
||||
pub mod graph;
|
||||
pub mod graph;
|
||||
|
||||
mod texture_atlas;
|
||||
pub use texture_atlas::*;
|
||||
|
||||
mod slot_buffer;
|
||||
pub use slot_buffer::*;
|
|
@ -1,4 +1,4 @@
|
|||
use std::{num::NonZeroU32, rc::Rc};
|
||||
use std::{num::NonZeroU32, sync::Arc};
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
|
@ -23,11 +23,11 @@ impl RenderBuffer {
|
|||
|
||||
pub struct BindGroupPair {
|
||||
pub bindgroup: wgpu::BindGroup,
|
||||
pub layout: Rc<wgpu::BindGroupLayout>,
|
||||
pub layout: Arc<wgpu::BindGroupLayout>,
|
||||
}
|
||||
|
||||
impl BindGroupPair {
|
||||
pub fn create_bind_group(device: &wgpu::Device, layout: Rc<wgpu::BindGroupLayout>, entries: &[wgpu::BindGroupEntry<'_>]) -> Self {
|
||||
pub fn create_bind_group(device: &wgpu::Device, layout: Arc<wgpu::BindGroupLayout>, entries: &[wgpu::BindGroupEntry<'_>]) -> Self {
|
||||
let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &layout,
|
||||
entries,
|
||||
|
@ -43,7 +43,7 @@ impl BindGroupPair {
|
|||
pub fn new(bindgroup: wgpu::BindGroup, layout: wgpu::BindGroupLayout) -> Self {
|
||||
Self {
|
||||
bindgroup,
|
||||
layout: Rc::new(layout),
|
||||
layout: Arc::new(layout),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,9 +116,9 @@ impl BufferWrapper {
|
|||
/// match the layout of this bind group.
|
||||
///
|
||||
/// See [`wgpu::RenderPass::set_bind_group`](https://docs.rs/wgpu/latest/wgpu/struct.RenderPass.html#method.set_bind_group).
|
||||
pub fn render_pass_bind_at<'a, 'b>(
|
||||
pub fn render_pass_bind_at<'a>(
|
||||
&'a self,
|
||||
pass: &'b mut wgpu::RenderPass<'a>,
|
||||
pass: &mut wgpu::RenderPass<'a>,
|
||||
index: u32,
|
||||
offsets: &[wgpu::DynamicOffset],
|
||||
) {
|
||||
|
@ -136,7 +136,7 @@ impl BufferWrapper {
|
|||
}
|
||||
|
||||
/// Take the bind group layout, the bind group, and the buffer out of the wrapper.
|
||||
pub fn parts(self) -> (Option<Rc<wgpu::BindGroupLayout>>, Option<wgpu::BindGroup>, wgpu::Buffer) {
|
||||
pub fn parts(self) -> (Option<Arc<wgpu::BindGroupLayout>>, Option<wgpu::BindGroup>, wgpu::Buffer) {
|
||||
if let Some(pair) = self.bindgroup_pair {
|
||||
(Some(pair.layout), Some(pair.bindgroup), self.inner_buf)
|
||||
} else {
|
||||
|
@ -297,7 +297,7 @@ impl BufferWrapperBuilder {
|
|||
|
||||
BindGroupPair {
|
||||
bindgroup: bg,
|
||||
layout: Rc::new(bg_layout),
|
||||
layout: Arc::new(bg_layout),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -308,7 +308,7 @@ impl BufferWrapperBuilder {
|
|||
len: Some(self.count.unwrap_or_default() as usize),
|
||||
} */
|
||||
|
||||
(Rc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize)
|
||||
(Arc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize)
|
||||
}
|
||||
|
||||
pub fn finish(self, device: &wgpu::Device) -> BufferWrapper {
|
||||
|
@ -316,7 +316,7 @@ impl BufferWrapperBuilder {
|
|||
|
||||
BufferWrapper {
|
||||
bindgroup_pair: Some(BindGroupPair {
|
||||
layout: Rc::new(bgl),
|
||||
layout: Arc::new(bgl),
|
||||
bindgroup: bg
|
||||
}),
|
||||
inner_buf: buff,
|
|
@ -0,0 +1,292 @@
|
|||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use lyra_ecs::World;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use tracing::{debug, instrument, warn};
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SubGraphNode, TransformsNode, TransformsNodeLabel, ViewTarget};
|
||||
|
||||
use super::graph::RenderGraph;
|
||||
use super::{resource::RenderPipeline, render_job::RenderJob};
|
||||
|
||||
use crate::math;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ScreenSize(glam::UVec2);
|
||||
|
||||
impl Deref for ScreenSize {
|
||||
type Target = glam::UVec2;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ScreenSize {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
struct TestSubGraphLabel;
|
||||
|
||||
pub trait Renderer {
|
||||
fn prepare(&mut self, main_world: &mut World);
|
||||
fn render(&mut self) -> Result<(), wgpu::SurfaceError>;
|
||||
fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize<u32>);
|
||||
|
||||
fn surface_size(&self) -> winit::dpi::PhysicalSize<u32>;
|
||||
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<RenderPipeline>);
|
||||
}
|
||||
|
||||
pub trait RenderPass {
|
||||
fn prepare(&mut self, main_world: &mut World);
|
||||
fn render(&mut self, encoder: &mut wgpu::CommandEncoder) -> Result<(), wgpu::SurfaceError>;
|
||||
fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>);
|
||||
}
|
||||
|
||||
pub struct BasicRenderer {
|
||||
pub device: Arc<wgpu::Device>, // device does not need to be mutable, no need for refcell
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub size: winit::dpi::PhysicalSize<u32>,
|
||||
pub window: Arc<Window>,
|
||||
|
||||
pub clear_color: wgpu::Color,
|
||||
|
||||
pub render_pipelines: rustc_hash::FxHashMap<u64, Arc<RenderPipeline>>,
|
||||
pub render_jobs: VecDeque<RenderJob>,
|
||||
|
||||
graph: RenderGraph,
|
||||
}
|
||||
|
||||
impl BasicRenderer {
|
||||
#[instrument(skip(world, window))]
|
||||
pub async fn create_with_window(world: &mut World, window: Arc<Window>) -> BasicRenderer {
|
||||
let size = window.inner_size();
|
||||
world.add_resource(ScreenSize(glam::UVec2::new(size.width, size.height)));
|
||||
|
||||
// Get a GPU handle
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: wgpu::Backends::all(),
|
||||
dx12_shader_compiler: Default::default(),
|
||||
flags: wgpu::InstanceFlags::default(),
|
||||
gles_minor_version: wgpu::Gles3MinorVersion::Automatic,
|
||||
});
|
||||
|
||||
let surface: wgpu::Surface::<'static> = instance.create_surface(window.clone()).unwrap();
|
||||
|
||||
let adapter = instance.request_adapter(
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
},
|
||||
).await.unwrap();
|
||||
|
||||
let (device, queue) = adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
required_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
// WebGL does not support all wgpu features.
|
||||
// Not sure if the engine will ever completely support WASM,
|
||||
// but its here just in case
|
||||
required_limits: if cfg!(target_arch = "wasm32") {
|
||||
wgpu::Limits::downlevel_webgl2_defaults()
|
||||
} else {
|
||||
wgpu::Limits {
|
||||
max_bind_groups: 8,
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
||||
},
|
||||
None,
|
||||
).await.unwrap();
|
||||
|
||||
let surface_caps = surface.get_capabilities(&adapter);
|
||||
let present_mode = surface_caps.present_modes[0];
|
||||
debug!("present mode: {:?}", present_mode);
|
||||
|
||||
let surface_format = surface_caps.formats.iter()
|
||||
.copied()
|
||||
.find(|f| f.is_srgb())
|
||||
.unwrap_or(surface_caps.formats[0]);
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
|
||||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::default(), //wgpu::PresentMode::Mailbox, // "Fast Vsync"
|
||||
alpha_mode: surface_caps.alpha_modes[0],
|
||||
desired_maximum_frame_latency: 2,
|
||||
view_formats: vec![],
|
||||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let device = Arc::new(device);
|
||||
let queue = Arc::new(queue);
|
||||
|
||||
let surface_target = RenderTarget::from_surface(surface, config);
|
||||
let view_target = Rc::new(RefCell::new(ViewTarget::new(device.clone(), surface_target)));
|
||||
|
||||
let mut main_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone());
|
||||
|
||||
debug!("Adding base pass");
|
||||
main_graph.add_node(BasePassLabel, BasePass::new());
|
||||
|
||||
{
|
||||
let mut forward_plus_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone());
|
||||
|
||||
debug!("Adding light base pass");
|
||||
forward_plus_graph.add_node(LightBasePassLabel, LightBasePass::new());
|
||||
|
||||
debug!("Adding light cull compute pass");
|
||||
forward_plus_graph.add_node(LightCullComputePassLabel, LightCullComputePass::new(size));
|
||||
|
||||
debug!("Adding Transforms node");
|
||||
forward_plus_graph.add_node(TransformsNodeLabel, TransformsNode::new());
|
||||
|
||||
debug!("Adding shadow maps pass");
|
||||
forward_plus_graph.add_node(ShadowMapsPassLabel, ShadowMapsPass::new(&device));
|
||||
|
||||
debug!("Adding mesh prep node");
|
||||
let mesh_prep = MeshPrepNode::new(&device);
|
||||
let material_bgl = mesh_prep.material_bgl.clone();
|
||||
forward_plus_graph.add_node(MeshPrepNodeLabel, mesh_prep);
|
||||
debug!("Adding mesh pass");
|
||||
forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new(material_bgl));
|
||||
forward_plus_graph.add_edge(TransformsNodeLabel, MeshPrepNodeLabel);
|
||||
|
||||
forward_plus_graph.add_edge(LightBasePassLabel, LightCullComputePassLabel);
|
||||
forward_plus_graph.add_edge(LightCullComputePassLabel, MeshesPassLabel);
|
||||
forward_plus_graph.add_edge(MeshPrepNodeLabel, MeshesPassLabel);
|
||||
|
||||
// run ShadowMapsPass after MeshPrep and before MeshesPass
|
||||
forward_plus_graph.add_edge(MeshPrepNodeLabel, ShadowMapsPassLabel);
|
||||
forward_plus_graph.add_edge(ShadowMapsPassLabel, MeshesPassLabel);
|
||||
|
||||
main_graph.add_sub_graph(TestSubGraphLabel, forward_plus_graph);
|
||||
main_graph.add_node(TestSubGraphLabel, SubGraphNode::new(TestSubGraphLabel,
|
||||
vec![
|
||||
/* RenderGraphLabelValue::from(BasePassSlots::WindowTextureView),
|
||||
RenderGraphLabelValue::from(BasePassSlots::MainRenderTarget), */
|
||||
RenderGraphLabelValue::from(BasePassSlots::DepthTexture),
|
||||
RenderGraphLabelValue::from(BasePassSlots::DepthTextureView),
|
||||
RenderGraphLabelValue::from(BasePassSlots::Camera),
|
||||
RenderGraphLabelValue::from(BasePassSlots::ScreenSize),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
main_graph.add_node(FxaaPassLabel, FxaaPass::default());
|
||||
main_graph.add_edge(TestSubGraphLabel, FxaaPassLabel);
|
||||
|
||||
//let present_pass_label = PresentPassLabel::new(BasePassSlots::Frame);//TintPassSlots::Frame);
|
||||
let p = PresentPass;
|
||||
main_graph.add_node(PresentPassLabel, p);
|
||||
|
||||
main_graph.add_edge(BasePassLabel, TestSubGraphLabel);
|
||||
main_graph.add_edge(TestSubGraphLabel, PresentPassLabel);
|
||||
|
||||
/* debug!("Adding base pass");
|
||||
g.add_node(BasePassLabel, BasePass::new(surface_target));
|
||||
|
||||
//debug!("Adding triangle pass");
|
||||
//g.add_node(TrianglePass::new());
|
||||
|
||||
|
||||
|
||||
debug!("Adding present pass");
|
||||
let present_pass_label = PresentPassLabel::new(BasePassSlots::Frame);//TintPassSlots::Frame);
|
||||
let p = PresentPass::from_node_label(present_pass_label.clone());
|
||||
g.add_node(p.label.clone(), p); */
|
||||
|
||||
/* debug!("adding tint pass");
|
||||
g.add_node(TintPassLabel, TintPass::new(surface_target));
|
||||
|
||||
g.add_edge(BasePassLabel, TintPassLabel);
|
||||
g.add_edge(LightCullComputePassLabel, TintPassLabel);
|
||||
g.add_edge(MeshesPassLabel, TintPassLabel);
|
||||
|
||||
g.add_edge(TintPassLabel, present_pass_label.clone());
|
||||
*/
|
||||
|
||||
/* g.add_edge(BasePassLabel, LightBasePassLabel);
|
||||
g.add_edge(LightBasePassLabel, LightCullComputePassLabel);
|
||||
g.add_edge(BasePassLabel, MeshesPassLabel);
|
||||
|
||||
g.add_edge(BasePassLabel, present_pass_label.clone());
|
||||
g.add_edge(LightCullComputePassLabel, present_pass_label.clone());
|
||||
g.add_edge(MeshesPassLabel, present_pass_label.clone()); */
|
||||
|
||||
main_graph.setup(&device);
|
||||
|
||||
Self {
|
||||
window,
|
||||
device,
|
||||
queue,
|
||||
size,
|
||||
clear_color: wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
},
|
||||
render_pipelines: Default::default(),
|
||||
render_jobs: Default::default(),
|
||||
|
||||
graph: main_graph,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer for BasicRenderer {
|
||||
#[instrument(skip(self, main_world))]
|
||||
fn prepare(&mut self, main_world: &mut World) {
|
||||
self.graph.prepare(main_world);
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||
self.graph.render();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(world, self))]
|
||||
fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
|
||||
// update surface config and the surface
|
||||
/* let mut rt = self.graph.slot_value_mut(BasePassSlots::MainRenderTarget)
|
||||
.unwrap().as_render_target_mut().unwrap();
|
||||
rt.resize(&self.device, math::UVec2::new(new_size.width, new_size.height)); */
|
||||
self.graph.view_target_mut().resize(&self.device, math::UVec2::new(new_size.width, new_size.height));
|
||||
/* rt.surface_config.width = new_size.width;
|
||||
rt.surface_config.height = new_size.height;
|
||||
rt.surface.configure(&self.device, &rt.surface_config); */
|
||||
|
||||
// update screen size resource in ecs
|
||||
let mut world_ss = world.get_resource_mut::<ScreenSize>()
|
||||
.expect("world missing ScreenSize resource");
|
||||
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn surface_size(&self) -> winit::dpi::PhysicalSize<u32> {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<RenderPipeline>) {
|
||||
self.render_pipelines.insert(shader_id, pipeline);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue