Compare commits

...

104 Commits

Author SHA1 Message Date
SeanOMik 62adcf2b50 ecs: create DynTypeId::as_unknown
CI / build (push) Failing after 6m11s Details
2024-11-06 21:23:01 -05:00
SeanOMik 4816b7333e
Make system_update_world_transforms also update world transforms for SceneGraphs
CI / build (push) Failing after 2m53s Details
Before this couldn't be done since lyra-scene could not depend on lyra-resource, it would have caused a cyclic dependency. The last commit, where I created lyra-gltf from code inside lyra-resource, made this possible
2024-11-01 19:45:29 -04:00
SeanOMik 5542467d7e
separate GLTF loader to its own crate 2024-11-01 12:09:01 -04:00
SeanOMik 3ce9ab6fb3
move crates into 'crates' folder 2024-11-01 11:17:36 -04:00
SeanOMik f02d3c6b2f
render: create a transform pass for sending transforms to the GPU
CI / build (push) Failing after 3m33s Details
2024-11-01 11:05:51 -04:00
SeanOMik 7ae0eae6ac Merge pull request 'Improve Lua ECS' (#30) from feat/improve-lua-ecs-29 into main
CI / build (push) Failing after 3m52s Details
Reviewed-on: #30
2024-10-29 23:22:19 -04:00
SeanOMik fae2cdfadc
lua: remove old implementation of world:view, replacing it with the new version
CI / build (pull_request) Failing after 4m7s Details
2024-10-29 23:20:52 -04:00
SeanOMik 076676e486
lua: write lua annotations for all queries and view one
CI / build (pull_request) Failing after 3m6s Details
2024-10-29 23:04:00 -04:00
SeanOMik 0e613bd216
lua: implement world:view_one for lua 2024-10-29 21:56:07 -04:00
SeanOMik 964c4ec423
lua: create LuaOptionalQuery 2024-10-29 14:22:03 -04:00
SeanOMik 23a215ba46
lua: create LuaTickOfQuery 2024-10-29 09:32:32 -04:00
SeanOMik 42112c2cf1
lua: create LuaOrQuery 2024-10-29 09:19:54 -04:00
SeanOMik f2ff2a9855
lua: start using LuaQueryResult in all lua ecs queries 2024-10-29 09:10:14 -04:00
SeanOMik 7c2efe3c6f
lua: create LuaNotQuery 2024-10-29 08:48:30 -04:00
SeanOMik cb3c3a601f
lua: create LuaQueryResult for simplying implementation of queries and filters 2024-10-23 16:47:45 -04:00
SeanOMik 8072ec1c7e
lua: create LuaHasQuery 2024-10-23 16:31:47 -04:00
SeanOMik 4dbd96832f
lua: add world:get_tick() to lua, write some missing type annotations
I tried to use generics to improve existing annotations, but I don't think it changed anything since the annotations for generics are kind of garbage
2024-10-21 21:58:22 -04:00
SeanOMik 2e33de5da2
lua: implement Changed query that supports components and resources 2024-10-20 21:20:43 -04:00
SeanOMik 74465ce614
lua: code cleanup 2024-10-19 20:51:54 -04:00
SeanOMik 380b15e560
lua: implement ecs queries that work with the new Views 2024-10-19 20:42:28 -04:00
SeanOMik 2ffdd4085b
lua: create `View`s that can query from the world 2024-10-19 17:45:59 -04:00
SeanOMik 156cbf25a4
fix ci by ignoring tracy tsc check
CI / build (push) Failing after 3m13s Details
2024-10-19 11:48:24 -04:00
SeanOMik b2d259ac71 Merge pull request 'Expose structs to Lua and write Lua type annotations' (#28) from feat/lua-type-defs into main
CI / build (push) Failing after 2m43s Details
Reviewed-on: #28
2024-10-19 11:16:33 -04:00
SeanOMik d001e136d0
lua: expose WorldTransform
CI / build (pull_request) Failing after 3m20s Details
2024-10-17 17:11:46 -04:00
SeanOMik d0e6fc6ecd
lua: make it easier to expose events and asset handle wrappers 2024-10-13 12:30:06 -04:00
SeanOMik 6a47cd2671
lua: expose DeviceEvent 2024-10-13 11:43:49 -04:00
SeanOMik 8e56ee1f0f
lua: start exposing events 2024-10-11 20:49:00 -04:00
SeanOMik 9e9478966b
lua: cleanup 2024-10-09 12:06:08 -04:00
SeanOMik 624cd5362f
lua: change lyra-scripting path in lyra-engine crate 2024-10-09 11:08:21 -04:00
SeanOMik eff6b221e0
remove unused code, cleanup some warnings 2024-10-09 10:56:54 -04:00
SeanOMik 77ec620adb
lua: remove unused fields in FreeFlyCamera 2024-10-09 10:30:45 -04:00
SeanOMik 6f65e2ce35
lua: add lua type defs for FreeFlyCamera and change name of field 2024-10-08 20:49:57 -04:00
SeanOMik b90e19161d
lua: expose FreeFlyCamera 2024-10-07 16:28:38 -04:00
SeanOMik e9cbb48653
lua: expose camera, support ToLua and FromLua structs in World:view 2024-10-07 15:20:13 -04:00
SeanOMik 49dfb38da3
lua: expose fields on some types from lyra_resource 2024-10-05 13:46:53 -04:00
SeanOMik 140ca506d6
lua: create type defs for World, Entity, ActionHandler, all asset handlers, add globals file 2024-10-04 23:48:58 -04:00
SeanOMik 06a4301c23
lua: create type defs for Vec2, Vec3, Vec4, Quat, Transform, and DeltaTime 2024-10-04 15:07:42 -04:00
SeanOMik de14b6211b
lua: create type defs for Window and start on Vec2 2024-10-03 19:07:11 -04:00
SeanOMik a2c52a0bb8
ecs: fix Changed query; lua: lock and hide mouse in window
CI / build (push) Failing after 3m10s Details
2024-10-02 21:29:13 -04:00
SeanOMik 76b7cac699
lua: expose most fields for window component 2024-10-02 20:54:54 -04:00
SeanOMik 64099f598c
fix ci test step
CI / build (push) Failing after 2m35s Details
2024-09-30 19:59:26 -04:00
SeanOMik 958c86cf73 Merge pull request 'Fix #19: Lua crashes when spawning entities in optimized builds' (#27) from fix/scripting-switch-to-mlua into main
CI / build (push) Failing after 3m44s Details
Reviewed-on: #27
2024-09-29 21:39:20 -04:00
SeanOMik ef2b0bf326
ecs,scripting: fix invalid resources being passed to lua
CI / build (pull_request) Failing after 3m35s Details
The issue was World::get_resource_ptr, it was returning a pointer to the AtomicRefCell instead of the actual resource data
2024-09-29 14:35:24 -04:00
SeanOMik fa22a0310c
scripting: switch to latest mlua, create custom impl of lua's `getmetatable` 2024-09-29 15:59:48 -04:00
SeanOMik 02f0c93aa2
game: fix some unhandled device events causing panics 2024-09-28 22:05:57 -04:00
SeanOMik 8fb686b7fe
scripting: switch to mlua, scripts need to be tested and fixed
currently the lua-scripting example doesnt work. For some reason the userdata's metatable is a boolean...
2024-09-28 12:32:37 -04:00
SeanOMik 798719a7a2
game: remove unused enum InputEvent, remove some warnings 2024-09-27 21:09:33 -04:00
SeanOMik d6d6b2df72
game: improve event system 2024-09-27 21:03:57 -04:00
SeanOMik f5aca87ede
ecs: don't automatically tick the world, use Res and ResMut anywhere ecs resources are requested to track changes better
now the user must manually tick the world. The engine will do this before every update
2024-09-27 21:03:27 -04:00
SeanOMik 9b1cc8c364
game: improve event handling, update input systems to use new event handling
CI / build (push) Failing after 12m37s Details
2024-09-24 20:43:08 -04:00
SeanOMik 9125b91977
ecs: add WorldTick query, implement IntoSystem and FnSystem for funcs with 11 args
accidentially missed the macro call for 11 arguments
2024-09-24 20:30:37 -04:00
SeanOMik eb43fad6c7 Merge pull request 'Update wgpu to 0.20.1 and winit to 0.30.3' (#26) from chore/winit-wgpu-update into main
CI / build (push) Failing after 14m58s Details
Reviewed-on: #26
2024-09-23 19:06:26 -04:00
SeanOMik 7219013593
delete Cargo.lock.old
CI / build (pull_request) Failing after 3m18s Details
2024-09-23 18:53:32 -04:00
SeanOMik 33ddf689be
game: create sync window system, handle more window events in the winit plugin 2024-09-22 21:17:40 -04:00
SeanOMik 393b4206d3
game: start on updated WindowOptions component and window_sync_system 2024-09-21 14:09:24 -04:00
SeanOMik 782d64f6cf
ecs: implement an actual Filter trait, create a Changed filter 2024-09-21 14:06:21 -04:00
SeanOMik 2107b8f7b0
engine: move winit ApplicationHandler to winit plugin 2024-09-19 17:30:30 -04:00
SeanOMik 8b1077cab7
engine: get a window showing and things rendered 2024-09-18 21:45:15 -04:00
SeanOMik 45fd190409
update wgpu and winit to latest versions
need to make a WinitPlugin though, so no window currently
2024-09-18 19:47:55 -04:00
SeanOMik 2b44d7f354 render: implement wgsl-preprocessor, split shaders
CI / build (push) Failing after 7m56s Details
2024-09-14 20:08:18 -04:00
SeanOMik 60c139f9b2 ecs: create DynamicViewOne
CI / build (push) Failing after 11m20s Details
2024-09-02 20:34:02 -04:00
SeanOMik 256025849e Merge pull request 'Implement Shadows' (#24) from feat/shadow-maps into main
CI / build (push) Successful in 3m38s Details
Reviewed-on: #24
2024-08-09 23:10:28 -04:00
SeanOMik 8545e7e27d
render: rewrite PCF for spot lights to somehow fix PCSS directional lights
CI / build (pull_request) Successful in 9m48s Details
2024-08-09 22:01:57 -04:00
SeanOMik a85178eeea Revert "render: shadow maps and PCF for spot lights"
This reverts commit 8c1738334c.
2024-08-09 21:51:56 -04:00
SeanOMik 8c1738334c
render: shadow maps and PCF for spot lights 2024-07-24 20:10:32 -04:00
SeanOMik fefcf58765
render: make shadow depth bias configurable per light source 2024-07-21 21:53:02 -04:00
SeanOMik b0a6d30afc
render: fix directional light shadows 2024-07-21 21:09:29 -04:00
SeanOMik fef709d5f1
render: implement PCF for point lights, support per-light shadow settings 2024-07-21 12:02:35 -04:00
SeanOMik c91ee67961
render: improve shadow settings to make it possible to switch between PCF, PCSS, hardware 2x2 PCF, or disable filtering all together 2024-07-19 17:56:27 -04:00
SeanOMik c961568b96
render: update the shadow filting poisson disc when shadow settings are modified 2024-07-19 16:07:40 -04:00
SeanOMik 54b47c2178
ecs: implement change tracking for world resources 2024-07-19 16:07:03 -04:00
SeanOMik 4449172c2b
render: implement PCSS for directional lights 2024-07-18 23:43:08 -04:00
SeanOMik 4c6c6c4dd5
render: PCF with poisson disc on directional lights 2024-07-14 22:14:08 -04:00
SeanOMik 27bc88c5a7
render: pass shadow settings to gpu 2024-07-14 19:46:15 -04:00
SeanOMik ff06bd55f3
render: simple PCF 2024-07-14 19:06:38 -04:00
SeanOMik d02258224a
render: fix bug with texture atlas not packing textures in last column 2024-07-14 12:24:13 -04:00
SeanOMik b45c2f4fab
render: point light shadows in texture atlas, fix bug with unaligned GpuSlotBuffer 2024-07-13 00:56:09 -04:00
SeanOMik 40fa9c09da
render: fix shadow map atlas packing by writing my own skyline packer 2024-07-12 14:58:18 -04:00
SeanOMik 87aa440691
render: create a GpuSlotBuffer for stable indices in a gpu buffer 2024-07-11 20:00:46 -04:00
SeanOMik cc1c482c40
render: provide shadow texture atlas frame for each shadow casting light 2024-07-11 18:27:26 -04:00
SeanOMik a4ce4cb432
render: implement packed texture atlas for shadow maps 2024-07-10 20:16:21 -04:00
SeanOMik e2b554b4ef
render: implement simple texture atlas for the shadow maps 2024-07-05 17:29:38 -04:00
SeanOMik 6d57b40629
render: cull back faces, code cleanup to fix warnings 2024-07-04 23:28:21 -04:00
SeanOMik fd65f754cf
render: get simple directional shadow maps working 2024-07-04 13:43:36 -04:00
SeanOMik 6c6893149a
render: bind direction light projection matrix to meshes shader 2024-06-30 21:58:08 -04:00
SeanOMik 1c649b2eb6
render: bind the shadow map atlas to the meshes shaders 2024-06-30 21:42:08 -04:00
SeanOMik 7b2d2424a3
render: start moving to a shadow map atlas texture and expose the resources as slots 2024-06-30 20:56:41 -04:00
SeanOMik e8974bbd44
render: create a depth map for the directional light 2024-06-30 19:33:51 -04:00
SeanOMik 3a80c069c9
render: move most of the mesh processing to a MeshPrepare node
Moving that out of the MeshesPass makes the rendering meshes accessible to other passes/nodes. The shadow pass will need access to them which is why this was done now
2024-06-29 22:23:49 -04:00
SeanOMik 7ff67a194b
create an example for testing shadow maps 2024-06-28 16:15:21 -04:00
SeanOMik c4aebdb25d
render: implement fxaa (#8)
CI / build (push) Successful in 3m33s Details
2024-06-28 15:26:14 -04:00
SeanOMik 5ebbec8cf9
ci: switch to ForgeJo actions
CI / build (push) Successful in 3m33s Details
2024-06-28 13:50:26 -04:00
SeanOMik 2eeca335e2
game: run clippy 2024-06-28 13:25:48 -04:00
SeanOMik 96dea5b1f9
render: code cleanup 2024-06-28 13:16:47 -04:00
SeanOMik 007b1047ef
render: implement view target chains for post processing steps 2024-06-27 23:48:24 -04:00
SeanOMik 545da71cda
render: create ViewTarget to make it easier to render to a non-surface texture and chain together postprocess steps 2024-06-26 21:33:39 -04:00
SeanOMik 0e71c5734f
render: make it easier to get Frame from RenderTarget 2024-06-26 17:14:31 -04:00
SeanOMik 6c1bff5768
render: get sub render graphs working and create a simple test of them 2024-06-25 21:32:29 -04:00
SeanOMik 5f1a61ef52
render: create RenderTarget and Frame, making it easier to render to a non-surface texture 2024-06-23 20:25:57 -04:00
SeanOMik f755a4c53b
render: create a node for sub render graphs, create a graph init node 2024-06-22 17:00:32 -04:00
SeanOMik 9d3de88c50
render: cleanup 2024-06-21 22:10:35 -04:00
SeanOMik f440f306be
render: impl Node for RenderGraph in prep for sub-graphs 2024-06-21 22:06:58 -04:00
SeanOMik f17bf40c77
render: rename RenderGraph::add_pass to add_node 2024-06-21 21:58:40 -04:00
SeanOMik 5fc1a0f134
Add todo panic in code when window is resized 2024-06-16 20:05:04 -04:00
331 changed files with 20949 additions and 8550 deletions

View File

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

5
.gitmodules vendored
View File

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

36
.vscode/launch.json vendored
View File

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

View File

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

View File

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

3402
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
#![feature(associated_type_defaults)]
extern crate self as lyra_ecs;
#[allow(unused_imports)]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

235
crates/lyra-game/src/game.rs Executable file
View File

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

View File

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

View File

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

View File

@ -88,6 +88,8 @@ pub enum MouseButton {
Left,
Right,
Middle,
Back,
Forward,
Other(u16),
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 545 B

After

Width:  |  Height:  |  Size: 545 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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