Compare commits
45 Commits
debug/sig-
...
main
Author | SHA1 | Date |
---|---|---|
SeanOMik | 2b44d7f354 | |
SeanOMik | 60c139f9b2 | |
SeanOMik | 256025849e | |
SeanOMik | 8545e7e27d | |
SeanOMik | a85178eeea | |
SeanOMik | 8c1738334c | |
SeanOMik | fefcf58765 | |
SeanOMik | b0a6d30afc | |
SeanOMik | fef709d5f1 | |
SeanOMik | c91ee67961 | |
SeanOMik | c961568b96 | |
SeanOMik | 54b47c2178 | |
SeanOMik | 4449172c2b | |
SeanOMik | 4c6c6c4dd5 | |
SeanOMik | 27bc88c5a7 | |
SeanOMik | ff06bd55f3 | |
SeanOMik | d02258224a | |
SeanOMik | b45c2f4fab | |
SeanOMik | 40fa9c09da | |
SeanOMik | 87aa440691 | |
SeanOMik | cc1c482c40 | |
SeanOMik | a4ce4cb432 | |
SeanOMik | e2b554b4ef | |
SeanOMik | 6d57b40629 | |
SeanOMik | fd65f754cf | |
SeanOMik | 6c6893149a | |
SeanOMik | 1c649b2eb6 | |
SeanOMik | 7b2d2424a3 | |
SeanOMik | e8974bbd44 | |
SeanOMik | 3a80c069c9 | |
SeanOMik | 7ff67a194b | |
SeanOMik | c4aebdb25d | |
SeanOMik | 5ebbec8cf9 | |
SeanOMik | 2eeca335e2 | |
SeanOMik | 96dea5b1f9 | |
SeanOMik | 007b1047ef | |
SeanOMik | 545da71cda | |
SeanOMik | 0e71c5734f | |
SeanOMik | 6c1bff5768 | |
SeanOMik | 5f1a61ef52 | |
SeanOMik | f755a4c53b | |
SeanOMik | 9d3de88c50 | |
SeanOMik | f440f306be | |
SeanOMik | f17bf40c77 | |
SeanOMik | 5fc1a0f134 |
|
@ -0,0 +1,31 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
container: git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
apt update
|
||||
apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo test --all
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "lyra-scripting/elua"]
|
||||
path = lyra-scripting/elua
|
||||
url = ../elua.git # git@git.seanomik.net:SeanOMik/elua.git
|
||||
[submodule "wgsl-preprocessor"]
|
||||
path = wgsl-preprocessor
|
||||
url = git@git.seanomik.net:SeanOMik/wgsl-preprocessor.git
|
||||
|
|
|
@ -22,6 +22,24 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/testbed"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug lyra shadows",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path", "${workspaceFolder}/examples/shadows/Cargo.toml"
|
||||
//"--bin=shadows",
|
||||
],
|
||||
"filter": {
|
||||
"name": "shadows",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/shadows"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
variables:
|
||||
- &rust_image 'git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm'
|
||||
|
||||
when:
|
||||
event: [push, manual, pull_request]
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
Build - Debug:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo build
|
||||
|
||||
Test - Debug:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo test --all
|
|
@ -1,20 +0,0 @@
|
|||
variables:
|
||||
- &rust_image 'git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm'
|
||||
|
||||
when:
|
||||
event: [release, pull_request, manual]
|
||||
|
||||
steps:
|
||||
Build - Release:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo build --release
|
||||
|
||||
Test - Release:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo test --all --release
|
|
@ -65,6 +65,24 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
|
||||
dependencies = [
|
||||
"as-slice",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned-array"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c92d086290f52938013f6242ac62bf7d401fab8ad36798a609faa65c3fd2c"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.16"
|
||||
|
@ -122,6 +140,15 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ash"
|
||||
version = "0.37.3+1.3.251"
|
||||
|
@ -351,6 +378,12 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
|
@ -786,6 +819,12 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "divrem"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82"
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
|
@ -795,6 +834,12 @@ dependencies = [
|
|||
"libloading 0.8.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.0"
|
||||
|
@ -807,6 +852,12 @@ version = "1.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "elapsed"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f4e5af126dafd0741c2ad62d47f68b28602550102e5f0dd45c8a97fc8b49c29"
|
||||
|
||||
[[package]]
|
||||
name = "elua"
|
||||
version = "0.1.0"
|
||||
|
@ -908,6 +959,18 @@ dependencies = [
|
|||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast_poisson"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2472baa9796d2ee497bd61690e3093a26935390d8ce0dd0ddc2db9b47a65898f"
|
||||
dependencies = [
|
||||
"kiddo",
|
||||
"rand 0.8.5",
|
||||
"rand_distr",
|
||||
"rand_xoshiro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
|
@ -953,6 +1016,19 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-timestep-rotating-model"
|
||||
version = "0.1.0"
|
||||
|
@ -1683,6 +1759,26 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kiddo"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1c5ea778d68eacd5c33f29537ba0b7b6c2595e74ee013a69cedc20ab4d3177"
|
||||
dependencies = [
|
||||
"aligned",
|
||||
"aligned-array",
|
||||
"az",
|
||||
"divrem",
|
||||
"doc-comment",
|
||||
"elapsed",
|
||||
"fixed",
|
||||
"log",
|
||||
"min-max-heap",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
|
@ -1750,6 +1846,12 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.0.2"
|
||||
|
@ -1868,6 +1970,7 @@ dependencies = [
|
|||
"bind_match",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"fast_poisson",
|
||||
"gilrs-core",
|
||||
"glam",
|
||||
"image",
|
||||
|
@ -1881,6 +1984,7 @@ dependencies = [
|
|||
"lyra-scene",
|
||||
"petgraph",
|
||||
"quote",
|
||||
"round_mult",
|
||||
"rustc-hash",
|
||||
"syn 2.0.51",
|
||||
"thiserror",
|
||||
|
@ -1892,6 +1996,7 @@ dependencies = [
|
|||
"unique",
|
||||
"uuid",
|
||||
"wgpu",
|
||||
"wgsl_preprocessor",
|
||||
"winit",
|
||||
]
|
||||
|
||||
|
@ -2078,6 +2183,12 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "min-max-heap"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2687e6cf9c00f48e9284cf9fd15f2ef341d03cc7743abf9df4c5f07fdee50b18"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
@ -2285,6 +2396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2531,6 +2643,51 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.5"
|
||||
|
@ -2726,6 +2883,25 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_distr"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
version = "0.1.3"
|
||||
|
@ -2787,9 +2963,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
version = "1.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -2884,6 +3060,15 @@ dependencies = [
|
|||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "round_mult"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74bc7d5286c4d36f09aa6ae93f76acf6aa068cd62bc02970a9deb24763655dee"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
@ -2896,6 +3081,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.27"
|
||||
|
@ -3010,6 +3204,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.194"
|
||||
|
@ -3075,6 +3275,16 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shadows"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"lyra-engine",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
@ -3191,6 +3401,12 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -3302,18 +3518,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -3606,6 +3822,12 @@ version = "1.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
|
@ -3988,6 +4210,18 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgsl_preprocessor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"regex",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.5.1"
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -5,7 +5,6 @@ edition = "2021"
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"examples/testbed",
|
||||
"lyra-resource",
|
||||
"lyra-ecs",
|
||||
"lyra-reflect",
|
||||
|
@ -14,10 +13,12 @@ members = [
|
|||
"lyra-math",
|
||||
"lyra-scene",
|
||||
|
||||
"examples/testbed",
|
||||
"examples/many-lights",
|
||||
"examples/fixed-timestep-rotating-model",
|
||||
"examples/lua-scripting",
|
||||
"examples/simple_scene"
|
||||
"examples/simple_scene",
|
||||
"examples/shadows",
|
||||
]
|
||||
|
||||
[features]
|
||||
|
@ -29,8 +30,8 @@ tracy = ["lyra-game/tracy"]
|
|||
lyra-game = { path = "lyra-game" }
|
||||
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
#[profile.dev]
|
||||
#opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
debug = true
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,142 @@
|
|||
{
|
||||
"asset":{
|
||||
"generator":"Khronos glTF Blender I/O v4.1.63",
|
||||
"version":"2.0"
|
||||
},
|
||||
"scene":0,
|
||||
"scenes":[
|
||||
{
|
||||
"name":"Scene",
|
||||
"nodes":[
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes":[
|
||||
{
|
||||
"mesh":0,
|
||||
"name":"Cube",
|
||||
"scale":[
|
||||
10,
|
||||
0.25,
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"materials":[
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Material.001",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorTexture":{
|
||||
"index":0
|
||||
},
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes":[
|
||||
{
|
||||
"name":"Cube.001",
|
||||
"primitives":[
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":0,
|
||||
"NORMAL":1,
|
||||
"TEXCOORD_0":2
|
||||
},
|
||||
"indices":3,
|
||||
"material":0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures":[
|
||||
{
|
||||
"sampler":0,
|
||||
"source":0
|
||||
}
|
||||
],
|
||||
"images":[
|
||||
{
|
||||
"mimeType":"image/jpeg",
|
||||
"name":"wood1",
|
||||
"uri":"wood1.jpg"
|
||||
}
|
||||
],
|
||||
"accessors":[
|
||||
{
|
||||
"bufferView":0,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"max":[
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"min":[
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":1,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":2,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":3,
|
||||
"componentType":5123,
|
||||
"count":36,
|
||||
"type":"SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews":[
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":288,
|
||||
"byteOffset":0,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":288,
|
||||
"byteOffset":288,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":192,
|
||||
"byteOffset":576,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":72,
|
||||
"byteOffset":768,
|
||||
"target":34963
|
||||
}
|
||||
],
|
||||
"samplers":[
|
||||
{
|
||||
"magFilter":9729,
|
||||
"minFilter":9987
|
||||
}
|
||||
],
|
||||
"buffers":[
|
||||
{
|
||||
"byteLength":840,
|
||||
"uri":"model.bin"
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "shadows"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-engine = { path = "../../", features = ["tracy"] }
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
tracing = "0.1.37"
|
|
@ -0,0 +1,59 @@
|
|||
---Return the userdata's name from its metatable
|
||||
---@param val userdata
|
||||
---@return string
|
||||
function udname(val)
|
||||
return getmetatable(val).__name
|
||||
end
|
||||
|
||||
function on_init()
|
||||
local cube = world:request_res("../assets/cube-texture-embedded.gltf")
|
||||
print("Loaded textured cube (" .. udname(cube) .. ")")
|
||||
|
||||
cube:wait_until_loaded()
|
||||
local scenes = cube:scenes()
|
||||
local cube_scene = scenes[1]
|
||||
|
||||
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
|
||||
|
||||
local e = world:spawn(pos, cube_scene)
|
||||
print("spawned entity " .. tostring(e))
|
||||
end
|
||||
|
||||
--[[ function on_first()
|
||||
print("Lua's first function was called")
|
||||
end
|
||||
|
||||
function on_pre_update()
|
||||
print("Lua's pre-update function was called")
|
||||
end ]]
|
||||
|
||||
function on_update()
|
||||
--[[ ---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
local act = world:resource(ActionHandler)
|
||||
---@type number
|
||||
local move_objs = act:get_axis("ObjectsMoveUpDown")
|
||||
|
||||
world:view(function (t)
|
||||
if move_objs ~= nil then
|
||||
t:translate(0, move_objs * 0.35 * dt, 0)
|
||||
return t
|
||||
end
|
||||
end, Transform) ]]
|
||||
|
||||
---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
|
||||
world:view(function (t)
|
||||
t:translate(0, 0.15 * dt, 0)
|
||||
return t
|
||||
end, Transform)
|
||||
end
|
||||
|
||||
--[[ function on_post_update()
|
||||
print("Lua's post-update function was called")
|
||||
end
|
||||
|
||||
function on_last()
|
||||
print("Lua's last function was called")
|
||||
end ]]
|
|
@ -0,0 +1,249 @@
|
|||
use lyra_engine::{
|
||||
assets::{gltf::Gltf, ResourceManager},
|
||||
game::Game,
|
||||
input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||
},
|
||||
math::{self, Quat, Transform, Vec3},
|
||||
render::{
|
||||
graph::{ShadowCasterSettings, ShadowFilteringMode},
|
||||
light::{directional::DirectionalLight, PointLight, SpotLight},
|
||||
},
|
||||
scene::{
|
||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||
},
|
||||
};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||
.add_action("Debug", Action::new(ActionKind::Button))
|
||||
.add_mapping(
|
||||
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_ROLL,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
"Debug",
|
||||
&[ActionSource::Keyboard(KeyCode::B).into_binding()],
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
Game::initialize()
|
||||
.await
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(game: &mut Game) {
|
||||
let world = game.world_mut();
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
|
||||
/* let camera_gltf = resman
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
.unwrap();
|
||||
|
||||
camera_gltf.wait_recurse_dependencies_load();
|
||||
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
camera_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -5.0, -2.0),
|
||||
)); */
|
||||
|
||||
let cube_gltf = resman
|
||||
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
|
||||
.unwrap();
|
||||
|
||||
cube_gltf.wait_recurse_dependencies_load();
|
||||
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
|
||||
|
||||
let palm_tree_platform_gltf = resman
|
||||
.request::<Gltf>("../assets/shadows-platform-palmtree.glb")
|
||||
.unwrap();
|
||||
|
||||
palm_tree_platform_gltf.wait_recurse_dependencies_load();
|
||||
let palm_tree_platform_mesh = &palm_tree_platform_gltf.data_ref().unwrap().scenes[0];
|
||||
|
||||
drop(resman);
|
||||
|
||||
// cube in the air
|
||||
/* world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -2.0, -5.0),
|
||||
));
|
||||
|
||||
// cube really high in the air
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(-6.0, 0.0, -5.0),
|
||||
));
|
||||
|
||||
// cube on the right, on the ground
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(3.0, -3.75, -5.0),
|
||||
));
|
||||
|
||||
world.spawn((
|
||||
platform_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
//Transform::from_xyz(0.0, -5.0, -5.0),
|
||||
Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
|
||||
)); */
|
||||
|
||||
world.spawn((
|
||||
palm_tree_platform_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(5.0, -15.0, 0.0),
|
||||
//Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
|
||||
));
|
||||
|
||||
//shadows-platform-palmtree.glb
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(0.0, 0.0, 0.0);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||
light_tran.rotate_y(math::Angle::Degrees(-35.0));
|
||||
world.spawn((
|
||||
//cube_mesh.clone(),
|
||||
DirectionalLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(1.0, 0.95, 0.9),
|
||||
intensity: 0.9,
|
||||
},
|
||||
ShadowCasterSettings {
|
||||
filtering_mode: ShadowFilteringMode::Pcss,
|
||||
pcf_samples_num: 64,
|
||||
pcss_blocker_search_samples: 36,
|
||||
constant_depth_bias_scale: 5.0,
|
||||
..Default::default()
|
||||
},
|
||||
light_tran,
|
||||
));
|
||||
|
||||
/* world.spawn((
|
||||
cube_mesh.clone(),
|
||||
PointLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(0.133, 0.098, 0.91),
|
||||
intensity: 2.0,
|
||||
range: 10.0,
|
||||
..Default::default()
|
||||
},
|
||||
ShadowCasterSettings {
|
||||
filtering_mode: ShadowFilteringMode::Pcf,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::new(
|
||||
Vec3::new(4.0 - 1.43, -13.0, 1.53),
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(0.5, 0.5, 0.5),
|
||||
),
|
||||
)); */
|
||||
|
||||
let t = Transform::new(
|
||||
Vec3::new(4.0 - 1.43, -13.0, 0.0),
|
||||
//Vec3::new(-5.0, 1.0, -0.28),
|
||||
//Vec3::new(-10.0, 0.94, -0.28),
|
||||
|
||||
Quat::from_euler(math::EulerRot::XYZ, 0.0, math::Angle::Degrees(-45.0).to_radians(), 0.0),
|
||||
Vec3::new(0.15, 0.15, 0.15),
|
||||
);
|
||||
|
||||
world.spawn((
|
||||
SpotLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(1.0, 0.0, 0.0),
|
||||
intensity: 3.0,
|
||||
range: 4.5,
|
||||
//cutoff: math::Angle::Degrees(45.0),
|
||||
..Default::default()
|
||||
},
|
||||
/* ShadowCasterSettings {
|
||||
filtering_mode: ShadowFilteringMode::Pcf,
|
||||
..Default::default()
|
||||
}, */
|
||||
WorldTransform::from(t),
|
||||
t,
|
||||
//cube_mesh.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation = math::Vec3::new(-1.0, -10.0, -1.5);
|
||||
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
|
||||
camera.transform.rotate_y(math::Angle::Degrees(-90.0));
|
||||
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
|
@ -12,6 +12,13 @@ pub struct Entity {
|
|||
}
|
||||
|
||||
impl Entity {
|
||||
pub fn new(id: EntityId, gen: u64) -> Self {
|
||||
Self {
|
||||
id,
|
||||
generation: gen,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> EntityId {
|
||||
self.id
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@ use crate::{World, ComponentColumn, ComponentInfo};
|
|||
mod view;
|
||||
pub use view::*;
|
||||
|
||||
mod view_one;
|
||||
pub use view_one::*;
|
||||
|
||||
use super::Fetch;
|
||||
|
||||
/// Data that rust does not know the type of
|
||||
|
|
|
@ -57,11 +57,17 @@ pub struct DynamicViewStateIter {
|
|||
}
|
||||
|
||||
impl DynamicViewStateIter {
|
||||
pub fn next(&mut self, world: &World) -> Option<DynamicViewItem> {
|
||||
pub fn next(&mut self, world: &World) -> Option<(Entity, Vec<DynamicType>)> {
|
||||
let archetypes = world.archetypes.values().collect::<Vec<_>>();
|
||||
|
||||
loop {
|
||||
if let Some(entity_index) = self.component_indices.next() {
|
||||
let entity = {
|
||||
let arch_id = self.next_archetype - 1;
|
||||
let arch = unsafe { archetypes.get_unchecked(arch_id) };
|
||||
arch.entity_at_index(ArchetypeEntityId(entity_index)).unwrap()
|
||||
};
|
||||
|
||||
let mut fetch_res = vec![];
|
||||
|
||||
for fetcher in self.fetchers.iter_mut() {
|
||||
|
@ -78,11 +84,7 @@ impl DynamicViewStateIter {
|
|||
continue;
|
||||
}
|
||||
|
||||
let arch = archetypes.get(self.next_archetype-1).unwrap();
|
||||
return Some(DynamicViewItem {
|
||||
row: fetch_res,
|
||||
entity: arch.entity_at_index(ArchetypeEntityId(entity_index)).unwrap()
|
||||
})
|
||||
return Some((entity, fetch_res));
|
||||
} else {
|
||||
if self.next_archetype >= archetypes.len() {
|
||||
return None; // ran out of archetypes to go through
|
||||
|
@ -138,7 +140,7 @@ impl<'a> DynamicView<'a> {
|
|||
/// This works great for a embedding with a scripting language (*cough* *cough* WASM) since
|
||||
/// Rust doesn't actually need to know the types of what its iterating over.
|
||||
impl<'a> IntoIterator for DynamicView<'a> {
|
||||
type Item = DynamicViewItem;
|
||||
type Item = (Entity, Vec<DynamicType>);
|
||||
|
||||
type IntoIter = DynamicViewIter<'a>;
|
||||
|
||||
|
@ -160,7 +162,7 @@ pub struct DynamicViewIter<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for DynamicViewIter<'a> {
|
||||
type Item = DynamicViewItem;
|
||||
type Item = (Entity, Vec<DynamicType>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next(&self.world)
|
||||
|
@ -193,8 +195,8 @@ mod tests {
|
|||
view.push(query);
|
||||
|
||||
let mut view_iter = view.into_iter();
|
||||
while let Some(view_row) = view_iter.next(&world) {
|
||||
assert_eq!(view_row.row.len(), 1);
|
||||
while let Some((_e, view_row)) = view_iter.next(&world) {
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.row.iter();
|
||||
|
||||
|
@ -222,8 +224,8 @@ mod tests {
|
|||
let mut view = DynamicView::new(&world);
|
||||
view.push(query);
|
||||
|
||||
for view_row in view.into_iter() {
|
||||
assert_eq!(view_row.row.len(), 1);
|
||||
for (_e, view_row) in view.into_iter() {
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.row.iter();
|
||||
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
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,
|
||||
pub entity: Entity,
|
||||
pub queries: Vec<QueryDynamicType>
|
||||
}
|
||||
|
||||
impl<'a> DynamicViewOne<'a> {
|
||||
pub fn new(world: &'a World, entity: Entity) -> Self {
|
||||
Self {
|
||||
world,
|
||||
entity,
|
||||
queries: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicViewOne`] with queries.
|
||||
pub fn new_with(world: &'a World, entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
|
||||
Self {
|
||||
world,
|
||||
entity,
|
||||
queries
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(self) -> Option<Vec<DynamicType>> {
|
||||
let arch = self.world.entity_archetype(self.entity)?;
|
||||
let aid = arch.entity_indexes().get(&self.entity)?;
|
||||
|
||||
// get all fetchers for the queries
|
||||
let mut fetchers: Vec<FetchDynamicTypeUnsafe> = self.queries.iter()
|
||||
.map(|q| unsafe { q.fetch(self.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);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@ use std::{any::{Any, TypeId}, sync::Arc};
|
|||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||
|
||||
use crate::{Tick, TickTracker};
|
||||
|
||||
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
|
||||
pub trait ResourceObject: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
@ -23,14 +25,17 @@ impl<T: Send + Sync + Any> ResourceObject for T {
|
|||
pub struct ResourceData {
|
||||
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
|
||||
type_id: TypeId,
|
||||
// use a tick tracker which has interior mutability
|
||||
pub(crate) tick: TickTracker,
|
||||
}
|
||||
|
||||
impl ResourceData {
|
||||
pub fn new<T: ResourceObject>(data: T) -> Self {
|
||||
pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
|
||||
|
||||
Self {
|
||||
data: Arc::new(AtomicRefCell::new(data)),
|
||||
type_id: TypeId::of::<T>(),
|
||||
tick: TickTracker::from(*tick),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,4 +85,8 @@ impl ResourceData {
|
|||
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn changed(&self, tick: Tick) -> bool {
|
||||
self.tick.current() >= tick
|
||||
}
|
||||
}
|
|
@ -374,33 +374,65 @@ impl World {
|
|||
ViewOne::new(self, entity.id, T::Query::new())
|
||||
}
|
||||
|
||||
//pub fn view_one(&self, entity: EntityId) ->
|
||||
|
||||
/// Add a resource to the world.
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data));
|
||||
let tick = self.tick();
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
|
||||
}
|
||||
|
||||
/// Add the default value of a resource.
|
||||
///
|
||||
/// Ticks the world.
|
||||
///
|
||||
/// > Note: This will replace existing values.
|
||||
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default()));
|
||||
let tick = self.tick();
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), tick));
|
||||
}
|
||||
|
||||
/// Add the default value of a resource if it does not already exist.
|
||||
///
|
||||
/// Returns a boolean indicating if the resource was added. Ticks the world if the resource
|
||||
/// was added.
|
||||
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
|
||||
let id = TypeId::of::<T>();
|
||||
if !self.resources.contains_key(&id) {
|
||||
let tick = self.tick();
|
||||
self.resources.insert(id, ResourceData::new(T::default(), tick));
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a resource from the world, or insert it into the world with the provided
|
||||
/// `fn` and return it.
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
|
||||
where
|
||||
F: Fn() -> T + 'static
|
||||
{
|
||||
self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(f()))
|
||||
.get_mut()
|
||||
let tick = self.tick();
|
||||
let res = self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(f(), tick));
|
||||
res.tick.tick_to(&tick);
|
||||
res.get_mut()
|
||||
}
|
||||
|
||||
/// Get a resource from the world, or insert it into the world as its default.
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
|
||||
{
|
||||
self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(T::default()))
|
||||
.get_mut()
|
||||
let tick = self.tick();
|
||||
let res = self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(T::default(), tick));
|
||||
res.tick.tick_to(&tick);
|
||||
res.get_mut()
|
||||
}
|
||||
|
||||
/// Gets a resource from the World.
|
||||
|
@ -413,6 +445,22 @@ impl World {
|
|||
.get()
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if the resource changed.
|
||||
///
|
||||
/// This will return false if the resource doesn't exist.
|
||||
pub fn has_resource_changed<T: ResourceObject>(&self) -> bool {
|
||||
let tick = self.current_tick();
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.changed(tick))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns the [`Tick`] that the resource was last modified at.
|
||||
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.tick.current())
|
||||
}
|
||||
|
||||
/// Returns boolean indicating if the World contains a resource of type `T`.
|
||||
pub fn has_resource<T: ResourceObject>(&self) -> bool {
|
||||
self.resources.contains_key(&TypeId::of::<T>())
|
||||
|
@ -430,18 +478,32 @@ impl World {
|
|||
///
|
||||
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
|
||||
/// a function that returns an option.
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
.get_mut()
|
||||
self.try_get_resource_mut::<T>()
|
||||
.unwrap_or_else(|| panic!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
}
|
||||
|
||||
/// Attempts to get a mutable borrow of a resource from the World.
|
||||
///
|
||||
/// Returns `None` if the resource was not found.
|
||||
/// Returns `None` if the resource was not found. Ticks the world if the resource was found.
|
||||
pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.and_then(|r| r.try_get_mut())
|
||||
.and_then(|r| {
|
||||
// now that the resource was retrieved, tick the world and the resource
|
||||
let new_tick = self.tick();
|
||||
r.tick.tick_to(&new_tick);
|
||||
r.try_get_mut()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the corresponding [`ResourceData`].
|
||||
///
|
||||
/// > Note: If you borrow the resource mutably, the world and the resource will not be ticked.
|
||||
pub fn try_get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.clone())
|
||||
}
|
||||
|
||||
/// Increments the TickTracker which is used for tracking changes to components.
|
||||
|
@ -688,4 +750,22 @@ mod tests {
|
|||
let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
|
||||
assert_eq!(*pos, Vec2::new(5.0, 5.0));
|
||||
}
|
||||
|
||||
/// Tests resource change checks
|
||||
#[test]
|
||||
fn resource_changed() {
|
||||
let mut world = World::new();
|
||||
world.add_resource(SimpleCounter(50));
|
||||
|
||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||
|
||||
world.spawn(Vec2::new(50.0, 50.0));
|
||||
|
||||
assert!(!world.has_resource_changed::<SimpleCounter>());
|
||||
|
||||
let mut counter = world.get_resource_mut::<SimpleCounter>();
|
||||
counter.0 += 100;
|
||||
|
||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||
}
|
||||
}
|
|
@ -10,9 +10,10 @@ lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
|||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
lyra-math = { path = "../lyra-math" }
|
||||
lyra-scene = { path = "../lyra-scene" }
|
||||
wgsl_preprocessor = { path = "../wgsl-preprocessor" }
|
||||
|
||||
winit = "0.28.1"
|
||||
wgpu = "0.15.1"
|
||||
wgpu = { version = "0.15.1", features = [ "expose-ids"] }
|
||||
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
|
||||
|
@ -38,6 +39,8 @@ unique = "0.9.1"
|
|||
rustc-hash = "1.1.0"
|
||||
petgraph = { version = "0.6.5", features = ["matrix_graph"] }
|
||||
bind_match = "0.1.2"
|
||||
round_mult = "0.1.3"
|
||||
fast_poisson = { version = "1.0.0", features = ["single_precision"] }
|
||||
|
||||
[features]
|
||||
tracy = ["dep:tracing-tracy"]
|
||||
|
|
|
@ -160,6 +160,10 @@ impl<E: Event> EventReader<E> {
|
|||
pub fn len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Iterator for EventReader<E> {
|
||||
|
|
|
@ -356,6 +356,7 @@ impl Game {
|
|||
t.with(filter::Targets::new()
|
||||
// done by prefix, so it includes all lyra subpackages
|
||||
.with_target("lyra", Level::DEBUG)
|
||||
.with_target("wgsl_preprocessor", Level::DEBUG)
|
||||
.with_target("wgpu", Level::WARN)
|
||||
.with_target("winit", Level::DEBUG)
|
||||
.with_default(Level::INFO))
|
||||
|
|
|
@ -92,10 +92,7 @@ impl<T: Button> InputButtons<T> {
|
|||
pub fn was_just_pressed(&self, button: T) -> bool {
|
||||
let hash = Self::get_button_hash(&button);
|
||||
match self.button_events.get(&hash) {
|
||||
Some(button_event) => match button_event {
|
||||
ButtonEvent::JustPressed(b) if button == *b => true,
|
||||
_ => false,
|
||||
},
|
||||
Some(button_event) => matches!(button_event, ButtonEvent::JustPressed(b) if button == *b),
|
||||
None => false
|
||||
}
|
||||
}
|
||||
|
@ -105,11 +102,8 @@ impl<T: Button> InputButtons<T> {
|
|||
/// This must be done so that a key does not stay as JustPressed between multiple ticks
|
||||
pub fn update(&mut self) {
|
||||
for bev in self.button_events.values_mut() {
|
||||
match bev {
|
||||
ButtonEvent::JustPressed(btn) => {
|
||||
*bev = ButtonEvent::Pressed(btn.clone());
|
||||
},
|
||||
_ => {},
|
||||
if let ButtonEvent::JustPressed(btn) = bev {
|
||||
*bev = ButtonEvent::Pressed(btn.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ impl<T> AVec<T> {
|
|||
#[inline(always)]
|
||||
fn slot_size(&self) -> usize {
|
||||
let a = self.align - 1;
|
||||
mem::align_of::<T>() + (a) & !a
|
||||
(mem::align_of::<T>() + (a)) & !a
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
|
@ -240,6 +240,11 @@ impl<T> AVec<T> {
|
|||
self.len
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Returns the capacity of the vector.
|
||||
///
|
||||
/// The capacity is the amount of elements that the vector can store without reallocating.
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
use tracing::instrument;
|
||||
|
||||
use super::{RenderGraphLabel, RenderGraphLabelValue};
|
||||
|
||||
/// A queued write to a GPU buffer targeting a graph slot.
|
||||
pub(crate) struct GraphBufferWrite {
|
||||
/// The name of the slot that has the resource that will be written
|
||||
pub(crate) target_slot: RenderGraphLabelValue,
|
||||
pub(crate) offset: u64,
|
||||
pub(crate) bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct RenderGraphContext<'a> {
|
||||
/// The [`wgpu::CommandEncoder`] used to encode GPU operations.
|
||||
///
|
||||
/// This is `None` during the `prepare` stage.
|
||||
pub encoder: Option<wgpu::CommandEncoder>,
|
||||
/// The gpu device that is being used.
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub(crate) buffer_writes: VecDeque<GraphBufferWrite>,
|
||||
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, '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, 'a>,
|
||||
) -> wgpu::RenderPass {
|
||||
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,6 +1,6 @@
|
|||
mod node;
|
||||
use std::{
|
||||
cell::{Ref, RefCell}, collections::{HashMap, VecDeque}, fmt::Debug, hash::Hash, rc::Rc, sync::Arc
|
||||
cell::{Ref, RefCell, RefMut}, collections::VecDeque, fmt::Debug, hash::Hash, rc::Rc, sync::Arc
|
||||
};
|
||||
|
||||
use lyra_ecs::World;
|
||||
|
@ -12,16 +12,21 @@ 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::ComputePass;
|
||||
use wgpu::CommandEncoder;
|
||||
|
||||
use super::resource::{ComputePipeline, Pipeline, RenderPipeline};
|
||||
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_dyn(&self) -> &dyn RenderGraphLabel;
|
||||
//fn as_partial_eq(&self) -> &dyn PartialEq<dyn RenderGraphLabel>;
|
||||
fn as_label_hash(&self) -> u64;
|
||||
|
||||
fn label_eq_rc(&self, other: &Rc<dyn RenderGraphLabel>) -> bool {
|
||||
|
@ -33,8 +38,7 @@ pub trait RenderGraphLabel: Debug + 'static {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RenderGraphHash(u64);
|
||||
|
||||
/// An owned [`RenderGraphLabel`].
|
||||
#[derive(Clone)]
|
||||
pub struct RenderGraphLabelValue(Rc<dyn RenderGraphLabel>);
|
||||
|
||||
|
@ -76,69 +80,64 @@ impl PartialEq for RenderGraphLabelValue {
|
|||
|
||||
impl Eq for RenderGraphLabelValue {}
|
||||
|
||||
struct PassEntry {
|
||||
struct NodeEntry {
|
||||
/// The Node
|
||||
inner: Arc<RefCell<dyn Node>>,
|
||||
/// The Node descriptor
|
||||
desc: Rc<RefCell<NodeDesc>>,
|
||||
/// The index of the pass in the execution graph
|
||||
/// The index of the node in the execution graph
|
||||
graph_index: petgraph::matrix_graph::NodeIndex<usize>,
|
||||
pipeline: Rc<RefCell<Option<PipelineResource>>>,
|
||||
/// The Node's optional pipeline
|
||||
pipeline: Rc<RefCell<Option<Pipeline>>>,
|
||||
}
|
||||
|
||||
pub struct BindGroupEntry {
|
||||
pub label: RenderGraphLabelValue,
|
||||
#[derive(Clone)]
|
||||
struct BindGroupEntry {
|
||||
label: RenderGraphLabelValue,
|
||||
/// BindGroup
|
||||
pub bg: Rc<wgpu::BindGroup>,
|
||||
bg: Arc<wgpu::BindGroup>,
|
||||
/// BindGroupLayout
|
||||
pub layout: Option<Rc<wgpu::BindGroupLayout>>,
|
||||
layout: Option<Arc<wgpu::BindGroupLayout>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct ResourcedSlot {
|
||||
#[derive(Clone)]
|
||||
struct ResourceSlot {
|
||||
label: RenderGraphLabelValue,
|
||||
ty: SlotType,
|
||||
value: SlotValue,
|
||||
}
|
||||
|
||||
/// Stores the pipeline and other resources it uses.
|
||||
///
|
||||
/// This stores the bind groups that have been created for it
|
||||
pub struct PipelineResource {
|
||||
pub pipeline: Pipeline,
|
||||
/// Lookup map for bind groups using names
|
||||
pub bg_layout_name_lookup: HashMap<String, u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderTarget {
|
||||
pub surface: wgpu::Surface,
|
||||
pub surface_config: wgpu::SurfaceConfiguration,
|
||||
pub current_texture: Option<wgpu::SurfaceTexture>,
|
||||
}
|
||||
|
||||
pub struct RenderGraph {
|
||||
device: Rc<wgpu::Device>,
|
||||
queue: Rc<wgpu::Queue>,
|
||||
slots: FxHashMap<RenderGraphLabelValue, ResourcedSlot>,
|
||||
nodes: FxHashMap<RenderGraphLabelValue, PassEntry>,
|
||||
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 describing the execution path of the RenderGraph
|
||||
execution_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
|
||||
/// 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: Rc<wgpu::Device>, queue: Rc<wgpu::Queue>) -> Self {
|
||||
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(),
|
||||
execution_graph: Default::default(),
|
||||
node_graph: Default::default(),
|
||||
view_target,
|
||||
shader_prepoc: wgsl_preprocessor::Processor::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &wgpu::Device {
|
||||
&*self.device
|
||||
&self.device
|
||||
}
|
||||
|
||||
/// Add a [`Node`] to the RenderGraph.
|
||||
|
@ -151,39 +150,28 @@ impl RenderGraph {
|
|||
/// * 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, pass), level = "debug")]
|
||||
pub fn add_pass<P: Node>(&mut self, label: impl RenderGraphLabel, mut pass: P) {
|
||||
let mut desc = pass.desc(self);
|
||||
#[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 pass
|
||||
// collect all the slots of the node
|
||||
for slot in &mut desc.slots {
|
||||
if let Some(other) = self
|
||||
.slots
|
||||
.get_mut(&slot.label)
|
||||
//.map(|s| (id, s))
|
||||
//.and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
|
||||
{
|
||||
debug_assert_eq!(
|
||||
slot.ty, other.ty,
|
||||
"slot {:?} in pass {:?} does not match existing slot of same name",
|
||||
"slot {:?} in node {:?} does not match existing slot of same name",
|
||||
slot.label, label
|
||||
);
|
||||
|
||||
/* trace!(
|
||||
"Found existing slot for {:?}, changing id to {}",
|
||||
slot.label,
|
||||
id
|
||||
); */
|
||||
|
||||
// if there is a slot of the same name
|
||||
//slot.id = *id;
|
||||
} else {
|
||||
debug_assert!(!self.slots.contains_key(&slot.label),
|
||||
"Reuse of id detected in render graph! Pass: {:?}, slot: {:?}",
|
||||
"Reuse of id detected in render graph! Node: {:?}, slot: {:?}",
|
||||
label, slot.label,
|
||||
);
|
||||
|
||||
let res_slot = ResourcedSlot {
|
||||
let res_slot = ResourceSlot {
|
||||
label: slot.label.clone(),
|
||||
ty: slot.ty,
|
||||
value: slot.value.clone().unwrap_or(SlotValue::None),
|
||||
|
@ -203,12 +191,12 @@ impl RenderGraph {
|
|||
}
|
||||
|
||||
let label: RenderGraphLabelValue = label.into();
|
||||
let index = self.execution_graph.add_node(label.clone());
|
||||
let index = self.node_graph.add_node(label.clone());
|
||||
|
||||
self.nodes.insert(
|
||||
label,
|
||||
PassEntry {
|
||||
inner: Arc::new(RefCell::new(pass)),
|
||||
NodeEntry {
|
||||
inner: Arc::new(RefCell::new(node)),
|
||||
desc: Rc::new(RefCell::new(desc)),
|
||||
graph_index: index,
|
||||
pipeline: Rc::new(RefCell::new(None)),
|
||||
|
@ -216,58 +204,69 @@ impl RenderGraph {
|
|||
);
|
||||
}
|
||||
|
||||
/// Creates all buffers required for the passes, also creates an internal execution path.
|
||||
/// 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 passes, create their pipelines
|
||||
for pass in self.nodes.values_mut() {
|
||||
let desc = (*pass.desc).borrow();
|
||||
// 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 pass"),
|
||||
.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 pass"),
|
||||
.expect("got render pipeline descriptor in a compute node"),
|
||||
)),
|
||||
NodeType::Presenter | NodeType::Node => {
|
||||
panic!("Present or Node RenderGraph passes should not have a pipeline descriptor!");
|
||||
}
|
||||
NodeType::Presenter | NodeType::Node | NodeType::Graph => {
|
||||
panic!("Present or Node RenderGraph nodes should not have a pipeline descriptor!");
|
||||
},
|
||||
};
|
||||
|
||||
drop(desc);
|
||||
let res = PipelineResource {
|
||||
pipeline,
|
||||
bg_layout_name_lookup: Default::default(),
|
||||
};
|
||||
|
||||
let mut pipeline = pass.pipeline.borrow_mut();
|
||||
*pipeline = Some(res);
|
||||
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) {
|
||||
// prepare all passes
|
||||
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);
|
||||
|
||||
for (label, pass) in &mut self.nodes {
|
||||
let mut context = RenderGraphContext::new(&self.device, &self.queue, None, label.clone());
|
||||
let mut inner = pass.inner.borrow_mut();
|
||||
inner.prepare(world, &mut context);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -280,79 +279,67 @@ impl RenderGraph {
|
|||
let slot = self
|
||||
.slots
|
||||
.get(&bufwr.target_slot)
|
||||
.expect(&format!(
|
||||
"Failed to find slot '{:?}' for buffer write",
|
||||
bufwr.target_slot
|
||||
));
|
||||
.unwrap_or_else(|| panic!("Failed to find slot '{:?}' for buffer write",
|
||||
bufwr.target_slot));
|
||||
let buf = slot
|
||||
.value
|
||||
.as_buffer()
|
||||
.expect(&format!("Slot '{:?}' is not a buffer", bufwr.target_slot));
|
||||
.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.execution_graph, None)
|
||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.node_graph, None)
|
||||
.expect("RenderGraph had cycled!")
|
||||
.iter()
|
||||
.map(|i| self.execution_graph[i.clone()].clone())
|
||||
.map(|i| self.node_graph[*i].clone())
|
||||
.collect();
|
||||
//debug!("Render graph execution order: {:?}", sorted);
|
||||
|
||||
let mut encoders = Vec::with_capacity(self.nodes.len() / 2);
|
||||
while let Some(pass_label) = sorted.pop_front() {
|
||||
let pass = self.nodes.get(&pass_label).unwrap();
|
||||
let pass_inn = pass.inner.clone();
|
||||
// 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 pass_desc = pass.desc.clone();
|
||||
let pass_desc = (*pass_desc).borrow();
|
||||
|
||||
let label = format!("{:?} Encoder", pass_label.0);
|
||||
|
||||
// encoders are not needed for presenter nodes.
|
||||
let encoder = if pass_desc.ty.should_have_pipeline() {
|
||||
Some(
|
||||
self.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some(&label),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
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();
|
||||
let mut context = RenderGraphContext::new(&device, &queue, encoder, pass_label.clone());
|
||||
|
||||
// all encoders need to be submitted before a presenter node is executed.
|
||||
if pass_desc.ty == NodeType::Presenter {
|
||||
trace!("Submitting {} encoderd before presenting", encoders.len());
|
||||
self.queue.submit(encoders.drain(..));
|
||||
// create a new encoder if the last one was submitted
|
||||
if encoder.is_none() {
|
||||
encoder = Some(self.create_encoder());
|
||||
}
|
||||
|
||||
trace!("Executing {:?}", pass_label.0);
|
||||
let mut inner = pass_inn.borrow_mut();
|
||||
inner.execute(self, &pass_desc, &mut context);
|
||||
let mut context = RenderGraphContext::new(device, queue, encoder.take(), node_label.clone());
|
||||
|
||||
if let Some(encoder) = context.encoder {
|
||||
encoders.push(encoder.finish());
|
||||
}
|
||||
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 !encoders.is_empty() {
|
||||
warn!(
|
||||
"{} encoders were not submitted in the same render cycle they were created. \
|
||||
Make sure there is a presenting pass at the end. You may still see something, \
|
||||
however it will be delayed a render cycle.",
|
||||
encoders.len()
|
||||
);
|
||||
self.queue.submit(encoders.into_iter());
|
||||
if let Some(encoder) = encoder {
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,32 +361,35 @@ impl RenderGraph {
|
|||
.and_then(|p| {
|
||||
let v = p.pipeline.borrow();
|
||||
|
||||
#[allow(clippy::manual_map)]
|
||||
match &*v {
|
||||
Some(_) => Some(Ref::map(v, |p| &p.as_ref().unwrap().pipeline)),
|
||||
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<&Rc<wgpu::BindGroup>> {
|
||||
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) -> &Rc<wgpu::BindGroup> {
|
||||
self.try_bind_group(label).expect("Unknown id for bind group")
|
||||
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<&Rc<wgpu::BindGroupLayout>> {
|
||||
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) -> &Rc<wgpu::BindGroupLayout> {
|
||||
self.try_bind_group_layout(label)
|
||||
.expect("Unknown id for bind group layout")
|
||||
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)
|
||||
|
@ -412,23 +402,23 @@ impl RenderGraph {
|
|||
.iter()
|
||||
.find(|p| *p.0 == from)
|
||||
.map(|p| p.1.graph_index)
|
||||
.expect("Failed to find from pass");
|
||||
.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 pass");
|
||||
.expect("Failed to find to node");
|
||||
|
||||
debug_assert_ne!(from_idx, to_idx, "cannot add edges between the same node");
|
||||
|
||||
self.execution_graph.add_edge(from_idx, to_idx, ());
|
||||
self.node_graph.add_edge(from_idx, to_idx, ());
|
||||
}
|
||||
|
||||
/// Utility method for setting the bind groups for a pass.
|
||||
/// 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 pass. If a bind group of the provided
|
||||
/// 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:
|
||||
|
@ -437,113 +427,146 @@ impl RenderGraph {
|
|||
/// &mut pass,
|
||||
/// &[
|
||||
/// // retrieves the `BasePassSlots::DepthTexture` bind group and sets the index 0 in the
|
||||
/// // pass to it.
|
||||
/// (&BasePassSlots::DepthTexture, 0),
|
||||
/// (&BasePassSlots::Camera, 1),
|
||||
/// (&LightBasePassSlots::Lights, 2),
|
||||
/// (&LightCullComputePassSlots::LightIndicesGridGroup, 3),
|
||||
/// (&BasePassSlots::ScreenSize, 4),
|
||||
/// // 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>(
|
||||
pub fn set_bind_groups<'a, P: Pass<'a>>(
|
||||
&'a self,
|
||||
pass: &mut ComputePass<'a>,
|
||||
pass: &mut P,
|
||||
bind_groups: &[(&dyn RenderGraphLabel, u32)],
|
||||
) {
|
||||
for (label, index) in bind_groups {
|
||||
let bg = self
|
||||
.bind_group(label.rc_clone());
|
||||
//.expect(&format!("Could not find bind group '{:?}'", label));
|
||||
|
||||
pass.set_bind_group(*index, bg, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
target_slot: RenderGraphLabelValue,
|
||||
offset: u64,
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
pub fn sub_graph_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut RenderGraph> {
|
||||
self.sub_graphs.get_mut(&label.into())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct RenderGraphContext<'a> {
|
||||
/// The [`wgpu::CommandEncoder`] used to encode GPU operations.
|
||||
/// Add a sub graph.
|
||||
///
|
||||
/// This is `None` during the `prepare` stage.
|
||||
pub encoder: Option<wgpu::CommandEncoder>,
|
||||
/// The gpu device that is being used.
|
||||
pub device: &'a wgpu::Device,
|
||||
pub queue: &'a wgpu::Queue,
|
||||
pub(crate) buffer_writes: VecDeque<GraphBufferWrite>,
|
||||
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, 'a>>,
|
||||
/// The label of this Node.
|
||||
pub label: RenderGraphLabelValue,
|
||||
}
|
||||
/// > 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);
|
||||
}
|
||||
|
||||
impl<'a> RenderGraphContext<'a> {
|
||||
pub(crate) fn new(device: &'a wgpu::Device, queue: &'a wgpu::Queue, encoder: Option<wgpu::CommandEncoder>, label: RenderGraphLabelValue) -> Self {
|
||||
Self {
|
||||
encoder,
|
||||
device,
|
||||
queue,
|
||||
buffer_writes: Default::default(),
|
||||
renderpass_desc: vec![],
|
||||
label,
|
||||
/// 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 begin_render_pass(
|
||||
&'a mut self,
|
||||
desc: wgpu::RenderPassDescriptor<'a, 'a>,
|
||||
) -> wgpu::RenderPass {
|
||||
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 view_target(&self) -> Ref<ViewTarget> {
|
||||
self.view_target.borrow()
|
||||
}
|
||||
|
||||
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)
|
||||
pub fn view_target_mut(&self) -> RefMut<ViewTarget> {
|
||||
self.view_target.borrow_mut()
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
})
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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));
|
||||
/// Preprocess a shader, returning the source.
|
||||
#[inline(always)]
|
||||
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
|
||||
self.shader_prepoc.preprocess_module(shader_path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubGraphNode {
|
||||
subg: RenderGraphLabelValue,
|
||||
slots: Vec<RenderGraphLabelValue>,
|
||||
}
|
||||
|
||||
impl SubGraphNode {
|
||||
pub fn new<L: Into<RenderGraphLabelValue>>(sub_label: L, slot_labels: Vec<RenderGraphLabelValue>) -> Self {
|
||||
Self {
|
||||
subg: sub_label.into(),
|
||||
slots: slot_labels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for SubGraphNode {
|
||||
fn desc(&mut self, _: &mut RenderGraph) -> NodeDesc {
|
||||
NodeDesc::new(NodeType::Graph, None, vec![])
|
||||
}
|
||||
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, _: &mut RenderGraphContext) {
|
||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
||||
|
||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
||||
sg.prepare(world);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut RenderGraph,
|
||||
_: &NodeDesc,
|
||||
_: &mut RenderGraphContext,
|
||||
) {
|
||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
||||
|
||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
||||
sg.render();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc};
|
||||
use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc, sync::Arc};
|
||||
|
||||
use bind_match::bind_match;
|
||||
use lyra_ecs::World;
|
||||
|
||||
use crate::render::resource::PipelineDescriptor;
|
||||
|
||||
use super::{RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget};
|
||||
use super::{Frame, RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
pub enum NodeType {
|
||||
|
@ -18,6 +18,8 @@ pub enum NodeType {
|
|||
Render,
|
||||
/// A node that presents render results to a render target.
|
||||
Presenter,
|
||||
/// A node that represents a sub-graph.
|
||||
Graph,
|
||||
}
|
||||
|
||||
impl NodeType {
|
||||
|
@ -28,6 +30,7 @@ impl NodeType {
|
|||
NodeType::Compute => true,
|
||||
NodeType::Render => true,
|
||||
NodeType::Presenter => false,
|
||||
NodeType::Graph => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,25 +43,35 @@ pub enum SlotType {
|
|||
Texture,
|
||||
Buffer,
|
||||
RenderTarget,
|
||||
Frame,
|
||||
}
|
||||
|
||||
/// The value of a slot in a [`Node`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub enum SlotValue {
|
||||
/// This slot doesn't have any value
|
||||
None,
|
||||
/// The value will be set during a later phase of the render graph. To see the type of value
|
||||
/// this will be set to, see the slots type.
|
||||
Lazy,
|
||||
TextureView(Rc<wgpu::TextureView>),
|
||||
TextureView(Arc<wgpu::TextureView>),
|
||||
Sampler(Rc<wgpu::Sampler>),
|
||||
Texture(Rc<wgpu::Texture>),
|
||||
Buffer(Rc<wgpu::Buffer>),
|
||||
Texture(Arc<wgpu::Texture>),
|
||||
Buffer(Arc<wgpu::Buffer>),
|
||||
RenderTarget(Rc<RefCell<RenderTarget>>),
|
||||
Frame(Rc<RefCell<Option<Frame>>>),
|
||||
}
|
||||
|
||||
impl SlotValue {
|
||||
pub fn as_texture_view(&self) -> Option<&Rc<wgpu::TextureView>> {
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub fn is_lazy(&self) -> bool {
|
||||
matches!(self, Self::Lazy)
|
||||
}
|
||||
|
||||
pub fn as_texture_view(&self) -> Option<&Arc<wgpu::TextureView>> {
|
||||
bind_match!(self, Self::TextureView(v) => v)
|
||||
}
|
||||
|
||||
|
@ -66,11 +79,11 @@ impl SlotValue {
|
|||
bind_match!(self, Self::Sampler(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_texture(&self) -> Option<&Rc<wgpu::Texture>> {
|
||||
pub fn as_texture(&self) -> Option<&Arc<wgpu::Texture>> {
|
||||
bind_match!(self, Self::Texture(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_buffer(&self) -> Option<&Rc<wgpu::Buffer>> {
|
||||
pub fn as_buffer(&self) -> Option<&Arc<wgpu::Buffer>> {
|
||||
bind_match!(self, Self::Buffer(v) => v)
|
||||
}
|
||||
|
||||
|
@ -81,6 +94,14 @@ impl SlotValue {
|
|||
pub fn as_render_target_mut(&mut self) -> Option<RefMut<RenderTarget>> {
|
||||
bind_match!(self, Self::RenderTarget(v) => v.borrow_mut())
|
||||
}
|
||||
|
||||
pub fn as_frame(&self) -> Option<Ref<Option<Frame>>> {
|
||||
bind_match!(self, Self::Frame(v) => v.borrow())
|
||||
}
|
||||
|
||||
pub fn as_frame_mut(&mut self) -> Option<RefMut<Option<Frame>>> {
|
||||
bind_match!(self, Self::Frame(v) => v.borrow_mut())
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlotAttribute {
|
||||
|
@ -132,6 +153,7 @@ pub struct RenderGraphPipelineInfo {
|
|||
pub multiview: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl RenderGraphPipelineInfo {
|
||||
pub fn new(
|
||||
label: &str,
|
||||
|
@ -147,7 +169,7 @@ impl RenderGraphPipelineInfo {
|
|||
label: Some(label.to_string()),
|
||||
bind_group_layouts: bind_group_layouts
|
||||
.into_iter()
|
||||
.map(|bgl| Rc::new(bgl))
|
||||
.map(Rc::new)
|
||||
.collect(),
|
||||
vertex,
|
||||
primitive,
|
||||
|
@ -175,8 +197,8 @@ pub struct NodeDesc {
|
|||
/// This makes the bind groups accessible to other Nodes.
|
||||
pub bind_groups: Vec<(
|
||||
RenderGraphLabelValue,
|
||||
Rc<wgpu::BindGroup>,
|
||||
Option<Rc<wgpu::BindGroupLayout>>,
|
||||
Arc<wgpu::BindGroup>,
|
||||
Option<Arc<wgpu::BindGroupLayout>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
|
@ -185,7 +207,7 @@ impl NodeDesc {
|
|||
pub fn new(
|
||||
pass_type: NodeType,
|
||||
pipeline_desc: Option<PipelineDescriptor>,
|
||||
bind_groups: Vec<(&dyn RenderGraphLabel, Rc<wgpu::BindGroup>, Option<Rc<wgpu::BindGroupLayout>>)>,
|
||||
bind_groups: Vec<(&dyn RenderGraphLabel, Arc<wgpu::BindGroup>, Option<Arc<wgpu::BindGroupLayout>>)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ty: pass_type,
|
||||
|
@ -342,13 +364,13 @@ impl NodeDesc {
|
|||
/// describes all resources the node requires for execution during the `execute` phase.
|
||||
pub trait Node: 'static {
|
||||
/// Retrieve a descriptor of the Node.
|
||||
fn desc<'a, 'b>(&'a mut self, graph: &'b mut RenderGraph) -> NodeDesc;
|
||||
fn desc(&mut self, graph: &mut RenderGraph) -> NodeDesc;
|
||||
|
||||
/// Prepare the node for rendering.
|
||||
///
|
||||
/// This phase runs before `execute` and is meant to be used to collect data from the World
|
||||
/// and write to GPU buffers.
|
||||
fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext);
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext);
|
||||
|
||||
/// Execute the node.
|
||||
///
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::UVec2;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
@ -9,8 +9,7 @@ use crate::{
|
|||
render::{
|
||||
camera::{CameraUniform, RenderCamera},
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeSlot,
|
||||
NodeType, RenderTarget, SlotAttribute, SlotType, SlotValue,
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
},
|
||||
render_buffer::BufferWrapper, texture::RenderTexture,
|
||||
},
|
||||
|
@ -25,8 +24,6 @@ pub enum BasePassSlots {
|
|||
DepthTexture,
|
||||
ScreenSize,
|
||||
Camera,
|
||||
MainRenderTarget,
|
||||
WindowTextureView,
|
||||
DepthTextureView,
|
||||
}
|
||||
|
||||
|
@ -35,27 +32,12 @@ pub enum BasePassSlots {
|
|||
/// screen size buffer, camera buffer,
|
||||
#[derive(Default)]
|
||||
pub struct BasePass {
|
||||
/// Temporary storage for the main render target
|
||||
///
|
||||
/// This should be Some when the pass is first created then after its added to
|
||||
/// the render graph it will be None and stay None.
|
||||
temp_render_target: Option<RenderTarget>,
|
||||
screen_size: glam::UVec2,
|
||||
screen_size: UVec2,
|
||||
}
|
||||
|
||||
impl BasePass {
|
||||
pub fn new(surface: wgpu::Surface, surface_config: wgpu::SurfaceConfiguration) -> Self {
|
||||
let size = glam::UVec2::new(surface_config.width, surface_config.height);
|
||||
|
||||
Self {
|
||||
temp_render_target: Some(RenderTarget {
|
||||
surface,
|
||||
surface_config,
|
||||
current_texture: None,
|
||||
}),
|
||||
screen_size: size,
|
||||
..Default::default()
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,11 +46,8 @@ impl Node for BasePass {
|
|||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let render_target = self.temp_render_target.take().unwrap();
|
||||
self.screen_size = UVec2::new(
|
||||
render_target.surface_config.width,
|
||||
render_target.surface_config.height,
|
||||
);
|
||||
let vt = graph.view_target();
|
||||
self.screen_size = vt.size();
|
||||
|
||||
let (screen_size_bgl, screen_size_bg, screen_size_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
|
@ -76,9 +55,9 @@ impl Node for BasePass {
|
|||
.visibility(wgpu::ShaderStages::COMPUTE)
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[self.screen_size])
|
||||
.finish_parts(&graph.device());
|
||||
let screen_size_bgl = Rc::new(screen_size_bgl);
|
||||
let screen_size_bg = Rc::new(screen_size_bg);
|
||||
.finish_parts(graph.device());
|
||||
let screen_size_bgl = Arc::new(screen_size_bgl);
|
||||
let screen_size_bg = Arc::new(screen_size_bg);
|
||||
|
||||
let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
|
@ -86,18 +65,18 @@ impl Node for BasePass {
|
|||
.visibility(wgpu::ShaderStages::all())
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[CameraUniform::default()])
|
||||
.finish_parts(&graph.device());
|
||||
let camera_bgl = Rc::new(camera_bgl);
|
||||
let camera_bg = Rc::new(camera_bg);
|
||||
.finish_parts(graph.device());
|
||||
let camera_bgl = Arc::new(camera_bgl);
|
||||
let camera_bg = Arc::new(camera_bg);
|
||||
|
||||
// create the depth texture using the utility struct, then take all the required fields
|
||||
let mut depth_texture = RenderTexture::create_depth_texture(&graph.device(), &render_target.surface_config, "depth_texture");
|
||||
let mut depth_texture = RenderTexture::create_depth_texture(graph.device(), self.screen_size, "depth_texture");
|
||||
depth_texture.create_bind_group(&graph.device);
|
||||
|
||||
let dt_bg_pair = depth_texture.bindgroup_pair.unwrap();
|
||||
let depth_texture_bg = Rc::new(dt_bg_pair.bindgroup);
|
||||
let depth_texture_bg = Arc::new(dt_bg_pair.bindgroup);
|
||||
let depth_texture_bgl = dt_bg_pair.layout;
|
||||
let depth_texture_view = Rc::new(depth_texture.view);
|
||||
let depth_texture_view = Arc::new(depth_texture.view);
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Node,
|
||||
|
@ -115,19 +94,6 @@ impl Node for BasePass {
|
|||
],
|
||||
);
|
||||
|
||||
desc.add_slot(NodeSlot {
|
||||
ty: SlotType::RenderTarget,
|
||||
attribute: SlotAttribute::Output,
|
||||
label: BasePassSlots::MainRenderTarget.into(),
|
||||
value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(
|
||||
render_target,
|
||||
)))),
|
||||
});
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::WindowTextureView,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Lazy),
|
||||
);
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::DepthTextureView,
|
||||
SlotAttribute::Output,
|
||||
|
@ -136,21 +102,23 @@ impl Node for BasePass {
|
|||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(screen_size_buf))),
|
||||
Some(SlotValue::Buffer(Arc::new(screen_size_buf))),
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::Camera,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(camera_buf))),
|
||||
Some(SlotValue::Buffer(Arc::new(camera_buf))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
if let Some(camera) = world.view_iter::<&mut CameraComponent>().next() {
|
||||
let screen_size = graph.view_target().size();
|
||||
|
||||
let mut render_cam =
|
||||
RenderCamera::new(PhysicalSize::new(self.screen_size.x, self.screen_size.y));
|
||||
RenderCamera::new(PhysicalSize::new(screen_size.x, screen_size.y));
|
||||
let uniform = render_cam.calc_view_projection(&camera);
|
||||
|
||||
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform)
|
||||
|
@ -165,35 +133,19 @@ impl Node for BasePass {
|
|||
_desc: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let tv_slot = graph
|
||||
.slot_value_mut(BasePassSlots::MainRenderTarget)
|
||||
.expect("somehow the main render target slot is missing");
|
||||
let mut rt = tv_slot.as_render_target_mut().unwrap();
|
||||
debug_assert!(
|
||||
let mut vt = graph.view_target_mut();
|
||||
vt.primary.create_frame();
|
||||
vt.primary.create_frame_view();
|
||||
/* debug_assert!(
|
||||
!rt.current_texture.is_some(),
|
||||
"main render target surface was not presented!"
|
||||
);
|
||||
); */
|
||||
|
||||
// update the screen size buffer if the size changed.
|
||||
if rt.surface_config.width != self.screen_size.x
|
||||
|| rt.surface_config.height != self.screen_size.y
|
||||
{
|
||||
self.screen_size = UVec2::new(rt.surface_config.width, rt.surface_config.height);
|
||||
let rt_size = vt.size();
|
||||
if rt_size != self.screen_size {
|
||||
self.screen_size = rt_size;
|
||||
context.queue_buffer_write_with(BasePassSlots::ScreenSize, 0, self.screen_size)
|
||||
}
|
||||
|
||||
let surface_tex = rt.surface.get_current_texture().unwrap();
|
||||
let view = surface_tex
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
rt.current_texture = Some(surface_tex);
|
||||
drop(rt); // must be manually dropped for borrow checker when getting texture view slot
|
||||
|
||||
// store the surface texture to the slot
|
||||
let tv_slot = graph
|
||||
.slot_value_mut(BasePassSlots::WindowTextureView)
|
||||
.expect("somehow the window texture view slot is missing");
|
||||
*tv_slot = SlotValue::TextureView(Rc::new(view));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
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::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: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
pass.set_pipeline(pipeline.as_render());
|
||||
|
||||
pass.set_bind_group(0, bg, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::graph::{Node, NodeDesc, NodeSlot, NodeType};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct InitNodeLabel;
|
||||
|
||||
pub struct InitNode {
|
||||
slots: Vec<NodeSlot>,
|
||||
}
|
||||
|
||||
impl Node for InitNode {
|
||||
fn desc(&mut self, _: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc {
|
||||
let mut desc = NodeDesc::new(NodeType::Node, None, vec![]);
|
||||
// the slots can just be cloned since the slot attribute doesn't really matter much.
|
||||
desc.slots = self.slots.clone();
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _: &mut crate::render::graph::RenderGraph, _: &mut lyra_ecs::World, _: &mut crate::render::graph::RenderGraphContext) {
|
||||
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
|
@ -2,8 +2,7 @@ use lyra_game_derive::RenderGraphLabel;
|
|||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute,
|
||||
SlotValue,
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
},
|
||||
light::LightUniformBuffers,
|
||||
};
|
||||
|
@ -58,10 +57,10 @@ impl Node for LightBasePass {
|
|||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
fn prepare(&mut self, _graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
let tick = world.current_tick();
|
||||
let lights = self.light_buffers.as_mut().unwrap();
|
||||
lights.update_lights(context.queue, tick, world);
|
||||
lights.update_lights(&context.queue, tick, world);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
use std::{mem, rc::Rc};
|
||||
use std::{mem, rc::Rc, sync::Arc};
|
||||
|
||||
use glam::Vec2Swizzles;
|
||||
use lyra_ecs::World;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute,
|
||||
SlotValue,
|
||||
},
|
||||
resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader},
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor, Shader}
|
||||
};
|
||||
|
||||
use super::{BasePassSlots, LightBasePassSlots};
|
||||
|
@ -27,12 +26,14 @@ pub enum LightCullComputePassSlots {
|
|||
|
||||
pub struct LightCullComputePass {
|
||||
workgroup_size: glam::UVec2,
|
||||
pipeline: Option<ComputePipeline>,
|
||||
}
|
||||
|
||||
impl LightCullComputePass {
|
||||
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
|
||||
Self {
|
||||
workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height),
|
||||
pipeline: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,19 +43,6 @@ impl Node for LightCullComputePass {
|
|||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("light_cull_comp_shader".into()),
|
||||
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
|
||||
});
|
||||
|
||||
// get the size of the work group for the grid
|
||||
let main_rt = graph
|
||||
.slot_value(BasePassSlots::MainRenderTarget)
|
||||
.and_then(|s| s.as_render_target())
|
||||
.expect("missing main render target");
|
||||
self.workgroup_size =
|
||||
glam::UVec2::new(main_rt.surface_config.width, main_rt.surface_config.height);
|
||||
|
||||
// initialize some buffers with empty data
|
||||
let mut contents = Vec::<u8>::new();
|
||||
let contents_len =
|
||||
|
@ -71,11 +59,11 @@ impl Node for LightCullComputePass {
|
|||
let light_index_counter_buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("light_index_counter_buffer"),
|
||||
contents: &bytemuck::cast_slice(&[0]),
|
||||
contents: bytemuck::cast_slice(&[0]),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let light_indices_bg_layout = Rc::new(device.create_bind_group_layout(
|
||||
let light_indices_bg_layout = Arc::new(device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
|
@ -140,7 +128,7 @@ impl Node for LightCullComputePass {
|
|||
array_layer_count: None,
|
||||
});
|
||||
|
||||
let light_indices_bg = Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
let light_indices_bg = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_indices_bg_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
|
@ -167,16 +155,16 @@ impl Node for LightCullComputePass {
|
|||
label: Some("light_indices_grid_bind_group"),
|
||||
}));
|
||||
|
||||
drop(main_rt);
|
||||
//drop(main_rt);
|
||||
|
||||
let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
/* let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize); */
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Compute,
|
||||
Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
|
||||
/* Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
|
||||
label: Some("light_cull_pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
layouts: vec![
|
||||
|
@ -188,7 +176,8 @@ impl Node for LightCullComputePass {
|
|||
],
|
||||
shader,
|
||||
shader_entry_point: "cs_main".into(),
|
||||
})),
|
||||
})), */
|
||||
None,
|
||||
vec![(
|
||||
&LightCullComputePassSlots::LightIndicesGridGroup,
|
||||
light_indices_bg,
|
||||
|
@ -196,11 +185,6 @@ impl Node for LightCullComputePass {
|
|||
)],
|
||||
);
|
||||
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::WindowTextureView,
|
||||
SlotAttribute::Input,
|
||||
None,
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Input,
|
||||
|
@ -210,14 +194,51 @@ impl Node for LightCullComputePass {
|
|||
desc.add_buffer_slot(
|
||||
LightCullComputePassSlots::IndexCounterBuffer,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))),
|
||||
Some(SlotValue::Buffer(Arc::new(light_index_counter_buffer))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _world: &mut World, context: &mut RenderGraphContext) {
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext) {
|
||||
context.queue_buffer_write_with(LightCullComputePassSlots::IndexCounterBuffer, 0, 0);
|
||||
|
||||
let screen_size = world.get_resource::<ScreenSize>();
|
||||
if screen_size.xy() != self.workgroup_size {
|
||||
self.workgroup_size = screen_size.xy();
|
||||
todo!("Resize buffers and other resources");
|
||||
}
|
||||
|
||||
if self.pipeline.is_none() {
|
||||
let device = graph.device();
|
||||
|
||||
let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
||||
let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("light_cull_comp_shader".into()),
|
||||
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor {
|
||||
label: Some("light_cull_pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
layouts: vec![
|
||||
depth_tex_bgl.clone(),
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
light_indices_bg_layout.clone(),
|
||||
screen_size_bgl.clone(),
|
||||
],
|
||||
shader,
|
||||
shader_entry_point: "cs_main".into(),
|
||||
});
|
||||
|
||||
self.pipeline = Some(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
|
@ -226,11 +247,7 @@ impl Node for LightCullComputePass {
|
|||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut RenderGraphContext,
|
||||
) {
|
||||
let label = context.label.clone();
|
||||
|
||||
let pipeline = graph.pipeline(label)
|
||||
.expect("Failed to find Pipeline for LightCullComputePass");
|
||||
let pipeline = pipeline.as_compute();
|
||||
let pipeline = self.pipeline.as_mut().unwrap();
|
||||
|
||||
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||
label: Some("light_cull_pass"),
|
||||
|
|
|
@ -0,0 +1,767 @@
|
|||
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::{Has, Not, Or},
|
||||
Entities, Res, ResMut, TickOf,
|
||||
},
|
||||
relation::{ChildOf, RelationOriginComponent},
|
||||
Component, Entity, ResourceObject, World,
|
||||
};
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use lyra_math::Transform;
|
||||
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||
use lyra_scene::{SceneGraph, WorldTransform};
|
||||
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::{TransformBuffers, TransformGroup},
|
||||
vertex::Vertex,
|
||||
},
|
||||
DeltaTime,
|
||||
};
|
||||
|
||||
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(Clone, Debug, Component)]
|
||||
struct InterpTransform {
|
||||
last_transform: Transform,
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
#[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_resource::gltf::MeshIndices::U16(v) => {
|
||||
bytemuck::pod_align_to::<u16, u32>(v).1
|
||||
}
|
||||
lyra_resource::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_resource::gltf::MeshIndices::U16(v) => {
|
||||
(wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v))
|
||||
}
|
||||
lyra_resource::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 render_limits = device.limits();
|
||||
|
||||
let last_epoch = world.current_tick();
|
||||
let mut alive_entities = HashSet::new();
|
||||
|
||||
{
|
||||
// prepare the world with resources
|
||||
if !world.has_resource::<TransformBuffers>() {
|
||||
let buffers = TransformBuffers::new(device);
|
||||
world.add_resource(buffers);
|
||||
}
|
||||
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>();
|
||||
render_meshes.clear();
|
||||
}
|
||||
|
||||
let view = world.view_iter::<(
|
||||
Entities,
|
||||
&Transform,
|
||||
TickOf<Transform>,
|
||||
Or<(&MeshHandle, TickOf<MeshHandle>), (&SceneHandle, TickOf<SceneHandle>)>,
|
||||
Option<&mut InterpTransform>,
|
||||
Res<DeltaTime>,
|
||||
ResMut<TransformBuffers>,
|
||||
ResMut<RenderMeshes>,
|
||||
ResMut<RenderAssets<MeshBufferStorage>>,
|
||||
ResMut<RenderAssets<Arc<GpuMaterial>>>,
|
||||
ResMut<FxHashMap<Entity, uuid::Uuid>>,
|
||||
)>();
|
||||
|
||||
// used to store InterpTransform components to add to entities later
|
||||
let mut component_queue: Vec<(Entity, InterpTransform)> = vec![];
|
||||
|
||||
for (
|
||||
entity,
|
||||
transform,
|
||||
_transform_epoch,
|
||||
(mesh_pair, scene_pair),
|
||||
interp_tran,
|
||||
delta_time,
|
||||
mut transforms,
|
||||
mut render_meshes,
|
||||
mut mesh_buffers,
|
||||
mut material_buffers,
|
||||
mut entity_meshes,
|
||||
) in view
|
||||
{
|
||||
alive_entities.insert(entity);
|
||||
|
||||
// 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 interp_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, interp));
|
||||
|
||||
*transform
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
// expand the transform buffers if they need to be.
|
||||
// this is done in its own scope to avoid multiple mutable references to self at
|
||||
// once; aka, make the borrow checker happy
|
||||
if transforms.needs_expand() {
|
||||
debug!("Expanding transform buffers");
|
||||
transforms.expand_buffers(device);
|
||||
}
|
||||
}
|
||||
|
||||
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 group = TransformGroup::EntityRes(entity, mesh_han.uuid());
|
||||
let transform_id = transforms.update_or_push(
|
||||
device,
|
||||
queue,
|
||||
&render_limits,
|
||||
group,
|
||||
interp_transform.calculate_mat4(),
|
||||
glam::Mat3::from_quat(interp_transform.rotation),
|
||||
);
|
||||
|
||||
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_id);
|
||||
render_meshes.push_back(job);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((scene_han, scene_epoch)) = scene_pair {
|
||||
if let Some(scene) = scene_han.data_ref() {
|
||||
if scene_epoch == last_epoch {
|
||||
let view = scene.world().view::<(
|
||||
Entities,
|
||||
&mut WorldTransform,
|
||||
&Transform,
|
||||
Not<Has<RelationOriginComponent<ChildOf>>>,
|
||||
)>();
|
||||
lyra_scene::system_update_world_transforms(scene.world(), view).unwrap();
|
||||
}
|
||||
|
||||
for (mesh_han, pos) in
|
||||
scene.world().view_iter::<(&MeshHandle, &WorldTransform)>()
|
||||
{
|
||||
if let Some(mesh) = mesh_han.data_ref() {
|
||||
let mesh_interpo = interp_transform + **pos;
|
||||
|
||||
// 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 scene_mesh_group =
|
||||
TransformGroup::Res(scene_han.uuid(), mesh_han.uuid());
|
||||
let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
|
||||
let transform_id = transforms.update_or_push(
|
||||
device,
|
||||
queue,
|
||||
&render_limits,
|
||||
group,
|
||||
mesh_interpo.calculate_mat4(),
|
||||
glam::Mat3::from_quat(mesh_interpo.rotation),
|
||||
);
|
||||
|
||||
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_id);
|
||||
render_meshes.push_back(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (en, interp) in component_queue {
|
||||
world.insert(en, interp);
|
||||
}
|
||||
|
||||
let mut transforms = world.get_resource_mut::<TransformBuffers>();
|
||||
transforms.send_to_gpu(queue);
|
||||
}
|
||||
|
||||
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: std::num::NonZeroU32::new(4 * dim.0),
|
||||
rows_per_image: std::num::NonZeroU32::new(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_resource::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,438 +1,369 @@
|
|||
use std::{collections::{HashSet, VecDeque}, rc::Rc};
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use glam::Vec3;
|
||||
use itertools::izip;
|
||||
use lyra_ecs::{query::{filter::{Has, Not, Or}, Entities, Res, TickOf}, relation::{ChildOf, RelationOriginComponent}, Component, Entity};
|
||||
use lyra_ecs::{AtomicRef, ResourceData};
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use lyra_math::Transform;
|
||||
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||
use lyra_scene::{SceneGraph, WorldTransform};
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug, instrument, warn};
|
||||
use uuid::Uuid;
|
||||
use wgpu::util::DeviceExt;
|
||||
use tracing::{instrument, warn};
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
desc_buf_lay::DescVertexBufferLayout, graph::{
|
||||
RenderGraphContext, Node, NodeDesc,
|
||||
NodeType,
|
||||
}, material::{Material, MaterialUniform}, render_buffer::{BufferStorage, BufferWrapper}, render_job::RenderJob, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, texture::RenderTexture, transform_buffer_storage::{TransformBuffers, TransformGroup}, vertex::Vertex
|
||||
},
|
||||
DeltaTime,
|
||||
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};
|
||||
|
||||
type MeshHandle = ResHandle<Mesh>;
|
||||
type SceneHandle = ResHandle<SceneGraph>;
|
||||
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
|
||||
Material,
|
||||
}
|
||||
|
||||
struct MeshBufferStorage {
|
||||
buffer_vertex: BufferStorage,
|
||||
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?
|
||||
material: Option<Rc<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(Clone, Debug, Component)]
|
||||
struct InterpTransform {
|
||||
last_transform: Transform,
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
//#[derive(Default)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MeshPass {
|
||||
transforms: Option<TransformBuffers>,
|
||||
mesh_buffers: FxHashMap<uuid::Uuid, MeshBufferStorage>,
|
||||
render_jobs: VecDeque<RenderJob>,
|
||||
|
||||
texture_bind_group_layout: Option<Rc<wgpu::BindGroupLayout>>,
|
||||
material_buffer: Option<wgpu::Buffer>,
|
||||
material_buffers: FxHashMap<uuid::Uuid, Rc<Material>>,
|
||||
entity_meshes: FxHashMap<Entity, uuid::Uuid>,
|
||||
|
||||
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() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn new(material_bgl: Arc<wgpu::BindGroupLayout>) -> Self {
|
||||
Self {
|
||||
default_texture: None,
|
||||
pipeline: None,
|
||||
material_bgl,
|
||||
|
||||
/// Checks if the mesh buffers in the GPU need to be updated.
|
||||
#[instrument(skip(self, device, queue, mesh_han))]
|
||||
fn check_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh_han: &ResHandle<Mesh>) {
|
||||
let mesh_uuid = mesh_han.uuid();
|
||||
transform_buffers: None,
|
||||
render_meshes: None,
|
||||
mesh_buffers: None,
|
||||
|
||||
if let (Some(mesh), Some(buffers)) = (mesh_han.data_ref(), self.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 = self.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_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
|
||||
lyra_resource::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));
|
||||
}
|
||||
shadows_atlas: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[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_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)),
|
||||
lyra_resource::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 )
|
||||
fn transform_buffers(&self) -> AtomicRef<TransformBuffers> {
|
||||
self.transform_buffers.as_ref().unwrap().get()
|
||||
}
|
||||
|
||||
#[instrument(skip(self, device, queue, mesh))]
|
||||
fn create_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, 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 = self.material_buffers.entry(material.uuid())
|
||||
.or_insert_with(|| {
|
||||
debug!(uuid=material.uuid().to_string(), "Sending material to gpu");
|
||||
Rc::new(Material::from_resource(&device, &queue, self.texture_bind_group_layout.clone().unwrap(), &material_ref))
|
||||
});
|
||||
|
||||
// TODO: support material uniforms from multiple uniforms
|
||||
let uni = MaterialUniform::from(&**material);
|
||||
queue.write_buffer(&self.material_buffer.as_ref().unwrap(), 0, bytemuck::bytes_of(&uni));
|
||||
|
||||
MeshBufferStorage {
|
||||
buffer_vertex: vertex_buffer,
|
||||
buffer_indices,
|
||||
material: Some(material.clone()),
|
||||
}
|
||||
fn render_meshes(&self) -> AtomicRef<RenderMeshes> {
|
||||
self.render_meshes.as_ref().unwrap().get()
|
||||
}
|
||||
|
||||
/// 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, entity))]
|
||||
fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
|
||||
#[allow(clippy::map_entry)]
|
||||
if !self.mesh_buffers.contains_key(&mesh_uuid) {
|
||||
// create the mesh's buffers
|
||||
let buffers = self.create_mesh_buffers(device, queue, mesh);
|
||||
self.mesh_buffers.insert(mesh_uuid, buffers);
|
||||
self.entity_meshes.insert(entity, mesh_uuid);
|
||||
|
||||
true
|
||||
} else { false }
|
||||
fn mesh_buffers(&self) -> AtomicRef<RenderAssets<MeshBufferStorage>> {
|
||||
self.mesh_buffers.as_ref().unwrap().get()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for MeshPass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
|
||||
let device = graph.device();
|
||||
|
||||
let transforms = TransformBuffers::new(device);
|
||||
let transform_bgl = transforms.bindgroup_layout.clone();
|
||||
self.transforms = Some(transforms);
|
||||
|
||||
let texture_bind_group_layout = Rc::new(RenderTexture::create_layout(&device));
|
||||
self.texture_bind_group_layout = Some(texture_bind_group_layout.clone());
|
||||
|
||||
let (material_bgl, material_bg, material_buf, _) = BufferWrapper::builder()
|
||||
.label_prefix("material")
|
||||
.visibility(wgpu::ShaderStages::FRAGMENT)
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.contents(&[MaterialUniform::default()])
|
||||
.finish_parts(device);
|
||||
let material_bgl = Rc::new(material_bgl);
|
||||
let material_bg = Rc::new(material_bg);
|
||||
|
||||
self.material_buffer = Some(material_buf);
|
||||
|
||||
// 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());
|
||||
//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());
|
||||
|
||||
// get surface config format
|
||||
let main_rt = graph.slot_value(BasePassSlots::MainRenderTarget)
|
||||
.and_then(|s| s.as_render_target())
|
||||
.expect("missing main render target");
|
||||
let surface_config_format = main_rt.surface_config.format;
|
||||
drop(main_rt);
|
||||
|
||||
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 shader = Rc::new(Shader {
|
||||
label: Some("base_shader".into()),
|
||||
source: include_str!("../../shaders/base.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let desc = NodeDesc::new(
|
||||
NodeDesc::new(
|
||||
NodeType::Render,
|
||||
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
|
||||
label: Some("meshes".into()),
|
||||
layouts: vec![
|
||||
texture_bind_group_layout.clone(),
|
||||
transform_bgl,
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
material_bgl.clone(),
|
||||
texture_bind_group_layout,
|
||||
light_grid_bgl.clone(),
|
||||
],
|
||||
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::default(),
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
})),
|
||||
None,
|
||||
vec![
|
||||
(&MeshesPassSlots::Material, material_bg, Some(material_bgl)),
|
||||
//(&MeshesPassSlots::Material, material_bg, Some(material_bgl)),
|
||||
],
|
||||
);
|
||||
|
||||
desc
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip(self, world, context))]
|
||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
let device = context.device;
|
||||
let queue = context.queue;
|
||||
let render_limits = device.limits();
|
||||
#[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 last_epoch = world.current_tick();
|
||||
let mut alive_entities = HashSet::new();
|
||||
|
||||
let view = world.view_iter::<(
|
||||
Entities,
|
||||
&Transform,
|
||||
TickOf<Transform>,
|
||||
Or<
|
||||
(&MeshHandle, TickOf<MeshHandle>),
|
||||
(&SceneHandle, TickOf<SceneHandle>)
|
||||
>,
|
||||
Option<&mut InterpTransform>,
|
||||
Res<DeltaTime>,
|
||||
)>();
|
||||
let device = graph.device();
|
||||
let surface_config_format = graph.view_target().format();
|
||||
|
||||
// used to store InterpTransform components to add to entities later
|
||||
let mut component_queue: Vec<(Entity, InterpTransform)> = vec![];
|
||||
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();
|
||||
|
||||
for (
|
||||
entity,
|
||||
transform,
|
||||
_transform_epoch,
|
||||
(
|
||||
mesh_pair,
|
||||
scene_pair
|
||||
),
|
||||
interp_tran,
|
||||
delta_time,
|
||||
) in view
|
||||
{
|
||||
alive_entities.insert(entity);
|
||||
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,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 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 interp_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
|
||||
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
|
||||
.try_get_resource_data::<TransformBuffers>()
|
||||
.expect("Missing transform buffers");
|
||||
self.transform_buffers = Some(transforms.clone());
|
||||
|
||||
let render_meshes = world
|
||||
.try_get_resource_data::<RenderMeshes>()
|
||||
.expect("Missing transform buffers");
|
||||
self.render_meshes = Some(render_meshes.clone());
|
||||
|
||||
let mesh_buffers = world
|
||||
.try_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,
|
||||
},
|
||||
None => {
|
||||
let interp = InterpTransform {
|
||||
last_transform: *transform,
|
||||
alpha: 0.5,
|
||||
};
|
||||
component_queue.push((entity, interp));
|
||||
|
||||
*transform
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
// expand the transform buffers if they need to be.
|
||||
// this is done in its own scope to avoid multiple mutable references to self at
|
||||
// once; aka, make the borrow checker happy
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
if transforms.needs_expand() {
|
||||
debug!("Expanding transform buffers");
|
||||
transforms.expand_buffers(device);
|
||||
}
|
||||
}
|
||||
|
||||
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, entity, &*mesh, mesh_han.uuid())
|
||||
&& mesh_epoch == last_epoch {
|
||||
self.check_mesh_buffers(device, queue, &mesh_han);
|
||||
}
|
||||
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
let group = TransformGroup::EntityRes(entity, mesh_han.uuid());
|
||||
let transform_id = transforms.update_or_push(device, queue, &render_limits,
|
||||
group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation));
|
||||
|
||||
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_id);
|
||||
self.render_jobs.push_back(job);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((scene_han, scene_epoch)) = scene_pair {
|
||||
if let Some(scene) = scene_han.data_ref() {
|
||||
if scene_epoch == last_epoch {
|
||||
let view = scene.world().view::<(Entities, &mut WorldTransform, &Transform, Not<Has<RelationOriginComponent<ChildOf>>>)>();
|
||||
lyra_scene::system_update_world_transforms(scene.world(), view).unwrap();
|
||||
}
|
||||
|
||||
for (mesh_han, pos) in scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() {
|
||||
if let Some(mesh) = mesh_han.data_ref() {
|
||||
let mesh_interpo = interp_transform + **pos;
|
||||
|
||||
// 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, entity, &*mesh, mesh_han.uuid())
|
||||
&& scene_epoch == last_epoch {
|
||||
self.check_mesh_buffers(device, queue, &mesh_han);
|
||||
}
|
||||
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid());
|
||||
let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
|
||||
let transform_id = transforms.update_or_push(device, queue, &render_limits,
|
||||
group, mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) );
|
||||
|
||||
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_id);
|
||||
self.render_jobs.push_back(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
for (en, interp) in component_queue {
|
||||
world.insert(en, interp);
|
||||
}
|
||||
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
transforms.send_to_gpu(queue);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
|
@ -443,11 +374,14 @@ impl Node for MeshPass {
|
|||
) {
|
||||
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 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)
|
||||
|
@ -455,101 +389,117 @@ impl Node for MeshPass {
|
|||
.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 camera_bg = graph.bind_group(BasePassSlots::Camera);
|
||||
|
||||
let light_grid_bg = graph
|
||||
.bind_group(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
let lights_bg = graph.bind_group(LightBasePassSlots::Lights);
|
||||
|
||||
let material_bg = graph
|
||||
.bind_group(MeshesPassSlots::Material);
|
||||
let light_grid_bg = graph.bind_group(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
|
||||
let pipeline = graph.pipeline(context.label.clone())
|
||||
.expect("Failed to find pipeline for MeshPass");
|
||||
let shadows_atlas_bg = &self.shadows_atlas.as_ref().unwrap().bg;
|
||||
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &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,
|
||||
//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: true,
|
||||
},
|
||||
})],
|
||||
// enable depth buffer
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
// enable depth buffer
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
stencil_ops: None,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
pass.set_pipeline(&pipeline.as_render());
|
||||
pass.set_pipeline(pipeline);
|
||||
|
||||
//let material_buffer_bg = self.material_buffer.as_ref().unwrap().bindgroup();
|
||||
let default_texture = self.default_texture.as_ref().unwrap();
|
||||
let transforms = self.transforms.as_mut().unwrap();
|
||||
//let default_texture = self.default_texture.as_ref().unwrap();
|
||||
|
||||
while let Some(job) = self.render_jobs.pop_front() {
|
||||
// get the mesh (containing vertices) and the buffers from storage
|
||||
let buffers = self.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();
|
||||
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(), &[]);
|
||||
}
|
||||
// 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(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, ]);
|
||||
// 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(2, camera_bg, &[]);
|
||||
pass.set_bind_group(3, lights_bg, &[]);
|
||||
//pass.set_bind_group(4, material_bg, &[]);
|
||||
|
||||
pass.set_bind_group(6, &light_grid_bg, &[]);
|
||||
pass.set_bind_group(4, light_grid_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_bind_group(5, shadows_atlas_bg, &[]);
|
||||
|
||||
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();
|
||||
// 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.draw(0..vertex_count as u32, 0..1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,19 @@ mod light_base;
|
|||
pub use light_base::*;
|
||||
|
||||
mod present_pass;
|
||||
pub use 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::*;
|
|
@ -2,63 +2,43 @@ use std::hash::Hash;
|
|||
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::graph::{RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, Node, NodeDesc, NodeSlot, NodeType, SlotAttribute, SlotType};
|
||||
use crate::render::graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext};
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
|
||||
pub struct PresentPassLabel(RenderGraphLabelValue);
|
||||
|
||||
impl PresentPassLabel {
|
||||
pub fn new(label: impl RenderGraphLabel) -> Self {
|
||||
Self(label.into())
|
||||
}
|
||||
}
|
||||
pub struct PresentPassLabel;
|
||||
|
||||
/// Supplies some basic things other passes needs.
|
||||
///
|
||||
/// screen size buffer, camera buffer,
|
||||
pub struct PresentPass {
|
||||
/// Label of this pass
|
||||
pub label: PresentPassLabel,
|
||||
}
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PresentPass;
|
||||
|
||||
impl PresentPass {
|
||||
pub fn new(render_target_slot: impl RenderGraphLabel) -> Self {
|
||||
Self {
|
||||
//render_target_slot: render_target_slot.rc_clone(),
|
||||
label: PresentPassLabel::new(render_target_slot),
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for PresentPass {
|
||||
fn desc(&mut self, _graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc {
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeDesc::new(
|
||||
NodeType::Presenter,
|
||||
None,
|
||||
vec![],
|
||||
);
|
||||
|
||||
desc.add_slot(
|
||||
NodeSlot {
|
||||
ty: SlotType::RenderTarget,
|
||||
attribute: SlotAttribute::Input,
|
||||
label: self.label.0.clone(),
|
||||
value: None,
|
||||
}
|
||||
);
|
||||
|
||||
desc
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) {
|
||||
fn prepare(&mut self, _graph: &mut RenderGraph, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) {
|
||||
|
||||
}
|
||||
|
||||
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::NodeDesc, _context: &mut crate::render::graph::RenderGraphContext) {
|
||||
let mut slot = graph.slot_value_mut(self.label.0.clone())
|
||||
.expect(&format!("render target slot '{:?}' for PresentPass is missing", self.label.0))
|
||||
.as_render_target_mut().unwrap();
|
||||
let surf_tex = slot.current_texture.take().unwrap();
|
||||
surf_tex.present();
|
||||
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::NodeDesc, context: &mut crate::render::graph::RenderGraphContext) {
|
||||
let mut vt = graph.view_target_mut();
|
||||
vt.copy_to_primary(context.encoder.as_mut().unwrap());
|
||||
context.submit_encoder();
|
||||
|
||||
let frame = vt.primary.frame.take()
|
||||
.expect("ViewTarget.primary was already presented");
|
||||
frame.present();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,166 @@
|
|||
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::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: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
pass.set_pipeline(pipeline.as_render());
|
||||
|
||||
pass.set_bind_group(0, bg, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use crate::math;
|
||||
|
||||
enum RenderTargetInner {
|
||||
Surface {
|
||||
surface: wgpu::Surface,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
},
|
||||
Texture {
|
||||
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, 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,17 +1,24 @@
|
|||
pub mod point;
|
||||
pub mod directional;
|
||||
pub mod point;
|
||||
pub mod spotlight;
|
||||
|
||||
use lyra_ecs::{Entity, Tick, World};
|
||||
pub use point::*;
|
||||
pub use spotlight::*;
|
||||
|
||||
use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, rc::Rc};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use crate::math::Transform;
|
||||
|
||||
use self::directional::DirectionalLight;
|
||||
|
||||
use super::graph::LightShadowMapId;
|
||||
|
||||
const MAX_LIGHT_COUNT: usize = 16;
|
||||
|
||||
/// A struct that stores a list of lights in a wgpu::Buffer.
|
||||
|
@ -20,7 +27,7 @@ pub struct LightBuffer<U: Default + bytemuck::Pod + bytemuck::Zeroable> {
|
|||
/// The max amount of light casters that could fit in this buffer.
|
||||
pub max_count: usize,
|
||||
/// The amount of light casters that are taking up space in the buffer.
|
||||
///
|
||||
///
|
||||
/// This means that a light may be inactive in the buffer, by being replaced
|
||||
/// with a default caster as to not affect lighting. Its easier this way than
|
||||
/// to recreate the array and remove the gaps.
|
||||
|
@ -47,15 +54,27 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
}
|
||||
|
||||
/// Update an existing light in the light buffer.
|
||||
pub fn update_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) {
|
||||
let buffer_idx = *self.used_indexes.get(&entity)
|
||||
pub fn update_light(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
light: U,
|
||||
) {
|
||||
let buffer_idx = *self
|
||||
.used_indexes
|
||||
.get(&entity)
|
||||
.expect("Entity for Light is not in buffer!");
|
||||
|
||||
lights_buffer[buffer_idx] = light;
|
||||
}
|
||||
|
||||
/// Add a new light to the light buffer.
|
||||
pub fn add_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) {
|
||||
pub fn add_light(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
light: U,
|
||||
) {
|
||||
let buffer_idx = match self.dead_indexes.pop_front() {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
|
@ -67,15 +86,20 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
assert!(self.buffer_count <= self.max_count);
|
||||
|
||||
i
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
self.used_indexes.insert(entity, buffer_idx);
|
||||
self.update_light(lights_buffer, entity, light);
|
||||
}
|
||||
|
||||
/// Update, or add a new caster, to the light buffer.
|
||||
pub fn update_or_add(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) {
|
||||
pub fn update_or_add(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
light: U,
|
||||
) {
|
||||
if self.used_indexes.contains_key(&entity) {
|
||||
self.update_light(lights_buffer, entity, light);
|
||||
} else {
|
||||
|
@ -84,7 +108,11 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
}
|
||||
|
||||
/// Remove a caster from the buffer, returns true if it was removed.
|
||||
pub fn remove_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity) -> bool {
|
||||
pub fn remove_light(
|
||||
&mut self,
|
||||
lights_buffer: &mut [U; MAX_LIGHT_COUNT],
|
||||
entity: Entity,
|
||||
) -> bool {
|
||||
if let Some(removed_idx) = self.used_indexes.remove(&entity) {
|
||||
self.dead_indexes.push_back(removed_idx);
|
||||
//self.current_count -= 1;
|
||||
|
@ -98,9 +126,9 @@ impl<U: Default + bytemuck::Pod + bytemuck::Zeroable> LightBuffer<U> {
|
|||
}
|
||||
|
||||
pub(crate) struct LightUniformBuffers {
|
||||
pub buffer: Rc<wgpu::Buffer>,
|
||||
pub bind_group: Rc<wgpu::BindGroup>,
|
||||
pub bind_group_layout: Rc<wgpu::BindGroupLayout>,
|
||||
pub buffer: Arc<wgpu::Buffer>,
|
||||
pub bind_group: Arc<wgpu::BindGroup>,
|
||||
pub bind_group_layout: Arc<wgpu::BindGroupLayout>,
|
||||
max_light_count: u64,
|
||||
}
|
||||
|
||||
|
@ -110,54 +138,44 @@ impl LightUniformBuffers {
|
|||
// TODO: ensure we dont write over this limit
|
||||
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2;
|
||||
|
||||
let buffer = device.create_buffer(
|
||||
&wgpu::BufferDescriptor {
|
||||
label: Some("UBO_Lights"),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
size: max_buffer_sizes,
|
||||
mapped_at_creation: false,
|
||||
}
|
||||
);
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("UBO_Lights"),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
size: max_buffer_sizes,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage {
|
||||
read_only: true
|
||||
},
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
],
|
||||
count: None,
|
||||
}],
|
||||
label: Some("BGL_Lights"),
|
||||
});
|
||||
|
||||
let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &bindgroup_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(
|
||||
wgpu::BufferBinding {
|
||||
buffer: &buffer,
|
||||
offset: 0,
|
||||
size: None, // use the full buffer
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &buffer,
|
||||
offset: 0,
|
||||
size: None, // use the full buffer
|
||||
}),
|
||||
}],
|
||||
label: Some("BG_Lights"),
|
||||
});
|
||||
|
||||
Self {
|
||||
buffer: Rc::new(buffer),
|
||||
bind_group: Rc::new(bindgroup),
|
||||
bind_group_layout: Rc::new(bindgroup_layout),
|
||||
buffer: Arc::new(buffer),
|
||||
bind_group: Arc::new(bindgroup),
|
||||
bind_group_layout: Arc::new(bindgroup_layout),
|
||||
max_light_count: max_buffer_sizes / mem::size_of::<LightUniform>() as u64,
|
||||
}
|
||||
}
|
||||
|
@ -166,27 +184,43 @@ impl LightUniformBuffers {
|
|||
let _ = world_tick;
|
||||
let mut lights = vec![];
|
||||
|
||||
for (point_light, transform) in world.view_iter::<(&PointLight, &Transform)>() {
|
||||
let uniform = LightUniform::from_point_light_bundle(&point_light, &transform);
|
||||
for (point_light, transform, shadow_map_id) in
|
||||
world.view_iter::<(&PointLight, &Transform, Option<&LightShadowMapId>)>()
|
||||
{
|
||||
let shadow_map_id = shadow_map_id.map(|m| m.clone());
|
||||
let uniform =
|
||||
LightUniform::from_point_light_bundle(&point_light, &transform, shadow_map_id);
|
||||
lights.push(uniform);
|
||||
}
|
||||
|
||||
for (spot_light, transform) in world.view_iter::<(&SpotLight, &Transform)>() {
|
||||
let uniform = LightUniform::from_spot_light_bundle(&spot_light, &transform);
|
||||
for (spot_light, transform, shadow_map_id) in
|
||||
world.view_iter::<(&SpotLight, &Transform, Option<&LightShadowMapId>)>()
|
||||
{
|
||||
let shadow_map_id = shadow_map_id.map(|m| m.clone());
|
||||
let uniform =
|
||||
LightUniform::from_spot_light_bundle(&spot_light, &transform, shadow_map_id);
|
||||
lights.push(uniform);
|
||||
}
|
||||
|
||||
for (dir_light, transform) in world.view_iter::<(&DirectionalLight, &Transform)>() {
|
||||
let uniform = LightUniform::from_directional_bundle(&dir_light, &transform);
|
||||
for (dir_light, transform, shadow_map_id) in
|
||||
world.view_iter::<(&DirectionalLight, &Transform, Option<&LightShadowMapId>)>()
|
||||
{
|
||||
let shadow_map_id = shadow_map_id.map(|m| m.clone());
|
||||
let uniform =
|
||||
LightUniform::from_directional_bundle(&dir_light, &transform, shadow_map_id);
|
||||
lights.push(uniform);
|
||||
}
|
||||
|
||||
assert!(lights.len() < self.max_light_count as _); // ensure we dont overwrite the buffer
|
||||
assert!(lights.len() < self.max_light_count as usize); // ensure we dont overwrite the buffer
|
||||
|
||||
// write the amount of lights to the buffer, and right after that the list of lights.
|
||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[lights.len()]));
|
||||
// the size of u32 is multiplied by 4 because of gpu alignment requirements
|
||||
queue.write_buffer(&self.buffer, mem::size_of::<u32>() as u64 * 4, bytemuck::cast_slice(lights.as_slice()));
|
||||
queue.write_buffer(
|
||||
&self.buffer,
|
||||
mem::size_of::<u32>() as u64 * 4,
|
||||
bytemuck::cast_slice(lights.as_slice()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -209,17 +243,22 @@ pub(crate) struct LightUniform {
|
|||
pub color: glam::Vec3,
|
||||
// no padding is needed here since range acts as the padding
|
||||
// that would usually be needed for the vec3
|
||||
|
||||
pub range: f32,
|
||||
pub intensity: f32,
|
||||
pub smoothness: f32,
|
||||
|
||||
pub spot_cutoff_rad: f32,
|
||||
pub spot_outer_cutoff_rad: f32,
|
||||
pub light_shadow_uniform_index: [i32; 6],
|
||||
_padding: [u32; 2],
|
||||
}
|
||||
|
||||
impl LightUniform {
|
||||
pub fn from_point_light_bundle(light: &PointLight, transform: &Transform) -> Self {
|
||||
pub fn from_point_light_bundle(
|
||||
light: &PointLight,
|
||||
transform: &Transform,
|
||||
map_id: Option<LightShadowMapId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type: LightType::Point as u32,
|
||||
enabled: light.enabled as u32,
|
||||
|
@ -233,11 +272,27 @@ impl LightUniform {
|
|||
|
||||
spot_cutoff_rad: 0.0,
|
||||
spot_outer_cutoff_rad: 0.0,
|
||||
|
||||
light_shadow_uniform_index: map_id
|
||||
.map(|m| {
|
||||
[
|
||||
m.uniform_index(0) as i32,
|
||||
m.uniform_index(1) as i32,
|
||||
m.uniform_index(2) as i32,
|
||||
m.uniform_index(3) as i32,
|
||||
m.uniform_index(4) as i32,
|
||||
m.uniform_index(5) as i32,
|
||||
]
|
||||
})
|
||||
.unwrap_or([-1; 6]),
|
||||
_padding: [0; 2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_directional_bundle(light: &DirectionalLight, transform: &Transform) -> Self {
|
||||
pub fn from_directional_bundle(
|
||||
light: &DirectionalLight,
|
||||
transform: &Transform,
|
||||
map_id: Option<LightShadowMapId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type: LightType::Directional as u32,
|
||||
enabled: light.enabled as u32,
|
||||
|
@ -251,11 +306,28 @@ impl LightUniform {
|
|||
|
||||
spot_cutoff_rad: 0.0,
|
||||
spot_outer_cutoff_rad: 0.0,
|
||||
light_shadow_uniform_index: map_id
|
||||
.map(|m| {
|
||||
[
|
||||
m.uniform_index(0) as i32,
|
||||
m.uniform_index(1) as i32,
|
||||
m.uniform_index(2) as i32,
|
||||
m.uniform_index(3) as i32,
|
||||
m.uniform_index(4) as i32,
|
||||
m.uniform_index(5) as i32,
|
||||
]
|
||||
})
|
||||
.unwrap_or([-1; 6]),
|
||||
_padding: [0; 2],
|
||||
}
|
||||
}
|
||||
|
||||
// Create the SpotLightUniform from an ECS bundle
|
||||
pub fn from_spot_light_bundle(light: &SpotLight, transform: &Transform) -> Self {
|
||||
pub fn from_spot_light_bundle(
|
||||
light: &SpotLight,
|
||||
transform: &Transform,
|
||||
map_id: Option<LightShadowMapId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
light_type: LightType::Spotlight as u32,
|
||||
enabled: light.enabled as u32,
|
||||
|
@ -269,7 +341,19 @@ impl LightUniform {
|
|||
|
||||
spot_cutoff_rad: light.cutoff.to_radians(),
|
||||
spot_outer_cutoff_rad: light.outer_cutoff.to_radians(),
|
||||
light_shadow_uniform_index: map_id
|
||||
.map(|m| {
|
||||
[
|
||||
m.uniform_index(0) as i32,
|
||||
m.uniform_index(1) as i32,
|
||||
m.uniform_index(2) as i32,
|
||||
m.uniform_index(3) as i32,
|
||||
m.uniform_index(4) as i32,
|
||||
m.uniform_index(5) as i32,
|
||||
]
|
||||
})
|
||||
.unwrap_or([-1; 6]),
|
||||
_padding: [0; 2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ pub(crate) struct LightIndicesGridBuffer {
|
|||
}
|
||||
|
||||
pub(crate) struct LightCullCompute {
|
||||
device: Rc<wgpu::Device>,
|
||||
queue: Rc<wgpu::Queue>,
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
pipeline: ComputePipeline,
|
||||
pub light_indices_grid: LightIndicesGridBuffer,
|
||||
screen_size_buffer: BufferWrapper,
|
||||
|
@ -153,7 +153,7 @@ impl LightCullCompute {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new(device: Rc<wgpu::Device>, queue: Rc<wgpu::Queue>, screen_size: PhysicalSize<u32>, lights_buffers: &LightUniformBuffers, camera_buffers: &BufferWrapper, depth_texture: &mut RenderTexture) -> Self {
|
||||
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, screen_size: PhysicalSize<u32>, lights_buffers: &LightUniformBuffers, camera_buffers: &BufferWrapper, depth_texture: &mut RenderTexture) -> Self {
|
||||
let screen_size_buffer = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.label_prefix("ScreenSize")
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use lyra_resource::{ResHandle, Texture};
|
||||
|
||||
use super::texture::RenderTexture;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MaterialSpecular {
|
||||
pub factor: f32,
|
||||
pub color_factor: glam::Vec3,
|
||||
|
@ -11,7 +12,7 @@ pub struct MaterialSpecular {
|
|||
pub color_texture: Option<RenderTexture>,
|
||||
}
|
||||
|
||||
fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> {
|
||||
fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> {
|
||||
if let Some(tex) = i {
|
||||
RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok()
|
||||
} else {
|
||||
|
@ -20,7 +21,7 @@ fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Rc<
|
|||
}
|
||||
|
||||
impl MaterialSpecular {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Specular) -> Self {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Specular) -> Self {
|
||||
let tex = texture_to_render(device, queue, &bg_layout, &value.texture);
|
||||
let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture);
|
||||
|
||||
|
@ -45,7 +46,7 @@ pub struct Material {
|
|||
}
|
||||
|
||||
impl Material {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self {
|
||||
let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture);
|
||||
|
||||
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));
|
||||
|
@ -57,7 +58,7 @@ impl Material {
|
|||
//diffuse: glam::Vec3::new(value.base_color.x, value.base_color.y, value.base_color.z),
|
||||
//diffuse: glam::Vec3::new(1.0, 0.5, 0.31),
|
||||
//specular: glam::Vec3::new(0.5, 0.5, 0.5),
|
||||
ambient: glam::Vec3::new(1.0, 1.0, 1.0),
|
||||
ambient: glam::Vec3::new(1.0, 1.0, 1.0) * 0.5,
|
||||
diffuse: glam::Vec3::new(1.0, 1.0, 1.0),
|
||||
shininess: 32.0,
|
||||
|
||||
|
|
|
@ -14,4 +14,10 @@ pub mod transform_buffer_storage;
|
|||
pub mod light;
|
||||
//pub mod light_cull_compute;
|
||||
pub mod avec;
|
||||
pub mod graph;
|
||||
pub mod graph;
|
||||
|
||||
mod texture_atlas;
|
||||
pub use texture_atlas::*;
|
||||
|
||||
mod slot_buffer;
|
||||
pub use slot_buffer::*;
|
|
@ -1,4 +1,4 @@
|
|||
use std::{num::NonZeroU32, rc::Rc};
|
||||
use std::{num::NonZeroU32, sync::Arc};
|
||||
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
|
@ -23,11 +23,11 @@ impl RenderBuffer {
|
|||
|
||||
pub struct BindGroupPair {
|
||||
pub bindgroup: wgpu::BindGroup,
|
||||
pub layout: Rc<wgpu::BindGroupLayout>,
|
||||
pub layout: Arc<wgpu::BindGroupLayout>,
|
||||
}
|
||||
|
||||
impl BindGroupPair {
|
||||
pub fn create_bind_group(device: &wgpu::Device, layout: Rc<wgpu::BindGroupLayout>, entries: &[wgpu::BindGroupEntry<'_>]) -> Self {
|
||||
pub fn create_bind_group(device: &wgpu::Device, layout: Arc<wgpu::BindGroupLayout>, entries: &[wgpu::BindGroupEntry<'_>]) -> Self {
|
||||
let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &layout,
|
||||
entries,
|
||||
|
@ -43,7 +43,7 @@ impl BindGroupPair {
|
|||
pub fn new(bindgroup: wgpu::BindGroup, layout: wgpu::BindGroupLayout) -> Self {
|
||||
Self {
|
||||
bindgroup,
|
||||
layout: Rc::new(layout),
|
||||
layout: Arc::new(layout),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,9 +116,9 @@ impl BufferWrapper {
|
|||
/// match the layout of this bind group.
|
||||
///
|
||||
/// See [`wgpu::RenderPass::set_bind_group`](https://docs.rs/wgpu/latest/wgpu/struct.RenderPass.html#method.set_bind_group).
|
||||
pub fn render_pass_bind_at<'a, 'b>(
|
||||
pub fn render_pass_bind_at<'a>(
|
||||
&'a self,
|
||||
pass: &'b mut wgpu::RenderPass<'a>,
|
||||
pass: &mut wgpu::RenderPass<'a>,
|
||||
index: u32,
|
||||
offsets: &[wgpu::DynamicOffset],
|
||||
) {
|
||||
|
@ -136,7 +136,7 @@ impl BufferWrapper {
|
|||
}
|
||||
|
||||
/// Take the bind group layout, the bind group, and the buffer out of the wrapper.
|
||||
pub fn parts(self) -> (Option<Rc<wgpu::BindGroupLayout>>, Option<wgpu::BindGroup>, wgpu::Buffer) {
|
||||
pub fn parts(self) -> (Option<Arc<wgpu::BindGroupLayout>>, Option<wgpu::BindGroup>, wgpu::Buffer) {
|
||||
if let Some(pair) = self.bindgroup_pair {
|
||||
(Some(pair.layout), Some(pair.bindgroup), self.inner_buf)
|
||||
} else {
|
||||
|
@ -297,7 +297,7 @@ impl BufferWrapperBuilder {
|
|||
|
||||
BindGroupPair {
|
||||
bindgroup: bg,
|
||||
layout: Rc::new(bg_layout),
|
||||
layout: Arc::new(bg_layout),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -308,7 +308,7 @@ impl BufferWrapperBuilder {
|
|||
len: Some(self.count.unwrap_or_default() as usize),
|
||||
} */
|
||||
|
||||
(Rc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize)
|
||||
(Arc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize)
|
||||
}
|
||||
|
||||
pub fn finish(self, device: &wgpu::Device) -> BufferWrapper {
|
||||
|
@ -316,7 +316,7 @@ impl BufferWrapperBuilder {
|
|||
|
||||
BufferWrapper {
|
||||
bindgroup_pair: Some(BindGroupPair {
|
||||
layout: Rc::new(bgl),
|
||||
layout: Arc::new(bgl),
|
||||
bindgroup: bg
|
||||
}),
|
||||
inner_buf: buff,
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
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, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel};
|
||||
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, FxaaPass, FxaaPassLabel, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshPrepNode, MeshPrepNodeLabel, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, ShadowMapsPass, ShadowMapsPassLabel, SubGraphNode, ViewTarget};
|
||||
|
||||
use super::graph::RenderGraph;
|
||||
use super::{resource::RenderPipeline, render_job::RenderJob};
|
||||
|
||||
use crate::math;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ScreenSize(glam::UVec2);
|
||||
|
||||
|
@ -29,6 +33,9 @@ impl DerefMut for ScreenSize {
|
|||
}
|
||||
}
|
||||
|
||||
#[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>;
|
||||
|
@ -45,8 +52,8 @@ pub trait RenderPass {
|
|||
}
|
||||
|
||||
pub struct BasicRenderer {
|
||||
pub device: Rc<wgpu::Device>, // device does not need to be mutable, no need for refcell
|
||||
pub queue: Rc<wgpu::Queue>,
|
||||
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>,
|
||||
|
||||
|
@ -83,7 +90,7 @@ impl BasicRenderer {
|
|||
|
||||
let (device, queue) = adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
|
||||
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
|
||||
|
@ -109,7 +116,7 @@ impl BasicRenderer {
|
|||
.find(|f| f.describe().srgb)
|
||||
.unwrap_or(surface_caps.formats[0]);
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
|
||||
format: surface_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
|
@ -119,37 +126,98 @@ impl BasicRenderer {
|
|||
};
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let device = Rc::new(device);
|
||||
let queue = Rc::new(queue);
|
||||
let device = Arc::new(device);
|
||||
let queue = Arc::new(queue);
|
||||
|
||||
let mut g = RenderGraph::new(device.clone(), queue.clone());
|
||||
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");
|
||||
g.add_pass(BasePassLabel, BasePass::new(surface, config));
|
||||
debug!("Adding light base pass");
|
||||
g.add_pass(LightBasePassLabel, LightBasePass::new());
|
||||
debug!("Adding light cull compute pass");
|
||||
g.add_pass(LightCullComputePassLabel, LightCullComputePass::new(size));
|
||||
//debug!("Adding triangle pass");
|
||||
//g.add_pass(TrianglePass::new());
|
||||
main_graph.add_node(BasePassLabel, BasePass::new());
|
||||
|
||||
debug!("Adding mesh pass");
|
||||
g.add_pass(MeshesPassLabel, MeshPass::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 mesh pass");
|
||||
forward_plus_graph.add_node(ShadowMapsPassLabel, ShadowMapsPass::new(&device));
|
||||
|
||||
let mesh_prep = MeshPrepNode::new(&device);
|
||||
let material_bgl = mesh_prep.material_bgl.clone();
|
||||
forward_plus_graph.add_node(MeshPrepNodeLabel, mesh_prep);
|
||||
forward_plus_graph.add_node(MeshesPassLabel, MeshPass::new(material_bgl));
|
||||
|
||||
|
||||
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 p = PresentPass::new(BasePassSlots::MainRenderTarget);
|
||||
g.add_pass(p.label.clone(), p);
|
||||
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); */
|
||||
|
||||
g.add_edge(BasePassLabel, LightBasePassLabel);
|
||||
/* 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);
|
||||
|
||||
// make sure that present runs last
|
||||
g.add_edge(BasePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
|
||||
g.add_edge(LightCullComputePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
|
||||
g.add_edge(MeshesPassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
|
||||
g.add_edge(BasePassLabel, present_pass_label.clone());
|
||||
g.add_edge(LightCullComputePassLabel, present_pass_label.clone());
|
||||
g.add_edge(MeshesPassLabel, present_pass_label.clone()); */
|
||||
|
||||
g.setup(&device);
|
||||
main_graph.setup(&device);
|
||||
|
||||
Self {
|
||||
window,
|
||||
|
@ -165,7 +233,7 @@ impl BasicRenderer {
|
|||
render_pipelines: Default::default(),
|
||||
render_jobs: Default::default(),
|
||||
|
||||
graph: g,
|
||||
graph: main_graph,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,15 +257,19 @@ impl Renderer for BasicRenderer {
|
|||
self.size = new_size;
|
||||
|
||||
// update surface config and the surface
|
||||
let mut rt = self.graph.slot_value_mut(BasePassSlots::MainRenderTarget)
|
||||
/* let mut rt = self.graph.slot_value_mut(BasePassSlots::MainRenderTarget)
|
||||
.unwrap().as_render_target_mut().unwrap();
|
||||
rt.surface_config.width = new_size.width;
|
||||
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);
|
||||
rt.surface.configure(&self.device, &rt.surface_config); */
|
||||
|
||||
// update screen size resource in ecs
|
||||
let mut world_ss = world.get_resource_mut::<ScreenSize>();
|
||||
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ops::Deref, rc::Rc};
|
||||
use std::{ops::Deref, rc::Rc, sync::Arc};
|
||||
|
||||
use wgpu::PipelineLayout;
|
||||
|
||||
|
@ -7,7 +7,7 @@ use super::Shader;
|
|||
//#[derive(Debug, Clone)]
|
||||
pub struct ComputePipelineDescriptor {
|
||||
pub label: Option<String>,
|
||||
pub layouts: Vec<Rc<wgpu::BindGroupLayout>>,
|
||||
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
|
||||
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
|
||||
// TODO: make this a ResHandle<Shader>
|
||||
/// The compiled shader module for the stage.
|
||||
|
@ -74,7 +74,7 @@ impl ComputePipeline {
|
|||
// they share the same shader. I tried to do it without an Rc but couldn't get past
|
||||
// the borrow checker
|
||||
let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: desc.shader.label.as_ref().map(|s| s.as_str()),
|
||||
label: desc.shader.label.as_deref(),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
&desc.shader.source,
|
||||
)),
|
||||
|
|
|
@ -8,4 +8,7 @@ mod compute_pipeline;
|
|||
pub use compute_pipeline::*;
|
||||
|
||||
mod render_pipeline;
|
||||
pub use render_pipeline::*;
|
||||
pub use render_pipeline::*;
|
||||
|
||||
mod pass;
|
||||
pub use pass::*;
|
|
@ -0,0 +1,16 @@
|
|||
/// 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,5 +1,6 @@
|
|||
use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline, ComputePipelineDescriptor, RenderPipelineDescriptor};
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum PipelineDescriptor {
|
||||
Render(RenderPipelineDescriptor),
|
||||
Compute(ComputePipelineDescriptor),
|
||||
|
@ -26,19 +27,19 @@ pub enum Pipeline {
|
|||
Compute(ComputePipeline),
|
||||
}
|
||||
|
||||
impl Into<RenderPipeline> for Pipeline {
|
||||
fn into(self) -> RenderPipeline {
|
||||
match self {
|
||||
Self::Render(r) => r,
|
||||
impl From<Pipeline> for RenderPipeline {
|
||||
fn from(val: Pipeline) -> Self {
|
||||
match val {
|
||||
Pipeline::Render(r) => r,
|
||||
_ => panic!("Pipeline is not a RenderPipeline"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ComputePipeline> for Pipeline {
|
||||
fn into(self) -> ComputePipeline {
|
||||
match self {
|
||||
Self::Compute(c) => c,
|
||||
impl From<Pipeline> for ComputePipeline {
|
||||
fn from(val: Pipeline) -> Self {
|
||||
match val {
|
||||
Pipeline::Compute(c) => c,
|
||||
_ => panic!("Pipeline is not a RenderPipeline"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{num::NonZeroU32, ops::Deref, rc::Rc};
|
||||
use std::{num::NonZeroU32, ops::Deref, sync::Arc};
|
||||
|
||||
use wgpu::PipelineLayout;
|
||||
|
||||
|
@ -7,7 +7,7 @@ use super::{FragmentState, VertexState};
|
|||
//#[derive(Debug, Clone)]
|
||||
pub struct RenderPipelineDescriptor {
|
||||
pub label: Option<String>,
|
||||
pub layouts: Vec<Rc<wgpu::BindGroupLayout>>,
|
||||
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
|
||||
pub push_constant_ranges: Vec<wgpu::PushConstantRange>,
|
||||
pub vertex: VertexState,
|
||||
pub fragment: Option<FragmentState>,
|
||||
|
@ -87,14 +87,14 @@ impl RenderPipeline {
|
|||
// an Rc was used here so that this shader could be reused by the fragment stage if
|
||||
// they share the same shader. I tried to do it without an Rc but couldn't get past
|
||||
// the borrow checker
|
||||
let vrtx_shad = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: desc.vertex.module.label.as_ref().map(|s| s.as_str()),
|
||||
let vrtx_shad = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: desc.vertex.module.label.as_deref(),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
|
||||
&desc.vertex.module.source,
|
||||
)),
|
||||
}));
|
||||
let vrtx_state = wgpu::VertexState {
|
||||
module: &*vrtx_shad,
|
||||
module: &vrtx_shad,
|
||||
entry_point: &desc.vertex.entry_point,
|
||||
buffers: &vrtx_buffs,
|
||||
};
|
||||
|
@ -103,8 +103,8 @@ impl RenderPipeline {
|
|||
if f.module == desc.vertex.module {
|
||||
vrtx_shad.clone()
|
||||
} else {
|
||||
Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: f.module.label.as_ref().map(|s| s.as_str()),
|
||||
Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: f.module.label.as_deref(),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)),
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Vertex shader
|
||||
#define_module lyra::main_3d
|
||||
#import lyra::shadows::bindings::{u_light_shadow}
|
||||
#import lyra::shadows::calc::{calc_shadow_dir_light, calc_shadow_point_light, calc_shadow_spot_light}
|
||||
|
||||
const max_light_count: u32 = 16u;
|
||||
// Vertex shader
|
||||
|
||||
const LIGHT_TY_DIRECTIONAL = 0u;
|
||||
const LIGHT_TY_POINT = 1u;
|
||||
|
@ -19,6 +21,7 @@ struct VertexOutput {
|
|||
@location(0) tex_coords: vec2<f32>,
|
||||
@location(1) world_position: vec3<f32>,
|
||||
@location(2) world_normal: vec3<f32>,
|
||||
@location(3) frag_pos_light_space: vec4<f32>,
|
||||
}
|
||||
|
||||
struct TransformData {
|
||||
|
@ -33,7 +36,7 @@ struct CameraUniform {
|
|||
projection: mat4x4<f32>,
|
||||
position: vec3<f32>,
|
||||
tile_debug: u32,
|
||||
};
|
||||
}
|
||||
|
||||
struct Light {
|
||||
position: vec3<f32>,
|
||||
|
@ -48,12 +51,13 @@ struct Light {
|
|||
|
||||
spot_cutoff: f32,
|
||||
spot_outer_cutoff: f32,
|
||||
};
|
||||
light_shadow_uniform_index: array<i32, 6>,
|
||||
}
|
||||
|
||||
struct Lights {
|
||||
light_count: u32,
|
||||
data: array<Light>,
|
||||
};
|
||||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> u_model_transform_data: TransformData;
|
||||
|
@ -70,16 +74,16 @@ fn vs_main(
|
|||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
|
||||
out.world_position = world_position.xyz;
|
||||
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.clip_position = u_camera.view_projection * u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
|
||||
out.clip_position = u_camera.view_projection * world_position;
|
||||
|
||||
// the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36
|
||||
let normal_mat4 = u_model_transform_data.normal_matrix;
|
||||
let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz);
|
||||
out.world_normal = normalize(normal_mat * model.normal, );
|
||||
|
||||
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
|
||||
out.world_position = world_position.xyz;
|
||||
out.world_normal = normalize(normal_mat * model.normal);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
@ -87,29 +91,24 @@ fn vs_main(
|
|||
// Fragment shader
|
||||
|
||||
struct Material {
|
||||
ambient: vec4<f32>,
|
||||
diffuse: vec4<f32>,
|
||||
specular: vec4<f32>,
|
||||
ambient: vec3<f32>,
|
||||
diffuse: vec3<f32>,
|
||||
shininess: f32,
|
||||
specular_factor: f32,
|
||||
specular_color: vec3<f32>,
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
var<uniform> u_material: Material;
|
||||
@group(0) @binding(1)
|
||||
var t_diffuse: texture_2d<f32>;
|
||||
@group(0) @binding(2)
|
||||
var s_diffuse: sampler;
|
||||
|
||||
@group(4) @binding(0)
|
||||
var<uniform> u_material: Material;
|
||||
|
||||
@group(5) @binding(0)
|
||||
var t_specular: texture_2d<f32>;
|
||||
@group(5) @binding(1)
|
||||
var s_specular: sampler;
|
||||
|
||||
@group(6) @binding(0)
|
||||
var<storage, read_write> u_light_indices: array<u32>;
|
||||
@group(6) @binding(1)
|
||||
var t_light_grid: texture_storage_2d<rg32uint, read_write>; // vec2<u32>
|
||||
@group(4) @binding(1)
|
||||
var t_light_grid: texture_storage_2d<rg32uint, read_write>; // rg32uint = vec2<u32>
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
|
@ -118,7 +117,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
}
|
||||
|
||||
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||
let specular_color: vec3<f32> = textureSample(t_specular, s_specular, in.tex_coords).xyz;
|
||||
let specular_color: vec3<f32> = vec3<f32>(0.0); //textureSample(t_specular, s_specular, in.tex_coords).xyz;
|
||||
var light_res = vec3<f32>(0.0);
|
||||
|
||||
if (object_color.a < ALPHA_CUTOFF) {
|
||||
|
@ -134,13 +133,20 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||
for (var i = 0u; i < light_count; i++) {
|
||||
let light_index = u_light_indices[light_offset + i];
|
||||
let light: Light = u_lights.data[light_index];
|
||||
let light_dir = normalize(-light.direction);
|
||||
|
||||
if (light.light_ty == LIGHT_TY_DIRECTIONAL) {
|
||||
light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color);
|
||||
let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
|
||||
let frag_pos_light_space = shadow_u.light_space_matrix * vec4<f32>(in.world_position, 1.0);
|
||||
|
||||
let shadow = calc_shadow_dir_light(in.world_position, in.world_normal, light_dir, light);
|
||||
light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
|
||||
} else if (light.light_ty == LIGHT_TY_POINT) {
|
||||
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color);
|
||||
let shadow = calc_shadow_point_light(in.world_position, in.world_normal, light_dir, light);
|
||||
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
|
||||
} else if (light.light_ty == LIGHT_TY_SPOT) {
|
||||
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color);
|
||||
let shadow = calc_shadow_spot_light(in.world_position, in.world_normal, light_dir, light);
|
||||
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,7 +169,7 @@ fn debug_grid(in: VertexOutput) -> vec4<f32> {
|
|||
return vec4<f32>(ratio, ratio, ratio, 1.0);
|
||||
}
|
||||
|
||||
fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> {
|
||||
fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
|
||||
let light_color = dir_light.color.xyz;
|
||||
let camera_view_pos = u_camera.position;
|
||||
|
||||
|
@ -189,10 +195,10 @@ fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light:
|
|||
diffuse_color *= dir_light.diffuse;
|
||||
specular_color *= dir_light.specular;*/
|
||||
|
||||
return (ambient_color + diffuse_color + specular_color) * dir_light.intensity;
|
||||
return (ambient_color + (shadow) * (diffuse_color + specular_color)) * dir_light.intensity;
|
||||
}
|
||||
|
||||
fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> {
|
||||
fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
|
||||
let light_color = point_light.color.xyz;
|
||||
let light_pos = point_light.position.xyz;
|
||||
let camera_view_pos = u_camera.position;
|
||||
|
@ -222,10 +228,11 @@ fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_li
|
|||
diffuse_color *= attenuation;
|
||||
specular_color *= attenuation;
|
||||
|
||||
return (ambient_color + diffuse_color + specular_color) * point_light.intensity;
|
||||
//return (ambient_color + shadow * (diffuse_color + specular_color)) * point_light.intensity;
|
||||
return (shadow * (ambient_color + diffuse_color + specular_color)) * point_light.intensity;
|
||||
}
|
||||
|
||||
fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>) -> vec3<f32> {
|
||||
fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
|
||||
let light_color = spot_light.color;
|
||||
let light_pos = spot_light.position;
|
||||
let camera_view_pos = u_camera.position;
|
||||
|
@ -260,13 +267,13 @@ fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_ligh
|
|||
let distance = length(light_pos - world_pos);
|
||||
let attenuation = calc_attenuation(spot_light, distance);
|
||||
|
||||
ambient_color *= attenuation * spot_light.intensity * cone;
|
||||
diffuse_color *= attenuation * spot_light.intensity * cone;
|
||||
specular_color *= attenuation * spot_light.intensity * cone;
|
||||
ambient_color *= attenuation * cone;
|
||||
diffuse_color *= attenuation * cone;
|
||||
specular_color *= attenuation * cone;
|
||||
//// end of spot light attenuation ////
|
||||
|
||||
|
||||
return /*ambient_color +*/ diffuse_color + specular_color;
|
||||
//return /*ambient_color +*/ diffuse_color + specular_color;
|
||||
return (shadow * (diffuse_color + specular_color)) * spot_light.intensity;
|
||||
}
|
||||
|
||||
fn calc_attenuation(light: Light, distance: f32) -> f32 {
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
// 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);
|
||||
}
|
|
@ -31,6 +31,7 @@ struct Light {
|
|||
|
||||
spot_cutoff: f32,
|
||||
spot_outer_cutoff: f32,
|
||||
light_shadow_uniform_index: array<i32, 6>,
|
||||
};
|
||||
|
||||
struct Lights {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#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>>;
|
|
@ -0,0 +1,352 @@
|
|||
#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);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#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);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#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,
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
@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);
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
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,4 +1,4 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use image::GenericImageView;
|
||||
use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode};
|
||||
|
@ -44,7 +44,7 @@ impl RenderTexture {
|
|||
})
|
||||
}
|
||||
|
||||
fn create_bind_group_pair(device: &wgpu::Device, layout: Rc<wgpu::BindGroupLayout>, view: &wgpu::TextureView, sampler: &wgpu::Sampler) -> BindGroupPair {
|
||||
fn create_bind_group_pair(device: &wgpu::Device, layout: Arc<wgpu::BindGroupLayout>, view: &wgpu::TextureView, sampler: &wgpu::Sampler) -> BindGroupPair {
|
||||
let bg = device.create_bind_group(
|
||||
&wgpu::BindGroupDescriptor {
|
||||
layout: &layout,
|
||||
|
@ -68,12 +68,12 @@ impl RenderTexture {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, bytes: &[u8], label: &str) -> anyhow::Result<Self> {
|
||||
pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, bytes: &[u8], label: &str) -> anyhow::Result<Self> {
|
||||
let img = image::load_from_memory(bytes)?;
|
||||
Self::from_image(device, queue, bg_layout, &img, Some(label))
|
||||
}
|
||||
|
||||
pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, img: &image::DynamicImage, label: Option<&str>) -> anyhow::Result<Self> {
|
||||
pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, img: &image::DynamicImage, label: Option<&str>) -> anyhow::Result<Self> {
|
||||
let rgba = img.to_rgba8();
|
||||
let dimensions = img.dimensions();
|
||||
|
||||
|
@ -134,7 +134,7 @@ impl RenderTexture {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Rc<wgpu::BindGroupLayout>, texture_res: &ResHandle<Texture>, label: Option<&str>) -> anyhow::Result<Self> {
|
||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, texture_res: &ResHandle<Texture>, label: Option<&str>) -> anyhow::Result<Self> {
|
||||
let texture_ref = texture_res.data_ref().unwrap();
|
||||
let img = texture_ref.image.data_ref().unwrap();
|
||||
|
||||
|
@ -254,10 +254,10 @@ impl RenderTexture {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn create_depth_texture(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, label: &str) -> Self {
|
||||
pub fn create_depth_texture(device: &wgpu::Device, size: crate::math::UVec2, label: &str) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
width: size.x,
|
||||
height: size.y,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
|
@ -371,7 +371,7 @@ impl RenderTexture {
|
|||
|
||||
/// Convert [`lyra_resource::WrappingMode`] to [`wgpu::AddressMode`]
|
||||
#[inline(always)]
|
||||
fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode {
|
||||
pub(crate) fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode {
|
||||
match wmode {
|
||||
WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
|
||||
WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
|
||||
|
@ -381,7 +381,7 @@ fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode {
|
|||
|
||||
/// Convert [`lyra_resource::FilterMode`] to [`wgpu::FilterMode`]
|
||||
#[inline(always)]
|
||||
fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode {
|
||||
pub(crate) fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode {
|
||||
match fmode {
|
||||
FilterMode::Nearest => wgpu::FilterMode::Nearest,
|
||||
FilterMode::Linear => wgpu::FilterMode::Linear,
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
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,4 +1,4 @@
|
|||
use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, rc::Rc};
|
||||
use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, sync::Arc};
|
||||
|
||||
use lyra_ecs::Entity;
|
||||
use tracing::instrument;
|
||||
|
@ -106,14 +106,17 @@ impl<K: Hash + Eq + PartialEq + Clone, V: Clone, S: BuildHasher> CachedValMap<K,
|
|||
where
|
||||
F: FnMut() -> V
|
||||
{
|
||||
if self.latest.contains_key(&key) {
|
||||
self.latest.insert(key, val_fn());
|
||||
None
|
||||
} else {
|
||||
let val = self.dead.pop_front()
|
||||
.unwrap_or_else(val_fn);
|
||||
self.latest.insert(key, val.clone());
|
||||
Some(val)
|
||||
match self.latest.entry(key) {
|
||||
std::collections::hash_map::Entry::Occupied(mut e) => {
|
||||
e.insert(val_fn());
|
||||
None
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(e) => {
|
||||
let val = self.dead.pop_front()
|
||||
.unwrap_or_else(val_fn);
|
||||
e.insert(val.clone());
|
||||
Some(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,7 +165,7 @@ impl<K: Hash + Eq + PartialEq + Clone, V: Clone, S: BuildHasher> CachedValMap<K,
|
|||
/// [`TransformGroup`]s are used to represent entries in the buffer. They are used to insert,
|
||||
/// update, and retrieve the transforms.
|
||||
pub struct TransformBuffers {
|
||||
pub bindgroup_layout: Rc<wgpu::BindGroupLayout>,
|
||||
pub bindgroup_layout: Arc<wgpu::BindGroupLayout>,
|
||||
//groups: CachedValMap<TransformGroupId, TransformIndex>,
|
||||
//groups: SlotMap<TransformGroupId, TransformIndex>,
|
||||
entries: Vec<BufferEntry>,
|
||||
|
@ -192,7 +195,7 @@ impl TransformBuffers {
|
|||
});
|
||||
|
||||
let mut s = Self {
|
||||
bindgroup_layout: Rc::new(bindgroup_layout),
|
||||
bindgroup_layout: Arc::new(bindgroup_layout),
|
||||
entries: Default::default(),
|
||||
max_transform_count: (limits.max_uniform_buffer_binding_size) as usize / (limits.min_uniform_buffer_offset_alignment as usize), //(mem::size_of::<glam::Mat4>()),
|
||||
limits,
|
||||
|
@ -217,7 +220,7 @@ impl TransformBuffers {
|
|||
entry.len = 0;
|
||||
|
||||
let p = entry.transforms.as_ptr();
|
||||
let bytes = unsafe { std::slice::from_raw_parts(p as *const u8, entry.transforms.len() * entry.transforms.align()) };
|
||||
let bytes = unsafe { std::slice::from_raw_parts(p, entry.transforms.len() * entry.transforms.align()) };
|
||||
|
||||
queue.write_buffer(&entry.buffer, 0, bytes);
|
||||
}
|
||||
|
@ -334,17 +337,14 @@ impl TransformBuffers {
|
|||
pub fn buffer_offset(&self, transform_index: TransformIndex) -> u32 {
|
||||
//Self::get_buffer_offset(&self.limits, transform_index)
|
||||
let transform_index = transform_index.transform_index % self.max_transform_count;
|
||||
let t = transform_index as u32 * self.limits.min_uniform_buffer_offset_alignment as u32;
|
||||
|
||||
//debug!("offset: {t}");
|
||||
t
|
||||
transform_index as u32 * self.limits.min_uniform_buffer_offset_alignment
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if the buffers need to be expanded
|
||||
pub fn needs_expand(&self) -> bool {
|
||||
false
|
||||
/* self.entries.last()
|
||||
.map(|entry| entry.len >= self.max_transform_count)
|
||||
.unwrap_or(false) */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,23 @@ impl Vertex {
|
|||
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 {
|
||||
|
|
|
@ -10,7 +10,7 @@ pub fn radians_to_degrees(radians: f32) -> f32 {
|
|||
radians * 180.0 / PI
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Angle {
|
||||
Degrees(f32),
|
||||
Radians(f32),
|
||||
|
@ -68,4 +68,18 @@ impl std::ops::SubAssign for Angle {
|
|||
Angle::Radians(r) => *r -= rhs.to_radians(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Angle {
|
||||
type Output = Angle;
|
||||
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Angle::Radians(self.to_radians() * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::MulAssign<f32> for Angle {
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
*self = *self * rhs;
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ impl From<gltf::material::AlphaMode> for AlphaMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Reflect)]
|
||||
#[derive(Clone, Reflect, Default)]
|
||||
pub struct Specular {
|
||||
/// The strength of the specular reflection, default of 1.0
|
||||
pub factor: f32,
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a761f4094bc18190285b4687ec804161fea874b6
|
||||
Subproject commit 54c9926a04cdef657289fd67730c0b85d1bdda3e
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 70daf320827f64b325a77718df07177d74d7ea58
|
Loading…
Reference in New Issue