Compare commits
1 Commits
main
...
debug/sig-
Author | SHA1 | Date |
---|---|---|
SeanOMik | 3209d65b93 |
|
@ -1,36 +0,0 @@
|
||||||
name: CI
|
|
||||||
|
|
||||||
env:
|
|
||||||
# Runners don't expose the TSC but we want to make sure these tests work, so we
|
|
||||||
# can ignore it.
|
|
||||||
TRACY_NO_INVARIANT_CHECK: 1
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: docker
|
|
||||||
container: git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
apt update
|
|
||||||
apt install libudev-dev lua5.4 liblua5.4-dev -y
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
cargo build
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
cargo test --all
|
|
|
@ -1,6 +1,3 @@
|
||||||
[submodule "lyra-scripting/elua"]
|
[submodule "lyra-scripting/elua"]
|
||||||
path = crates/lyra-scripting/elua
|
path = lyra-scripting/elua
|
||||||
url = ../elua.git # git@git.seanomik.net:SeanOMik/elua.git
|
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
|
|
||||||
|
|
|
@ -7,12 +7,11 @@
|
||||||
{
|
{
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug lyra lua-scripting",
|
"name": "Debug example lua-scripting",
|
||||||
"cargo": {
|
"cargo": {
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"--manifest-path", "${workspaceFolder}/examples/lua-scripting/Cargo.toml"
|
"--manifest-path", "${workspaceFolder}/examples/lua-scripting/Cargo.toml"
|
||||||
//"--bin=testbed",
|
|
||||||
],
|
],
|
||||||
"filter": {
|
"filter": {
|
||||||
"name": "lua-scripting",
|
"name": "lua-scripting",
|
||||||
|
@ -40,24 +39,6 @@
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}/examples/testbed"
|
"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",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
|
@ -0,0 +1,20 @@
|
||||||
|
variables:
|
||||||
|
- &rust_image 'git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm'
|
||||||
|
|
||||||
|
when:
|
||||||
|
event: [release, pull_request, manual]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
Build - Release:
|
||||||
|
image: *rust_image
|
||||||
|
commands:
|
||||||
|
- apt update
|
||||||
|
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||||
|
- cargo build --release
|
||||||
|
|
||||||
|
Test - Release:
|
||||||
|
image: *rust_image
|
||||||
|
commands:
|
||||||
|
- apt update
|
||||||
|
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||||
|
- cargo test --all --release
|
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
|
@ -5,15 +5,19 @@ edition = "2021"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/*",
|
"examples/testbed",
|
||||||
|
"lyra-resource",
|
||||||
|
"lyra-ecs",
|
||||||
|
"lyra-reflect",
|
||||||
|
"lyra-scripting",
|
||||||
|
"lyra-game",
|
||||||
|
"lyra-math",
|
||||||
|
"lyra-scene",
|
||||||
|
|
||||||
"examples/2d",
|
"examples/many-lights",
|
||||||
"examples/fixed-timestep-rotating-model",
|
"examples/fixed-timestep-rotating-model",
|
||||||
"examples/lua-scripting",
|
"examples/lua-scripting",
|
||||||
"examples/many-lights",
|
"examples/simple_scene"
|
||||||
"examples/shadows",
|
|
||||||
"examples/simple_scene",
|
|
||||||
"examples/testbed",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -22,11 +26,11 @@ lua_scripting = ["scripting", "lyra-scripting/lua"]
|
||||||
tracy = ["lyra-game/tracy"]
|
tracy = ["lyra-game/tracy"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lyra-game = { path = "crates/lyra-game" }
|
lyra-game = { path = "lyra-game" }
|
||||||
lyra-scripting = { path = "crates/lyra-scripting", optional = true }
|
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||||
|
|
||||||
#[profile.dev]
|
[profile.dev]
|
||||||
#opt-level = 1
|
opt-level = 1
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
|
@ -1,168 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use crate::{query::{AsFilter, AsQuery, Fetch, Filter, Query}, Component, ComponentColumn, DynTypeId, Tick, World};
|
|
||||||
|
|
||||||
pub struct ChangedFetcher<'a, T> {
|
|
||||||
col: &'a ComponentColumn,
|
|
||||||
tick: Tick,
|
|
||||||
_phantom: PhantomData<&'a T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Fetch<'a> for ChangedFetcher<'a, T>
|
|
||||||
where
|
|
||||||
T: 'a,
|
|
||||||
{
|
|
||||||
type Item = bool;
|
|
||||||
|
|
||||||
fn dangling() -> Self {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
|
||||||
let tick = self.col.entity_ticks[entity.0 as usize];
|
|
||||||
*tick >= (*self.tick) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A filter that fetches components that have changed.
|
|
||||||
///
|
|
||||||
/// Since [`AsQuery`] is implemented for `&T`, you can use this query like this:
|
|
||||||
/// ```nobuild
|
|
||||||
/// for ts in world.view::<&T>() {
|
|
||||||
/// println!("Got a &T!");
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub struct Changed<T> {
|
|
||||||
type_id: DynTypeId,
|
|
||||||
_phantom: PhantomData<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Component> Default for Changed<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
type_id: DynTypeId::of::<T>(),
|
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// manually implemented to avoid a Copy bound on T
|
|
||||||
impl<T> Copy for Changed<T> {}
|
|
||||||
|
|
||||||
// manually implemented to avoid a Clone bound on T
|
|
||||||
impl<T> Clone for Changed<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Component> Changed<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Component> Query for Changed<T>
|
|
||||||
where
|
|
||||||
T: 'static
|
|
||||||
{
|
|
||||||
type Item<'a> = bool;
|
|
||||||
type Fetch<'a> = ChangedFetcher<'a, T>;
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
Changed::<T>::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
|
||||||
archetype.has_column(self.type_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn fetch<'a>(&self, w: &'a World, a: &'a crate::archetype::Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
|
||||||
ChangedFetcher {
|
|
||||||
col: a.get_column(self.type_id).unwrap(),
|
|
||||||
tick: w.current_tick(),
|
|
||||||
_phantom: PhantomData::<&T>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Component> AsQuery for Changed<T> {
|
|
||||||
type Query = Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Component> Filter for Changed<T> { }
|
|
||||||
|
|
||||||
impl<T: Component> AsFilter for Changed<T> {
|
|
||||||
type Filter = Self;
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
mod has;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
pub use has::*;
|
|
||||||
|
|
||||||
mod or;
|
|
||||||
pub use or::*;
|
|
||||||
|
|
||||||
mod not;
|
|
||||||
pub use not::*;
|
|
||||||
|
|
||||||
mod changed;
|
|
||||||
pub use changed::*;
|
|
||||||
|
|
||||||
use super::Fetch;
|
|
||||||
|
|
||||||
/// A fetcher that just returns a provided value
|
|
||||||
pub struct StaticFetcher<T: Clone> {
|
|
||||||
value: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: Clone> StaticFetcher<T> {
|
|
||||||
pub fn new(value: T) -> Self {
|
|
||||||
Self {
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl<'a, T> Fetch<'a> for StaticFetcher<T>
|
|
||||||
where
|
|
||||||
T: Clone + 'a,
|
|
||||||
{
|
|
||||||
type Item = T;
|
|
||||||
|
|
||||||
fn dangling() -> Self {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_item(&mut self, _: crate::world::ArchetypeEntityId) -> Self::Item {
|
|
||||||
self.value.clone()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
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>) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,216 +0,0 @@
|
||||||
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>) { }
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,138 +0,0 @@
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use glam::Vec2;
|
|
||||||
use lyra_ecs::query::ResMut;
|
|
||||||
use winit::{event::{MouseScrollDelta, WindowEvent}, keyboard::PhysicalKey};
|
|
||||||
|
|
||||||
use crate::{game::GameStages, plugin::Plugin, winit::DeviceEventPair, EventReader, EventWriter};
|
|
||||||
|
|
||||||
use super::{events::*, InputButtons, KeyCode};
|
|
||||||
|
|
||||||
fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter<MouseScroll>, delta: &MouseScrollDelta) {
|
|
||||||
let event = match delta {
|
|
||||||
MouseScrollDelta::LineDelta(x, y) => MouseScroll {
|
|
||||||
unit: MouseScrollUnit::Line(Vec2::new(*x, *y)),
|
|
||||||
},
|
|
||||||
MouseScrollDelta::PixelDelta(delta) => MouseScroll {
|
|
||||||
unit: MouseScrollUnit::Pixel(Vec2::new(delta.x as f32, delta.y as f32)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
mouse_scroll_ev.write(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_key_event(key_buttons: &mut ResMut<InputButtons<KeyCode>>, physical_key: PhysicalKey, state: winit::event::ElementState) {
|
|
||||||
if let PhysicalKey::Code(code) = physical_key {
|
|
||||||
key_buttons.add_input_from_winit(KeyCode::from(code), state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input_system(
|
|
||||||
mut key_code_res: ResMut<InputButtons<KeyCode>>,
|
|
||||||
mut mouse_btn_res: ResMut<InputButtons<MouseButton>>,
|
|
||||||
mut touches_res: ResMut<Touches>,
|
|
||||||
window_ev: EventReader<WindowEvent>,
|
|
||||||
device_ev: EventReader<DeviceEventPair>,
|
|
||||||
mut mouse_scroll_ev: EventWriter<MouseScroll>,
|
|
||||||
mouse_btn_ev: EventWriter<MouseButton>,
|
|
||||||
mouse_exact_ev: EventWriter<MouseExact>,
|
|
||||||
mouse_entered_ev: EventWriter<CursorEnteredWindow>,
|
|
||||||
mouse_left_ev: EventWriter<CursorLeftWindow>,
|
|
||||||
mouse_motion_ev: EventWriter<MouseMotion>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
while let Some(event) = window_ev.read() {
|
|
||||||
match event.deref() {
|
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
|
||||||
write_key_event(&mut key_code_res, event.physical_key, event.state);
|
|
||||||
},
|
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
|
||||||
let exact = MouseExact {
|
|
||||||
pos: Vec2::new(position.x as f32, position.y as f32)
|
|
||||||
};
|
|
||||||
|
|
||||||
mouse_exact_ev.write(exact);
|
|
||||||
},
|
|
||||||
WindowEvent::CursorEntered { .. } => {
|
|
||||||
mouse_entered_ev.write(CursorEnteredWindow);
|
|
||||||
},
|
|
||||||
WindowEvent::CursorLeft { .. } => {
|
|
||||||
mouse_left_ev.write(CursorLeftWindow);
|
|
||||||
},
|
|
||||||
WindowEvent::MouseWheel { delta, .. } => {
|
|
||||||
write_scroll_delta(&mut mouse_scroll_ev, delta);
|
|
||||||
},
|
|
||||||
WindowEvent::MouseInput { button, state, .. } => {
|
|
||||||
let button_event = match button {
|
|
||||||
winit::event::MouseButton::Left => MouseButton::Left,
|
|
||||||
winit::event::MouseButton::Right => MouseButton::Right,
|
|
||||||
winit::event::MouseButton::Middle => MouseButton::Middle,
|
|
||||||
winit::event::MouseButton::Back => MouseButton::Back,
|
|
||||||
winit::event::MouseButton::Forward => MouseButton::Forward,
|
|
||||||
winit::event::MouseButton::Other(v) => MouseButton::Other(*v),
|
|
||||||
};
|
|
||||||
|
|
||||||
mouse_btn_ev.write(button_event);
|
|
||||||
mouse_btn_res.add_input_from_winit(button_event, *state);
|
|
||||||
},
|
|
||||||
WindowEvent::Touch(t) => {
|
|
||||||
let touch = Touch {
|
|
||||||
phase: TouchPhase::from(t.phase),
|
|
||||||
location: Vec2::new(t.location.x as f32, t.location.y as f32),
|
|
||||||
force: t.force.map(Force::from),
|
|
||||||
finger_id: t.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
touches_res.touches.push(touch);
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(device) = device_ev.read() {
|
|
||||||
match &device.event {
|
|
||||||
winit::event::DeviceEvent::Motion { .. } => {
|
|
||||||
// TODO: handle device motion events
|
|
||||||
// A todo! isn't used since these are triggered alongside MouseMotion events
|
|
||||||
}
|
|
||||||
winit::event::DeviceEvent::MouseMotion { delta } => {
|
|
||||||
let delta = MouseMotion {
|
|
||||||
delta: Vec2::new(delta.0 as f32, delta.1 as f32)
|
|
||||||
};
|
|
||||||
|
|
||||||
mouse_motion_ev.write(delta);
|
|
||||||
},
|
|
||||||
winit::event::DeviceEvent::MouseWheel { delta } => {
|
|
||||||
write_scroll_delta(&mut mouse_scroll_ev, delta);
|
|
||||||
},
|
|
||||||
winit::event::DeviceEvent::Key(key) => {
|
|
||||||
write_key_event(&mut key_code_res, key.physical_key, key.state);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
todo!("unhandled device event: {:?}", device.event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Plugin that runs InputSystem
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct InputPlugin;
|
|
||||||
|
|
||||||
impl Plugin for InputPlugin {
|
|
||||||
fn setup(&mut self, app: &mut crate::game::App) {
|
|
||||||
app.add_resource(InputButtons::<KeyCode>::default());
|
|
||||||
app.add_resource(InputButtons::<MouseButton>::default());
|
|
||||||
app.add_resource(Touches::default());
|
|
||||||
|
|
||||||
app.register_event::<MouseScroll>();
|
|
||||||
app.register_event::<MouseButton>();
|
|
||||||
app.register_event::<MouseMotion>();
|
|
||||||
app.register_event::<MouseExact>();
|
|
||||||
app.register_event::<CursorEnteredWindow>();
|
|
||||||
app.register_event::<CursorLeftWindow>();
|
|
||||||
|
|
||||||
app.add_system_to_stage(GameStages::PreUpdate, "input", input_system, &[]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,572 +0,0 @@
|
||||||
mod node;
|
|
||||||
use std::{
|
|
||||||
cell::{Ref, RefCell, RefMut}, collections::VecDeque, fmt::Debug, hash::Hash, rc::Rc, sync::Arc
|
|
||||||
};
|
|
||||||
|
|
||||||
use lyra_ecs::World;
|
|
||||||
pub use node::*;
|
|
||||||
|
|
||||||
mod passes;
|
|
||||||
pub use passes::*;
|
|
||||||
|
|
||||||
mod slot_desc;
|
|
||||||
pub use slot_desc::*;
|
|
||||||
|
|
||||||
mod context;
|
|
||||||
pub use context::*;
|
|
||||||
|
|
||||||
mod render_target;
|
|
||||||
pub use render_target::*;
|
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use tracing::{debug_span, instrument, trace, warn};
|
|
||||||
use wgpu::CommandEncoder;
|
|
||||||
|
|
||||||
use super::resource::{ComputePipeline, Pass, Pipeline, RenderPipeline};
|
|
||||||
|
|
||||||
/// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`].
|
|
||||||
pub trait RenderGraphLabel: Debug + 'static {
|
|
||||||
fn rc_clone(&self) -> Rc<dyn RenderGraphLabel>;
|
|
||||||
fn as_label_hash(&self) -> u64;
|
|
||||||
|
|
||||||
fn label_eq_rc(&self, other: &Rc<dyn RenderGraphLabel>) -> bool {
|
|
||||||
self.as_label_hash() == other.as_label_hash()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn label_eq(&self, other: &dyn RenderGraphLabel) -> bool {
|
|
||||||
self.as_label_hash() == other.as_label_hash()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An owned [`RenderGraphLabel`].
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RenderGraphLabelValue(Rc<dyn RenderGraphLabel>);
|
|
||||||
|
|
||||||
impl Debug for RenderGraphLabelValue {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<L: RenderGraphLabel> From<L> for RenderGraphLabelValue {
|
|
||||||
fn from(value: L) -> Self {
|
|
||||||
Self(Rc::new(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
|
||||||
fn from(value: Rc<dyn RenderGraphLabel>) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
|
||||||
fn from(value: &Rc<dyn RenderGraphLabel>) -> Self {
|
|
||||||
Self(value.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for RenderGraphLabelValue {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
state.write_u64(self.0.as_label_hash());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for RenderGraphLabelValue {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0.label_eq_rc(&other.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for RenderGraphLabelValue {}
|
|
||||||
|
|
||||||
struct NodeEntry {
|
|
||||||
/// The Node
|
|
||||||
inner: Arc<RefCell<dyn Node>>,
|
|
||||||
/// The Node descriptor
|
|
||||||
desc: Rc<RefCell<NodeDesc>>,
|
|
||||||
/// The index of the node in the execution graph
|
|
||||||
graph_index: petgraph::matrix_graph::NodeIndex<usize>,
|
|
||||||
/// The Node's optional pipeline
|
|
||||||
pipeline: Rc<RefCell<Option<Pipeline>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct BindGroupEntry {
|
|
||||||
label: RenderGraphLabelValue,
|
|
||||||
/// BindGroup
|
|
||||||
bg: Arc<wgpu::BindGroup>,
|
|
||||||
/// BindGroupLayout
|
|
||||||
layout: Option<Arc<wgpu::BindGroupLayout>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ResourceSlot {
|
|
||||||
label: RenderGraphLabelValue,
|
|
||||||
ty: SlotType,
|
|
||||||
value: SlotValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RenderGraph {
|
|
||||||
device: Arc<wgpu::Device>,
|
|
||||||
queue: Arc<wgpu::Queue>,
|
|
||||||
slots: FxHashMap<RenderGraphLabelValue, ResourceSlot>,
|
|
||||||
nodes: FxHashMap<RenderGraphLabelValue, NodeEntry>,
|
|
||||||
sub_graphs: FxHashMap<RenderGraphLabelValue, RenderGraph>,
|
|
||||||
bind_groups: FxHashMap<RenderGraphLabelValue, BindGroupEntry>,
|
|
||||||
/// A directed graph used to determine dependencies of nodes.
|
|
||||||
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
|
|
||||||
view_target: Rc<RefCell<ViewTarget>>,
|
|
||||||
shader_prepoc: wgsl_preprocessor::Processor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderGraph {
|
|
||||||
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>) -> Self {
|
|
||||||
Self {
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
slots: Default::default(),
|
|
||||||
nodes: Default::default(),
|
|
||||||
sub_graphs: Default::default(),
|
|
||||||
bind_groups: Default::default(),
|
|
||||||
node_graph: Default::default(),
|
|
||||||
view_target,
|
|
||||||
shader_prepoc: wgsl_preprocessor::Processor::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn device(&self) -> &wgpu::Device {
|
|
||||||
&self.device
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a [`Node`] to the RenderGraph.
|
|
||||||
///
|
|
||||||
/// When the node is added, its [`Node::desc`] method will be executed.
|
|
||||||
///
|
|
||||||
/// Additionally, all [`Slot`](node::NodeSlot)s of the node will be iterated,
|
|
||||||
/// 1. Ensuring that there are no two slots of the same name, with different value types
|
|
||||||
/// 2. Changing the id of insert slots to match the id of the output slot of the same name.
|
|
||||||
/// * This means that the id of insert slots **ARE NOT STABLE**. **DO NOT** rely on them to
|
|
||||||
/// not change. The IDs of output slots do stay the same.
|
|
||||||
/// 3. Ensuring that no two slots share the same ID when the names do not match.
|
|
||||||
#[instrument(skip(self, node), level = "debug")]
|
|
||||||
pub fn add_node<P: Node>(&mut self, label: impl RenderGraphLabel, mut node: P) {
|
|
||||||
let mut desc = node.desc(self);
|
|
||||||
|
|
||||||
// collect all the slots of the node
|
|
||||||
for slot in &mut desc.slots {
|
|
||||||
if let Some(other) = self
|
|
||||||
.slots
|
|
||||||
.get_mut(&slot.label)
|
|
||||||
{
|
|
||||||
debug_assert_eq!(
|
|
||||||
slot.ty, other.ty,
|
|
||||||
"slot {:?} in node {:?} does not match existing slot of same name",
|
|
||||||
slot.label, label
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
debug_assert!(!self.slots.contains_key(&slot.label),
|
|
||||||
"Reuse of id detected in render graph! Node: {:?}, slot: {:?}",
|
|
||||||
label, slot.label,
|
|
||||||
);
|
|
||||||
|
|
||||||
let res_slot = ResourceSlot {
|
|
||||||
label: slot.label.clone(),
|
|
||||||
ty: slot.ty,
|
|
||||||
value: slot.value.clone().unwrap_or(SlotValue::None),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.slots.insert(slot.label.clone(), res_slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get clones of the bind groups and layouts
|
|
||||||
for (label, bg, bgl) in &desc.bind_groups {
|
|
||||||
self.bind_groups.insert(label.clone(), BindGroupEntry {
|
|
||||||
label: label.clone(),
|
|
||||||
bg: bg.clone(),
|
|
||||||
layout: bgl.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let label: RenderGraphLabelValue = label.into();
|
|
||||||
let index = self.node_graph.add_node(label.clone());
|
|
||||||
|
|
||||||
self.nodes.insert(
|
|
||||||
label,
|
|
||||||
NodeEntry {
|
|
||||||
inner: Arc::new(RefCell::new(node)),
|
|
||||||
desc: Rc::new(RefCell::new(desc)),
|
|
||||||
graph_index: index,
|
|
||||||
pipeline: Rc::new(RefCell::new(None)),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates all buffers required for the nodes.
|
|
||||||
///
|
|
||||||
/// This only needs to be ran when the [`Node`]s in the graph change, or they are removed or
|
|
||||||
/// added.
|
|
||||||
#[instrument(skip(self, device))]
|
|
||||||
pub fn setup(&mut self, device: &wgpu::Device) {
|
|
||||||
// For all nodes, create their pipelines
|
|
||||||
for node in self.nodes.values_mut() {
|
|
||||||
let desc = (*node.desc).borrow();
|
|
||||||
if let Some(pipeline_desc) = &desc.pipeline_desc {
|
|
||||||
let pipeline = match desc.ty {
|
|
||||||
NodeType::Render => Pipeline::Render(RenderPipeline::create(
|
|
||||||
device,
|
|
||||||
pipeline_desc
|
|
||||||
.as_render_pipeline_descriptor()
|
|
||||||
.expect("got compute pipeline descriptor in a render node"),
|
|
||||||
)),
|
|
||||||
NodeType::Compute => Pipeline::Compute(ComputePipeline::create(
|
|
||||||
device,
|
|
||||||
pipeline_desc
|
|
||||||
.as_compute_pipeline_descriptor()
|
|
||||||
.expect("got render pipeline descriptor in a compute node"),
|
|
||||||
)),
|
|
||||||
NodeType::Presenter | NodeType::Node | NodeType::Graph => {
|
|
||||||
panic!("Present or Node RenderGraph nodes should not have a pipeline descriptor!");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
drop(desc);
|
|
||||||
|
|
||||||
let mut node_pipeline = node.pipeline.borrow_mut();
|
|
||||||
*node_pipeline = Some(pipeline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for sub in self.sub_graphs.values_mut() {
|
|
||||||
sub.setup(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self, world))]
|
|
||||||
pub fn prepare(&mut self, world: &mut World) {
|
|
||||||
let mut buffer_writes = VecDeque::<GraphBufferWrite>::new();
|
|
||||||
// reserve some buffer writes. not all nodes write so half the amount of them is probably
|
|
||||||
// fine.
|
|
||||||
buffer_writes.reserve(self.nodes.len() / 2);
|
|
||||||
|
|
||||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.node_graph, None)
|
|
||||||
.expect("RenderGraph had cycled!")
|
|
||||||
.iter()
|
|
||||||
.map(|i| self.node_graph[*i].clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
while let Some(node_label) = sorted.pop_front() {
|
|
||||||
let node = self.nodes.get(&node_label).unwrap();
|
|
||||||
let device = self.device.clone();
|
|
||||||
let queue = self.queue.clone();
|
|
||||||
|
|
||||||
let inner = node.inner.clone();
|
|
||||||
let mut inner = inner.borrow_mut();
|
|
||||||
|
|
||||||
let mut context = RenderGraphContext::new(device, queue, None, node_label.clone());
|
|
||||||
inner.prepare(self, world, &mut context);
|
|
||||||
buffer_writes.append(&mut context.buffer_writes);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// Queue all buffer writes to the gpu
|
|
||||||
let s = debug_span!("queue_buffer_writes");
|
|
||||||
let _e = s.enter();
|
|
||||||
|
|
||||||
while let Some(bufwr) = buffer_writes.pop_front() {
|
|
||||||
let slot = self
|
|
||||||
.slots
|
|
||||||
.get(&bufwr.target_slot)
|
|
||||||
.unwrap_or_else(|| panic!("Failed to find slot '{:?}' for buffer write",
|
|
||||||
bufwr.target_slot));
|
|
||||||
let buf = slot
|
|
||||||
.value
|
|
||||||
.as_buffer()
|
|
||||||
.unwrap_or_else(|| panic!("Slot '{:?}' is not a buffer", bufwr.target_slot));
|
|
||||||
|
|
||||||
self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_encoder(&self) -> CommandEncoder {
|
|
||||||
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
||||||
label: Some("graph encoder"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
|
||||||
pub fn render(&mut self) {
|
|
||||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.node_graph, None)
|
|
||||||
.expect("RenderGraph had cycled!")
|
|
||||||
.iter()
|
|
||||||
.map(|i| self.node_graph[*i].clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// A bit of 'encoder hot potato' is played using this.
|
|
||||||
// Although the encoder is an option, its only an option so ownership of it can be given
|
|
||||||
// to the context for the time of the node execution.
|
|
||||||
// After the node is executed, the encoder is taken back. If the node is a presenter node,
|
|
||||||
// the encoder will be submitted and a new one will be made.
|
|
||||||
let mut encoder = Some(self.create_encoder());
|
|
||||||
|
|
||||||
while let Some(node_label) = sorted.pop_front() {
|
|
||||||
let node = self.nodes.get(&node_label).unwrap();
|
|
||||||
let node_inn = node.inner.clone();
|
|
||||||
|
|
||||||
let node_desc = node.desc.clone();
|
|
||||||
let node_desc = (*node_desc).borrow();
|
|
||||||
|
|
||||||
// clone of the Rc's is required to appease the borrow checker
|
|
||||||
let device = self.device.clone();
|
|
||||||
let queue = self.queue.clone();
|
|
||||||
|
|
||||||
// create a new encoder if the last one was submitted
|
|
||||||
if encoder.is_none() {
|
|
||||||
encoder = Some(self.create_encoder());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut context = RenderGraphContext::new(device, queue, encoder.take(), node_label.clone());
|
|
||||||
|
|
||||||
trace!("Executing {:?}", node_label.0);
|
|
||||||
let mut inner = node_inn.borrow_mut();
|
|
||||||
inner.execute(self, &node_desc, &mut context);
|
|
||||||
|
|
||||||
// take back the encoder from the context
|
|
||||||
encoder = context.encoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(encoder) = encoder {
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slot_value<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&SlotValue> {
|
|
||||||
self.slots.get(&label.into()).map(|s| &s.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slot_value_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut SlotValue> {
|
|
||||||
self.slots.get_mut(&label.into()).map(|s| &mut s.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn node_desc<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<NodeDesc>> {
|
|
||||||
self.nodes.get(&label.into()).map(|s| (*s.desc).borrow())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn pipeline<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<Pipeline>> {
|
|
||||||
self.nodes.get(&label.into())
|
|
||||||
.and_then(|p| {
|
|
||||||
let v = p.pipeline.borrow();
|
|
||||||
|
|
||||||
#[allow(clippy::manual_map)]
|
|
||||||
match &*v {
|
|
||||||
Some(_) => Some(Ref::map(v, |p| p.as_ref().unwrap())),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn try_bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroup>> {
|
|
||||||
self.bind_groups.get(&label.into()).map(|e| &e.bg)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroup> {
|
|
||||||
let l = label.into();
|
|
||||||
self.try_bind_group(l.clone()).unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn try_bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroupLayout>> {
|
|
||||||
self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroupLayout> {
|
|
||||||
let l = label.into();
|
|
||||||
self.try_bind_group_layout(l.clone())
|
|
||||||
.unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_edge(&mut self, from: impl RenderGraphLabel, to: impl RenderGraphLabel)
|
|
||||||
{
|
|
||||||
let from = RenderGraphLabelValue::from(from);
|
|
||||||
let to = RenderGraphLabelValue::from(to);
|
|
||||||
|
|
||||||
let from_idx = self
|
|
||||||
.nodes
|
|
||||||
.iter()
|
|
||||||
.find(|p| *p.0 == from)
|
|
||||||
.map(|p| p.1.graph_index)
|
|
||||||
.expect("Failed to find from node");
|
|
||||||
let to_idx = self
|
|
||||||
.nodes
|
|
||||||
.iter()
|
|
||||||
.find(|p| *p.0 == to)
|
|
||||||
.map(|p| p.1.graph_index)
|
|
||||||
.expect("Failed to find to node");
|
|
||||||
|
|
||||||
debug_assert_ne!(from_idx, to_idx, "cannot add edges between the same node");
|
|
||||||
|
|
||||||
self.node_graph.add_edge(from_idx, to_idx, ());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility method for setting the bind groups for a node.
|
|
||||||
///
|
|
||||||
/// The parameter `bind_groups` can be used to specify the labels of a bind group, and the
|
|
||||||
/// index of the bind group in the pipeline for the node. If a bind group of the provided
|
|
||||||
/// name is not found in the graph, a panic will occur.
|
|
||||||
///
|
|
||||||
/// # Example:
|
|
||||||
/// ```nobuild
|
|
||||||
/// graph.set_bind_groups(
|
|
||||||
/// &mut pass,
|
|
||||||
/// &[
|
|
||||||
/// // retrieves the `BasePassSlots::DepthTexture` bind group and sets the index 0 in the
|
|
||||||
/// // node to it.
|
|
||||||
/// (&BaseNodeSlots::DepthTexture, 0),
|
|
||||||
/// (&BaseNodeSlots::Camera, 1),
|
|
||||||
/// (&LightBaseNodeSlots::Lights, 2),
|
|
||||||
/// (&LightCullComputeNodeSlots::LightIndicesGridGroup, 3),
|
|
||||||
/// (&BaseNodeSlots::ScreenSize, 4),
|
|
||||||
/// ],
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// Panics if a bind group of a provided name is not found.
|
|
||||||
pub fn set_bind_groups<'a, P: Pass<'a>>(
|
|
||||||
&'a self,
|
|
||||||
pass: &mut P,
|
|
||||||
bind_groups: &[(&dyn RenderGraphLabel, u32)],
|
|
||||||
) {
|
|
||||||
for (label, index) in bind_groups {
|
|
||||||
let bg = self
|
|
||||||
.bind_group(label.rc_clone());
|
|
||||||
|
|
||||||
pass.set_bind_group(*index, bg, &[]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sub_graph_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut RenderGraph> {
|
|
||||||
self.sub_graphs.get_mut(&label.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a sub graph.
|
|
||||||
///
|
|
||||||
/// > Note: the sub graph is not ran unless you add a node that executes it. See [`SubGraphNode`].
|
|
||||||
pub fn add_sub_graph<L: Into<RenderGraphLabelValue>>(&mut self, label: L, sub: RenderGraph) {
|
|
||||||
self.sub_graphs.insert(label.into(), sub);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clone rendering resources (slots, bind groups, etc.) to a sub graph.
|
|
||||||
fn clone_resources_to_sub(&mut self, sub_graph: RenderGraphLabelValue, slots: Vec<RenderGraphLabelValue>) {
|
|
||||||
// instead of inserting the slots to the sub graph as they are extracted from the parent graph,
|
|
||||||
// they are done separately to make the borrow checker happy. If this is not done,
|
|
||||||
// the borrow checker complains about multiple mutable borrows (or an inmutable borrow
|
|
||||||
// while mutable borrowing) to self; caused by borrowing the sub graph from self, and
|
|
||||||
// self.slots.
|
|
||||||
let mut collected_slots = VecDeque::new();
|
|
||||||
let mut collected_bind_groups = VecDeque::new();
|
|
||||||
|
|
||||||
for slot in slots.iter() {
|
|
||||||
let mut found_res = false;
|
|
||||||
|
|
||||||
// Since slots and bind groups may go by the same label,
|
|
||||||
// there must be a way to collect both of them. A flag variable is used to detect
|
|
||||||
// if neither was found.
|
|
||||||
|
|
||||||
if let Some(slot_res) = self.slots.get(slot) {
|
|
||||||
collected_slots.push_back(slot_res.clone());
|
|
||||||
found_res = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(bg_res) = self.bind_groups.get(slot) {
|
|
||||||
collected_bind_groups.push_back(bg_res.clone());
|
|
||||||
found_res = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found_res {
|
|
||||||
panic!("sub graph is missing {:?} input slot or bind group", slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sg = self.sub_graph_mut(sub_graph.clone()).unwrap();
|
|
||||||
while let Some(res) = collected_slots.pop_front() {
|
|
||||||
sg.slots.insert(res.label.clone(), res);
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(bg) = collected_bind_groups.pop_front() {
|
|
||||||
sg.bind_groups.insert(bg.label.clone(), bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_target(&self) -> Ref<ViewTarget> {
|
|
||||||
self.view_target.borrow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_target_mut(&self) -> RefMut<ViewTarget> {
|
|
||||||
self.view_target.borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register a shader with the preprocessor.
|
|
||||||
///
|
|
||||||
/// This step also parses the shader and will return errors if it failed to parse.
|
|
||||||
///
|
|
||||||
/// Returns: The shader module import path if the module specified one.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> {
|
|
||||||
self.shader_prepoc.parse_module(shader_src)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Preprocess a shader, returning the source.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
|
|
||||||
self.shader_prepoc.preprocess_module(shader_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SubGraphNode {
|
|
||||||
subg: RenderGraphLabelValue,
|
|
||||||
slots: Vec<RenderGraphLabelValue>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SubGraphNode {
|
|
||||||
pub fn new<L: Into<RenderGraphLabelValue>>(sub_label: L, slot_labels: Vec<RenderGraphLabelValue>) -> Self {
|
|
||||||
Self {
|
|
||||||
subg: sub_label.into(),
|
|
||||||
slots: slot_labels,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node for SubGraphNode {
|
|
||||||
fn desc(&mut self, _: &mut RenderGraph) -> NodeDesc {
|
|
||||||
NodeDesc::new(NodeType::Graph, None, vec![])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, _: &mut RenderGraphContext) {
|
|
||||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
|
||||||
|
|
||||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
|
||||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
|
||||||
sg.prepare(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute(
|
|
||||||
&mut self,
|
|
||||||
graph: &mut RenderGraph,
|
|
||||||
_: &NodeDesc,
|
|
||||||
_: &mut RenderGraphContext,
|
|
||||||
) {
|
|
||||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
|
||||||
|
|
||||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
|
||||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
|
||||||
sg.render();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
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,
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,667 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,507 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
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::*;
|
|
|
@ -1,44 +0,0 @@
|
||||||
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
|
@ -1,168 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,212 +0,0 @@
|
||||||
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,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,355 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use crate::math;
|
|
||||||
|
|
||||||
enum RenderTargetInner {
|
|
||||||
Surface {
|
|
||||||
/// The surface that will be rendered to.
|
|
||||||
///
|
|
||||||
/// You can create a new surface with a `'static` lifetime if you have an `Arc<Window>`:
|
|
||||||
/// ```nobuild
|
|
||||||
/// let window = Arc::new(window);
|
|
||||||
/// let surface = instance.create_surface(Arc::clone(&window))?;
|
|
||||||
/// ```
|
|
||||||
surface: wgpu::Surface<'static>,
|
|
||||||
/// the configuration of the surface render target..
|
|
||||||
config: wgpu::SurfaceConfiguration,
|
|
||||||
},
|
|
||||||
Texture {
|
|
||||||
/// The texture that will be rendered to.
|
|
||||||
texture: Arc<wgpu::Texture>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A render target that is a surface or a texture.
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct RenderTarget(RenderTargetInner);
|
|
||||||
|
|
||||||
impl From<wgpu::Texture> for RenderTarget {
|
|
||||||
fn from(value: wgpu::Texture) -> Self {
|
|
||||||
Self(RenderTargetInner::Texture { texture: Arc::new(value) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderTarget {
|
|
||||||
pub fn from_surface(surface: wgpu::Surface<'static>, config: wgpu::SurfaceConfiguration) -> Self {
|
|
||||||
Self(RenderTargetInner::Surface { surface, config })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_texture(device: &wgpu::Device, format: wgpu::TextureFormat, size: math::UVec2) -> Self {
|
|
||||||
let tex = device.create_texture(&wgpu::TextureDescriptor {
|
|
||||||
label: None,
|
|
||||||
size: wgpu::Extent3d {
|
|
||||||
width: size.x,
|
|
||||||
height: size.y,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: wgpu::TextureDimension::D2,
|
|
||||||
format,
|
|
||||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
|
|
||||||
view_formats: &[],
|
|
||||||
});
|
|
||||||
|
|
||||||
Self(RenderTargetInner::Texture { texture: Arc::new(tex) })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format(&self) -> wgpu::TextureFormat {
|
|
||||||
match &self.0 {
|
|
||||||
RenderTargetInner::Surface { config, .. } => config.format,
|
|
||||||
RenderTargetInner::Texture { texture } => texture.format(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> math::UVec2 {
|
|
||||||
match &self.0 {
|
|
||||||
RenderTargetInner::Surface { config, .. } => math::UVec2::new(config.width, config.height),
|
|
||||||
RenderTargetInner::Texture { texture } => {
|
|
||||||
let s = texture.size();
|
|
||||||
math::UVec2::new(s.width, s.height)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the frame texture of the [`RenderTarget`]
|
|
||||||
///
|
|
||||||
/// If this is target is a surface and the frame texture was already retrieved from the swap
|
|
||||||
/// chain, a [`wgpu::SurfaceError`] error will be returned.
|
|
||||||
pub fn frame_texture(&self) -> Result<FrameTexture, wgpu::SurfaceError> {
|
|
||||||
match &self.0 {
|
|
||||||
RenderTargetInner::Surface { surface, .. } => Ok(FrameTexture::Surface(surface.get_current_texture()?)),
|
|
||||||
RenderTargetInner::Texture { texture } => Ok(FrameTexture::Texture(texture.clone())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, device: &wgpu::Device, new_size: math::UVec2) {
|
|
||||||
match &mut self.0 {
|
|
||||||
RenderTargetInner::Surface { surface, config } => {
|
|
||||||
config.width = new_size.x;
|
|
||||||
config.height = new_size.y;
|
|
||||||
surface.configure(device, config);
|
|
||||||
},
|
|
||||||
RenderTargetInner::Texture { texture } => {
|
|
||||||
let format = texture.format();
|
|
||||||
let size = self.size();
|
|
||||||
|
|
||||||
*self = Self::new_texture(device, format, size);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the frame of the RenderTarget.
|
|
||||||
///
|
|
||||||
/// If this is target is a surface and the frame texture was already retrieved from the
|
|
||||||
/// swap chain, a [`wgpu::SurfaceError`] error will be returned.
|
|
||||||
pub fn create_frame(&self) -> Frame {
|
|
||||||
let texture = self.frame_texture()
|
|
||||||
.expect("failed to create frame texture"); // TODO: should be returned to the user
|
|
||||||
let size = self.size();
|
|
||||||
|
|
||||||
Frame {
|
|
||||||
size,
|
|
||||||
texture,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum FrameTexture {
|
|
||||||
Surface(wgpu::SurfaceTexture),
|
|
||||||
Texture(Arc<wgpu::Texture>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents the current frame that is being rendered to.
|
|
||||||
//#[allow(dead_code)]
|
|
||||||
pub struct Frame {
|
|
||||||
pub(crate) size: math::UVec2,
|
|
||||||
pub(crate) texture: FrameTexture,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Frame {
|
|
||||||
pub fn texture(&self) -> &wgpu::Texture {
|
|
||||||
match &self.texture {
|
|
||||||
FrameTexture::Surface(s) => &s.texture,
|
|
||||||
FrameTexture::Texture(t) => t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Present the frame
|
|
||||||
///
|
|
||||||
/// If this frame is from a surface, it will be present, else nothing will happen.
|
|
||||||
pub fn present(self) {
|
|
||||||
match self.texture {
|
|
||||||
FrameTexture::Surface(s) => s.present(),
|
|
||||||
FrameTexture::Texture(_) => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The size of the frame
|
|
||||||
pub fn size(&self) -> math::UVec2 {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores the current frame, and the render target it came from.
|
|
||||||
pub struct FrameTarget {
|
|
||||||
pub render_target: RenderTarget,
|
|
||||||
/// None when a frame has not been created yet
|
|
||||||
pub frame: Option<Frame>,
|
|
||||||
/// The view to use to render to the frame.
|
|
||||||
pub frame_view: Option<wgpu::TextureView>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameTarget {
|
|
||||||
pub fn new(render_target: RenderTarget) -> Self {
|
|
||||||
Self {
|
|
||||||
render_target,
|
|
||||||
frame: None,
|
|
||||||
frame_view: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size of the [`RenderTarget`].
|
|
||||||
pub fn size(&self) -> math::UVec2 {
|
|
||||||
self.render_target.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the [`wgpu::TextureFormat`] of the [`RenderTarget`].
|
|
||||||
pub fn format(&self) -> wgpu::TextureFormat {
|
|
||||||
self.render_target.format()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the frame using the inner [`RenderTarget`].
|
|
||||||
pub fn create_frame(&mut self) -> &mut Frame {
|
|
||||||
self.frame = Some(self.render_target.create_frame());
|
|
||||||
self.frame.as_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the [`wgpu::TextureView`] for the [`Frame`], storing it in self and returning a reference to it.
|
|
||||||
pub fn create_frame_view(&mut self) -> &wgpu::TextureView {
|
|
||||||
let frame = self.frame.as_ref().expect("frame was not created, cannot create view");
|
|
||||||
|
|
||||||
self.frame_view = Some(frame.texture().create_view(&wgpu::TextureViewDescriptor::default()));
|
|
||||||
self.frame_view.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TargetViewChain<'a> {
|
|
||||||
pub source: &'a mut FrameTarget,
|
|
||||||
pub dest: &'a mut FrameTarget,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ViewChain {
|
|
||||||
source: FrameTarget,
|
|
||||||
dest: FrameTarget,
|
|
||||||
/// tracks the target that is currently being presented
|
|
||||||
active: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewChain {
|
|
||||||
/// Returns the currently active [`FrameTarget`].
|
|
||||||
fn active(&self) -> &FrameTarget {
|
|
||||||
if self.active == 0 {
|
|
||||||
&self.source
|
|
||||||
} else if self.active == 1 {
|
|
||||||
&self.dest
|
|
||||||
} else {
|
|
||||||
panic!("active chain index became invalid! ({})", self.active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ViewTarget {
|
|
||||||
device: Arc<wgpu::Device>,
|
|
||||||
/// The primary RenderTarget, likely a Surface
|
|
||||||
pub primary: FrameTarget,
|
|
||||||
chain: Option<ViewChain>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewTarget {
|
|
||||||
pub fn new(device: Arc<wgpu::Device>, primary: RenderTarget) -> Self {
|
|
||||||
let mut s = Self {
|
|
||||||
device,
|
|
||||||
primary: FrameTarget::new(primary),
|
|
||||||
chain: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
s.create_chain(s.primary.format(), s.primary.size());
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size of the target.
|
|
||||||
pub fn size(&self) -> math::UVec2 {
|
|
||||||
self.primary.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the [`wgpu::TextureFormat`]
|
|
||||||
pub fn format(&self) -> wgpu::TextureFormat {
|
|
||||||
self.primary.format()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resize all the targets, causes the chain to be recreated.
|
|
||||||
pub fn resize(&mut self, device: &wgpu::Device, size: math::UVec2) {
|
|
||||||
if size != self.primary.size() {
|
|
||||||
self.primary.render_target.resize(device, size);
|
|
||||||
self.create_chain(self.primary.format(), size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_chain(&mut self, format: wgpu::TextureFormat, size: math::UVec2) {
|
|
||||||
debug!("Creating chain with {:?} format and {:?} size", format, size);
|
|
||||||
|
|
||||||
let mut source = FrameTarget::new(RenderTarget::new_texture(&self.device, format, size));
|
|
||||||
source.create_frame();
|
|
||||||
source.create_frame_view();
|
|
||||||
|
|
||||||
let mut dest = FrameTarget::new(RenderTarget::new_texture(&self.device, format, size));
|
|
||||||
dest.create_frame();
|
|
||||||
dest.create_frame_view();
|
|
||||||
|
|
||||||
self.chain = Some(ViewChain {
|
|
||||||
source,
|
|
||||||
dest,
|
|
||||||
active: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cycle the target view chain, storing it in self, and returning a mutable borrow to it.
|
|
||||||
pub fn get_chain(&mut self) -> TargetViewChain {
|
|
||||||
let format = self.primary.format();
|
|
||||||
let size = self.primary.size();
|
|
||||||
|
|
||||||
if let Some(chain) = &self.chain {
|
|
||||||
// check if the chain needs to be recreated
|
|
||||||
if chain.source.format() != format || chain.source.size() != size {
|
|
||||||
self.create_chain(format, size);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.create_chain(format, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
let chain = self.chain.as_mut().unwrap();
|
|
||||||
|
|
||||||
if chain.active == 0 {
|
|
||||||
chain.active = 1;
|
|
||||||
TargetViewChain {
|
|
||||||
source: &mut chain.source,
|
|
||||||
dest: &mut chain.dest,
|
|
||||||
}
|
|
||||||
} else if chain.active == 1 {
|
|
||||||
chain.active = 0;
|
|
||||||
TargetViewChain {
|
|
||||||
source: &mut chain.dest,
|
|
||||||
dest: &mut chain.source,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("active chain index became invalid! ({})", chain.active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the [`wgpu::TextureView`] to render to.
|
|
||||||
pub fn render_view(&self) -> &wgpu::TextureView {
|
|
||||||
let chain = self.chain.as_ref().unwrap();
|
|
||||||
chain.active().frame_view.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy the chain target to the primary target
|
|
||||||
///
|
|
||||||
/// The primary target must have `wgpu::TextureUsages::COPY_DST`. This also resets the active
|
|
||||||
/// chain texture.
|
|
||||||
pub fn copy_to_primary(&mut self, encoder: &mut wgpu::CommandEncoder) {
|
|
||||||
let chain = self.chain.as_mut().unwrap();
|
|
||||||
let active_tex = chain.active().frame.as_ref().unwrap().texture();
|
|
||||||
|
|
||||||
let active_copy = wgpu::ImageCopyTexture {
|
|
||||||
texture: active_tex,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: wgpu::Origin3d::ZERO,
|
|
||||||
aspect: wgpu::TextureAspect::All,
|
|
||||||
};
|
|
||||||
|
|
||||||
let dest_tex = self.primary.frame.as_ref().unwrap().texture();
|
|
||||||
let dest_copy = wgpu::ImageCopyTexture {
|
|
||||||
texture: dest_tex,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: wgpu::Origin3d::ZERO,
|
|
||||||
aspect: wgpu::TextureAspect::All,
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = self.primary.size();
|
|
||||||
let size = wgpu::Extent3d {
|
|
||||||
width: size.x,
|
|
||||||
height: size.y,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
encoder.copy_texture_to_texture(active_copy, dest_copy, size);
|
|
||||||
|
|
||||||
// reset active texture after a render
|
|
||||||
// must get the chain again because of the borrow checker
|
|
||||||
let chain = self.chain.as_mut().unwrap();
|
|
||||||
chain.active = 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,292 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
mod shader;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub use shader::*;
|
|
||||||
|
|
||||||
mod pipeline;
|
|
||||||
pub use pipeline::*;
|
|
||||||
|
|
||||||
mod compute_pipeline;
|
|
||||||
pub use compute_pipeline::*;
|
|
||||||
|
|
||||||
mod render_pipeline;
|
|
||||||
pub use render_pipeline::*;
|
|
||||||
|
|
||||||
mod pass;
|
|
||||||
pub use pass::*;
|
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub struct PipelineCompilationOptions {
|
|
||||||
pub constants: HashMap<String, f64>,
|
|
||||||
pub zero_initialize_workgroup_memory: bool,
|
|
||||||
pub vertex_pulling_transform: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PipelineCompilationOptions {
|
|
||||||
pub fn as_wgpu(&self) -> wgpu::PipelineCompilationOptions {
|
|
||||||
wgpu::PipelineCompilationOptions {
|
|
||||||
constants: &self.constants,
|
|
||||||
zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory,
|
|
||||||
vertex_pulling_transform: self.vertex_pulling_transform,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
/// A trait that represents a [`wgpu::ComputePass`] or [`wgpu::RenderPass`].
|
|
||||||
pub trait Pass<'a> {
|
|
||||||
fn set_bind_group(&mut self, index: u32, bind_group: &'a wgpu::BindGroup, offsets: &[wgpu::DynamicOffset]);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Pass<'a> for wgpu::ComputePass<'a> {
|
|
||||||
fn set_bind_group(&mut self, index: u32, bind_group: &'a wgpu::BindGroup, offsets: &[wgpu::DynamicOffset]) {
|
|
||||||
self.set_bind_group(index, bind_group, offsets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Pass<'a> for wgpu::RenderPass<'a> {
|
|
||||||
fn set_bind_group(&mut self, index: u32, bind_group: &'a wgpu::BindGroup, offsets: &[wgpu::DynamicOffset]) {
|
|
||||||
self.set_bind_group(index, bind_group, offsets);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,263 +0,0 @@
|
||||||
// Largely based off of https://blog.simonrodriguez.fr/articles/2016/07/implementing_fxaa.html
|
|
||||||
|
|
||||||
const EDGE_THRESHOLD_MIN: f32 = 0.0312;
|
|
||||||
const EDGE_THRESHOLD_MAX: f32 = 0.125;
|
|
||||||
const ITERATIONS: i32 = 12;
|
|
||||||
const SUBPIXEL_QUALITY: f32 = 0.75;
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
|
||||||
var t_screen: texture_2d<f32>;
|
|
||||||
@group(0) @binding(1)
|
|
||||||
var s_screen: sampler;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position)
|
|
||||||
clip_position: vec4<f32>,
|
|
||||||
@location(0)
|
|
||||||
tex_coords: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn QUALITY(q: i32) -> f32 {
|
|
||||||
switch (q) {
|
|
||||||
default: { return 1.0; }
|
|
||||||
case 5: { return 1.5; }
|
|
||||||
case 6, 7, 8, 9: { return 2.0; }
|
|
||||||
case 10: { return 4.0; }
|
|
||||||
case 11: { return 8.0; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rgb2luma(rgb: vec3<f32>) -> f32 {
|
|
||||||
return sqrt(dot(rgb, vec3<f32>(0.299, 0.587, 0.114)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(
|
|
||||||
@builtin(vertex_index) vertex_index: u32,
|
|
||||||
) -> VertexOutput {
|
|
||||||
let tex_coords = vec2<f32>(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0;
|
|
||||||
let clip_position = vec4<f32>(tex_coords * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
|
|
||||||
return VertexOutput(clip_position, tex_coords);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn texture_offset(tex: texture_2d<f32>, samp: sampler, point: vec2<f32>, offset: vec2<i32>) -> vec3<f32> {
|
|
||||||
var tex_coords = point + vec2<f32>(offset);
|
|
||||||
return textureSample(tex, samp, tex_coords).xyz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
let resolution = vec2<f32>(textureDimensions(t_screen));
|
|
||||||
let inverse_screen_size = 1.0 / resolution.xy;
|
|
||||||
let tex_coords = in.clip_position.xy * inverse_screen_size;
|
|
||||||
|
|
||||||
var color_center: vec3<f32> = textureSampleLevel(t_screen, s_screen, tex_coords, 0.0).xyz;
|
|
||||||
|
|
||||||
// Luma at the current fragment
|
|
||||||
let luma_center = rgb2luma(color_center);
|
|
||||||
|
|
||||||
// Luma at the four direct neighbours of the current fragment.
|
|
||||||
let luma_down = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(0, -1)).xyz);
|
|
||||||
let luma_up = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(0, 1)).xyz);
|
|
||||||
let luma_left = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(-1, 0)).xyz);
|
|
||||||
let luma_right = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(1, 0)).xyz);
|
|
||||||
|
|
||||||
// Find the maximum and minimum luma around the current fragment.
|
|
||||||
let luma_min = min(luma_center, min(min(luma_down, luma_up), min(luma_left, luma_right)));
|
|
||||||
let luma_max = max(luma_center, max(max(luma_down, luma_up), max(luma_left, luma_right)));
|
|
||||||
|
|
||||||
// Compute the delta
|
|
||||||
let luma_range = luma_max - luma_min;
|
|
||||||
|
|
||||||
// If the luma variation is lower that a threshold (or if we are in a really dark area),
|
|
||||||
// we are not on an edge, don't perform any AA.
|
|
||||||
if (luma_range < max(EDGE_THRESHOLD_MIN, luma_max * EDGE_THRESHOLD_MAX)) {
|
|
||||||
return vec4<f32>(color_center, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the 4 remaining corners lumas
|
|
||||||
let luma_down_left = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(-1, -1)).xyz);
|
|
||||||
let luma_up_right = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(1, 1)).xyz);
|
|
||||||
let luma_up_left = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(-1, 1)).xyz);
|
|
||||||
let luma_down_right = rgb2luma(textureSampleLevel(t_screen, s_screen, tex_coords, 0.0, vec2<i32>(1, -1)).xyz);
|
|
||||||
|
|
||||||
// Combine the four edges lumas (using intermediary variables for future computations with the same values).
|
|
||||||
let luma_down_up = luma_down + luma_up;
|
|
||||||
let luma_left_right = luma_left + luma_right;
|
|
||||||
|
|
||||||
// Same for corners
|
|
||||||
let luma_left_corners = luma_down_left + luma_up_left;
|
|
||||||
let luma_down_corners = luma_down_left + luma_down_right;
|
|
||||||
let luma_right_corners = luma_down_right + luma_up_right;
|
|
||||||
let luma_up_corners = luma_up_right + luma_up_left;
|
|
||||||
|
|
||||||
// Compute an estimation of the gradient along the horizontal and verical axis.
|
|
||||||
let edge_horizontal = abs(-2.0 * luma_left + luma_left_corners)
|
|
||||||
+ abs(-2.0 * luma_center + luma_down_up) * 2.0
|
|
||||||
+ abs(-2.0 * luma_right + luma_right_corners);
|
|
||||||
let edge_vertical = abs(-2.0 * luma_up + luma_up_corners)
|
|
||||||
+ abs(-2.0 * luma_center + luma_left_right) * 2.0
|
|
||||||
+ abs(-2.0 * luma_down + luma_down_corners);
|
|
||||||
|
|
||||||
// Is the local edge horizontal or vertical?
|
|
||||||
let is_horizontal = edge_horizontal >= edge_vertical;
|
|
||||||
|
|
||||||
// Select the two neighboring texels lumas in the opposite direction to the local edge.
|
|
||||||
let luma1 = select(luma_left, luma_down, is_horizontal);
|
|
||||||
let luma2 = select(luma_right, luma_up, is_horizontal);
|
|
||||||
|
|
||||||
// Compute gradients in this direction
|
|
||||||
let gradient1 = luma1 - luma_center;
|
|
||||||
let gradient2 = luma2 - luma_center;
|
|
||||||
|
|
||||||
// Which direction is the steepest?
|
|
||||||
let is_1_steepest = abs(gradient1) >= abs(gradient2);
|
|
||||||
|
|
||||||
// Gradient in the corresponding direction, normalized
|
|
||||||
let gradient_scaled = 0.25 * max(abs(gradient1), abs(gradient2));
|
|
||||||
|
|
||||||
// Choose the step size (one pixel) according to the edge direction.
|
|
||||||
var step_length: f32;
|
|
||||||
if (is_horizontal) {
|
|
||||||
step_length = inverse_screen_size.y;
|
|
||||||
} else {
|
|
||||||
step_length = inverse_screen_size.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Average luma in the correct direction.
|
|
||||||
var luma_local_average = 0.0;
|
|
||||||
if (is_1_steepest) {
|
|
||||||
// Switch the direction
|
|
||||||
step_length = -step_length;
|
|
||||||
luma_local_average = 0.5 * (luma1 + luma_center);
|
|
||||||
} else {
|
|
||||||
luma_local_average = 0.5 * (luma2 + luma_center);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shift UV in the correct direction by half a pixel.
|
|
||||||
var current_uv = tex_coords;
|
|
||||||
if (is_horizontal) {
|
|
||||||
current_uv.y += step_length * 0.5;
|
|
||||||
} else {
|
|
||||||
current_uv.x += step_length * 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute offset (for each iteration step) in the right direction.
|
|
||||||
var offset: vec2<f32>;
|
|
||||||
if (is_horizontal) {
|
|
||||||
offset = vec2<f32>(inverse_screen_size.x, 0.0);
|
|
||||||
} else {
|
|
||||||
offset = vec2<f32>(0.0, inverse_screen_size.y);
|
|
||||||
}
|
|
||||||
// Compute UVs to explore on each side of the edge, orthogonally. The QUALITY allows us to
|
|
||||||
// step faster.
|
|
||||||
var uv1 = current_uv - offset;
|
|
||||||
var uv2 = current_uv + offset;
|
|
||||||
|
|
||||||
// Read the lumas at both current extremities of the exploration segment, and compute the
|
|
||||||
// delta wrt to the local average luma.
|
|
||||||
var luma_end1 = rgb2luma(textureSampleLevel(t_screen, s_screen, uv1, 0.0).xyz);
|
|
||||||
var luma_end2 = rgb2luma(textureSampleLevel(t_screen, s_screen, uv2, 0.0).xyz);
|
|
||||||
luma_end1 -= luma_local_average;
|
|
||||||
luma_end2 -= luma_local_average;
|
|
||||||
|
|
||||||
// If the luma deltas at the current extremities are larger than the local gradient, we have
|
|
||||||
// reached the side of the edge.
|
|
||||||
var reached1 = abs(luma_end1) >= gradient_scaled;
|
|
||||||
var reached2 = abs(luma_end2) >= gradient_scaled;
|
|
||||||
var reached_both = reached1 && reached2;
|
|
||||||
|
|
||||||
// If the side is not reached, we continue to explore in this direction.
|
|
||||||
if (!reached1) {
|
|
||||||
uv1 -= offset;
|
|
||||||
}
|
|
||||||
if (!reached2) {
|
|
||||||
uv2 += offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reached_both) {
|
|
||||||
for (var i = 2; i < ITERATIONS; i++) {
|
|
||||||
// If needed, read luma in 1st direction, compute delta.
|
|
||||||
if (!reached1) {
|
|
||||||
luma_end1 = rgb2luma(textureSampleLevel(t_screen, s_screen, uv1, 0.0).xyz);
|
|
||||||
luma_end1 = luma_end1 - luma_local_average;
|
|
||||||
}
|
|
||||||
// If needed, read luma in opposite direction, compute delta.
|
|
||||||
if (!reached2) {
|
|
||||||
luma_end2 = rgb2luma(textureSampleLevel(t_screen, s_screen, uv2, 0.0).xyz);
|
|
||||||
luma_end2 = luma_end2 - luma_local_average;
|
|
||||||
}
|
|
||||||
// If the luma deltas at the current extremities is larger than the local gradient, we have reached the side of the edge.
|
|
||||||
reached1 = abs(luma_end1) >= gradient_scaled;
|
|
||||||
reached2 = abs(luma_end2) >= gradient_scaled;
|
|
||||||
reached_both = reached1 && reached2;
|
|
||||||
|
|
||||||
// If the side is not reached, we continue to explore in this direction, with a variable quality.
|
|
||||||
if (!reached1) {
|
|
||||||
uv1 -= offset * QUALITY(i);
|
|
||||||
}
|
|
||||||
if (!reached2) {
|
|
||||||
uv2 += offset * QUALITY(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both sides have been reached, stop the exploration
|
|
||||||
if (reached_both) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the distances to each extremity of the edge.
|
|
||||||
var distance1 = select(tex_coords.y - uv1.y, tex_coords.x - uv1.x, is_horizontal);
|
|
||||||
var distance2 = select(uv2.y - tex_coords.y, uv2.x - tex_coords.x, is_horizontal);
|
|
||||||
|
|
||||||
// In which direction is the extremity of the edge closer?
|
|
||||||
let is_direction1 = distance1 < distance2;
|
|
||||||
let distance_final = min(distance1, distance2);
|
|
||||||
|
|
||||||
// Length of the edge.
|
|
||||||
let edge_thickness = (distance1 + distance2);
|
|
||||||
|
|
||||||
// UV offset: read in the direction of the closest side of the edge.
|
|
||||||
let pixel_offset = -distance_final / edge_thickness + 0.5;
|
|
||||||
|
|
||||||
// Is the luma at center smaller than the local average?
|
|
||||||
let is_luma_center_smaller = luma_center < luma_local_average;
|
|
||||||
|
|
||||||
// If the luma at center is smaller than at its neighbour, the delta luma at each end should
|
|
||||||
// be positive (same variation). (in the direction of the closer side of the edge.)
|
|
||||||
var direction_luma_end: f32;
|
|
||||||
if (is_direction1) {
|
|
||||||
direction_luma_end = luma_end1;
|
|
||||||
} else {
|
|
||||||
direction_luma_end = luma_end2;
|
|
||||||
}
|
|
||||||
let correct_variation = (direction_luma_end < 0.0) != is_luma_center_smaller;
|
|
||||||
|
|
||||||
// If the luma variation is incorrect, do not offset.
|
|
||||||
var final_offset = select(0.0, pixel_offset, correct_variation);
|
|
||||||
|
|
||||||
// Sub-pixel shifting
|
|
||||||
// Full weighted average of the luma over the 3x3 neighborhood.
|
|
||||||
let luma_average = (1.0 / 12.0) * (2.0 * (luma_down_up + luma_left_right) + luma_left_corners + luma_right_corners);
|
|
||||||
// Ratio of the delta between the global average and the center luma, over the luma range
|
|
||||||
// in the 3x3 neighborhood.
|
|
||||||
let sub_pixel_offset1 = clamp(abs(luma_average - luma_center) / luma_range, 0.0, 1.0);
|
|
||||||
let sub_pixel_offset2 = (-2.0 * sub_pixel_offset1 + 3.0) * sub_pixel_offset1 * sub_pixel_offset1;
|
|
||||||
// Compute a sub-pixel offset based on this delta.
|
|
||||||
let sub_pixel_offset_final = sub_pixel_offset2 * sub_pixel_offset2 * SUBPIXEL_QUALITY;
|
|
||||||
|
|
||||||
// Pick the biggest of the two offsets.
|
|
||||||
final_offset = max(final_offset, sub_pixel_offset_final);
|
|
||||||
|
|
||||||
var final_uv = tex_coords;
|
|
||||||
if (is_horizontal) {
|
|
||||||
final_uv.y += final_offset * step_length;
|
|
||||||
} else {
|
|
||||||
final_uv.x += final_offset * step_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
let color = textureSampleLevel(t_screen, s_screen, final_uv, 0.0).xyz;
|
|
||||||
return vec4<f32>(color, 1.0);
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
#define_module lyra::shadows::bindings
|
|
||||||
#import lyra::shadows::structs::{ShadowSettingsUniform, LightShadowMapUniform}
|
|
||||||
|
|
||||||
@group(5) @binding(0)
|
|
||||||
var t_shadow_maps_atlas: texture_depth_2d;
|
|
||||||
@group(5) @binding(1)
|
|
||||||
var s_shadow_maps_atlas: sampler;
|
|
||||||
@group(5) @binding(2)
|
|
||||||
var s_shadow_maps_atlas_compare: sampler_comparison;
|
|
||||||
@group(5) @binding(3)
|
|
||||||
var<uniform> u_shadow_settings: ShadowSettingsUniform;
|
|
||||||
@group(5) @binding(4)
|
|
||||||
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
|
|
||||||
@group(5) @binding(5)
|
|
||||||
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
|
|
||||||
@group(5) @binding(6)
|
|
||||||
var<storage, read> u_pcf_poisson_disc_3d: array<vec3<f32>>;
|
|
||||||
@group(5) @binding(7)
|
|
||||||
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;
|
|
|
@ -1,352 +0,0 @@
|
||||||
#define_module lyra::shadows::calc
|
|
||||||
#import lyra::shadows::structs::{ShadowSettingsUniform, LightShadowMapUniform}
|
|
||||||
#import lyra::shadows::bindings::{t_shadow_maps_atlas, s_shadow_maps_atlas, s_shadow_maps_atlas_compare, u_shadow_settings, u_light_shadow, u_pcf_poisson_disc, u_pcss_poisson_disc}
|
|
||||||
|
|
||||||
/// Convert 3d coords for an unwrapped cubemap to 2d coords and a side index of the cube map.
|
|
||||||
///
|
|
||||||
/// The `xy` components are the 2d coordinates in the side of the cube, and `z` is the cube
|
|
||||||
/// map side index.
|
|
||||||
///
|
|
||||||
/// Cube map index results:
|
|
||||||
/// 0 -> UNKNOWN
|
|
||||||
/// 1 -> right
|
|
||||||
/// 2 -> left
|
|
||||||
/// 3 -> top
|
|
||||||
/// 4 -> bottom
|
|
||||||
/// 5 -> near
|
|
||||||
/// 6 -> far
|
|
||||||
fn coords_to_cube_atlas(tex_coord: vec3<f32>) -> vec3<f32> {
|
|
||||||
let abs_x = abs(tex_coord.x);
|
|
||||||
let abs_y = abs(tex_coord.y);
|
|
||||||
let abs_z = abs(tex_coord.z);
|
|
||||||
|
|
||||||
var major_axis: f32 = 0.0;
|
|
||||||
var cube_idx: i32 = 0;
|
|
||||||
var res = vec2<f32>(0.0);
|
|
||||||
|
|
||||||
// Determine the dominant axis
|
|
||||||
if (abs_x >= abs_y && abs_x >= abs_z) {
|
|
||||||
major_axis = tex_coord.x;
|
|
||||||
if (tex_coord.x > 0.0) {
|
|
||||||
cube_idx = 1;
|
|
||||||
res = vec2<f32>(-tex_coord.z, -tex_coord.y);
|
|
||||||
} else {
|
|
||||||
cube_idx = 2;
|
|
||||||
res = vec2<f32>(tex_coord.z, -tex_coord.y);
|
|
||||||
}
|
|
||||||
} else if (abs_y >= abs_x && abs_y >= abs_z) {
|
|
||||||
major_axis = tex_coord.y;
|
|
||||||
if (tex_coord.y > 0.0) {
|
|
||||||
cube_idx = 3;
|
|
||||||
res = vec2<f32>(tex_coord.x, tex_coord.z);
|
|
||||||
} else {
|
|
||||||
cube_idx = 4;
|
|
||||||
res = vec2<f32>(tex_coord.x, -tex_coord.z);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
major_axis = tex_coord.z;
|
|
||||||
if (tex_coord.z > 0.0) {
|
|
||||||
cube_idx = 5;
|
|
||||||
res = vec2<f32>(tex_coord.x, -tex_coord.y);
|
|
||||||
} else {
|
|
||||||
cube_idx = 6;
|
|
||||||
res = vec2<f32>(-tex_coord.x, -tex_coord.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res = (res / abs(major_axis) + 1.0) * 0.5;
|
|
||||||
res.y = 1.0 - res.y;
|
|
||||||
|
|
||||||
return vec3<f32>(res, f32(cube_idx));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get shadow settings for a light.
|
|
||||||
/// Returns x as `pcf_samples_num` and y as `pcss_blocker_search_samples`.
|
|
||||||
fn get_shadow_settings(shadow_u: LightShadowMapUniform) -> vec2<u32> {
|
|
||||||
if shadow_u.has_shadow_settings == 1u {
|
|
||||||
return vec2<u32>(shadow_u.pcf_samples_num, shadow_u.pcss_blocker_search_samples);
|
|
||||||
} else {
|
|
||||||
return vec2<u32>(u_shadow_settings.pcf_samples_num, u_shadow_settings.pcss_blocker_search_samples);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_shadow_dir_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
|
|
||||||
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
|
|
||||||
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
|
|
||||||
|
|
||||||
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
|
|
||||||
// for some reason the y component is flipped after transforming
|
|
||||||
proj_coords.y = -proj_coords.y;
|
|
||||||
|
|
||||||
// Remap xy to [0.0, 1.0]
|
|
||||||
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
|
|
||||||
|
|
||||||
// use a bias to avoid shadow acne
|
|
||||||
let current_depth = proj_coords.z - map_data.constant_depth_bias;
|
|
||||||
|
|
||||||
// get settings
|
|
||||||
let settings = get_shadow_settings(map_data);
|
|
||||||
let pcf_samples_num = settings.x;
|
|
||||||
let pcss_blocker_search_samples = settings.y;
|
|
||||||
|
|
||||||
var shadow = 0.0;
|
|
||||||
// hardware 2x2 PCF via camparison sampler
|
|
||||||
if pcf_samples_num == 2u {
|
|
||||||
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
|
|
||||||
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
|
|
||||||
}
|
|
||||||
// PCSS
|
|
||||||
else if pcf_samples_num > 0u && pcss_blocker_search_samples > 0u {
|
|
||||||
shadow = pcss_dir_light(xy_remapped, current_depth, map_data);
|
|
||||||
}
|
|
||||||
// only PCF
|
|
||||||
else if pcf_samples_num > 0u {
|
|
||||||
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
|
|
||||||
shadow = pcf_dir_light(xy_remapped, current_depth, map_data, texel_size);
|
|
||||||
}
|
|
||||||
// no filtering
|
|
||||||
else {
|
|
||||||
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
|
|
||||||
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
|
|
||||||
shadow = select(1.0, 0.0, current_depth > closest_depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dont cast shadows outside the light's far plane
|
|
||||||
if (proj_coords.z > 1.0) {
|
|
||||||
shadow = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dont cast shadows if the texture coords would go past the shadow maps
|
|
||||||
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
|
|
||||||
shadow = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shadow;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comes from https://developer.download.nvidia.com/whitepapers/2008/PCSS_Integration.pdf
|
|
||||||
fn search_width(light_near: f32, uv_light_size: f32, receiver_depth: f32) -> f32 {
|
|
||||||
return uv_light_size * (receiver_depth - light_near) / receiver_depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert texture coords to be texture coords of an atlas frame.
|
|
||||||
///
|
|
||||||
/// If `safety_offset` is true, the frame will be shrank by a tiny amount to avoid bleeding
|
|
||||||
/// into adjacent frames from fiiltering.
|
|
||||||
fn to_atlas_frame_coords(shadow_u: LightShadowMapUniform, coords: vec2<f32>, safety_offset: bool) -> vec2<f32> {
|
|
||||||
let atlas_dimensions = textureDimensions(t_shadow_maps_atlas);
|
|
||||||
|
|
||||||
// get the rect of the frame as a vec4
|
|
||||||
var region_rect = vec4<f32>(f32(shadow_u.atlas_frame.x), f32(shadow_u.atlas_frame.y),
|
|
||||||
f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height));
|
|
||||||
// put the frame rect in atlas UV space
|
|
||||||
region_rect /= f32(atlas_dimensions.x);
|
|
||||||
|
|
||||||
// if safety_offset is true, calculate a relatively tiny offset to avoid getting the end of
|
|
||||||
// the frame and causing linear or nearest filtering to bleed to the adjacent frame.
|
|
||||||
let texel_size = select(0.0, (1.0 / f32(shadow_u.atlas_frame.x)) * 4.0, safety_offset);
|
|
||||||
|
|
||||||
// lerp input coords
|
|
||||||
let region_coords = vec2<f32>(
|
|
||||||
mix(region_rect.x + texel_size, region_rect.x + region_rect.z - texel_size, coords.x),
|
|
||||||
mix(region_rect.y + texel_size, region_rect.y + region_rect.w - texel_size, coords.y)
|
|
||||||
);
|
|
||||||
|
|
||||||
return region_coords;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the average blocker distance for a directiona llight
|
|
||||||
fn find_blocker_distance_dir_light(tex_coords: vec2<f32>, receiver_depth: f32, bias: f32, shadow_u: LightShadowMapUniform) -> vec2<f32> {
|
|
||||||
let search_width = search_width(shadow_u.near_plane, shadow_u.light_size_uv, receiver_depth);
|
|
||||||
|
|
||||||
var blockers = 0;
|
|
||||||
var avg_dist = 0.0;
|
|
||||||
let samples = i32(u_shadow_settings.pcss_blocker_search_samples);
|
|
||||||
for (var i = 0; i < samples; i++) {
|
|
||||||
let offset_coords = tex_coords + u_pcss_poisson_disc[i] * search_width;
|
|
||||||
let new_coords = to_atlas_frame_coords(shadow_u, offset_coords, false);
|
|
||||||
let z = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, new_coords, 0.0);
|
|
||||||
|
|
||||||
if z < (receiver_depth - bias) {
|
|
||||||
blockers += 1;
|
|
||||||
avg_dist += z;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let b = f32(blockers);
|
|
||||||
return vec2<f32>(avg_dist / b, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pcss_dir_light(tex_coords: vec2<f32>, receiver_depth: f32, shadow_u: LightShadowMapUniform) -> f32 {
|
|
||||||
let blocker_search = find_blocker_distance_dir_light(tex_coords, receiver_depth, 0.0, shadow_u);
|
|
||||||
|
|
||||||
// If no blockers were found, exit now to save in filtering
|
|
||||||
if blocker_search.y == 0.0 {
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
let blocker_depth = blocker_search.x;
|
|
||||||
|
|
||||||
// penumbra estimation
|
|
||||||
let penumbra_width = (receiver_depth - blocker_depth) / blocker_depth;
|
|
||||||
|
|
||||||
// PCF
|
|
||||||
let uv_radius = penumbra_width * shadow_u.light_size_uv * shadow_u.near_plane / receiver_depth;
|
|
||||||
return pcf_dir_light(tex_coords, receiver_depth, shadow_u, uv_radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the shadow coefficient using PCF of a directional light
|
|
||||||
fn pcf_dir_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, uv_radius: f32) -> f32 {
|
|
||||||
var shadow = 0.0;
|
|
||||||
let samples_num = i32(u_shadow_settings.pcf_samples_num);
|
|
||||||
for (var i = 0; i < samples_num; i++) {
|
|
||||||
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
|
|
||||||
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
|
|
||||||
|
|
||||||
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
|
|
||||||
}
|
|
||||||
shadow /= f32(samples_num);
|
|
||||||
|
|
||||||
// clamp shadow to [0; 1]
|
|
||||||
return saturate(shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_shadow_point_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
|
|
||||||
var frag_to_light = world_pos - light.position;
|
|
||||||
let temp = coords_to_cube_atlas(normalize(frag_to_light));
|
|
||||||
var coords_2d = temp.xy;
|
|
||||||
let cube_idx = i32(temp.z);
|
|
||||||
|
|
||||||
var indices = light.light_shadow_uniform_index;
|
|
||||||
let i = indices[cube_idx - 1];
|
|
||||||
let u: LightShadowMapUniform = u_light_shadow[i];
|
|
||||||
|
|
||||||
let uniforms = array<LightShadowMapUniform, 6>(
|
|
||||||
u_light_shadow[indices[0]],
|
|
||||||
u_light_shadow[indices[1]],
|
|
||||||
u_light_shadow[indices[2]],
|
|
||||||
u_light_shadow[indices[3]],
|
|
||||||
u_light_shadow[indices[4]],
|
|
||||||
u_light_shadow[indices[5]]
|
|
||||||
);
|
|
||||||
|
|
||||||
var current_depth = length(frag_to_light);
|
|
||||||
current_depth /= u.far_plane;
|
|
||||||
current_depth -= u.constant_depth_bias;
|
|
||||||
|
|
||||||
// get settings
|
|
||||||
let settings = get_shadow_settings(u);
|
|
||||||
let pcf_samples_num = settings.x;
|
|
||||||
let pcss_blocker_search_samples = settings.y;
|
|
||||||
|
|
||||||
var shadow = 0.0;
|
|
||||||
// hardware 2x2 PCF via camparison sampler
|
|
||||||
if pcf_samples_num == 2u {
|
|
||||||
let region_coords = to_atlas_frame_coords(u, coords_2d, true);
|
|
||||||
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
|
|
||||||
}
|
|
||||||
// PCSS
|
|
||||||
else if pcf_samples_num > 0u && pcss_blocker_search_samples > 0u {
|
|
||||||
shadow = pcss_dir_light(coords_2d, current_depth, u);
|
|
||||||
}
|
|
||||||
// only PCF
|
|
||||||
else if pcf_samples_num > 0u {
|
|
||||||
let texel_size = 1.0 / f32(u.atlas_frame.width);
|
|
||||||
shadow = pcf_point_light(frag_to_light, current_depth, uniforms, pcf_samples_num, 0.007);
|
|
||||||
//shadow = pcf_point_light(coords_2d, current_depth, u, pcf_samples_num, texel_size);
|
|
||||||
}
|
|
||||||
// no filtering
|
|
||||||
else {
|
|
||||||
let region_coords = to_atlas_frame_coords(u, coords_2d, true);
|
|
||||||
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
|
|
||||||
shadow = select(1.0, 0.0, current_depth > closest_depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return shadow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the shadow coefficient using PCF of a directional light
|
|
||||||
fn pcf_point_light(tex_coords: vec3<f32>, test_depth: f32, shadow_us: array<LightShadowMapUniform, 6>, samples_num: u32, uv_radius: f32) -> f32 {
|
|
||||||
var shadow_unis = shadow_us;
|
|
||||||
|
|
||||||
var shadow = 0.0;
|
|
||||||
for (var i = 0; i < i32(samples_num); i++) {
|
|
||||||
var temp = coords_to_cube_atlas(tex_coords);
|
|
||||||
var coords_2d = temp.xy;
|
|
||||||
var cube_idx = i32(temp.z);
|
|
||||||
var shadow_u = shadow_unis[cube_idx - 1];
|
|
||||||
|
|
||||||
coords_2d += u_pcf_poisson_disc[i] * uv_radius;
|
|
||||||
|
|
||||||
let new_coords = to_atlas_frame_coords(shadow_u, coords_2d, true);
|
|
||||||
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
|
|
||||||
}
|
|
||||||
shadow /= f32(samples_num);
|
|
||||||
|
|
||||||
// clamp shadow to [0; 1]
|
|
||||||
return saturate(shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_shadow_spot_light(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: vec3<f32>, light: Light) -> f32 {
|
|
||||||
let map_data: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
|
|
||||||
let frag_pos_light_space = map_data.light_space_matrix * vec4<f32>(world_pos, 1.0);
|
|
||||||
|
|
||||||
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
|
|
||||||
// for some reason the y component is flipped after transforming
|
|
||||||
proj_coords.y = -proj_coords.y;
|
|
||||||
|
|
||||||
// Remap xy to [0.0, 1.0]
|
|
||||||
let xy_remapped = proj_coords.xy * 0.5 + 0.5;
|
|
||||||
|
|
||||||
// use a bias to avoid shadow acne
|
|
||||||
let current_depth = proj_coords.z - map_data.constant_depth_bias;
|
|
||||||
|
|
||||||
// get settings
|
|
||||||
let settings = get_shadow_settings(map_data);
|
|
||||||
let pcf_samples_num = settings.x;
|
|
||||||
let pcss_blocker_search_samples = settings.y;
|
|
||||||
|
|
||||||
var shadow = 0.0;
|
|
||||||
// hardware 2x2 PCF via camparison sampler
|
|
||||||
if pcf_samples_num == 2u {
|
|
||||||
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
|
|
||||||
shadow = textureSampleCompareLevel(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth);
|
|
||||||
}
|
|
||||||
// only PCF is supported for spot lights
|
|
||||||
else if pcf_samples_num > 0u {
|
|
||||||
let texel_size = 1.0 / f32(map_data.atlas_frame.width);
|
|
||||||
shadow = pcf_spot_light(xy_remapped, current_depth, map_data, i32(pcf_samples_num), texel_size);
|
|
||||||
}
|
|
||||||
// no filtering
|
|
||||||
else {
|
|
||||||
let region_coords = to_atlas_frame_coords(map_data, xy_remapped, false);
|
|
||||||
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0);
|
|
||||||
shadow = select(1.0, 0.0, current_depth > closest_depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dont cast shadows outside the light's far plane
|
|
||||||
if (proj_coords.z > 1.0) {
|
|
||||||
shadow = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dont cast shadows if the texture coords would go past the shadow maps
|
|
||||||
if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) {
|
|
||||||
shadow = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shadow;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the shadow coefficient using PCF of a directional light
|
|
||||||
fn pcf_spot_light(tex_coords: vec2<f32>, test_depth: f32, shadow_u: LightShadowMapUniform, samples_num: i32, uv_radius: f32) -> f32 {
|
|
||||||
var shadow = 0.0;
|
|
||||||
for (var i = 0; i < samples_num; i++) {
|
|
||||||
let offset = tex_coords + u_pcf_poisson_disc[i] * uv_radius;
|
|
||||||
let new_coords = to_atlas_frame_coords(shadow_u, offset, false);
|
|
||||||
|
|
||||||
shadow += textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, new_coords, test_depth);
|
|
||||||
}
|
|
||||||
shadow /= f32(samples_num);
|
|
||||||
|
|
||||||
// clamp shadow to [0; 1]
|
|
||||||
return saturate(shadow);
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
#define_module lyra::shadows::depth_pass
|
|
||||||
#import lyra::shadows::structs::{LightShadowMapUniform}
|
|
||||||
|
|
||||||
struct TransformData {
|
|
||||||
transform: mat4x4<f32>,
|
|
||||||
normal_matrix: mat4x4<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
|
||||||
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<uniform> u_model_transform_data: TransformData;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position)
|
|
||||||
clip_position: vec4<f32>,
|
|
||||||
@location(0) world_pos: vec3<f32>,
|
|
||||||
@location(1) instance_index: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(
|
|
||||||
@location(0) position: vec3<f32>,
|
|
||||||
@builtin(instance_index) instance_index: u32,
|
|
||||||
) -> VertexOutput {
|
|
||||||
let world_pos = u_model_transform_data.transform * vec4<f32>(position, 1.0);
|
|
||||||
let pos = u_light_shadow[instance_index].light_space_matrix * world_pos;
|
|
||||||
return VertexOutput(pos, world_pos.xyz, instance_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FragmentOutput {
|
|
||||||
@builtin(frag_depth) depth: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fragment shader used for point lights (or other perspective lights) to create linear depth
|
|
||||||
@fragment
|
|
||||||
fn fs_point_light_main(
|
|
||||||
in: VertexOutput
|
|
||||||
) -> FragmentOutput {
|
|
||||||
let u = u_light_shadow[in.instance_index];
|
|
||||||
|
|
||||||
var light_dis = length(in.world_pos - u.light_pos);
|
|
||||||
|
|
||||||
// map to [0; 1] range by dividing by far plane
|
|
||||||
light_dis = light_dis / u.far_plane;
|
|
||||||
|
|
||||||
return FragmentOutput(light_dis);
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
#define_module lyra::shadows::structs
|
|
||||||
|
|
||||||
struct TextureAtlasFrame {
|
|
||||||
/*offset: vec2<u32>,
|
|
||||||
size: vec2<u32>,*/
|
|
||||||
x: u32,
|
|
||||||
y: u32,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LightShadowMapUniform {
|
|
||||||
light_space_matrix: mat4x4<f32>,
|
|
||||||
atlas_frame: TextureAtlasFrame,
|
|
||||||
near_plane: f32,
|
|
||||||
far_plane: f32,
|
|
||||||
light_size_uv: f32,
|
|
||||||
light_pos: vec3<f32>,
|
|
||||||
/// boolean casted as u32
|
|
||||||
has_shadow_settings: u32,
|
|
||||||
pcf_samples_num: u32,
|
|
||||||
pcss_blocker_search_samples: u32,
|
|
||||||
constant_depth_bias: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ShadowSettingsUniform {
|
|
||||||
pcf_samples_num: u32,
|
|
||||||
pcss_blocker_search_samples: u32,
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
@group(0) @binding(0)
|
|
||||||
var t_screen: texture_2d<f32>;
|
|
||||||
@group(0) @binding(1)
|
|
||||||
var s_screen: sampler;
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@builtin(position)
|
|
||||||
clip_position: vec4<f32>,
|
|
||||||
@location(0)
|
|
||||||
tex_coords: vec2<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(
|
|
||||||
@builtin(vertex_index) vertex_index: u32,
|
|
||||||
) -> VertexOutput {
|
|
||||||
let tex_coords = vec2<f32>(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0;
|
|
||||||
let clip_position = vec4<f32>(tex_coords * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), 0.0, 1.0);
|
|
||||||
|
|
||||||
return VertexOutput(clip_position, tex_coords);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
let resolution = vec2<f32>(textureDimensions(t_screen));
|
|
||||||
let inverse_screen_size = 1.0 / resolution.xy;
|
|
||||||
let tex_coords = in.clip_position.xy * inverse_screen_size;
|
|
||||||
|
|
||||||
var rgb: vec3<f32> = textureSample(t_screen, s_screen, tex_coords).xyz;
|
|
||||||
rgb *= vec3<f32>(1.0, 0.2, 0.2);
|
|
||||||
return vec4<f32>(rgb, 1.0);
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
use std::{collections::VecDeque, marker::PhantomData, mem, sync::Arc};
|
|
||||||
|
|
||||||
/// A buffer on the GPU that has persistent indices.
|
|
||||||
///
|
|
||||||
/// `GpuSlotBuffer` allocates a buffer on the GPU and keeps stable indices of elements and
|
|
||||||
/// reuses ones that were removed. It supports aligned buffers with [`GpuSlotBuffer::new_aligned`],
|
|
||||||
/// as well as unaligned buffers with [`GpuSlotBuffer::new`].
|
|
||||||
pub struct GpuSlotBuffer<T: bytemuck::Pod + bytemuck::Zeroable> {
|
|
||||||
/// The amount of elements that can fit in the buffer.
|
|
||||||
capacity: u64,
|
|
||||||
/// The ending point of the buffer elements.
|
|
||||||
len: u64,
|
|
||||||
/// The list of dead and reusable indices in the buffer.
|
|
||||||
dead_indices: VecDeque<u64>,
|
|
||||||
/// The optional alignment of elements in the buffer.
|
|
||||||
alignment: Option<u64>,
|
|
||||||
/// The actual gpu buffer
|
|
||||||
buffer: Arc<wgpu::Buffer>,
|
|
||||||
_marker: PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: bytemuck::Pod + bytemuck::Zeroable> GpuSlotBuffer<T> {
|
|
||||||
/// Create a new GpuSlotBuffer with unaligned elements.
|
|
||||||
///
|
|
||||||
/// See [`GpuSlotBuffer::new_aligned`].
|
|
||||||
pub fn new(device: &wgpu::Device, label: Option<&str>, usage: wgpu::BufferUsages, capacity: u64) -> Self {
|
|
||||||
Self::new_impl(device, label, usage, capacity, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new buffer with **aligned** elements.
|
|
||||||
///
|
|
||||||
/// See [`GpuSlotBuffer::new`].
|
|
||||||
pub fn new_aligned(device: &wgpu::Device, label: Option<&str>, usage: wgpu::BufferUsages, capacity: u64, alignment: u64) -> Self {
|
|
||||||
Self::new_impl(device, label, usage, capacity, Some(alignment))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_impl(device: &wgpu::Device, label: Option<&str>, usage: wgpu::BufferUsages, capacity: u64, alignment: Option<u64>) -> Self {
|
|
||||||
let buffer = Arc::new(device.create_buffer(&wgpu::BufferDescriptor {
|
|
||||||
label,
|
|
||||||
size: capacity * mem::size_of::<T>() as u64,
|
|
||||||
usage,
|
|
||||||
mapped_at_creation: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
Self {
|
|
||||||
capacity,
|
|
||||||
len: 0,
|
|
||||||
dead_indices: VecDeque::default(),
|
|
||||||
buffer,
|
|
||||||
alignment,
|
|
||||||
_marker: PhantomData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the byte offset in the buffer of the element at `i`.
|
|
||||||
pub fn offset_of(&self, i: u64) -> u64 {
|
|
||||||
if let Some(align) = self.alignment {
|
|
||||||
let transform_index = i % self.capacity;
|
|
||||||
transform_index * align
|
|
||||||
} else {
|
|
||||||
i * mem::size_of::<T>() as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set an element at `i` in the buffer to `val`.
|
|
||||||
pub fn set_at(&self, queue: &wgpu::Queue, i: u64, val: &T) {
|
|
||||||
let offset = self.offset_of(i);
|
|
||||||
queue.write_buffer(&self.buffer, offset, bytemuck::bytes_of(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to insert an element to the GPU buffer, returning the index it was inserted at.
|
|
||||||
///
|
|
||||||
/// Returns `None` when the buffer has no space to fit the element.
|
|
||||||
pub fn try_insert(&mut self, queue: &wgpu::Queue, val: &T) -> Option<u64> {
|
|
||||||
// reuse a dead index or get the next one
|
|
||||||
let i = match self.dead_indices.pop_front() {
|
|
||||||
Some(i) => i,
|
|
||||||
None => {
|
|
||||||
if self.len == self.capacity {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = self.len;
|
|
||||||
self.len += 1;
|
|
||||||
i
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.set_at(queue, i, val);
|
|
||||||
|
|
||||||
Some(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert an element to the GPU buffer, returning the index it was inserted at.
|
|
||||||
///
|
|
||||||
/// The index is not guaranteed to be the end of the buffer since this structure reuses
|
|
||||||
/// indices after they're removed.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// Panics if the buffer does not have space to fit `val`, see [`GpuSlotBuffer::try_insert`].
|
|
||||||
pub fn insert(&mut self, queue: &wgpu::Queue, val: &T) -> u64 {
|
|
||||||
self.try_insert(queue, val)
|
|
||||||
.expect("GPU slot buffer ran out of slots to push elements into")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the element at `i`, clearing the elements slot in the buffer.
|
|
||||||
///
|
|
||||||
/// If you do not care that the slot in the buffer is emptied, use
|
|
||||||
/// [`GpuSlotBuffer::remove_quick`].
|
|
||||||
pub fn remove(&mut self, queue: &wgpu::Queue, i: u64) {
|
|
||||||
let mut zeros = Vec::new();
|
|
||||||
zeros.resize(mem::size_of::<T>(), 0);
|
|
||||||
|
|
||||||
let offset = self.offset_of(i);
|
|
||||||
queue.write_buffer(&self.buffer, offset, bytemuck::cast_slice(zeros.as_slice()));
|
|
||||||
self.dead_indices.push_back(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the element at `i` without clearing its space in the buffer.
|
|
||||||
///
|
|
||||||
/// If you want to ensure that the slot in the buffer is emptied, use
|
|
||||||
/// [`GpuSlotBuffer::remove`].
|
|
||||||
pub fn remove_quick(&mut self, i: u64) {
|
|
||||||
self.dead_indices.push_back(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the backing [`wgpu::Buffer`].
|
|
||||||
pub fn buffer(&self) -> &Arc<wgpu::Buffer> {
|
|
||||||
&self.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the length of the buffer.
|
|
||||||
///
|
|
||||||
/// This value may not reflect the amount of elements that are actually alive in the buffer if
|
|
||||||
/// elements were removed and not re-added.
|
|
||||||
pub fn len(&self) -> u64 {
|
|
||||||
self.len
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the amount of inuse indices in the buffer.
|
|
||||||
pub fn inuse_len(&self) -> u64 {
|
|
||||||
self.len - self.dead_indices.len() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the amount of elements the buffer can fit.
|
|
||||||
pub fn capacity(&self) -> u64 {
|
|
||||||
self.capacity
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,297 +0,0 @@
|
||||||
use std::{
|
|
||||||
cmp::max, collections::HashMap, sync::Arc
|
|
||||||
};
|
|
||||||
|
|
||||||
use glam::UVec2;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum AtlasPackError {
|
|
||||||
/// The rectangles can't be placed into the atlas. The atlas must increase in size
|
|
||||||
#[error("There is not enough space in the atlas for the textures")]
|
|
||||||
NotEnoughSpace,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct AtlasFrame {
|
|
||||||
pub x: u32,
|
|
||||||
pub y: u32,
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AtlasFrame {
|
|
||||||
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
x, y, width, height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
|
|
||||||
atlas_size: UVec2,
|
|
||||||
|
|
||||||
texture_format: wgpu::TextureFormat,
|
|
||||||
texture: Arc<wgpu::Texture>,
|
|
||||||
view: Arc<wgpu::TextureView>,
|
|
||||||
|
|
||||||
packer: P,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: AtlasPacker> TextureAtlas<P> {
|
|
||||||
pub fn new(
|
|
||||||
device: &wgpu::Device,
|
|
||||||
format: wgpu::TextureFormat,
|
|
||||||
usages: wgpu::TextureUsages,
|
|
||||||
atlas_size: UVec2,
|
|
||||||
) -> Self {
|
|
||||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
|
||||||
label: Some("texture_atlas"),
|
|
||||||
size: wgpu::Extent3d {
|
|
||||||
width: atlas_size.x,
|
|
||||||
height: atlas_size.y,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: wgpu::TextureDimension::D2,
|
|
||||||
format,
|
|
||||||
usage: usages,
|
|
||||||
view_formats: &[],
|
|
||||||
});
|
|
||||||
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
Self {
|
|
||||||
atlas_size,
|
|
||||||
texture_format: format,
|
|
||||||
texture: Arc::new(texture),
|
|
||||||
view: Arc::new(view),
|
|
||||||
packer: P::new(atlas_size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a texture of `size` and pack it into the atlas, returning the id of the texture in
|
|
||||||
/// the atlas.
|
|
||||||
///
|
|
||||||
/// If you are adding multiple textures at a time and want to wait to pack the atlas, use
|
|
||||||
/// [`TextureAtlas::add_texture_unpacked`] and then after you're done adding them, pack them
|
|
||||||
/// with [`TextureAtlas::pack_atlas`].
|
|
||||||
pub fn pack(&mut self, width: u32, height: u32) -> Result<u64, AtlasPackError> {
|
|
||||||
let id = self.packer.pack(width, height)?;
|
|
||||||
|
|
||||||
Ok(id as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the viewport of a texture index in the atlas.
|
|
||||||
pub fn texture_frame(&self, atlas_index: u64) -> Option<AtlasFrame> {
|
|
||||||
self.packer.frame(atlas_index as _)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view(&self) -> &Arc<wgpu::TextureView> {
|
|
||||||
&self.view
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture(&self) -> &Arc<wgpu::Texture> {
|
|
||||||
&self.texture
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn texture_format(&self) -> &wgpu::TextureFormat {
|
|
||||||
&self.texture_format
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size of the entire texture atlas.
|
|
||||||
pub fn atlas_size(&self) -> UVec2 {
|
|
||||||
self.atlas_size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AtlasPacker {
|
|
||||||
fn new(size: UVec2) -> Self;
|
|
||||||
|
|
||||||
/// Get an [`AtlasFrame`] of a texture with `id`.
|
|
||||||
fn frame(&self, id: usize) -> Option<AtlasFrame>;
|
|
||||||
|
|
||||||
/// Get all [`AtlasFrame`]s in the atlas.
|
|
||||||
fn frames(&self) -> &HashMap<usize, AtlasFrame>;
|
|
||||||
|
|
||||||
/// Pack a new rect into the atlas.
|
|
||||||
fn pack(&mut self, width: u32, height: u32) -> Result<usize, AtlasPackError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Skyline {
|
|
||||||
/// Starting x of the skyline
|
|
||||||
x: usize,
|
|
||||||
/// Starting y of the skyline
|
|
||||||
y: usize,
|
|
||||||
/// Width of the skyline
|
|
||||||
width: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Skyline {
|
|
||||||
fn right(&self) -> usize {
|
|
||||||
self.x + self.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SkylinePacker {
|
|
||||||
size: UVec2,
|
|
||||||
skylines: Vec<Skyline>,
|
|
||||||
frame_idx: usize,
|
|
||||||
frames: HashMap<usize, AtlasFrame>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SkylinePacker {
|
|
||||||
pub fn new(size: UVec2) -> Self {
|
|
||||||
let skylines = vec![Skyline {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: size.x as _,
|
|
||||||
}];
|
|
||||||
|
|
||||||
Self {
|
|
||||||
size,
|
|
||||||
skylines,
|
|
||||||
frame_idx: 0,
|
|
||||||
frames: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_add(&self, mut i: usize, w: u32, h: u32) -> Option<usize> {
|
|
||||||
let x = self.skylines[i].x as u32;
|
|
||||||
if x + w > self.size.x {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut width_left = w;
|
|
||||||
let mut y = self.skylines[i].y as u32;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
y = max(y, self.skylines[i].y as u32);
|
|
||||||
|
|
||||||
if y + h > self.size.y {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.skylines[i].width as u32 >= width_left {
|
|
||||||
return Some(y as usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
width_left -= self.skylines[i].width as u32;
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
if i >= self.skylines.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_skyline(&self, width: u32, height: u32) -> Option<(usize, AtlasFrame)> {
|
|
||||||
let mut min_height = std::u32::MAX;
|
|
||||||
let mut min_width = std::u32::MAX;
|
|
||||||
let mut index = None;
|
|
||||||
let mut frame = AtlasFrame::default();
|
|
||||||
|
|
||||||
// keep the min height as small as possible
|
|
||||||
for i in 0..self.skylines.len() {
|
|
||||||
if let Some(y) = self.can_add(i, width, height) {
|
|
||||||
let y = y as u32;
|
|
||||||
/* if r.bottom() < min_height
|
|
||||||
|| (r.bottom() == min_height && self.skylines[i].width < min_width as usize) */
|
|
||||||
if y + height < min_height ||
|
|
||||||
(y + height == min_height && self.skylines[i].width < min_width as usize)
|
|
||||||
{
|
|
||||||
min_height = y + height;
|
|
||||||
min_width = self.skylines[i].width as _;
|
|
||||||
index = Some(i);
|
|
||||||
frame = AtlasFrame::new(self.skylines[i].x as _, y, width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: rotation
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(index) = index {
|
|
||||||
Some((index, frame))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split(&mut self, i: usize, frame: &AtlasFrame) {
|
|
||||||
let skyline = Skyline {
|
|
||||||
x: frame.x as _,
|
|
||||||
y: (frame.y + frame.height) as _,
|
|
||||||
width: frame.width as _
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(skyline.right() <= self.size.x as usize);
|
|
||||||
assert!(skyline.y <= self.size.y as usize);
|
|
||||||
|
|
||||||
self.skylines.insert(i, skyline);
|
|
||||||
|
|
||||||
let i = i + 1;
|
|
||||||
|
|
||||||
while i < self.skylines.len() {
|
|
||||||
assert!(self.skylines[i - 1].x <= self.skylines[i].x);
|
|
||||||
|
|
||||||
if self.skylines[i].x < self.skylines[i - 1].x + self.skylines[i - 1].width {
|
|
||||||
let shrink = self.skylines[i-1].x + self.skylines[i-1].width - self.skylines[i].x;
|
|
||||||
|
|
||||||
if self.skylines[i].width <= shrink {
|
|
||||||
self.skylines.remove(i);
|
|
||||||
} else {
|
|
||||||
self.skylines[i].x += shrink;
|
|
||||||
self.skylines[i].width -= shrink;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Merge skylines with the same y value
|
|
||||||
fn merge(&mut self) {
|
|
||||||
let mut i = 1;
|
|
||||||
while i < self.skylines.len() {
|
|
||||||
if self.skylines[i - 1].y == self.skylines[i].y {
|
|
||||||
self.skylines[i - 1].width += self.skylines[i].width;
|
|
||||||
self.skylines.remove(i);
|
|
||||||
} else {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//pub fn pack(&mut self, )
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AtlasPacker for SkylinePacker {
|
|
||||||
fn new(size: UVec2) -> Self {
|
|
||||||
SkylinePacker::new(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frame(&self, id: usize) -> Option<AtlasFrame> {
|
|
||||||
self.frames.get(&id).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frames(&self) -> &HashMap<usize, AtlasFrame> {
|
|
||||||
&self.frames
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pack(&mut self, width: u32, height: u32) -> Result<usize, AtlasPackError> {
|
|
||||||
if let Some((i, frame)) = self.find_skyline(width, height) {
|
|
||||||
self.split(i, &frame);
|
|
||||||
self.merge();
|
|
||||||
|
|
||||||
let frame_idx = self.frame_idx;
|
|
||||||
self.frame_idx += 1;
|
|
||||||
|
|
||||||
self.frames.insert(frame_idx, frame);
|
|
||||||
|
|
||||||
Ok(frame_idx)
|
|
||||||
} else {
|
|
||||||
Err(AtlasPackError::NotEnoughSpace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct Vertex {
|
|
||||||
pub position: glam::Vec3,
|
|
||||||
pub tex_coords: glam::Vec2,
|
|
||||||
pub normals: glam::Vec3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vertex {
|
|
||||||
pub fn new(position: glam::Vec3, tex_coords: glam::Vec2, normals: glam::Vec3) -> Self {
|
|
||||||
Self {
|
|
||||||
position, tex_coords, normals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a [`wgpu::VertexBufferLayout`] with only the position as a vertex attribute.
|
|
||||||
///
|
|
||||||
/// The stride is still `std::mem::size_of::<Vertex>()`, but only position is included.
|
|
||||||
pub fn position_desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
|
||||||
wgpu::VertexBufferLayout {
|
|
||||||
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
|
||||||
step_mode: wgpu::VertexStepMode::Vertex,
|
|
||||||
attributes: &[
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: 0,
|
|
||||||
shader_location: 0,
|
|
||||||
format: wgpu::VertexFormat::Float32x3, // Vec3
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DescVertexBufferLayout for Vertex {
|
|
||||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
|
||||||
wgpu::VertexBufferLayout {
|
|
||||||
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
|
||||||
step_mode: wgpu::VertexStepMode::Vertex,
|
|
||||||
attributes: &[
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: 0,
|
|
||||||
shader_location: 0,
|
|
||||||
format: wgpu::VertexFormat::Float32x3, // Vec3
|
|
||||||
},
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 1,
|
|
||||||
format: wgpu::VertexFormat::Float32x2, // Vec2
|
|
||||||
},
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: std::mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 2,
|
|
||||||
format: wgpu::VertexFormat::Float32x3, // Vec3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct Vertex2D {
|
|
||||||
pub position: glam::Vec3,
|
|
||||||
pub tex_coords: glam::Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vertex2D {
|
|
||||||
pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self {
|
|
||||||
Self {
|
|
||||||
position, tex_coords
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
|
||||||
wgpu::VertexBufferLayout {
|
|
||||||
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
|
||||||
step_mode: wgpu::VertexStepMode::Vertex,
|
|
||||||
attributes: &[
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: 0,
|
|
||||||
shader_location: 0,
|
|
||||||
format: wgpu::VertexFormat::Float32x3, // Vec3
|
|
||||||
},
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: std::mem::size_of::<glam::Vec3>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 1,
|
|
||||||
format: wgpu::VertexFormat::Float32x2, // Vec2
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
use lyra_ecs::Component;
|
|
||||||
use lyra_reflect::Reflect;
|
|
||||||
use lyra_resource::ResHandle;
|
|
||||||
use lyra_math::{Vec3, Vec2};
|
|
||||||
|
|
||||||
/// How the sprite is positioned and rotated relative to its [`Transform`].
|
|
||||||
///
|
|
||||||
/// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Default, Component, Reflect)]
|
|
||||||
pub enum Pivot {
|
|
||||||
#[default]
|
|
||||||
Center,
|
|
||||||
CenterLeft,
|
|
||||||
CenterRight,
|
|
||||||
TopLeft,
|
|
||||||
TopRight,
|
|
||||||
TopCenter,
|
|
||||||
BottomLeft,
|
|
||||||
BottomRight,
|
|
||||||
BottomCenter,
|
|
||||||
/// A custom anchor point relative to top left.
|
|
||||||
/// Top left is `(0.0, 0.0)`.
|
|
||||||
Custom(Vec2)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pivot {
|
|
||||||
/// Get the pivot point as a Vec2.
|
|
||||||
///
|
|
||||||
/// The point is offset from the top left `(0.0, 0.0)`.
|
|
||||||
pub fn as_vec(&self) -> Vec2 {
|
|
||||||
match self {
|
|
||||||
Pivot::Center => Vec2::new(0.5, 0.5),
|
|
||||||
Pivot::CenterLeft => Vec2::new(0.0, 0.5),
|
|
||||||
Pivot::CenterRight => Vec2::new(1.0, 0.5),
|
|
||||||
Pivot::TopLeft => Vec2::ZERO,
|
|
||||||
Pivot::TopRight => Vec2::new(1.0, 0.0),
|
|
||||||
Pivot::TopCenter => Vec2::new(0.0, 0.5),
|
|
||||||
Pivot::BottomLeft => Vec2::new(0.0, 1.0),
|
|
||||||
Pivot::BottomRight => Vec2::new(1.0, 1.0),
|
|
||||||
Pivot::BottomCenter => Vec2::new(0.5, 1.0),
|
|
||||||
Pivot::Custom(v) => *v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Component, Reflect)]
|
|
||||||
pub struct Sprite {
|
|
||||||
pub texture: ResHandle<lyra_resource::Texture>,
|
|
||||||
pub color: Vec3,
|
|
||||||
pub pivot: Pivot,
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
mod plugin;
|
|
||||||
pub use plugin::*;
|
|
||||||
|
|
||||||
mod window;
|
|
||||||
pub use window::*;
|
|
||||||
|
|
||||||
pub use winit::dpi as dpi;
|
|
|
@ -1,336 +0,0 @@
|
||||||
use std::{collections::VecDeque, sync::Arc};
|
|
||||||
|
|
||||||
use async_std::task::block_on;
|
|
||||||
use glam::{DVec2, IVec2, UVec2};
|
|
||||||
use lyra_ecs::Entity;
|
|
||||||
use lyra_reflect::Reflect;
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use tracing::{debug, error, warn};
|
|
||||||
use winit::{
|
|
||||||
application::ApplicationHandler,
|
|
||||||
event::WindowEvent,
|
|
||||||
event_loop::{ActiveEventLoop, EventLoop},
|
|
||||||
window::{Window, WindowAttributes, WindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use winit::event::{DeviceId, DeviceEvent, MouseScrollDelta, ElementState, RawKeyEvent};
|
|
||||||
pub use winit::keyboard::PhysicalKey;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
game::{App, WindowState},
|
|
||||||
plugin::Plugin,
|
|
||||||
render::renderer::BasicRenderer, winit::{FullscreenMode, LastWindow, PrimaryWindow},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::WindowOptions;
|
|
||||||
|
|
||||||
/// A struct that contains a [`DeviceEvent`](winit::event::DeviceEvent) with its source
|
|
||||||
/// [`DeviceId`](winit::event::DeviceId).
|
|
||||||
#[derive(Debug, Clone, Reflect)]
|
|
||||||
pub struct DeviceEventPair {
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub device_src: DeviceId,
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub event: DeviceEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WinitPlugin {
|
|
||||||
/// The primary window that will be created.
|
|
||||||
///
|
|
||||||
/// This will become `None` after the window is created. If you want to get the
|
|
||||||
/// primary world later, query for an entity with the [`PrimaryWindow`] and
|
|
||||||
/// [`WindowOptions`] components.
|
|
||||||
pub primary_window: Option<WindowOptions>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WinitPlugin {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
primary_window: Some(WindowOptions::default()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for WinitPlugin {
|
|
||||||
fn setup(&mut self, app: &mut crate::game::App) {
|
|
||||||
app.set_run_fn(winit_app_runner);
|
|
||||||
app.register_event::<WindowEvent>();
|
|
||||||
app.register_event::<DeviceEventPair>();
|
|
||||||
|
|
||||||
if let Some(prim) = self.primary_window.take() {
|
|
||||||
app.add_resource(WinitWindows::with_window(prim));
|
|
||||||
} else {
|
|
||||||
app.add_resource(WinitWindows::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_ready(&self, _app: &mut crate::game::App) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete(&self, _app: &mut crate::game::App) {}
|
|
||||||
|
|
||||||
fn cleanup(&self, _app: &mut crate::game::App) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct WinitWindows {
|
|
||||||
pub windows: FxHashMap<WindowId, Arc<Window>>,
|
|
||||||
pub entity_to_window: FxHashMap<Entity, WindowId>,
|
|
||||||
pub window_to_entity: FxHashMap<WindowId, Entity>,
|
|
||||||
/// windows that will be created when the Winit runner first starts.
|
|
||||||
window_queue: VecDeque<WindowOptions>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WinitWindows {
|
|
||||||
pub fn with_window(window: WindowOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
window_queue: vec![window].into(),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_window(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &ActiveEventLoop,
|
|
||||||
entity: Entity,
|
|
||||||
attr: WindowAttributes,
|
|
||||||
) -> Result<WindowId, winit::error::OsError> {
|
|
||||||
let win = event_loop.create_window(attr)?;
|
|
||||||
let id = win.id();
|
|
||||||
|
|
||||||
self.windows.insert(id, Arc::new(win));
|
|
||||||
self.entity_to_window.insert(entity, id);
|
|
||||||
self.window_to_entity.insert(id, entity);
|
|
||||||
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_entity_window(&self, entity: Entity) -> Option<&Arc<Window>> {
|
|
||||||
self.entity_to_window
|
|
||||||
.get(&entity)
|
|
||||||
.and_then(|id| self.windows.get(id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn winit_app_runner(app: App) {
|
|
||||||
let evloop = EventLoop::new().expect("failed to create winit EventLoop");
|
|
||||||
|
|
||||||
let mut winit_runner = WinitRunner { app };
|
|
||||||
evloop.run_app(&mut winit_runner).expect("loop error");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WinitRunner {
|
|
||||||
app: App,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationHandler for WinitRunner {
|
|
||||||
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
|
||||||
self.app.update();
|
|
||||||
|
|
||||||
let renderer = self
|
|
||||||
.app
|
|
||||||
.renderer
|
|
||||||
.get_mut()
|
|
||||||
.expect("renderer was not initialized");
|
|
||||||
renderer.prepare(&mut self.app.world);
|
|
||||||
|
|
||||||
match renderer.render() {
|
|
||||||
Ok(_) => {}
|
|
||||||
// Reconfigure the surface if lost
|
|
||||||
//Err(wgpu::SurfaceError::Lost) => self.on_resize(.surface_size()),
|
|
||||||
// The system is out of memory, we should probably quit
|
|
||||||
Err(wgpu::SurfaceError::OutOfMemory) => {
|
|
||||||
error!("OOM");
|
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
|
||||||
Err(e) => eprintln!("{:?}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
let windows = self.app.world.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource");
|
|
||||||
for window in windows.windows.values() {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
|
||||||
let world = &mut self.app.world;
|
|
||||||
let en = world.reserve_entity();
|
|
||||||
|
|
||||||
let mut windows = world.get_resource_mut::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource");
|
|
||||||
let mut to_create_window = windows.window_queue.pop_front().unwrap_or_default();
|
|
||||||
let window_attr = to_create_window.as_attributes();
|
|
||||||
//drop(windows);
|
|
||||||
|
|
||||||
|
|
||||||
//let en = world.spawn((to_create_window, last, PrimaryWindow));
|
|
||||||
|
|
||||||
//let mut windows = world.get_resource_mut::<WinitWindows>()
|
|
||||||
//.expect("world missing WinitWindows resource");
|
|
||||||
let wid = windows.create_window(event_loop, en, window_attr).unwrap();
|
|
||||||
let window = windows.windows.get(&wid).unwrap().clone();
|
|
||||||
drop(windows);
|
|
||||||
|
|
||||||
// update fields that default to `None`
|
|
||||||
to_create_window.position = window.outer_position()
|
|
||||||
.or_else(|_| window.inner_position())
|
|
||||||
.ok()
|
|
||||||
.map(|p| IVec2::new(p.x, p.y));
|
|
||||||
|
|
||||||
// See [`WindowOptions::as_attributes`], it defaults to Windowed fullscreen mode, so we
|
|
||||||
// must trigger an update in the sync system;
|
|
||||||
let mut last = LastWindow { last: to_create_window.clone() };
|
|
||||||
last.last.fullscreen_mode = FullscreenMode::Windowed;
|
|
||||||
|
|
||||||
world.insert(en, (to_create_window, last, PrimaryWindow));
|
|
||||||
|
|
||||||
debug!("Created window after resume");
|
|
||||||
|
|
||||||
let renderer = block_on(BasicRenderer::create_with_window(world, window));
|
|
||||||
if self.app.renderer.set(Box::new(renderer)).is_err() {
|
|
||||||
warn!("renderer was re-initialized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_event(
|
|
||||||
&mut self,
|
|
||||||
_: &ActiveEventLoop,
|
|
||||||
device_src: winit::event::DeviceId,
|
|
||||||
event: winit::event::DeviceEvent,
|
|
||||||
) {
|
|
||||||
self.app.push_event(DeviceEventPair { device_src, event });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
||||||
window_id: winit::window::WindowId,
|
|
||||||
event: WindowEvent,
|
|
||||||
) {
|
|
||||||
/* let windows = self.app.world.get_resource::<WinitWindows>();
|
|
||||||
let window = match windows.windows.get(&window_id) {
|
|
||||||
Some(w) => w.clone(),
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
drop(windows); */
|
|
||||||
|
|
||||||
self.app.push_event(event.clone());
|
|
||||||
match event {
|
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
|
||||||
let windows = self.app.world.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource");
|
|
||||||
let en = windows.window_to_entity.get(&window_id)
|
|
||||||
.expect("missing window entity");
|
|
||||||
|
|
||||||
// update the window and its cache so the sync system doesn't try to update the window
|
|
||||||
let (mut en_window, mut en_last_win) = self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*en).get().unwrap();
|
|
||||||
let pos = Some(DVec2::new(position.x, position.y));
|
|
||||||
en_window.set_physical_cursor_position(pos);
|
|
||||||
en_last_win.set_physical_cursor_position(pos);
|
|
||||||
},
|
|
||||||
WindowEvent::ActivationTokenDone { .. } => todo!(),
|
|
||||||
WindowEvent::Resized(physical_size) => {
|
|
||||||
self.app.on_resize(physical_size);
|
|
||||||
|
|
||||||
let (mut window, mut last_window) = self
|
|
||||||
.app
|
|
||||||
.world
|
|
||||||
.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource")
|
|
||||||
.window_to_entity
|
|
||||||
.get(&window_id)
|
|
||||||
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// update the window and its cache so the sync system doesn't try to update the window
|
|
||||||
let size = UVec2::new(physical_size.width, physical_size.height);
|
|
||||||
window.set_physical_size(size);
|
|
||||||
last_window.set_physical_size(size);
|
|
||||||
},
|
|
||||||
// Mark the cursor as outside the window when it leaves
|
|
||||||
WindowEvent::CursorLeft { .. } => {
|
|
||||||
let (mut window, mut last_window) = self
|
|
||||||
.app
|
|
||||||
.world
|
|
||||||
.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource")
|
|
||||||
.window_to_entity
|
|
||||||
.get(&window_id)
|
|
||||||
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
|
|
||||||
.unwrap();
|
|
||||||
window.set_physical_cursor_position(None);
|
|
||||||
last_window.set_physical_cursor_position(None);
|
|
||||||
},
|
|
||||||
WindowEvent::Moved(physical_position) => {
|
|
||||||
let mut state = self.app.world.get_resource_or_else(WindowState::new);
|
|
||||||
state.position = IVec2::new(physical_position.x, physical_position.y);
|
|
||||||
},
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
self.app.on_exit();
|
|
||||||
event_loop.exit();
|
|
||||||
},
|
|
||||||
WindowEvent::Destroyed => todo!(),
|
|
||||||
WindowEvent::DroppedFile(_path_buf) => todo!(),
|
|
||||||
WindowEvent::HoveredFile(_path_buf) => todo!(),
|
|
||||||
WindowEvent::HoveredFileCancelled => todo!(),
|
|
||||||
WindowEvent::Focused(focused) => {
|
|
||||||
let mut window_opts = self
|
|
||||||
.app
|
|
||||||
.world
|
|
||||||
.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource")
|
|
||||||
.window_to_entity
|
|
||||||
.get(&window_id)
|
|
||||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
|
||||||
.unwrap();
|
|
||||||
window_opts.focused = focused;
|
|
||||||
},
|
|
||||||
WindowEvent::ModifiersChanged(modifiers) => {
|
|
||||||
debug!("modifiers changed: {:?}", modifiers)
|
|
||||||
},
|
|
||||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
|
||||||
let mut window_opts = self
|
|
||||||
.app
|
|
||||||
.world
|
|
||||||
.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource")
|
|
||||||
.window_to_entity
|
|
||||||
.get(&window_id)
|
|
||||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
|
||||||
.unwrap();
|
|
||||||
window_opts.scale_factor = scale_factor;
|
|
||||||
},
|
|
||||||
WindowEvent::ThemeChanged(theme) => {
|
|
||||||
let mut window_opts = self
|
|
||||||
.app
|
|
||||||
.world
|
|
||||||
.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource")
|
|
||||||
.window_to_entity
|
|
||||||
.get(&window_id)
|
|
||||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
|
||||||
.unwrap();
|
|
||||||
window_opts.theme = Some(theme);
|
|
||||||
},
|
|
||||||
WindowEvent::Occluded(occ) => {
|
|
||||||
let mut window_opts = self
|
|
||||||
.app
|
|
||||||
.world
|
|
||||||
.get_resource::<WinitWindows>()
|
|
||||||
.expect("world missing WinitWindows resource")
|
|
||||||
.window_to_entity
|
|
||||||
.get(&window_id)
|
|
||||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
|
||||||
.unwrap();
|
|
||||||
window_opts.occluded = occ;
|
|
||||||
},
|
|
||||||
WindowEvent::RedrawRequested => {
|
|
||||||
//debug!("should redraw");
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,723 +0,0 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
use glam::{DVec2, IVec2, UVec2, Vec2};
|
|
||||||
use lyra_ecs::{query::{filter::Changed, Entities, Res, View}, Component};
|
|
||||||
use lyra_math::Area;
|
|
||||||
use lyra_reflect::Reflect;
|
|
||||||
use lyra_resource::Image;
|
|
||||||
use tracing::{error, warn};
|
|
||||||
use winit::{dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, monitor::{MonitorHandle, VideoModeHandle}, window::{CustomCursor, Window}};
|
|
||||||
|
|
||||||
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
|
|
||||||
|
|
||||||
use crate::{plugin::Plugin, winit::WinitWindows, lyra_engine};
|
|
||||||
|
|
||||||
/// Flag component that
|
|
||||||
#[derive(Clone, Component)]
|
|
||||||
pub struct PrimaryWindow;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
|
||||||
pub enum CursorAppearance {
|
|
||||||
Icon(CursorIcon),
|
|
||||||
Custom(CustomCursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Reflect)]
|
|
||||||
pub enum FullscreenMode{
|
|
||||||
#[default]
|
|
||||||
Windowed,
|
|
||||||
BorderlessFullscreen,
|
|
||||||
SizedFullscreen,
|
|
||||||
Fullscreen,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FullscreenMode {
|
|
||||||
pub fn as_winit_fullscreen(&self, monitor: MonitorHandle, physical_size: UVec2) -> Option<winit::window::Fullscreen> {
|
|
||||||
match &self {
|
|
||||||
FullscreenMode::Windowed => None,
|
|
||||||
FullscreenMode::BorderlessFullscreen => Some(winit::window::Fullscreen::Borderless(None)),
|
|
||||||
// find closest video mode for full screen sizes
|
|
||||||
_ => {
|
|
||||||
let closest = find_closest_video_mode(monitor, physical_size);
|
|
||||||
|
|
||||||
if let Some(closest) = closest {
|
|
||||||
Some(winit::window::Fullscreen::Exclusive(closest))
|
|
||||||
} else {
|
|
||||||
warn!("Could not find closest video mode, falling back to windowed.");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Cursor {
|
|
||||||
/// Modifies the cursor icon of the window.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Orbital:** Unsupported.
|
|
||||||
/// * **Web:** Custom cursors have to be loaded and decoded first, until then the previous cursor is shown.
|
|
||||||
pub appearance: CursorAppearance,
|
|
||||||
|
|
||||||
/// Gets/sets the window's cursor grab mode
|
|
||||||
///
|
|
||||||
/// # Tip:
|
|
||||||
/// First try confining the cursor, and if it fails, try locking it instead.
|
|
||||||
pub grab: CursorGrabMode,
|
|
||||||
|
|
||||||
/// Gets/sets whether the window catches cursor events.
|
|
||||||
///
|
|
||||||
/// If `false`, events are passed through the window such that any other window behind it
|
|
||||||
/// receives them. By default hittest is enabled.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
pub hittest: bool,
|
|
||||||
|
|
||||||
/// Gets/sets the cursor's visibility
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Windows / X11 / Wayland:** The cursor is only hidden within the confines of the window.
|
|
||||||
/// * **macOS:** The cursor is hidden as long as the window has input focus, even if the
|
|
||||||
/// cursor is outside of the window.
|
|
||||||
/// * **iOS / Android:** Unsupported.
|
|
||||||
pub visible: bool,
|
|
||||||
//cursor_position: Option<PhysicalPosition<i32>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Options that the window will be created with.
|
|
||||||
#[derive(Clone, Component, Reflect)]
|
|
||||||
pub struct WindowOptions {
|
|
||||||
/// The enabled window buttons.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Wayland / X11 / Orbital:** Not implemented. Always set to [`WindowButtons::all`].
|
|
||||||
/// * **Web / iOS / Android:** Unsupported. Always set to [`WindowButtons::all`].
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub enabled_buttons: WindowButtons,
|
|
||||||
|
|
||||||
/// Gets or sets if the window is in focus.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Wayland / Orbital:** Unsupported.
|
|
||||||
pub focused: bool,
|
|
||||||
|
|
||||||
/// Gets or sets the fullscreen setting.
|
|
||||||
pub fullscreen_mode: FullscreenMode,
|
|
||||||
|
|
||||||
/// Gets/sets the position of the top-left hand corner of the window relative to
|
|
||||||
/// the top-left hand corner of the desktop.
|
|
||||||
///
|
|
||||||
/// Note that the top-left hand corner of the desktop is not necessarily the same
|
|
||||||
/// as the screen. If the user uses a desktop with multiple monitors, the top-left
|
|
||||||
/// hand corner of the desktop is the top-left hand corner of the monitor at the
|
|
||||||
/// top-left of the desktop.
|
|
||||||
///
|
|
||||||
/// If this is none, the position will be chosen by the windowing manager at creation, then set
|
|
||||||
/// when the window is created.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS:** Value is the top left coordinates of the window’s safe area in the screen
|
|
||||||
/// space coordinate system.
|
|
||||||
/// * **Web:** Value is the top-left coordinates relative to the viewport. Note: this will be
|
|
||||||
/// the same value as [`WindowOptions::outer_position`].
|
|
||||||
/// * **Android / Wayland:** Unsupported.
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub position: Option<IVec2>,
|
|
||||||
|
|
||||||
/// Gets/sets the size of the view in the window.
|
|
||||||
///
|
|
||||||
/// The size does not include the window title bars and borders.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Web:** The size of the canvas element. Doesn’t account for CSS `transform`.
|
|
||||||
#[reflect(skip)]
|
|
||||||
physical_size: UVec2,
|
|
||||||
|
|
||||||
/// Gets/sets if the window has decorations.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Web:** Always set to `true`.
|
|
||||||
pub decorated: bool,
|
|
||||||
|
|
||||||
/// Gets/sets the window's current maximized state
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Web:** Unsupported.
|
|
||||||
pub maximized: bool,
|
|
||||||
|
|
||||||
/// Gets/sets the window's current minimized state.
|
|
||||||
///
|
|
||||||
/// Is `None` if the minimized state could not be determined.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Wayland:** always `None`, un-minimize is unsupported.
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
pub minimized: Option<bool>,
|
|
||||||
|
|
||||||
/// Gets/sets the window's current resizable state
|
|
||||||
///
|
|
||||||
/// If this is false, the window can still be resized by changing [`WindowOptions::size`].
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// Setting this only has an affect on desktop platforms.
|
|
||||||
///
|
|
||||||
/// * **X11:** Due to a bug in XFCE, setting this has no effect..
|
|
||||||
/// * **iOS / Android / Web:** Unsupported.
|
|
||||||
pub resizable: bool,
|
|
||||||
|
|
||||||
/// Gets/sets the window's current visibility state.
|
|
||||||
///
|
|
||||||
/// `None` means it couldn't be determined.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **X11:** Not implemented.
|
|
||||||
/// * **Wayland / Android / Web:** Unsupported.
|
|
||||||
/// * **iOS:** Setting is not implemented, getting is unsupported.
|
|
||||||
pub visible: Option<bool>,
|
|
||||||
|
|
||||||
/// Gets/sets the window resize increments.
|
|
||||||
///
|
|
||||||
/// This is a niche constraint hint usually employed by terminal emulators and other apps
|
|
||||||
/// that need “blocky” resizes.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers.
|
|
||||||
/// * **Wayland:** Not implemented, always `None`.
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub resize_increments: Option<Size>,
|
|
||||||
|
|
||||||
/// Gets the scale factor.
|
|
||||||
///
|
|
||||||
/// The scale factor is the ratio of physical pixels to logical pixels.
|
|
||||||
/// See [winit docs](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor)
|
|
||||||
/// for more information.
|
|
||||||
pub scale_factor: f64,
|
|
||||||
|
|
||||||
/// Gets/sets the window's blur state.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Android / iOS / X11 / Web / Windows:** Unsupported.
|
|
||||||
/// * **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
|
|
||||||
pub blur: bool,
|
|
||||||
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub cursor: Cursor,
|
|
||||||
|
|
||||||
/// Sets whether the window should get IME events
|
|
||||||
///
|
|
||||||
/// When IME is allowed, the window will receive [`Ime`](winit::event::WindowEvent::Ime)
|
|
||||||
/// events, and during the preedit phase the window will NOT get KeyboardInput events.
|
|
||||||
/// The window should allow IME while it is expecting text input.
|
|
||||||
///
|
|
||||||
/// When IME is not allowed, the window won’t receive [`Ime`](winit::event::WindowEvent::Ime)
|
|
||||||
/// events, and will receive [`KeyboardInput`](winit::event::WindowEvent::KeyboardInput) events
|
|
||||||
/// for every keypress instead. Not allowing IME is useful for games for example.
|
|
||||||
/// IME is not allowed by default.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined.
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
|
|
||||||
pub ime_allowed: bool,
|
|
||||||
|
|
||||||
/// Sets area of IME box in physical coordinates relative to the top left.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **X11:** - area is not supported, only position.
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
#[reflect(skip)]
|
|
||||||
physical_ime_cursor_area: Option<Area<Vec2, Vec2>>,
|
|
||||||
|
|
||||||
/// Gets/sets the minimum size of the window.
|
|
||||||
///
|
|
||||||
/// Units are in logical pixels.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Orbital:** Unsupported.
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub min_size: Option<Vec2>,
|
|
||||||
|
|
||||||
/// Gets/sets the maximum size of the window.
|
|
||||||
///
|
|
||||||
/// Units are in logical pixels.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Orbital:** Unsupported.
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub max_size: Option<Vec2>,
|
|
||||||
|
|
||||||
/// Gets/sets the current window theme.
|
|
||||||
///
|
|
||||||
/// Specify `None` to reset the theme to the system default. May also be `None` on unsupported
|
|
||||||
/// platforms.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus
|
|
||||||
/// to get the system preference.
|
|
||||||
/// * **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used,
|
|
||||||
/// it will default to [`Theme::Dark`](winit::window::Theme::Dark).
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub theme: Option<Theme>,
|
|
||||||
|
|
||||||
/// Gets/sets the title of the window.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android:** Unsupported.
|
|
||||||
/// * **X11 / Wayland / Web:** Cannot get, will always be an empty string.
|
|
||||||
pub title: String,
|
|
||||||
|
|
||||||
/// Gets/sets the window's transparency state.
|
|
||||||
///
|
|
||||||
/// This is just a hint that may not change anything about the window transparency, however
|
|
||||||
/// doing a mismatch between the content of your window and this hint may result in visual
|
|
||||||
/// artifacts.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **macOS:** This will reset the window’s background color.
|
|
||||||
/// * **Web / iOS / Android:** Unsupported.
|
|
||||||
/// * **X11:** Can only be set while building the window.
|
|
||||||
pub transparent: bool,
|
|
||||||
|
|
||||||
/// Sets the window's icon.
|
|
||||||
///
|
|
||||||
/// On Windows and X11, this is typically the small icon in the top-left corner of
|
|
||||||
/// the titlebar.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported.
|
|
||||||
/// * **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it’s
|
|
||||||
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
|
||||||
/// * **X11:** Has no universal guidelines for icon sizes, so you’re at the whims of
|
|
||||||
/// the WM. That said, it’s usually in the same ballpark as on Windows.
|
|
||||||
pub window_icon: Option<lyra_resource::ResHandle<Image>>,
|
|
||||||
|
|
||||||
/// Change the window level.
|
|
||||||
///
|
|
||||||
/// This is just a hint to the OS, and the system could ignore it.
|
|
||||||
///
|
|
||||||
/// See [`WindowLevel`] for details.
|
|
||||||
#[reflect(skip)]
|
|
||||||
pub window_level: WindowLevel,
|
|
||||||
|
|
||||||
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu)
|
|
||||||
/// at a specified position in physical coordinates.
|
|
||||||
///
|
|
||||||
/// This is the context menu that is normally shown when interacting with the title bar. This is useful when implementing custom decorations.
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported.
|
|
||||||
//pub physical_window_menu_pos: Option<Vec2>,
|
|
||||||
|
|
||||||
/// Gets the window's occluded state (completely hidden from view).
|
|
||||||
///
|
|
||||||
/// This is different to window visibility as it depends on whether the window is
|
|
||||||
/// closed, minimised, set invisible, or fully occluded by another window.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS:** this is set to `false` in response to an applicationWillEnterForeground
|
|
||||||
/// callback which means the application should start preparing its data.
|
|
||||||
/// Its `true` in response to an applicationDidEnterBackground callback which means
|
|
||||||
/// the application should free resources (according to the iOS application lifecycle).
|
|
||||||
/// * **Web:** Doesn't take into account CSS border, padding, or transform.
|
|
||||||
/// * **Android / Wayland / Windows / Orbital:** Unsupported.
|
|
||||||
// TODO: update
|
|
||||||
pub(crate) occluded: bool,
|
|
||||||
|
|
||||||
/// Gets/sets the position of the cursor in physical coordinates.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **Wayland:** Cursor must be in [`CursorGrabMode::Locked`].
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
#[reflect(skip)]
|
|
||||||
physical_cursor_position: Option<DVec2>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fn physical_to_vec2<P: winit::dpi::Pixel>(size: PhysicalSize<P>) -> Vec2 {
|
|
||||||
let size = size.cast::<f32>();
|
|
||||||
Vec2::new(size.width, size.height)
|
|
||||||
} */
|
|
||||||
|
|
||||||
fn logical_to_vec2(size: LogicalSize<f32>) -> Vec2 {
|
|
||||||
Vec2::new(size.width, size.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<winit::window::WindowAttributes> for WindowOptions {
|
|
||||||
fn from(value: winit::window::WindowAttributes) -> Self {
|
|
||||||
Self {
|
|
||||||
enabled_buttons: value.enabled_buttons,
|
|
||||||
focused: false,
|
|
||||||
fullscreen_mode: value.fullscreen.map(|m| match m {
|
|
||||||
winit::window::Fullscreen::Exclusive(video_mode_handle) => {
|
|
||||||
if video_mode_handle.size() == video_mode_handle.monitor().size() {
|
|
||||||
FullscreenMode::Fullscreen
|
|
||||||
} else {
|
|
||||||
FullscreenMode::SizedFullscreen
|
|
||||||
}
|
|
||||||
},
|
|
||||||
winit::window::Fullscreen::Borderless(_) => FullscreenMode::BorderlessFullscreen,
|
|
||||||
}).unwrap_or(FullscreenMode::Windowed),
|
|
||||||
position: value.position.map(|p| {
|
|
||||||
let s = p.to_physical::<i32>(1.0);
|
|
||||||
IVec2::new(s.x, s.y)
|
|
||||||
}),
|
|
||||||
physical_size: value.inner_size.map(|s| {
|
|
||||||
let s = s.to_physical::<u32>(1.0);
|
|
||||||
UVec2::new(s.width, s.height)
|
|
||||||
}).unwrap_or(UVec2::new(1280, 720)),
|
|
||||||
decorated: value.decorations,
|
|
||||||
maximized: value.maximized,
|
|
||||||
minimized: None,
|
|
||||||
resizable: value.resizable,
|
|
||||||
visible: Some(value.visible),
|
|
||||||
resize_increments: value.resize_increments.map(|r| r.into()),
|
|
||||||
scale_factor: 1.0,
|
|
||||||
blur: value.blur,
|
|
||||||
cursor: Cursor {
|
|
||||||
appearance: match value.cursor {
|
|
||||||
winit::window::Cursor::Icon(icon) => CursorAppearance::Icon(icon),
|
|
||||||
winit::window::Cursor::Custom(custom) => CursorAppearance::Custom(custom),
|
|
||||||
},
|
|
||||||
grab: CursorGrabMode::None,
|
|
||||||
hittest: true,
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
ime_allowed: false,
|
|
||||||
physical_ime_cursor_area: None,
|
|
||||||
min_size: value.min_inner_size.map(|m| logical_to_vec2(m.to_logical(1.0))),
|
|
||||||
max_size: value.max_inner_size.map(|m| logical_to_vec2(m.to_logical(1.0))),
|
|
||||||
theme: value.preferred_theme,
|
|
||||||
title: value.title,
|
|
||||||
transparent: value.transparent,
|
|
||||||
window_icon: None,
|
|
||||||
window_level: value.window_level,
|
|
||||||
occluded: false,
|
|
||||||
physical_cursor_position: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WindowOptions {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::from(Window::default_attributes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_closest_video_mode(monitor: MonitorHandle, physical_size: UVec2) -> Option<VideoModeHandle> {
|
|
||||||
let mut modes = monitor.video_modes();
|
|
||||||
let mut closest = modes.next()?;
|
|
||||||
let closest_size = closest.size();
|
|
||||||
let mut closest_size = UVec2::new(closest_size.width, closest_size.height);
|
|
||||||
|
|
||||||
for mode in modes {
|
|
||||||
let s = closest.size();
|
|
||||||
let s = UVec2::new(s.width, s.height);
|
|
||||||
|
|
||||||
if (physical_size - s).length_squared() < (physical_size - closest_size).length_squared() {
|
|
||||||
closest = mode;
|
|
||||||
closest_size = s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(closest)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowOptions {
|
|
||||||
/// Create winit [`WindowAttributes`] from self.
|
|
||||||
///
|
|
||||||
/// This will ignore [`WindowOptions::fullscreen`] mode on self, defaulting to
|
|
||||||
/// [`FullscreenMode::Windowed`]. It will be updated on first run of the sync system.
|
|
||||||
pub(crate) fn as_attributes(&self) -> winit::window::WindowAttributes {
|
|
||||||
let mut att = winit::window::Window::default_attributes();
|
|
||||||
|
|
||||||
att.enabled_buttons = self.enabled_buttons.clone();
|
|
||||||
att.fullscreen = None;
|
|
||||||
att.inner_size = Some(Size::Physical(PhysicalSize::new(self.physical_size.x, self.physical_size.y)));
|
|
||||||
att.decorations = self.decorated;
|
|
||||||
att.maximized = self.maximized;
|
|
||||||
att.resizable = self.resizable;
|
|
||||||
att.visible = self.visible.unwrap_or(true);
|
|
||||||
att.position = self.position.map(|p| Position::Physical(PhysicalPosition::new(p.x, p.y)));
|
|
||||||
att.resize_increments = self.resize_increments.map(|i| i.into());
|
|
||||||
att.blur = self.blur;
|
|
||||||
att.cursor = match self.cursor.appearance.clone() {
|
|
||||||
CursorAppearance::Icon(icon) => winit::window::Cursor::Icon(icon),
|
|
||||||
CursorAppearance::Custom(custom) => winit::window::Cursor::Custom(custom),
|
|
||||||
};
|
|
||||||
att.min_inner_size = self.min_size.map(|s| Size::Logical(LogicalSize::new(s.x as _, s.y as _)));
|
|
||||||
att.max_inner_size = self.max_size.map(|s| Size::Logical(LogicalSize::new(s.x as _, s.y as _)));
|
|
||||||
att.preferred_theme = self.theme;
|
|
||||||
att.title = self.title.clone();
|
|
||||||
att.transparent = self.transparent;
|
|
||||||
if self.window_icon.is_some() {
|
|
||||||
todo!("cannot set window attribute icon yet");
|
|
||||||
}
|
|
||||||
att.window_level = self.window_level;
|
|
||||||
|
|
||||||
att
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The size of the window in physical coordinates.
|
|
||||||
pub fn physical_size(&self) -> UVec2 {
|
|
||||||
self.physical_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the size of the window in physical coordinates.
|
|
||||||
pub fn set_physical_size(&mut self, size: UVec2) {
|
|
||||||
self.physical_size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The size of the window in logical coordinates.
|
|
||||||
pub fn size(&self) -> Vec2 {
|
|
||||||
self.physical_size.as_vec2() / self.scale_factor as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the size of the window in logical coordinates.
|
|
||||||
pub fn set_size(&mut self, size: Vec2) {
|
|
||||||
self.physical_size = (size * self.scale_factor as f32).as_uvec2();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a boolean indicating if the mouse is inside the window.
|
|
||||||
pub fn is_mouse_inside(&self) -> bool {
|
|
||||||
if let Some(pos) = self.physical_cursor_position {
|
|
||||||
let s = self.physical_size;
|
|
||||||
return pos.x >= 0.0 && pos.x <= s.x as f64
|
|
||||||
&& pos.y >= 0.0 && pos.y <= s.y as f64;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The cursor position in the window in logical coordinates.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the cursor is not in the window.
|
|
||||||
pub fn cursor_position(&self) -> Option<Vec2> {
|
|
||||||
if !self.is_mouse_inside() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.physical_cursor_position.map(|p| (p / self.scale_factor).as_vec2())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The cursor position in the window in physical coordinates.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the cursor is not in the window.
|
|
||||||
pub fn physical_cursor_position(&self) -> Option<Vec2> {
|
|
||||||
if !self.is_mouse_inside() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.physical_cursor_position.map(|p| p.as_vec2())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the cursor position in logical coordinates.
|
|
||||||
///
|
|
||||||
/// Can be used to mark the cursor outside of the window as well.
|
|
||||||
pub fn set_cursor_position(&mut self, pos: Option<Vec2>) {
|
|
||||||
self.physical_cursor_position = pos.map(|p| p.as_dvec2() * self.scale_factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the cursor position in physical coordinates.
|
|
||||||
///
|
|
||||||
/// Can be used to mark the cursor outside of the window as well.
|
|
||||||
pub fn set_physical_cursor_position(&mut self, pos: Option<DVec2>) {
|
|
||||||
self.physical_cursor_position = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The window's occluded state (completely hidden from view).
|
|
||||||
///
|
|
||||||
/// This is different to window visibility as it depends on whether the window is
|
|
||||||
/// closed, minimised, set invisible, or fully occluded by another window.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS:** this is set to `false` in response to an applicationWillEnterForeground
|
|
||||||
/// callback which means the application should start preparing its data.
|
|
||||||
/// Its `true` in response to an applicationDidEnterBackground callback which means
|
|
||||||
/// the application should free resources (according to the iOS application lifecycle).
|
|
||||||
/// * **Web:** Doesn't take into account CSS border, padding, or transform.
|
|
||||||
/// * **Android / Wayland / Windows / Orbital:** Unsupported.
|
|
||||||
pub fn occluded(&self) -> bool {
|
|
||||||
self.occluded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The state of the window last time it was changed.
|
|
||||||
///
|
|
||||||
/// This is used in [`window_sync_system`] to see what fields of [`WindowOptions`] changed
|
|
||||||
/// when syncing the winit window with the component.
|
|
||||||
#[derive(Clone, Component)]
|
|
||||||
pub struct LastWindow {
|
|
||||||
pub last: WindowOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for LastWindow {
|
|
||||||
type Target = WindowOptions;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.last
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for LastWindow {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.last
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct WindowPlugin {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
create_options: WindowOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A system that syncs Winit Windows with [`WindowOptions`] components.
|
|
||||||
pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &WindowOptions, &mut LastWindow), Changed<WindowOptions>>) -> anyhow::Result<()> {
|
|
||||||
for (entity, opts, mut last) in view.iter() {
|
|
||||||
let window = windows.get_entity_window(entity)
|
|
||||||
.expect("entity's window is missing");
|
|
||||||
|
|
||||||
if opts.enabled_buttons != last.enabled_buttons {
|
|
||||||
window.set_enabled_buttons(opts.enabled_buttons);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.focused != last.focused && opts.focused {
|
|
||||||
window.focus_window();
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.fullscreen_mode != last.fullscreen_mode {
|
|
||||||
let monitor = window.primary_monitor().unwrap_or_else(|| {
|
|
||||||
let mut m = window.available_monitors();
|
|
||||||
m.next().expect("failed to find any available monitor")
|
|
||||||
});
|
|
||||||
|
|
||||||
window.set_fullscreen(opts.fullscreen_mode.as_winit_fullscreen(monitor, opts.physical_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.physical_size != last.physical_size {
|
|
||||||
let size = PhysicalSize::new(opts.physical_size.x, opts.physical_size.y);
|
|
||||||
if window.request_inner_size(size).is_some() {
|
|
||||||
error!("request to increase window size failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.decorated != last.decorated {
|
|
||||||
window.set_decorations(opts.decorated);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.maximized != last.maximized {
|
|
||||||
window.set_maximized(opts.maximized);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.minimized != last.minimized && opts.minimized.is_some() {
|
|
||||||
window.set_minimized(opts.minimized.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.visible != last.visible && opts.visible.is_some() {
|
|
||||||
window.set_visible(opts.visible.unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.position != last.position && opts.position.is_some() {
|
|
||||||
let pos = opts.position.unwrap();
|
|
||||||
let pos = PhysicalPosition::new(pos.x, pos.y);
|
|
||||||
window.set_outer_position(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.resize_increments != last.resize_increments {
|
|
||||||
window.set_resize_increments(opts.resize_increments);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.blur != last.blur {
|
|
||||||
window.set_blur(opts.blur);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.cursor.appearance != last.cursor.appearance {
|
|
||||||
match opts.cursor.appearance.clone() {
|
|
||||||
CursorAppearance::Icon(icon) => window.set_cursor(winit::window::Cursor::Icon(icon)),
|
|
||||||
CursorAppearance::Custom(custom) => window.set_cursor(winit::window::Cursor::Custom(custom)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.cursor.grab != last.cursor.grab {
|
|
||||||
if let Err(e) = window.set_cursor_grab(opts.cursor.grab) {
|
|
||||||
error!("could not set cursor grab mode: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.cursor.hittest != last.cursor.hittest {
|
|
||||||
if let Err(e) = window.set_cursor_hittest(opts.cursor.hittest) {
|
|
||||||
error!("could not set cursor hittest: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.cursor.visible != last.cursor.visible {
|
|
||||||
window.set_cursor_visible(opts.cursor.visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.ime_allowed != last.ime_allowed {
|
|
||||||
window.set_ime_allowed(opts.ime_allowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.physical_ime_cursor_area != last.physical_ime_cursor_area && opts.physical_ime_cursor_area.is_some() {
|
|
||||||
let area = opts.physical_ime_cursor_area.unwrap();
|
|
||||||
let pos = PhysicalPosition::new(area.position.x, area.position.y);
|
|
||||||
let size = PhysicalSize::new(area.size.x, area.size.y);
|
|
||||||
window.set_ime_cursor_area(pos, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.min_size != last.min_size {
|
|
||||||
let s = opts.min_size.map(|s| LogicalSize::new(s.x, s.y));
|
|
||||||
window.set_min_inner_size(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.max_size != last.max_size {
|
|
||||||
let s = opts.max_size.map(|s| LogicalSize::new(s.x, s.y));
|
|
||||||
window.set_max_inner_size(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.theme != last.theme {
|
|
||||||
window.set_theme(opts.theme);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.title != last.title {
|
|
||||||
window.set_title(&opts.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.transparent != last.transparent {
|
|
||||||
window.set_transparent(opts.transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare the resource version and uuid. These will get changed
|
|
||||||
// when the image is reloaded
|
|
||||||
let opts_icon = opts.window_icon.as_ref()
|
|
||||||
.map(|i| (i.version(), i.uuid()));
|
|
||||||
let last_icon = last.window_icon.as_ref()
|
|
||||||
.map(|i| (i.version(), i.uuid()));
|
|
||||||
if opts_icon != last_icon {
|
|
||||||
todo!("cannot set window icon yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.window_level != last.window_level {
|
|
||||||
window.set_window_level(opts.window_level);
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.physical_cursor_position != last.physical_cursor_position && opts.physical_cursor_position.is_some() {
|
|
||||||
let pos = opts.physical_cursor_position.unwrap();
|
|
||||||
let pos = PhysicalPosition::new(pos.x, pos.y);
|
|
||||||
if let Err(e) = window.set_cursor_position(pos) {
|
|
||||||
error!("failed to set cursor position: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
last.last = opts.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin for WindowPlugin {
|
|
||||||
fn setup(&mut self, app: &mut crate::game::App) {
|
|
||||||
app.with_system("window_sync", window_sync_system, &[]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
use lyra_math::Transform;
|
|
||||||
use lyra_resource::{optionally_add_to_dep, ResourceData, UntypedResHandle};
|
|
||||||
|
|
||||||
use super::Mesh;
|
|
||||||
use crate::ResHandle;
|
|
||||||
|
|
||||||
/// A Node in the Gltf file
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct GltfNode {
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub mesh: Option<ResHandle<Mesh>>,
|
|
||||||
pub transform: Transform,
|
|
||||||
pub children: Vec<GltfNode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourceData for GltfNode {
|
|
||||||
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
|
|
||||||
let mut deps: Vec<UntypedResHandle> = self.children.iter()
|
|
||||||
.flat_map(|c| c.mesh.as_ref().map(|h| h.untyped_clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
optionally_add_to_dep(&mut deps, &self.mesh);
|
|
||||||
|
|
||||||
deps
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
pub struct Area<P, S>
|
|
||||||
where
|
|
||||||
P: Clone + Copy + PartialEq,
|
|
||||||
S: Clone + Copy + PartialEq,
|
|
||||||
{
|
|
||||||
pub position: P,
|
|
||||||
pub size: S
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P, S> Area<P, S>
|
|
||||||
where
|
|
||||||
P: Clone + Copy + PartialEq,
|
|
||||||
S: Clone + Copy + PartialEq,
|
|
||||||
{
|
|
||||||
pub fn new(pos: P, size: S) -> Self {
|
|
||||||
Self {
|
|
||||||
position: pos,
|
|
||||||
size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
use lyra_math::Angle;
|
|
||||||
use lyra_reflect_derive::{impl_reflect_simple_struct, impl_reflect_trait_value};
|
|
||||||
|
|
||||||
use crate::{lyra_engine, Enum, Method, Reflect, ReflectMut, ReflectRef};
|
|
||||||
|
|
||||||
impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32));
|
|
||||||
impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32));
|
|
||||||
impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32));
|
|
||||||
impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32));
|
|
||||||
|
|
||||||
impl_reflect_simple_struct!(
|
|
||||||
lyra_math::Transform,
|
|
||||||
fields(
|
|
||||||
translation = lyra_math::Vec3,
|
|
||||||
rotation = lyra_math::Quat,
|
|
||||||
scale = lyra_math::Vec3
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
impl_reflect_trait_value!(lyra_math::Mat4);
|
|
||||||
|
|
||||||
impl Reflect for Angle {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
"Angle".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn type_id(&self) -> std::any::TypeId {
|
|
||||||
std::any::TypeId::of::<Self>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_boxed_any(self: Box<Self>) -> Box<dyn std::any::Any> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply(&mut self, val: &dyn Reflect) {
|
|
||||||
if let ReflectRef::Enum(e) = val.reflect_ref() {
|
|
||||||
let s = e.as_any().downcast_ref::<Self>()
|
|
||||||
.expect("cannot apply mismatched reflected enum");
|
|
||||||
*self = *s;
|
|
||||||
} else {
|
|
||||||
panic!("Provided value was not an enum!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_inner(&self) -> Box<dyn Reflect> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_ref(&self) -> crate::ReflectRef {
|
|
||||||
ReflectRef::Enum(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_mut(&mut self) -> crate::ReflectMut {
|
|
||||||
ReflectMut::Enum(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_val(&self) -> &dyn Reflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_val_mut(&mut self) -> &mut dyn Reflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Enum for Angle {
|
|
||||||
fn field(&self, _: &str) -> Option<&dyn Reflect> {
|
|
||||||
// no struct variants
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_mut(&mut self, _: &str) -> Option<&mut dyn Reflect> {
|
|
||||||
// no struct variants
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_at(&self, idx: usize) -> Option<&dyn Reflect> {
|
|
||||||
// all variants only have one tuple field
|
|
||||||
if idx != 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Angle::Degrees(v) => Some(v),
|
|
||||||
Angle::Radians(v) => Some(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_at_mut(&mut self, idx: usize) -> Option<&mut dyn Reflect> {
|
|
||||||
// all variants only have one tuple field
|
|
||||||
if idx != 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Angle::Degrees(v) => Some(v),
|
|
||||||
Angle::Radians(v) => Some(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_name_at(&self, _: usize) -> Option<String> {
|
|
||||||
// no struct variants
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_field(&self, _: &str) -> bool {
|
|
||||||
// no struct variants
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fields_len(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn variants_len(&self) -> usize {
|
|
||||||
2
|
|
||||||
}
|
|
||||||
|
|
||||||
fn variant_name(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Angle::Degrees(_) => "degrees".into(),
|
|
||||||
Angle::Radians(_) => "radians".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn variant_index(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Angle::Degrees(_) => 0,
|
|
||||||
Angle::Radians(_) => 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_variant_name(&self, name: &str) -> bool {
|
|
||||||
self.variant_name() == name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn variant_type(&self) -> crate::EnumType {
|
|
||||||
crate::EnumType::Tuple
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "lyra-resource"
|
|
||||||
version = "0.0.1"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
|
||||||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
|
||||||
lyra-math = { path = "../lyra-math" }
|
|
||||||
anyhow = "1.0.75"
|
|
||||||
base64 = "0.21.4"
|
|
||||||
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
|
|
||||||
glam = "0.29.0"
|
|
||||||
image = "0.25.2"
|
|
||||||
# not using custom matcher, or file type from file path
|
|
||||||
infer = { version = "0.15.0", default-features = false }
|
|
||||||
mime = "0.3.17"
|
|
||||||
notify = "6.1.1"
|
|
||||||
notify-debouncer-full = "0.3.1"
|
|
||||||
#notify = { version = "6.1.1", default-features = false, features = [ "fsevent-sys", "macos_fsevent" ]} # disables crossbeam-channel
|
|
||||||
percent-encoding = "2.3.0"
|
|
||||||
thiserror = "1.0.48"
|
|
||||||
tracing = "0.1.37"
|
|
||||||
uuid = { version = "1.4.1", features = ["v4"] }
|
|
||||||
instant = "0.1"
|
|
||||||
async-std = "1.12.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
rand = "0.8.5"
|
|
|
@ -1 +0,0 @@
|
||||||
use base64::Engine;
|
|
|
@ -1,143 +0,0 @@
|
||||||
use syn::{parenthesized, token, Token};
|
|
||||||
|
|
||||||
pub(crate) enum FieldType {
|
|
||||||
Unknown,
|
|
||||||
Type(syn::Path),
|
|
||||||
Wrapped(syn::Path),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FieldType {
|
|
||||||
pub fn is_unknown(&self) -> bool {
|
|
||||||
matches!(self, FieldType::Unknown)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_wrapped(&self) -> bool {
|
|
||||||
matches!(self, FieldType::Wrapped(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_type_path(&self) -> Option<&syn::Path> {
|
|
||||||
match self {
|
|
||||||
FieldType::Unknown => None,
|
|
||||||
FieldType::Type(path) => Some(path),
|
|
||||||
FieldType::Wrapped(path) => Some(path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct Field {
|
|
||||||
pub field: syn::Ident,
|
|
||||||
pub field_ty: FieldType,
|
|
||||||
pub skip_setter: bool,
|
|
||||||
pub setter: Option<syn::Block>,
|
|
||||||
pub getter: Option<syn::Block>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Field {
|
|
||||||
fn parse_extended(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let field_name = input.parse()?;
|
|
||||||
|
|
||||||
let fty = if input.peek(Token![:]) {
|
|
||||||
let _col: Token![:] = input.parse()?;
|
|
||||||
let s: syn::Path = input.parse()?;
|
|
||||||
let mut fty = FieldType::Type(s.clone());
|
|
||||||
|
|
||||||
if let Some(ident) = s.get_ident() {
|
|
||||||
if ident.to_string() == "wrap" {
|
|
||||||
let content;
|
|
||||||
let _parens: token::Paren = parenthesized!(content in input);
|
|
||||||
fty = FieldType::Wrapped(content.parse()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fty
|
|
||||||
} else {
|
|
||||||
FieldType::Unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut s = Self {
|
|
||||||
field: field_name,
|
|
||||||
field_ty: fty,
|
|
||||||
skip_setter: false,
|
|
||||||
setter: None,
|
|
||||||
getter: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
while input.peek(Token![,]) {
|
|
||||||
let _: Token![,] = input.parse()?;
|
|
||||||
|
|
||||||
if input.peek(syn::Ident) {
|
|
||||||
let ident: syn::Ident = input.parse()?;
|
|
||||||
let ident_str = ident.to_string();
|
|
||||||
let ident_str = ident_str.as_str();
|
|
||||||
|
|
||||||
match ident_str {
|
|
||||||
"skip_set" => {
|
|
||||||
s.skip_setter = true;
|
|
||||||
}
|
|
||||||
"set" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
|
|
||||||
s.setter = Some(input.parse()?);
|
|
||||||
}
|
|
||||||
"get" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
|
|
||||||
s.getter = Some(input.parse()?);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(syn::Error::new_spanned(ident, "unknown wrapper command"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s.getter.is_some() || s.setter.is_some()) && s.field_ty.is_wrapped() {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
input.span(),
|
|
||||||
"cannot specify custom getter or setter \
|
|
||||||
with wrapped type",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl syn::parse::Parse for Field {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
if input.peek(token::Paren) {
|
|
||||||
let content;
|
|
||||||
let _parens: token::Paren = parenthesized!(content in input);
|
|
||||||
|
|
||||||
Self::parse_extended(&content)
|
|
||||||
} else {
|
|
||||||
let field_name = input.parse()?;
|
|
||||||
|
|
||||||
let fty = if input.peek(Token![:]) {
|
|
||||||
let _col: Token![:] = input.parse()?;
|
|
||||||
let s: syn::Path = input.parse()?;
|
|
||||||
let mut fty = FieldType::Type(s.clone());
|
|
||||||
|
|
||||||
if let Some(ident) = s.get_ident() {
|
|
||||||
if ident.to_string() == "wrap" {
|
|
||||||
let content;
|
|
||||||
let _parens: token::Paren = parenthesized!(content in input);
|
|
||||||
fty = FieldType::Wrapped(content.parse()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fty
|
|
||||||
} else {
|
|
||||||
FieldType::Unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
field: field_name,
|
|
||||||
field_ty: fty,
|
|
||||||
skip_setter: false,
|
|
||||||
setter: None,
|
|
||||||
getter: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,362 +0,0 @@
|
||||||
use proc_macro2::Span;
|
|
||||||
use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Token};
|
|
||||||
use quote::{quote, ToTokens};
|
|
||||||
use crate::{field::Field, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE};
|
|
||||||
|
|
||||||
fn field_table_setter(field: &Field) -> proc_macro2::TokenStream {
|
|
||||||
let ident = &field.field;
|
|
||||||
|
|
||||||
match &field.setter {
|
|
||||||
Some(set) => {
|
|
||||||
quote! {
|
|
||||||
table.set(stringify!(#ident), #set)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
let ty = field.field_ty.get_type_path()
|
|
||||||
.expect("no field type specified");
|
|
||||||
|
|
||||||
let arg = if field.field_ty.is_wrapped() {
|
|
||||||
quote!(#ty(self.#ident))
|
|
||||||
} else { quote!(self.#ident) };
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
table.set(stringify!(#ident), #arg)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_table_getter(field: &Field) -> proc_macro2::TokenStream {
|
|
||||||
let ident = &field.field;
|
|
||||||
|
|
||||||
match &field.getter {
|
|
||||||
Some(get) => {
|
|
||||||
quote! {
|
|
||||||
let #ident = #get;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
let ty = field.field_ty.get_type_path()
|
|
||||||
.expect("no field type specified");
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
let #ident: #ty = table.get(stringify!(#ident))?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrapper_creation(wrapper: &syn::Ident, type_path: &syn::Path, struct_type: StructType, create: Option<&syn::Block>, fields: &Vec<Field>) -> proc_macro2::TokenStream {
|
|
||||||
|
|
||||||
match create {
|
|
||||||
Some(b) => quote!(#b),
|
|
||||||
None => {
|
|
||||||
/* let field_iter = fields.iter().map(|f| match &f.field_ty {
|
|
||||||
crate::field::FieldType::Type(path) => quote!(#path),
|
|
||||||
crate::field::FieldType::Wrapped(path) => quote!(*#path),
|
|
||||||
_ => todo!()
|
|
||||||
}); */
|
|
||||||
let field_iter = fields.iter().map(|f| {
|
|
||||||
let ident = &f.field;
|
|
||||||
if f.field_ty.is_wrapped() && struct_type == StructType::Fields {
|
|
||||||
quote!(#ident: (*#ident).clone())
|
|
||||||
} else {
|
|
||||||
quote!(#ident)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
match struct_type {
|
|
||||||
StructType::Fields => {
|
|
||||||
quote! {
|
|
||||||
#wrapper(#type_path {
|
|
||||||
#(
|
|
||||||
#field_iter
|
|
||||||
),*
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
StructType::Tuple => {
|
|
||||||
quote! {
|
|
||||||
#wrapper(#type_path( #(#field_iter),* ))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_reflect_lua_functions(ty: &ReflectType, type_path: &syn::Path, set_data: bool) -> proc_macro2::TokenStream {
|
|
||||||
let data = if set_data {
|
|
||||||
quote!(Some(this.into_wrapped()))
|
|
||||||
} else { quote!(None) };
|
|
||||||
|
|
||||||
match ty {
|
|
||||||
ReflectType::Component => {
|
|
||||||
quote! {
|
|
||||||
Ok(ScriptBorrow::from_component::<#type_path>(#data))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ReflectType::Resource => {
|
|
||||||
quote! {
|
|
||||||
Ok(ScriptBorrow::from_component::<#type_path>(#data))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum ReflectType {
|
|
||||||
//Unknown,
|
|
||||||
Component,
|
|
||||||
Resource,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The type of the wrapping struct
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum StructType {
|
|
||||||
#[default]
|
|
||||||
Fields,
|
|
||||||
Tuple,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IntoLuaUsage {
|
|
||||||
type_path: syn::Path,
|
|
||||||
struct_type: StructType,
|
|
||||||
override_name: Option<syn::Ident>,
|
|
||||||
table_name: String,
|
|
||||||
derives: Vec<syn::Ident>,
|
|
||||||
fields: Vec<Field>,
|
|
||||||
create: Option<syn::Block>,
|
|
||||||
reflection_type: Option<ReflectType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl syn::parse::Parse for IntoLuaUsage {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let type_path: syn::Path = input.parse()?;
|
|
||||||
let type_ident = &type_path
|
|
||||||
.segments
|
|
||||||
.last()
|
|
||||||
.expect("Failure to find typename in macro usage!")
|
|
||||||
.ident;
|
|
||||||
let lua_name = type_ident.to_string();
|
|
||||||
|
|
||||||
let mut s = Self {
|
|
||||||
type_path,
|
|
||||||
struct_type: StructType::Fields,
|
|
||||||
override_name: None,
|
|
||||||
table_name: lua_name,
|
|
||||||
derives: vec![],
|
|
||||||
fields: vec![],
|
|
||||||
create: None,
|
|
||||||
reflection_type: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
while input.peek(Token![,]) {
|
|
||||||
let _: Token![,] = input.parse()?;
|
|
||||||
|
|
||||||
if input.peek(syn::Ident) {
|
|
||||||
let ident: syn::Ident = input.parse()?;
|
|
||||||
let ident_str = ident.to_string();
|
|
||||||
let ident_str = ident_str.as_str();
|
|
||||||
|
|
||||||
match ident_str {
|
|
||||||
"name" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
|
|
||||||
let name: syn::Ident = input.parse()?;
|
|
||||||
s.override_name = Some(name);
|
|
||||||
},
|
|
||||||
"struct_type" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
|
|
||||||
let st_token = input.parse::<syn::LitStr>()?;
|
|
||||||
let st_str = st_token.value().to_lowercase();
|
|
||||||
let st_str = st_str.as_str();
|
|
||||||
|
|
||||||
let st = match st_str {
|
|
||||||
"fields" => StructType::Fields,
|
|
||||||
"tuple" => StructType::Tuple,
|
|
||||||
_ => return Err(syn::Error::new_spanned(
|
|
||||||
st_token,
|
|
||||||
format!("unknown struct type: '{}', expected 'fields', or `tuple`", st_str),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
s.struct_type = st;
|
|
||||||
},
|
|
||||||
"lua_name" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
s.table_name = input.parse::<syn::LitStr>()?.value();
|
|
||||||
},
|
|
||||||
"derives" => {
|
|
||||||
if input.peek(token::Paren) {
|
|
||||||
let content;
|
|
||||||
let _parens: token::Paren = parenthesized!(content in input);
|
|
||||||
|
|
||||||
let derives: Punctuated<syn::Ident, Token![,]> =
|
|
||||||
content.parse_terminated(syn::Ident::parse, Token![,])?;
|
|
||||||
s.derives = derives.into_iter().collect();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fields" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
|
|
||||||
if input.peek(token::Brace) {
|
|
||||||
let content;
|
|
||||||
let _braced: token::Brace = braced!(content in input);
|
|
||||||
|
|
||||||
let terminated = content.parse_terminated(Field::parse, Token![,])?;
|
|
||||||
s.fields.extend(terminated.into_iter());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"create" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
s.create = Some(input.parse()?);
|
|
||||||
},
|
|
||||||
"reflect" => {
|
|
||||||
let _eq: Token![=] = input.parse()?;
|
|
||||||
let ty: syn::Ident = input.parse()?;
|
|
||||||
let ty_str = ty.to_string();
|
|
||||||
let ty_str = ty_str.as_str();
|
|
||||||
|
|
||||||
let ty = match ty_str {
|
|
||||||
"component" => ReflectType::Component,
|
|
||||||
"resource" => ReflectType::Resource,
|
|
||||||
_ => return Err(syn::Error::new_spanned(
|
|
||||||
ident,
|
|
||||||
format!("unknown wrapper type: '{}', expected 'component' or 'resource'", ty_str),
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
s.reflection_type = Some(ty);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
ident,
|
|
||||||
format!("unknown wrapper command: '{}'", ident_str),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.reflection_type.is_none() {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
input.span(),
|
|
||||||
format!("Wrapper type not specified! Expected 'type=component' or 'type=resource'"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.table_name.is_empty() {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
input.span(),
|
|
||||||
format!("No lua table specified. Use 'lua_name=\"Camera\"'"),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
||||||
let input = parse_macro_input!(input as IntoLuaUsage);
|
|
||||||
|
|
||||||
// unwrap is fine since `Some` is ensured in parse impl
|
|
||||||
let reflect_type = input.reflection_type.as_ref().unwrap();
|
|
||||||
let type_path = &input.type_path;
|
|
||||||
let type_name = &type_path
|
|
||||||
.segments
|
|
||||||
.last()
|
|
||||||
.expect("Failure to find typename in macro usage!")
|
|
||||||
.ident;
|
|
||||||
let wrapper = input.override_name
|
|
||||||
.unwrap_or_else(|| syn::Ident::new(&format!("Lua{}", type_name), Span::call_site()));
|
|
||||||
|
|
||||||
let derives_iter = input.derives.into_iter();
|
|
||||||
|
|
||||||
let lua_name = &input.table_name;
|
|
||||||
let field_getters_iter = input.fields.iter().map(|f| field_table_getter(f));
|
|
||||||
let field_setters_iter = input.fields.iter().map(|f| field_table_setter(f));
|
|
||||||
let struct_creator = wrapper_creation(&wrapper, type_path, input.struct_type, input.create.as_ref(), &input.fields);
|
|
||||||
let reflect_fn = get_reflect_lua_functions(reflect_type, &input.type_path, true);
|
|
||||||
let reflect_type_fn = get_reflect_lua_functions(reflect_type, &input.type_path, false);
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[derive(Clone, #(#derives_iter),*)]
|
|
||||||
pub struct #wrapper(pub(crate) #type_path);
|
|
||||||
|
|
||||||
impl std::ops::Deref for #wrapper {
|
|
||||||
type Target = #type_path;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::DerefMut for #wrapper {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for #wrapper {
|
|
||||||
fn from_lua(val: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let ty = val.type_name();
|
|
||||||
let table = val.as_table().ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: ty,
|
|
||||||
to: "Table".into(),
|
|
||||||
message: Some("expected Table".into()),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
#(
|
|
||||||
#field_getters_iter
|
|
||||||
)*
|
|
||||||
|
|
||||||
Ok(#struct_creator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::IntoLua for #wrapper {
|
|
||||||
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
|
||||||
let table = lua.create_table()?;
|
|
||||||
#(
|
|
||||||
#field_setters_iter
|
|
||||||
)*
|
|
||||||
|
|
||||||
table.set(
|
|
||||||
#FN_NAME_INTERNAL_REFLECT,
|
|
||||||
lua.create_function(|_, this: Self| {
|
|
||||||
#reflect_fn
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
table.set(
|
|
||||||
#FN_NAME_INTERNAL_REFLECT_TYPE,
|
|
||||||
lua.create_function(|_, ()| {
|
|
||||||
#reflect_type_fn
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
table.set(mlua::MetaMethod::Type.name(), #lua_name)?;
|
|
||||||
|
|
||||||
Ok(mlua::Value::Table(table))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaWrapper for #wrapper {
|
|
||||||
type Wrap = #type_path;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn wrapped_type_id() -> std::any::TypeId {
|
|
||||||
std::any::TypeId::of::<#type_path>()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn into_wrapped(self) -> Self::Wrap {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}.into_token_stream().into()
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
---Create a Resource query that will return the specific ECS world resource.
|
|
||||||
---
|
|
||||||
---@see ResQuery
|
|
||||||
---@param resource table|userdata
|
|
||||||
---@return ResQuery
|
|
||||||
function Res(resource)
|
|
||||||
return ResQuery.new(resource)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@alias Query function|table|userdata
|
|
||||||
|
|
||||||
---Create a `ChangedQuery` query that will return only if the resource or component has changed
|
|
||||||
---since last tick.
|
|
||||||
---
|
|
||||||
---@see ChangedQuery
|
|
||||||
---@param val table|userdata
|
|
||||||
---@return ChangedQuery
|
|
||||||
function Changed(val)
|
|
||||||
return ChangedQuery.new(val)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Create a `HasQuery` filter that will return only if the entity has a specific component.
|
|
||||||
---
|
|
||||||
---@see HasQuery
|
|
||||||
---@param val table|userdata
|
|
||||||
---@return HasQuery
|
|
||||||
function Has(val)
|
|
||||||
return HasQuery.new(val)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Create a `NotQuery` filter that will allow results if the query returns nothing or
|
|
||||||
---filter denies.
|
|
||||||
---
|
|
||||||
---@see NotQuery
|
|
||||||
---@param val Query
|
|
||||||
---@return NotQuery
|
|
||||||
function Not(val)
|
|
||||||
return NotQuery.new(val)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Create a `AnyQuery` filter that will allow results if any of the queries return something.
|
|
||||||
---
|
|
||||||
---The queries are evaluated in the order they were provided.
|
|
||||||
---
|
|
||||||
---@see AnyQuery
|
|
||||||
---@param ... Query
|
|
||||||
---@return AnyQuery
|
|
||||||
function Any(...)
|
|
||||||
return AnyQuery.new(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Create a `TickOfQuery` for retrieving the tick of the resource or component on the entity.
|
|
||||||
---
|
|
||||||
---@see TickOfQuery
|
|
||||||
---@param ... table|userdata
|
|
||||||
---@return TickOfQuery
|
|
||||||
function TickOf(...)
|
|
||||||
return TickOfQuery.new(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
---Create any `OptionalQuery` that allows for a query to return nothing.
|
|
||||||
---
|
|
||||||
---If the query is a filter, its result will essentially be ignored. If the query returns `None`
|
|
||||||
---or `AlwaysNone`, this query will return `Nil`. If the query results in a value, its value
|
|
||||||
---will be the result of this query.
|
|
||||||
---
|
|
||||||
---@see OptionalQuery
|
|
||||||
---@param q Query
|
|
||||||
---@return OptionalQuery
|
|
||||||
function Optional(q)
|
|
||||||
return OptionalQuery.new(q)
|
|
||||||
end
|
|
|
@ -1,94 +0,0 @@
|
||||||
---@enum WindowMode
|
|
||||||
WindowMode = {
|
|
||||||
WNDOWED = "windowed",
|
|
||||||
BORDERLESS_FULLSCREEN = "borderless_fullscreen",
|
|
||||||
SIZED_FULLSCREEN = "sized_fullscreen",
|
|
||||||
FULLSCREEN = "fullscreen",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum CursorGrabMode
|
|
||||||
CursorGrabMode = {
|
|
||||||
NONE = "none",
|
|
||||||
CONFINED = "confined",
|
|
||||||
LOCKED = "locked",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum WindowTheme
|
|
||||||
WindowTheme = {
|
|
||||||
LIGHT = "light",
|
|
||||||
DARK = "dark",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum WindowLevel
|
|
||||||
WindowLevel = {
|
|
||||||
ALWAYS_ON_BOTTOM = "always_on_bottom",
|
|
||||||
NORMAL = "normal",
|
|
||||||
ALWAYS_ON_TOP = "always_on_top",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum HandleState
|
|
||||||
HandleState = {
|
|
||||||
LOADING = "loading",
|
|
||||||
READY = "ready",
|
|
||||||
ERROR = "error",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum ActionKind
|
|
||||||
ActionKind = {
|
|
||||||
BUTTON = "button",
|
|
||||||
AXIS = "axis",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum ActionState
|
|
||||||
ActionState = {
|
|
||||||
IDLE = "idle",
|
|
||||||
PRESSED = "pressed",
|
|
||||||
JUST_PRESSED = "just_pressed",
|
|
||||||
JUST_RELEASED = "just_released",
|
|
||||||
AXIS = "axis",
|
|
||||||
OTHER = "other",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum FilterMode
|
|
||||||
FilterMode = {
|
|
||||||
NEAREST = "nearest",
|
|
||||||
LINEAR = "linear",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum WrappingMode
|
|
||||||
WrappingMode = {
|
|
||||||
CLAMP_TO_EDGE = "clamp_to_edge",
|
|
||||||
MIRRORED_REPEAT = "mirrored_repeat",
|
|
||||||
REPEAT = "repeat",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum CameraProjectionMode
|
|
||||||
CameraProjectionMode = {
|
|
||||||
PERSPECTIVE = "perspective",
|
|
||||||
ORTHOGRAPHIC = "orthographic",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum DeviceEventKind
|
|
||||||
DeviceEventKind = {
|
|
||||||
ADDED = "added",
|
|
||||||
REMOVED = "removed",
|
|
||||||
MOUSE_MOTION = "mouse_motion",
|
|
||||||
MOUSE_WHEEL = "mouse_wheel",
|
|
||||||
MOTION = "motion",
|
|
||||||
BUTTON = "button",
|
|
||||||
KEY = "key",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum NativeKeyCodeKind
|
|
||||||
NativeKeyCodeKind = {
|
|
||||||
ANDROID = "android",
|
|
||||||
MACOS = "macos",
|
|
||||||
WINDOWS = "windows",
|
|
||||||
XKB = "xkb",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum ElementState
|
|
||||||
ElementState = {
|
|
||||||
PRESSED = "pressed",
|
|
||||||
RELEASED = "released",
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class GltfHandle: Handle
|
|
||||||
---
|
|
||||||
---A handle to a GLTF asset.
|
|
||||||
GltfHandle = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
---Get a list of scenes in the GLTF file.
|
|
||||||
---
|
|
||||||
---@return SceneHandle[]
|
|
||||||
function GltfHandle:scenes() end
|
|
||||||
|
|
||||||
---Get a list of materials in the GLTF file.
|
|
||||||
---
|
|
||||||
---@return MaterialHandle[]
|
|
||||||
function GltfHandle:materials() end
|
|
||||||
|
|
||||||
---Get a list of meshes in the GLTF file.
|
|
||||||
---
|
|
||||||
---@return MeshHandle[]
|
|
||||||
function GltfHandle:meshes() end
|
|
|
@ -1,43 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Handle: userdata
|
|
||||||
---
|
|
||||||
---A handle to an asset. Assets are loaded asynchronously, so you cannot immediately
|
|
||||||
---use them after you request them from the World.
|
|
||||||
Handle = {
|
|
||||||
---The path the asset was loaded from.
|
|
||||||
---
|
|
||||||
---@type string
|
|
||||||
path = nil,
|
|
||||||
|
|
||||||
---The version of the resource.
|
|
||||||
---
|
|
||||||
---Increments every time a resource is loaded.
|
|
||||||
---
|
|
||||||
---@type number
|
|
||||||
version = nil,
|
|
||||||
|
|
||||||
---The unique id of the resource.
|
|
||||||
---
|
|
||||||
---This is not changed for the entire lifetime of the handle, it does not change
|
|
||||||
---when an asset is reloaded.
|
|
||||||
---
|
|
||||||
---@type string
|
|
||||||
uuid = nil,
|
|
||||||
|
|
||||||
---Current state of the asset handle.
|
|
||||||
---@type HandleState
|
|
||||||
state = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---Returns true if the asset is watched for auto-reloading.
|
|
||||||
---
|
|
||||||
---@return boolean
|
|
||||||
function Handle:is_watched() end
|
|
||||||
|
|
||||||
---Returns true if the asset is loaded.
|
|
||||||
---@return boolean
|
|
||||||
function Handle:is_loaded() end
|
|
||||||
|
|
||||||
---Blocks execution until the asset and its dependencies are loaded.
|
|
||||||
function Handle:wait_until_loaded() end
|
|
|
@ -1,102 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class MaterialHandle: Handle
|
|
||||||
---
|
|
||||||
---A handle to a material asset in a GLTF file.
|
|
||||||
MaterialHandle = {
|
|
||||||
---The unique id of the GPU shader.
|
|
||||||
---@type number?
|
|
||||||
shader_uuid = nil,
|
|
||||||
|
|
||||||
---The name of the material from GLTF.
|
|
||||||
---@type string?
|
|
||||||
name = nil,
|
|
||||||
|
|
||||||
---@type boolean
|
|
||||||
double_sided = nil,
|
|
||||||
|
|
||||||
---The RGBA base color of the model.
|
|
||||||
---
|
|
||||||
---If a texture is supplied with `base_color_texture`, this value will tint the
|
|
||||||
---texture. If a texture is not provided, this value would be the color of
|
|
||||||
---the Material.
|
|
||||||
---
|
|
||||||
---@type Vec4
|
|
||||||
base_color = nil,
|
|
||||||
|
|
||||||
---The metalness of the material
|
|
||||||
---
|
|
||||||
---From 0.0 (non-metal) to 1.0 (metal).
|
|
||||||
---
|
|
||||||
---@type number
|
|
||||||
metallic = nil,
|
|
||||||
|
|
||||||
---The roughness of the material
|
|
||||||
---
|
|
||||||
---From 0.0 (smooth) to 1.0 (rough)
|
|
||||||
---
|
|
||||||
---@type number
|
|
||||||
roughness = nil,
|
|
||||||
|
|
||||||
---The base color texture of the model.
|
|
||||||
---@type TextureHandle?
|
|
||||||
base_color_texture = nil,
|
|
||||||
|
|
||||||
---The metallic-roughness texture.
|
|
||||||
---
|
|
||||||
---The metalness values are sampled from the B channel. The roughness values are sampled from
|
|
||||||
---the G channel. These values are linear. If other channels are present (R or A), they are
|
|
||||||
---ignored for metallic-roughness calculations.
|
|
||||||
---@type TextureHandle?
|
|
||||||
metallic_roughness_texture = nil,
|
|
||||||
|
|
||||||
---A set of parameter values that are used to define the specular-glossiness material model
|
|
||||||
---from Physically-Based Rendering (PBR) methodology.
|
|
||||||
---GLTF extension: [KHR_materials_pbrSpecularGlossiness](https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness)
|
|
||||||
---@type TextureHandle?
|
|
||||||
pbr_glossiness = nil,
|
|
||||||
|
|
||||||
---The optional alpha cutoff value of the material.
|
|
||||||
---
|
|
||||||
---This will be used instead of the renderer's default.
|
|
||||||
---
|
|
||||||
---@type number?
|
|
||||||
alpha_cutoff = nil,
|
|
||||||
|
|
||||||
---The alpha rendering mode of the material.
|
|
||||||
---
|
|
||||||
---The material's alpha rendering
|
|
||||||
---mode enumeration specifying the interpretation of the alpha value of the main
|
|
||||||
---factor and texture.
|
|
||||||
---
|
|
||||||
---* In `Opaque` mode (default) the alpha value is ignored
|
|
||||||
--- and the rendered output is fully opaque.
|
|
||||||
---* In `Mask` mode, the rendered
|
|
||||||
--- output is either fully opaque or fully transparent depending on the alpha
|
|
||||||
--- value and the specified alpha cutoff value.
|
|
||||||
---* In `Blend` mode, the alpha value is used to composite the source and
|
|
||||||
--- destination areas and the rendered output is combined with the background
|
|
||||||
--- using the normal painting operation (i.e. the Porter and Duff over
|
|
||||||
--- operator).
|
|
||||||
---
|
|
||||||
---@type AlphaMode
|
|
||||||
alpha_mode = nil,
|
|
||||||
|
|
||||||
---@type Specular?
|
|
||||||
specular = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum AlphaMode
|
|
||||||
AlphaMode = {
|
|
||||||
OPAQUE = "opaque",
|
|
||||||
MASK = "mask",
|
|
||||||
BLEND = "blend",
|
|
||||||
}
|
|
||||||
|
|
||||||
---@class PbrGlossiness
|
|
||||||
---TODO: implement
|
|
||||||
PbrGlossiness = {}
|
|
||||||
|
|
||||||
---@class Specular
|
|
||||||
---TODO: implement
|
|
||||||
Specular = {}
|
|
|
@ -1,14 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class MeshHandle: Handle
|
|
||||||
---
|
|
||||||
---A handle to a mesh in a GLTF file.
|
|
||||||
MeshHandle = {
|
|
||||||
---The material of the mesh
|
|
||||||
---@type MaterialHandle
|
|
||||||
material = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---Get the indices in the mesh.
|
|
||||||
---@return number[]
|
|
||||||
function MeshHandle:indices() end
|
|
|
@ -1,9 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class SceneHandle: Handle
|
|
||||||
---
|
|
||||||
---A handle to a scene asset in a GLTF file.
|
|
||||||
SceneHandle = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class TextureHandle
|
|
||||||
TextureHandle = {}
|
|
|
@ -1,125 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class ActionHandler: userdata
|
|
||||||
ActionHandler = {}
|
|
||||||
|
|
||||||
--- Create a new `ActionHandler`.
|
|
||||||
---
|
|
||||||
--- ```lua
|
|
||||||
--- local handler = ActionHandler.new {
|
|
||||||
--- -- A list of layout IDs
|
|
||||||
--- layouts = { 0 },
|
|
||||||
--- actions = {
|
|
||||||
--- -- A list of action names and the `ActionKind`s.
|
|
||||||
--- -- Actions can be buttons or axes.
|
|
||||||
--- MoveForwardBackward = ActionKind.AXIS,
|
|
||||||
--- MoveLeftRight = ActionKind.AXIS,
|
|
||||||
--- MoveUpDown = ActionKind.AXIS,
|
|
||||||
--- LookLeftRight = ActionKind.AXIS,
|
|
||||||
--- LookUpDown = ActionKind.AXIS,
|
|
||||||
--- LookRoll = ActionKind.AXIS,
|
|
||||||
--- ObjectsMoveUpDown = ActionKind.AXIS
|
|
||||||
--- },
|
|
||||||
--- mappings = {
|
|
||||||
--- -- Each entry here is a mapping of actions for a layout.
|
|
||||||
--- -- This can be used so that when the current layout is changed,
|
|
||||||
--- -- the mapping would also change.
|
|
||||||
--- {
|
|
||||||
--- -- Specify the layout id that this mapping is for.
|
|
||||||
--- layout = 0,
|
|
||||||
--- binds = {
|
|
||||||
--- -- This is an Action bind. A bind is used to bind an input to an action.
|
|
||||||
--- -- These actions are defined above in "actions".
|
|
||||||
--- MoveForwardBackward = {
|
|
||||||
--- -- This is how you bind a button. In this case the button is a key.
|
|
||||||
--- -- "key" is the device the bind comes from, then after the colon is the
|
|
||||||
--- -- input name, in this case a specific key. We specify a modifier to the bind
|
|
||||||
--- -- after the equal sign.
|
|
||||||
--- "key:w=1.0",
|
|
||||||
--- "key:s=-1.0"
|
|
||||||
--- },
|
|
||||||
--- MoveLeftRight = {
|
|
||||||
--- "key:a=-1.0", "key:d=1.0"
|
|
||||||
--- },
|
|
||||||
--- MoveUpDown = {
|
|
||||||
--- "key:c=1.0", "key:z=-1.0"
|
|
||||||
--- },
|
|
||||||
--- LookLeftRight = {
|
|
||||||
--- "key:left=-1.0", "key:right=1.0",
|
|
||||||
--- -- Here is a bind to an axis.
|
|
||||||
--- -- We use "mouse", for the device the bind is from, then "axis" to specify
|
|
||||||
--- -- that we want to bind a specific axis, then we use the name of the axis,
|
|
||||||
--- -- in this case "x".
|
|
||||||
--- -- So this binds to the x axis of the mouse.
|
|
||||||
--- "mouse:axis:x"
|
|
||||||
--- },
|
|
||||||
--- LookUpDown = {
|
|
||||||
--- "key:up=-1.0", "key:down=1.0",
|
|
||||||
--- -- Here we bind to the y axis of the mouse.
|
|
||||||
--- "mouse:axis:y",
|
|
||||||
--- },
|
|
||||||
--- LookRoll = {
|
|
||||||
--- "key:e=-1.0", "key:q=1.0",
|
|
||||||
--- },
|
|
||||||
--- ObjectsMoveUpDown = {
|
|
||||||
--- "key:u=1.0", "key:j=-1.0"
|
|
||||||
--- }
|
|
||||||
--- }
|
|
||||||
--- }
|
|
||||||
--- }
|
|
||||||
--- }
|
|
||||||
---
|
|
||||||
--- -- Add the handler to the world so the host will process it.
|
|
||||||
--- world:add_resource(handler)
|
|
||||||
--- ```
|
|
||||||
---
|
|
||||||
---@param table table See above example to see the format of this table.
|
|
||||||
function ActionHandler.new(table) end
|
|
||||||
|
|
||||||
---Returns the action's modifier if its an updated axis.
|
|
||||||
---
|
|
||||||
---Returns `nil` if the action's state is not `ActionState::Axis`, or if the
|
|
||||||
---action was not found.
|
|
||||||
---@param action string
|
|
||||||
---@return number?
|
|
||||||
function ActionHandler:get_axis(action) end
|
|
||||||
|
|
||||||
---Returns true if the action is pressed (or was just pressed).
|
|
||||||
---
|
|
||||||
---Returns `nil` if the action's was not found.
|
|
||||||
---@param action string
|
|
||||||
---@return boolean?
|
|
||||||
function ActionHandler:is_pressed(action) end
|
|
||||||
|
|
||||||
---Returns true if the action was **just** pressed.
|
|
||||||
---
|
|
||||||
---Returns `nil` if the action was not found
|
|
||||||
---
|
|
||||||
---@param action string
|
|
||||||
---@return boolean?
|
|
||||||
function ActionHandler:was_just_pressed(action) end
|
|
||||||
|
|
||||||
---Returns true if the action was just released.
|
|
||||||
---
|
|
||||||
---Returns `nil` if the action was not found
|
|
||||||
---
|
|
||||||
---@param action string
|
|
||||||
---@return boolean?
|
|
||||||
function ActionHandler:was_just_released(action) end
|
|
||||||
|
|
||||||
---Returns the action's modifier if it was just pressed.
|
|
||||||
---
|
|
||||||
---Returns `nil` if the action's state is not `ActionState.JUST_PRESSED`,
|
|
||||||
---or if the action was not found.
|
|
||||||
---
|
|
||||||
---@param action string
|
|
||||||
---@return number?
|
|
||||||
function ActionHandler:get_just_pressed(action) end
|
|
||||||
|
|
||||||
---Returns the current state of the action.
|
|
||||||
---
|
|
||||||
---The first element in the returned tuple is the state enum, and the second
|
|
||||||
---is the state modifier. The modifer will be `nil` if the state is "idle"
|
|
||||||
---
|
|
||||||
---@return [ActionState, number?]
|
|
||||||
function ActionHandler:get_action_state(action) end
|
|
|
@ -1,18 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Camera: userdata
|
|
||||||
Camera = {
|
|
||||||
---The position of the camera
|
|
||||||
---@type Transform
|
|
||||||
transform = nil,
|
|
||||||
---The field of view of the camera
|
|
||||||
---@type Angle
|
|
||||||
fov = nil,
|
|
||||||
---The projection mode the camera.
|
|
||||||
---Can be used to specify if the camera is 2D (orthographic), or 3D (perspective).
|
|
||||||
---@type CameraProjectionMode
|
|
||||||
mode = nil,
|
|
||||||
---Flag to enable some debug rendering stuff.
|
|
||||||
---@type boolean
|
|
||||||
debug = nil,
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class DeltaTime: userdata
|
|
||||||
---
|
|
||||||
---DeltaTime is an ECS world resource. When its requested from the world, a `number`
|
|
||||||
---is returned.
|
|
||||||
---
|
|
||||||
---Example:
|
|
||||||
---```lua
|
|
||||||
------@type number
|
|
||||||
---local dt = world:resource(DeltaTime)
|
|
||||||
---
|
|
||||||
---print(type(dt)) --> number
|
|
||||||
---```
|
|
||||||
DeltaTime = {}
|
|
|
@ -1,29 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An entity handle.
|
|
||||||
---@class Entity: userdata
|
|
||||||
Entity = {}
|
|
||||||
|
|
||||||
---Get the id of the Entity.
|
|
||||||
---@return number
|
|
||||||
function Entity:id() end
|
|
||||||
|
|
||||||
---Get the generation number of the Entity.
|
|
||||||
---
|
|
||||||
---Entity handles are reused by the ECS World, the generation is used to tell reused Entity
|
|
||||||
---id's apart from previous generations.
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function Entity:generation() end
|
|
||||||
|
|
||||||
---A reference to an entity in the world.
|
|
||||||
---
|
|
||||||
---Can be used to insert and update components on the entity.
|
|
||||||
---
|
|
||||||
---@class EntityRef: userdata
|
|
||||||
EntityRef = {}
|
|
||||||
|
|
||||||
---Update components that are **already** on an Entity.
|
|
||||||
---
|
|
||||||
---@param ... any The components to update on the entity.
|
|
||||||
function EntityRef:update(...) end
|
|
|
@ -1,9 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class EventReader<T>: userdata
|
|
||||||
EventReader = {}
|
|
||||||
|
|
||||||
---Get an iterator for reading the event.
|
|
||||||
---@generic T
|
|
||||||
---@return fun(): T? iterator An iterator for reading the events.
|
|
||||||
function EventReader:read() end
|
|
|
@ -1,17 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class FreeFlyCamera: userdata
|
|
||||||
FreeFlyCamera = {
|
|
||||||
---Movement speed of the camera.
|
|
||||||
---@type number
|
|
||||||
speed = nil,
|
|
||||||
---The speed of the camera rotation.
|
|
||||||
---@type number
|
|
||||||
look_speed = nil,
|
|
||||||
---The sensitivity of the mouse when looking.
|
|
||||||
---
|
|
||||||
---This is additional to `look_speed`, but onyl applied to mouse movement.
|
|
||||||
---
|
|
||||||
---@type number
|
|
||||||
mouse_sensitivity = nil,
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
require "action_handler"
|
|
||||||
require "camera"
|
|
||||||
require "delta_time"
|
|
||||||
require "entity"
|
|
||||||
require "event_reader"
|
|
||||||
require "free_fly_camera"
|
|
||||||
require "window"
|
|
||||||
require "world_transform"
|
|
||||||
require "world"
|
|
|
@ -1,25 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An ECS filter that will return if any of the provided queries return.
|
|
||||||
---
|
|
||||||
---The queries are evaluated in the order they were provided. When a query or filter returns a value,
|
|
||||||
---that value will be returned.
|
|
||||||
---
|
|
||||||
---Use the utility function `Any(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Any
|
|
||||||
---@class AnyQuery: userdata
|
|
||||||
AnyQuery = {}
|
|
||||||
|
|
||||||
---Create a new AnyQuery.
|
|
||||||
---
|
|
||||||
---Use the utility function `Any(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Any
|
|
||||||
---@param ... Query The query to invert.
|
|
||||||
function AnyQuery:new(...) end
|
|
||||||
|
|
||||||
---An internal function used by the engine to retrieve the query result.
|
|
||||||
function AnyQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
|
@ -1,22 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An ECS query used for obtaining **changed** resources or components from the world.
|
|
||||||
---
|
|
||||||
---Use the utility function `Changed(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---This query will not return if the resource or component has not changed since the last tick.
|
|
||||||
---
|
|
||||||
---@class ChangedQuery: userdata
|
|
||||||
ChangedQuery = {}
|
|
||||||
|
|
||||||
---Create a new ChangedQuery.
|
|
||||||
---
|
|
||||||
---Use the utility function `Changed(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@param val table|userdata The component or resource to detect changed of.
|
|
||||||
function ChangedQuery:new(val) end
|
|
||||||
|
|
||||||
---An internal function used by the engine to retrieve the query result.
|
|
||||||
function ChangedQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
|
@ -1,22 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An ECS filter that allows the query if the entity has the Component.
|
|
||||||
---
|
|
||||||
---Use the utility function `Has(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Has
|
|
||||||
---@class HasQuery: userdata
|
|
||||||
HasQuery = {}
|
|
||||||
|
|
||||||
---Create a new HasQuery.
|
|
||||||
---
|
|
||||||
---Use the utility function `Has(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Has
|
|
||||||
---@param val table|userdata The component to look for on the entity.
|
|
||||||
function HasQuery:new(val) end
|
|
||||||
|
|
||||||
---An internal function used by the engine to retrieve the query result.
|
|
||||||
function HasQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
|
@ -1,9 +0,0 @@
|
||||||
require "view"
|
|
||||||
require "view_one"
|
|
||||||
require "changed"
|
|
||||||
require "res"
|
|
||||||
require "has"
|
|
||||||
require "any"
|
|
||||||
require "not"
|
|
||||||
require "optional"
|
|
||||||
require "tick_of"
|
|
|
@ -1,22 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An ECS filter that inverts the provided filter/query result.
|
|
||||||
---
|
|
||||||
---Use the utility function `Not(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Not
|
|
||||||
---@class NotQuery: userdata
|
|
||||||
NotQuery = {}
|
|
||||||
|
|
||||||
---Create a new NotQuery.
|
|
||||||
---
|
|
||||||
---Use the utility function `Not(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Not
|
|
||||||
---@param val Query The query to invert.
|
|
||||||
function NotQuery:new(val) end
|
|
||||||
|
|
||||||
---An internal function used by the engine to retrieve the query result.
|
|
||||||
function NotQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
|
@ -1,26 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An ECS query that ignores filters and queries that dont return anything.
|
|
||||||
---
|
|
||||||
---If the provided query returns nothing, this query will provide a `nil` value.
|
|
||||||
---The results of filters are essentially ignored, since it doesn't matter the result, this query
|
|
||||||
---will return. If the provided query has a result, this query will also return it.
|
|
||||||
---
|
|
||||||
---Use the utility function `Optional(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Optional
|
|
||||||
---@class OptionalQuery: userdata
|
|
||||||
OptionalQuery = {}
|
|
||||||
|
|
||||||
---Create a new OptionalQuery.
|
|
||||||
---
|
|
||||||
---Use the utility function `Optional(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see Optional
|
|
||||||
---@param val Query The query to invert.
|
|
||||||
function OptionalQuery:new(val) end
|
|
||||||
|
|
||||||
---An internal function used by the engine to retrieve the query result.
|
|
||||||
function OptionalQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
|
@ -1,16 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An ECS query used for obtaining Resources from the `World`.
|
|
||||||
---@class ResQuery: userdata
|
|
||||||
ResQuery = {}
|
|
||||||
|
|
||||||
---Create a new ResQuery for getting a Resource from the `World`.
|
|
||||||
---
|
|
||||||
---Use the utility function `Res(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@param val table|userdata The resource type to obtain.
|
|
||||||
function ResQuery:new(val) end
|
|
||||||
|
|
||||||
---An internal function used by the engine to retrieve the query result.
|
|
||||||
function ResQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
|
@ -1,22 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---An ECS query that returns the tick of the resource or component provided.
|
|
||||||
---
|
|
||||||
---Use the utility function `TickOf(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see TickOf
|
|
||||||
---@class TickOfQuery: userdata
|
|
||||||
TickOfQuery = {}
|
|
||||||
|
|
||||||
---Create a new TickOfQuery.
|
|
||||||
---
|
|
||||||
---Use the utility function `TickOf(...)` to create a new query since its faster to
|
|
||||||
---write than this.
|
|
||||||
---
|
|
||||||
---@see TickOf
|
|
||||||
---@param val table|userdata The component or resource to retrieve the tick of.
|
|
||||||
function TickOfQuery:new(val) end
|
|
||||||
|
|
||||||
---An internal function used by the engine to retrieve the query result.
|
|
||||||
function TickOfQuery:__lyra_internal_ecs_query_result(world, entity) end
|
|
|
@ -1,23 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class View: userdata
|
|
||||||
View = {}
|
|
||||||
|
|
||||||
---Create a new view to query for components and world resources.
|
|
||||||
---
|
|
||||||
---Each parameter is a query. If you want to query entities with components, you would just use
|
|
||||||
---the component names.
|
|
||||||
---There are other queries, like `Changed` for querying for changed resources and components,
|
|
||||||
---and `Res` for querying for resources.
|
|
||||||
---
|
|
||||||
---@return View
|
|
||||||
function View.new(...) end
|
|
||||||
|
|
||||||
---@class ViewResult: userdata
|
|
||||||
ViewResult = {}
|
|
||||||
|
|
||||||
---Returns an interator over the results of the View.
|
|
||||||
---
|
|
||||||
---@generic T...
|
|
||||||
---@return fun(): EntityRef, T... iterator An iterator over the results. In the same order of the created View.
|
|
||||||
function ViewResult:iter() end
|
|
|
@ -1,19 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---Results of a View over a single entity.
|
|
||||||
---@class ViewOneResult: userdata
|
|
||||||
ViewOneResult = {}
|
|
||||||
|
|
||||||
---Returns the results of the view over a single entity.
|
|
||||||
---
|
|
||||||
---@see ViewOneResult.__call
|
|
||||||
---@generic T...
|
|
||||||
---@return T...
|
|
||||||
function ViewOneResult:get() end
|
|
||||||
|
|
||||||
---Returns the results of the view over a single entity.
|
|
||||||
---
|
|
||||||
---@see ViewOneResult.get
|
|
||||||
---@generic T...
|
|
||||||
---@return T...
|
|
||||||
function ViewOneResult:__call() end
|
|
|
@ -1,128 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Window: userdata
|
|
||||||
Window = {
|
|
||||||
---Gets or sets the window's focus.
|
|
||||||
---@type boolean
|
|
||||||
focused = nil,
|
|
||||||
---Gets or sets the window mode.
|
|
||||||
---@type WindowMode
|
|
||||||
window_mode = nil,
|
|
||||||
---Gets or sets the position of the top-left corner of the window.
|
|
||||||
---
|
|
||||||
---The top-left hand corner of the desktop is not necessarily the same
|
|
||||||
---as the screen. If the user uses a desktop with multiple monitors, the top-left
|
|
||||||
---hand corner of the desktop is the top-left hand corner of the monitor at the
|
|
||||||
---top-left of the desktop.
|
|
||||||
---
|
|
||||||
---If this is `nil`, the position will be chosen by the windowing manager at creation,
|
|
||||||
---then set when the window is created.
|
|
||||||
---
|
|
||||||
---@type Vec2?
|
|
||||||
position = nil,
|
|
||||||
---@type Vec2
|
|
||||||
physical_size = nil,
|
|
||||||
---@type Vec2
|
|
||||||
size = nil,
|
|
||||||
---Gets/sets if the window has decorations.
|
|
||||||
---@type boolean
|
|
||||||
decorated = nil,
|
|
||||||
---Gets/sets the window's current maximized state.
|
|
||||||
---@type boolean
|
|
||||||
maximized = nil,
|
|
||||||
---Gets/sets the window's current minimized state.
|
|
||||||
---
|
|
||||||
---Is `nil` if the minimized state could not be determined.
|
|
||||||
---
|
|
||||||
---@type boolean?
|
|
||||||
minimized = nil,
|
|
||||||
---Gets/sets the window's current resizable state
|
|
||||||
---@type boolean
|
|
||||||
resizable = nil,
|
|
||||||
---Gets/sets the window's current visibility state.
|
|
||||||
---
|
|
||||||
---Is `nil` when it could not be determined.
|
|
||||||
---
|
|
||||||
---@type boolean?
|
|
||||||
visible = nil,
|
|
||||||
|
|
||||||
--TODO: resize_increments
|
|
||||||
|
|
||||||
---Gets the scale factor.
|
|
||||||
---
|
|
||||||
---You cannot set this field.
|
|
||||||
---
|
|
||||||
---@type number
|
|
||||||
scale_factor = nil,
|
|
||||||
---Gets/sets the window's blur state.
|
|
||||||
---@type boolean
|
|
||||||
blur = nil,
|
|
||||||
|
|
||||||
--TODO: cursor appearance
|
|
||||||
|
|
||||||
---Gets/sets the window's cursor grab mode.
|
|
||||||
---@type CursorGrabMode
|
|
||||||
cursor_grab = nil,
|
|
||||||
---Gets/sets whether the window catches cursor events.
|
|
||||||
---@type boolean
|
|
||||||
cursor_hittest = nil,
|
|
||||||
---Gets/sets the cursor's visibility.
|
|
||||||
---@type boolean
|
|
||||||
cursor_visible = nil,
|
|
||||||
---Sets whether the window should get IME events.
|
|
||||||
---
|
|
||||||
---When IME is allowed, the window will receive Ime events, and during the preedit phase
|
|
||||||
---the window will NOT get KeyboardInput events. The window should allow IME while
|
|
||||||
---it is expecting text input.
|
|
||||||
---
|
|
||||||
---When IME is not allowed, the window won’t receive window ime events, and will receive
|
|
||||||
---KeyboardInput events for every keypress instead. Not allowing IME is useful for games
|
|
||||||
---for example. IME is not allowed by default.
|
|
||||||
---
|
|
||||||
---@type boolean
|
|
||||||
ime_allowed = nil,
|
|
||||||
---Gets/sets the minimum size of the window.
|
|
||||||
---@type Vec2?
|
|
||||||
min_size = nil,
|
|
||||||
---Gets/sets the maximum size of the window.
|
|
||||||
---@type Vec2?
|
|
||||||
max_size = nil,
|
|
||||||
---Gets/sets the current window theme.
|
|
||||||
---
|
|
||||||
---Specify `nil` to reset the theme to the system default. May also be `nil` on
|
|
||||||
---unsupported platforms.
|
|
||||||
---
|
|
||||||
---@type WindowTheme?
|
|
||||||
theme = nil,
|
|
||||||
---Gets/sets the title of the window.
|
|
||||||
---@type string
|
|
||||||
title = nil,
|
|
||||||
---Gets/sets the window's transparency state.
|
|
||||||
---@type boolean
|
|
||||||
transparent = nil,
|
|
||||||
|
|
||||||
--TODO: window_icon
|
|
||||||
|
|
||||||
---Change the window level.
|
|
||||||
---@type WindowLevel
|
|
||||||
window_level = nil,
|
|
||||||
---Gets the window's occluded state (completely hidden from view).
|
|
||||||
---@type boolean
|
|
||||||
occluded = nil,
|
|
||||||
---Gets/sets the cursor position in the window in logical coordinates.
|
|
||||||
---
|
|
||||||
---The value is `nil` when the cursor is not in the window.
|
|
||||||
---
|
|
||||||
---@type Vec2?
|
|
||||||
cursor_position = nil,
|
|
||||||
---Gets/sets the cursor position in the window in physical coordinates.
|
|
||||||
---
|
|
||||||
---The value is `nil` when the cursor is not in the window.
|
|
||||||
---
|
|
||||||
---@type Vec2?
|
|
||||||
physical_cursor_position = nil,
|
|
||||||
---Checks if the mouse is inside the window
|
|
||||||
---@param self Window
|
|
||||||
---@return boolean
|
|
||||||
is_mouse_inside = function (self) return false end,
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class World: userdata
|
|
||||||
World = {}
|
|
||||||
|
|
||||||
---Spawn an entity with components.
|
|
||||||
---
|
|
||||||
---@param ... userdata The components to spawn on the new entity, currently must be userdata.
|
|
||||||
---@return Entity
|
|
||||||
function World:spawn(...) end
|
|
||||||
|
|
||||||
---Get an ECS resource.
|
|
||||||
---
|
|
||||||
---Returns `nil` if the resource was not found in the world. Many resources will
|
|
||||||
---return userdata, however some may return Lua types like `DeltaTime`
|
|
||||||
---returning a `number`.
|
|
||||||
---
|
|
||||||
---Example:
|
|
||||||
---```lua
|
|
||||||
------@type number
|
|
||||||
---local dt = world:resource(DeltaTime)
|
|
||||||
---
|
|
||||||
---print(type(dt)) --> number
|
|
||||||
---```
|
|
||||||
---
|
|
||||||
---@param resource userdata This shouldn't be an instance of userdata.
|
|
||||||
---@return any?
|
|
||||||
function World:resource(resource) end
|
|
||||||
|
|
||||||
---Add a resource to the world.
|
|
||||||
---
|
|
||||||
---If the resource already exists, it will be overwritten.
|
|
||||||
---
|
|
||||||
---@param resource userdata
|
|
||||||
function World:add_resource(resource) end
|
|
||||||
|
|
||||||
---Request an asset.
|
|
||||||
---
|
|
||||||
---Assets are loaded asyncronously, so you must wait before trying to access fields on
|
|
||||||
---the asset. You can spawn an entity with it when its still loading.
|
|
||||||
---
|
|
||||||
---Returns an asset handle to the requested resource type
|
|
||||||
---
|
|
||||||
---@param path string
|
|
||||||
---@return Handle asset An asset handle to the requested resource type.
|
|
||||||
function World:request_asset(path) end
|
|
||||||
|
|
||||||
---Get the current tick of the world.
|
|
||||||
---
|
|
||||||
---The tick is used to drive changed detection of resources and components.
|
|
||||||
---The world tick is iterated every frame.
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function World:get_tick() end
|
|
||||||
|
|
||||||
---Get an event reader of a specific event.
|
|
||||||
---
|
|
||||||
---@generic T
|
|
||||||
---@param event T
|
|
||||||
---@return EventReader<T>
|
|
||||||
function World:read_event(event) end
|
|
||||||
|
|
||||||
---View the world using the queries contained in a View.
|
|
||||||
---
|
|
||||||
---Example:
|
|
||||||
---```lua
|
|
||||||
----- Get entities without WorldTransform
|
|
||||||
---local view = View.new(Transform, Not(Has(WorldTransform)), Res(DeltaTime))
|
|
||||||
---local res = world:view_query(view)
|
|
||||||
------@param transform Transform
|
|
||||||
------@param dt DeltaTime
|
|
||||||
---for entity, transform, dt in res:iter() do
|
|
||||||
--- transform:translate(0, 0.15 * dt, 0)
|
|
||||||
--- entity:update(transform)
|
|
||||||
---end
|
|
||||||
---```
|
|
||||||
---
|
|
||||||
---@see View
|
|
||||||
---@see ViewResult
|
|
||||||
---@param view View
|
|
||||||
---@return ViewResult
|
|
||||||
function World:view(view) end
|
|
||||||
|
|
||||||
---View a single entity in the world.
|
|
||||||
---
|
|
||||||
---The view can contain queries and filters, but they will only be evaluated for a single entity.
|
|
||||||
---
|
|
||||||
---@param en Entity
|
|
||||||
---@param view View
|
|
||||||
---@return ViewOneResult
|
|
||||||
function World:view_one(en, view) end
|
|
||||||
|
|
||||||
--World global
|
|
||||||
---@type World
|
|
||||||
world = nil
|
|
|
@ -1,9 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
--- A Transform represents the relative position of the entity to its parent entity, while
|
|
||||||
--- a world transform is the position relative to the World. When wanting to move an entity,
|
|
||||||
--- you should use its [`Transform`]. You cannot mutate [`WorldTransform`] as its managed completey
|
|
||||||
--- by the [`system_update_world_transforms`] system. For the WorldTransform to work properly, you
|
|
||||||
--- must have both a [`Transform`] and [`WorldTransform`] on the entities in the scene.
|
|
||||||
---@alias WorldTransform Transform The world transform of an entity.
|
|
||||||
WorldTransform = {}
|
|
|
@ -1,140 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class DeviceEventAdded: table
|
|
||||||
DeviceEventAdded = {
|
|
||||||
---@type DeviceEventKind
|
|
||||||
kind = DeviceEventKind.ADDED,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@class DeviceEventRemoved: table
|
|
||||||
DeviceEventRemoved = {
|
|
||||||
---@type DeviceEventKind
|
|
||||||
kind = DeviceEventKind.REMOVED,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@class DeviceEventMouseMotion: table
|
|
||||||
DeviceEventMouseMotion = {
|
|
||||||
---@type DeviceEventKind
|
|
||||||
kind = DeviceEventKind.MOUSE_MOTION,
|
|
||||||
|
|
||||||
---The change in physical position of a pointing device.
|
|
||||||
---
|
|
||||||
---This represents raw, unfiltered physical motion.
|
|
||||||
---
|
|
||||||
---@type Vec2
|
|
||||||
delta = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---A physical scroll event from a device.
|
|
||||||
---
|
|
||||||
---`line_delta` and `pixel_delta` are exclusive, only one is non-nil at a time.
|
|
||||||
---
|
|
||||||
---@class DeviceEventMouseWheel: table
|
|
||||||
DeviceEventMouseWheel = {
|
|
||||||
---@type DeviceEventKind
|
|
||||||
kind = DeviceEventKind.MOUSE_WHEEL,
|
|
||||||
|
|
||||||
---Amount in lines or rows to scroll.
|
|
||||||
---
|
|
||||||
---Positive values indicate that the content that is being scrolled should move right
|
|
||||||
---and down (revealing more content left and up).
|
|
||||||
---
|
|
||||||
---@type Vec2?
|
|
||||||
line_delta = nil,
|
|
||||||
|
|
||||||
---Amount in pixels to scroll in the horizontal and vertical direction.
|
|
||||||
---
|
|
||||||
---Positive values indicate that the content being scrolled should move right/down.
|
|
||||||
---
|
|
||||||
---@type Vec2?
|
|
||||||
pixel_delta = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---Device event that was triggered by motion on an analog axis.
|
|
||||||
---@class DeviceEventMotion: table
|
|
||||||
DeviceEventMotion = {
|
|
||||||
---@type DeviceEventKind
|
|
||||||
kind = DeviceEventKind.MOTION,
|
|
||||||
|
|
||||||
---The analog axis that motion was triggered from.
|
|
||||||
---@type number
|
|
||||||
axis = nil,
|
|
||||||
|
|
||||||
---The amount of motion.
|
|
||||||
---@type number
|
|
||||||
value = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@class DeviceEventButton: table
|
|
||||||
DeviceEventButton = {
|
|
||||||
---@type DeviceEventKind
|
|
||||||
kind = DeviceEventKind.BUTTON,
|
|
||||||
|
|
||||||
---The button id that the event is from.
|
|
||||||
---@type number
|
|
||||||
button = nil,
|
|
||||||
|
|
||||||
---The state of the button.
|
|
||||||
---@type ElementState
|
|
||||||
state = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---A device event triggered from a key press.
|
|
||||||
---
|
|
||||||
---The field `code` will be nil if the key code identifier is unknown.
|
|
||||||
---When the identifier is unknown, it can be retrieved with `native_code`. The field
|
|
||||||
---`native` specifies the kind of platform the code is from.
|
|
||||||
---
|
|
||||||
---@class DeviceEventKey: table
|
|
||||||
DeviceEventKey = {
|
|
||||||
---@type DeviceEventKind
|
|
||||||
kind = DeviceEventKind.KEY,
|
|
||||||
|
|
||||||
---@type ElementState
|
|
||||||
state = nil,
|
|
||||||
|
|
||||||
---The known key name.
|
|
||||||
---
|
|
||||||
---This is `nil` if `native` or `native_code` is specified.
|
|
||||||
---
|
|
||||||
---@type string
|
|
||||||
code = nil,
|
|
||||||
|
|
||||||
---The kind of native platform this code is for.
|
|
||||||
---
|
|
||||||
---This is `nil` if `code` is specified.
|
|
||||||
---
|
|
||||||
---@type NativeKeyCodeKind
|
|
||||||
native = nil,
|
|
||||||
|
|
||||||
---The platform-native physical key identifier.
|
|
||||||
---
|
|
||||||
---This is `nil` if `code` is specified.
|
|
||||||
---
|
|
||||||
---@type number
|
|
||||||
native_code = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---@alias DeviceEventRaw
|
|
||||||
---| DeviceEventAdded
|
|
||||||
---| DeviceEventRemoved
|
|
||||||
---| DeviceEventMotion
|
|
||||||
---| DeviceEventMouseMotion
|
|
||||||
---| DeviceEventMouseWheel
|
|
||||||
---| DeviceEventButton
|
|
||||||
---| DeviceEventKey
|
|
||||||
DeviceEventRaw = { }
|
|
||||||
|
|
||||||
---@class DeviceId: userdata
|
|
||||||
DeviceId = {}
|
|
||||||
|
|
||||||
---@class DeviceEvent: table
|
|
||||||
DeviceEvent = {
|
|
||||||
---The device id that the event came from
|
|
||||||
---@type DeviceId
|
|
||||||
source = nil,
|
|
||||||
---The actual device event
|
|
||||||
---@type DeviceEventRaw
|
|
||||||
event = nil,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class EventReader: userdata
|
|
||||||
EventReader = {}
|
|
||||||
|
|
||||||
---Returns an iterator over the events in the reader.
|
|
||||||
---@return fun(): any
|
|
||||||
function EventReader:read() end
|
|
||||||
|
|
||||||
---@class EventWriter: userdata
|
|
||||||
EventWriter = {}
|
|
||||||
|
|
||||||
---Writes an event.
|
|
||||||
---
|
|
||||||
---The event must be the same type that this writer wraps.
|
|
||||||
---
|
|
||||||
---@param event any
|
|
||||||
function EventWriter:write(event) end
|
|
|
@ -1,5 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---The world global that is provided to every Lua script.
|
|
||||||
---@type World
|
|
||||||
world = nil
|
|
|
@ -1,4 +0,0 @@
|
||||||
require "math.init"
|
|
||||||
require "ecs.init"
|
|
||||||
|
|
||||||
require "asset.handle"
|
|
|
@ -1,25 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Angle: userdata
|
|
||||||
Angle = {}
|
|
||||||
|
|
||||||
---Create a new angle in degrees.
|
|
||||||
---@param deg number
|
|
||||||
function Angle.new_degrees(deg) end
|
|
||||||
---Create a new angle in radians.
|
|
||||||
---@param rad number
|
|
||||||
function Angle.new_radians(rad) end
|
|
||||||
|
|
||||||
---Get the angle in radians.
|
|
||||||
---
|
|
||||||
---Will convert from degrees automatically.
|
|
||||||
---
|
|
||||||
---@return number radians
|
|
||||||
function Angle:radians() end
|
|
||||||
|
|
||||||
---Get the angle in degrees.
|
|
||||||
---
|
|
||||||
---Will convert from radians automatically.
|
|
||||||
---
|
|
||||||
---@return number degrees
|
|
||||||
function Angle:degrees() end
|
|
|
@ -1,6 +0,0 @@
|
||||||
require "math.vec2"
|
|
||||||
require "math.vec3"
|
|
||||||
require "math.vec4"
|
|
||||||
require "math.quat"
|
|
||||||
require "math.transform"
|
|
||||||
require "math.angle"
|
|
|
@ -1,188 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Quat: userdata
|
|
||||||
---This is a Lua export of [`glam::Quat`](https://docs.rs/glam/latest/glam/f32/struct.Quat.html)
|
|
||||||
---
|
|
||||||
---@operator add(self): self
|
|
||||||
---@operator sub(self): self
|
|
||||||
---@operator div(number): self
|
|
||||||
---@operator mul(self|Vec3|number): self
|
|
||||||
---@diagnostic disable-next-line: unknown-operator
|
|
||||||
---@operator eq: self
|
|
||||||
Quat = {
|
|
||||||
---The x coordinate
|
|
||||||
---@type number
|
|
||||||
x = nil,
|
|
||||||
|
|
||||||
---The y coordinate
|
|
||||||
---@type number
|
|
||||||
y = nil,
|
|
||||||
|
|
||||||
---The z coordinate
|
|
||||||
---@type number
|
|
||||||
z = nil,
|
|
||||||
|
|
||||||
---The w coordinate
|
|
||||||
---@type number
|
|
||||||
w = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
---Create a new `Quat`
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param z number
|
|
||||||
---@param w number
|
|
||||||
---@return self
|
|
||||||
function Quat.new(x, y, z, w) end
|
|
||||||
|
|
||||||
---Creates a quaternion from the angle (in radians) around the x axis.
|
|
||||||
---@param rad number
|
|
||||||
---@return self
|
|
||||||
function Quat.from_rotation_x(rad) end
|
|
||||||
|
|
||||||
---Creates a quaternion from the angle (in radians) around the y axis.
|
|
||||||
---@param rad number
|
|
||||||
---@return self
|
|
||||||
function Quat.from_rotation_y(rad) end
|
|
||||||
|
|
||||||
---Creates a quaternion from the angle (in radians) around the z axis.
|
|
||||||
---@param rad number
|
|
||||||
---@return self
|
|
||||||
function Quat.from_rotation_z(rad) end
|
|
||||||
|
|
||||||
---Creates a quaternion from a `Vec4`.
|
|
||||||
---@param vec4 Vec4
|
|
||||||
---@return self
|
|
||||||
function Quat.from_vec4(vec4) end
|
|
||||||
|
|
||||||
---Create a quaternion for a normalized rotation axis and angle (in radians).
|
|
||||||
---
|
|
||||||
---The axis must be a unit vector.
|
|
||||||
---
|
|
||||||
---@param axis Vec3
|
|
||||||
---@param rad number
|
|
||||||
---@return self
|
|
||||||
function Quat.from_axis_angle(axis, rad) end
|
|
||||||
|
|
||||||
---Computes the dot product of self and rhs.
|
|
||||||
---
|
|
||||||
---The dot product is equal to the cosine of the angle between two
|
|
||||||
---quaternion rotations.
|
|
||||||
---
|
|
||||||
---@param rhs Quat
|
|
||||||
---@return number
|
|
||||||
function Quat:dot(rhs) end
|
|
||||||
|
|
||||||
---Computes the length of self.
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function Quat:length() end
|
|
||||||
|
|
||||||
---Computes the squared length of self.
|
|
||||||
---
|
|
||||||
---This is generally faster than length() as it avoids a square root operation.
|
|
||||||
---
|
|
||||||
---@return number
|
|
||||||
function Quat:length_squared() end
|
|
||||||
|
|
||||||
---Computes 1.0 / length().
|
|
||||||
---
|
|
||||||
---For valid results, self must not be of length zero.
|
|
||||||
---@return number
|
|
||||||
function length_recip() end
|
|
||||||
|
|
||||||
---Returns `self` normalized to length `1.0`.
|
|
||||||
---
|
|
||||||
---For valid results, `self` must not be of length zero.
|
|
||||||
---
|
|
||||||
---@return self
|
|
||||||
function Quat:normalize() end
|
|
||||||
|
|
||||||
---Multipies `self` with a `Quat`
|
|
||||||
---@param rhs Quat
|
|
||||||
function Quat:mult_quat(rhs) end
|
|
||||||
|
|
||||||
---Multiplies `self` with a `Vec3`
|
|
||||||
---@param rhs Vec3
|
|
||||||
function Quat:mult_vec3(rhs) end
|
|
||||||
|
|
||||||
---Performs a linear interpolation between `self` and `rhs` based on `alpha`.
|
|
||||||
---
|
|
||||||
---Both `Quat`s must be normalized.
|
|
||||||
---
|
|
||||||
---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`,
|
|
||||||
---the result will be equal to `rhs`.
|
|
||||||
---
|
|
||||||
---@param rhs Quat
|
|
||||||
---@param alpha number
|
|
||||||
function Quat:lerp(rhs, alpha) end
|
|
||||||
|
|
||||||
---Performs a spherical linear interpolation between `self` and `rhs` based on `alpha`.
|
|
||||||
---
|
|
||||||
---Both `Quat`s must be normalized.
|
|
||||||
---
|
|
||||||
---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`,
|
|
||||||
---the result will be equal to `rhs`.
|
|
||||||
---
|
|
||||||
---@param rhs Quat
|
|
||||||
---@param alpha number
|
|
||||||
function Quat:slerp(rhs, alpha) end
|
|
||||||
|
|
||||||
|
|
||||||
---Returns the inverse of a normalized quaternion.
|
|
||||||
---
|
|
||||||
---Typically quaternion inverse returns the conjugate of a normalized quaternion.
|
|
||||||
---Because `self` is assumed to already be unit length this method does not
|
|
||||||
---normalize before returning the conjugate.
|
|
||||||
---@return self
|
|
||||||
function Quat:inverse() end
|
|
||||||
|
|
||||||
---Returns `true` if, and only if, all elements are finite. If any element is either
|
|
||||||
---`NaN`, positive or negative infinity, this will return `false`.
|
|
||||||
---
|
|
||||||
---@return boolean
|
|
||||||
function Quat:is_finite() end
|
|
||||||
|
|
||||||
---@return boolean
|
|
||||||
function Quat:is_nan() end
|
|
||||||
|
|
||||||
---Returns whether `self` is of length `1.0` or not.
|
|
||||||
---
|
|
||||||
---Uses a precision threshold of `1e-6`.
|
|
||||||
---@return boolean
|
|
||||||
function Quat:is_normalized() end
|
|
||||||
|
|
||||||
---@return boolean
|
|
||||||
function Quat:is_near_identity() end
|
|
||||||
|
|
||||||
---Returns the angle (in radians) for the minimal rotation for transforming
|
|
||||||
---this quaternion into another.
|
|
||||||
---
|
|
||||||
---Both quaternions must be normalized.
|
|
||||||
---@return number
|
|
||||||
function Quat:angle_between(rhs) end
|
|
||||||
|
|
||||||
---Rotates towards `rhs` up to `max_angle` (in radians).
|
|
||||||
---
|
|
||||||
---When `max_angle` is `0.0`, the result will be equal to `self`. When `max_angle`
|
|
||||||
---is equal to `self.angle_between(rhs)`, the result will be equal to `rhs`.
|
|
||||||
---If `max_angle` is negative, rotates towards the exact opposite of `rhs`.
|
|
||||||
---Will not go past the target.
|
|
||||||
---
|
|
||||||
---Both quaternions must be normalized.
|
|
||||||
---@return self
|
|
||||||
function Quat:rotate_towards(rhs, max_angle) end
|
|
||||||
|
|
||||||
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
|
|
||||||
---than or equal to `max_abs_diff`.
|
|
||||||
---
|
|
||||||
---This can be used to compare if two quaternions contain similar elements. It works best when
|
|
||||||
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
|
|
||||||
---values being compared against.
|
|
||||||
---
|
|
||||||
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
|
|
||||||
---
|
|
||||||
---@param rhs Quat
|
|
||||||
---@param max_abs_diff number
|
|
||||||
---@return boolean
|
|
||||||
function Quat:abs_diff_eq(rhs, max_abs_diff) end
|
|
|
@ -1,134 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Transform: userdata
|
|
||||||
---
|
|
||||||
---A Transform represents a transformation of an object. A transform includes the position
|
|
||||||
---(called translation here), rotation, and scale. Rotation is represented using a Quaternion
|
|
||||||
---(or Quat for short).
|
|
||||||
---
|
|
||||||
---Although Quats can be scary, they are much more robust than euler angles for games
|
|
||||||
---since they do not suffer from things like
|
|
||||||
---[gimbal-lock](https://en.wikipedia.org/wiki/Gimbal_lock).
|
|
||||||
---
|
|
||||||
---This is a Lua export of [`lyra_math::Transform`].
|
|
||||||
---
|
|
||||||
---@operator add(Quat): self
|
|
||||||
---@operator mul(Vec3): self
|
|
||||||
---@diagnostic disable-next-line: unknown-operator
|
|
||||||
---@operator eq: self
|
|
||||||
Transform = {
|
|
||||||
---The translation/position of the transform.
|
|
||||||
---@type Vec3
|
|
||||||
translation = nil,
|
|
||||||
---The rotation of the transform.
|
|
||||||
---@type Quat
|
|
||||||
rotation = nil,
|
|
||||||
---The scale of the transform.
|
|
||||||
---@type Vec3
|
|
||||||
scale = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Transform:__tostring() end
|
|
||||||
|
|
||||||
---@return self
|
|
||||||
function Transform.default() end
|
|
||||||
|
|
||||||
---Create a new transform with its components.
|
|
||||||
---
|
|
||||||
---@param translation Vec3
|
|
||||||
---@param rotation Quat
|
|
||||||
---@param scale Vec3
|
|
||||||
---@return self
|
|
||||||
function Transform.new(translation, rotation, scale) end
|
|
||||||
|
|
||||||
---Create a new transform with a `Vec3` translation.
|
|
||||||
---@param translation Vec3
|
|
||||||
---@return self
|
|
||||||
function Transform.from_translation(translation) end
|
|
||||||
|
|
||||||
---Create a new transform with a translation of `x`, `y`, and `z`.
|
|
||||||
---
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param z number
|
|
||||||
---@return self
|
|
||||||
function Transform.from_translation(x, y, z) end
|
|
||||||
|
|
||||||
---Create a clone of `self`
|
|
||||||
---@return self
|
|
||||||
function Transform:clone() end
|
|
||||||
|
|
||||||
---Returns a normalized vector pointing in the direction the Transform is facing.
|
|
||||||
---
|
|
||||||
---This represents the front of the object can be used for movement, camera orientation, and
|
|
||||||
---other directional calculations.
|
|
||||||
---
|
|
||||||
---@return Vec3
|
|
||||||
function Transform:forward() end
|
|
||||||
|
|
||||||
---Returns a normalized vector pointing to the left side of the Transform.
|
|
||||||
---
|
|
||||||
---The vector is in local space. This represents the direction that is
|
|
||||||
---perpendicular to the object's forward direction.
|
|
||||||
---
|
|
||||||
---@return Vec3
|
|
||||||
function Transform:left() end
|
|
||||||
|
|
||||||
---Returns a normalized vector that indicates the upward direction of the Transform.
|
|
||||||
---
|
|
||||||
---This vector is commonly used to define an object's orientation and is essential for maintaining
|
|
||||||
---consistent vertical alignment in 3D environments, such as for camera positioning and object alignment.
|
|
||||||
---@return Vec3
|
|
||||||
function Transform:up() end
|
|
||||||
|
|
||||||
---Rotate `self` using a quaternion
|
|
||||||
---@param quat Quat
|
|
||||||
function Transform:rotate(quat) end
|
|
||||||
|
|
||||||
---Rotate `self` around the x axis by **degrees**.
|
|
||||||
---
|
|
||||||
---@param deg number The amount of **degrees** to rotate by.
|
|
||||||
function Transform:rotate_x(deg) end
|
|
||||||
|
|
||||||
---Rotate `self` around the y axis by **degrees**.
|
|
||||||
---
|
|
||||||
---@param deg number The amount of **degrees** to rotate by.
|
|
||||||
function Transform:rotate_y(deg) end
|
|
||||||
|
|
||||||
---Rotate `self` around the z axis by **degrees**.
|
|
||||||
---
|
|
||||||
---@param deg number The amount of **degrees** to rotate by.
|
|
||||||
function Transform:rotate_z(deg) end
|
|
||||||
|
|
||||||
---Rotate `self` around the x axis by **radians** .
|
|
||||||
---
|
|
||||||
---@param rad number The amount of **radians** to rotate by.
|
|
||||||
function Transform:rotate_x_rad(rad) end
|
|
||||||
|
|
||||||
---Rotate `self` around the y axis by **radians** .
|
|
||||||
---
|
|
||||||
---@param rad number The amount of **radians** to rotate by.
|
|
||||||
function Transform:rotate_y_rad(rad) end
|
|
||||||
|
|
||||||
---Rotate `self` around the z axis by **radians** .
|
|
||||||
---
|
|
||||||
---@param rad number The amount of **radians** to rotate by.
|
|
||||||
function Transform:rotate_z_rad(rad) end
|
|
||||||
|
|
||||||
---Move `self` by `x`, `y`, and `z`.
|
|
||||||
---
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param z number
|
|
||||||
function Transform:translate(x, y, z) end
|
|
||||||
|
|
||||||
---Performs a linear interpolation between `self` and `rhs` based on `alpha`.
|
|
||||||
---
|
|
||||||
---This will normalize the rotation `Quat`.
|
|
||||||
---
|
|
||||||
---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`,
|
|
||||||
---the result will be equal to `rhs`.
|
|
||||||
---
|
|
||||||
---@param rhs Transform
|
|
||||||
---@param alpha number
|
|
||||||
function Transform:lerp(rhs, alpha) end
|
|
|
@ -1,125 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Vec2: userdata
|
|
||||||
---This is a Lua export of [`glam::Vec2`](https://docs.rs/glam/latest/glam/f32/struct.Vec2.html)
|
|
||||||
---
|
|
||||||
---@operator add(self|number): self
|
|
||||||
---@operator sub(self|number): self
|
|
||||||
---@operator div(self|number): self
|
|
||||||
---@operator mul(self|number): self
|
|
||||||
---@operator mod(self|number): self
|
|
||||||
---@operator unm: self
|
|
||||||
---@diagnostic disable-next-line: unknown-operator
|
|
||||||
---@operator eq: self
|
|
||||||
Vec2 = {
|
|
||||||
---The x coordinate
|
|
||||||
---@type number
|
|
||||||
x = nil,
|
|
||||||
|
|
||||||
---The y coordinate
|
|
||||||
---@type number
|
|
||||||
y = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with coordinates as `f32::NAN`.
|
|
||||||
---@type Vec2
|
|
||||||
NAN = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with `x` as `-1.0`.
|
|
||||||
---@type Vec2
|
|
||||||
NEG_X = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with `y` as `-1.0`.
|
|
||||||
---@type Vec2
|
|
||||||
NEG_Y = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with both components as `-1.0`.
|
|
||||||
---@type Vec2
|
|
||||||
NEG_ONE = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with `x` as `1.0`.
|
|
||||||
---@type Vec2
|
|
||||||
POS_X = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with `y` as `1.0`.
|
|
||||||
---@type Vec2
|
|
||||||
POS_Y = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with both components as `1.0`.
|
|
||||||
---@type Vec2
|
|
||||||
ONE = nil,
|
|
||||||
|
|
||||||
---A constant `Vec2` with both components as `0.0`.
|
|
||||||
---@type Vec2
|
|
||||||
ZERO = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Vec2:__tostring() end
|
|
||||||
|
|
||||||
---Create a new `Vec2`
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@return self
|
|
||||||
function Vec2.new(x, y) end
|
|
||||||
|
|
||||||
---Returns a vector with a length no less than min and no more than max.
|
|
||||||
---@param min number the minimum value to clamp the length to
|
|
||||||
---@param max number the maximum value to clamp the length to
|
|
||||||
---@return self
|
|
||||||
function Vec2:clamp_length(min, max) end
|
|
||||||
|
|
||||||
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
|
|
||||||
---than or equal to `max_abs_diff`.
|
|
||||||
---
|
|
||||||
---This can be used to compare if two vectors contain similar elements. It works best when
|
|
||||||
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
|
|
||||||
---values being compared against.
|
|
||||||
---
|
|
||||||
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
|
|
||||||
---
|
|
||||||
---@param rhs Vec2 The other `Vec2` to compare to.
|
|
||||||
---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`.
|
|
||||||
---@return boolean
|
|
||||||
function Vec2:abs_diff_eq(rhs, max_abs_diff) end
|
|
||||||
|
|
||||||
---Returns a vector containing the smallest integer greater than or equal to a number for each
|
|
||||||
---element of self.
|
|
||||||
---@return self
|
|
||||||
function Vec2:ceil() end
|
|
||||||
|
|
||||||
---Returns the angle of rotation (in radians) from `self` to `rhs` in the range [-π, +π].
|
|
||||||
---
|
|
||||||
---The inputs do not need to be unit vectors however they must be non-zero.
|
|
||||||
---
|
|
||||||
---@param rhs Vec2 The other `Vec2` to get the angle to.
|
|
||||||
---@return number
|
|
||||||
function Vec2:angle_to(rhs) end
|
|
||||||
|
|
||||||
---Returns a vector containing the absolute value of each element of `self`.
|
|
||||||
---
|
|
||||||
---@return self
|
|
||||||
function Vec2:abs() end
|
|
||||||
|
|
||||||
---Component-wise clamping of values.
|
|
||||||
---
|
|
||||||
---Each element in `min` must be less-or-equal to the corresponding element in `max`.
|
|
||||||
---
|
|
||||||
---@param min self The minimum `Vec2` components to clamp the components of `self` to.
|
|
||||||
---@param max self The maximum `Vec2` components to clamp the components of `self` to.
|
|
||||||
---@return self
|
|
||||||
function Vec2:clamp(min, max) end
|
|
||||||
|
|
||||||
---Converts `self` to an array `[x, y]`
|
|
||||||
---
|
|
||||||
---@return number[]
|
|
||||||
function Vec2:to_array() end
|
|
||||||
|
|
||||||
---Move `self` by `x` and `y` values.
|
|
||||||
---
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
function Vec2:move_by(x, y) end
|
|
||||||
|
|
||||||
---Move `self` by a `Vec2`.
|
|
||||||
---
|
|
||||||
---@param rhs Vec2
|
|
||||||
function Vec2:move_by(rhs) end
|
|
|
@ -1,139 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Vec3: userdata
|
|
||||||
---This is a Lua export of [`glam::Vec3`](https://docs.rs/glam/latest/glam/f32/struct.Vec3.html)
|
|
||||||
---
|
|
||||||
---@operator add(self|number): self
|
|
||||||
---@operator sub(self|number): self
|
|
||||||
---@operator div(self|number): self
|
|
||||||
---@operator mul(self|number): self
|
|
||||||
---@operator mod(self|number): self
|
|
||||||
---@operator unm: self
|
|
||||||
---@diagnostic disable-next-line: unknown-operator
|
|
||||||
---@operator eq: self
|
|
||||||
Vec3 = {
|
|
||||||
---The x coordinate
|
|
||||||
---@type number
|
|
||||||
x = nil,
|
|
||||||
|
|
||||||
---The y coordinate
|
|
||||||
---@type number
|
|
||||||
y = nil,
|
|
||||||
|
|
||||||
---The z coordinate
|
|
||||||
---@type number
|
|
||||||
z = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with coordinates as `f32::NAN`.
|
|
||||||
---@type Vec3
|
|
||||||
NAN = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with `x` as `-1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
NEG_X = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with `y` as `-1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
NEG_Y = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with `z` as `-1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
NEG_Z = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with all components as `-1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
NEG_ONE = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with `x` as `1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
POS_X = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with `y` as `1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
POS_Y = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with `z` as `1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
POS_Z = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with all components as `1.0`.
|
|
||||||
---@type Vec3
|
|
||||||
ONE = nil,
|
|
||||||
|
|
||||||
---A constant `Vec3` with all components as `0.0`.
|
|
||||||
---@type Vec3
|
|
||||||
ZERO = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Vec3:__tostring() end
|
|
||||||
|
|
||||||
---Create a new `Vec3`
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param z number
|
|
||||||
---@return self
|
|
||||||
function Vec3.new(x, y, z) end
|
|
||||||
|
|
||||||
---Returns a vector with a length no less than min and no more than max.
|
|
||||||
---@param min number the minimum value to clamp the length to
|
|
||||||
---@param max number the maximum value to clamp the length to
|
|
||||||
---@return self
|
|
||||||
function Vec3:clamp_length(min, max) end
|
|
||||||
|
|
||||||
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
|
|
||||||
---than or equal to `max_abs_diff`.
|
|
||||||
---
|
|
||||||
---This can be used to compare if two vectors contain similar elements. It works best when
|
|
||||||
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
|
|
||||||
---values being compared against.
|
|
||||||
---
|
|
||||||
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
|
|
||||||
---
|
|
||||||
---@param rhs Vec3 The other `Vec3` to compare to.
|
|
||||||
---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`.
|
|
||||||
---@return boolean
|
|
||||||
function Vec3:abs_diff_eq(rhs, max_abs_diff) end
|
|
||||||
|
|
||||||
---Returns a vector containing the smallest integer greater than or equal to a number for each
|
|
||||||
---element of self.
|
|
||||||
---@return self
|
|
||||||
function Vec3:ceil() end
|
|
||||||
|
|
||||||
---Returns the angle (in radians) between two vectors in the range [-π, +π].
|
|
||||||
---
|
|
||||||
---The inputs do not need to be unit vectors however they must be non-zero.
|
|
||||||
---
|
|
||||||
---@param rhs Vec3 The other `Vec3` to get the angle to.
|
|
||||||
---@return number
|
|
||||||
function Vec3:angle_between(rhs) end
|
|
||||||
|
|
||||||
---Returns a vector containing the absolute value of each element of `self`.
|
|
||||||
---
|
|
||||||
---@return self
|
|
||||||
function Vec3:abs() end
|
|
||||||
|
|
||||||
---Component-wise clamping of values.
|
|
||||||
---
|
|
||||||
---Each element in `min` must be less-or-equal to the corresponding element in `max`.
|
|
||||||
---
|
|
||||||
---@param min self The minimum `Vec3` components to clamp the components of `self` to.
|
|
||||||
---@param max self The maximum `Vec3` components to clamp the components of `self` to.
|
|
||||||
---@return self
|
|
||||||
function Vec3:clamp(min, max) end
|
|
||||||
|
|
||||||
---Converts `self` to an array `[x, y, z]`
|
|
||||||
---
|
|
||||||
---@return number[]
|
|
||||||
function Vec3:to_array() end
|
|
||||||
|
|
||||||
---Move `self` by `x`, `y`, and `z` values.
|
|
||||||
---
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param z number
|
|
||||||
function Vec3:move_by(x, y, z) end
|
|
||||||
|
|
||||||
---Move `self` by a `Vec3`.
|
|
||||||
---
|
|
||||||
---@param rhs Vec3
|
|
||||||
function Vec3:move_by(rhs) end
|
|
|
@ -1,132 +0,0 @@
|
||||||
---@meta
|
|
||||||
|
|
||||||
---@class Vec4: userdata
|
|
||||||
---This is a Lua export of [`glam::Vec4`](https://docs.rs/glam/latest/glam/f32/struct.Vec4.html)
|
|
||||||
---
|
|
||||||
---@operator add(self|number): self
|
|
||||||
---@operator sub(self|number): self
|
|
||||||
---@operator div(self|number): self
|
|
||||||
---@operator mul(self|number): self
|
|
||||||
---@operator mod(self|number): self
|
|
||||||
---@operator unm: self
|
|
||||||
---@diagnostic disable-next-line: unknown-operator
|
|
||||||
---@operator eq: self
|
|
||||||
Vec4 = {
|
|
||||||
---The x coordinate
|
|
||||||
---@type number
|
|
||||||
x = nil,
|
|
||||||
|
|
||||||
---The y coordinate
|
|
||||||
---@type number
|
|
||||||
y = nil,
|
|
||||||
|
|
||||||
---The z coordinate
|
|
||||||
---@type number
|
|
||||||
z = nil,
|
|
||||||
|
|
||||||
---The w coordinate
|
|
||||||
---@type number
|
|
||||||
w = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with coordinates as `f32::NAN`.
|
|
||||||
---@type Vec4
|
|
||||||
NAN = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `x` as `-1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
NEG_X = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `y` as `-1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
NEG_Y = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `z` as `-1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
NEG_Z = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `w` as `-1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
NEG_W = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with all components as `-1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
NEG_ONE = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `x` as `1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
POS_X = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `y` as `1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
POS_Y = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `z` as `1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
POS_Z = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with `w` as `1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
POS_W = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with all components as `1.0`.
|
|
||||||
---@type Vec4
|
|
||||||
ONE = nil,
|
|
||||||
|
|
||||||
---A constant `Vec4` with all components as `0.0`.
|
|
||||||
---@type Vec4
|
|
||||||
ZERO = nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Vec4:__tostring() end
|
|
||||||
|
|
||||||
---Create a new `Vec4`
|
|
||||||
---@param x number
|
|
||||||
---@param y number
|
|
||||||
---@param z number
|
|
||||||
---@param w number
|
|
||||||
---@return self
|
|
||||||
function Vec4.new(x, y, z, w) end
|
|
||||||
|
|
||||||
---Returns a vector with a length no less than min and no more than max.
|
|
||||||
---@param min number the minimum value to clamp the length to
|
|
||||||
---@param max number the maximum value to clamp the length to
|
|
||||||
---@return self
|
|
||||||
function Vec4:clamp_length(min, max) end
|
|
||||||
|
|
||||||
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
|
|
||||||
---than or equal to `max_abs_diff`.
|
|
||||||
---
|
|
||||||
---This can be used to compare if two vectors contain similar elements. It works best when
|
|
||||||
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
|
|
||||||
---values being compared against.
|
|
||||||
---
|
|
||||||
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
|
|
||||||
---
|
|
||||||
---@param rhs Vec4 The other `Vec4` to compare to.
|
|
||||||
---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`.
|
|
||||||
---@return boolean
|
|
||||||
function Vec4:abs_diff_eq(rhs, max_abs_diff) end
|
|
||||||
|
|
||||||
---Returns a vector containing the smallest integer greater than or equal to a number for each
|
|
||||||
---element of self.
|
|
||||||
---@return self
|
|
||||||
function Vec4:ceil() end
|
|
||||||
|
|
||||||
---Returns a vector containing the absolute value of each element of `self`.
|
|
||||||
---
|
|
||||||
---@return self
|
|
||||||
function Vec4:abs() end
|
|
||||||
|
|
||||||
---Component-wise clamping of values.
|
|
||||||
---
|
|
||||||
---Each element in `min` must be less-or-equal to the corresponding element in `max`.
|
|
||||||
---
|
|
||||||
---@param min self The minimum `Vec4` components to clamp the components of `self` to.
|
|
||||||
---@param max self The maximum `Vec4` components to clamp the components of `self` to.
|
|
||||||
---@return self
|
|
||||||
function Vec4:clamp(min, max) end
|
|
||||||
|
|
||||||
---Converts `self` to an array `[x, y, z]`
|
|
||||||
---
|
|
||||||
---@return number[]
|
|
||||||
function Vec4:to_array() end
|
|
|
@ -1,82 +0,0 @@
|
||||||
use std::ptr::NonNull;
|
|
||||||
|
|
||||||
use lyra_ecs::{query::dynamic::DynamicViewStateIter, Entity};
|
|
||||||
use lyra_reflect::TypeRegistry;
|
|
||||||
|
|
||||||
use crate::ScriptWorldPtr;
|
|
||||||
|
|
||||||
#[cfg(feature = "lua")]
|
|
||||||
use super::ReflectLuaProxy;
|
|
||||||
|
|
||||||
#[cfg(feature = "lua")]
|
|
||||||
pub struct ReflectedItem {
|
|
||||||
pub comp_ptr: NonNull<u8>,
|
|
||||||
pub comp_val: mlua::Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "lua")]
|
|
||||||
pub struct ReflectedRow {
|
|
||||||
pub entity: Entity,
|
|
||||||
pub row: Vec<ReflectedItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ReflectedIteratorOwned {
|
|
||||||
pub world_ptr: ScriptWorldPtr,
|
|
||||||
pub dyn_view: DynamicViewStateIter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ReflectedIteratorOwned {
|
|
||||||
pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option<ReflectedRow> {
|
|
||||||
let world = self.world_ptr.read();
|
|
||||||
next_lua(lua, &world, &mut self.dyn_view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ReflectedIterator<'a> {
|
|
||||||
pub world: &'a lyra_ecs::World,
|
|
||||||
pub dyn_view: DynamicViewStateIter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ReflectedIterator<'a> {
|
|
||||||
pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option<ReflectedRow> {
|
|
||||||
next_lua(lua, &self.world, &mut self.dyn_view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_lua(lua: &mlua::Lua, world: &lyra_ecs::World, dyn_view: &mut DynamicViewStateIter) -> Option<ReflectedRow> {
|
|
||||||
use mlua::IntoLua;
|
|
||||||
|
|
||||||
//let world = world.read();
|
|
||||||
let n = dyn_view.next(&world);
|
|
||||||
|
|
||||||
if let Some((en, row)) = n {
|
|
||||||
let reflected_components = world.get_resource::<TypeRegistry>().unwrap();
|
|
||||||
|
|
||||||
let mut dynamic_row = vec![];
|
|
||||||
for d in row.iter() {
|
|
||||||
let id = d.info.type_id().as_rust();
|
|
||||||
|
|
||||||
let reg_type = reflected_components.get_type(id)
|
|
||||||
.expect("Requested type was not found in TypeRegistry");
|
|
||||||
let proxy = reg_type.get_data::<ReflectLuaProxy>()
|
|
||||||
// TODO: properly handle this error
|
|
||||||
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
|
||||||
let value = proxy.as_lua(lua, d.ptr.cast()).unwrap()
|
|
||||||
.into_lua(lua).unwrap();
|
|
||||||
|
|
||||||
dynamic_row.push(ReflectedItem {
|
|
||||||
comp_ptr: d.ptr,
|
|
||||||
comp_val: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let row = ReflectedRow {
|
|
||||||
entity: en,
|
|
||||||
row: dynamic_row
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(row)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
mod view;
|
|
||||||
pub use view::*;
|
|
||||||
|
|
||||||
mod view_one;
|
|
||||||
pub use view_one::*;
|
|
||||||
|
|
||||||
pub mod query;
|
|
|
@ -1,107 +0,0 @@
|
||||||
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
|
|
||||||
use mlua::IntoLua;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
|
||||||
ReflectBranch, ScriptEntity, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::LuaQueryResult;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaChangedQuery(LuaComponent);
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaChangedQuery {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "ChangedQuery".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for LuaChangedQuery {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp)));
|
|
||||||
|
|
||||||
methods.add_method(
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
|lua, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
|
||||||
let mut world = world.write();
|
|
||||||
let reflect = this.0.reflect_type()?;
|
|
||||||
|
|
||||||
let tyid = reflect.reflect_branch.reflect_type_id();
|
|
||||||
match &reflect.reflect_branch {
|
|
||||||
ReflectBranch::Component(comp) => {
|
|
||||||
if !comp.reflect_is_changed(&world, *en).unwrap_or(false) {
|
|
||||||
return Ok(LuaQueryResult::FilterDeny);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the pointer of the component in the archetype column.
|
|
||||||
let arch = match world.entity_archetype(*en) {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return Ok(LuaQueryResult::FilterDeny),
|
|
||||||
};
|
|
||||||
let arch_idx = *arch.entity_indexes().get(&en).unwrap();
|
|
||||||
|
|
||||||
let col = match arch.get_column(tyid) {
|
|
||||||
Some(col) => col,
|
|
||||||
None => {
|
|
||||||
// the entity doesn't have the component
|
|
||||||
return Ok(LuaQueryResult::FilterDeny);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let col_ptr = col.component_ptr_non_tick(*arch_idx as usize).cast();
|
|
||||||
|
|
||||||
// get the type registry to apply the new value
|
|
||||||
let reg = world.get_resource::<TypeRegistry>().unwrap();
|
|
||||||
let reg_type = reg.get_type(tyid).unwrap();
|
|
||||||
|
|
||||||
let proxy = reg_type
|
|
||||||
.get_data::<ReflectLuaProxy>()
|
|
||||||
// this should actually be safe since the ReflectedIterator
|
|
||||||
// attempts to get the type data before it is tried here
|
|
||||||
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
|
||||||
Ok(LuaQueryResult::Some(proxy.as_lua(lua, col_ptr)?))
|
|
||||||
}
|
|
||||||
ReflectBranch::Resource(res) => {
|
|
||||||
// Check if the resource was changed. Per API spec, must return false.
|
|
||||||
match res.reflect_is_changed(&world) {
|
|
||||||
Some(false) => {
|
|
||||||
return Ok(LuaQueryResult::FilterDeny);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// the resource was not found
|
|
||||||
return Ok(LuaQueryResult::AlwaysNone);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unwrap is safe here since the match above would verify that the
|
|
||||||
// resource exists.
|
|
||||||
let res_ptr = res.reflect_ptr(&mut world).unwrap();
|
|
||||||
let reg_type = world
|
|
||||||
.get_type::<RegisteredType>(tyid)
|
|
||||||
.expect("Resource is not type registered!");
|
|
||||||
let proxy = reg_type
|
|
||||||
.get_data::<ReflectLuaProxy>()
|
|
||||||
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
|
||||||
|
|
||||||
Ok(LuaQueryResult::Some(
|
|
||||||
proxy
|
|
||||||
.as_lua(lua, res_ptr.cast())
|
|
||||||
.and_then(|ud| ud.into_lua(lua))?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
use crate::{
|
|
||||||
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
|
||||||
ScriptEntity, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::LuaQueryResult;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaHasQuery(LuaComponent);
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaHasQuery {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "HasQuery".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for LuaHasQuery {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, comp: LuaComponent| {
|
|
||||||
let reflect = comp.reflect_type()?;
|
|
||||||
if !reflect.reflect_branch.is_component() {
|
|
||||||
Err(mlua::Error::runtime("provided type is not a component!"))
|
|
||||||
} else {
|
|
||||||
Ok(Self(comp))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
methods.add_method(
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
|
||||||
let world = world.write();
|
|
||||||
let reflect = this.0.reflect_type()?;
|
|
||||||
|
|
||||||
let tyid = reflect.reflect_branch.reflect_type_id();
|
|
||||||
|
|
||||||
// try to find the entity's archetype and the component column in the archetype
|
|
||||||
let arch = match world.entity_archetype(*en) {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return Ok(LuaQueryResult::FilterDeny)
|
|
||||||
};
|
|
||||||
let component_col = arch.get_column(tyid);
|
|
||||||
|
|
||||||
if component_col.is_some() {
|
|
||||||
Ok(LuaQueryResult::FilterPass)
|
|
||||||
} else {
|
|
||||||
Ok(LuaQueryResult::FilterDeny)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,183 +0,0 @@
|
||||||
mod res;
|
|
||||||
pub use res::*;
|
|
||||||
|
|
||||||
mod changed;
|
|
||||||
pub use changed::*;
|
|
||||||
|
|
||||||
mod has;
|
|
||||||
pub use has::*;
|
|
||||||
|
|
||||||
mod not;
|
|
||||||
pub use not::*;
|
|
||||||
|
|
||||||
mod or;
|
|
||||||
pub use or::*;
|
|
||||||
|
|
||||||
mod tick_of;
|
|
||||||
pub use tick_of::*;
|
|
||||||
|
|
||||||
mod optional;
|
|
||||||
pub use optional::*;
|
|
||||||
|
|
||||||
use lyra_ecs::Entity;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
|
||||||
ScriptEntity, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
enum QueryInner {
|
|
||||||
Component(LuaComponent),
|
|
||||||
Function(mlua::Function),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaQuery(QueryInner);
|
|
||||||
|
|
||||||
impl LuaQuery {
|
|
||||||
pub fn new(query: LuaComponent) -> Self {
|
|
||||||
Self(QueryInner::Component(query))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_function(f: mlua::Function) -> Self {
|
|
||||||
Self(QueryInner::Function(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the result of the query
|
|
||||||
///
|
|
||||||
/// > WARNING: ensure that the world pointer is not locked. If its locked when you call this,
|
|
||||||
/// you WILL cause a deadlock.
|
|
||||||
pub fn get_query_result(
|
|
||||||
&self,
|
|
||||||
world: ScriptWorldPtr,
|
|
||||||
entity: Entity,
|
|
||||||
) -> mlua::Result<LuaQueryResult> {
|
|
||||||
let lua_en = ScriptEntity(entity);
|
|
||||||
match &self.0 {
|
|
||||||
QueryInner::Component(comp) => {
|
|
||||||
comp.call_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, (world, lua_en))
|
|
||||||
}
|
|
||||||
QueryInner::Function(function) => function.call((world, lua_en)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaQuery {
|
|
||||||
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
|
|
||||||
if let Some(f) = value.as_function() {
|
|
||||||
Ok(Self(QueryInner::Function(f.clone())))
|
|
||||||
} else if let Ok(c) = LuaComponent::from_lua(value, lua) {
|
|
||||||
Ok(Self(QueryInner::Component(c)))
|
|
||||||
} else {
|
|
||||||
Err(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "Query".into(),
|
|
||||||
message: Some("expected query function, table, or user data".into()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum LuaQueryResult {
|
|
||||||
None,
|
|
||||||
AlwaysNone,
|
|
||||||
FilterPass,
|
|
||||||
FilterDeny,
|
|
||||||
Some(mlua::Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::IntoLua for LuaQueryResult {
|
|
||||||
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
|
||||||
let t = lua.create_table()?;
|
|
||||||
t.set("enum_ty", "query_result")?;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
LuaQueryResult::None => {
|
|
||||||
t.set("result", "none")?;
|
|
||||||
}
|
|
||||||
LuaQueryResult::AlwaysNone => {
|
|
||||||
t.set("result", "always_none")?;
|
|
||||||
}
|
|
||||||
LuaQueryResult::FilterPass => {
|
|
||||||
t.set("result", "filter_pass")?;
|
|
||||||
}
|
|
||||||
LuaQueryResult::FilterDeny => {
|
|
||||||
t.set("result", "filter_deny")?;
|
|
||||||
}
|
|
||||||
LuaQueryResult::Some(value) => {
|
|
||||||
t.set("result", "some")?;
|
|
||||||
t.set("val", value)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.into_lua(lua)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn from_lua_error_query_result(ty: &'static str, msg: &str) -> mlua::Error {
|
|
||||||
mlua::Error::FromLuaConversionError {
|
|
||||||
from: ty,
|
|
||||||
to: "QueryResult".into(),
|
|
||||||
message: Some(msg.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn malformed_table_error_query_result(ty: &'static str, missing_field: &str) -> mlua::Error {
|
|
||||||
mlua::Error::FromLuaConversionError {
|
|
||||||
from: ty,
|
|
||||||
to: "QueryResult".into(),
|
|
||||||
message: Some(format!(
|
|
||||||
"malformed table, cannot convert, failed to get field '{}'",
|
|
||||||
missing_field
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaQueryResult {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let ty = value.type_name();
|
|
||||||
let table = value
|
|
||||||
.as_table()
|
|
||||||
.ok_or(from_lua_error_query_result(ty, "expected Table"))?;
|
|
||||||
|
|
||||||
let var_name: String = table
|
|
||||||
.get("enum_ty")
|
|
||||||
.map_err(|_| malformed_table_error_query_result(ty, "enum_ty"))?;
|
|
||||||
if var_name != "query_result" {
|
|
||||||
return Err(mlua::Error::FromLuaConversionError {
|
|
||||||
from: ty,
|
|
||||||
to: "QueryResult".into(),
|
|
||||||
message: Some(format!("mismatched enum_ty: '{}'", var_name)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: String = table
|
|
||||||
.get("result")
|
|
||||||
.map_err(|_| malformed_table_error_query_result(ty, "result"))?;
|
|
||||||
let result_str = result.as_str();
|
|
||||||
|
|
||||||
match result_str {
|
|
||||||
"none" => Ok(Self::None),
|
|
||||||
"always_none" => Ok(Self::AlwaysNone),
|
|
||||||
"filter_pass" => Ok(Self::FilterPass),
|
|
||||||
"filter_deny" => Ok(Self::FilterDeny),
|
|
||||||
"some" => {
|
|
||||||
let val: mlua::Value = table
|
|
||||||
.get("val")
|
|
||||||
.map_err(|_| malformed_table_error_query_result(ty, "val"))?;
|
|
||||||
Ok(Self::Some(val))
|
|
||||||
}
|
|
||||||
_ => Err(mlua::Error::FromLuaConversionError {
|
|
||||||
from: ty,
|
|
||||||
to: "QueryResult".into(),
|
|
||||||
message: Some(format!("unknown result type: '{}'", result_str)),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
use crate::{
|
|
||||||
lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
ScriptEntity, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{LuaQuery, LuaQueryResult};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaNotQuery(LuaQuery);
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaNotQuery {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "NotQuery".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for LuaNotQuery {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, q: LuaQuery| {
|
|
||||||
Ok(Self(q))
|
|
||||||
});
|
|
||||||
|
|
||||||
methods.add_method(
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
|
||||||
let res = this.0.get_query_result(world, en.0)?;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
LuaQueryResult::None => Ok(LuaQueryResult::FilterPass),
|
|
||||||
LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::FilterPass),
|
|
||||||
LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterDeny),
|
|
||||||
LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass),
|
|
||||||
LuaQueryResult::Some(_) => Ok(LuaQueryResult::FilterDeny),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
use crate::{
|
|
||||||
lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{LuaQuery, LuaQueryResult};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaOptionalQuery(LuaQuery);
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaOptionalQuery {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "OptionalQuery".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for LuaOptionalQuery {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, q: LuaQuery| Ok(Self(q)));
|
|
||||||
|
|
||||||
methods.add_method(
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
|
||||||
let res = this.0.get_query_result(world, en.0)?;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
LuaQueryResult::None => Ok(LuaQueryResult::Some(mlua::Value::Nil)),
|
|
||||||
LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::Some(mlua::Value::Nil)),
|
|
||||||
LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterPass),
|
|
||||||
LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass),
|
|
||||||
LuaQueryResult::Some(v) => Ok(LuaQueryResult::Some(v)),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
use crate::{lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr};
|
|
||||||
|
|
||||||
use super::{LuaQuery, LuaQueryResult};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaOrQuery(Vec<LuaQuery>);
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaOrQuery {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "OrQuery".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for LuaOrQuery {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, qs: mlua::Variadic<LuaQuery>| {
|
|
||||||
Ok(Self(qs.to_vec()))
|
|
||||||
});
|
|
||||||
|
|
||||||
methods.add_method(
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
|
||||||
for q in &this.0 {
|
|
||||||
let res = q.get_query_result(world.clone(), en.0)?;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
LuaQueryResult::None
|
|
||||||
| LuaQueryResult::AlwaysNone
|
|
||||||
| LuaQueryResult::FilterDeny => {}
|
|
||||||
LuaQueryResult::FilterPass => return Ok(LuaQueryResult::FilterPass),
|
|
||||||
LuaQueryResult::Some(v) => return Ok(LuaQueryResult::Some(v)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(LuaQueryResult::FilterDeny)
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
use lyra_reflect::{ReflectWorldExt, RegisteredType};
|
|
||||||
use mlua::IntoLua;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
|
||||||
ScriptEntity, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::LuaQueryResult;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaResQuery {
|
|
||||||
ty: LuaComponent,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaResQuery {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "ResQuery".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for LuaResQuery {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, comp: LuaComponent| Ok(Self { ty: comp }));
|
|
||||||
|
|
||||||
methods.add_method(
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
|lua, this, (world, _): (ScriptWorldPtr, ScriptEntity)| {
|
|
||||||
let mut world = world.write();
|
|
||||||
let reflect = this.ty.reflect_type()?;
|
|
||||||
|
|
||||||
let res = reflect.reflect_branch.as_resource_unchecked();
|
|
||||||
if let Some(res_ptr) = res.reflect_ptr(&mut world) {
|
|
||||||
let reg_type = world
|
|
||||||
.get_type::<RegisteredType>(reflect.reflect_branch.reflect_type_id())
|
|
||||||
.expect("Resource is not type registered!");
|
|
||||||
let proxy = reg_type
|
|
||||||
.get_data::<ReflectLuaProxy>()
|
|
||||||
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
|
||||||
|
|
||||||
Ok(LuaQueryResult::Some(
|
|
||||||
proxy
|
|
||||||
.as_lua(lua, res_ptr.cast())
|
|
||||||
.and_then(|ud| ud.into_lua(lua))?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
// if the resource is not found in the world, return nil
|
|
||||||
//Ok(mlua::Value::Nil)
|
|
||||||
Ok(LuaQueryResult::AlwaysNone)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
use crate::{
|
|
||||||
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
|
|
||||||
ReflectBranch, ScriptEntity, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::LuaQueryResult;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LuaTickOfQuery(LuaComponent);
|
|
||||||
|
|
||||||
impl mlua::FromLua for LuaTickOfQuery {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "TickOfQuery".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for LuaTickOfQuery {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp)));
|
|
||||||
|
|
||||||
methods.add_method(
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|
|
||||||
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
|
|
||||||
let world = world.read();
|
|
||||||
let reflect = this.0.reflect_type()?;
|
|
||||||
match &reflect.reflect_branch {
|
|
||||||
ReflectBranch::Component(comp) => {
|
|
||||||
if let Some(tick) = comp.reflect_tick(&world, *en) {
|
|
||||||
Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _)))
|
|
||||||
} else {
|
|
||||||
Ok(LuaQueryResult::FilterDeny)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReflectBranch::Resource(res) => {
|
|
||||||
if let Some(tick) = res.reflect_tick(&world) {
|
|
||||||
Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _)))
|
|
||||||
} else {
|
|
||||||
Ok(LuaQueryResult::FilterDeny)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,345 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use atomic_refcell::AtomicRefCell;
|
|
||||||
use lyra_ecs::{
|
|
||||||
query::dynamic::{DynamicViewState, QueryDynamicType},
|
|
||||||
Entity,
|
|
||||||
};
|
|
||||||
use mlua::{IntoLua, IntoLuaMulti, ObjectLike};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
lua::{
|
|
||||||
LuaComponent, LuaEntityRef, ReflectedIteratorOwned, TypeLookup, WorldError,
|
|
||||||
FN_NAME_INTERNAL_ECS_QUERY_RESULT, FN_NAME_INTERNAL_REFLECT_TYPE,
|
|
||||||
},
|
|
||||||
ScriptBorrow, ScriptWorldPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::query::{LuaQuery, LuaQueryResult};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) enum ViewQueryItem {
|
|
||||||
UserData(mlua::AnyUserData),
|
|
||||||
Table(mlua::Table),
|
|
||||||
Function(mlua::Function),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for ViewQueryItem {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
match value {
|
|
||||||
mlua::Value::Table(table) => Ok(Self::Table(table)),
|
|
||||||
mlua::Value::Function(function) => Ok(Self::Function(function)),
|
|
||||||
mlua::Value::UserData(any_user_data) => Ok(Self::UserData(any_user_data)),
|
|
||||||
_ => Err(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "ViewQueryItem".into(),
|
|
||||||
message: Some("expected Table, Function, or UserData".into()),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewQueryItem {
|
|
||||||
/// Returns `true` if the QueryItem has a function of `name`.
|
|
||||||
///
|
|
||||||
/// Returns `false` if self is a function.
|
|
||||||
pub fn has_function(&self, name: &str) -> mlua::Result<bool> {
|
|
||||||
match self {
|
|
||||||
Self::UserData(ud) => ud.get::<mlua::Value>(name).map(|v| !v.is_nil()),
|
|
||||||
Self::Table(t) => t.contains_key(name),
|
|
||||||
Self::Function(_) => Ok(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if self is a Query.
|
|
||||||
///
|
|
||||||
/// If self is a function, it will return true. Else, it checks for a function with the
|
|
||||||
/// name of [`FN_NAME_INTERNAL_ECS_QUERY_RESULT`] on the table or userdata. If the function
|
|
||||||
/// is found, it returns true.
|
|
||||||
pub fn is_query(&self) -> mlua::Result<bool> {
|
|
||||||
Ok(matches!(self, ViewQueryItem::Function(_))
|
|
||||||
|| self.has_function(FN_NAME_INTERNAL_ECS_QUERY_RESULT)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get self as a [`LuaQuery`].
|
|
||||||
///
|
|
||||||
/// If self is a function, it assumes that it is a filter.
|
|
||||||
pub fn as_query(&self) -> LuaQuery {
|
|
||||||
match self.clone() {
|
|
||||||
ViewQueryItem::UserData(ud) => LuaQuery::new(LuaComponent::UserData(ud)),
|
|
||||||
ViewQueryItem::Table(t) => LuaQuery::new(LuaComponent::Table(t)),
|
|
||||||
ViewQueryItem::Function(function) => LuaQuery::from_function(function),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct View {
|
|
||||||
pub(crate) items: Vec<ViewQueryItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for View {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "View".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for View {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_, args: mlua::Variadic<ViewQueryItem>| {
|
|
||||||
Ok(Self {
|
|
||||||
items: args.iter().cloned().collect(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Results of queries in a View.
|
|
||||||
///
|
|
||||||
/// Represents the results of multiple queries.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub(crate) enum ViewQueryResult {
|
|
||||||
None,
|
|
||||||
AlwaysNone,
|
|
||||||
FilterDeny,
|
|
||||||
/// The results of the queries and the index they should be inserted at in the resulting row.
|
|
||||||
Some(Vec<(mlua::Value, u32)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ViewResult {
|
|
||||||
world: ScriptWorldPtr,
|
|
||||||
reflected_iter: Arc<atomic_refcell::AtomicRefCell<ReflectedIteratorOwned>>,
|
|
||||||
/// The queries and the index they would be inserted in the result.
|
|
||||||
queries: Vec<(LuaQuery, u32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for ViewResult {}
|
|
||||||
|
|
||||||
impl ViewResult {
|
|
||||||
pub fn new(world: ScriptWorldPtr, view: &View) -> Result<Self, mlua::Error> {
|
|
||||||
let items = view.items.clone();
|
|
||||||
let w = world.read();
|
|
||||||
let mut view = DynamicViewState::new();
|
|
||||||
let mut queries = vec![];
|
|
||||||
|
|
||||||
for (idx, comp) in items.iter().enumerate() {
|
|
||||||
if comp.is_query()? {
|
|
||||||
queries.push((comp.as_query(), idx as u32));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match comp {
|
|
||||||
ViewQueryItem::Table(t) => {
|
|
||||||
let name: String = t.get(mlua::MetaMethod::Type.name())?;
|
|
||||||
|
|
||||||
let lookup = w.get_resource::<TypeLookup>().ok_or(mlua::Error::runtime(
|
|
||||||
"Unable to lookup table proxy, none were ever registered!",
|
|
||||||
))?;
|
|
||||||
let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| {
|
|
||||||
mlua::Error::BadArgument {
|
|
||||||
to: Some("ViewResult.new".into()),
|
|
||||||
pos: 2 + idx,
|
|
||||||
name: Some("query...".into()),
|
|
||||||
cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage(
|
|
||||||
format!("the 'Table' with name {} is unknown to the engine!", name),
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let dyn_type = QueryDynamicType::from_info(info.clone());
|
|
||||||
view.push(dyn_type);
|
|
||||||
}
|
|
||||||
ViewQueryItem::UserData(ud) => {
|
|
||||||
let reflect = ud
|
|
||||||
.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
|
|
||||||
.expect("Type does not implement 'reflect_type' properly");
|
|
||||||
let refl_comp = reflect.reflect_branch.as_component()
|
|
||||||
.expect("`self` is not an instance of `ReflectBranch::Component`");
|
|
||||||
|
|
||||||
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
|
|
||||||
view.push(dyn_type);
|
|
||||||
}
|
|
||||||
// functions are queries, the if statement at the start would cause this to
|
|
||||||
// be unreachable.
|
|
||||||
ViewQueryItem::Function(_) => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(w);
|
|
||||||
|
|
||||||
let view_iter = view.into_iter();
|
|
||||||
let reflected_iter = ReflectedIteratorOwned {
|
|
||||||
world_ptr: world.clone(),
|
|
||||||
dyn_view: view_iter,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
world,
|
|
||||||
reflected_iter: Arc::new(AtomicRefCell::new(reflected_iter)),
|
|
||||||
queries,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the next row of components
|
|
||||||
fn next_components(
|
|
||||||
&mut self,
|
|
||||||
lua: &mlua::Lua,
|
|
||||||
) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> {
|
|
||||||
let mut query_iter = self.reflected_iter.borrow_mut();
|
|
||||||
if let Some(row) = query_iter.next_lua(lua) {
|
|
||||||
let (values, _): (Vec<_>, Vec<_>) = row
|
|
||||||
.row
|
|
||||||
.into_iter()
|
|
||||||
.into_iter()
|
|
||||||
.map(|r| (r.comp_val, r.comp_ptr.cast::<()>()))
|
|
||||||
.unzip();
|
|
||||||
let mult_val = mlua::MultiValue::from_iter(values.into_iter());
|
|
||||||
Ok(Some((row.entity, mult_val)))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the query results and the indexes that they were provided in.
|
|
||||||
///
|
|
||||||
/// The indexes are used to make sure that the results are in the same order that the script
|
|
||||||
/// requested them in.
|
|
||||||
fn get_query_results(&self, entity: Entity) -> mlua::Result<ViewQueryResult> {
|
|
||||||
let mut query_vals = vec![];
|
|
||||||
|
|
||||||
// A modifier is used that will be incremented every time a filter allowed the query.
|
|
||||||
// this is used to remove the value of a filter without leaving a gap in the results.
|
|
||||||
let mut index_mod = 0;
|
|
||||||
for (query, i) in &self.queries {
|
|
||||||
let qres = query.get_query_result(self.world.clone(), entity)?;
|
|
||||||
|
|
||||||
match qres {
|
|
||||||
LuaQueryResult::None => return Ok(ViewQueryResult::None),
|
|
||||||
LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone),
|
|
||||||
LuaQueryResult::FilterPass => {
|
|
||||||
// do not push a boolean to values, its considered a filter
|
|
||||||
index_mod += 1;
|
|
||||||
},
|
|
||||||
LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny),
|
|
||||||
LuaQueryResult::Some(value) => {
|
|
||||||
let idx = (*i - index_mod).max(0);
|
|
||||||
query_vals.push((value, idx));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ViewQueryResult::Some(query_vals))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for ViewResult {
|
|
||||||
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
let tyname = value.type_name();
|
|
||||||
value
|
|
||||||
.as_userdata()
|
|
||||||
.ok_or(mlua::Error::FromLuaConversionError {
|
|
||||||
from: tyname,
|
|
||||||
to: "View".into(),
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
.and_then(|ud| ud.borrow::<Self>())
|
|
||||||
.map(|ud| ud.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for ViewResult {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_method_mut("next", |lua, this, ()| {
|
|
||||||
match this.next_components(lua)? {
|
|
||||||
Some((en, mut vals)) => {
|
|
||||||
loop {
|
|
||||||
let query_vals = match this.get_query_results(en)? {
|
|
||||||
ViewQueryResult::Some(v) => v,
|
|
||||||
ViewQueryResult::AlwaysNone => {
|
|
||||||
return mlua::Value::Nil.into_lua_multi(lua);
|
|
||||||
},
|
|
||||||
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
|
|
||||||
// try to get it next loop
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// insert query values to the result row
|
|
||||||
for (qval, qi) in query_vals {
|
|
||||||
vals.insert(qi as _, qval);
|
|
||||||
}
|
|
||||||
|
|
||||||
vals.push_front(LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?);
|
|
||||||
return Ok(vals);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => mlua::Value::Nil.into_lua_multi(lua),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
methods.add_method("iter", |lua, this, ()| {
|
|
||||||
let key_arc = Arc::new(atomic_refcell::AtomicRefCell::new(Some(
|
|
||||||
lua.create_registry_value(this.clone())?,
|
|
||||||
)));
|
|
||||||
|
|
||||||
lua.create_function(move |lua, ()| {
|
|
||||||
let mut key_mut = key_arc.borrow_mut();
|
|
||||||
|
|
||||||
if let Some(key) = key_mut.as_ref() {
|
|
||||||
let mut this = lua.registry_value::<mlua::UserDataRefMut<Self>>(&key)?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match this.next_components(lua)? {
|
|
||||||
Some((en, mut vals)) => {
|
|
||||||
let lua_en =
|
|
||||||
LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?;
|
|
||||||
|
|
||||||
let query_vals = match this.get_query_results(en)? {
|
|
||||||
ViewQueryResult::Some(v) => v,
|
|
||||||
ViewQueryResult::AlwaysNone => {
|
|
||||||
return mlua::Value::Nil.into_lua_multi(lua);
|
|
||||||
},
|
|
||||||
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
|
|
||||||
// try to get it next loop
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// insert query values to the result row
|
|
||||||
for (qval, qi) in query_vals {
|
|
||||||
vals.insert(qi as _, qval);
|
|
||||||
}
|
|
||||||
|
|
||||||
vals.push_front(lua_en);
|
|
||||||
return Ok(vals);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// If this is the last row, remove the registry value
|
|
||||||
// This doesn't protect against iterators that aren't fully consumed,
|
|
||||||
// that would cause a leak in the lua registry.
|
|
||||||
// TODO: fix leak
|
|
||||||
let key = key_mut.take().unwrap();
|
|
||||||
lua.remove_registry_value(key)?;
|
|
||||||
|
|
||||||
return mlua::Value::Nil.into_lua_multi(lua);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mlua::Value::Nil.into_lua_multi(lua)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue