diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 5053bea8..323de4bf 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -17,7 +17,7 @@ jobs: install-cargo-gpu: strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-24.04, macos-latest] runs-on: ${{ matrix.os }} defaults: run: @@ -117,7 +117,9 @@ jobs: - name: Install linux deps if: runner.os == 'Linux' - run: sudo apt-get -y install mesa-vulkan-drivers libvulkan1 vulkan-tools vulkan-validationlayers + run: | + sudo apt-get -y update + sudo apt-get -y install mesa-vulkan-drivers libvulkan1 vulkan-tools vulkan-validationlayers - name: Install cargo-nextest run: cargo install --locked cargo-nextest || true diff --git a/Cargo.lock b/Cargo.lock index 47793739..5c2a0dd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -14,30 +14,24 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" - -[[package]] -name = "acorn_prng" -version = "3.0.1" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4812dd5f835c603721f95a2c057394fc12a9d53206588664e68247e7cb25549b" +checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -54,9 +48,18 @@ dependencies = [ [[package]] name = "aligned-vec" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" @@ -65,7 +68,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.8.0", + "bitflags 2.9.1", "cc", "cesu8", "jni", @@ -74,7 +77,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "thiserror 1.0.69", ] @@ -85,6 +88,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -105,9 +114,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -120,44 +129,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "approx" @@ -191,7 +200,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -240,9 +249,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" dependencies = [ "concurrent-queue", "event-listener-strategy", @@ -257,7 +266,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ "async-lock", - "autocfg 1.4.0", + "autocfg 1.5.0", "blocking", "futures-lite 1.13.0", ] @@ -300,20 +309,20 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" dependencies = [ - "autocfg 1.4.0", + "autocfg 1.5.0", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "av1-grain" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" dependencies = [ "anyhow", "arrayvec", @@ -325,9 +334,9 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42" dependencies = [ "arrayvec", ] @@ -367,9 +376,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -397,11 +406,11 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ - "async-channel 2.3.1", + "async-channel 2.5.0", "async-task", "futures-io", "futures-lite 2.6.0", @@ -410,34 +419,34 @@ dependencies = [ [[package]] name = "built" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -454,9 +463,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "calloop" @@ -464,10 +473,10 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "log", "polling", - "rustix", + "rustix 0.38.44", "slab", "thiserror 1.0.69", ] @@ -479,16 +488,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix", + "rustix 0.38.44", "wayland-backend", "wayland-client", ] [[package]] name = "cc" -version = "1.2.10" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -513,9 +522,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -535,6 +544,20 @@ dependencies = [ "serde", ] +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "clap" version = "2.34.0" @@ -552,9 +575,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.26" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -562,9 +585,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -574,21 +597,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cloudabi" @@ -601,10 +624,11 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ + "serde", "termcolor", "unicode-width", ] @@ -617,9 +641,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -670,6 +694,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -683,8 +717,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "foreign-types", "libc", ] @@ -696,31 +730,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation 0.9.4", + "core-graphics", + "foreign-types", "libc", ] [[package]] name = "craballoc" -version = "0.1.11" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fab2b385e6d66aa64ef46e97f8752018c8d3393d1ed58a25fe5ff29931ab129" +checksum = "1b95922b0bb3618d32bb66e762e807c8edd73578434e14da2e0dd38ef685bad1" dependencies = [ "async-channel 1.9.0", "bytemuck", "crabslab", "log", "rustc-hash 1.1.0", - "snafu 0.8.5", + "snafu 0.8.6", "tracing", "wgpu", ] [[package]] name = "crabslab" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3486b5979b1c73ca6ba48f69e2abd7941dd72b0e725519d113aef61ff7a236" +checksum = "cfe77aba0fb1ad6ddb4d30744bfc61e1420a9cc1fa981b3556cc423506df9450" dependencies = [ "crabslab-derive", "futures-lite 1.13.0", @@ -730,13 +787,13 @@ dependencies = [ [[package]] name = "crabslab-derive" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef999dd82fff9dc1f2cf371c0f9a6315016b9562a04811fbefae2d80da6a1fad" +checksum = "32554318ca91eb0c2c6a05659ec2cd1627fd44142d54547bb3d0aa7405a979b2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -750,9 +807,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -790,9 +847,9 @@ checksum = "0dc013e70da3bfe5b552de26a1f34ecf67d61ea811251d2bf75c1324a1ecb425" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "ctor" @@ -801,14 +858,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "cursor-icon" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "dagga" @@ -828,6 +885,27 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.60.2", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -851,9 +929,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "document-features" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] @@ -872,15 +950,27 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" -version = "0.1.1" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dwrote" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "env_logger" @@ -895,20 +985,40 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -939,9 +1049,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener 5.4.0", "pin-project-lite", @@ -951,19 +1061,18 @@ dependencies = [ name = "example" version = "0.1.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.40", "craballoc", "env_logger", "futures-lite 1.13.0", "gltf", "icosahedron", - "image", + "image 0.25.6", "img-diff", "lazy_static", "loading-bytes", "log", "renderling", - "renderling_ui", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1058,14 +1167,20 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + [[package]] name = "float_next_after" version = "1.0.0" @@ -1074,9 +1189,34 @@ checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "font-kit" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" +dependencies = [ + "bitflags 2.9.1", + "byteorder", + "core-foundation 0.9.4", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] [[package]] name = "foreign-types" @@ -1096,7 +1236,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -1105,6 +1245,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1160,20 +1311,42 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gif" -version = "0.13.1" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -1192,9 +1365,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.29.2" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11" dependencies = [ "libm", ] @@ -1220,7 +1393,7 @@ dependencies = [ "base64", "byteorder", "gltf-json", - "image", + "image 0.25.6", "lazy_static", "serde_json", "urlencoding", @@ -1235,7 +1408,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -1261,14 +1434,14 @@ dependencies = [ [[package]] name = "glyph_brush" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225e5bd28cddb97578e31da78ae24eff4d6611523fb03b73f300d511461834c0" +checksum = "0060f4ed4ef64a5876d9836d7d6c9ed43a463f3ca431682bec1c326064c8c93e" dependencies = [ "glyph_brush_draw_cache", "glyph_brush_layout", "ordered-float", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "twox-hash", ] @@ -1283,7 +1456,7 @@ dependencies = [ "crossbeam-deque", "linked-hash-map", "rayon", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", ] [[package]] @@ -1303,7 +1476,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "gpu-alloc-types", ] @@ -1313,7 +1486,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] @@ -1330,11 +1503,11 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "gpu-descriptor-types", "hashbrown", ] @@ -1345,25 +1518,28 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", + "num-traits", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -1390,9 +1566,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hexf-parse" @@ -1400,11 +1576,41 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "human-repr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" + [[package]] name = "humantime" -version = "2.1.0" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] [[package]] name = "icosahedron" @@ -1422,15 +1628,29 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "image" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", - "gif", + "gif 0.13.3", "image-webp", "num-traits", "png", @@ -1445,9 +1665,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b" dependencies = [ "byteorder-lite", "quick-error", @@ -1458,7 +1678,8 @@ name = "img-diff" version = "0.1.0" dependencies = [ "glam", - "image", + "image 0.25.6", + "log", "snafu 0.7.5", ] @@ -1470,9 +1691,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -1501,16 +1722,16 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi 0.5.2", "libc", "windows-sys 0.52.0", ] @@ -1532,9 +1753,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" @@ -1560,18 +1781,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -1614,15 +1836,15 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libfuzzer-sys" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" dependencies = [ "arbitrary", "cc", @@ -1630,12 +1852,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -1646,13 +1868,13 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "libc", - "redox_syscall 0.5.8", + "redox_syscall 0.5.13", ] [[package]] @@ -1667,6 +1889,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litrs" version = "0.4.1" @@ -1680,7 +1908,7 @@ dependencies = [ "async-fs", "js-sys", "send_wrapper", - "snafu 0.8.5", + "snafu 0.8.6", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1688,19 +1916,19 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ - "autocfg 1.4.0", + "autocfg 1.5.0", "scopeguard", ] [[package]] name = "log" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" @@ -1744,9 +1972,9 @@ dependencies = [ [[package]] name = "lyon_path" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e" +checksum = "0047f508cd7a85ad6bad9518f68cce7b1bf6b943fb71f6da0ee3bc1e8cb75f25" dependencies = [ "lyon_geom", "num-traits", @@ -1784,9 +2012,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -1799,13 +2027,13 @@ dependencies = [ [[package]] name = "metal" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types", "log", "objc", @@ -1830,9 +2058,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -1840,25 +2068,29 @@ dependencies = [ [[package]] name = "naga" -version = "24.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" +checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.8.0", + "bitflags 2.9.1", + "cfg-if", "cfg_aliases", "codespan-reporting", + "half", + "hashbrown", "hexf-parse", "indexmap", + "libm", "log", + "num-traits", + "once_cell", "petgraph", "rustc-hash 1.1.0", "spirv", - "strum", - "termcolor", - "thiserror 2.0.11", - "unicode-xid", + "thiserror 2.0.12", + "unicode-ident", ] [[package]] @@ -1867,10 +2099,10 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "jni-sys", "log", - "ndk-sys 0.6.0+11769913", + "ndk-sys", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -1882,15 +2114,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.5.0+25.2.9519653" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" -dependencies = [ - "jni-sys", -] - [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -1932,6 +2155,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -1940,7 +2172,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -1969,29 +2201,30 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg 1.4.0", + "autocfg 1.5.0", "libm", ] [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -2025,7 +2258,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "libc", "objc2", @@ -2041,7 +2274,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-core-location", @@ -2065,7 +2298,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-foundation", @@ -2097,9 +2330,9 @@ dependencies = [ [[package]] name = "objc2-encode" -version = "4.0.3" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" @@ -2107,7 +2340,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "dispatch", "libc", @@ -2132,7 +2365,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-foundation", @@ -2144,7 +2377,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-foundation", @@ -2167,7 +2400,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-cloud-kit", @@ -2199,7 +2432,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "objc2", "objc2-core-location", @@ -2208,12 +2441,24 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "orbclient" +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orbclient" version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" @@ -2223,9 +2468,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "4.6.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" dependencies = [ "num-traits", ] @@ -2247,9 +2492,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2257,13 +2502,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.8", + "redox_syscall 0.5.13", "smallvec", "windows-targets 0.52.6", ] @@ -2280,6 +2525,25 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57" +dependencies = [ + "rustc_version", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2288,32 +2552,33 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" dependencies = [ "fixedbitset", + "hashbrown", "indexmap", ] [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -2335,9 +2600,55 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "chrono", + "font-kit", + "image 0.24.9", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser 0.20.0", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-bitmap" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "72ce181e3f6bf82d6c1dc569103ca7b1bd964c60ba03d7e6cdfbb3e3eb7f7405" +dependencies = [ + "gif 0.12.0", + "image 0.24.9", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] [[package]] name = "png" @@ -2354,24 +2665,39 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi 0.5.2", "pin-project-lite", - "rustix", + "rustix 1.0.7", "tracing", "windows-sys 0.59.0", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -2392,41 +2718,50 @@ dependencies = [ "yansi", ] +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -2446,22 +2781,28 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.6.5" @@ -2492,6 +2833,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + [[package]] name = "rand_chacha" version = "0.1.1" @@ -2512,6 +2863,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -2533,7 +2894,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] @@ -2641,9 +3011,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.11" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" dependencies = [ "avif-serialize", "imgref", @@ -2700,11 +3070,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "bitflags 2.8.0", + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", ] [[package]] @@ -2746,7 +3127,6 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" name = "renderling" version = "0.5.0" dependencies = [ - "acorn_prng", "assert_approx_eq", "async-channel 1.9.0", "bytemuck", @@ -2762,21 +3142,27 @@ dependencies = [ "futures-lite 1.13.0", "glam", "gltf", + "glyph_brush", "half", + "human-repr", "icosahedron", - "image", + "image 0.25.6", "img-diff", + "loading-bytes", "log", + "lyon", "metal", "naga", "pathdiff", + "plotters", "pretty_assertions", "quote", "renderling_build", "rustc-hash 1.1.0", "serde", "serde_json", - "snafu 0.8.5", + "similarity", + "snafu 0.8.6", "spirv-std", "ttf-parser 0.20.0", "wgpu", @@ -2795,33 +3181,11 @@ dependencies = [ "serde_json", ] -[[package]] -name = "renderling_ui" -version = "0.3.6" -dependencies = [ - "craballoc", - "crabslab", - "ctor", - "env_logger", - "futures-lite 1.13.0", - "glyph_brush", - "image", - "img-diff", - "loading-bytes", - "log", - "lyon", - "pretty_assertions", - "renderling", - "rustc-hash 1.1.0", - "snafu 0.8.5", - "wgpu", -] - [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f" [[package]] name = "rustc-hash" @@ -2831,34 +3195,70 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustfft" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f140db74548f7c9d7cce60912c9ac414e74df5e718dc947d514b051b42f3f4" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + +[[package]] +name = "rustix" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] [[package]] name = "rustix" -version = "0.38.43" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.59.0", + "linux-raw-sys 0.9.4", + "windows-sys 0.52.0", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -2903,6 +3303,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "send_wrapper" version = "0.6.0" @@ -2911,29 +3317,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -2943,9 +3349,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -2972,14 +3378,23 @@ dependencies = [ ] [[package]] -name = "slab" -version = "0.4.9" +name = "similarity" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "ccc60ff94fb3aba15a5a89bcab3ed826bf8f25b15c531aab64671ea1b237fa10" dependencies = [ - "autocfg 1.4.0", + "num-traits", + "rand 0.9.1", + "rayon", + "rustfft", ] +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + [[package]] name = "slotmap" version = "1.0.7" @@ -2991,9 +3406,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" @@ -3001,14 +3416,14 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", "memmap2", - "rustix", + "rustix 0.38.44", "thiserror 1.0.69", "wayland-backend", "wayland-client", @@ -3041,11 +3456,11 @@ dependencies = [ [[package]] name = "snafu" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "223891c85e2a29c3fe8fb900c1fae5e69c2e42415e3177752e8718475efa5019" +checksum = "320b01e011bf8d5d7a4a4a4be966d9160968935849c83b918827f6a435e7f627" dependencies = [ - "snafu-derive 0.8.5", + "snafu-derive 0.8.6", ] [[package]] @@ -3062,14 +3477,14 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c3c6b7927ffe7ecaa769ee0e3994da3b8cafc8f444578982c83ecb161af917" +checksum = "1961e2ef424c1424204d3a5d6975f934f56b6d50ff5732382d84ebf460e147f7" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -3078,16 +3493,17 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] name = "spirv-std" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=6e2c84d#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421" +source = "git+https://github.com/LegNeato/rust-gpu.git?rev=425328a#425328a3ac7f1f18db914d24b3d4754bf13bb7ac" dependencies = [ "bitflags 1.3.2", "glam", + "libm", "num-traits", "spirv-std-macros", "spirv-std-types", @@ -3096,18 +3512,18 @@ dependencies = [ [[package]] name = "spirv-std-macros" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=6e2c84d#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421" +source = "git+https://github.com/LegNeato/rust-gpu.git?rev=425328a#425328a3ac7f1f18db914d24b3d4754bf13bb7ac" dependencies = [ "proc-macro2", "quote", "spirv-std-types", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "spirv-std-types" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu?rev=6e2c84d#6e2c84d4fe64e32df4c060c5a7f3e35a32e45421" +source = "git+https://github.com/LegNeato/rust-gpu.git?rev=425328a#425328a3ac7f1f18db914d24b3d4754bf13bb7ac" [[package]] name = "static_assertions" @@ -3115,6 +3531,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "strict-num" version = "0.1.1" @@ -3133,28 +3555,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.96", -] - [[package]] name = "syn" version = "1.0.109" @@ -3168,9 +3568,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -3225,11 +3625,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -3240,18 +3640,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -3292,9 +3692,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -3304,18 +3704,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", @@ -3337,24 +3737,34 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "ttf-parser" version = "0.20.0" @@ -3369,18 +3779,18 @@ checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" [[package]] name = "twox-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908" +checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" dependencies = [ - "rand 0.8.5", + "rand 0.9.1", ] [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -3394,12 +3804,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "urlencoding" version = "2.1.3" @@ -3414,9 +3818,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "v_frame" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" dependencies = [ "aligned-vec", "num-traits", @@ -3459,9 +3863,18 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasm-bindgen" @@ -3485,7 +3898,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -3520,7 +3933,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3555,18 +3968,18 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.44", "scoped-tls", "smallvec", "wayland-sys", @@ -3574,12 +3987,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.7" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ - "bitflags 2.8.0", - "rustix", + "bitflags 2.9.1", + "rustix 0.38.44", "wayland-backend", "wayland-scanner", ] @@ -3590,29 +4003,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.7" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" dependencies = [ - "rustix", + "rustix 0.38.44", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.5" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -3620,11 +4033,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3633,11 +4046,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3646,9 +4059,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", "quick-xml", @@ -3657,9 +4070,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" dependencies = [ "dlib", "log", @@ -3689,24 +4102,27 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" -version = "24.0.1" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47f55718f85c2fa756edffa0e7f0e0a60aba463d1362b57e23123c58f035e4b6" +checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" dependencies = [ "arrayvec", - "bitflags 2.8.0", + "bitflags 2.9.1", + "cfg-if", "cfg_aliases", "document-features", + "hashbrown", "js-sys", "log", "naga", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "smallvec", @@ -3721,50 +4137,85 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "24.0.0" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a39b8842dc9ffcbe34346e3ab6d496b32a47f6497e119d762c97fcaae3cb37" +checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9" dependencies = [ "arrayvec", + "bit-set", "bit-vec", - "bitflags 2.8.0", + "bitflags 2.9.1", "bytemuck", "cfg_aliases", "document-features", + "hashbrown", "indexmap", "log", "naga", "once_cell", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14" +dependencies = [ + "wgpu-hal", +] + [[package]] name = "wgpu-hal" -version = "24.0.0" +version = "26.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a782e5056b060b0b4010881d1decddd059e44f2ecd01e2db2971b48ad3627e5" +checksum = "7df2c64ac282a91ad7662c90bc4a77d4a2135bc0b2a2da5a4d4e267afc034b9e" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags 2.8.0", + "bitflags 2.9.1", "block", "bytemuck", + "cfg-if", "cfg_aliases", - "core-graphics-types", + "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", + "hashbrown", "js-sys", "khronos-egl", "libc", @@ -3772,34 +4223,36 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys 0.5.0+25.2.9519653", + "ndk-sys", "objc", - "once_cell", "ordered-float", "parking_lot", + "portable-atomic", + "portable-atomic-util", "profiling", "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", "wasm-bindgen", "web-sys", "wgpu-types", "windows", - "windows-core", + "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "24.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", + "bytemuck", "js-sys", "log", + "thiserror 2.0.12", "web-sys", ] @@ -3825,7 +4278,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3840,7 +4293,7 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core", + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -3850,13 +4303,26 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -3865,7 +4331,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -3876,9 +4353,26 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-result" version = "0.2.0" @@ -3888,16 +4382,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3925,6 +4437,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3964,13 +4485,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3989,6 +4526,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4007,6 +4550,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4025,12 +4574,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4049,6 +4610,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4067,6 +4634,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4085,6 +4658,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4103,22 +4682,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winit" -version = "0.30.8" +version = "0.30.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d74280aabb958072864bff6cfbcf9025cf8bfacdde5e32b5e12920ef703b0f" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.8.0", + "bitflags 2.9.1", "block2", "bytemuck", "calloop", "cfg_aliases", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", @@ -4135,7 +4720,7 @@ dependencies = [ "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix", + "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -4157,13 +4742,31 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.24" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -4186,7 +4789,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix", + "rustix 0.38.44", "x11rb-protocol", ] @@ -4198,9 +4801,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xi-unicode" @@ -4214,7 +4817,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "dlib", "log", "once_cell", @@ -4229,15 +4832,15 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xtask" version = "0.1.0" dependencies = [ - "clap 4.5.26", + "clap 4.5.40", "env_logger", "log", "renderling_build", @@ -4249,25 +4852,35 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.104", ] [[package]] @@ -4287,9 +4900,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.14" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index a79115a9..e7716e39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "crates/loading-bytes", "crates/renderling", "crates/renderling-build", - "crates/renderling-ui", "crates/sandbox", "crates/xtask" ] @@ -16,23 +15,26 @@ exclude = ["./shaders"] resolver = "2" [workspace.dependencies] -acorn_prng = "3.0" assert_approx_eq = "1.1.0" async-channel = "1.8" bytemuck = { version = "1.19.0", features = ["derive"] } cfg_aliases = "0.2" clap = { version = "4.5.23", features = ["derive"] } -craballoc = { version = "0.1.11" } -crabslab = { version = "0.6.3", default-features = false } +craballoc = { version = "0.2.2" } +crabslab = { version = "0.6.5", default-features = false } +plotters = "0.3.7" ctor = "0.2.2" dagga = "0.2.1" env_logger = "0.10.0" futures-lite = "1.13" -glam = { version = "0.29", default-features = false } +glam = { version = "0.30", default-features = false } gltf = { version = "1.4,1", features = ["KHR_lights_punctual", "KHR_materials_unlit", "KHR_materials_emissive_strength", "extras", "extensions"] } +glyph_brush = "0.7.8" image = "0.25" log = "0.4" -naga = { version = "24.0", features = ["spv-in", "wgsl-out", "wgsl-in", "msl-out"] } +loading-bytes = { path = "crates/loading-bytes", version = "0.1.1" } +lyon = "1.0.1" +naga = { version = "26.0", features = ["spv-in", "wgsl-out", "wgsl-in", "msl-out"] } pretty_assertions = "1.4.0" proc-macro2 = { version = "1.0", features = ["span-locations"] } quote = "1.0" @@ -40,17 +42,19 @@ rustc-hash = "1.1" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0.117" send_wrapper = "0.6.0" +similarity = "0.2.0" snafu = "0.8" -spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "6e2c84d" } +spirv-std = { git = "https://github.com/LegNeato/rust-gpu.git", rev = "425328a" } +spirv-std-macros = { git = "https://github.com/LegNeato/rust-gpu.git", rev = "425328a" } syn = { version = "2.0.49", features = ["full", "extra-traits", "parsing"] } tracing = "0.1.41" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = "0.3" winit = { version = "0.30" } -wgpu = { version = "24.0" } -wgpu-core = { version = "24.0" } -metal = "0.31" +wgpu = { version = "26.0" } +wgpu-core = { version = "26.0" } +metal = "0.32" [profile.dev] opt-level = 1 @@ -62,4 +66,4 @@ opt-level = 3 opt-level = 3 [patch.crates-io] -spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "6e2c84d" } +spirv-std = { git = "https://github.com/LegNeato/rust-gpu.git", rev = "425328a" } diff --git a/bin_lights.wgsl b/bin_lights.wgsl new file mode 100644 index 00000000..ebd61548 --- /dev/null +++ b/bin_lights.wgsl @@ -0,0 +1,703 @@ +struct type_2 { + member: array, +} + +struct type_10 { + member: u32, + member_1: u32, +} + +struct type_15 { + member: vec3, + member_1: vec4, + member_2: vec3, +} + +struct type_17 { + member: vec2, + member_1: type_10, + member_2: f32, +} + +struct type_18 { + member: type_10, + member_1: u32, + member_2: u32, + member_3: u32, + member_4: u32, +} + +struct type_20 { + member: vec3, + member_1: type_10, + member_2: u32, + member_3: u32, +} + +struct type_21 { + member: vec3, + member_1: vec4, + member_2: f32, +} + +struct type_22 { + member: vec3, + member_1: vec3, + member_2: f32, + member_3: f32, + member_4: vec4, + member_5: f32, +} + +struct type_25 { + member: array>, +} + +var global: vec3; +@group(0) @binding(0) +var global_1: type_2; +@group(0) @binding(1) +var global_2: type_25; +var global_3: vec2 = vec2(16u, 16u); + +fn function() { + var phi_747_: type_18; + var phi_2201_: bool; + var phi_777_: type_17; + var phi_2244_: bool; + var phi_816_: u32; + var phi_891_: u32; + var phi_2411_: bool; + var phi_910_: type_10; + var phi_3515_: bool; + var phi_1236_: type_20; + var phi_1279_: u32; + var phi_1282_: type_10; + var phi_1300_: u32; + var phi_2497_: bool; + var phi_1361_: type_15; + var phi_2529_: bool; + var phi_1601_: type_22; + var phi_2883_: f32; + var phi_2905_: bool; + var phi_1449_: type_21; + var phi_3258_: f32; + var phi_3280_: bool; + var phi_1407_: type_21; + var phi_1692_: f32; + var phi_1693_: f32; + var phi_1704_: f32; + var phi_3316_: u32; + var phi_3503_: bool; + var phi_3362_: u32; + var phi_3387_: u32; + var phi_1778_: type_10; + var phi_1781_: u32; + var phi_1783_: f32; + var phi_1779_: type_10; + var phi_1806_: type_10; + var phi_3413_: u32; + var phi_1782_: u32; + var phi_1784_: f32; + var phi_1825_: bool; + var local: u32; + var local_1: bool; + var phi_3540_: bool; + var phi_1851_: bool; + var phi_3539_: bool; + var phi_1855_: bool; + var phi_3538_: bool; + var phi_1859_: bool; + var phi_3537_: bool; + + switch bitcast(0u) { + default: { + let _e73 = arrayLength((&global_2.member)); + let _e74 = global; + if select(false, true, (_e73 >= 6u)) { + let _e79 = atomicLoad((&global_2.member[0u])); + let _e82 = atomicLoad((&global_2.member[1u])); + let _e85 = atomicLoad((&global_2.member[2u])); + let _e88 = atomicLoad((&global_2.member[3u])); + let _e91 = atomicLoad((&global_2.member[4u])); + let _e94 = atomicLoad((&global_2.member[5u])); + phi_747_ = type_18(type_10(_e79, _e82), _e85, _e88, _e91, _e94); + } else { + phi_747_ = type_18(type_10(4294967295u, 0u), 4294967295u, 4294967295u, 0u, 4294967295u); + } + let _e98 = phi_747_; + if (_e73 >= 5u) { + phi_2201_ = (_e98.member_4 <= (_e73 - 5u)); + } else { + phi_2201_ = false; + } + let _e104 = phi_2201_; + if _e104 { + let _e107 = atomicLoad((&global_2.member[_e98.member_4])); + let _e111 = atomicLoad((&global_2.member[(_e98.member_4 + 1u)])); + let _e116 = atomicLoad((&global_2.member[(_e98.member_4 + 2u)])); + let _e120 = atomicLoad((&global_2.member[(_e98.member_4 + 3u)])); + let _e124 = atomicLoad((&global_2.member[(_e98.member_4 + 4u)])); + phi_777_ = type_17(vec2(_e107, _e111), type_10(_e116, _e120), bitcast(_e124)); + } else { + phi_777_ = type_17(vec2(0u, 0u), type_10(4294967295u, 0u), 0.25f); + } + let _e129 = phi_777_; + if (_e74.x < _e129.member.x) { + phi_2244_ = (_e74.y < _e129.member.y); + } else { + phi_2244_ = false; + } + let _e142 = phi_2244_; + if _e142 { + let _e144 = (_e74.x / 16u); + let _e145 = (_e74.y / 16u); + let _e146 = f32(_e129.member.x); + let _e150 = global_3[0u]; + let _e154 = ceil((_e146 / f32(_e150))); + let _e161 = ((_e145 * select(select(u32(_e154), 0u, (_e154 < 0f)), 4294967295u, (_e154 > 4294967000f))) + _e144); + if (_e161 >= _e129.member_1.member_1) { + phi_816_ = 4294967295u; + } else { + phi_816_ = (_e129.member_1.member + (9u * _e161)); + } + let _e166 = phi_816_; + let _e169 = atomicLoad((&global_2.member[_e166])); + let _e173 = atomicLoad((&global_2.member[(_e166 + 1u)])); + let _e175 = (f32(_e169) * 0.00000000023283064f); + let _e177 = (f32(_e173) * 0.00000000023283064f); + if (_e175 == _e177) { + } else { + let _e183 = global_3[0u]; + let _e185 = global_3[1u]; + let _e189 = ceil((_e146 / f32(_e183))); + let _e190 = ceil((f32(_e129.member.y) / f32(_e185))); + let _e208 = ((((f32(_e144) + 0.5f) / f32(select(select(u32(_e189), 0u, (_e189 < 0f)), 4294967295u, (_e189 > 4294967000f)))) * 2f) - 1f); + let _e209 = (((1f - ((f32(_e145) + 0.5f) / f32(select(select(u32(_e190), 0u, (_e190 < 0f)), 4294967295u, (_e190 > 4294967000f))))) * 2f) - 1f); + let _e210 = global_3[0u]; + let _e213 = ceil((_e146 / f32(_e210))); + let _e220 = ((_e145 * select(select(u32(_e213), 0u, (_e213 < 0f)), 4294967295u, (_e213 > 4294967000f))) + _e144); + if (_e220 >= _e129.member_1.member_1) { + phi_891_ = 4294967295u; + } else { + phi_891_ = (_e129.member_1.member + (9u * _e220)); + } + let _e225 = phi_891_; + let _e226 = (_e225 + 3u); + if (_e73 >= 2u) { + phi_2411_ = (_e226 <= (_e73 - 2u)); + } else { + phi_2411_ = false; + } + let _e231 = phi_2411_; + if _e231 { + let _e234 = atomicLoad((&global_2.member[_e226])); + let _e238 = atomicLoad((&global_2.member[(_e225 + 4u)])); + phi_910_ = type_10(_e234, _e238); + } else { + phi_910_ = type_10(4294967295u, 0u); + } + let _e241 = phi_910_; + let _e244 = (_e225 + 2u); + let _e247 = global_1.member[0u]; + let _e250 = global_1.member[_e247]; + let _e251 = bitcast(_e250); + let _e255 = global_1.member[(_e247 + 1u)]; + let _e256 = bitcast(_e255); + let _e260 = global_1.member[(_e247 + 2u)]; + let _e261 = bitcast(_e260); + let _e265 = global_1.member[(_e247 + 3u)]; + let _e266 = bitcast(_e265); + let _e270 = global_1.member[(_e247 + 4u)]; + let _e271 = bitcast(_e270); + let _e275 = global_1.member[(_e247 + 5u)]; + let _e276 = bitcast(_e275); + let _e280 = global_1.member[(_e247 + 6u)]; + let _e281 = bitcast(_e280); + let _e285 = global_1.member[(_e247 + 7u)]; + let _e286 = bitcast(_e285); + let _e290 = global_1.member[(_e247 + 8u)]; + let _e291 = bitcast(_e290); + let _e295 = global_1.member[(_e247 + 9u)]; + let _e296 = bitcast(_e295); + let _e300 = global_1.member[(_e247 + 10u)]; + let _e301 = bitcast(_e300); + let _e305 = global_1.member[(_e247 + 11u)]; + let _e306 = bitcast(_e305); + let _e310 = global_1.member[(_e247 + 12u)]; + let _e311 = bitcast(_e310); + let _e315 = global_1.member[(_e247 + 13u)]; + let _e316 = bitcast(_e315); + let _e320 = global_1.member[(_e247 + 14u)]; + let _e321 = bitcast(_e320); + let _e325 = global_1.member[(_e247 + 15u)]; + let _e326 = bitcast(_e325); + let _e330 = global_1.member[(_e247 + 16u)]; + let _e331 = bitcast(_e330); + let _e335 = global_1.member[(_e247 + 17u)]; + let _e336 = bitcast(_e335); + let _e340 = global_1.member[(_e247 + 18u)]; + let _e341 = bitcast(_e340); + let _e345 = global_1.member[(_e247 + 19u)]; + let _e346 = bitcast(_e345); + let _e350 = global_1.member[(_e247 + 20u)]; + let _e351 = bitcast(_e350); + let _e355 = global_1.member[(_e247 + 21u)]; + let _e356 = bitcast(_e355); + let _e360 = global_1.member[(_e247 + 22u)]; + let _e361 = bitcast(_e360); + let _e365 = global_1.member[(_e247 + 23u)]; + let _e366 = bitcast(_e365); + let _e370 = global_1.member[(_e247 + 24u)]; + let _e371 = bitcast(_e370); + let _e375 = global_1.member[(_e247 + 25u)]; + let _e376 = bitcast(_e375); + let _e380 = global_1.member[(_e247 + 26u)]; + let _e381 = bitcast(_e380); + let _e385 = global_1.member[(_e247 + 27u)]; + let _e386 = bitcast(_e385); + let _e390 = global_1.member[(_e247 + 28u)]; + let _e391 = bitcast(_e390); + let _e395 = global_1.member[(_e247 + 29u)]; + let _e396 = bitcast(_e395); + let _e400 = global_1.member[(_e247 + 30u)]; + let _e401 = bitcast(_e400); + let _e405 = global_1.member[(_e247 + 31u)]; + let _e406 = bitcast(_e405); + let _e409 = atomicLoad((&global_2.member[0u])); + let _e412 = atomicLoad((&global_2.member[1u])); + phi_3515_ = false; + phi_1236_ = type_20(_e74, type_10(_e409, _e412), 0u, 256u); + loop { + let _e416 = phi_3515_; + let _e418 = phi_1236_; + let _e429 = ((_e418.member_2 * _e418.member_3) + (((_e418.member.y % 16u) * 16u) + (_e418.member.x % 16u))); + if (_e429 < _e418.member_1.member_1) { + if (_e429 >= _e418.member_1.member_1) { + phi_1279_ = 4294967295u; + } else { + phi_1279_ = (_e418.member_1.member + _e429); + } + let _e447 = phi_1279_; + phi_1282_ = type_10(1u, _e447); + } else { + phi_1282_ = type_10(0u, type_10().member_1); + } + let _e450 = phi_1282_; + switch bitcast(_e450.member) { + case 0: { + phi_3538_ = _e416; + phi_1859_ = false; + break; + } + case 1: { + let _e456 = atomicLoad((&global_2.member[_e450.member_1])); + let _e459 = atomicLoad((&global_2.member[_e456])); + switch bitcast(_e459) { + case 0: { + phi_1300_ = 0u; + break; + } + case 1: { + phi_1300_ = 1u; + break; + } + case 2: { + phi_1300_ = 2u; + break; + } + default: { + phi_1300_ = 0u; + break; + } + } + let _e462 = phi_1300_; + let _e466 = atomicLoad((&global_2.member[(_e456 + 1u)])); + let _e470 = atomicLoad((&global_2.member[(_e456 + 2u)])); + if (_e73 >= 10u) { + phi_2497_ = (_e470 <= (_e73 - 10u)); + } else { + phi_2497_ = false; + } + let _e475 = phi_2497_; + if _e475 { + let _e478 = atomicLoad((&global_2.member[_e470])); + let _e483 = atomicLoad((&global_2.member[(_e470 + 1u)])); + let _e488 = atomicLoad((&global_2.member[(_e470 + 2u)])); + let _e494 = atomicLoad((&global_2.member[(_e470 + 3u)])); + let _e499 = atomicLoad((&global_2.member[(_e470 + 4u)])); + let _e504 = atomicLoad((&global_2.member[(_e470 + 5u)])); + let _e509 = atomicLoad((&global_2.member[(_e470 + 6u)])); + let _e515 = atomicLoad((&global_2.member[(_e470 + 7u)])); + let _e520 = atomicLoad((&global_2.member[(_e470 + 8u)])); + let _e525 = atomicLoad((&global_2.member[(_e470 + 9u)])); + phi_1361_ = type_15(vec3(bitcast(_e478), bitcast(_e483), bitcast(_e488)), vec4(bitcast(_e494), bitcast(_e499), bitcast(_e504), bitcast(_e509)), vec3(bitcast(_e515), bitcast(_e520), bitcast(_e525))); + } else { + phi_1361_ = type_15(vec3(0f, 0f, 0f), vec4(0f, 0f, 0f, 1f), vec3(1f, 1f, 1f)); + } + let _e530 = phi_1361_; + switch bitcast(_e462) { + case 0: { + if (_e73 >= 8u) { + phi_3280_ = (_e466 <= (_e73 - 8u)); + } else { + phi_3280_ = false; + } + let _e1168 = phi_3280_; + if _e1168 { + let _e1171 = atomicLoad((&global_2.member[_e466])); + let _e1176 = atomicLoad((&global_2.member[(_e466 + 1u)])); + let _e1181 = atomicLoad((&global_2.member[(_e466 + 2u)])); + let _e1187 = atomicLoad((&global_2.member[(_e466 + 3u)])); + let _e1192 = atomicLoad((&global_2.member[(_e466 + 4u)])); + let _e1197 = atomicLoad((&global_2.member[(_e466 + 5u)])); + let _e1202 = atomicLoad((&global_2.member[(_e466 + 6u)])); + let _e1208 = atomicLoad((&global_2.member[(_e466 + 7u)])); + phi_1407_ = type_21(vec3(bitcast(_e1171), bitcast(_e1176), bitcast(_e1181)), vec4(bitcast(_e1187), bitcast(_e1192), bitcast(_e1197), bitcast(_e1202)), bitcast(_e1208)); + } else { + phi_1407_ = type_21(vec3(0f, -1f, 0f), vec4(1f, 1f, 1f, 1f), 1f); + } + let _e1212 = phi_1407_; + phi_1692_ = _e1212.member_2; + phi_1693_ = 0f; + break; + } + case 1: { + if (_e73 >= 8u) { + phi_2905_ = (_e466 <= (_e73 - 8u)); + } else { + phi_2905_ = false; + } + let _e865 = phi_2905_; + if _e865 { + let _e868 = atomicLoad((&global_2.member[_e466])); + let _e873 = atomicLoad((&global_2.member[(_e466 + 1u)])); + let _e878 = atomicLoad((&global_2.member[(_e466 + 2u)])); + let _e884 = atomicLoad((&global_2.member[(_e466 + 3u)])); + let _e889 = atomicLoad((&global_2.member[(_e466 + 4u)])); + let _e894 = atomicLoad((&global_2.member[(_e466 + 5u)])); + let _e899 = atomicLoad((&global_2.member[(_e466 + 6u)])); + let _e905 = atomicLoad((&global_2.member[(_e466 + 7u)])); + phi_1449_ = type_21(vec3(bitcast(_e868), bitcast(_e873), bitcast(_e878)), vec4(bitcast(_e884), bitcast(_e889), bitcast(_e894), bitcast(_e899)), bitcast(_e905)); + } else { + phi_1449_ = type_21(vec3(0f, 0f, 0f), vec4(1f, 1f, 1f, 1f), 1f); + } + let _e909 = phi_1449_; + let _e917 = (_e530.member_1.x + _e530.member_1.x); + let _e918 = (_e530.member_1.y + _e530.member_1.y); + let _e919 = (_e530.member_1.z + _e530.member_1.z); + let _e920 = (_e530.member_1.x * _e917); + let _e921 = (_e530.member_1.x * _e918); + let _e922 = (_e530.member_1.x * _e919); + let _e923 = (_e530.member_1.y * _e918); + let _e924 = (_e530.member_1.y * _e919); + let _e925 = (_e530.member_1.z * _e919); + let _e926 = (_e530.member_1.w * _e917); + let _e927 = (_e530.member_1.w * _e918); + let _e928 = (_e530.member_1.w * _e919); + let _e945 = (vec4((1f - (_e923 + _e925)), (_e921 + _e928), (_e922 - _e927), 0f) * _e530.member_2.x); + let _e947 = (vec4((_e921 - _e928), (1f - (_e920 + _e925)), (_e924 + _e926), 0f) * _e530.member_2.y); + let _e949 = (vec4((_e922 + _e927), (_e924 - _e926), (1f - (_e920 + _e923)), 0f) * _e530.member_2.z); + let _e981 = (_e530.member.x + ((_e949.x * _e909.member.z) + ((_e947.x * _e909.member.y) + (_e945.x * _e909.member.x)))); + let _e982 = (_e530.member.y + ((_e949.y * _e909.member.z) + ((_e947.y * _e909.member.y) + (_e945.y * _e909.member.x)))); + let _e983 = (_e530.member.z + ((_e949.z * _e909.member.z) + ((_e947.z * _e909.member.y) + (_e945.z * _e909.member.x)))); + let _e1119 = (((((_e266 * _e391) + (_e286 * _e396)) + (_e306 * _e401)) + (_e326 * _e406)) + ((((((_e266 * _e371) + (_e286 * _e376)) + (_e306 * _e381)) + (_e326 * _e386)) * _e983) + ((((((_e266 * _e351) + (_e286 * _e356)) + (_e306 * _e361)) + (_e326 * _e366)) * _e982) + (((((_e266 * _e331) + (_e286 * _e336)) + (_e306 * _e341)) + (_e326 * _e346)) * _e981)))); + let _e1120 = ((((((_e251 * _e391) + (_e271 * _e396)) + (_e291 * _e401)) + (_e311 * _e406)) + ((((((_e251 * _e371) + (_e271 * _e376)) + (_e291 * _e381)) + (_e311 * _e386)) * _e983) + ((((((_e251 * _e351) + (_e271 * _e356)) + (_e291 * _e361)) + (_e311 * _e366)) * _e982) + (((((_e251 * _e331) + (_e271 * _e336)) + (_e291 * _e341)) + (_e311 * _e346)) * _e981)))) / _e1119); + let _e1121 = ((((((_e256 * _e391) + (_e276 * _e396)) + (_e296 * _e401)) + (_e316 * _e406)) + ((((((_e256 * _e371) + (_e276 * _e376)) + (_e296 * _e381)) + (_e316 * _e386)) * _e983) + ((((((_e256 * _e351) + (_e276 * _e356)) + (_e296 * _e361)) + (_e316 * _e366)) * _e982) + (((((_e256 * _e331) + (_e276 * _e336)) + (_e296 * _e341)) + (_e316 * _e346)) * _e981)))) / _e1119); + let _e1122 = ((((((_e261 * _e391) + (_e281 * _e396)) + (_e301 * _e401)) + (_e321 * _e406)) + ((((((_e261 * _e371) + (_e281 * _e376)) + (_e301 * _e381)) + (_e321 * _e386)) * _e983) + ((((((_e261 * _e351) + (_e281 * _e356)) + (_e301 * _e361)) + (_e321 * _e366)) * _e982) + (((((_e261 * _e331) + (_e281 * _e336)) + (_e301 * _e341)) + (_e321 * _e346)) * _e981)))) / _e1119); + let _e1123 = (_e208 - _e208); + let _e1124 = (_e209 - _e209); + let _e1125 = (_e175 - _e177); + let _e1131 = sqrt((((_e1123 * _e1123) + (_e1124 * _e1124)) + (_e1125 * _e1125))); + if (_e1131 <= 0.00000011920929f) { + let _e1152 = (_e1120 - _e208); + let _e1153 = (_e1121 - _e209); + let _e1154 = (_e1122 - _e175); + phi_3258_ = sqrt((((_e1152 * _e1152) + (_e1153 * _e1153)) + (_e1154 * _e1154))); + } else { + let _e1133 = (_e1120 - _e208); + let _e1134 = (_e1121 - _e209); + let _e1135 = (_e1122 - _e175); + let _e1136 = (_e1122 - _e177); + let _e1139 = ((_e1134 * _e1136) - (_e1134 * _e1135)); + let _e1142 = ((_e1135 * _e1133) - (_e1136 * _e1133)); + let _e1143 = (_e1133 * _e1134); + let _e1144 = (_e1143 - _e1143); + phi_3258_ = (sqrt((((_e1139 * _e1139) + (_e1142 * _e1142)) + (_e1144 * _e1144))) / _e1131); + } + let _e1162 = phi_3258_; + phi_1692_ = _e909.member_2; + phi_1693_ = _e1162; + break; + } + case 2: { + if (_e73 >= 13u) { + phi_2529_ = (_e466 <= (_e73 - 13u)); + } else { + phi_2529_ = false; + } + let _e536 = phi_2529_; + if _e536 { + let _e539 = atomicLoad((&global_2.member[_e466])); + let _e544 = atomicLoad((&global_2.member[(_e466 + 1u)])); + let _e549 = atomicLoad((&global_2.member[(_e466 + 2u)])); + let _e555 = atomicLoad((&global_2.member[(_e466 + 3u)])); + let _e560 = atomicLoad((&global_2.member[(_e466 + 4u)])); + let _e565 = atomicLoad((&global_2.member[(_e466 + 5u)])); + let _e571 = atomicLoad((&global_2.member[(_e466 + 6u)])); + let _e576 = atomicLoad((&global_2.member[(_e466 + 7u)])); + let _e581 = atomicLoad((&global_2.member[(_e466 + 8u)])); + let _e586 = atomicLoad((&global_2.member[(_e466 + 9u)])); + let _e591 = atomicLoad((&global_2.member[(_e466 + 10u)])); + let _e596 = atomicLoad((&global_2.member[(_e466 + 11u)])); + let _e602 = atomicLoad((&global_2.member[(_e466 + 12u)])); + phi_1601_ = type_22(vec3(bitcast(_e539), bitcast(_e544), bitcast(_e549)), vec3(bitcast(_e555), bitcast(_e560), bitcast(_e565)), bitcast(_e571), bitcast(_e576), vec4(bitcast(_e581), bitcast(_e586), bitcast(_e591), bitcast(_e596)), bitcast(_e602)); + } else { + phi_1601_ = type_22(vec3(0f, 0f, 0f), vec3(0f, -1f, 0f), 0.077143565f, 0.09075713f, vec4(1f, 1f, 1f, 1f), 1f); + } + let _e606 = phi_1601_; + let _e614 = (_e530.member_1.x + _e530.member_1.x); + let _e615 = (_e530.member_1.y + _e530.member_1.y); + let _e616 = (_e530.member_1.z + _e530.member_1.z); + let _e617 = (_e530.member_1.x * _e614); + let _e618 = (_e530.member_1.x * _e615); + let _e619 = (_e530.member_1.x * _e616); + let _e620 = (_e530.member_1.y * _e615); + let _e621 = (_e530.member_1.y * _e616); + let _e622 = (_e530.member_1.z * _e616); + let _e623 = (_e530.member_1.w * _e614); + let _e624 = (_e530.member_1.w * _e615); + let _e625 = (_e530.member_1.w * _e616); + let _e642 = (vec4((1f - (_e620 + _e622)), (_e618 + _e625), (_e619 - _e624), 0f) * _e530.member_2.x); + let _e644 = (vec4((_e618 - _e625), (1f - (_e617 + _e622)), (_e621 + _e623), 0f) * _e530.member_2.y); + let _e646 = (vec4((_e619 + _e624), (_e621 - _e623), (1f - (_e617 + _e620)), 0f) * _e530.member_2.z); + let _e678 = (_e530.member.x + ((_e646.x * _e606.member.z) + ((_e644.x * _e606.member.y) + (_e642.x * _e606.member.x)))); + let _e679 = (_e530.member.y + ((_e646.y * _e606.member.z) + ((_e644.y * _e606.member.y) + (_e642.y * _e606.member.x)))); + let _e680 = (_e530.member.z + ((_e646.z * _e606.member.z) + ((_e644.z * _e606.member.y) + (_e642.z * _e606.member.x)))); + let _e816 = (((((_e266 * _e391) + (_e286 * _e396)) + (_e306 * _e401)) + (_e326 * _e406)) + ((((((_e266 * _e371) + (_e286 * _e376)) + (_e306 * _e381)) + (_e326 * _e386)) * _e680) + ((((((_e266 * _e351) + (_e286 * _e356)) + (_e306 * _e361)) + (_e326 * _e366)) * _e679) + (((((_e266 * _e331) + (_e286 * _e336)) + (_e306 * _e341)) + (_e326 * _e346)) * _e678)))); + let _e817 = ((((((_e251 * _e391) + (_e271 * _e396)) + (_e291 * _e401)) + (_e311 * _e406)) + ((((((_e251 * _e371) + (_e271 * _e376)) + (_e291 * _e381)) + (_e311 * _e386)) * _e680) + ((((((_e251 * _e351) + (_e271 * _e356)) + (_e291 * _e361)) + (_e311 * _e366)) * _e679) + (((((_e251 * _e331) + (_e271 * _e336)) + (_e291 * _e341)) + (_e311 * _e346)) * _e678)))) / _e816); + let _e818 = ((((((_e256 * _e391) + (_e276 * _e396)) + (_e296 * _e401)) + (_e316 * _e406)) + ((((((_e256 * _e371) + (_e276 * _e376)) + (_e296 * _e381)) + (_e316 * _e386)) * _e680) + ((((((_e256 * _e351) + (_e276 * _e356)) + (_e296 * _e361)) + (_e316 * _e366)) * _e679) + (((((_e256 * _e331) + (_e276 * _e336)) + (_e296 * _e341)) + (_e316 * _e346)) * _e678)))) / _e816); + let _e819 = ((((((_e261 * _e391) + (_e281 * _e396)) + (_e301 * _e401)) + (_e321 * _e406)) + ((((((_e261 * _e371) + (_e281 * _e376)) + (_e301 * _e381)) + (_e321 * _e386)) * _e680) + ((((((_e261 * _e351) + (_e281 * _e356)) + (_e301 * _e361)) + (_e321 * _e366)) * _e679) + (((((_e261 * _e331) + (_e281 * _e336)) + (_e301 * _e341)) + (_e321 * _e346)) * _e678)))) / _e816); + let _e820 = (_e208 - _e208); + let _e821 = (_e209 - _e209); + let _e822 = (_e175 - _e177); + let _e828 = sqrt((((_e820 * _e820) + (_e821 * _e821)) + (_e822 * _e822))); + if (_e828 <= 0.00000011920929f) { + let _e849 = (_e817 - _e208); + let _e850 = (_e818 - _e209); + let _e851 = (_e819 - _e175); + phi_2883_ = sqrt((((_e849 * _e849) + (_e850 * _e850)) + (_e851 * _e851))); + } else { + let _e830 = (_e817 - _e208); + let _e831 = (_e818 - _e209); + let _e832 = (_e819 - _e175); + let _e833 = (_e819 - _e177); + let _e836 = ((_e831 * _e833) - (_e831 * _e832)); + let _e839 = ((_e832 * _e830) - (_e833 * _e830)); + let _e840 = (_e830 * _e831); + let _e841 = (_e840 - _e840); + phi_2883_ = (sqrt((((_e836 * _e836) + (_e839 * _e839)) + (_e841 * _e841))) / _e828); + } + let _e859 = phi_2883_; + phi_1692_ = _e606.member_5; + phi_1693_ = _e859; + break; + } + default: { + phi_1692_ = f32(); + phi_1693_ = f32(); + break; + } + } + let _e1215 = phi_1692_; + let _e1217 = phi_1693_; + if (_e1217 > 0.00000011920929f) { + phi_1704_ = (_e1215 / (_e1217 * _e1217)); + } else { + phi_1704_ = 340282350000000000000000000000000000000f; + } + let _e1224 = phi_1704_; + if (sqrt((_e1215 / _e129.member_2)) >= _e1217) { + if (_e244 < _e73) { + } else { + phi_3537_ = true; + break; + } + let _e1229 = atomicAdd((&global_2.member[_e244]), 1u); + let _e1231 = (_e1229 >= _e241.member_1); + if _e1231 { + let _e1237 = (_e225 + 7u); + loop { + let _e1238 = (_e1237 < _e73); + local_1 = _e1238; + if _e1238 { + } else { + phi_3503_ = true; + break; + } + let _e1241 = atomicExchange((&global_2.member[_e1237]), 1u); + continue; + continuing { + phi_3503_ = _e416; + break if !(select(true, false, (_e1241 == 0u))); + } + } + let _e1246 = phi_3503_; + phi_3537_ = _e1246; + if _e1246 { + break; + } + let _e1249 = atomicLoad((&global_2.member[(_e225 + 5u)])); + let _e1252 = atomicLoad((&global_2.member[(_e225 + 6u)])); + let _e1255 = atomicLoad((&global_2.member[(_e225 + 8u)])); + if (_e1255 >= _e1252) { + phi_3362_ = 4294967295u; + } else { + phi_3362_ = (_e1249 + _e1255); + } + let _e1259 = phi_3362_; + let _e1261 = atomicLoad((&global_2.member[_e1259])); + let _e1263 = (_e1224 > bitcast(_e1261)); + if _e1263 { + let _e1264 = atomicLoad((&global_2.member[(_e225 + 8u)])); + if (_e1264 >= _e241.member_1) { + phi_3387_ = 4294967295u; + } else { + phi_3387_ = (_e241.member + _e1264); + } + let _e1268 = phi_3387_; + atomicStore((&global_2.member[_e1268]), _e456); + phi_1778_ = type_10(0u, _e1252); + phi_1781_ = _e1264; + phi_1783_ = _e1224; + loop { + let _e1272 = phi_1778_; + let _e1274 = phi_1781_; + let _e1276 = phi_1783_; + local = _e1274; + if (_e1272.member < _e1272.member_1) { + phi_1779_ = type_10((_e1272.member + 1u), _e1272.member_1); + phi_1806_ = type_10(1u, _e1272.member); + } else { + phi_1779_ = _e1272; + phi_1806_ = type_10(0u, type_10().member_1); + } + let _e1289 = phi_1779_; + let _e1291 = phi_1806_; + switch bitcast(_e1291.member) { + case 0: { + phi_1782_ = u32(); + phi_1784_ = f32(); + phi_1825_ = false; + break; + } + case 1: { + if (_e1291.member_1 >= _e1252) { + phi_3413_ = 4294967295u; + } else { + phi_3413_ = (_e1249 + _e1291.member_1); + } + let _e1298 = phi_3413_; + let _e1300 = atomicLoad((&global_2.member[_e1298])); + let _e1301 = bitcast(_e1300); + let _e1302 = (_e1301 < _e1276); + phi_1782_ = select(_e1274, _e1291.member_1, _e1302); + phi_1784_ = select(_e1276, _e1301, _e1302); + phi_1825_ = true; + break; + } + default: { + phi_1782_ = u32(); + phi_1784_ = f32(); + phi_1825_ = bool(); + break; + } + } + let _e1306 = phi_1782_; + let _e1308 = phi_1784_; + let _e1310 = phi_1825_; + continue; + continuing { + phi_1778_ = _e1289; + phi_1781_ = _e1306; + phi_1783_ = _e1308; + break if !(_e1310); + } + } + let _e1313 = local; + atomicStore((&global_2.member[(_e225 + 8u)]), _e1313); + let _e1315 = local_1; + if _e1315 { + } else { + phi_3537_ = true; + break; + } + loop { + let _e1317 = atomicExchange((&global_2.member[_e1237]), 0u); + if select(true, false, (_e1317 == 1u)) { + continue; + } else { + break; + } + } + } + phi_3540_ = _e1246; + phi_1851_ = _e1263; + } else { + if _e1231 { + phi_3316_ = 4294967295u; + } else { + phi_3316_ = (_e241.member + _e1229); + } + let _e1234 = phi_3316_; + atomicStore((&global_2.member[_e1234]), _e456); + phi_3540_ = _e416; + phi_1851_ = true; + } + let _e1321 = phi_3540_; + let _e1323 = phi_1851_; + phi_3539_ = _e1321; + phi_1855_ = _e1323; + } else { + phi_3539_ = _e416; + phi_1855_ = true; + } + let _e1325 = phi_3539_; + let _e1327 = phi_1855_; + phi_3538_ = _e1325; + phi_1859_ = _e1327; + break; + } + default: { + phi_3538_ = _e416; + phi_1859_ = bool(); + break; + } + } + let _e1329 = phi_3538_; + let _e1331 = phi_1859_; + continue; + continuing { + phi_3515_ = _e1329; + phi_1236_ = type_20(_e418.member, _e418.member_1, (_e418.member_2 + 1u), _e418.member_3); + phi_3537_ = _e1329; + break if !(_e1331); + } + } + let _e1334 = phi_3537_; + if _e1334 { + break; + } + } + } + break; + } + } + return; +} + +@compute @workgroup_size(16, 16, 1) +fn lightlight_tiling_bin_lights(@builtin(global_invocation_id) param: vec3) { + global = param; + function(); +} diff --git a/bin_lights.wgsl.txt b/bin_lights.wgsl.txt new file mode 100644 index 00000000..ebd61548 --- /dev/null +++ b/bin_lights.wgsl.txt @@ -0,0 +1,703 @@ +struct type_2 { + member: array, +} + +struct type_10 { + member: u32, + member_1: u32, +} + +struct type_15 { + member: vec3, + member_1: vec4, + member_2: vec3, +} + +struct type_17 { + member: vec2, + member_1: type_10, + member_2: f32, +} + +struct type_18 { + member: type_10, + member_1: u32, + member_2: u32, + member_3: u32, + member_4: u32, +} + +struct type_20 { + member: vec3, + member_1: type_10, + member_2: u32, + member_3: u32, +} + +struct type_21 { + member: vec3, + member_1: vec4, + member_2: f32, +} + +struct type_22 { + member: vec3, + member_1: vec3, + member_2: f32, + member_3: f32, + member_4: vec4, + member_5: f32, +} + +struct type_25 { + member: array>, +} + +var global: vec3; +@group(0) @binding(0) +var global_1: type_2; +@group(0) @binding(1) +var global_2: type_25; +var global_3: vec2 = vec2(16u, 16u); + +fn function() { + var phi_747_: type_18; + var phi_2201_: bool; + var phi_777_: type_17; + var phi_2244_: bool; + var phi_816_: u32; + var phi_891_: u32; + var phi_2411_: bool; + var phi_910_: type_10; + var phi_3515_: bool; + var phi_1236_: type_20; + var phi_1279_: u32; + var phi_1282_: type_10; + var phi_1300_: u32; + var phi_2497_: bool; + var phi_1361_: type_15; + var phi_2529_: bool; + var phi_1601_: type_22; + var phi_2883_: f32; + var phi_2905_: bool; + var phi_1449_: type_21; + var phi_3258_: f32; + var phi_3280_: bool; + var phi_1407_: type_21; + var phi_1692_: f32; + var phi_1693_: f32; + var phi_1704_: f32; + var phi_3316_: u32; + var phi_3503_: bool; + var phi_3362_: u32; + var phi_3387_: u32; + var phi_1778_: type_10; + var phi_1781_: u32; + var phi_1783_: f32; + var phi_1779_: type_10; + var phi_1806_: type_10; + var phi_3413_: u32; + var phi_1782_: u32; + var phi_1784_: f32; + var phi_1825_: bool; + var local: u32; + var local_1: bool; + var phi_3540_: bool; + var phi_1851_: bool; + var phi_3539_: bool; + var phi_1855_: bool; + var phi_3538_: bool; + var phi_1859_: bool; + var phi_3537_: bool; + + switch bitcast(0u) { + default: { + let _e73 = arrayLength((&global_2.member)); + let _e74 = global; + if select(false, true, (_e73 >= 6u)) { + let _e79 = atomicLoad((&global_2.member[0u])); + let _e82 = atomicLoad((&global_2.member[1u])); + let _e85 = atomicLoad((&global_2.member[2u])); + let _e88 = atomicLoad((&global_2.member[3u])); + let _e91 = atomicLoad((&global_2.member[4u])); + let _e94 = atomicLoad((&global_2.member[5u])); + phi_747_ = type_18(type_10(_e79, _e82), _e85, _e88, _e91, _e94); + } else { + phi_747_ = type_18(type_10(4294967295u, 0u), 4294967295u, 4294967295u, 0u, 4294967295u); + } + let _e98 = phi_747_; + if (_e73 >= 5u) { + phi_2201_ = (_e98.member_4 <= (_e73 - 5u)); + } else { + phi_2201_ = false; + } + let _e104 = phi_2201_; + if _e104 { + let _e107 = atomicLoad((&global_2.member[_e98.member_4])); + let _e111 = atomicLoad((&global_2.member[(_e98.member_4 + 1u)])); + let _e116 = atomicLoad((&global_2.member[(_e98.member_4 + 2u)])); + let _e120 = atomicLoad((&global_2.member[(_e98.member_4 + 3u)])); + let _e124 = atomicLoad((&global_2.member[(_e98.member_4 + 4u)])); + phi_777_ = type_17(vec2(_e107, _e111), type_10(_e116, _e120), bitcast(_e124)); + } else { + phi_777_ = type_17(vec2(0u, 0u), type_10(4294967295u, 0u), 0.25f); + } + let _e129 = phi_777_; + if (_e74.x < _e129.member.x) { + phi_2244_ = (_e74.y < _e129.member.y); + } else { + phi_2244_ = false; + } + let _e142 = phi_2244_; + if _e142 { + let _e144 = (_e74.x / 16u); + let _e145 = (_e74.y / 16u); + let _e146 = f32(_e129.member.x); + let _e150 = global_3[0u]; + let _e154 = ceil((_e146 / f32(_e150))); + let _e161 = ((_e145 * select(select(u32(_e154), 0u, (_e154 < 0f)), 4294967295u, (_e154 > 4294967000f))) + _e144); + if (_e161 >= _e129.member_1.member_1) { + phi_816_ = 4294967295u; + } else { + phi_816_ = (_e129.member_1.member + (9u * _e161)); + } + let _e166 = phi_816_; + let _e169 = atomicLoad((&global_2.member[_e166])); + let _e173 = atomicLoad((&global_2.member[(_e166 + 1u)])); + let _e175 = (f32(_e169) * 0.00000000023283064f); + let _e177 = (f32(_e173) * 0.00000000023283064f); + if (_e175 == _e177) { + } else { + let _e183 = global_3[0u]; + let _e185 = global_3[1u]; + let _e189 = ceil((_e146 / f32(_e183))); + let _e190 = ceil((f32(_e129.member.y) / f32(_e185))); + let _e208 = ((((f32(_e144) + 0.5f) / f32(select(select(u32(_e189), 0u, (_e189 < 0f)), 4294967295u, (_e189 > 4294967000f)))) * 2f) - 1f); + let _e209 = (((1f - ((f32(_e145) + 0.5f) / f32(select(select(u32(_e190), 0u, (_e190 < 0f)), 4294967295u, (_e190 > 4294967000f))))) * 2f) - 1f); + let _e210 = global_3[0u]; + let _e213 = ceil((_e146 / f32(_e210))); + let _e220 = ((_e145 * select(select(u32(_e213), 0u, (_e213 < 0f)), 4294967295u, (_e213 > 4294967000f))) + _e144); + if (_e220 >= _e129.member_1.member_1) { + phi_891_ = 4294967295u; + } else { + phi_891_ = (_e129.member_1.member + (9u * _e220)); + } + let _e225 = phi_891_; + let _e226 = (_e225 + 3u); + if (_e73 >= 2u) { + phi_2411_ = (_e226 <= (_e73 - 2u)); + } else { + phi_2411_ = false; + } + let _e231 = phi_2411_; + if _e231 { + let _e234 = atomicLoad((&global_2.member[_e226])); + let _e238 = atomicLoad((&global_2.member[(_e225 + 4u)])); + phi_910_ = type_10(_e234, _e238); + } else { + phi_910_ = type_10(4294967295u, 0u); + } + let _e241 = phi_910_; + let _e244 = (_e225 + 2u); + let _e247 = global_1.member[0u]; + let _e250 = global_1.member[_e247]; + let _e251 = bitcast(_e250); + let _e255 = global_1.member[(_e247 + 1u)]; + let _e256 = bitcast(_e255); + let _e260 = global_1.member[(_e247 + 2u)]; + let _e261 = bitcast(_e260); + let _e265 = global_1.member[(_e247 + 3u)]; + let _e266 = bitcast(_e265); + let _e270 = global_1.member[(_e247 + 4u)]; + let _e271 = bitcast(_e270); + let _e275 = global_1.member[(_e247 + 5u)]; + let _e276 = bitcast(_e275); + let _e280 = global_1.member[(_e247 + 6u)]; + let _e281 = bitcast(_e280); + let _e285 = global_1.member[(_e247 + 7u)]; + let _e286 = bitcast(_e285); + let _e290 = global_1.member[(_e247 + 8u)]; + let _e291 = bitcast(_e290); + let _e295 = global_1.member[(_e247 + 9u)]; + let _e296 = bitcast(_e295); + let _e300 = global_1.member[(_e247 + 10u)]; + let _e301 = bitcast(_e300); + let _e305 = global_1.member[(_e247 + 11u)]; + let _e306 = bitcast(_e305); + let _e310 = global_1.member[(_e247 + 12u)]; + let _e311 = bitcast(_e310); + let _e315 = global_1.member[(_e247 + 13u)]; + let _e316 = bitcast(_e315); + let _e320 = global_1.member[(_e247 + 14u)]; + let _e321 = bitcast(_e320); + let _e325 = global_1.member[(_e247 + 15u)]; + let _e326 = bitcast(_e325); + let _e330 = global_1.member[(_e247 + 16u)]; + let _e331 = bitcast(_e330); + let _e335 = global_1.member[(_e247 + 17u)]; + let _e336 = bitcast(_e335); + let _e340 = global_1.member[(_e247 + 18u)]; + let _e341 = bitcast(_e340); + let _e345 = global_1.member[(_e247 + 19u)]; + let _e346 = bitcast(_e345); + let _e350 = global_1.member[(_e247 + 20u)]; + let _e351 = bitcast(_e350); + let _e355 = global_1.member[(_e247 + 21u)]; + let _e356 = bitcast(_e355); + let _e360 = global_1.member[(_e247 + 22u)]; + let _e361 = bitcast(_e360); + let _e365 = global_1.member[(_e247 + 23u)]; + let _e366 = bitcast(_e365); + let _e370 = global_1.member[(_e247 + 24u)]; + let _e371 = bitcast(_e370); + let _e375 = global_1.member[(_e247 + 25u)]; + let _e376 = bitcast(_e375); + let _e380 = global_1.member[(_e247 + 26u)]; + let _e381 = bitcast(_e380); + let _e385 = global_1.member[(_e247 + 27u)]; + let _e386 = bitcast(_e385); + let _e390 = global_1.member[(_e247 + 28u)]; + let _e391 = bitcast(_e390); + let _e395 = global_1.member[(_e247 + 29u)]; + let _e396 = bitcast(_e395); + let _e400 = global_1.member[(_e247 + 30u)]; + let _e401 = bitcast(_e400); + let _e405 = global_1.member[(_e247 + 31u)]; + let _e406 = bitcast(_e405); + let _e409 = atomicLoad((&global_2.member[0u])); + let _e412 = atomicLoad((&global_2.member[1u])); + phi_3515_ = false; + phi_1236_ = type_20(_e74, type_10(_e409, _e412), 0u, 256u); + loop { + let _e416 = phi_3515_; + let _e418 = phi_1236_; + let _e429 = ((_e418.member_2 * _e418.member_3) + (((_e418.member.y % 16u) * 16u) + (_e418.member.x % 16u))); + if (_e429 < _e418.member_1.member_1) { + if (_e429 >= _e418.member_1.member_1) { + phi_1279_ = 4294967295u; + } else { + phi_1279_ = (_e418.member_1.member + _e429); + } + let _e447 = phi_1279_; + phi_1282_ = type_10(1u, _e447); + } else { + phi_1282_ = type_10(0u, type_10().member_1); + } + let _e450 = phi_1282_; + switch bitcast(_e450.member) { + case 0: { + phi_3538_ = _e416; + phi_1859_ = false; + break; + } + case 1: { + let _e456 = atomicLoad((&global_2.member[_e450.member_1])); + let _e459 = atomicLoad((&global_2.member[_e456])); + switch bitcast(_e459) { + case 0: { + phi_1300_ = 0u; + break; + } + case 1: { + phi_1300_ = 1u; + break; + } + case 2: { + phi_1300_ = 2u; + break; + } + default: { + phi_1300_ = 0u; + break; + } + } + let _e462 = phi_1300_; + let _e466 = atomicLoad((&global_2.member[(_e456 + 1u)])); + let _e470 = atomicLoad((&global_2.member[(_e456 + 2u)])); + if (_e73 >= 10u) { + phi_2497_ = (_e470 <= (_e73 - 10u)); + } else { + phi_2497_ = false; + } + let _e475 = phi_2497_; + if _e475 { + let _e478 = atomicLoad((&global_2.member[_e470])); + let _e483 = atomicLoad((&global_2.member[(_e470 + 1u)])); + let _e488 = atomicLoad((&global_2.member[(_e470 + 2u)])); + let _e494 = atomicLoad((&global_2.member[(_e470 + 3u)])); + let _e499 = atomicLoad((&global_2.member[(_e470 + 4u)])); + let _e504 = atomicLoad((&global_2.member[(_e470 + 5u)])); + let _e509 = atomicLoad((&global_2.member[(_e470 + 6u)])); + let _e515 = atomicLoad((&global_2.member[(_e470 + 7u)])); + let _e520 = atomicLoad((&global_2.member[(_e470 + 8u)])); + let _e525 = atomicLoad((&global_2.member[(_e470 + 9u)])); + phi_1361_ = type_15(vec3(bitcast(_e478), bitcast(_e483), bitcast(_e488)), vec4(bitcast(_e494), bitcast(_e499), bitcast(_e504), bitcast(_e509)), vec3(bitcast(_e515), bitcast(_e520), bitcast(_e525))); + } else { + phi_1361_ = type_15(vec3(0f, 0f, 0f), vec4(0f, 0f, 0f, 1f), vec3(1f, 1f, 1f)); + } + let _e530 = phi_1361_; + switch bitcast(_e462) { + case 0: { + if (_e73 >= 8u) { + phi_3280_ = (_e466 <= (_e73 - 8u)); + } else { + phi_3280_ = false; + } + let _e1168 = phi_3280_; + if _e1168 { + let _e1171 = atomicLoad((&global_2.member[_e466])); + let _e1176 = atomicLoad((&global_2.member[(_e466 + 1u)])); + let _e1181 = atomicLoad((&global_2.member[(_e466 + 2u)])); + let _e1187 = atomicLoad((&global_2.member[(_e466 + 3u)])); + let _e1192 = atomicLoad((&global_2.member[(_e466 + 4u)])); + let _e1197 = atomicLoad((&global_2.member[(_e466 + 5u)])); + let _e1202 = atomicLoad((&global_2.member[(_e466 + 6u)])); + let _e1208 = atomicLoad((&global_2.member[(_e466 + 7u)])); + phi_1407_ = type_21(vec3(bitcast(_e1171), bitcast(_e1176), bitcast(_e1181)), vec4(bitcast(_e1187), bitcast(_e1192), bitcast(_e1197), bitcast(_e1202)), bitcast(_e1208)); + } else { + phi_1407_ = type_21(vec3(0f, -1f, 0f), vec4(1f, 1f, 1f, 1f), 1f); + } + let _e1212 = phi_1407_; + phi_1692_ = _e1212.member_2; + phi_1693_ = 0f; + break; + } + case 1: { + if (_e73 >= 8u) { + phi_2905_ = (_e466 <= (_e73 - 8u)); + } else { + phi_2905_ = false; + } + let _e865 = phi_2905_; + if _e865 { + let _e868 = atomicLoad((&global_2.member[_e466])); + let _e873 = atomicLoad((&global_2.member[(_e466 + 1u)])); + let _e878 = atomicLoad((&global_2.member[(_e466 + 2u)])); + let _e884 = atomicLoad((&global_2.member[(_e466 + 3u)])); + let _e889 = atomicLoad((&global_2.member[(_e466 + 4u)])); + let _e894 = atomicLoad((&global_2.member[(_e466 + 5u)])); + let _e899 = atomicLoad((&global_2.member[(_e466 + 6u)])); + let _e905 = atomicLoad((&global_2.member[(_e466 + 7u)])); + phi_1449_ = type_21(vec3(bitcast(_e868), bitcast(_e873), bitcast(_e878)), vec4(bitcast(_e884), bitcast(_e889), bitcast(_e894), bitcast(_e899)), bitcast(_e905)); + } else { + phi_1449_ = type_21(vec3(0f, 0f, 0f), vec4(1f, 1f, 1f, 1f), 1f); + } + let _e909 = phi_1449_; + let _e917 = (_e530.member_1.x + _e530.member_1.x); + let _e918 = (_e530.member_1.y + _e530.member_1.y); + let _e919 = (_e530.member_1.z + _e530.member_1.z); + let _e920 = (_e530.member_1.x * _e917); + let _e921 = (_e530.member_1.x * _e918); + let _e922 = (_e530.member_1.x * _e919); + let _e923 = (_e530.member_1.y * _e918); + let _e924 = (_e530.member_1.y * _e919); + let _e925 = (_e530.member_1.z * _e919); + let _e926 = (_e530.member_1.w * _e917); + let _e927 = (_e530.member_1.w * _e918); + let _e928 = (_e530.member_1.w * _e919); + let _e945 = (vec4((1f - (_e923 + _e925)), (_e921 + _e928), (_e922 - _e927), 0f) * _e530.member_2.x); + let _e947 = (vec4((_e921 - _e928), (1f - (_e920 + _e925)), (_e924 + _e926), 0f) * _e530.member_2.y); + let _e949 = (vec4((_e922 + _e927), (_e924 - _e926), (1f - (_e920 + _e923)), 0f) * _e530.member_2.z); + let _e981 = (_e530.member.x + ((_e949.x * _e909.member.z) + ((_e947.x * _e909.member.y) + (_e945.x * _e909.member.x)))); + let _e982 = (_e530.member.y + ((_e949.y * _e909.member.z) + ((_e947.y * _e909.member.y) + (_e945.y * _e909.member.x)))); + let _e983 = (_e530.member.z + ((_e949.z * _e909.member.z) + ((_e947.z * _e909.member.y) + (_e945.z * _e909.member.x)))); + let _e1119 = (((((_e266 * _e391) + (_e286 * _e396)) + (_e306 * _e401)) + (_e326 * _e406)) + ((((((_e266 * _e371) + (_e286 * _e376)) + (_e306 * _e381)) + (_e326 * _e386)) * _e983) + ((((((_e266 * _e351) + (_e286 * _e356)) + (_e306 * _e361)) + (_e326 * _e366)) * _e982) + (((((_e266 * _e331) + (_e286 * _e336)) + (_e306 * _e341)) + (_e326 * _e346)) * _e981)))); + let _e1120 = ((((((_e251 * _e391) + (_e271 * _e396)) + (_e291 * _e401)) + (_e311 * _e406)) + ((((((_e251 * _e371) + (_e271 * _e376)) + (_e291 * _e381)) + (_e311 * _e386)) * _e983) + ((((((_e251 * _e351) + (_e271 * _e356)) + (_e291 * _e361)) + (_e311 * _e366)) * _e982) + (((((_e251 * _e331) + (_e271 * _e336)) + (_e291 * _e341)) + (_e311 * _e346)) * _e981)))) / _e1119); + let _e1121 = ((((((_e256 * _e391) + (_e276 * _e396)) + (_e296 * _e401)) + (_e316 * _e406)) + ((((((_e256 * _e371) + (_e276 * _e376)) + (_e296 * _e381)) + (_e316 * _e386)) * _e983) + ((((((_e256 * _e351) + (_e276 * _e356)) + (_e296 * _e361)) + (_e316 * _e366)) * _e982) + (((((_e256 * _e331) + (_e276 * _e336)) + (_e296 * _e341)) + (_e316 * _e346)) * _e981)))) / _e1119); + let _e1122 = ((((((_e261 * _e391) + (_e281 * _e396)) + (_e301 * _e401)) + (_e321 * _e406)) + ((((((_e261 * _e371) + (_e281 * _e376)) + (_e301 * _e381)) + (_e321 * _e386)) * _e983) + ((((((_e261 * _e351) + (_e281 * _e356)) + (_e301 * _e361)) + (_e321 * _e366)) * _e982) + (((((_e261 * _e331) + (_e281 * _e336)) + (_e301 * _e341)) + (_e321 * _e346)) * _e981)))) / _e1119); + let _e1123 = (_e208 - _e208); + let _e1124 = (_e209 - _e209); + let _e1125 = (_e175 - _e177); + let _e1131 = sqrt((((_e1123 * _e1123) + (_e1124 * _e1124)) + (_e1125 * _e1125))); + if (_e1131 <= 0.00000011920929f) { + let _e1152 = (_e1120 - _e208); + let _e1153 = (_e1121 - _e209); + let _e1154 = (_e1122 - _e175); + phi_3258_ = sqrt((((_e1152 * _e1152) + (_e1153 * _e1153)) + (_e1154 * _e1154))); + } else { + let _e1133 = (_e1120 - _e208); + let _e1134 = (_e1121 - _e209); + let _e1135 = (_e1122 - _e175); + let _e1136 = (_e1122 - _e177); + let _e1139 = ((_e1134 * _e1136) - (_e1134 * _e1135)); + let _e1142 = ((_e1135 * _e1133) - (_e1136 * _e1133)); + let _e1143 = (_e1133 * _e1134); + let _e1144 = (_e1143 - _e1143); + phi_3258_ = (sqrt((((_e1139 * _e1139) + (_e1142 * _e1142)) + (_e1144 * _e1144))) / _e1131); + } + let _e1162 = phi_3258_; + phi_1692_ = _e909.member_2; + phi_1693_ = _e1162; + break; + } + case 2: { + if (_e73 >= 13u) { + phi_2529_ = (_e466 <= (_e73 - 13u)); + } else { + phi_2529_ = false; + } + let _e536 = phi_2529_; + if _e536 { + let _e539 = atomicLoad((&global_2.member[_e466])); + let _e544 = atomicLoad((&global_2.member[(_e466 + 1u)])); + let _e549 = atomicLoad((&global_2.member[(_e466 + 2u)])); + let _e555 = atomicLoad((&global_2.member[(_e466 + 3u)])); + let _e560 = atomicLoad((&global_2.member[(_e466 + 4u)])); + let _e565 = atomicLoad((&global_2.member[(_e466 + 5u)])); + let _e571 = atomicLoad((&global_2.member[(_e466 + 6u)])); + let _e576 = atomicLoad((&global_2.member[(_e466 + 7u)])); + let _e581 = atomicLoad((&global_2.member[(_e466 + 8u)])); + let _e586 = atomicLoad((&global_2.member[(_e466 + 9u)])); + let _e591 = atomicLoad((&global_2.member[(_e466 + 10u)])); + let _e596 = atomicLoad((&global_2.member[(_e466 + 11u)])); + let _e602 = atomicLoad((&global_2.member[(_e466 + 12u)])); + phi_1601_ = type_22(vec3(bitcast(_e539), bitcast(_e544), bitcast(_e549)), vec3(bitcast(_e555), bitcast(_e560), bitcast(_e565)), bitcast(_e571), bitcast(_e576), vec4(bitcast(_e581), bitcast(_e586), bitcast(_e591), bitcast(_e596)), bitcast(_e602)); + } else { + phi_1601_ = type_22(vec3(0f, 0f, 0f), vec3(0f, -1f, 0f), 0.077143565f, 0.09075713f, vec4(1f, 1f, 1f, 1f), 1f); + } + let _e606 = phi_1601_; + let _e614 = (_e530.member_1.x + _e530.member_1.x); + let _e615 = (_e530.member_1.y + _e530.member_1.y); + let _e616 = (_e530.member_1.z + _e530.member_1.z); + let _e617 = (_e530.member_1.x * _e614); + let _e618 = (_e530.member_1.x * _e615); + let _e619 = (_e530.member_1.x * _e616); + let _e620 = (_e530.member_1.y * _e615); + let _e621 = (_e530.member_1.y * _e616); + let _e622 = (_e530.member_1.z * _e616); + let _e623 = (_e530.member_1.w * _e614); + let _e624 = (_e530.member_1.w * _e615); + let _e625 = (_e530.member_1.w * _e616); + let _e642 = (vec4((1f - (_e620 + _e622)), (_e618 + _e625), (_e619 - _e624), 0f) * _e530.member_2.x); + let _e644 = (vec4((_e618 - _e625), (1f - (_e617 + _e622)), (_e621 + _e623), 0f) * _e530.member_2.y); + let _e646 = (vec4((_e619 + _e624), (_e621 - _e623), (1f - (_e617 + _e620)), 0f) * _e530.member_2.z); + let _e678 = (_e530.member.x + ((_e646.x * _e606.member.z) + ((_e644.x * _e606.member.y) + (_e642.x * _e606.member.x)))); + let _e679 = (_e530.member.y + ((_e646.y * _e606.member.z) + ((_e644.y * _e606.member.y) + (_e642.y * _e606.member.x)))); + let _e680 = (_e530.member.z + ((_e646.z * _e606.member.z) + ((_e644.z * _e606.member.y) + (_e642.z * _e606.member.x)))); + let _e816 = (((((_e266 * _e391) + (_e286 * _e396)) + (_e306 * _e401)) + (_e326 * _e406)) + ((((((_e266 * _e371) + (_e286 * _e376)) + (_e306 * _e381)) + (_e326 * _e386)) * _e680) + ((((((_e266 * _e351) + (_e286 * _e356)) + (_e306 * _e361)) + (_e326 * _e366)) * _e679) + (((((_e266 * _e331) + (_e286 * _e336)) + (_e306 * _e341)) + (_e326 * _e346)) * _e678)))); + let _e817 = ((((((_e251 * _e391) + (_e271 * _e396)) + (_e291 * _e401)) + (_e311 * _e406)) + ((((((_e251 * _e371) + (_e271 * _e376)) + (_e291 * _e381)) + (_e311 * _e386)) * _e680) + ((((((_e251 * _e351) + (_e271 * _e356)) + (_e291 * _e361)) + (_e311 * _e366)) * _e679) + (((((_e251 * _e331) + (_e271 * _e336)) + (_e291 * _e341)) + (_e311 * _e346)) * _e678)))) / _e816); + let _e818 = ((((((_e256 * _e391) + (_e276 * _e396)) + (_e296 * _e401)) + (_e316 * _e406)) + ((((((_e256 * _e371) + (_e276 * _e376)) + (_e296 * _e381)) + (_e316 * _e386)) * _e680) + ((((((_e256 * _e351) + (_e276 * _e356)) + (_e296 * _e361)) + (_e316 * _e366)) * _e679) + (((((_e256 * _e331) + (_e276 * _e336)) + (_e296 * _e341)) + (_e316 * _e346)) * _e678)))) / _e816); + let _e819 = ((((((_e261 * _e391) + (_e281 * _e396)) + (_e301 * _e401)) + (_e321 * _e406)) + ((((((_e261 * _e371) + (_e281 * _e376)) + (_e301 * _e381)) + (_e321 * _e386)) * _e680) + ((((((_e261 * _e351) + (_e281 * _e356)) + (_e301 * _e361)) + (_e321 * _e366)) * _e679) + (((((_e261 * _e331) + (_e281 * _e336)) + (_e301 * _e341)) + (_e321 * _e346)) * _e678)))) / _e816); + let _e820 = (_e208 - _e208); + let _e821 = (_e209 - _e209); + let _e822 = (_e175 - _e177); + let _e828 = sqrt((((_e820 * _e820) + (_e821 * _e821)) + (_e822 * _e822))); + if (_e828 <= 0.00000011920929f) { + let _e849 = (_e817 - _e208); + let _e850 = (_e818 - _e209); + let _e851 = (_e819 - _e175); + phi_2883_ = sqrt((((_e849 * _e849) + (_e850 * _e850)) + (_e851 * _e851))); + } else { + let _e830 = (_e817 - _e208); + let _e831 = (_e818 - _e209); + let _e832 = (_e819 - _e175); + let _e833 = (_e819 - _e177); + let _e836 = ((_e831 * _e833) - (_e831 * _e832)); + let _e839 = ((_e832 * _e830) - (_e833 * _e830)); + let _e840 = (_e830 * _e831); + let _e841 = (_e840 - _e840); + phi_2883_ = (sqrt((((_e836 * _e836) + (_e839 * _e839)) + (_e841 * _e841))) / _e828); + } + let _e859 = phi_2883_; + phi_1692_ = _e606.member_5; + phi_1693_ = _e859; + break; + } + default: { + phi_1692_ = f32(); + phi_1693_ = f32(); + break; + } + } + let _e1215 = phi_1692_; + let _e1217 = phi_1693_; + if (_e1217 > 0.00000011920929f) { + phi_1704_ = (_e1215 / (_e1217 * _e1217)); + } else { + phi_1704_ = 340282350000000000000000000000000000000f; + } + let _e1224 = phi_1704_; + if (sqrt((_e1215 / _e129.member_2)) >= _e1217) { + if (_e244 < _e73) { + } else { + phi_3537_ = true; + break; + } + let _e1229 = atomicAdd((&global_2.member[_e244]), 1u); + let _e1231 = (_e1229 >= _e241.member_1); + if _e1231 { + let _e1237 = (_e225 + 7u); + loop { + let _e1238 = (_e1237 < _e73); + local_1 = _e1238; + if _e1238 { + } else { + phi_3503_ = true; + break; + } + let _e1241 = atomicExchange((&global_2.member[_e1237]), 1u); + continue; + continuing { + phi_3503_ = _e416; + break if !(select(true, false, (_e1241 == 0u))); + } + } + let _e1246 = phi_3503_; + phi_3537_ = _e1246; + if _e1246 { + break; + } + let _e1249 = atomicLoad((&global_2.member[(_e225 + 5u)])); + let _e1252 = atomicLoad((&global_2.member[(_e225 + 6u)])); + let _e1255 = atomicLoad((&global_2.member[(_e225 + 8u)])); + if (_e1255 >= _e1252) { + phi_3362_ = 4294967295u; + } else { + phi_3362_ = (_e1249 + _e1255); + } + let _e1259 = phi_3362_; + let _e1261 = atomicLoad((&global_2.member[_e1259])); + let _e1263 = (_e1224 > bitcast(_e1261)); + if _e1263 { + let _e1264 = atomicLoad((&global_2.member[(_e225 + 8u)])); + if (_e1264 >= _e241.member_1) { + phi_3387_ = 4294967295u; + } else { + phi_3387_ = (_e241.member + _e1264); + } + let _e1268 = phi_3387_; + atomicStore((&global_2.member[_e1268]), _e456); + phi_1778_ = type_10(0u, _e1252); + phi_1781_ = _e1264; + phi_1783_ = _e1224; + loop { + let _e1272 = phi_1778_; + let _e1274 = phi_1781_; + let _e1276 = phi_1783_; + local = _e1274; + if (_e1272.member < _e1272.member_1) { + phi_1779_ = type_10((_e1272.member + 1u), _e1272.member_1); + phi_1806_ = type_10(1u, _e1272.member); + } else { + phi_1779_ = _e1272; + phi_1806_ = type_10(0u, type_10().member_1); + } + let _e1289 = phi_1779_; + let _e1291 = phi_1806_; + switch bitcast(_e1291.member) { + case 0: { + phi_1782_ = u32(); + phi_1784_ = f32(); + phi_1825_ = false; + break; + } + case 1: { + if (_e1291.member_1 >= _e1252) { + phi_3413_ = 4294967295u; + } else { + phi_3413_ = (_e1249 + _e1291.member_1); + } + let _e1298 = phi_3413_; + let _e1300 = atomicLoad((&global_2.member[_e1298])); + let _e1301 = bitcast(_e1300); + let _e1302 = (_e1301 < _e1276); + phi_1782_ = select(_e1274, _e1291.member_1, _e1302); + phi_1784_ = select(_e1276, _e1301, _e1302); + phi_1825_ = true; + break; + } + default: { + phi_1782_ = u32(); + phi_1784_ = f32(); + phi_1825_ = bool(); + break; + } + } + let _e1306 = phi_1782_; + let _e1308 = phi_1784_; + let _e1310 = phi_1825_; + continue; + continuing { + phi_1778_ = _e1289; + phi_1781_ = _e1306; + phi_1783_ = _e1308; + break if !(_e1310); + } + } + let _e1313 = local; + atomicStore((&global_2.member[(_e225 + 8u)]), _e1313); + let _e1315 = local_1; + if _e1315 { + } else { + phi_3537_ = true; + break; + } + loop { + let _e1317 = atomicExchange((&global_2.member[_e1237]), 0u); + if select(true, false, (_e1317 == 1u)) { + continue; + } else { + break; + } + } + } + phi_3540_ = _e1246; + phi_1851_ = _e1263; + } else { + if _e1231 { + phi_3316_ = 4294967295u; + } else { + phi_3316_ = (_e241.member + _e1229); + } + let _e1234 = phi_3316_; + atomicStore((&global_2.member[_e1234]), _e456); + phi_3540_ = _e416; + phi_1851_ = true; + } + let _e1321 = phi_3540_; + let _e1323 = phi_1851_; + phi_3539_ = _e1321; + phi_1855_ = _e1323; + } else { + phi_3539_ = _e416; + phi_1855_ = true; + } + let _e1325 = phi_3539_; + let _e1327 = phi_1855_; + phi_3538_ = _e1325; + phi_1859_ = _e1327; + break; + } + default: { + phi_3538_ = _e416; + phi_1859_ = bool(); + break; + } + } + let _e1329 = phi_3538_; + let _e1331 = phi_1859_; + continue; + continuing { + phi_3515_ = _e1329; + phi_1236_ = type_20(_e418.member, _e418.member_1, (_e418.member_2 + 1u), _e418.member_3); + phi_3537_ = _e1329; + break if !(_e1331); + } + } + let _e1334 = phi_3537_; + if _e1334 { + break; + } + } + } + break; + } + } + return; +} + +@compute @workgroup_size(16, 16, 1) +fn lightlight_tiling_bin_lights(@builtin(global_invocation_id) param: vec3) { + global = param; + function(); +} diff --git a/blender/normal_mapping_brick_sphere.blend b/blender/normal_mapping_brick_sphere.blend new file mode 100644 index 00000000..03a4c4f9 Binary files /dev/null and b/blender/normal_mapping_brick_sphere.blend differ diff --git a/blender/pedestal.blend b/blender/pedestal.blend new file mode 100644 index 00000000..1709e437 Binary files /dev/null and b/blender/pedestal.blend differ diff --git a/crates/example-culling/src/main.rs b/crates/example-culling/src/main.rs index f2193152..42eaef78 100644 --- a/crates/example-culling/src/main.rs +++ b/crates/example-culling/src/main.rs @@ -33,7 +33,7 @@ struct CullingExample { app_camera: AppCamera, controller: example::camera::TurntableCameraController, stage: Stage, - dlights: [AnalyticalLightBundle; 2], + dlights: [AnalyticalLight; 2], material_aabb_overlapping: Hybrid, material_aabb_outside: Hybrid, material_frustum: Hybrid, @@ -183,22 +183,16 @@ impl TestAppHandler for CullingExample { let mut seed = 46; let mut resources = BagOfResources::default(); let stage = ctx.new_stage().with_lighting(true); - let sunlight_a = stage.new_analytical_light( - DirectionalLightDescriptor { - direction: Vec3::new(-0.8, -1.0, 0.5).normalize(), - color: Vec4::ONE, - intensity: 10.0, - }, - None, - ); - let sunlight_b = stage.new_analytical_light( - DirectionalLightDescriptor { - direction: Vec3::new(1.0, 1.0, -0.1).normalize(), - color: Vec4::ONE, - intensity: 1.0, - }, - None, - ); + let sunlight_a = stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::new(-0.8, -1.0, 0.5).normalize(), + color: Vec4::ONE, + intensity: 10.0, + }); + let sunlight_b = stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::new(1.0, 1.0, -0.1).normalize(), + color: Vec4::ONE, + intensity: 1.0, + }); let dlights = [sunlight_a, sunlight_b]; diff --git a/crates/example/Cargo.toml b/crates/example/Cargo.toml index 53dde42d..61dd5a72 100644 --- a/crates/example/Cargo.toml +++ b/crates/example/Cargo.toml @@ -22,7 +22,6 @@ lazy_static = "1.4.0" loading-bytes = { path = "../loading-bytes" } log = { workspace = true } renderling = { path = "../renderling" } -renderling_ui = { path = "../renderling-ui"} wasm-bindgen = { workspace = true } wasm-bindgen-futures = "^0.4" web-sys = { workspace = true, features = ["Performance", "Window"] } diff --git a/crates/example/src/lib.rs b/crates/example/src/lib.rs index 4076db90..ef2b38f8 100644 --- a/crates/example/src/lib.rs +++ b/crates/example/src/lib.rs @@ -10,13 +10,13 @@ use renderling::{ atlas::AtlasImage, bvol::{Aabb, BoundingSphere}, camera::Camera, - light::{AnalyticalLightBundle, DirectionalLightDescriptor}, + light::{AnalyticalLight, DirectionalLightDescriptor}, math::{Mat4, UVec2, Vec2, Vec3, Vec4}, skybox::Skybox, stage::{Animator, GltfDocument, Renderlet, Stage, Vertex}, + ui::{FontArc, Section, Text, Ui, UiPath, UiText}, Context, }; -use renderling_ui::{FontArc, Section, Text, Ui, UiPath, UiText}; pub mod camera; use camera::{ @@ -128,7 +128,7 @@ pub struct App { loads: Arc>>>, pub stage: Stage, camera: Hybrid, - lighting: AnalyticalLightBundle, + lighting: AnalyticalLight, model: Model, animators: Option>, animations_conflict: bool, @@ -150,7 +150,7 @@ impl App { color: renderling::math::hex_to_vec4(0xFDFBD3FF), intensity: 10.0, }; - let sunlight_bundle = stage.new_analytical_light(directional_light, None); + let sunlight_bundle = stage.new_analytical_light(directional_light); stage .set_atlas_size(wgpu::Extent3d { diff --git a/crates/example/src/main.rs b/crates/example/src/main.rs index eacfb8da..be2e603c 100644 --- a/crates/example/src/main.rs +++ b/crates/example/src/main.rs @@ -61,7 +61,7 @@ impl InnerApp { self.app.set_size(size); } winit::event::WindowEvent::RedrawRequested => { - self.ctx.get_device().poll(wgpu::Maintain::Wait); + self.ctx.get_device().poll(wgpu::PollType::Wait).unwrap(); } e => self.app.camera_controller.window_event(e), } diff --git a/crates/img-diff/Cargo.toml b/crates/img-diff/Cargo.toml index 8a93cd81..94531c38 100644 --- a/crates/img-diff/Cargo.toml +++ b/crates/img-diff/Cargo.toml @@ -8,4 +8,5 @@ edition = "2021" [dependencies] glam = { workspace = true, features = ["std"] } image.workspace = true +log.workspace = true snafu = "^0.7" diff --git a/crates/img-diff/src/lib.rs b/crates/img-diff/src/lib.rs index 1a404735..efbd3b9f 100644 --- a/crates/img-diff/src/lib.rs +++ b/crates/img-diff/src/lib.rs @@ -25,8 +25,8 @@ fn checkerboard_background_color(x: u32, y: u32) -> Vec3 { #[derive(Debug, Snafu)] enum ImgDiffError { - #[snafu(display("Images are different sizes"))] - ImageSize, + #[snafu(display("Images are different sizes. Expected {lhs:?}, saw {rhs:?}"))] + ImageSize { lhs: (u32, u32), rhs: (u32, u32) }, } pub struct DiffCfg { @@ -68,7 +68,7 @@ fn get_results( ) -> Result, ImgDiffError> { let lid @ (width, height) = left_image.dimensions(); let rid = right_image.dimensions(); - snafu::ensure!(lid == rid, ImageSizeSnafu); + snafu::ensure!(lid == rid, ImageSizeSnafu { lhs: lid, rhs: rid }); let results = left_image .enumerate_pixels() @@ -147,13 +147,17 @@ pub fn assert_eq_cfg( image_threshold, test_name, } = cfg; + let results = match get_results(&lhs, &rhs, pixel_threshold) { + Ok(maybe_diff) => maybe_diff, + Err(e) => panic!("Asserting {filename} failed: {e}"), + }; if let Some(DiffResults { num_pixels: diffs, diff_image, mask_image, max_delta_length, avg_delta_length, - }) = get_results(&lhs, &rhs, pixel_threshold).unwrap() + }) = results { println!("{filename} has {diffs} pixel differences (threshold={pixel_threshold})"); println!(" max_delta_length: {max_delta_length}"); @@ -234,6 +238,8 @@ pub fn normalize_gray_img(seen: &mut image::GrayImage) { }); c.0 = comps; }); + log::info!("normalize_gray_img-min: {min}"); + log::info!("normalize_gray_img-max: {max}"); } #[cfg(test)] diff --git a/crates/renderling-build/src/lib.rs b/crates/renderling-build/src/lib.rs index a14edf47..dd548123 100644 --- a/crates/renderling-build/src/lib.rs +++ b/crates/renderling-build/src/lib.rs @@ -1,4 +1,8 @@ #![allow(unexpected_cfgs)] +use naga::{ + back::wgsl::WriterFlags, + valid::{ValidationFlags, Validator}, +}; use quote::quote; #[derive(Debug, serde::Deserialize)] @@ -83,6 +87,7 @@ impl Linkage { } fn wgsl(spv_filepath: impl AsRef, destination: impl AsRef) { + log::trace!("generating WGSL for {}", spv_filepath.as_ref().display()); let bytes = std::fs::read(spv_filepath.as_ref()).unwrap_or_else(|e| { panic!( "could not read spv filepath '{}' while attempting to translate to wgsl: {e}", @@ -90,20 +95,43 @@ fn wgsl(spv_filepath: impl AsRef, destination: impl AsRef = None; + for (vflags, name) in [ + (ValidationFlags::empty(), "empty"), + (ValidationFlags::all(), "all"), + ] { + let mut validator = Validator::new(vflags, Default::default()); + match validator.validate(&module) { + Err(e) => { + panic_msg = Some(format!( + "Could not validate '{}' with WGSL validation flags {name}: {}", + spv_filepath.as_ref().display(), + e.emit_to_string(&wgsl) + )); + } + Ok(i) => { + wgsl = naga::back::wgsl::write_string(&module, &i, WriterFlags::empty()) + .unwrap_or_else(|e| { + panic!( + "could not generate WGSL code for {}: {e}", + spv_filepath.as_ref().display() + ); + }); + } + }; + } + let destination = destination.as_ref().with_extension("wgsl"); std::fs::write(destination, wgsl).unwrap(); + if let Some(msg) = panic_msg { + panic!( + "{msg}\nWGSL was written to {}", + spv_filepath.as_ref().display() + ); + } } pub struct RenderlingPaths { @@ -140,7 +168,12 @@ impl RenderlingPaths { } /// Generate linkage (Rust source) files for each shader in the manifest. - pub fn generate_linkage(&self, from_cargo: bool, with_wgsl: bool) { + pub fn generate_linkage( + &self, + from_cargo: bool, + with_wgsl: bool, + just_fn_name: Option, + ) { log::trace!("{:#?}", std::env::vars().collect::>()); assert!( self.shader_manifest.is_file(), @@ -165,6 +198,12 @@ impl RenderlingPaths { for linkage in manifest.into_iter() { log::debug!("linkage: {linkage:#?}"); let fn_name = linkage.fn_name(); + if let Some(fn_name_match) = just_fn_name.as_ref() { + if fn_name != fn_name_match { + log::debug!(" skipping {fn_name} != {fn_name_match}"); + continue; + } + } if set.contains(fn_name) { panic!("Shader name '{fn_name}' is used for two or more shaders, aborting!"); diff --git a/crates/renderling-ui/Cargo.toml b/crates/renderling-ui/Cargo.toml deleted file mode 100644 index bd8b16e5..00000000 --- a/crates/renderling-ui/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "renderling_ui" -version = "0.3.6" -edition = "2021" -description = "User-friendly real-time 2d rendering. 🍖" -repository = "https://github.com/schell/renderling" -license = "MIT OR Apache-2.0" -keywords = ["game", "graphics", "shader", "rendering"] -categories = ["rendering", "game-development", "graphics"] -readme = "README.md" - -[dependencies] -craballoc.workspace = true -crabslab = {workspace = true} -glyph_brush = "0.7.8" -image = {workspace=true} -loading-bytes = {version = "0.1.1", path = "../loading-bytes"} -log = {workspace=true} -lyon = "1.0.1" -renderling = {version = "0.5", path = "../renderling"} -rustc-hash = {workspace = true} -snafu = {workspace = true} -wgpu = {workspace = true} - -[dev-dependencies] -ctor = {workspace = true} -env_logger = {workspace = true} -futures-lite = {workspace = true} -img-diff = {path = "../img-diff"} -pretty_assertions.workspace = true diff --git a/crates/renderling-ui/README.md b/crates/renderling-ui/README.md deleted file mode 100644 index 553eb173..00000000 --- a/crates/renderling-ui/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# renderling-ui - -`renderling-ui` specializes [`renderling`](https://crates.io/crates/renderling) to drawing -two dimensional user interfaces. diff --git a/crates/renderling/Cargo.toml b/crates/renderling/Cargo.toml index d92bec8f..2840bf46 100644 --- a/crates/renderling/Cargo.toml +++ b/crates/renderling/Cargo.toml @@ -23,15 +23,13 @@ multimodule = true crate-type = ["rlib", "cdylib"] [features] -default = ["gltf", "tutorial", "winit"] +default = ["gltf", "ui", "winit"] gltf = ["dep:gltf", "dep:serde_json"] -# optional shaders -array_test = [] test_i8_i16_extraction = [] -test_spirv_atomics = [] -tutorial = [] - +ui = ["dep:glyph_brush", "dep:loading-bytes", "dep:lyon"] wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"] +debug-slab = [] +light-tiling-stats = [] [build-dependencies] cfg_aliases.workspace = true @@ -41,6 +39,7 @@ quote.workspace = true renderling_build = { path = "../renderling-build", version = "0.1.0" } serde.workspace = true serde_json.workspace = true +similarity.workspace = true # dependencies for CPU and GPU code [dependencies] @@ -62,9 +61,12 @@ crunch = "0.5" futures-lite = {workspace=true} glam = { workspace = true, features = ["std"] } gltf = {workspace = true, optional = true} +glyph_brush = {workspace = true, optional = true} half = "2.3" image = {workspace = true, features = ["hdr"]} +loading-bytes = { workspace = true, optional = true } log = {workspace = true} +lyon = {workspace = true, optional = true} pretty_assertions.workspace = true rustc-hash = {workspace = true} serde_json = {workspace = true, optional = true} @@ -73,15 +75,16 @@ wgpu = { workspace = true, features = ["spirv"] } winit = { workspace = true, optional = true } [dev-dependencies] -acorn_prng.workspace = true -assert_approx_eq = {workspace = true} +assert_approx_eq.workspace = true ctor = "0.2.2" -env_logger = {workspace = true} +env_logger.workspace = true example = { path = "../example" } fastrand = "2.1.1" +human-repr = "1.1.0" icosahedron = "0.1" img-diff = { path = "../img-diff" } naga.workspace = true +plotters.workspace = true ttf-parser = "0.20.0" wgpu-core.workspace = true winit.workspace = true diff --git a/crates/renderling/shaders/atlas-atlas_blit_vertex.spv b/crates/renderling/shaders/atlas-atlas_blit_vertex.spv index 45409ff2..9cb3260a 100644 Binary files a/crates/renderling/shaders/atlas-atlas_blit_vertex.spv and b/crates/renderling/shaders/atlas-atlas_blit_vertex.spv differ diff --git a/crates/renderling/shaders/bloom-bloom_downsample_fragment.spv b/crates/renderling/shaders/bloom-bloom_downsample_fragment.spv index c581fd34..d6660d64 100644 Binary files a/crates/renderling/shaders/bloom-bloom_downsample_fragment.spv and b/crates/renderling/shaders/bloom-bloom_downsample_fragment.spv differ diff --git a/crates/renderling/shaders/bloom-bloom_mix_fragment.spv b/crates/renderling/shaders/bloom-bloom_mix_fragment.spv index 125e4e3a..792d9178 100644 Binary files a/crates/renderling/shaders/bloom-bloom_mix_fragment.spv and b/crates/renderling/shaders/bloom-bloom_mix_fragment.spv differ diff --git a/crates/renderling/shaders/bloom-bloom_upsample_fragment.spv b/crates/renderling/shaders/bloom-bloom_upsample_fragment.spv index 01e78d0a..b8f8b0ce 100644 Binary files a/crates/renderling/shaders/bloom-bloom_upsample_fragment.spv and b/crates/renderling/shaders/bloom-bloom_upsample_fragment.spv differ diff --git a/crates/renderling/shaders/convolution-brdf_lut_convolution_fragment.spv b/crates/renderling/shaders/convolution-brdf_lut_convolution_fragment.spv index 758e616b..cc9214b5 100644 Binary files a/crates/renderling/shaders/convolution-brdf_lut_convolution_fragment.spv and b/crates/renderling/shaders/convolution-brdf_lut_convolution_fragment.spv differ diff --git a/crates/renderling/shaders/convolution-prefilter_environment_cubemap_fragment.spv b/crates/renderling/shaders/convolution-prefilter_environment_cubemap_fragment.spv index 5ded01d2..91a72716 100644 Binary files a/crates/renderling/shaders/convolution-prefilter_environment_cubemap_fragment.spv and b/crates/renderling/shaders/convolution-prefilter_environment_cubemap_fragment.spv differ diff --git a/crates/renderling/shaders/convolution-prefilter_environment_cubemap_vertex.spv b/crates/renderling/shaders/convolution-prefilter_environment_cubemap_vertex.spv index a8e990ab..d77ce473 100644 Binary files a/crates/renderling/shaders/convolution-prefilter_environment_cubemap_vertex.spv and b/crates/renderling/shaders/convolution-prefilter_environment_cubemap_vertex.spv differ diff --git a/crates/renderling/shaders/cull-compute_culling.spv b/crates/renderling/shaders/cull-compute_culling.spv index 677b805d..95c9f82d 100644 Binary files a/crates/renderling/shaders/cull-compute_culling.spv and b/crates/renderling/shaders/cull-compute_culling.spv differ diff --git a/crates/renderling/shaders/debug-debug_overlay_fragment.spv b/crates/renderling/shaders/debug-debug_overlay_fragment.spv index fd85e0dc..5d0205f2 100644 Binary files a/crates/renderling/shaders/debug-debug_overlay_fragment.spv and b/crates/renderling/shaders/debug-debug_overlay_fragment.spv differ diff --git a/crates/renderling/shaders/ibl-diffuse_irradiance-di_convolution_fragment.spv b/crates/renderling/shaders/ibl-diffuse_irradiance-di_convolution_fragment.spv index 0843942c..f3df3c56 100644 Binary files a/crates/renderling/shaders/ibl-diffuse_irradiance-di_convolution_fragment.spv and b/crates/renderling/shaders/ibl-diffuse_irradiance-di_convolution_fragment.spv differ diff --git a/crates/renderling/shaders/light-light_tiling_bin_lights.spv b/crates/renderling/shaders/light-light_tiling_bin_lights.spv new file mode 100644 index 00000000..64bc0187 Binary files /dev/null and b/crates/renderling/shaders/light-light_tiling_bin_lights.spv differ diff --git a/crates/renderling/shaders/light-light_tiling_clear_tiles.spv b/crates/renderling/shaders/light-light_tiling_clear_tiles.spv new file mode 100644 index 00000000..01484a69 Binary files /dev/null and b/crates/renderling/shaders/light-light_tiling_clear_tiles.spv differ diff --git a/crates/renderling/shaders/light-light_tiling_compute_tile_min_and_max_depth.spv b/crates/renderling/shaders/light-light_tiling_compute_tile_min_and_max_depth.spv new file mode 100644 index 00000000..bee81551 Binary files /dev/null and b/crates/renderling/shaders/light-light_tiling_compute_tile_min_and_max_depth.spv differ diff --git a/crates/renderling/shaders/light-light_tiling_compute_tile_min_and_max_depth_multisampled.spv b/crates/renderling/shaders/light-light_tiling_compute_tile_min_and_max_depth_multisampled.spv new file mode 100644 index 00000000..f1439e2f Binary files /dev/null and b/crates/renderling/shaders/light-light_tiling_compute_tile_min_and_max_depth_multisampled.spv differ diff --git a/crates/renderling/shaders/light-light_tiling_depth_pre_pass.spv b/crates/renderling/shaders/light-light_tiling_depth_pre_pass.spv new file mode 100644 index 00000000..cde999c7 Binary files /dev/null and b/crates/renderling/shaders/light-light_tiling_depth_pre_pass.spv differ diff --git a/crates/renderling/shaders/light-shadow_mapping_vertex.spv b/crates/renderling/shaders/light-shadow_mapping_vertex.spv index a2d57ff6..43da3b39 100644 Binary files a/crates/renderling/shaders/light-shadow_mapping_vertex.spv and b/crates/renderling/shaders/light-shadow_mapping_vertex.spv differ diff --git a/crates/renderling/shaders/manifest.json b/crates/renderling/shaders/manifest.json index 4a7da913..ca27d96e 100644 --- a/crates/renderling/shaders/manifest.json +++ b/crates/renderling/shaders/manifest.json @@ -104,6 +104,31 @@ "entry_point": "ibl::diffuse_irradiance::di_convolution_fragment", "wgsl_entry_point": "ibldiffuse_irradiancedi_convolution_fragment" }, + { + "source_path": "shaders/light-light_tiling_bin_lights.spv", + "entry_point": "light::light_tiling_bin_lights", + "wgsl_entry_point": "lightlight_tiling_bin_lights" + }, + { + "source_path": "shaders/light-light_tiling_clear_tiles.spv", + "entry_point": "light::light_tiling_clear_tiles", + "wgsl_entry_point": "lightlight_tiling_clear_tiles" + }, + { + "source_path": "shaders/light-light_tiling_compute_tile_min_and_max_depth.spv", + "entry_point": "light::light_tiling_compute_tile_min_and_max_depth", + "wgsl_entry_point": "lightlight_tiling_compute_tile_min_and_max_depth" + }, + { + "source_path": "shaders/light-light_tiling_compute_tile_min_and_max_depth_multisampled.spv", + "entry_point": "light::light_tiling_compute_tile_min_and_max_depth_multisampled", + "wgsl_entry_point": "lightlight_tiling_compute_tile_min_and_max_depth_multisampled" + }, + { + "source_path": "shaders/light-light_tiling_depth_pre_pass.spv", + "entry_point": "light::light_tiling_depth_pre_pass", + "wgsl_entry_point": "lightlight_tiling_depth_pre_pass" + }, { "source_path": "shaders/light-shadow_mapping_fragment.spv", "entry_point": "light::shadow_mapping_fragment", diff --git a/crates/renderling/shaders/skybox-skybox_cubemap_fragment.spv b/crates/renderling/shaders/skybox-skybox_cubemap_fragment.spv index d3c61bab..792aea28 100644 Binary files a/crates/renderling/shaders/skybox-skybox_cubemap_fragment.spv and b/crates/renderling/shaders/skybox-skybox_cubemap_fragment.spv differ diff --git a/crates/renderling/shaders/skybox-skybox_cubemap_vertex.spv b/crates/renderling/shaders/skybox-skybox_cubemap_vertex.spv index f4a16170..ea184762 100644 Binary files a/crates/renderling/shaders/skybox-skybox_cubemap_vertex.spv and b/crates/renderling/shaders/skybox-skybox_cubemap_vertex.spv differ diff --git a/crates/renderling/shaders/skybox-skybox_equirectangular_fragment.spv b/crates/renderling/shaders/skybox-skybox_equirectangular_fragment.spv index 6c0cb4bd..3617b009 100644 Binary files a/crates/renderling/shaders/skybox-skybox_equirectangular_fragment.spv and b/crates/renderling/shaders/skybox-skybox_equirectangular_fragment.spv differ diff --git a/crates/renderling/shaders/skybox-skybox_vertex.spv b/crates/renderling/shaders/skybox-skybox_vertex.spv index 8316eb73..049c982e 100644 Binary files a/crates/renderling/shaders/skybox-skybox_vertex.spv and b/crates/renderling/shaders/skybox-skybox_vertex.spv differ diff --git a/crates/renderling/shaders/stage-renderlet_fragment.spv b/crates/renderling/shaders/stage-renderlet_fragment.spv index d4760ae9..112c469e 100644 Binary files a/crates/renderling/shaders/stage-renderlet_fragment.spv and b/crates/renderling/shaders/stage-renderlet_fragment.spv differ diff --git a/crates/renderling/shaders/stage-renderlet_vertex.spv b/crates/renderling/shaders/stage-renderlet_vertex.spv index 330a7ac7..48543f63 100644 Binary files a/crates/renderling/shaders/stage-renderlet_vertex.spv and b/crates/renderling/shaders/stage-renderlet_vertex.spv differ diff --git a/crates/renderling/shaders/tonemapping-tonemapping_fragment.spv b/crates/renderling/shaders/tonemapping-tonemapping_fragment.spv index c982db1f..cf72d613 100644 Binary files a/crates/renderling/shaders/tonemapping-tonemapping_fragment.spv and b/crates/renderling/shaders/tonemapping-tonemapping_fragment.spv differ diff --git a/crates/renderling/src/atlas/cpu.rs b/crates/renderling/src/atlas/cpu.rs index 55ab2688..89fe3c16 100644 --- a/crates/renderling/src/atlas/cpu.rs +++ b/crates/renderling/src/atlas/cpu.rs @@ -491,6 +491,7 @@ fn pack_images<'a>( ); let mut newly_packed_layers: Vec> = vec![]; for (i, new_layer) in new_packing_layers.into_iter().enumerate() { + log::trace!(" packing layer {i} into power of 2 {}", extent.width); let packed = crunch::pack_into_po2( extent.width as usize, new_layer.into_iter().map(|p| { @@ -770,6 +771,7 @@ impl AtlasBlittingOperation { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, @@ -1013,7 +1015,7 @@ mod test { let (projection, view) = crate::camera::default_ortho2d(w as f32, h as f32); let _camera = stage.new_camera(Camera::new(projection, view)); let texels = AtlasImage::from_path("../../img/happy_mac.png").unwrap(); - let entries = stage.set_images(std::iter::repeat(texels).take(3)).unwrap(); + let entries = stage.set_images(std::iter::repeat_n(texels, 3)).unwrap(); let clamp_tex = &entries[0]; let repeat_tex = &entries[1]; repeat_tex.modify(|t| { diff --git a/crates/renderling/src/bloom/cpu.rs b/crates/renderling/src/bloom/cpu.rs index 5d1c38c7..ed11a46b 100644 --- a/crates/renderling/src/bloom/cpu.rs +++ b/crates/renderling/src/bloom/cpu.rs @@ -408,8 +408,7 @@ impl Bloom { let runtime = runtime.as_ref(); let resolution = UVec2::new(hdr_texture.width(), hdr_texture.height()); - let slab = - SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::empty(), Some("bloom-slab")); + let slab = SlabAllocator::new(runtime, "bloom-slab", wgpu::BufferUsages::empty()); let downsample_pixel_sizes = slab.new_array( config_resolutions(resolution).map(|r| 1.0 / Vec2::new(r.x as f32, r.y as f32)), ); @@ -593,6 +592,7 @@ impl Bloom { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, @@ -639,6 +639,7 @@ impl Bloom { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, @@ -669,6 +670,7 @@ impl Bloom { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, diff --git a/crates/renderling/src/build.rs b/crates/renderling/src/build.rs index 41ded627..10c9dd58 100644 --- a/crates/renderling/src/build.rs +++ b/crates/renderling/src/build.rs @@ -3,7 +3,7 @@ fn main() { if std::env::var("CARGO_CFG_TARGET_ARCH").as_deref() != Ok("spirv") { if let Some(paths) = renderling_build::RenderlingPaths::new() { - paths.generate_linkage(true, true); + paths.generate_linkage(true, true, None); } } diff --git a/crates/renderling/src/bvol.rs b/crates/renderling/src/bvol.rs index dc59422a..a63236b9 100644 --- a/crates/renderling/src/bvol.rs +++ b/crates/renderling/src/bvol.rs @@ -13,7 +13,7 @@ use crabslab::SlabItem; use glam::{Mat4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; -#[cfg(target_arch = "spirv")] +#[cfg(gpu)] use spirv_std::num_traits::Float; use crate::{camera::Camera, transform::Transform}; @@ -90,8 +90,7 @@ pub fn mo_vertex(plane: &Vec4, aabb: &Aabb) -> Vec3 { } /// Axis aligned bounding box. -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] -#[derive(Clone, Copy, Default, PartialEq, SlabItem)] +#[derive(Clone, Copy, Debug, Default, PartialEq, SlabItem)] pub struct Aabb { pub min: Vec3, pub max: Vec3, @@ -130,6 +129,10 @@ impl Aabb { (self.min + self.max) * 0.5 } + pub fn extents(&self) -> Vec3 { + self.max - self.center() + } + pub fn diagonal_length(&self) -> f32 { self.min.distance(self.max) } @@ -182,6 +185,18 @@ impl Aabb { }) .collect() } + + /// Returns whether this `Aabb` intersects another `Aabb`. + /// + /// Returns `false` if the two are touching, but not overlapping. + pub fn intersects_aabb(&self, other: &Aabb) -> bool { + self.min.x < other.max.x + && self.max.x > other.min.x + && self.min.y < other.max.y + && self.max.y > other.min.y + && self.min.z < other.max.z + && self.max.z > other.min.z + } } /// Six planes of a view frustum. @@ -311,14 +326,102 @@ impl Frustum { true } + /// Returns the depth of the frustum. pub fn depth(&self) -> f32 { (self.planes[0].w - self.planes[5].w).abs() } } -/// Bounding sphere consisting of a center and radius. +/// Bounding box consisting of a center and three half extents. +/// +/// Essentially a point at the center and a vector pointing from +/// the center to the corner. +/// +/// This is _not_ an axis aligned bounding box. #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[derive(Clone, Copy, Default, PartialEq, SlabItem)] +pub struct BoundingBox { + pub center: Vec3, + pub half_extent: Vec3, +} + +impl BoundingBox { + pub fn from_min_max(min: Vec3, max: Vec3) -> Self { + let center = (min + max) / 2.0; + let half_extent = max - center; + Self { + center, + half_extent, + } + } + + pub fn distance(&self, point: Vec3) -> f32 { + let p = point - self.center; + let component_edge_distance = p.abs() - self.half_extent; + let outside = component_edge_distance.max(Vec3::ZERO).length(); + let inside = component_edge_distance + .x + .max(component_edge_distance.y) + .min(0.0); + inside + outside + } + + #[cfg(cpu)] + /// Return a triangle mesh connecting this `Aabb`'s corners. + /// + /// ```ignore + /// y 1_____2 _____ + /// | / /| /| | (same box, left and front sides removed) + /// |___x 0/___3/ | /7|____|6 + /// / | | / | / / + /// z/ |____|/ 4|/____/5 + /// + /// 7 is min + /// 3 is max + /// ``` + pub fn get_mesh(&self) -> [(Vec3, Vec3); 36] { + // Deriving the corner positions from centre and half-extent, + + let p0 = Vec3::new(-self.half_extent.x, self.half_extent.y, self.half_extent.z); + let p1 = Vec3::new(-self.half_extent.x, self.half_extent.y, -self.half_extent.z); + let p2 = Vec3::new(self.half_extent.x, self.half_extent.y, -self.half_extent.z); + let p3 = self.half_extent; + let p4 = Vec3::new(-self.half_extent.x, -self.half_extent.y, self.half_extent.z); + let p5 = Vec3::new(self.half_extent.x, -self.half_extent.y, self.half_extent.z); + let p6 = Vec3::new(self.half_extent.x, -self.half_extent.y, -self.half_extent.z); + // min + let p7 = -self.half_extent; + + let positions = + crate::math::convex_mesh([p0, p1, p2, p3, p4, p5, p6, p7].map(|p| p + self.center)); + + // Attach per-triangle face normals. + let vertices: Vec<(Vec3, Vec3)> = positions + .chunks_exact(3) + .flat_map(|chunk| match chunk { + [a, b, c] => { + let n = crate::math::triangle_face_normal(*a, *b, *c); + [(*a, n), (*b, n), (*c, n)] + } + _ => unreachable!(), + }) + .collect(); + + // Convert into fixed-size array (12 triangles × 3 vertices). + vertices + .try_into() + .unwrap_or_else(|v: Vec<(Vec3, Vec3)>| panic!("expected 36 vertices, got {}", v.len())) + } + + pub fn contains_point(&self, point: Vec3) -> bool { + let delta = (point - self.center).abs(); + let extent = self.half_extent.abs(); + delta.x <= extent.x && delta.y <= extent.y && delta.z <= extent.z + } +} + +/// Bounding sphere consisting of a center and radius. +#[derive(Clone, Copy, Debug, Default, PartialEq, SlabItem)] pub struct BoundingSphere { pub center: Vec3, pub radius: f32, @@ -363,6 +466,20 @@ impl BoundingSphere { (sphere.is_inside_frustum(camera.frustum()), sphere) } + /// Transform this `BoundingSphere` by the given view projection matrix. + pub fn project_by(&self, view_projection: &Mat4) -> Self { + let center = self.center; + // Pick any direction to find a point on the surface. + let surface_point = self.center + self.radius * Vec3::Z; + let new_center = view_projection.project_point3(center); + let new_surface_point = view_projection.project_point3(surface_point); + let new_radius = new_center.distance(new_surface_point); + Self { + center: new_center, + radius: new_radius, + } + } + /// Returns an [`Aabb`] with x and y coordinates in viewport pixels and z coordinate /// in NDC depth. pub fn project_onto_viewport(&self, camera: &Camera, viewport: Vec2) -> Aabb { @@ -470,7 +587,7 @@ impl BVol for Aabb { mod test { use glam::{Mat4, Quat}; - use crate::{stage::Vertex, Context}; + use crate::{pbr::Material, stage::Vertex, Context}; use super::*; @@ -523,41 +640,84 @@ mod test { } #[test] - fn bounding_sphere_from_min_max() { - let ctx = Context::headless(100, 100); + fn bounding_box_from_min_max() { + let ctx = Context::headless(256, 256); let stage = ctx .new_stage() - .with_lighting(false) - .with_background_color(Vec4::ONE) - .with_debug_overlay(true) - .with_frustum_culling(false); - - let projection = Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, 1.0, 0.1, 20.0); - let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.0), Vec3::ZERO, Vec3::Y); - let _camera = stage.new_camera(Camera::new(projection, view)); - - let mut min = Vec3::splat(f32::INFINITY); - let mut max = Vec3::splat(f32::NEG_INFINITY); - let _rez = stage + .with_background_color(Vec4::ZERO) + .with_msaa_sample_count(4) + .with_lighting(true); + let _camera = stage.new_camera({ + Camera::new( + // BUG: using orthographic here renderes nothing + // Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, 10.0, -10.0), + crate::camera::perspective(256.0, 256.0), + Mat4::look_at_rh(Vec3::new(-3.0, 3.0, 5.0) * 0.5, Vec3::ZERO, Vec3::Y), + ) + }); + let _lights = crate::test::make_two_directional_light_setup(&stage); + + let white = stage.new_material(Material { + albedo_factor: Vec4::ONE, + ..Default::default() + }); + let red = stage.new_material(Material { + albedo_factor: Vec4::new(1.0, 0.0, 0.0, 1.0), + ..Default::default() + }); + + let _w = stage .builder() - .with_vertices(crate::math::unit_cube().into_iter().map(|(p, n)| { - min = min.min(p); - max = max.max(p); - Vertex::default() - .with_position(p) - .with_normal(n) - .with_color(Vec4::new(1.0, 0.0, 0.0, 1.0)) - })) - .with_bounds({ - log::info!("bounds: {:?}", (min, max)); - (min, max) - }) + .with_material_id(white.id()) + .with_vertices( + crate::math::unit_cube() + .into_iter() + .map(|(p, n)| Vertex::default().with_position(p).with_normal(n)), + ) .build(); + let mut corners = vec![]; + for x in [-1.0, 1.0] { + for y in [-1.0, 1.0] { + for z in [-1.0, 1.0] { + corners.push(Vec3::new(x, y, z)); + } + } + } + let mut rs = vec![]; + for corner in corners.iter() { + let bb = BoundingBox { + center: Vec3::new(0.5, 0.5, 0.5) * corner, + half_extent: Vec3::splat(0.25), + }; + assert!( + bb.contains_point(bb.center), + "BoundingBox {bb:?} does not contain center" + ); + + rs.push( + stage + .builder() + .with_material_id(red.id()) + .with_vertices( + bb.get_mesh() + .map(|(p, n)| Vertex::default().with_position(p).with_normal(n)), + ) + .build(), + ); + } + let frame = ctx.get_next_frame().unwrap(); stage.render(&frame.view()); let img = frame.read_image().unwrap(); - img_diff::save("bvol/bounding_sphere_from_min_max.png", img); - frame.present(); + img_diff::assert_img_eq("bvol/bounding_box/get_mesh.png", img); + } + + #[test] + fn aabb_intersection() { + let a = Aabb::new(Vec3::ZERO, Vec3::ONE); + let b = Aabb::new(Vec3::splat(0.9), Vec3::splat(1.9)); + assert!(a.intersects_aabb(&b)); + assert!(b.intersects_aabb(&a)); } } diff --git a/crates/renderling/src/camera.rs b/crates/renderling/src/camera.rs index 0e0ab952..cf732511 100644 --- a/crates/renderling/src/camera.rs +++ b/crates/renderling/src/camera.rs @@ -1,8 +1,11 @@ //! Camera projection, view and utilities. use crabslab::SlabItem; -use glam::{Mat4, Vec3, Vec4}; +use glam::{Mat4, Vec2, Vec3, Vec4}; -use crate::bvol::{dist_bpp, Frustum}; +use crate::{ + bvol::{dist_bpp, Frustum}, + math::IsVector, +}; /// A camera used for transforming the stage during rendering. /// @@ -14,6 +17,10 @@ pub struct Camera { view: Mat4, position: Vec3, frustum: Frustum, + /// Nearest center point on the frustum + z_near_point: Vec3, + /// Furthest center point on the frustum + z_far_point: Vec3, } impl Camera { @@ -39,6 +46,9 @@ impl Camera { self.projection = projection; self.view = view; self.position = view.inverse().transform_point3(Vec3::ZERO); + let inverse = (projection * view).inverse(); + self.z_near_point = inverse.project_point3(Vec3::ZERO); + self.z_far_point = inverse.project_point3(Vec2::ZERO.extend(1.0)); self.frustum = Frustum::from_camera(self); } @@ -91,23 +101,28 @@ impl Camera { /// Returns **roughly** the location of the znear plane. pub fn z_near(&self) -> f32 { - dist_bpp(&self.frustum.planes[0], self.position) + self.z_near_point.distance(self.position) } pub fn z_far(&self) -> f32 { - dist_bpp(&self.frustum.planes[5], self.position) + self.z_far_point.distance(self.position) } pub fn depth(&self) -> f32 { - self.frustum.depth() + (self.z_far() - self.z_near()).abs() } - #[warn(soft_unstable, reason = "depth linearization is currently unreliable")] - pub fn linearize_depth(&self, d: f32) -> f32 { - // TODO: figure out why z_near and z_far come out equal in ortho - let z_near = self.z_near(); - let z_far = self.z_far(); - z_near * z_far / (z_far + d * (z_near - z_far)) + /// Returns the normalized forward vector which points in the direction the camera is looking. + pub fn forward(&self) -> Vec3 { + (self.z_far_point - self.z_near_point).alt_norm_or_zero() + } + + pub fn frustum_near_point(&self) -> Vec3 { + self.forward() * self.z_near() + } + + pub fn frustum_far_point(&self) -> Vec3 { + self.forward() * self.z_far() } } @@ -177,3 +192,36 @@ pub fn default_ortho2d(width: f32, height: f32) -> (Mat4, Mat4) { let view = Mat4::IDENTITY; (projection, view) } + +#[cfg(test)] +mod tests { + use super::*; + use glam::Vec3; + + #[test] + fn forward() { + let eyes = [Vec3::new(0.0, 0.0, 5.0), Vec3::new(250.0, 200.0, 250.0)]; + + let expected_forwards = [ + Vec3::new(0.0, 0.0, -1.0), + Vec3::new(-0.6154574, -0.49236593, -0.6154574), + ]; + + for (eye, expected_forward) in eyes.into_iter().zip(expected_forwards) { + let projection = Mat4::perspective_rh(45.0_f32.to_radians(), 800.0 / 600.0, 0.1, 100.0); + let view = Mat4::look_at_rh(eye, Vec3::ZERO, Vec3::Y); + let camera = Camera::new(projection, view); + + let forward = camera.forward(); + let distance = forward.distance(expected_forward); + const THRESHOLD: f32 = 1e-3; + assert!( + distance < THRESHOLD, + "Forward vector is incorrect\n\ + forward: {forward}\n\ + expected: {expected_forward}\n\ + distance: {distance}, threshold: {THRESHOLD}" + ); + } + } +} diff --git a/crates/renderling/src/context.rs b/crates/renderling/src/context.rs index 9b1fa59a..459270cd 100644 --- a/crates/renderling/src/context.rs +++ b/crates/renderling/src/context.rs @@ -151,15 +151,13 @@ async fn device( let limits = adapter.limits(); log::info!("adapter limits: {limits:#?}"); adapter - .request_device( - &wgpu::DeviceDescriptor { - required_features, - required_limits: adapter.limits(), - label: None, - memory_hints: wgpu::MemoryHints::default(), - }, - None, - ) + .request_device(&wgpu::DeviceDescriptor { + required_features, + required_limits: adapter.limits(), + label: None, + memory_hints: wgpu::MemoryHints::default(), + trace: wgpu::Trace::Off, + }) .await } @@ -260,8 +258,8 @@ pub enum ContextError { #[snafu(display("missing surface texture: {}", source))] Surface { source: wgpu::SurfaceError }, - #[snafu(display("cannot create adaptor"))] - CannotCreateAdaptor, + #[snafu(display("cannot create adaptor: {source}"))] + CannotCreateAdaptor { source: wgpu::RequestAdapterError }, #[snafu(display("cannot request device: {}", source))] CannotRequestDevice { source: wgpu::RequestDeviceError }, @@ -429,6 +427,14 @@ impl Frame { } } +/// Configurable default values to use when creating new [`Stage`]s. +#[derive(Debug, Clone, Copy)] +pub(crate) struct GlobalStageConfig { + pub(crate) atlas_size: wgpu::Extent3d, + pub(crate) shadow_map_atlas_size: wgpu::Extent3d, + pub(crate) use_compute_culling: bool, +} + /// Contains the adapter, device, queue, [`RenderTarget`] and initial atlas sizing. /// /// A `Context` is created to initialize rendering to a window, canvas or @@ -443,7 +449,7 @@ pub struct Context { runtime: WgpuRuntime, adapter: Arc, render_target: RenderTarget, - pub(crate) atlas_size: Arc>, + pub(crate) stage_config: Arc>, } impl AsRef for Context { @@ -464,13 +470,21 @@ impl Context { let w = limits .max_texture_dimension_2d .min(crate::atlas::ATLAS_SUGGESTED_SIZE); - let atlas_size = Arc::new(RwLock::new(wgpu::Extent3d { - width: w, - height: w, - depth_or_array_layers: adapter - .limits() - .max_texture_array_layers - .min(crate::atlas::ATLAS_SUGGESTED_LAYERS), + let stage_config = Arc::new(RwLock::new(GlobalStageConfig { + atlas_size: wgpu::Extent3d { + width: w, + height: w, + depth_or_array_layers: adapter + .limits() + .max_texture_array_layers + .min(crate::atlas::ATLAS_SUGGESTED_LAYERS), + }, + shadow_map_atlas_size: wgpu::Extent3d { + width: w, + height: w, + depth_or_array_layers: 4, + }, + use_compute_culling: false, })); Self { adapter, @@ -479,7 +493,7 @@ impl Context { queue: queue.into(), }, render_target: target, - atlas_size, + stage_config, } } @@ -651,14 +665,11 @@ impl Context { }) } - /// Set the default texture size for any atlas. + /// Set the default texture size for the material atlas. /// /// * Width is `size.x` and must be a power of two. /// * Height is `size.y`, must match `size.x` and must be a power of two. /// * Layers is `size.z` and must be two or greater. - /// - /// ## Panics - /// Will panic if the above conditions are not met. pub fn set_default_atlas_texture_size(&self, size: impl Into) -> &Self { let size = size.into(); let size = wgpu::Extent3d { @@ -667,11 +678,11 @@ impl Context { depth_or_array_layers: size.z, }; crate::atlas::check_size(size); - *self.atlas_size.write().unwrap() = size; + self.stage_config.write().unwrap().atlas_size = size; self } - /// Set the default texture size for any atlas. + /// Set the default texture size for the material atlas. /// /// * Width is `size.x` and must be a power of two. /// * Height is `size.y`, must match `size.x` and must be a power of two. @@ -684,6 +695,61 @@ impl Context { self } + /// Set the default texture size for the shadow mapping atlas. + /// + /// * Width is `size.x` and must be a power of two. + /// * Height is `size.y`, must match `size.x` and must be a power of two. + /// * Layers is `size.z` and must be two or greater. + pub fn set_shadow_mapping_atlas_texture_size(&self, size: impl Into) -> &Self { + let size = size.into(); + let size = wgpu::Extent3d { + width: size.x, + height: size.y, + depth_or_array_layers: size.z, + }; + crate::atlas::check_size(size); + self.stage_config.write().unwrap().shadow_map_atlas_size = size; + self + } + + /// Set the default texture size for the shadow mapping atlas. + /// + /// * Width is `size.x` and must be a power of two. + /// * Height is `size.y`, must match `size.x` and must be a power of two. + /// * Layers is `size.z` and must be greater than zero. + /// + /// ## Panics + /// Will panic if the above conditions are not met. + pub fn with_shadow_mapping_atlas_texture_size(self, size: impl Into) -> Self { + self.set_shadow_mapping_atlas_texture_size(size); + self + } + + /// Set the use of direct drawing. + /// + /// Default is **false**. + /// + /// If set to **true**, all compute culling, including frustum and occlusion culling, + /// will **not** run. + pub fn set_use_direct_draw(&self, use_direct_drawing: bool) { + self.stage_config.write().unwrap().use_compute_culling = !use_direct_drawing; + } + + /// Set the use of direct drawing. + /// + /// Default is **false**. + /// + /// If set to **true**, all compute culling is turned **off**. + /// This includes frustum and occlusion culling. + pub fn with_use_direct_draw(self, use_direct_drawing: bool) -> Self { + self.set_use_direct_draw(use_direct_drawing); + self + } + + pub fn get_use_direct_draw(&self) -> bool { + !self.stage_config.read().unwrap().use_compute_culling + } + /// Create and return a new [`Stage`] renderer. pub fn new_stage(&self) -> Stage { Stage::new(self) diff --git a/crates/renderling/src/cubemap/cpu.rs b/crates/renderling/src/cubemap/cpu.rs index 62c030a8..09734d32 100644 --- a/crates/renderling/src/cubemap/cpu.rs +++ b/crates/renderling/src/cubemap/cpu.rs @@ -121,6 +121,7 @@ impl SceneCubemap { load: wgpu::LoadOp::Clear(self.clear_color), store: wgpu::StoreOp::Store, }, + depth_slice: None, }; let depth_stencil_attachment = wgpu::RenderPassDepthStencilAttachment { view: &self.depth_texture.view, @@ -317,7 +318,7 @@ mod test { ); scene_cubemap.run(&stage); - let slab = SlabAllocator::new(&ctx, wgpu::BufferUsages::empty()); + let slab = SlabAllocator::new(&ctx, "cubemap-sampling-test", wgpu::BufferUsages::empty()); let uv = slab.new_value(Vec3::ZERO); let buffer = slab.commit(); let label = Some("cubemap-sampling-test"); @@ -469,6 +470,7 @@ mod test { }), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, @@ -480,7 +482,8 @@ mod test { } let submission_index = ctx.get_queue().submit(Some(encoder.finish())); ctx.get_device() - .poll(wgpu::Maintain::wait_for(submission_index)); + .poll(wgpu::PollType::WaitForSubmissionIndex(submission_index)) + .unwrap(); let img = Texture::read(&ctx, &render_target, 1, 1, 4, 1) .into_image::>(ctx.get_device()) @@ -580,13 +583,8 @@ mod test { // add in some deterministic pseudo-randomn points { - let order = acorn_prng::Order::new(666); - let seed = acorn_prng::Seed::new(1_000_000); - let mut prng = acorn_prng::Acorn::new(order, seed); - let mut rf32 = move || { - let u = prng.generate_u32_between_range(0..=u32::MAX); - f32::from_bits(u) - }; + let mut prng = crate::math::GpuRng::new(666); + let mut rf32 = move || prng.gen_f32(0.0, 1.0); let mut rxvec3 = { || Vec3::new(f32::MAX, rf32(), rf32()).normalize_or(Vec3::X) }; // let mut rvec3 = || Vec3::new(rf32(), rf32(), rf32()); uvs.extend((0..20).map(|_| rxvec3())); diff --git a/crates/renderling/src/cull/cpu.rs b/crates/renderling/src/cull/cpu.rs index ac3ec7ae..edb86349 100644 --- a/crates/renderling/src/cull/cpu.rs +++ b/crates/renderling/src/cull/cpu.rs @@ -223,7 +223,7 @@ pub struct DepthPyramid { } impl DepthPyramid { - const LABEL: Option<&'static str> = Some("depth-pyramid"); + const LABEL: &str = "depth-pyramid"; fn allocate( size: UVec2, @@ -249,7 +249,7 @@ impl DepthPyramid { } pub fn new(runtime: impl AsRef, size: UVec2) -> Self { - let slab = SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::empty(), Self::LABEL); + let slab = SlabAllocator::new(runtime, Self::LABEL, wgpu::BufferUsages::empty()); let desc = slab.new_value(DepthPyramidDescriptor::default()); let (mip_data, mip) = Self::allocate(size, &desc, &slab); @@ -709,7 +709,7 @@ mod test { frame.present(); let depth_texture = stage.get_depth_texture(); - let depth_img = depth_texture.read_image().unwrap(); + let depth_img = depth_texture.read_image().unwrap().unwrap(); img_diff::save("cull/pyramid/depth.png", depth_img); let pyramid_images = futures_lite::future::block_on( @@ -918,7 +918,7 @@ mod test { save_render("3_purple_cube"); // save the normalized depth image - let mut depth_img = stage.get_depth_texture().read_image().unwrap(); + let mut depth_img = stage.get_depth_texture().read_image().unwrap().unwrap(); img_diff::normalize_gray_img(&mut depth_img); img_diff::save("cull/debugging_4_depth.png", depth_img); diff --git a/crates/renderling/src/debug/cpu.rs b/crates/renderling/src/debug/cpu.rs index 90529303..f9993ddc 100644 --- a/crates/renderling/src/debug/cpu.rs +++ b/crates/renderling/src/debug/cpu.rs @@ -149,6 +149,7 @@ impl DebugOverlay { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, diff --git a/crates/renderling/src/draw.rs b/crates/renderling/src/draw.rs index 84a0e740..d2990b7a 100644 --- a/crates/renderling/src/draw.rs +++ b/crates/renderling/src/draw.rs @@ -6,14 +6,14 @@ //! [`Stage::render`](crate::prelude::Stage::render). use crabslab::SlabItem; -#[cfg(not(target_arch = "spirv"))] +#[cfg(cpu)] mod cpu; -#[cfg(not(target_arch = "spirv"))] +#[cfg(cpu)] pub use cpu::*; /// Argument buffer layout for draw_indirect commands. #[repr(C)] -#[cfg_attr(not(target_arch = "spirv"), derive(bytemuck::Pod, bytemuck::Zeroable))] +#[cfg_attr(cpu, derive(Debug, bytemuck::Pod, bytemuck::Zeroable))] #[derive(Clone, Copy, Default, SlabItem)] pub struct DrawIndirectArgs { pub vertex_count: u32, diff --git a/crates/renderling/src/draw/cpu.rs b/crates/renderling/src/draw/cpu.rs index a8ec0653..a92f432a 100644 --- a/crates/renderling/src/draw/cpu.rs +++ b/crates/renderling/src/draw/cpu.rs @@ -32,15 +32,6 @@ impl InternalRenderlet { inner: WeakHybrid::from_hybrid(hr), } } - - fn copy_inner(&self) -> Option { - let hy = self.get_hybrid()?; - Some(hy.get()) - } - - fn get_hybrid(&self) -> Option> { - self.inner.upgrade() - } } /// Issues indirect draw calls. @@ -59,11 +50,8 @@ impl IndirectDraws { depth_texture: &Texture, ) -> Self { let runtime = runtime.as_ref(); - let indirect_slab = SlabAllocator::new_with_label( - runtime, - wgpu::BufferUsages::INDIRECT, - Some("indirect-slab"), - ); + let indirect_slab = + SlabAllocator::new(runtime, "indirect-slab", wgpu::BufferUsages::INDIRECT); Self { compute_culling: ComputeCulling::new( runtime, @@ -78,6 +66,7 @@ impl IndirectDraws { fn invalidate(&mut self) { if !self.draws.is_empty() { + log::trace!("draining indirect draws after invalidation"); let _ = self.draws.drain(..); } } @@ -165,7 +154,6 @@ pub struct DrawCalls { /// Internal representation of all staged renderlets. internal_renderlets: Vec, pub(crate) drawing_strategy: DrawingStrategy, - stage_slab_buffer: SlabBuffer, } impl DrawCalls { @@ -208,7 +196,6 @@ impl DrawCalls { DrawingStrategy::Direct } }, - stage_slab_buffer: stage_slab_buffer.clone(), } } @@ -275,7 +262,7 @@ impl DrawCalls { pub fn renderlets_iter(&self) -> impl Iterator> + '_ { self.internal_renderlets.iter().map(|ir| ir.inner.clone()) } - + /// /// Perform upkeep on queued draw calls and synchronize internal buffers. pub fn upkeep(&mut self) { let mut redraw_args = false; @@ -302,7 +289,7 @@ impl DrawCalls { self.internal_renderlets.len() } - /// Perform pre-draw steps like compute culling, if available. + /// Perform pre-draw steps like frustum and occlusion culling, if available. /// /// This does not do upkeep, please call [`DrawCalls::upkeep`] before /// calling this function. @@ -345,7 +332,24 @@ impl DrawCalls { } } + /// Draw into the given `RenderPass` by directly calling each draw. + pub fn draw_direct(&self, render_pass: &mut wgpu::RenderPass) { + for ir in self.internal_renderlets.iter() { + // UNWRAP: panic on purpose + if let Some(hr) = ir.inner.upgrade() { + let ir = hr.get(); + let vertex_range = 0..ir.get_vertex_count(); + let id = hr.id(); + let instance_range = id.inner()..id.inner() + 1; + render_pass.draw(vertex_range, instance_range); + } + } + } + /// Draw into the given `RenderPass`. + /// + /// This method draws using the indirect draw buffer, if possible, otherwise + /// it falls back to `draw_direct`. pub fn draw(&self, render_pass: &mut wgpu::RenderPass) { let num_draw_calls = self.draw_count(); if num_draw_calls > 0 { @@ -362,16 +366,7 @@ impl DrawCalls { } DrawingStrategy::Direct => { log::trace!("drawing {num_draw_calls} renderlets using direct"); - for ir in self.internal_renderlets.iter() { - // UNWRAP: panic on purpose - if let Some(hr) = ir.inner.upgrade() { - let ir = hr.get(); - let vertex_range = 0..ir.get_vertex_count(); - let id = hr.id(); - let instance_range = id.inner()..id.inner() + 1; - render_pass.draw(vertex_range, instance_range); - } - } + self.draw_direct(render_pass); } } } diff --git a/crates/renderling/src/draw_indirect.rs b/crates/renderling/src/draw_indirect.rs deleted file mode 100644 index cad1e2bc..00000000 --- a/crates/renderling/src/draw_indirect.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crabslab::SlabItem; - -#[derive(Default, Debug, Clone, Copy, PartialEq, SlabItem)] -pub struct DrawIndirect { - pub vertex_count: u32, - pub instance_count: u32, - pub base_vertex: u32, - pub base_instance: u32, -} diff --git a/crates/renderling/src/geometry/cpu.rs b/crates/renderling/src/geometry/cpu.rs index 0c8adc31..0402c4aa 100644 --- a/crates/renderling/src/geometry/cpu.rs +++ b/crates/renderling/src/geometry/cpu.rs @@ -1,6 +1,8 @@ //! CPU side of the [super::geometry](geometry) module. //! +use std::sync::{Arc, Mutex}; + use craballoc::{ runtime::WgpuRuntime, slab::{SlabAllocator, SlabBuffer}, @@ -23,6 +25,11 @@ use crate::{ pub struct Geometry { slab: SlabAllocator, descriptor: Hybrid, + /// Holds the current camera just in case the user drops it, + /// this way we never lose a camera that is in use. Dropping + /// the camera would cause a blank screen, which is very confusing + /// =( + _camera: Arc>>>, } impl AsRef for Geometry { @@ -41,14 +48,17 @@ impl Geometry { // TODO: move atlas size into materials. pub fn new(runtime: impl AsRef, resolution: UVec2, atlas_size: UVec2) -> Self { let runtime = runtime.as_ref(); - let slab = - SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::empty(), Some("geometry")); + let slab = SlabAllocator::new(runtime, "geometry", wgpu::BufferUsages::empty()); let descriptor = slab.new_value(GeometryDescriptor { atlas_size, resolution, ..Default::default() }); - Self { slab, descriptor } + Self { + slab, + descriptor, + _camera: Default::default(), + } } pub fn runtime(&self) -> &WgpuRuntime { @@ -74,18 +84,18 @@ impl Geometry { pub fn new_camera(&self, camera: Camera) -> Hybrid { let c = self.slab.new_value(camera); if self.descriptor.get().camera_id.is_none() { - log::info!("automatically using camera: {:?}", c.id()); - self.descriptor.modify(|cfg| { - cfg.camera_id = c.id(); - }); + self.use_camera(&c); } c } /// Set all geometry to use the given camera. pub fn use_camera(&self, camera: impl AsRef>) { - self.descriptor - .modify(|cfg| cfg.camera_id = camera.as_ref().id()); + let c = camera.as_ref(); + log::info!("using camera: {:?}", c.id()); + // Save a clone so we never lose the active camera, even if the user drops it + *self._camera.lock().unwrap() = Some(c.clone()); + self.descriptor.modify(|cfg| cfg.camera_id = c.id()); } pub fn new_transform(&self, transform: Transform) -> Hybrid { diff --git a/crates/renderling/src/lib.rs b/crates/renderling/src/lib.rs index 89ab0b87..e64b3e9b 100644 --- a/crates/renderling/src/lib.rs +++ b/crates/renderling/src/lib.rs @@ -169,11 +169,14 @@ pub mod pbr; pub mod sdf; pub mod skybox; pub mod stage; +pub mod sync; #[cfg(cpu)] pub mod texture; pub mod tonemapping; pub mod transform; pub mod tuple; +#[cfg(feature = "ui")] +pub mod ui; #[cfg(cpu)] pub use context::*; @@ -215,17 +218,38 @@ mod test { use glam::{Mat3, Mat4, Quat, UVec2, Vec2, Vec3, Vec4}; use img_diff::DiffCfg; + use light::{AnalyticalLight, DirectionalLightDescriptor}; use pretty_assertions::assert_eq; + use stage::Stage; #[ctor::ctor] fn init_logging() { let _ = env_logger::builder().is_test(true).try_init(); + log::info!("logging is on"); } pub fn workspace_dir() -> std::path::PathBuf { std::path::PathBuf::from(std::env!("CARGO_WORKSPACE_DIR")) } + pub fn test_output_dir() -> std::path::PathBuf { + workspace_dir().join("test_output") + } + + pub fn make_two_directional_light_setup(stage: &Stage) -> (AnalyticalLight, AnalyticalLight) { + let sunlight_a = stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::new(-0.8, -1.0, 0.5).normalize(), + color: Vec4::ONE, + intensity: 100.0, + }); + let sunlight_b = stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::new(1.0, 1.0, -0.1).normalize(), + color: Vec4::ONE, + intensity: 10.0, + }); + (sunlight_a, sunlight_b) + } + #[allow(unused, reason = "Used in debugging on macos")] pub fn capture_gpu_frame( ctx: &Context, @@ -256,18 +280,12 @@ mod test { desc.set_destination(metal::MTLCaptureDestination::GpuTraceDocument); desc.set_output_url(path); - unsafe { - ctx.get_device() - .as_hal::(|maybe_metal_device| { - if let Some(metal_device) = maybe_metal_device { - desc.set_capture_device( - metal_device.raw_device().try_lock().unwrap().as_ref(), - ); - } else { - panic!("not a capturable device") - } - }) - }; + let maybe_metal_device = unsafe { ctx.get_device().as_hal::() }; + if let Some(metal_device) = maybe_metal_device { + desc.set_capture_device(metal_device.raw_device().try_lock().unwrap().as_ref()); + } else { + panic!("not a capturable device") + } m.start_capture(&desc).unwrap(); let t = f(); m.stop_capture(); @@ -324,7 +342,7 @@ mod test { frame.present(); let depth_texture = stage.get_depth_texture(); - let depth_img = depth_texture.read_image().unwrap(); + let depth_img = depth_texture.read_image().unwrap().unwrap(); img_diff::assert_img_eq("cmy_triangle/depth.png", depth_img); let hdr_img = stage @@ -776,37 +794,33 @@ mod test { let red = Vec3::X.extend(1.0); let green = Vec3::Y.extend(1.0); let blue = Vec3::Z.extend(1.0); - let dir_red = stage.new_analytical_light( - DirectionalLightDescriptor { - direction: Vec3::NEG_Y, - color: red, - intensity: 10.0, - }, - None, - ); - let _dir_green = stage.new_analytical_light( - DirectionalLightDescriptor { - direction: Vec3::NEG_X, - color: green, - intensity: 10.0, - }, - None, - ); - let _dir_blue = stage.new_analytical_light( - DirectionalLightDescriptor { - direction: Vec3::NEG_Z, - color: blue, - intensity: 10.0, - }, - None, - ); + let dir_red = stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::NEG_Y, + color: red, + intensity: 10.0, + }); + let _dir_green = stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::NEG_X, + color: green, + intensity: 10.0, + }); + let _dir_blue = stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::NEG_Z, + color: blue, + intensity: 10.0, + }); assert_eq!( Light { light_type: LightStyle::Directional, - index: dir_red.light_details.as_directional().unwrap().id().inner(), + index: dir_red + .light_details() + .as_directional() + .unwrap() + .id() + .inner(), ..Default::default() }, - Light::from(dir_red.light_details.as_directional().unwrap().id()) + Light::from(dir_red.light_details().as_directional().unwrap().id()) ); let _rez = stage @@ -829,7 +843,7 @@ mod test { stage.render(&frame.view()); let img = frame.read_image().unwrap(); let depth_texture = stage.get_depth_texture(); - let depth_img = depth_texture.read_image().unwrap(); + let depth_img = depth_texture.read_image().unwrap().unwrap(); img_diff::assert_img_eq("stage/cube_directional_depth.png", depth_img); img_diff::assert_img_eq("stage/cube_directional.png", img); } @@ -991,7 +1005,7 @@ mod test { stage.render(&frame.view()); let img = frame.read_image().unwrap(); assert_eq!(size, UVec2::new(img.width(), img.height())); - img_diff::save("stage/resize_100.png", img); + img_diff::assert_img_eq("stage/resize_100.png", img); frame.present(); let new_size = UVec2::new(200, 200); @@ -1002,7 +1016,37 @@ mod test { stage.render(&frame.view()); let img = frame.read_image().unwrap(); assert_eq!(new_size, UVec2::new(img.width(), img.height())); - img_diff::save("stage/resize_200.png", img); + img_diff::assert_img_eq("stage/resize_200.png", img); + frame.present(); + } + + #[test] + fn can_direct_draw_cube() { + let size = UVec2::new(100, 100); + let ctx = Context::headless(size.x, size.y).with_use_direct_draw(true); + let stage = ctx.new_stage(); + + // create the CMY cube + let camera_position = Vec3::new(0.0, 12.0, 20.0); + let _camera = stage.new_camera(Camera::new( + Mat4::perspective_rh(std::f32::consts::PI / 4.0, 1.0, 0.1, 100.0), + Mat4::look_at_rh(camera_position, Vec3::ZERO, Vec3::Y), + )); + let _rez = stage + .builder() + .with_vertices(gpu_cube_vertices()) + .with_transform(Transform { + scale: Vec3::new(6.0, 6.0, 6.0), + rotation: Quat::from_axis_angle(Vec3::Y, -std::f32::consts::FRAC_PI_4), + ..Default::default() + }) + .build(); + + let frame = ctx.get_next_frame().unwrap(); + stage.render(&frame.view()); + let img = frame.read_image().unwrap(); + assert_eq!(size, UVec2::new(img.width(), img.height())); + img_diff::assert_img_eq("stage/resize_100.png", img); frame.present(); } } diff --git a/crates/renderling/src/light.rs b/crates/renderling/src/light.rs index fdd9698b..34706fd2 100644 --- a/crates/renderling/src/light.rs +++ b/crates/renderling/src/light.rs @@ -3,14 +3,19 @@ //! Directional, point and spot lights. //! //! Shadow mapping. +//! +//! Tiling. use crabslab::{Array, Id, Slab, SlabItem}; -use glam::{Mat4, UVec2, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; -use spirv_std::spirv; +use glam::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; +#[cfg(gpu)] +use spirv_std::num_traits::Float; +use spirv_std::{spirv, Image}; use crate::{ atlas::{AtlasDescriptor, AtlasTexture}, cubemap::{CubemapDescriptor, CubemapFaceDirection}, - math::{IsSampler, IsVector, Sample2dArray}, + geometry::GeometryDescriptor, + math::{Fetch, IsSampler, IsVector, Sample2dArray}, stage::Renderlet, transform::Transform, }; @@ -25,6 +30,11 @@ mod shadow_map; #[cfg(cpu)] pub use shadow_map::*; +#[cfg(cpu)] +mod tiling; +#[cfg(cpu)] +pub use tiling::*; + /// Root descriptor of the lighting system. #[derive(Clone, Copy, Default, SlabItem, core::fmt::Debug)] #[offsets] @@ -40,6 +50,9 @@ pub struct LightingDescriptor { pub update_shadow_map_id: Id, /// The index of the shadow map atlas texture to update. pub update_shadow_map_texture_index: u32, + /// `Id` of the [`LightTilingDescriptor`] to use when performing + /// light tiling. + pub light_tiling_descriptor_id: Id, } #[derive(Clone, Copy, SlabItem, core::fmt::Debug)] @@ -207,14 +220,12 @@ impl SpotLightCalculation { node_transform: Mat4, fragment_world_position: Vec3, ) -> Self { - #[cfg(gpu)] - use spirv_std::num_traits::Float; - let light_position = node_transform.transform_point3(spot_light_descriptor.position); let frag_position = fragment_world_position; let frag_to_light = light_position - frag_position; let frag_to_light_distance = frag_to_light.length(); if frag_to_light_distance == 0.0 { + crate::println!("frag_to_light_distance: {frag_to_light_distance}"); return Self::default(); } let frag_to_light = frag_to_light.alt_norm_or_zero(); @@ -247,6 +258,15 @@ impl SpotLightCalculation { } } +/// Description of a spot light. +/// +/// ## Tips +/// +/// If your spotlight is not illuminating your scenery, ensure that the +/// `inner_cutoff` and `outer_cutoff` values are "correct". `outer_cutoff` +/// should be _greater than_ `inner_cutoff` and the values should be a large +/// enough to cover at least one pixel at the distance between the light and +/// the scenery. #[repr(C)] #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[derive(Copy, Clone, SlabItem)] @@ -262,8 +282,8 @@ pub struct SpotLightDescriptor { impl Default for SpotLightDescriptor { fn default() -> Self { let white = Vec4::splat(1.0); - let inner_cutoff = core::f32::consts::PI / 3.0; - let outer_cutoff = core::f32::consts::PI / 2.0; + let inner_cutoff = 0.077143565; + let outer_cutoff = 0.09075713; let direction = Vec3::new(0.0, -1.0, 0.0); let color = white; let intensity = 1.0; @@ -293,7 +313,8 @@ impl SpotLightDescriptor { .transform_vector3(self.direction) .alt_norm_or_zero(); let position = parent_light_transform.transform_point3(self.position); - let view = Mat4::look_to_rh(position, direction, Vec3::Z); + let up = direction.orthonormal_vectors()[0]; + let view = Mat4::look_to_rh(position, direction, up); (projection, view) } } @@ -334,6 +355,7 @@ impl DirectionalLightDescriptor { // Far limits of the light's reach z_far: f32, ) -> (Mat4, Mat4) { + crate::println!("descriptor: {self:#?}"); let depth = (z_far - z_near).abs(); let hd = depth * 0.5; let projection = Mat4::orthographic_rh(-hd, hd, -hd, hd, z_near, z_far); @@ -343,7 +365,8 @@ impl DirectionalLightDescriptor { let position = -direction * depth * 0.5; crate::println!("direction: {direction}"); crate::println!("position: {position}"); - let view = Mat4::look_to_rh(position, direction, Vec3::Z); + let up = direction.orthonormal_vectors()[0]; + let view = Mat4::look_to_rh(position, direction, up); (projection, view) } } @@ -354,6 +377,7 @@ impl DirectionalLightDescriptor { pub struct PointLightDescriptor { pub position: Vec3, pub color: Vec4, + /// Expressed as candelas. pub intensity: f32, } @@ -404,6 +428,20 @@ impl PointLightDescriptor { } } +/// Returns the radius of illumination in meters. +/// +/// * Moonlight: < 1 lux. +/// - Full moon on a clear night: 0.25 lux. +/// - Quarter moon: 0.01 lux +/// - Starlight overcast moonless night sky: 0.0001 lux. +/// * General indoor lighting: Around 100 to 300 lux. +/// * Office lighting: Typically around 300 to 500 lux. +/// * Reading or task lighting: Around 500 to 750 lux. +/// * Detailed work (e.g., drafting, surgery): 1000 lux or more. +pub fn radius_of_illumination(intensity_candelas: f32, minimum_illuminance_lux: f32) -> f32 { + (intensity_candelas / minimum_illuminance_lux).sqrt() +} + #[repr(u32)] #[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[derive(Copy, Clone, PartialEq)] @@ -413,6 +451,16 @@ pub enum LightStyle { Spot = 2, } +impl core::fmt::Display for LightStyle { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + LightStyle::Directional => f.write_str("directional"), + LightStyle::Point => f.write_str("point"), + LightStyle::Spot => f.write_str("spot"), + } + } +} + impl SlabItem for LightStyle { const SLAB_SIZE: usize = { 1 }; @@ -432,7 +480,7 @@ impl SlabItem for LightStyle { } } -/// A type-erased/generic light that is used as a slab pointer to a +/// A generic light that is used as a slab pointer to a /// specific light type. // TODO: rename to `LightDescriptor` #[repr(C)] @@ -637,7 +685,7 @@ impl ShadowCalculation { crate::println!("closest_depth: {shadow_map_depth}"); let bias = (bias_max * (1.0 - surface_normal.dot(*light_direction))).max(*bias_min); - if (fragment_depth - bias) > shadow_map_depth { + if (fragment_depth - bias) >= shadow_map_depth { shadow += 1.0 } total += 1.0; @@ -727,8 +775,473 @@ impl ShadowCalculation { } } +/// Depth pre-pass for the light tiling feature. +/// +/// This shader writes all staged [`Renderlet`]'s depth into a buffer. +/// +/// This shader is very much like [`shadow_mapping_vertex`], except that +/// shader gets its projection+view matrix from the light stored in a +/// `ShadowMapDescriptor`. +/// +/// Here we want to render as normal forward pass would, with the `Renderlet`'s view +/// and the [`Camera`]'s projection. +/// +/// ## Note +/// This shader will likely be expanded to include parts of occlusion culling and order +/// independent transparency. +#[spirv(vertex)] +pub fn light_tiling_depth_pre_pass( + // Points at a `Renderlet`. + #[spirv(instance_index)] renderlet_id: Id, + // Which vertex within the renderlet are we rendering? + #[spirv(vertex_index)] vertex_index: u32, + // The slab where the renderlet's geometry is staged + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32], + // Output clip coords + #[spirv(position)] out_clip_pos: &mut Vec4, +) { + let renderlet = geometry_slab.read_unchecked(renderlet_id); + if !renderlet.visible { + // put it outside the clipping frustum + *out_clip_pos = Vec3::splat(100.0).extend(1.0); + return; + } + + let camera_id = geometry_slab + .read_unchecked(Id::::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID); + let camera = geometry_slab.read_unchecked(camera_id); + + let (_vertex, _transform, _model_matrix, world_pos) = + renderlet.get_vertex_info(vertex_index, geometry_slab); + + *out_clip_pos = camera.view_projection() * world_pos.extend(1.0); +} + +pub type DepthImage2d = Image!(2D, type=f32, sampled, depth); + +pub type DepthImage2dMultisampled = Image!(2D, type=f32, sampled, depth, multisampled=true); + +/// A tile of screen space used to cull lights. +#[derive(Clone, Copy, Default, SlabItem)] +#[offsets] +pub struct LightTile { + /// Minimum depth of objects found within the frustum of the tile. + pub depth_min: u32, + /// Maximum depth of objects foudn within the frustum of the tile. + pub depth_max: u32, + /// The count of lights in this tile. + /// + /// Also, the next available light index. + pub next_light_index: u32, + /// List of light ids that intersect this tile's frustum. + pub lights_array: Array>, +} + +impl core::fmt::Debug for LightTile { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("LightTile") + .field("depth_min", &dequantize_depth_u32_to_f32(self.depth_min)) + .field("depth_max", &dequantize_depth_u32_to_f32(self.depth_max)) + .field("next_light_index", &self.next_light_index) + .field("lights_array", &self.lights_array) + .finish() + } +} + +/// Descriptor of the light tiling operation, which culls lights by accumulating +/// them into lists that illuminate tiles of the screen. +#[derive(Clone, Copy, SlabItem, core::fmt::Debug)] +pub struct LightTilingDescriptor { + /// Size of the [`Stage`]'s depth texture. + pub depth_texture_size: UVec2, + /// Configurable tile size. + pub tile_size: u32, + /// Array pointing to the lighting "tiles". + pub tiles_array: Array, + /// Minimum illuminance. + /// + /// Used to determine whether a light illuminates a tile. + pub minimum_illuminance_lux: f32, +} + +impl Default for LightTilingDescriptor { + fn default() -> Self { + Self { + depth_texture_size: Default::default(), + tile_size: 16, + tiles_array: Default::default(), + minimum_illuminance_lux: 0.1, + } + } +} + +impl LightTilingDescriptor { + /// Returns the dimensions of the grid of tiles. + pub fn tile_grid_size(&self) -> UVec2 { + let dims_f32 = self.depth_texture_size.as_vec2() / self.tile_size as f32; + dims_f32.ceil().as_uvec2() + } + + pub fn tile_coord_for_fragment(&self, frag_coord: Vec2) -> UVec2 { + let frag_coord = frag_coord.as_uvec2(); + frag_coord / self.tile_size + } + + pub fn tile_index_for_fragment(&self, frag_coord: Vec2) -> usize { + let tile_coord = self.tile_coord_for_fragment(frag_coord); + let tile_dimensions = self.tile_grid_size(); + (tile_coord.y * tile_dimensions.x + tile_coord.x) as usize + } +} + +/// Quantizes a fragment depth from `f32` to `u32`. +pub fn quantize_depth_f32_to_u32(depth: f32) -> u32 { + (u32::MAX as f32 * depth).round() as u32 +} + +/// Reconstructs a previously quantized depth from a `u32`. +pub fn dequantize_depth_u32_to_f32(depth: u32) -> f32 { + depth as f32 / u32::MAX as f32 +} + +/// Helper for determining the next light to check during an +/// invocation of the light list computation. +struct NextLightIndex { + current_step: usize, + tile_size: u32, + lights: Array>, + global_id: UVec3, +} + +impl Iterator for NextLightIndex { + type Item = Id>; + + fn next(&mut self) -> Option { + let next_index = self.next_index(); + self.current_step += 1; + if next_index < self.lights.len() { + Some(self.lights.at(next_index)) + } else { + None + } + } +} + +impl NextLightIndex { + pub fn new( + global_id: UVec3, + tile_size: u32, + analytical_lights_array: Array>, + ) -> Self { + Self { + current_step: 0, + tile_size, + lights: analytical_lights_array, + global_id, + } + } + + pub fn next_index(&self) -> usize { + // Determine the xy coord of this invocation within the _tile_ + let frag_tile_xy = self.global_id.xy() % self.tile_size; + // Determine the index of this invocation within the _tile_ + let offset = frag_tile_xy.y * self.tile_size + frag_tile_xy.x; + let stride = (self.tile_size * self.tile_size) as usize; + self.current_step * stride + offset as usize + } +} + +struct LightTilingInvocation { + global_id: UVec3, + descriptor: LightTilingDescriptor, +} + +impl LightTilingInvocation { + fn new(global_id: UVec3, descriptor: LightTilingDescriptor) -> Self { + Self { + global_id, + descriptor, + } + } + + /// The fragment's position. + /// + /// X range is 0 to (width - 1), Y range is 0 to (height - 1). + fn frag_pos(&self) -> UVec2 { + self.global_id.xy() + } + + /// The number of tiles in X and Y within the depth texture. + fn tile_grid_size(&self) -> UVec2 { + self.descriptor.tile_grid_size() + } + + /// The tile's coordinate among all tiles in the tile grid. + /// + /// The units are in tile x y. + fn tile_coord(&self) -> UVec2 { + self.global_id.xy() / self.descriptor.tile_size + } + + /// The tile's index in all the [`LightTilingDescriptor`]'s `tile_array`. + fn tile_index(&self) -> usize { + let tile_pos = self.tile_coord(); + let tile_dimensions = self.tile_grid_size(); + (tile_pos.y * tile_dimensions.x + tile_pos.x) as usize + } + + /// The tile's normalized min and max. + fn tile_ndc_min_max(&self) -> (Vec2, Vec2) { + let grid_size = self.tile_grid_size(); + let min_coord = self.tile_coord().as_vec2(); + let max_coord = min_coord + 1.0; + let min = crate::math::convert_pixel_to_ndc(min_coord, grid_size); + let max = crate::math::convert_pixel_to_ndc(max_coord, grid_size); + (min, max) + } + + /// The tile's normalized midpoint. + fn tile_ndc_midpoint(&self) -> Vec2 { + let min_coord = self.tile_coord().as_vec2(); + let mid_coord = min_coord + 0.5; + crate::math::convert_pixel_to_ndc(mid_coord, self.tile_grid_size()) + } + + /// Compute the min and max depth of one fragment/invocation for light tiling. + /// + /// The min and max is stored in a tile on lighting slab. + fn compute_min_and_max_depth( + &self, + depth_texture: &impl Fetch, + lighting_slab: &mut [u32], + ) { + let frag_pos = self.frag_pos(); + // Depth frag value at the fragment position + let frag_depth: f32 = depth_texture.fetch(frag_pos).x; + // Fragment depth scaled to min/max of u32 values + // + // This is so we can compare with normal atomic ops instead of using the float extension + let frag_depth_u32 = quantize_depth_f32_to_u32(frag_depth); + + // The tile's index in all the tiles + let tile_index = self.tile_index(); + let lighting_desc = lighting_slab.read_unchecked(Id::::new(0)); + let tiling_desc = lighting_slab.read_unchecked(lighting_desc.light_tiling_descriptor_id); + // index of the tile's min depth atomic value in the lighting slab + let tile_id = tiling_desc.tiles_array.at(tile_index); + let min_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MIN; + // index of the tile's max depth atomic value in the lighting slab + let max_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MAX; + + let _prev_min_depth = crate::sync::atomic_u_min::< + { spirv_std::memory::Scope::Workgroup as u32 }, + { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() }, + >(lighting_slab, min_depth_index, frag_depth_u32); + let _prev_max_depth = crate::sync::atomic_u_max::< + { spirv_std::memory::Scope::Workgroup as u32 }, + { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() }, + >(lighting_slab, max_depth_index, frag_depth_u32); + } + + /// Determine whether this invocation should run. + fn should_invoke(&self) -> bool { + self.global_id.x < self.descriptor.depth_texture_size.x + && self.global_id.y < self.descriptor.depth_texture_size.y + } + + /// Clears one tile. + /// + /// ## Note + /// This is only valid to call from the [`light_tiling_clear_tiles`] shader. + fn clear_tile(&self, lighting_slab: &mut [u32]) { + let dimensions = self.tile_grid_size(); + let index = (self.global_id.y * dimensions.x + self.global_id.x) as usize; + if index < self.descriptor.tiles_array.len() { + let tile_id = self.descriptor.tiles_array.at(index); + let mut tile = lighting_slab.read(tile_id); + tile.depth_min = u32::MAX; + tile.depth_max = 0; + tile.next_light_index = 0; + lighting_slab.write(tile_id, &tile); + // Zero out the light list and the ratings + for id in tile.lights_array.iter() { + lighting_slab.write(id, &Id::NONE); + } + } + } + + // The difficulty here is that in SPIRV we can access `lighting_slab` atomically without wrapping it + // in a type, but on CPU we must pass an array of (something like) `AtomicU32`. I'm not sure how to + // model this interaction to test it on the CPU. + fn compute_light_lists(&self, geometry_slab: &[u32], lighting_slab: &mut [u32]) { + let index = self.tile_index(); + let tile_id = self.descriptor.tiles_array.at(index); + // Construct the tile's frustum in clip space. + let depth_min_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MIN); + let depth_max_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MAX); + let depth_min = dequantize_depth_u32_to_f32(depth_min_u32); + let depth_max = dequantize_depth_u32_to_f32(depth_max_u32); + + if depth_min == depth_max { + // If we would construct a frustum with zero volume, abort. + // + // See + // for more info. + return; + } + + let camera_id = geometry_slab.read_unchecked( + Id::::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID, + ); + let camera = geometry_slab.read_unchecked(camera_id); + + // let (ndc_tile_min, ndc_tile_max) = self.tile_ndc_min_max(); + // // This is the AABB frustum, in NDC coords + // let ndc_tile_aabb = Aabb::new( + // ndc_tile_min.extend(depth_min), + // ndc_tile_max.extend(depth_max), + // ); + + // Get the frustum (here simplified to a line) in world coords, since we'll be + // using it to compare against the radius of illumination of each light + let tile_ndc_midpoint = self.tile_ndc_midpoint(); + let tile_line_ndc = ( + tile_ndc_midpoint.extend(depth_min), + tile_ndc_midpoint.extend(depth_max), + ); + let inverse_viewproj = camera.view_projection().inverse(); + let tile_line = ( + inverse_viewproj.project_point3(tile_line_ndc.0), + inverse_viewproj.project_point3(tile_line_ndc.1), + ); + + let tile_index = self.tile_index(); + let tile_id = self.descriptor.tiles_array.at(tile_index); + let tile_lights_array = lighting_slab.read(tile_id + LightTile::OFFSET_OF_LIGHTS_ARRAY); + let next_light_id = tile_id + LightTile::OFFSET_OF_NEXT_LIGHT_INDEX; + + // List of all analytical lights in the scene + let analytical_lights_array = lighting_slab.read_unchecked( + Id::::new(0) + + LightingDescriptor::OFFSET_OF_ANALYTICAL_LIGHTS_ARRAY, + ); + + // Each invocation will calculate a few lights' contribution to the tile, until all lights + // have been visited + let next_light = NextLightIndex::new( + self.global_id, + self.descriptor.tile_size, + analytical_lights_array, + ); + for id_of_light_id in next_light { + let light_id = lighting_slab.read_unchecked(id_of_light_id); + let light = lighting_slab.read_unchecked(light_id); + let transform = lighting_slab.read(light.transform_id); + // Get the distance to the light in world coords, and the + // intensity of the light. + let (distance, intensity_candelas) = match light.light_type { + LightStyle::Directional => { + let directional_light = lighting_slab.read(light.into_directional_id()); + (0.0, directional_light.intensity) + } + LightStyle::Point => { + let point_light = lighting_slab.read(light.into_point_id()); + let center = Mat4::from(transform).transform_point3(point_light.position); + let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1); + (distance, point_light.intensity) + } + LightStyle::Spot => { + // TODO: take into consideration the direction the spot light is pointing + let spot_light = lighting_slab.read(light.into_spot_id()); + let center = Mat4::from(transform).transform_point3(spot_light.position); + let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1); + (distance, spot_light.intensity) + } + }; + + let radius = + radius_of_illumination(intensity_candelas, self.descriptor.minimum_illuminance_lux); + let should_add = radius >= distance; + if should_add { + // If the light should be added to the bin, get the next available index in the bin, + // then write the id of the light into that index. + let next_index = crate::sync::atomic_i_increment::< + { spirv_std::memory::Scope::Workgroup as u32 }, + { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() }, + >(lighting_slab, next_light_id); + if next_index as usize >= tile_lights_array.len() { + // We've already filled the bin, so abort. + // + // TODO: Figure out a better way to handle light tile list overrun. + break; + } else { + // Get the id that corresponds to the next available index in the ratings bin + let binned_light_id = tile_lights_array.at(next_index as usize); + // Write to that location + lighting_slab.write(binned_light_id, &light_id); + } + } + } + } +} + +#[spirv(compute(threads(16, 16, 1)))] +pub fn light_tiling_clear_tiles( + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32], + #[spirv(global_invocation_id)] global_id: UVec3, +) { + let lighting_descriptor = lighting_slab.read(Id::::new(0)); + let light_tiling_descriptor = + lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id); + let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor); + invocation.clear_tile(lighting_slab); +} + +#[spirv(compute(threads(16, 16, 1)))] +pub fn light_tiling_compute_tile_min_and_max_depth( + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32], + #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2d, + #[spirv(global_invocation_id)] global_id: UVec3, +) { + let lighting_descriptor = lighting_slab.read(Id::::new(0)); + let light_tiling_descriptor = + lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id); + let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor); + invocation.compute_min_and_max_depth(depth_texture, lighting_slab); +} + +#[spirv(compute(threads(16, 16, 1)))] +pub fn light_tiling_compute_tile_min_and_max_depth_multisampled( + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32], + #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2dMultisampled, + #[spirv(global_invocation_id)] global_id: UVec3, +) { + let lighting_descriptor = lighting_slab.read(Id::::new(0)); + let light_tiling_descriptor = + lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id); + let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor); + invocation.compute_min_and_max_depth(depth_texture, lighting_slab); +} + +#[spirv(compute(threads(16, 16, 1)))] +pub fn light_tiling_bin_lights( + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32], + #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32], + #[spirv(global_invocation_id)] global_id: UVec3, +) { + let lighting_descriptor = lighting_slab.read(Id::::new(0)); + let light_tiling_descriptor = + lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id); + let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor); + if invocation.should_invoke() { + invocation.compute_light_lists(geometry_slab, lighting_slab); + } +} + #[cfg(test)] mod test { + use crate::math::GpuRng; + use super::*; #[cfg(feature = "gltf")] @@ -790,4 +1303,121 @@ mod test { "should be outside" ); } + + #[test] + fn finding_orthogonal_vectors_sanity() { + const THRESHOLD: f32 = f32::EPSILON * 3.0; + + let mut prng = GpuRng::new(0); + for _ in 0..100 { + let v2 = prng.gen_vec2(Vec2::splat(-100.0), Vec2::splat(100.0)); + let v2_ortho = v2.orthonormal_vectors(); + let v2_dot = v2.dot(v2_ortho); + if v2_dot.abs() >= THRESHOLD { + panic!("{v2} • {v2_ortho} < {THRESHOLD}, saw {v2_dot}") + } + + let v3 = prng + .gen_vec3(Vec3::splat(-100.0), Vec3::splat(100.0)) + .alt_norm_or_zero(); + for v3_ortho in v3.orthonormal_vectors() { + let v3_dot = v3.dot(v3_ortho); + if v3_dot.abs() >= THRESHOLD { + panic!("{v3} • {v3_ortho} < {THRESHOLD}, saw {v3_dot}"); + } + } + } + } + + #[test] + fn next_light_sanity() { + { + let lights_array = Array::new(0, 1); + // When there's only one light we only need one invocation to check that one light + // (per tile) + let mut next_light = NextLightIndex::new(UVec3::new(0, 0, 0), 16, lights_array); + assert_eq!(Some(0u32.into()), next_light.next()); + assert_eq!(None, next_light.next()); + // The next invocation won't check anything + let mut next_light = NextLightIndex::new(UVec3::new(1, 0, 0), 16, lights_array); + assert_eq!(None, next_light.next()); + // Neither will the next row + let mut next_light = NextLightIndex::new(UVec3::new(0, 1, 0), 16, lights_array); + assert_eq!(None, next_light.next()); + } + { + let lights_array = Array::new(0, 2); + // When there's two lights we need two invocations + let mut next_light = NextLightIndex::new(UVec3::new(0, 0, 0), 16, lights_array); + assert_eq!(Some(0u32.into()), next_light.next()); + assert_eq!(None, next_light.next()); + // The next invocation checks the second light + let mut next_light = NextLightIndex::new(UVec3::new(1, 0, 0), 16, lights_array); + assert_eq!(Some(1u32.into()), next_light.next()); + assert_eq!(None, next_light.next()); + // The next one doesn't check anything + let mut next_light = NextLightIndex::new(UVec3::new(2, 0, 0), 16, lights_array); + assert_eq!(None, next_light.next()); + } + { + // With 256 lights (16*16), each fragment in the tile checks exactly one light + let lights_array = Array::new(0, 16 * 16); + let mut checked_lights = vec![]; + for y in 0..16 { + for x in 0..16 { + let mut next_light = NextLightIndex::new(UVec3::new(x, y, 0), 16, lights_array); + let next_index = next_light.next_index(); + let checked_light = next_light.next().unwrap(); + assert_eq!(next_index, checked_light.index()); + checked_lights.push(checked_light); + assert_eq!(None, next_light.next()); + } + } + println!("checked_lights: {checked_lights:#?}"); + assert_eq!(256, checked_lights.len()); + } + } + + #[test] + fn frag_coord_to_tile_index() { + let tiling_desc = LightTilingDescriptor { + depth_texture_size: UVec2::new(1024, 800), + ..Default::default() + }; + for x in 0..16 { + let index = tiling_desc.tile_index_for_fragment(Vec2::new(x as f32, 0.0)); + assert_eq!(0, index); + } + let index = tiling_desc.tile_index_for_fragment(Vec2::new(16.0, 0.0)); + assert_eq!(1, index); + let index = tiling_desc.tile_index_for_fragment(Vec2::new(0.0, 16.0)); + assert_eq!(1024 / 16, index); + + let tiling_desc = LightTilingDescriptor { + depth_texture_size: UVec2::new( + (10.0 * 2.0f32.powi(8)) as u32, + (9.0 * 2.0f32.powi(8)) as u32, + ), + ..Default::default() + }; + let frag_coord = Vec2::new(1917.0, 979.0); + let tile_coord = tiling_desc.tile_coord_for_fragment(frag_coord); + assert_eq!(UVec2::new(119, 61), tile_coord); + } + + #[test] + fn light_tiling_invocation_tile_aabb() { + let invocation = LightTilingInvocation { + global_id: UVec3::new(446, 1342, 1), + descriptor: LightTilingDescriptor { + depth_texture_size: UVec2::new(2560, 2304), + ..Default::default() + }, + }; + let (min, max) = invocation.tile_ndc_min_max(); + println!("min: {min}"); + println!("max: {max}"); + assert!(min.x < 0.0, "min: {min}"); + assert!(min.y < 0.0, "min: {min}"); + } } diff --git a/crates/renderling/src/light/cpu.rs b/crates/renderling/src/light/cpu.rs index b8d5991e..4fdbfb09 100644 --- a/crates/renderling/src/light/cpu.rs +++ b/crates/renderling/src/light/cpu.rs @@ -1,6 +1,5 @@ //! CPU-only lighting and shadows. - -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, RwLock, RwLockReadGuard}; use craballoc::{ prelude::{Hybrid, SlabAllocator, WgpuRuntime}, @@ -14,6 +13,7 @@ use snafu::prelude::*; use crate::{ atlas::{Atlas, AtlasBlitter, AtlasError}, geometry::Geometry, + prelude::Transform, stage::NestedTransform, }; @@ -32,6 +32,9 @@ pub enum LightingError { #[snafu(display("AnalyticalLightBundle attached to this ShadowMap was dropped"))] DroppedAnalyticalLightBundle, + + #[snafu(display("Driver poll error: {source}"))] + Poll { source: wgpu::PollError }, } impl From for LightingError { @@ -122,15 +125,42 @@ impl LightDetails { /// /// Create an `AnalyticalLightBundle` with the `Lighting::new_analytical_light`, /// or from `Stage::new_analytical_light`. -pub struct AnalyticalLightBundle { - pub light: Ct::Container, - pub light_details: LightDetails, - pub transform: NestedTransform, +pub struct AnalyticalLight { + /// The generic light descriptor. + light: Ct::Container, + /// The specific light descriptor. + light_details: LightDetails, + /// The light's global transform. + /// + /// This value lives in the lighting slab. + transform: Ct::Container, + /// The light's nested transform. + /// + /// This value comes from the light's node, if it belongs to one. + /// This may have been set if this light originated from a GLTF file. + /// This value lives on the geometry slab and must be referenced here + /// to keep the two in sync, which is required to animate lights. + node_transform: Arc>>>, } -impl Clone for AnalyticalLightBundle +impl core::fmt::Display for AnalyticalLight { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_fmt(format_args!( + "AnalyticalLightBundle type={} light-id={:?} node-nested-transform-global-id:{:?}", + self.light_details.style(), + self.light.id(), + self.node_transform.read().unwrap().as_ref().and_then(|wh| { + let h: NestedTransform = wh.upgrade()?; + Some(h.global_transform_id()) + }) + )) + } +} + +impl Clone for AnalyticalLight where Ct::Container: Clone, + Ct::Container: Clone, LightDetails: Clone, NestedTransform: Clone, { @@ -139,31 +169,34 @@ where light: self.light.clone(), light_details: self.light_details.clone(), transform: self.transform.clone(), + node_transform: self.node_transform.clone(), } } } -impl AnalyticalLightBundle { - pub(crate) fn from_hybrid(light: &AnalyticalLightBundle) -> Self { - AnalyticalLightBundle { +impl AnalyticalLight { + pub(crate) fn from_hybrid(light: &AnalyticalLight) -> Self { + AnalyticalLight { light: WeakHybrid::from_hybrid(&light.light), light_details: LightDetails::from_hybrid(&light.light_details), - transform: NestedTransform::from_hybrid(&light.transform), + transform: WeakHybrid::from_hybrid(&light.transform), + node_transform: light.node_transform.clone(), } } - pub(crate) fn upgrade(&self) -> Option { - Some(AnalyticalLightBundle { + pub(crate) fn upgrade(&self) -> Option { + Some(AnalyticalLight { light: self.light.upgrade()?, light_details: self.light_details.upgrade()?, transform: self.transform.upgrade()?, + node_transform: self.node_transform.clone(), }) } } -impl AnalyticalLightBundle { - pub fn weak(&self) -> AnalyticalLightBundle { - AnalyticalLightBundle::from_hybrid(self) +impl AnalyticalLight { + pub fn weak(&self) -> AnalyticalLight { + AnalyticalLight::from_hybrid(self) } pub fn light_space_transforms(&self, z_near: f32, z_far: f32) -> Vec { @@ -192,6 +225,62 @@ impl AnalyticalLightBundle { } } +impl AnalyticalLight { + /// Link this light to a node's `NestedTransform`. + pub fn link_node_transform(&self, transform: &NestedTransform) { + *self.node_transform.write().unwrap() = + Some(NestedTransform::::from_hybrid(transform)); + } + + /// Get a reference to the generic light descriptor. + pub fn light(&self) -> &Ct::Container { + &self.light + } + + /// Get a reference to the specific light descriptor. + pub fn light_details(&self) -> &LightDetails { + &self.light_details + } + + /// Get a reference to the light's global transform. + /// + /// This value lives in the lighting slab. + /// + /// ## Note + /// If a `NestedTransform` has been linked to this light by using [`Self::link_node_transform`], + /// the transform returned by this function may be overwritten at any point by the given + /// `NestedTransform`. + pub fn transform(&self) -> &Ct::Container { + &self.transform + } + + /// Get a reference to the light's linked global node transform. + /// + /// ## Note + /// The returned transform, if any, is the global transform of a linked `NestedTransform`. + /// To change this value, you should do so through the `NestedTransform`, which is likely + /// held in the + pub fn linked_node_transform(&self) -> Option { + let guard = self.node_transform.read().unwrap(); + let weak = guard.as_ref()?; + weak.upgrade() + } +} + +struct AnalyticalLightIterator<'a> { + inner: RwLockReadGuard<'a, Vec>>, + index: usize, +} + +impl Iterator for AnalyticalLightIterator<'_> { + type Item = AnalyticalLight; + + fn next(&mut self) -> Option { + let item = self.inner.get(self.index)?; + item.upgrade() + } +} + /// Manages lighting for an entire scene. #[derive(Clone)] pub struct Lighting { @@ -200,8 +289,8 @@ pub struct Lighting { pub(crate) light_slab_buffer: Arc>>, pub(crate) geometry_slab_buffer: Arc>>, pub(crate) lighting_descriptor: Hybrid, - pub(crate) analytical_lights: Arc>>>, - pub(crate) analytical_lights_array: Arc>>>, + pub(crate) analytical_lights: Arc>>>, + pub(crate) analytical_lights_array: Arc>>>>, pub(crate) shadow_map_update_pipeline: Arc, pub(crate) shadow_map_update_bindgroup_layout: Arc, pub(crate) shadow_map_update_blitter: AtlasBlitter, @@ -248,8 +337,6 @@ impl LightingBindGroupLayoutEntries { } impl Lighting { - const LABEL: Option<&str> = Some("lighting"); - /// Create the atlas used to store all shadow maps. fn create_shadow_map_atlas( light_slab: &SlabAllocator, @@ -267,23 +354,10 @@ impl Lighting { ) } - fn create_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { - let LightingBindGroupLayoutEntries { - light_slab, - shadow_map_image, - shadow_map_sampler, - } = LightingBindGroupLayoutEntries::new(0); - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Self::LABEL, - entries: &[light_slab, shadow_map_image, shadow_map_sampler], - }) - } - /// Create a new [`Lighting`] manager. - pub fn new(geometry: &Geometry) -> Self { + pub fn new(atlas_size: wgpu::Extent3d, geometry: &Geometry) -> Self { let runtime = geometry.runtime(); - let light_slab = - SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::empty(), Some("light-slab")); + let light_slab = SlabAllocator::new(runtime, "light-slab", wgpu::BufferUsages::empty()); let lighting_descriptor = light_slab.new_value(LightingDescriptor::default()); let light_slab_buffer = light_slab.commit(); let shadow_map_update_bindgroup_layout: Arc<_> = @@ -292,17 +366,9 @@ impl Lighting { ShadowMap::create_update_pipeline(&runtime.device, &shadow_map_update_bindgroup_layout) .into(); Self { - shadow_map_atlas: Self::create_shadow_map_atlas( - &light_slab, - // TODO: make the shadow map atlas size configurable - wgpu::Extent3d { - width: 1024, - height: 1024, - depth_or_array_layers: 4, - }, - ), + shadow_map_atlas: Self::create_shadow_map_atlas(&light_slab, atlas_size), analytical_lights: Default::default(), - analytical_lights_array: Arc::new(Mutex::new(light_slab.new_array([]))), + analytical_lights_array: Default::default(), geometry_slab: geometry.slab_allocator().clone(), light_slab, light_slab_buffer: Arc::new(RwLock::new(light_slab_buffer)), @@ -328,22 +394,19 @@ impl Lighting { /// /// This can be used to add the light back to the scene after using /// [`Lighting::remove_light`]. - pub fn add_light(&self, bundle: &AnalyticalLightBundle) { - { - // Update the array of light ids - // UNWRAP: POP - let mut analytical_lights_array_guard = self.analytical_lights_array.lock().unwrap(); - let mut analytical_light_ids_vec = analytical_lights_array_guard.get_vec(); - analytical_light_ids_vec.push(bundle.light.id()); - *analytical_lights_array_guard = self.light_slab.new_array(analytical_light_ids_vec); - } - { - // Update our list of weakly ref'd light bundles - self.analytical_lights - .lock() - .unwrap() - .push(AnalyticalLightBundle::::from_hybrid(bundle)); - } + pub fn add_light(&self, bundle: &AnalyticalLight) { + log::trace!( + "adding light {:?} ({})", + bundle.light.id(), + bundle.light_details.style() + ); + // Update our list of weakly ref'd light bundles + self.analytical_lights + .write() + .unwrap() + .push(AnalyticalLight::::from_hybrid(bundle)); + // Invalidate the array of lights + *self.analytical_lights_array.write().unwrap() = None; } /// Remove an [`AnalyticalLightBundle`] from the internal list of lights. @@ -351,16 +414,22 @@ impl Lighting { /// Use this to exclude a light from rendering, without dropping the light. /// /// After calling this function you can include the light again using [`Lighting::add_light`]. - pub fn remove_light(&self, bundle: &AnalyticalLightBundle) { - let ids = { - let mut guard = self.analytical_lights.lock().unwrap(); - guard.retain(|stored_light| stored_light.light.id() != bundle.light.id()); - guard - .iter() - .map(|stored_light| stored_light.light.id()) - .collect::>() - }; - *self.analytical_lights_array.lock().unwrap() = self.light_slab.new_array(ids); + pub fn remove_light(&self, bundle: &AnalyticalLight) { + log::trace!( + "removing light {:?} ({})", + bundle.light.id(), + bundle.light_details.style() + ); + // Remove the light from the list of weakly ref'd light bundles + let mut guard = self.analytical_lights.write().unwrap(); + guard.retain(|stored_light| stored_light.light.id() != bundle.light.id()); + *self.analytical_lights_array.write().unwrap() = None; + } + + /// Return an iterator over all lights. + pub fn lights(&self) -> impl Iterator + '_ { + let inner = self.analytical_lights.read().unwrap(); + AnalyticalLightIterator { inner, index: 0 } } /// Create a new [`AnalyticalLightBundle`] for the given descriptor `T`. @@ -369,29 +438,31 @@ impl Lighting { /// - [`DirectionalLightDescriptor`] /// - [`SpotLightDescriptor`] /// - [`PointLightDescriptor`] - pub fn new_analytical_light( - &self, - light_descriptor: T, - nested_transform: Option, - ) -> AnalyticalLightBundle + pub fn new_analytical_light(&self, light_descriptor: T) -> AnalyticalLight where T: Clone + Copy + SlabItem + Send + Sync, Light: From>, LightDetails: From>, { - let transform = nested_transform.unwrap_or_else(|| NestedTransform::new(&self.light_slab)); + let transform = self.light_slab.new_value(Transform::default()); let light_inner = self.light_slab.new_value(light_descriptor); let light = self.light_slab.new_value({ let mut light = Light::from(light_inner.id()); - light.transform_id = transform.global_transform_id(); + light.transform_id = transform.id(); light }); let light_details = LightDetails::from(light_inner); - let bundle = AnalyticalLightBundle { + let bundle = AnalyticalLight { light, light_details, transform, + node_transform: Default::default(), }; + log::trace!( + "created light {:?} ({})", + bundle.light.id(), + bundle.light_details.style() + ); self.add_light(&bundle); @@ -402,7 +473,7 @@ impl Lighting { /// a new [`ShadowMap`]. pub fn new_shadow_map( &self, - analytical_light_bundle: &AnalyticalLightBundle, + analytical_light_bundle: &AnalyticalLight, // Size of the shadow map size: UVec2, // Distance to the near plane of the shadow map's frustum. @@ -419,172 +490,84 @@ impl Lighting { #[must_use] pub fn commit(&self) -> SlabBuffer { - { - // Drop any analytical lights that don't have external references, - // and update our lights array. - let mut guard = self.analytical_lights.lock().unwrap(); - let mut changed = false; - guard.retain(|light_bundle| { + log::trace!("committing lights"); + let lights_array = { + let mut lights_guard = self.analytical_lights.write().unwrap(); + // Update the list of analytical lights to only reference lights that are still + // held somewhere in the outside program. + let mut analytical_lights_dropped = false; + lights_guard.retain_mut(|light_bundle| { let has_refs = light_bundle.light.has_external_references(); - changed = changed || !has_refs; + if has_refs { + let mut node_transform_guard = light_bundle.node_transform.write().unwrap(); + // References to this light still exist, so we'll check to see + // if we need to update the values of linked node transforms. + if let Some(weak_node_transform) = node_transform_guard.take() { + if let Some(node_transform) = weak_node_transform.upgrade() { + // If we can upgrade the node transform, something is holding onto + // it and may updated it in the future, so put it back. + *node_transform_guard = Some(weak_node_transform); + // Get on with checking the update. + let node_global_transform_value = node_transform.get_global_transform(); + // UNWRAP: Safe because we checked that the light has external references + let light_global_transform = light_bundle.transform.upgrade().unwrap(); + let global_transform_value = light_global_transform.get(); + if global_transform_value != node_global_transform_value { + // TODO: write a test that animates a light using GLTF to ensure + // that this is working correctly + light_global_transform.set(node_global_transform_value); + } + } + } + } + analytical_lights_dropped = analytical_lights_dropped || !has_refs; has_refs }); - if changed { - *self.analytical_lights_array.lock().unwrap() = self - .light_slab - .new_array(guard.iter().map(|bundle| bundle.light.id())); - } - } - self.lighting_descriptor.set(LightingDescriptor { - analytical_lights_array: self.analytical_lights_array.lock().unwrap().array(), - shadow_map_atlas_descriptor_id: self.shadow_map_atlas.descriptor_id(), - update_shadow_map_id: Id::NONE, - update_shadow_map_texture_index: 0, - }); - self.light_slab.commit() - } -} - -#[cfg(test)] -mod test { - - use glam::Vec3; - use crate::{light::SpotLightCalculation, prelude::Transform}; - - use super::*; + // If lights have been dropped, invalidate the array + let mut array_guard = self.analytical_lights_array.write().unwrap(); + if analytical_lights_dropped { + array_guard.take(); + } - #[test] - /// Ensures that a spot light can determine if a point lies inside or outside its cone - /// of emission. - fn spot_one_calc() { - let (doc, _, _) = gltf::import( - crate::test::workspace_dir() - .join("gltf") - .join("spot_one.glb"), - ) - .unwrap(); - let light = doc.lights().unwrap().next().unwrap(); - let spot = if let gltf::khr_lights_punctual::Kind::Spot { - inner_cone_angle, - outer_cone_angle, - } = light.kind() - { - (inner_cone_angle, outer_cone_angle) - } else { - panic!("not a spot light"); + // If lights have been invalidated (either by some being dropped or if + // it was previously invalidated by `Lighting::add_light` or `Lighting::remove_light`), + // create a new array + array_guard + .get_or_insert_with(|| { + log::trace!(" analytical lights array was invalidated"); + let new_lights = lights_guard + .iter() + .map(|bundle| bundle.light.id()) + .collect::>(); + let array = self.light_slab.new_array(new_lights); + log::trace!(" lights array is now: {:?}", array.array()); + array + }) + .array() }; - log::info!("spot: {spot:#?}"); - - let light_node = doc.nodes().find(|node| node.light().is_some()).unwrap(); - let parent_transform = Transform::from(light_node.transform()); - log::info!("parent_transform: {parent_transform:#?}"); - - let spot_descriptor = SpotLightDescriptor { - position: Vec3::ZERO, - direction: Vec3::NEG_Z, - inner_cutoff: spot.0, - outer_cutoff: spot.1, - color: Vec3::from(light.color()).extend(1.0), - intensity: light.intensity(), - }; - - let specific_points = [ - (Vec3::ZERO, true, true, Some(1.0)), - (Vec3::new(0.5, 0.0, 0.0), false, true, None), - (Vec3::new(0.5, 0.0, 0.5), false, false, None), - (Vec3::new(1.0, 0.0, 0.0), false, false, Some(0.0)), - ]; - for (i, (point, inside_inner, inside_outer, maybe_contribution)) in - specific_points.into_iter().enumerate() - { - log::info!("{i} descriptor: {spot_descriptor:#?}"); - let spot_calc = - SpotLightCalculation::new(spot_descriptor, parent_transform.into(), point); - log::info!("{i} spot_calc@{point}:\n{spot_calc:#?}"); - assert_eq!( - (inside_inner, inside_outer), - ( - spot_calc.fragment_is_inside_inner_cone, - spot_calc.fragment_is_inside_outer_cone - ), - ); - if let Some(expected_contribution) = maybe_contribution { - assert_eq!(expected_contribution, spot_calc.contribution); - } - } - } - #[test] - /// Ensures that a spot light illuminates only the objects within its cone of - /// emission. - fn spot_one_frame() { - let m = 32.0; - let (w, h) = (16.0f32 * m, 9.0 * m); - let ctx = crate::Context::headless(w as u32, h as u32); - let mut stage = ctx.new_stage().with_msaa_sample_count(4); - let doc = stage - .load_gltf_document_from_path( - crate::test::workspace_dir() - .join("gltf") - .join("spot_one.glb"), - ) - .unwrap(); - let camera = doc.cameras.first().unwrap(); - camera - .as_ref() - .modify(|cam| cam.set_projection(crate::camera::perspective(w, h))); - stage.use_camera(camera); - - let frame = ctx.get_next_frame().unwrap(); - stage.render(&frame.view()); - let img = frame.read_image().unwrap(); - img_diff::assert_img_eq("lights/spot_lights/one.png", img); - frame.present(); - } - - #[test] - /// Test the spot lights. - /// - /// This should render a cube with two spot lights illuminating a spot on two - /// of its sides. - fn spot_lights() { - let w = 800.0; - let h = 800.0; - let ctx = crate::Context::headless(w as u32, h as u32); - let mut stage = ctx - .new_stage() - .with_lighting(true) - .with_msaa_sample_count(4); - - let doc = stage - .load_gltf_document_from_path( - crate::test::workspace_dir() - .join("gltf") - .join("spot_lights.glb"), - ) - .unwrap(); - let camera = doc.cameras.first().unwrap(); - // TODO: investigate using the camera's aspect for any frame size. - // A `TextureView` of the frame could be created that renders to the frame - // within the camera's expected aspect ratio. - // - // We'd probably need to constrain rendering to one camera, though. - camera - .as_ref() - .modify(|cam| cam.set_projection(crate::camera::perspective(w, h))); - stage.use_camera(camera); - - let down_light = doc.lights.first().unwrap(); - log::info!( - "down_light: {:#?}", - down_light.light_details.as_spot().unwrap().get() + self.lighting_descriptor.modify( + |LightingDescriptor { + analytical_lights_array, + shadow_map_atlas_descriptor_id, + update_shadow_map_id, + update_shadow_map_texture_index, + // Don't change the tiling descriptor + light_tiling_descriptor_id: _, + }| { + *analytical_lights_array = lights_array; + *shadow_map_atlas_descriptor_id = self.shadow_map_atlas.descriptor_id(); + *update_shadow_map_id = Id::NONE; + *update_shadow_map_texture_index = 0; + }, ); - let frame = ctx.get_next_frame().unwrap(); - stage.render(&frame.view()); - let img = frame.read_image().unwrap(); - img_diff::assert_img_eq("lights/spot_lights/frame.png", img); - frame.present(); + let buffer = self.light_slab.commit(); + log::trace!(" light slab creation time: {}", buffer.creation_time()); + buffer } } + +#[cfg(test)] +mod test; diff --git a/crates/renderling/src/light/cpu/test.rs b/crates/renderling/src/light/cpu/test.rs new file mode 100644 index 00000000..b5672201 --- /dev/null +++ b/crates/renderling/src/light/cpu/test.rs @@ -0,0 +1,1115 @@ +//! Tests of the lighting system. + +use glam::{Vec3, Vec4, Vec4Swizzles}; + + +use spirv_std::num_traits::Zero; + +use crate::{ + bvol::BoundingBox, + camera::Camera, + color::linear_xfer_vec4, + light::{LightTiling, LightTilingConfig, SpotLightCalculation}, + math::GpuRng, + pbr::Material, + prelude::Transform, + stage::{Renderlet, RenderletPbrVertexInfo, Stage, Vertex}, +}; + +use super::*; + +#[test] +/// Ensures that a spot light can determine if a point lies inside or outside its cone +/// of emission. +fn spot_one_calc() { + let (doc, _, _) = gltf::import( + crate::test::workspace_dir() + .join("gltf") + .join("spot_one.glb"), + ) + .unwrap(); + let light = doc.lights().unwrap().next().unwrap(); + let spot = if let gltf::khr_lights_punctual::Kind::Spot { + inner_cone_angle, + outer_cone_angle, + } = light.kind() + { + (inner_cone_angle, outer_cone_angle) + } else { + panic!("not a spot light"); + }; + log::info!("spot: {spot:#?}"); + + let light_node = doc.nodes().find(|node| node.light().is_some()).unwrap(); + let parent_transform = Transform::from(light_node.transform()); + log::info!("parent_transform: {parent_transform:#?}"); + + let spot_descriptor = SpotLightDescriptor { + position: Vec3::ZERO, + direction: Vec3::NEG_Z, + inner_cutoff: spot.0, + outer_cutoff: spot.1, + color: Vec3::from(light.color()).extend(1.0), + intensity: light.intensity(), + }; + + let specific_points = [ + (Vec3::ZERO, true, true, Some(1.0)), + (Vec3::new(0.5, 0.0, 0.0), false, true, None), + (Vec3::new(0.5, 0.0, 0.5), false, false, None), + (Vec3::new(1.0, 0.0, 0.0), false, false, Some(0.0)), + ]; + for (i, (point, inside_inner, inside_outer, maybe_contribution)) in + specific_points.into_iter().enumerate() + { + log::info!("{i} descriptor: {spot_descriptor:#?}"); + let spot_calc = SpotLightCalculation::new(spot_descriptor, parent_transform.into(), point); + log::info!("{i} spot_calc@{point}:\n{spot_calc:#?}"); + assert_eq!( + (inside_inner, inside_outer), + ( + spot_calc.fragment_is_inside_inner_cone, + spot_calc.fragment_is_inside_outer_cone + ), + ); + if let Some(expected_contribution) = maybe_contribution { + assert_eq!(expected_contribution, spot_calc.contribution); + } + } +} + +#[test] +/// Ensures that a spot light illuminates only the objects within its cone of +/// emission. +fn spot_one_frame() { + let m = 32.0; + let (w, h) = (16.0f32 * m, 9.0 * m); + let ctx = crate::Context::headless(w as u32, h as u32); + let stage = ctx.new_stage().with_msaa_sample_count(4); + let doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("spot_one.glb"), + ) + .unwrap(); + let camera = doc.cameras.first().unwrap(); + camera + .as_ref() + .modify(|cam| cam.set_projection(crate::camera::perspective(w, h))); + stage.use_camera(camera); + + let frame = ctx.get_next_frame().unwrap(); + stage.render(&frame.view()); + let img = frame.read_image().unwrap(); + img_diff::assert_img_eq("light/spot_lights/one.png", img); + frame.present(); +} + +#[test] +/// Test the spot lights. +/// +/// This should render a cube with two spot lights illuminating a spot on two +/// of its sides. +fn spot_lights() { + let w = 800.0; + let h = 800.0; + let ctx = crate::Context::headless(w as u32, h as u32); + let stage = ctx + .new_stage() + .with_lighting(true) + .with_msaa_sample_count(4); + + let doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("spot_lights.glb"), + ) + .unwrap(); + let camera = doc.cameras.first().unwrap(); + camera + .as_ref() + .modify(|cam| cam.set_projection(crate::camera::perspective(w, h))); + stage.use_camera(camera); + + let down_light = doc.lights.first().unwrap(); + log::info!( + "down_light: {:#?}", + down_light.light_details.as_spot().unwrap().get() + ); + + let frame = ctx.get_next_frame().unwrap(); + stage.render(&frame.view()); + let img = frame.read_image().unwrap(); + img_diff::assert_img_eq("light/spot_lights/frame.png", img); + frame.present(); +} + +#[test] +fn light_tiling_light_bounds() { + let magnification = 8; + let w = 16.0 * 2.0f32.powi(magnification); + let h = 9.0 * 2.0f32.powi(magnification); + let ctx = crate::Context::headless(w as u32, h as u32); + let stage = ctx.new_stage().with_msaa_sample_count(4); + let doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("light_tiling_test.glb"), + ) + .unwrap(); + let camera = doc.cameras.first().unwrap(); + + stage.use_camera(camera); + + let _lights = crate::test::make_two_directional_light_setup(&stage); + + // Here we only want to render the bounding boxes of the renderlets, + // so mark the renderlets themeselves invisible + doc.renderlets_iter().for_each(|hy_rend| { + hy_rend.modify(|r| { + r.visible = false; + }); + }); + + let colors = [0x6DE1D2FF, 0xFFD63AFF, 0x6DE1D2FF, 0xF75A5AFF].map(|albedo_factor| { + stage.new_material(Material { + albedo_factor: { + let mut color = crate::math::hex_to_vec4(albedo_factor); + linear_xfer_vec4(&mut color); + color + }, + ..Default::default() + }) + }); + let mut resources = vec![]; + for (i, node) in doc.nodes.iter().enumerate() { + if node.mesh.is_none() { + continue; + } + let transform = Mat4::from(node.transform.get_global_transform()); + if let Some(mesh_index) = node.mesh { + log::info!("mesh: {}", node.name.as_deref().unwrap_or("unknown")); + let mesh = &doc.meshes[mesh_index]; + for prim in mesh.primitives.iter() { + let (min, max) = prim.bounding_box; + let min = transform.transform_point3(min); + let max = transform.transform_point3(max); + let bb = BoundingBox::from_min_max(min, max); + if bb.half_extent.min_element().is_zero() { + log::warn!("bounding box is not a volume, skipping"); + continue; + } + log::info!("min: {min}, max: {max}"); + resources.push( + stage + .builder() + .with_vertices({ + bb.get_mesh() + .map(|(p, n)| Vertex::default().with_position(p).with_normal(n)) + }) + .with_material_id(colors[i % colors.len()].id()) + .build(), + ); + } + } + } + + let frame = ctx.get_next_frame().unwrap(); + stage.render(&frame.view()); + let img = frame.read_image().unwrap(); + img_diff::save("light/tiling/bounds.png", img); + frame.present(); +} + +fn gen_vec3(prng: &mut GpuRng) -> Vec3 { + let x = prng.gen_f32(-120.0, 120.0); + let y = prng.gen_f32(0.0, 80.0); + let z = prng.gen_f32(-120.0, 120.0); + Vec3::new(x, y, z) +} + +struct GeneratedLight { + _unused_transform: Hybrid, + _mesh_geometry: HybridArray, + _mesh_material: Hybrid, + _light: AnalyticalLight, + mesh_renderlet: Hybrid, +} + +fn gen_light(stage: &Stage, prng: &mut GpuRng, bounding_boxes: &[BoundingBox]) -> GeneratedLight { + let mut position = gen_vec3(prng); + while bounding_boxes.iter().any(|bb| bb.contains_point(position)) { + position = gen_vec3(prng); + } + assert!(!position.x.is_nan()); + assert!(!position.y.is_nan()); + assert!(!position.z.is_nan()); + + let color = Vec4::new( + prng.gen_f32(0.0, 1.0), + prng.gen_f32(0.0, 1.0), + prng.gen_f32(0.0, 1.0), + 1.0, + ); + + let scale = prng.gen_f32(0.1, 1.0); + + let light_bb = BoundingBox { + center: Vec3::ZERO, + half_extent: Vec3::new(scale, scale, scale) * 0.5, + }; + + // Also make a renderlet for the light, so we can see where it is. + // let transform = stage.new_nested_transform(); + // transform.modify(|t| { + // if transform.global_transform_id().inner() == 5676 { + // println!("generated position: {position}"); + // } + // t.translation = position; + // }); + let (a, b, c, d, e) = stage + .builder() + .with_transform(Transform { + translation: position, + ..Default::default() + }) + .with_vertices( + light_bb + .get_mesh() + .map(|(p, n)| Vertex::default().with_position(p).with_normal(n)), + ) + .with_material(Material { + albedo_factor: color, + has_lighting: false, + emissive_factor: color.xyz(), + emissive_strength_multiplier: 100.0, + ..Default::default() + }) + .suffix({ + // suffix the actual analytical light + let intensity = scale * 100.0; + + let light_descriptor = PointLightDescriptor { + position, + color, + intensity, + }; + + stage.new_analytical_light(light_descriptor) + }) + .build(); + GeneratedLight { + _unused_transform: a, + _mesh_geometry: b, + _mesh_material: c, + _light: d, + mesh_renderlet: e, + } +} + +fn size() -> UVec2 { + UVec2::new( + (10.0 * 2.0f32.powi(8)) as u32, + (9.0 * 2.0f32.powi(8)) as u32, + ) +} + +fn make_camera() -> Camera { + let size = size(); + let eye = Vec3::new(250.0, 200.0, 250.0); + let target = Vec3::ZERO; + log::info!("make_camera: forward {}", (target - eye).normalize()); + Camera::new( + Mat4::perspective_rh( + std::f32::consts::FRAC_PI_4, + size.x as f32 / size.y as f32, + 50.0, + 600.0, + ), + Mat4::look_at_rh(eye, target, Vec3::Y), + ) +} + +/// Ensures that `LightTile`s are cleared by the clear_tiles shader. +#[test] +fn clear_tiles_sanity() { + let _ = env_logger::builder().is_test(true).try_init(); + let s = 256; + let depth_texture_size = UVec2::splat(s); + let ctx = crate::Context::headless(s, s); + let stage = ctx.new_stage(); + let lighting: &Lighting = stage.as_ref(); + let tiling_config = LightTilingConfig::default(); + let tiling = LightTiling::new_hybrid(lighting, false, depth_texture_size, tiling_config); + let desc = tiling.tiling_descriptor.get(); + let tile_dimensions = desc.tile_grid_size(); + + // Write to the tiles to ensure we know the starting state, that way we can + // ensure each step of tiling is correct. + { + let mut rng = GpuRng::new(0); + let max_distance = UVec2::ZERO.manhattan_distance(tile_dimensions) as f32; + for i in 0..tiling.tiles().len() { + tiling.tiles().modify(i, |item| { + let x = i as u32 % tile_dimensions.x; + let y = i as u32 / tile_dimensions.x; + let tile_coord = UVec2::new(x, y); + let distance = tile_coord.manhattan_distance(tile_dimensions) as f32; + // This should produce an image where pixels get darker towards the lower right corner. + let min = distance / max_distance; + // This should produce an image where pixels get darker towards the upper left corner. + let max = 1.0 - distance / max_distance; + + item.depth_min = crate::light::quantize_depth_f32_to_u32(min); + item.depth_max = crate::light::quantize_depth_f32_to_u32(max); + + // This should produce an image that looks like noise + item.next_light_index = rng.gen_u32(0, 32); + }); + } + let _ = lighting.commit(); + ctx.get_device().poll(wgpu::PollType::Wait).unwrap(); + + let (mins, maxs, lights) = futures_lite::future::block_on(tiling.read_images(lighting)); + img_diff::assert_img_eq("light/tiling/clear_tiles/1-mins.png", mins); + img_diff::assert_img_eq("light/tiling/clear_tiles/1-maxs.png", maxs); + img_diff::assert_img_eq("light/tiling/clear_tiles/1-lights.png", lights); + } + + // Run the clear_tiles shader to ensure that the tiles are cleared. + { + tiling.prepare(lighting, depth_texture_size); + let stage_commit_result = stage.commit(); + let bindgroup = tiling.get_bindgroup( + ctx.get_device(), + &stage_commit_result.geometry_buffer, + &stage_commit_result.lighting_buffer, + &stage.depth_texture.read().unwrap(), + ); + let label = Some("light-tiling-clear-tiles-test"); + let mut encoder = ctx + .get_device() + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + tiling.clear_tiles(&mut encoder, bindgroup.as_ref(), depth_texture_size); + } + ctx.runtime().queue.submit(Some(encoder.finish())); + + let (mins, maxs, lights) = futures_lite::future::block_on(tiling.read_images(lighting)); + img_diff::assert_img_eq("light/tiling/clear_tiles/2-mins.png", mins); + img_diff::assert_img_eq("light/tiling/clear_tiles/2-maxs.png", maxs); + img_diff::assert_img_eq("light/tiling/clear_tiles/2-lights.png", lights); + } +} + +#[test] +fn min_max_depth_sanity() { + let _ = env_logger::builder().is_test(true).try_init(); + let s = 256; + let depth_texture_size = UVec2::splat(s); + let ctx = crate::Context::headless(s, s); + let stage = ctx.new_stage(); + let _doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("light_tiling_test.glb"), + ) + .unwrap(); + let camera = stage.new_camera(make_camera()); + stage.use_camera(camera); + snapshot( + &ctx, + &stage, + "light/tiling/min_max_depth/1-scene.png", + false, + ); + + let lighting = &stage.lighting; + let tiling = LightTiling::new_hybrid(lighting, false, depth_texture_size, Default::default()); + tiling.prepare(lighting, depth_texture_size); + + let stage_commit_result = stage.commit(); + let bindgroup = tiling.get_bindgroup( + ctx.get_device(), + &stage_commit_result.geometry_buffer, + &stage_commit_result.lighting_buffer, + &stage.depth_texture.read().unwrap(), + ); + let label = Some("light-tiling-min-max-depth-test"); + + // Clear the tiles, which is verified in `clear_tiles_sanity`, then assert the min/max depth + { + let mut encoder = ctx + .get_device() + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + tiling.clear_tiles(&mut encoder, bindgroup.as_ref(), depth_texture_size); + tiling.compute_min_max_depth(&mut encoder, bindgroup.as_ref(), depth_texture_size); + } + ctx.runtime().queue.submit(Some(encoder.finish())); + let (mins, maxs, _lights) = futures_lite::future::block_on(tiling.read_images(lighting)); + img_diff::assert_img_eq("light/tiling/min_max_depth/2-mins.png", mins); + img_diff::assert_img_eq("light/tiling/min_max_depth/2-maxs.png", maxs); + } +} + +#[test] +fn light_bins_sanity() { + let _ = env_logger::builder().is_test(true).try_init(); + let s = 256; + let depth_texture_size = UVec2::splat(s); + let ctx = crate::Context::headless(s, s); + let stage = ctx.new_stage(); + let doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("light_tiling_test.glb"), + ) + .unwrap(); + let camera = stage.new_camera(make_camera()); + stage.use_camera(camera); + snapshot(&ctx, &stage, "light/tiling/bins/1-scene.png", false); + + let lighting = &stage.lighting; + let tiling = LightTiling::new_hybrid(lighting, false, depth_texture_size, Default::default()); + assert_eq!( + tiling.tiling_descriptor.get().tiles_array, + tiling.tiles().array() + ); + tiling.prepare(lighting, depth_texture_size); + + let stage_commit_result = stage.commit(); + let bindgroup = tiling.get_bindgroup( + ctx.get_device(), + &stage_commit_result.geometry_buffer, + &stage_commit_result.lighting_buffer, + &stage.depth_texture.read().unwrap(), + ); + let label = Some("light-tiling-min-max-depth-test"); + + { + { + let mut encoder = ctx + .get_device() + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + tiling.clear_tiles(&mut encoder, bindgroup.as_ref(), depth_texture_size); + tiling.compute_min_max_depth(&mut encoder, bindgroup.as_ref(), depth_texture_size); + tiling.compute_bins(&mut encoder, bindgroup.as_ref(), depth_texture_size); + ctx.runtime().queue.submit(Some(encoder.finish())); + } + let (_mins, _maxs, mut lights) = + futures_lite::future::block_on(tiling.read_images(lighting)); + img_diff::normalize_gray_img(&mut lights); + img_diff::assert_img_eq("light/tiling/bins/2-lights.png", lights); + } + let directional_light = doc.lights.first().unwrap(); + let tiles = futures_lite::future::block_on(tiling.read_tiles(lighting)); + for tile in tiles.into_iter() { + let light_bin = + futures_lite::future::block_on(lighting.light_slab.read_array(tile.lights_array)) + .unwrap(); + // Assert either the light is the correct one, or we're using the zero frustum optimization + // discussed in + if tile.depth_min != tile.depth_max { + assert_eq!(light_bin[0], directional_light.light.id()); + assert_eq!(light_bin[1], Id::NONE); + } else { + assert_eq!(0, tile.next_light_index); + assert_eq!(light_bin[0], Id::NONE); + } + } +} + +// Ensures point lights are being binned properly. +#[test] +fn light_bins_point() { + let ctx = crate::Context::headless(256, 256); + let stage = ctx + .new_stage() + .with_msaa_sample_count(1) + .with_bloom_mix_strength(0.08); + let doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("pedestal.glb"), + ) + .unwrap(); + let materials = doc.materials.get_vec(); + log::info!("materials: {materials:#?}"); + doc.materials.set_item( + 0, + Material { + albedo_factor: Vec4::ONE, + roughness_factor: 1.0, + metallic_factor: 0.0, + ..Default::default() + }, + ); + let camera = doc.cameras.first().unwrap(); + camera.camera.modify(|cam| { + let view = Mat4::look_at_rh(Vec3::new(-7.0, 5.0, 7.0), Vec3::ZERO, Vec3::Y); + let proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_6, 1.0, 0.1, 15.0); + cam.set_projection_and_view(proj, view); + }); + + let _point_light = stage.new_analytical_light(PointLightDescriptor { + position: Vec3::new(1.1, 1.0, 1.1), + color: Vec4::ONE, + intensity: 5.0, + }); + snapshot( + &ctx, + &stage, + "light/tiling/light_bins_point/1-scene.png", + true, + ); + + let tiling = stage.new_light_tiling(LightTilingConfig { + max_lights_per_tile: 2, + ..Default::default() + }); + tiling.run(&stage); + + let (_mins, _maxs, lights) = futures_lite::future::block_on(tiling.read_images(stage.as_ref())); + img_diff::save("light/tiling/light_bins_point/2-lights.png", lights); + + let mut found = 0; + for tile in futures_lite::future::block_on(tiling.read_tiles(stage.as_ref())) { + if tile.depth_min != tile.depth_max && found < 3 { + found += 1; + log::info!("tile: {tile:#?}"); + } + } +} + +fn tiling_e2e_sanity_with( + tile_size: u32, + max_lights_per_tile: u32, + i: u32, + minimum_illuminance: f32, + save_images: bool, +) { + let size = size(); + let ctx = crate::Context::headless(size.x, size.y); + let stage = ctx + .new_stage() + .with_bloom(true) + .with_bloom_mix_strength(0.5) + .with_msaa_sample_count(1); + + let doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("light_tiling_test.glb"), + ) + .unwrap(); + + let camera = stage.new_camera(make_camera()); + let camera_value = camera.get(); + log::info!("camera: {camera_value:#?}"); + stage.use_camera(camera); + + let _ = stage.lighting.commit(); + + let moonlight = doc.lights.first().unwrap(); + let _shadow = { + let sm = stage + .new_shadow_map(moonlight, UVec2::splat(1024), 0.1, 256.0) + .unwrap(); + sm.shadowmap_descriptor.modify(|d| { + d.bias_min = 0.0; + d.bias_max = 0.0; + d.pcf_samples = 2; + }); + sm.update(&stage, doc.renderlets_iter()).unwrap(); + sm + }; + + let mut bounding_boxes = vec![]; + for node in doc.nodes.iter() { + if node.mesh.is_none() { + continue; + } + let transform = Mat4::from(node.transform.get_global_transform()); + if let Some(mesh_index) = node.mesh { + let mesh = &doc.meshes[mesh_index]; + for prim in mesh.primitives.iter() { + let (min, max) = prim.bounding_box; + let min = transform.transform_point3(min); + let max = transform.transform_point3(max); + let bb = BoundingBox::from_min_max(min, max); + if bb.half_extent.min_element().is_zero() { + continue; + } + bounding_boxes.push(bb); + } + } + } + log::info!("have {} bounding boxes", bounding_boxes.len()); + + let mut prng = crate::math::GpuRng::new(666); + let mut lights: Vec = vec![]; + + for _ in 0..MAX_LIGHTS { + lights.push(gen_light(&stage, &mut prng, &bounding_boxes)); + } + + // Remove the light meshes + for generated_light in lights.iter() { + stage.remove_renderlet(&generated_light.mesh_renderlet); + } + snapshot( + &ctx, + &stage, + "light/tiling/e2e/4-scene-no-tiling.png", + false, + ); + + let config = LightTilingConfig { + tile_size, + max_lights_per_tile, + minimum_illuminance, + }; + println!("generating rendering with config: {config:#?}"); + let tiling = stage.new_light_tiling(config); + tiling.run(&stage); + snapshot( + &ctx, + &stage, + &format!("light/tiling/e2e/6-scene-{tile_size}-{max_lights_per_tile}-lights-{i}-{minimum_illuminance}-min-lux.png"), + save_images + ); + + #[cfg(feature = "light-tiling-stats")] + { + use stats::*; + // Stats + let mut stats = LightTilingStats::default(); + for number_of_lights_exponent in 2..=MAX_LIGHTS.ilog2() { + let number_of_lights = 2usize.pow(number_of_lights_exponent); + let mut run = LightTilingStatsRun { + number_of_lights, + iterations: vec![], + }; + + for (i, generated_light) in lights.iter().enumerate() { + stage.remove_light(&generated_light._light); + if i < number_of_lights { + stage.add_light(&generated_light._light); + } + } + + const NUM_RUNS: usize = 2; + for (i, with_tiling) in (0..NUM_RUNS).zip([true, false].iter().cycle()) { + log::info!( + "{number_of_lights} {i} {} running", + if true { "tiling" } else { "non-tiling" } + ); + if *with_tiling { + tiling.run(&stage); + } + stage.lighting.lighting_descriptor.modify(|desc| { + desc.light_tiling_descriptor_id = if *with_tiling { + tiling.tiling_descriptor.id() + } else { + Id::NONE + } + }); + let start = std::time::Instant::now(); + let frame = ctx.get_next_frame().unwrap(); + stage.render(&frame.view()); + frame.present(); + ctx.get_device().poll(wgpu::PollType::Wait).unwrap(); + let duration = start.elapsed(); + run.iterations.push((*with_tiling, duration)); + } + stats.runs.push(run); + } + plot(stats, &format!("frame-time-{tile_size}-{max_lights_per_tile}-lights-{i}-{minimum_illuminance}-min-lux")); + } +} + +#[test] +/// Test the light tiling feature, end to end. +fn tiling_e2e_sanity() { + let _ = env_logger::builder().is_test(true).try_init(); + let config = LightTilingConfig::default(); + tiling_e2e_sanity_with( + config.tile_size, + config.max_lights_per_tile, + 0, + config.minimum_illuminance, + false, + ); + + #[cfg(feature = "light-tiling-stats")] + { + let tile_sizes = [4, 8, 16]; + let max_lights_per_tile = [16, 32, 64, 128, 256]; + let minimum_illuminance_lux = [0.05, 0.1, 0.3, 1.0, 2.0]; + for tile_size in tile_sizes { + for max_lights_per_tile in max_lights_per_tile { + for (i, minimum_illuminance) in minimum_illuminance_lux.iter().enumerate() { + tiling_e2e_sanity_with( + tile_size, + max_lights_per_tile, + i as u32, + *minimum_illuminance, + true + ) + } + } + } + } +} + +fn snapshot(ctx: &crate::Context, stage: &Stage, path: &str, save: bool) { + let frame = ctx.get_next_frame().unwrap(); + let start = std::time::Instant::now(); + stage.render(&frame.view()); + let elapsed = start.elapsed(); + log::info!("shapshot: {}s '{path}'", elapsed.as_secs_f32()); + let img = frame.read_image().unwrap(); + if save { + img_diff::save(path, img); + } else { + img_diff::assert_img_eq(path, img); + } + frame.present(); +} + +const MAX_LIGHTS: usize = 2usize.pow(10); + +mod stats { + #![allow(dead_code)] + use core::time::Duration; + + + use plotters::{ + chart::{ChartBuilder, SeriesLabelPosition}, + prelude::{BitMapBackend, Circle, EmptyElement, IntoDrawingArea, PathElement}, + series::{LineSeries, PointSeries}, + style::{Color, IntoFont, ShapeStyle}, + }; + + pub struct LightTilingStatsRun { + pub number_of_lights: usize, + pub iterations: Vec<(bool, Duration)>, + } + + impl LightTilingStatsRun { + fn avg_frame_time(&self, with_tiling: bool) -> f32 { + let total: Duration = self + .iterations + .iter() + .filter_map(|(had_tiling, dur)| { + if *had_tiling == with_tiling { + Some(dur) + } else { + None + } + }) + .sum(); + total.as_secs_f32() / self.iterations.len() as f32 + } + } + + #[derive(Default)] + pub struct LightTilingStats { + pub runs: Vec, + } + + pub fn plot(stats: LightTilingStats, filename: &str) { + let path = + crate::test::workspace_dir().join(format!("test_output/light/tiling/e2e/{filename}.png")); + let root_drawing_area = BitMapBackend::new(&path, (800, 600)).into_drawing_area(); + root_drawing_area.fill(&plotters::style::WHITE).unwrap(); + + let mut chart = ChartBuilder::on(&root_drawing_area) + .caption( + "Renderling lighting frame time", + ("sans-serif", 50).into_font(), + ) + .margin(30) + .margin_right(100) + .margin_left(60) + .x_label_area_size(30) + .y_label_area_size(30) + .build_cartesian_2d( + 0..super::MAX_LIGHTS + 1, + 0.0..stats + .runs + .iter() + .flat_map(|r| [r.avg_frame_time(true), r.avg_frame_time(false)]) + .max_by(|a, b| a.total_cmp(b)) + .unwrap_or_default(), + ) + .unwrap(); + fn y_fmt(coord: &f32) -> String { + let fps = 1.0 / coord; + format!("{coord}s ({fps:.2} fps)") + } + chart + .configure_mesh() + .x_desc("number of lights") + .y_label_formatter(&y_fmt) + .draw() + .unwrap(); + chart + .draw_series(LineSeries::new( + stats + .runs + .iter() + .map(|r| (r.number_of_lights, r.avg_frame_time(false))), + plotters::style::RED, + )) + .unwrap() + .label("without tiling") + .legend(|(x, y)| { + PathElement::new(vec![(x, y), (x + 20, y)], plotters::style::RED.filled()) + }); + chart + .draw_series(LineSeries::new( + stats + .runs + .iter() + .map(|r| (r.number_of_lights, r.avg_frame_time(true))), + plotters::style::BLUE, + )) + .unwrap() + .label("with tiling") + .legend(|(x, y)| { + PathElement::new(vec![(x, y), (x + 20, y)], plotters::style::BLUE.filled()) + }); + chart + .draw_series(PointSeries::of_element( + stats + .runs + .iter() + .map(|r| (r.number_of_lights, r.avg_frame_time(false))), + 5, + ShapeStyle::from(&plotters::style::RED).filled(), + &|(num_lights, seconds_per_frame), size, style| { + EmptyElement::at((num_lights, seconds_per_frame)) + Circle::new((0, 0), size, style) + }, + )) + .unwrap(); + chart + .draw_series(PointSeries::of_element( + stats + .runs + .iter() + .map(|r| (r.number_of_lights, r.avg_frame_time(true))), + 5, + ShapeStyle::from(&plotters::style::BLUE).filled(), + &|(num_lights, seconds_per_frame), size, style| { + EmptyElement::at((num_lights, seconds_per_frame)) + Circle::new((0, 0), size, style) + }, + )) + .unwrap(); + + chart + .configure_series_labels() + .position(SeriesLabelPosition::UpperLeft) + .margin(20) + .label_font(("sans-serif", 20)) + .draw() + .unwrap(); + root_drawing_area.present().unwrap(); + } +} + +#[test] +/// For all light types that have a position: +/// +/// Ensures that a light with a translated position renders the same +/// as a light at the origin that has a linked `NestedTransform` applied with +/// that same translation. +/// +/// In other words, light w/ nested transform is the same as light with +/// that same transform pre-applied. +fn pedestal() { + let ctx = crate::Context::headless(256, 256); + let stage = ctx + .new_stage() + .with_lighting(false) + .with_msaa_sample_count(4) + .with_bloom_mix_strength(0.08); + let doc = stage + .load_gltf_document_from_path( + crate::test::workspace_dir() + .join("gltf") + .join("pedestal.glb"), + ) + .unwrap(); + let materials = doc.materials.get_vec(); + log::info!("materials: {materials:#?}"); + doc.materials.set_item( + 0, + Material { + albedo_factor: Vec4::ONE, + roughness_factor: 1.0, + metallic_factor: 0.0, + ..Default::default() + }, + ); + let camera = doc.cameras.first().unwrap(); + camera.camera.modify(|cam| { + let view = Mat4::look_at_rh(Vec3::new(-7.0, 5.0, 7.0), Vec3::ZERO, Vec3::Y); + let proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_6, 1.0, 0.1, 15.0); + cam.set_projection_and_view(proj, view); + }); + + let color = { + // let mut c = hex_to_vec4(0xEEDF7AFF); + // linear_xfer_vec4(&mut c); + // c + Vec4::ONE + }; + let position = Vec3::new(1.1, 1.0, 1.1); + let transform = stage.new_nested_transform(); + transform.modify(|t| t.translation = position); + + stage.set_has_lighting(true); + + let mut dir_infos = vec![]; + { + log::info!("adding dir light"); + let _dir_light = stage.new_analytical_light(DirectionalLightDescriptor { + direction: -position, + color, + intensity: 5.0, + }); + snapshot(&ctx, &stage, "light/pedestal/directional.png", false); + + let geometry_slab = + futures_lite::future::block_on(stage.geometry.slab_allocator().read(..)).unwrap(); + + let renderlet = doc.renderlets_iter().next().unwrap(); + log::info!("renderlet: {renderlet:#?}"); + + for vertex_index in 0..renderlet.get().vertices_array.len() { + let mut info = RenderletPbrVertexInfo::default(); + crate::stage::renderlet_vertex( + renderlet.id(), + vertex_index as u32, + &geometry_slab, + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut info, + ); + + dir_infos.push(info); + } + log::info!("dropping dir light"); + } + assert_eq!(0, stage.lighting.lights().count()); + + // Point lights + { + log::info!("adding point light with pre-applied position"); + let _point_light = stage.new_analytical_light(PointLightDescriptor { + position, + color, + intensity: 5.0, + }); + snapshot(&ctx, &stage, "light/pedestal/point.png", false); + log::info!("dropping point light"); + } + + { + log::info!("adding point light with nested transform"); + let transform = stage.new_nested_transform(); + transform.modify(|t| t.translation = position); + + let point_light = stage.new_analytical_light(PointLightDescriptor { + position: Vec3::ZERO, + color, + intensity: 5.0, + }); + point_light.link_node_transform(&transform); + + snapshot(&ctx, &stage, "light/pedestal/point.png", false); + log::info!("dropping point light"); + } + + { + log::info!("adding spot light with pre-applied position"); + let spot_desc = SpotLightDescriptor { + position, + direction: -position, + color, + intensity: 5.0, + inner_cutoff: core::f32::consts::PI / 5.0, + outer_cutoff: core::f32::consts::PI / 4.0, + // ..Default::default() + }; + let _spot = stage.new_analytical_light(spot_desc); + snapshot(&ctx, &stage, "light/pedestal/spot.png", false); + + let geometry_slab = + futures_lite::future::block_on(stage.geometry.slab_allocator().read(..)).unwrap(); + + let renderlet = doc.renderlets_iter().next().unwrap(); + log::info!("renderlet: {renderlet:#?}"); + let mut spot_infos = vec![]; + + for vertex_index in 0..renderlet.get().vertices_array.len() { + let mut info = RenderletPbrVertexInfo::default(); + crate::stage::renderlet_vertex( + renderlet.id(), + vertex_index as u32, + &geometry_slab, + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut Default::default(), + &mut info, + ); + spot_infos.push(info); + } + + // assert that the output of the vertex shader is the same for the first renderlet, + // regardless of the lighting + pretty_assertions::assert_eq!(dir_infos, spot_infos); + } + + { + log::info!("adding spot light with node position"); + + let node_transform = stage.new_nested_transform(); + node_transform.modify(|t| t.translation = position); + + let spot_desc = SpotLightDescriptor { + position: Vec3::ZERO, + direction: -position, + color, + intensity: 5.0, + inner_cutoff: core::f32::consts::PI / 5.0, + outer_cutoff: core::f32::consts::PI / 4.0, + // ..Default::default() + }; + let spot = stage.new_analytical_light(spot_desc); + spot.link_node_transform(&node_transform); + snapshot(&ctx, &stage, "light/pedestal/spot.png", false); + } +} diff --git a/crates/renderling/src/light/shadow_map.rs b/crates/renderling/src/light/shadow_map.rs index 910edbbc..91b73f79 100644 --- a/crates/renderling/src/light/shadow_map.rs +++ b/crates/renderling/src/light/shadow_map.rs @@ -9,7 +9,7 @@ use craballoc::{ }; use crabslab::Id; use glam::{Mat4, UVec2}; -use snafu::OptionExt; +use snafu::{OptionExt, ResultExt}; use crate::{ atlas::{AtlasBlittingOperation, AtlasImage, AtlasTexture}, @@ -18,7 +18,7 @@ use crate::{ }; use super::{ - AnalyticalLightBundle, DroppedAnalyticalLightBundleSnafu, Lighting, LightingError, + AnalyticalLight, DroppedAnalyticalLightBundleSnafu, Lighting, LightingError, PollSnafu, ShadowMapDescriptor, }; @@ -44,7 +44,7 @@ pub struct ShadowMap { pub(crate) _atlas_textures_array: HybridArray>, pub(crate) update_texture: crate::texture::Texture, pub(crate) blitting_op: AtlasBlittingOperation, - pub(crate) light_bundle: AnalyticalLightBundle, + pub(crate) light_bundle: AnalyticalLight, } impl ShadowMap { @@ -164,7 +164,7 @@ impl ShadowMap { /// a new [`ShadowMap`]. pub fn new( lighting: &Lighting, - analytical_light_bundle: &AnalyticalLightBundle, + analytical_light_bundle: &AnalyticalLight, // Size of the shadow map size: UVec2, // Distance to the shadow map frustum's near plane @@ -174,7 +174,7 @@ impl ShadowMap { ) -> Result { let stage_slab_buffer = lighting.geometry_slab_buffer.read().unwrap(); let is_point_light = - analytical_light_bundle.light_details.style() == super::LightStyle::Point; + analytical_light_bundle.light_details().style() == super::LightStyle::Point; let count = if is_point_light { 6 } else { 1 }; let atlas = &lighting.shadow_map_atlas; let image = AtlasImage::new(size, crate::atlas::AtlasImageFormat::R32FLOAT); @@ -211,10 +211,10 @@ impl ShadowMap { pcf_samples: 4, }); // Set the descriptor in the light, so the shader knows to use it - analytical_light_bundle.light.modify(|light| { + analytical_light_bundle.light().modify(|light| { light.shadow_map_desc_id = shadowmap_descriptor.id(); }); - let light_slab_buffer = lighting.light_slab.commit(); + let light_slab_buffer = lighting.commit(); let update_bindgroup = ManagedBindGroup::from(ShadowMap::create_update_bindgroup( lighting.light_slab.device(), &lighting.shadow_map_update_bindgroup_layout, @@ -238,7 +238,7 @@ impl ShadowMap { /// Update the `ShadowMap`, rendering the given [`Renderlet`]s to the map as shadow casters. /// - /// The `ShadowMap` contains a weak referenc to the [`AnalyticalLightBundle`] used to create + /// The `ShadowMap` contains a weak reference to the [`AnalyticalLightBundle`] used to create /// it. Updates made to this `AnalyticalLightBundle` will automatically propogate to this /// `ShadowMap`. /// @@ -357,7 +357,9 @@ impl ShadowMap { atlas_texture, )?; let submission = queue.submit(Some(encoder.finish())); - device.poll(wgpu::Maintain::wait_for(submission)); + device + .poll(wgpu::PollType::WaitForSubmissionIndex(submission)) + .context(PollSnafu)?; } Ok(()) } @@ -366,9 +368,7 @@ impl ShadowMap { #[cfg(test)] #[allow(clippy::unused_enumerate_index)] mod test { - use image::Luma; - - use crate::{camera::Camera, texture::DepthTexture}; + use crate::camera::Camera; use super::super::*; @@ -377,7 +377,7 @@ mod test { let w = 800.0; let h = 800.0; let ctx = crate::Context::headless(w as u32, h as u32); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(true) .with_msaa_sample_count(4); @@ -431,7 +431,7 @@ mod test { let w = 800.0; let h = 800.0; let ctx = crate::Context::headless(w as u32, h as u32); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(true) .with_msaa_sample_count(4); @@ -483,8 +483,9 @@ mod test { fn shadow_mapping_sanity() { let w = 800.0; let h = 800.0; - let ctx = crate::Context::headless(w as u32, h as u32); - let mut stage = ctx.new_stage().with_lighting(true); + let ctx = crate::Context::headless(w as u32, h as u32) + .with_shadow_mapping_atlas_texture_size([1024, 1024, 2]); + let stage = ctx.new_stage().with_lighting(true); let doc = stage .load_gltf_document_from_path( @@ -509,8 +510,8 @@ mod test { let gltf_light = doc.lights.first().unwrap(); assert_eq!( - gltf_light.light.get().transform_id, - gltf_light.transform.global_transform_id(), + gltf_light.light().get().transform_id, + gltf_light.transform().id(), "light's global transform id is different from its transform_id" ); @@ -519,27 +520,34 @@ mod test { .unwrap(); shadows.update(&stage, doc.renderlets_iter()).unwrap(); - { - // Ensure the state of the "update texture", which receives the depth of the scene on update - let shadow_map_update_texture = - DepthTexture::try_new_from(&ctx, shadows.update_texture.clone()).unwrap(); - let mut shadow_map_update_img = shadow_map_update_texture.read_image().unwrap(); - img_diff::normalize_gray_img(&mut shadow_map_update_img); - img_diff::assert_img_eq( - "shadows/shadow_mapping_sanity/shadows_update_texture.png", - shadow_map_update_img, - ); - } - - let lighting: &Lighting = stage.as_ref(); - let shadow_depth_buffer = lighting.shadow_map_atlas.atlas_img_buffer(&ctx, 0); - let shadow_depth_img = shadow_depth_buffer - .into_image::>(ctx.get_device()) - .unwrap(); - let shadow_depth_img = shadow_depth_img.into_luma8(); - let mut depth_img = shadow_depth_img.clone(); - img_diff::normalize_gray_img(&mut depth_img); - img_diff::assert_img_eq("shadows/shadow_mapping_sanity/depth.png", depth_img); + // Extra sanity checks + // { + // use crate::texture::DepthTexture; + // use image::Luma; + // { + // // Ensure the state of the "update texture", which receives the depth of the scene on update + // let shadow_map_update_texture = + // DepthTexture::try_new_from(&ctx, shadows.update_texture.clone()).unwrap(); + // let mut shadow_map_update_img = shadow_map_update_texture.read_image().unwrap(); + // img_diff::normalize_gray_img(&mut shadow_map_update_img); + // img_diff::save( + // "shadows/shadow_mapping_sanity/shadows_update_texture.png", + // shadow_map_update_img, + // ); + // } + + // { + // let lighting: &Lighting = stage.as_ref(); + // let shadow_depth_buffer = lighting.shadow_map_atlas.atlas_img_buffer(&ctx, 0); + // let shadow_depth_img = shadow_depth_buffer + // .into_image::>(ctx.get_device()) + // .unwrap(); + // let shadow_depth_img = shadow_depth_img.into_luma8(); + // let mut depth_img = shadow_depth_img.clone(); + // img_diff::normalize_gray_img(&mut depth_img); + // img_diff::save("shadows/shadow_mapping_sanity/depth.png", depth_img); + // } + // } // Now do the rendering *with the shadow map* to see if it works. let frame = ctx.get_next_frame().unwrap(); @@ -547,7 +555,14 @@ mod test { let img = frame.read_image().unwrap(); frame.present(); - img_diff::assert_img_eq("shadows/shadow_mapping_sanity/stage_render.png", img); + img_diff::assert_img_eq_cfg( + "shadows/shadow_mapping_sanity/stage_render.png", + img, + img_diff::DiffCfg { + image_threshold: 0.01, + ..Default::default() + }, + ); } #[test] @@ -555,7 +570,7 @@ mod test { let w = 800.0; let h = 800.0; let ctx = crate::Context::headless(w as u32, h as u32); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(true) .with_msaa_sample_count(4); @@ -579,9 +594,9 @@ mod test { let z_far = 100.0; for (_i, light_bundle) in doc.lights.iter().enumerate() { { - let desc = light_bundle.light_details.as_spot().unwrap().get(); + let desc = light_bundle.light_details().as_spot().unwrap().get(); let (p, v) = desc.shadow_mapping_projection_and_view( - &light_bundle.transform.get_global_transform().into(), + &light_bundle.transform().get().into(), z_near, z_far, ); @@ -620,7 +635,7 @@ mod test { let w = 800.0; let h = 800.0; let ctx = crate::Context::headless(w as u32, h as u32); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(true) .with_background_color(Vec3::splat(0.05087).extend(1.0)) @@ -642,11 +657,12 @@ mod test { let mut shadows = vec![]; let z_near = 0.1; let z_far = 100.0; - for (_i, light_bundle) in doc.lights.iter().enumerate() { + for (i, light_bundle) in doc.lights.iter().enumerate() { { - let desc = light_bundle.light_details.as_point().unwrap().get(); + let desc = light_bundle.light_details().as_point().unwrap().get(); + println!("point light {i}: {desc:?}"); let (p, vs) = desc.shadow_mapping_projection_and_view_matrices( - &light_bundle.transform.get_global_transform().into(), + &light_bundle.transform().get().into(), z_near, z_far, ); diff --git a/crates/renderling/src/light/tiling.rs b/crates/renderling/src/light/tiling.rs new file mode 100644 index 00000000..b47a0a66 --- /dev/null +++ b/crates/renderling/src/light/tiling.rs @@ -0,0 +1,524 @@ +//! Implementation of light tiling. +//! +//! For more info on what light tiling is, see +//! [this blog post](https://renderling.xyz/articles/live/light_tiling.html). + +use core::sync::atomic::AtomicUsize; +use std::sync::Arc; + +use craballoc::{ + slab::SlabBuffer, + value::{GpuArrayContainer, Hybrid, HybridArrayContainer, IsContainer}, +}; +use crabslab::Id; +use glam::UVec2; + +use crate::{ + bindgroup::ManagedBindGroup, + light::{LightTile, LightTilingDescriptor, Lighting}, + stage::Stage, +}; + +/// Shaders and resources for conducting light tiling. +/// +/// This struct takes a container type variable in order to allow +/// tests to read and write [`LightTile`] values on the GPU. +/// +/// For info on what light tiling is, see +/// . +pub struct LightTiling { + pub(crate) tiling_descriptor: Hybrid, + tiles: Ct::Container, + /// Cache of the id of the Stage's depth texture. + /// + /// Used to invalidate our tiling bindgroup. + depth_texture_id: Arc, + + bindgroup: ManagedBindGroup, + bindgroup_layout: Arc, + bindgroup_creation_time: Arc, + + clear_tiles_pipeline: Arc, + compute_min_max_depth_pipeline: Arc, + compute_bins_pipeline: Arc, +} + +const LABEL: Option<&'static str> = Some("light-tiling"); + +impl LightTiling { + fn create_bindgroup_layout(device: &wgpu::Device, multisampled: bool) -> wgpu::BindGroupLayout { + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: LABEL, + entries: &[ + // Geometry slab + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Lighting slab + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Depth texture + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Depth, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled, + }, + count: None, + }, + ], + }) + } + + fn create_clear_tiles_pipeline( + device: &wgpu::Device, + multisampled: bool, + ) -> wgpu::ComputePipeline { + const LABEL: Option<&'static str> = Some("light-tiling-clear-tiles"); + let module = crate::linkage::light_tiling_clear_tiles::linkage(device); + let (pipeline_layout, _) = Self::create_layouts(device, multisampled); + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: LABEL, + layout: Some(&pipeline_layout), + module: &module.module, + entry_point: Some(module.entry_point), + compilation_options: wgpu::PipelineCompilationOptions::default(), + cache: None, + }) + } + + fn create_compute_min_max_depth_pipeline( + device: &wgpu::Device, + multisampled: bool, + ) -> wgpu::ComputePipeline { + const LABEL: Option<&'static str> = Some("light-tiling-compute-min-max-depth"); + let module = crate::linkage::light_tiling_compute_tile_min_and_max_depth::linkage(device); + let (pipeline_layout, _) = Self::create_layouts(device, multisampled); + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: LABEL, + layout: Some(&pipeline_layout), + module: &module.module, + entry_point: Some(module.entry_point), + compilation_options: wgpu::PipelineCompilationOptions::default(), + cache: None, + }) + } + + fn create_compute_bins_pipeline( + device: &wgpu::Device, + multisampled: bool, + ) -> wgpu::ComputePipeline { + const LABEL: Option<&'static str> = Some("light-tiling-compute-bins"); + let module = crate::linkage::light_tiling_bin_lights::linkage(device); + let (pipeline_layout, _) = Self::create_layouts(device, multisampled); + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: LABEL, + layout: Some(&pipeline_layout), + module: &module.module, + entry_point: Some(module.entry_point), + compilation_options: wgpu::PipelineCompilationOptions::default(), + cache: None, + }) + } + + /// All pipelines share the same layout, so we do it here, once. + fn create_layouts( + device: &wgpu::Device, + multisampled: bool, + ) -> (wgpu::PipelineLayout, wgpu::BindGroupLayout) { + let bindgroup_layout = Self::create_bindgroup_layout(device, multisampled); + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: LABEL, + bind_group_layouts: &[&bindgroup_layout], + push_constant_ranges: &[], + }); + (pipeline_layout, bindgroup_layout) + } + + pub(crate) fn prepare(&self, lighting: &Lighting, depth_texture_size: UVec2) { + self.tiling_descriptor.modify(|d| { + d.depth_texture_size = depth_texture_size; + }); + lighting.lighting_descriptor.modify(|desc| { + desc.light_tiling_descriptor_id = self.tiling_descriptor.id(); + }); + } + + pub(crate) fn clear_tiles( + &self, + encoder: &mut wgpu::CommandEncoder, + bindgroup: &wgpu::BindGroup, + depth_texture_size: UVec2, + ) { + let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("light-tiling-clear-tiles"), + timestamp_writes: None, + }); + compute_pass.set_pipeline(&self.clear_tiles_pipeline); + compute_pass.set_bind_group(0, bindgroup, &[]); + + let tile_size = self.tiling_descriptor.get().tile_size; + let dims_f32 = depth_texture_size.as_vec2() / tile_size as f32; + let workgroups = (dims_f32 / 16.0).ceil().as_uvec2(); + let x = workgroups.x; + let y = workgroups.y; + let z = 1; + compute_pass.dispatch_workgroups(x, y, z); + } + + pub(crate) fn compute_min_max_depth( + &self, + encoder: &mut wgpu::CommandEncoder, + bindgroup: &wgpu::BindGroup, + depth_texture_size: UVec2, + ) { + let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("light-tiling-compute-min-max-depth"), + timestamp_writes: None, + }); + compute_pass.set_pipeline(&self.compute_min_max_depth_pipeline); + compute_pass.set_bind_group(0, bindgroup, &[]); + + let tile_size = self.tiling_descriptor.get().tile_size; + let x = (depth_texture_size.x / tile_size) + 1; + let y = (depth_texture_size.y / tile_size) + 1; + let z = 1; + compute_pass.dispatch_workgroups(x, y, z); + } + + pub(crate) fn compute_bins( + &self, + encoder: &mut wgpu::CommandEncoder, + bindgroup: &wgpu::BindGroup, + depth_texture_size: UVec2, + ) { + let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("light-tiling-compute-bins"), + timestamp_writes: None, + }); + compute_pass.set_pipeline(&self.compute_bins_pipeline); + compute_pass.set_bind_group(0, bindgroup, &[]); + + let tile_size = self.tiling_descriptor.get().tile_size; + let x = (depth_texture_size.x / tile_size) + 1; + let y = (depth_texture_size.y / tile_size) + 1; + let z = 1; + compute_pass.dispatch_workgroups(x, y, z); + } + + /// Get the bindgroup. + pub fn get_bindgroup( + &self, + device: &wgpu::Device, + geometry_slab: &SlabBuffer, + lighting_slab: &SlabBuffer, + depth_texture: &crate::texture::Texture, + ) -> Arc { + // UNWRAP: safe because we know there are elements + let latest_buffer_creation = [geometry_slab.creation_time(), lighting_slab.creation_time()] + .into_iter() + .max() + .unwrap(); + let prev_buffer_creation = self + .bindgroup_creation_time + .swap(latest_buffer_creation, std::sync::atomic::Ordering::Relaxed); + let prev_depth_texture_id = self + .depth_texture_id + .swap(depth_texture.id(), std::sync::atomic::Ordering::Relaxed); + let should_invalidate = prev_buffer_creation < latest_buffer_creation + || prev_depth_texture_id < depth_texture.id(); + self.bindgroup.get(should_invalidate, || { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("light-tiling"), + layout: &self.bindgroup_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: geometry_slab.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: lighting_slab.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(&depth_texture.view), + }, + ], + }) + }) + } + + /// Set the minimum illuminance, in lux, to determine if a light illuminates a tile. + pub fn set_minimum_illuminance(&self, minimum_illuminance_lux: f32) { + self.tiling_descriptor.modify(|desc| { + desc.minimum_illuminance_lux = minimum_illuminance_lux; + }); + } + + /// Run light tiling, resulting in edits to the lighting slab. + pub fn run(&self, stage: &Stage) { + let depth_texture = stage.depth_texture.read().unwrap(); + let depth_texture_size = depth_texture.size(); + let lighting = stage.as_ref(); + self.prepare(lighting, depth_texture_size); + + let light_slab = &lighting.light_slab; + let geometry_slab = &lighting.geometry_slab; + let runtime = light_slab.runtime(); + let label = Some("light-tiling-run"); + let bindgroup = self.get_bindgroup( + &runtime.device, + &geometry_slab.commit(), + &light_slab.commit(), + &depth_texture, + ); + + let mut encoder = runtime + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label }); + { + self.clear_tiles(&mut encoder, bindgroup.as_ref(), depth_texture_size); + self.compute_min_max_depth(&mut encoder, bindgroup.as_ref(), depth_texture_size); + self.compute_bins(&mut encoder, bindgroup.as_ref(), depth_texture_size); + } + runtime.queue.submit(Some(encoder.finish())); + } + + pub fn tiles(&self) -> &Ct::Container { + &self.tiles + } + + #[cfg(test)] + /// Read the tiles from the light slab. + pub(crate) async fn read_tiles(&self, lighting: &Lighting) -> Vec { + lighting + .light_slab + .read_array(self.tiling_descriptor.get().tiles_array) + .await + .unwrap() + } + + #[cfg(test)] + #[allow(dead_code)] + pub(crate) fn read_tile(&self, lighting: &Lighting, tile_coord: UVec2) -> LightTile { + let desc = self.tiling_descriptor.get(); + let tile_index = tile_coord.y * desc.tile_grid_size().x + tile_coord.x; + let tile_id = desc.tiles_array.at(tile_index as usize); + futures_lite::future::block_on(lighting.light_slab.read_one(tile_id)).unwrap() + } + + #[cfg(test)] + /// Returns a tuple containing an image of depth mins, depth maximums and number of lights. + pub(crate) async fn read_images( + &self, + lighting: &Lighting, + ) -> (image::GrayImage, image::GrayImage, image::GrayImage) { + use crabslab::Slab; + + use crate::light::dequantize_depth_u32_to_f32; + + let tile_dimensions = self.tiling_descriptor.get().tile_grid_size(); + let slab = lighting.light_slab.read(..).await.unwrap(); + let tiling_descriptor_id_in_lighting = lighting + .lighting_descriptor + .get() + .light_tiling_descriptor_id; + let tiling_descriptor_id = self.tiling_descriptor.id(); + assert_eq!(tiling_descriptor_id_in_lighting, tiling_descriptor_id); + let desc = slab.read( + lighting + .lighting_descriptor + .get() + .light_tiling_descriptor_id, + ); + let should_be_len = tile_dimensions.x * tile_dimensions.y; + if should_be_len != desc.tiles_array.len() as u32 { + log::error!( + "LightTilingDescriptor's tiles array is borked: {:?}\n\ + expected {should_be_len} tiles\n\ + tile_dimensions: {tile_dimensions}", + desc.tiles_array, + ); + } + let mut mins_img = image::GrayImage::new(tile_dimensions.x, tile_dimensions.y); + let mut maxs_img = image::GrayImage::new(tile_dimensions.x, tile_dimensions.y); + let mut lights_img = image::GrayImage::new(tile_dimensions.x, tile_dimensions.y); + slab.read_vec(desc.tiles_array) + .into_iter() + .enumerate() + .for_each(|(i, tile)| { + let x = i as u32 % tile_dimensions.x; + let y = i as u32 / tile_dimensions.x; + let min = dequantize_depth_u32_to_f32(tile.depth_min); + let max = dequantize_depth_u32_to_f32(tile.depth_max); + + mins_img.get_pixel_mut(x, y).0[0] = crate::math::scaled_f32_to_u8(min); + maxs_img.get_pixel_mut(x, y).0[0] = crate::math::scaled_f32_to_u8(max); + lights_img.get_pixel_mut(x, y).0[0] = crate::math::scaled_f32_to_u8( + tile.next_light_index as f32 / tile.lights_array.len() as f32, + ); + }); + + (mins_img, maxs_img, lights_img) + } +} + +/// Parameters for tuning light tiling. +#[derive(Debug, Clone, Copy)] +pub struct LightTilingConfig { + /// The size of each tile, in pixels. + /// + /// Default is `16`. + pub tile_size: u32, + /// The maximum number of lights per tile. + /// + /// Default is `32`. + pub max_lights_per_tile: u32, + /// The minimum illuminance, in lux. + /// + /// Used to determine the radius of illumination of a light, + /// which is then used to determine if a light illuminates a tile. + /// + /// * Moonlight: < 1 lux. + /// - Full moon on a clear night: 0.25 lux. + /// - Quarter moon: 0.01 lux + /// - Starlight overcast moonless night sky: 0.0001 lux. + /// * General indoor lighting: Around 100 to 300 lux. + /// * Office lighting: Typically around 300 to 500 lux. + /// * Reading or task lighting: Around 500 to 750 lux. + /// * Detailed work (e.g., drafting, surgery): 1000 lux or more. + /// + /// Default is `0.1`. + pub minimum_illuminance: f32, +} + +impl Default for LightTilingConfig { + fn default() -> Self { + LightTilingConfig { + tile_size: 16, + max_lights_per_tile: 32, + minimum_illuminance: 0.1, + } + } +} + +impl LightTiling { + /// Creates a new [`LightTiling`] struct with a [`HybridArray`] of tiles. + pub fn new_hybrid( + lighting: &Lighting, + multisampled: bool, + depth_texture_size: UVec2, + config: LightTilingConfig, + ) -> Self { + log::trace!("creating LightTiling"); + let lighting_slab = lighting.slab_allocator(); + let runtime = lighting_slab.runtime(); + let desc = LightTilingDescriptor { + depth_texture_size, + tile_size: config.tile_size, + minimum_illuminance_lux: config.minimum_illuminance, + ..Default::default() + }; + let tiling_descriptor = lighting_slab.new_value(desc); + lighting.lighting_descriptor.modify(|desc| { + desc.light_tiling_descriptor_id = tiling_descriptor.id(); + }); + log::trace!("created tiling descriptor: {tiling_descriptor:#?}"); + let tiled_size = desc.tile_grid_size(); + log::trace!(" grid size: {tiled_size}"); + let mut tiles = Vec::new(); + for _ in 0..tiled_size.x * tiled_size.y { + let lights = + lighting_slab.new_array(vec![Id::NONE; config.max_lights_per_tile as usize]); + tiles.push(LightTile { + lights_array: lights.array(), + ..Default::default() + }); + } + let tiles = lighting_slab.new_array(tiles); + tiling_descriptor.modify(|d| { + let tiles_array = tiles.array(); + log::trace!(" setting tiles array: {tiles_array:?}"); + d.tiles_array = tiles_array; + }); + let clear_tiles_pipeline = Arc::new(Self::create_clear_tiles_pipeline( + &runtime.device, + multisampled, + )); + let compute_min_max_depth_pipeline = Arc::new(Self::create_compute_min_max_depth_pipeline( + &runtime.device, + multisampled, + )); + let compute_bins_pipeline = Arc::new(Self::create_compute_bins_pipeline( + &runtime.device, + multisampled, + )); + let bindgroup_layout = + Arc::new(Self::create_bindgroup_layout(&runtime.device, multisampled)); + + Self { + tiling_descriptor, + tiles, + // The inner bindgroup is created on-demand + bindgroup: ManagedBindGroup::default(), + bindgroup_creation_time: Default::default(), + bindgroup_layout, + depth_texture_id: Default::default(), + clear_tiles_pipeline, + compute_min_max_depth_pipeline, + compute_bins_pipeline, + } + } +} + +impl LightTiling { + /// Creates a new [`LightTiling`] struct. + pub fn new( + lighting: &Lighting, + multisampled: bool, + depth_texture_size: UVec2, + config: LightTilingConfig, + ) -> Self { + // Note to self, I wish we had `fmap` here. + let LightTiling { + tiling_descriptor, + tiles, + bindgroup_creation_time, + depth_texture_id, + bindgroup_layout, + bindgroup, + clear_tiles_pipeline, + compute_min_max_depth_pipeline, + compute_bins_pipeline, + } = LightTiling::new_hybrid(lighting, multisampled, depth_texture_size, config); + Self { + tiling_descriptor, + tiles: tiles.into_gpu_only(), + depth_texture_id, + bindgroup, + bindgroup_layout, + bindgroup_creation_time, + clear_tiles_pipeline, + compute_min_max_depth_pipeline, + compute_bins_pipeline, + } + } +} diff --git a/crates/renderling/src/linkage.rs b/crates/renderling/src/linkage.rs index 4230a81b..8cc47c8b 100644 --- a/crates/renderling/src/linkage.rs +++ b/crates/renderling/src/linkage.rs @@ -25,6 +25,11 @@ pub mod debug_overlay_vertex; pub mod di_convolution_fragment; pub mod generate_mipmap_fragment; pub mod generate_mipmap_vertex; +pub mod light_tiling_bin_lights; +pub mod light_tiling_clear_tiles; +pub mod light_tiling_compute_tile_min_and_max_depth; +pub mod light_tiling_compute_tile_min_and_max_depth_multisampled; +pub mod light_tiling_depth_pre_pass; pub mod prefilter_environment_cubemap_fragment; pub mod prefilter_environment_cubemap_vertex; pub mod renderlet_fragment; diff --git a/crates/renderling/src/linkage/light_tiling_bin_lights.rs b/crates/renderling/src/linkage/light_tiling_bin_lights.rs new file mode 100644 index 00000000..6db4017f --- /dev/null +++ b/crates/renderling/src/linkage/light_tiling_bin_lights.rs @@ -0,0 +1,34 @@ +#![allow(dead_code)] +//! Automatically generated by Renderling's `build.rs`. +use crate::linkage::ShaderLinkage; +#[cfg(not(target_arch = "wasm32"))] +mod target { + pub const ENTRY_POINT: &str = "light::light_tiling_bin_lights"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_spirv!("../../shaders/light-light_tiling_bin_lights.spv") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!("creating native linkage for {}", "light_tiling_bin_lights"); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +#[cfg(target_arch = "wasm32")] +mod target { + pub const ENTRY_POINT: &str = "lightlight_tiling_bin_lights"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_wgsl!("../../shaders/light-light_tiling_bin_lights.wgsl") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!("creating web linkage for {}", "light_tiling_bin_lights"); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +pub fn linkage(device: &wgpu::Device) -> ShaderLinkage { + target::linkage(device) +} diff --git a/crates/renderling/src/linkage/light_tiling_clear_tiles.rs b/crates/renderling/src/linkage/light_tiling_clear_tiles.rs new file mode 100644 index 00000000..dcd8723b --- /dev/null +++ b/crates/renderling/src/linkage/light_tiling_clear_tiles.rs @@ -0,0 +1,34 @@ +#![allow(dead_code)] +//! Automatically generated by Renderling's `build.rs`. +use crate::linkage::ShaderLinkage; +#[cfg(not(target_arch = "wasm32"))] +mod target { + pub const ENTRY_POINT: &str = "light::light_tiling_clear_tiles"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_spirv!("../../shaders/light-light_tiling_clear_tiles.spv") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!("creating native linkage for {}", "light_tiling_clear_tiles"); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +#[cfg(target_arch = "wasm32")] +mod target { + pub const ENTRY_POINT: &str = "lightlight_tiling_clear_tiles"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_wgsl!("../../shaders/light-light_tiling_clear_tiles.wgsl") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!("creating web linkage for {}", "light_tiling_clear_tiles"); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +pub fn linkage(device: &wgpu::Device) -> ShaderLinkage { + target::linkage(device) +} diff --git a/crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth.rs b/crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth.rs new file mode 100644 index 00000000..9067d0d8 --- /dev/null +++ b/crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth.rs @@ -0,0 +1,40 @@ +#![allow(dead_code)] +//! Automatically generated by Renderling's `build.rs`. +use crate::linkage::ShaderLinkage; +#[cfg(not(target_arch = "wasm32"))] +mod target { + pub const ENTRY_POINT: &str = "light::light_tiling_compute_tile_min_and_max_depth"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_spirv!("../../shaders/light-light_tiling_compute_tile_min_and_max_depth.spv") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!( + "creating native linkage for {}", + "light_tiling_compute_tile_min_and_max_depth" + ); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +#[cfg(target_arch = "wasm32")] +mod target { + pub const ENTRY_POINT: &str = "lightlight_tiling_compute_tile_min_and_max_depth"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_wgsl!("../../shaders/light-light_tiling_compute_tile_min_and_max_depth.wgsl") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!( + "creating web linkage for {}", + "light_tiling_compute_tile_min_and_max_depth" + ); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +pub fn linkage(device: &wgpu::Device) -> ShaderLinkage { + target::linkage(device) +} diff --git a/crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth_multisampled.rs b/crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth_multisampled.rs new file mode 100644 index 00000000..22f3b90c --- /dev/null +++ b/crates/renderling/src/linkage/light_tiling_compute_tile_min_and_max_depth_multisampled.rs @@ -0,0 +1,44 @@ +#![allow(dead_code)] +//! Automatically generated by Renderling's `build.rs`. +use crate::linkage::ShaderLinkage; +#[cfg(not(target_arch = "wasm32"))] +mod target { + pub const ENTRY_POINT: &str = "light::light_tiling_compute_tile_min_and_max_depth_multisampled"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_spirv!( + "../../shaders/light-light_tiling_compute_tile_min_and_max_depth_multisampled.spv" + ) + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!( + "creating native linkage for {}", + "light_tiling_compute_tile_min_and_max_depth_multisampled" + ); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +#[cfg(target_arch = "wasm32")] +mod target { + pub const ENTRY_POINT: &str = "lightlight_tiling_compute_tile_min_and_max_depth_multisampled"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_wgsl!( + "../../shaders/light-light_tiling_compute_tile_min_and_max_depth_multisampled.wgsl" + ) + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!( + "creating web linkage for {}", + "light_tiling_compute_tile_min_and_max_depth_multisampled" + ); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +pub fn linkage(device: &wgpu::Device) -> ShaderLinkage { + target::linkage(device) +} diff --git a/crates/renderling/src/linkage/light_tiling_depth_pre_pass.rs b/crates/renderling/src/linkage/light_tiling_depth_pre_pass.rs new file mode 100644 index 00000000..0423c693 --- /dev/null +++ b/crates/renderling/src/linkage/light_tiling_depth_pre_pass.rs @@ -0,0 +1,37 @@ +#![allow(dead_code)] +//! Automatically generated by Renderling's `build.rs`. +use crate::linkage::ShaderLinkage; +#[cfg(not(target_arch = "wasm32"))] +mod target { + pub const ENTRY_POINT: &str = "light::light_tiling_depth_pre_pass"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_spirv!("../../shaders/light-light_tiling_depth_pre_pass.spv") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!( + "creating native linkage for {}", + "light_tiling_depth_pre_pass" + ); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +#[cfg(target_arch = "wasm32")] +mod target { + pub const ENTRY_POINT: &str = "lightlight_tiling_depth_pre_pass"; + pub fn descriptor() -> wgpu::ShaderModuleDescriptor<'static> { + wgpu::include_wgsl!("../../shaders/light-light_tiling_depth_pre_pass.wgsl") + } + pub fn linkage(device: &wgpu::Device) -> super::ShaderLinkage { + log::debug!("creating web linkage for {}", "light_tiling_depth_pre_pass"); + super::ShaderLinkage { + entry_point: ENTRY_POINT, + module: device.create_shader_module(descriptor()).into(), + } + } +} +pub fn linkage(device: &wgpu::Device) -> ShaderLinkage { + target::linkage(device) +} diff --git a/crates/renderling/src/material/cpu.rs b/crates/renderling/src/material/cpu.rs index 4a7e97f1..a8ef1a17 100644 --- a/crates/renderling/src/material/cpu.rs +++ b/crates/renderling/src/material/cpu.rs @@ -23,8 +23,7 @@ impl AsRef for Materials { impl Materials { pub fn new(runtime: impl AsRef, atlas_size: wgpu::Extent3d) -> Self { - let slab = - SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::empty(), Some("materials")); + let slab = SlabAllocator::new(runtime, "materials", wgpu::BufferUsages::empty()); let atlas = Atlas::new(&slab, atlas_size, None, Some("materials-atlas"), None); Self { slab, atlas } } diff --git a/crates/renderling/src/math.rs b/crates/renderling/src/math.rs index 530d13ae..6c30ffe4 100644 --- a/crates/renderling/src/math.rs +++ b/crates/renderling/src/math.rs @@ -6,15 +6,40 @@ //! run on the CPU. //! //! Lastly, it provides some constant geometry used in many shaders. -use core::ops::Mul; +use core::ops::{IndexMut, Mul}; +use crabslab::{Id, Slab}; use spirv_std::{ - image::{Cubemap, Image2d, Image2dArray}, + image::{sample_with, Cubemap, Image2d, Image2dArray, ImageWithMethods}, Image, Sampler, }; pub use glam::*; pub use spirv_std::num_traits::{clamp, Float, Zero}; +pub trait Fetch { + type Output; + + fn fetch(&self, coords: Coords) -> Self::Output; +} + +impl Fetch for Image!(2D, type=f32, sampled, depth) { + type Output = Vec4; + + fn fetch(&self, coords: UVec2) -> Self::Output { + self.fetch_with(coords, sample_with::lod(0)) + } +} + +impl Fetch for Image!(2D, type=f32, sampled, depth, multisampled=true) { + type Output = Vec4; + + fn fetch(&self, coords: UVec2) -> Self::Output { + // TODO: check whether this is doing what we think it's doing. + // (We think its doing roughly the same thing as the non-multisampled version above) + self.fetch_with(coords, sample_with::sample_index(0)) + } +} + pub trait IsSampler: Copy + Clone {} impl IsSampler for () {} @@ -88,6 +113,14 @@ mod cpu { /// value when sampled. pub struct ConstTexture(Vec4); + impl Fetch for ConstTexture { + type Output = Vec4; + + fn fetch(&self, _coords: UVec2) -> Self::Output { + self.0 + } + } + impl Sample2d for ConstTexture { type Sampler = (); @@ -133,6 +166,21 @@ mod cpu { } } + impl Fetch for CpuTexture2d + where + P: image::Pixel, + Container: std::ops::Deref, + { + type Output = Vec4; + + fn fetch(&self, coords: UVec2) -> Self::Output { + let x = coords.x.clamp(0, self.image.width() - 1); + let y = coords.y.clamp(0, self.image.height() - 1); + let p = self.image.get_pixel(x, y); + (self.convert_fn)(p) + } + } + impl Sample2d for CpuTexture2d where P: image::Pixel, @@ -143,14 +191,9 @@ mod cpu { fn sample_by_lod(&self, _sampler: Self::Sampler, uv: glam::Vec2, _lod: f32) -> Vec4 { // TODO: lerp the CPU texture sampling // TODO: use configurable wrap mode on CPU sampling - let px = uv.x.clamp(0.0, 1.0) * self.image.width() as f32; - let py = uv.y.clamp(0.0, 1.0) * self.image.height() as f32; - println!("sampling: ({px}, {py})"); - let p = self.image.get_pixel( - px.round().min(self.image.width() as f32) as u32, - py.round().min(self.image.height() as f32) as u32, - ); - (self.convert_fn)(p) + let px = uv.x.clamp(0.0, 1.0) * (self.image.width() as f32 - 1.0); + let py = uv.y.clamp(0.0, 1.0) * (self.image.height() as f32 - 1.0); + self.fetch(UVec2::new(px.round() as u32, py.round() as u32)) } } @@ -215,10 +258,20 @@ mod cpu { u as f32 / 255.0 } + pub fn luma_u8_to_vec4(p: &image::Luma) -> Vec4 { + let shade = scaled_u8_to_f32(p.0[0]); + Vec3::splat(shade).extend(1.0) + } + /// Convert an f32 in range 0.0 - 1.0 into a u8 in range 0-255. pub fn scaled_f32_to_u8(f: f32) -> u8 { (f * 255.0) as u8 } + + /// Convert a u32 in rang 0-u32::MAX to a u8 in rang 0-255. + pub fn scaled_u32_to_u8(u: u32) -> u8 { + ((u as f32 / u32::MAX as f32) * 255.0) as u8 + } } #[cfg(not(target_arch = "spirv"))] pub use cpu::*; @@ -232,6 +285,9 @@ pub use cpu::*; /// See [this issue](https://github.com/gfx-rs/naga/issues/2461) and `crate::linkage::test` /// for more info. pub trait IsVector { + /// Type returned by the `orthogonal_vectors` extension function. + type OrthogonalVectors; + /// Normalize or return zero. fn alt_norm_or_zero(&self) -> Self; @@ -241,9 +297,14 @@ pub trait IsVector { /// Returns the dot product of a vector with itself (the square of its /// length). fn dot2(&self) -> f32; + + /// Returns normalized orthogonal vectors. + fn orthonormal_vectors(&self) -> Self::OrthogonalVectors; } impl IsVector for glam::Vec2 { + type OrthogonalVectors = Vec2; + fn alt_norm_or_zero(&self) -> Self { if self.length().is_zero() { glam::Vec2::ZERO @@ -259,9 +320,15 @@ impl IsVector for glam::Vec2 { fn dot2(&self) -> f32 { self.dot(*self) } + + fn orthonormal_vectors(&self) -> Self::OrthogonalVectors { + Vec3::new(self.x, self.y, 0.0).cross(Vec3::Z).xy() + } } impl IsVector for glam::Vec3 { + type OrthogonalVectors = [Vec3; 2]; + fn alt_norm_or_zero(&self) -> Self { if self.length().is_zero() { glam::Vec3::ZERO @@ -281,6 +348,31 @@ impl IsVector for glam::Vec3 { fn dot2(&self) -> f32 { self.dot(*self) } + + fn orthonormal_vectors(&self) -> Self::OrthogonalVectors { + // From https://graphics.pixar.com/library/OrthonormalB/paper.pdf + let s = self.alt_norm_or_zero(); + let sign = signum_or_zero(s.z); + let a = -1.0 / (sign + s.z); + let b = s.x * s.y * a; + [ + Self::new(1.0 + sign * s.x * s.x * a, sign * b, -sign * s.x), + Self::new(b, sign + s.y * s.y * a, -s.y), + ] + } +} + +/// Quantize an f32 + +/// Determine the distance from a point to a line segment. +pub fn distance_to_line(p: Vec3, a: Vec3, b: Vec3) -> f32 { + let ab_distance = a.distance(b); + if ab_distance <= f32::EPSILON { + p.distance(a) + } else { + let tri_area = (p - a).cross(p - b).length(); + tri_area / ab_distance + } } /// Additional/replacement methods for glam matrix types. @@ -442,7 +534,13 @@ pub fn smoothstep(edge_in: f32, edge_out: f32, mut x: f32) -> f32 { pub fn triangle_face_normal(p1: Vec3, p2: Vec3, p3: Vec3) -> Vec3 { let a = p1 - p2; let b = p1 - p3; - let n: Vec3 = a.cross(b).normalize(); + let n: Vec3 = a.cross(b).alt_norm_or_zero(); + #[cfg(cpu)] + debug_assert_ne!( + Vec3::ZERO, + n, + "normal is zero - p1: {p1}, p2: {p2}, p3: {p3}" + ); n } @@ -590,6 +688,75 @@ pub const fn convex_mesh([p0, p1, p2, p3, p4, p5, p6, p7]: [Vec3; 8]) -> [Vec3; ] } +/// An PCG PRNG that is optimized for GPUs, in that it is fast to evaluate and accepts +/// sequential ids as it's initial state without sacrificing on RNG quality. +/// +/// https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/ +/// https://jcgt.org/published/0009/03/02/ +/// +/// Thanks to Firestar99 at +/// +pub struct GpuRng(pub u32); + +impl GpuRng { + pub fn new(state: u32) -> GpuRng { + Self(state) + } + + pub fn gen(&mut self) -> u32 { + let state = self.0; + self.0 = if cfg!(gpu) { + self.0 * 747796405 + 2891336453 + } else { + self.0.wrapping_sub(747796405).wrapping_add(2891336453) + }; + let word = (state >> ((state >> 28) + 4)) ^ state; + let word = if cfg!(gpu) { + word * 277803737 + } else { + word.wrapping_mul(277803737) + }; + (word >> 22) ^ word + } + + pub fn gen_u32(&mut self, min: u32, max: u32) -> u32 { + let range = max - min; + let percent = self.gen_f32(0.0, 1.0); + min + (range as f32 * percent).round() as u32 + } + + pub fn gen_f32(&mut self, min: f32, max: f32) -> f32 { + let range = max - min; + let numerator = self.gen(); + let percentage = numerator as f32 / u32::MAX as f32; + min + range * percentage + } + + pub fn gen_vec3(&mut self, min: Vec3, max: Vec3) -> Vec3 { + let x = self.gen_f32(min.x, max.x); + let y = self.gen_f32(min.y, max.y); + let z = self.gen_f32(min.z, max.z); + Vec3::new(x, y, z) + } + + pub fn gen_vec2(&mut self, min: Vec2, max: Vec2) -> Vec2 { + let x = self.gen_f32(min.x, max.x); + let y = self.gen_f32(min.y, max.y); + Vec2::new(x, y) + } +} + +/// Convert a pixel coordinate in screen space (origin is top left, Y increases downwards) +/// to normalized device coordinates (origin is center, Y increses upwards). +pub fn convert_pixel_to_ndc(pixel_coord: Vec2, viewport_size: UVec2) -> Vec2 { + // Normalize the point to the range [0.0, 1.0]; + let mut normalized = pixel_coord / viewport_size.as_vec2(); + // Flip the Y axis to increase upward + normalized.y = 1.0 - normalized.y; + // Move the origin to the center + (normalized * 2.0) - 1.0 +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/renderling/src/pbr.rs b/crates/renderling/src/pbr.rs index ef561fcf..2bbdc0f0 100644 --- a/crates/renderling/src/pbr.rs +++ b/crates/renderling/src/pbr.rs @@ -136,7 +136,7 @@ fn fresnel_schlick_roughness(cos_theta: f32, f0: Vec3, roughness: f32) -> Vec3 { } #[allow(clippy::too_many_arguments)] -fn outgoing_radiance( +pub fn outgoing_radiance( light_color: Vec4, albedo: Vec3, attenuation: f32, @@ -288,6 +288,7 @@ pub fn fragment_impl( renderlet_id: Id, + frag_coord: Vec4, in_color: Vec4, in_uv0: Vec2, in_uv1: Vec2, @@ -534,6 +535,7 @@ pub fn fragment_impl( specular, brdf, lighting_slab, + frag_coord, ) } else { crate::println!("no shading!"); @@ -562,6 +564,7 @@ pub fn shade_fragment( brdf: Vec2, light_slab: &[u32], + frag_coord: Vec4, ) -> Vec4 where S: IsSampler, @@ -572,18 +575,30 @@ where // There is always a `LightingDescriptor` stored at index `0` of the // light slab. let lighting_desc = light_slab.read_unchecked(Id::::new(0)); - let analytical_lights_array = lighting_desc.analytical_lights_array; + // If light tiling is enabled, use the pre-computed tile's light list + let analytical_lights_array = if lighting_desc.light_tiling_descriptor_id.is_none() { + lighting_desc.analytical_lights_array + } else { + let tiling_descriptor = light_slab.read_unchecked(lighting_desc.light_tiling_descriptor_id); + let tile_index = tiling_descriptor.tile_index_for_fragment(frag_coord.xy()); + let tile = light_slab.read_unchecked(tiling_descriptor.tiles_array.at(tile_index)); + tile.lights_array + }; my_println!("lights: {analytical_lights_array:?}"); - my_println!("n: {n:?}"); - my_println!("v: {v:?}"); + my_println!("surface normal: {n:?}"); + my_println!("vector from surface to camera: {v:?}"); // accumulated outgoing radiance let mut lo = Vec3::ZERO; - for i in 0..analytical_lights_array.len() { + for light_id_id in analytical_lights_array.iter() { // calculate per-light radiance - let light_id = light_slab.read(analytical_lights_array.at(i)); + let light_id = light_slab.read(light_id_id); + if light_id.is_none() { + break; + } let light = light_slab.read(light_id); let transform = light_slab.read(light.transform_id); + crate::println!("transform: {transform:?}"); let transform = Mat4::from(transform); // determine the light ray and the radiance @@ -595,13 +610,18 @@ where intensity, } = light_slab.read(light.into_point_id()); let position = transform.transform_point3(position); + // This definitely is the direction pointing from fragment to the light. + // It needs to stay this way. + // For more info, see + // let frag_to_light = position - in_pos; let distance = frag_to_light.length(); if distance == 0.0 { + crate::println!("distance between point light and surface is zero"); continue; } let l = frag_to_light.alt_norm_or_zero(); - let attenuation = intensity * 1.0 / (distance * distance); + let attenuation = intensity / (distance * distance); let radiance = outgoing_radiance(color, albedo, attenuation, v, l, n, metallic, roughness); let shadow = if light.shadow_map_desc_id.is_some() { @@ -623,6 +643,7 @@ where let spot_light_descriptor = light_slab.read(light.into_spot_id()); let calculation = SpotLightCalculation::new(spot_light_descriptor, transform, in_pos); + crate::println!("calculation: {calculation:#?}"); if calculation.frag_to_light_distance == 0.0 { continue; } @@ -671,6 +692,8 @@ where (radiance, shadow) } }; + crate::println!("radiance: {radiance}"); + crate::println!("shadow: {shadow}"); lo += radiance * (1.0 - shadow); } diff --git a/crates/renderling/src/skybox/cpu.rs b/crates/renderling/src/skybox/cpu.rs index 878a58a7..0281556e 100644 --- a/crates/renderling/src/skybox/cpu.rs +++ b/crates/renderling/src/skybox/cpu.rs @@ -187,8 +187,7 @@ impl Skybox { let runtime = runtime.as_ref(); log::trace!("creating skybox"); - let slab = - SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::VERTEX, Some("skybox-slab")); + let slab = SlabAllocator::new(runtime, "skybox-slab", wgpu::BufferUsages::VERTEX); let proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, 10.0); let camera = slab.new_value(Camera::default().with_projection(proj)); @@ -409,6 +408,7 @@ impl Skybox { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() @@ -529,6 +529,7 @@ impl Skybox { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() @@ -630,6 +631,7 @@ impl Skybox { load: wgpu::LoadOp::Clear(wgpu::Color::RED), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() @@ -685,7 +687,7 @@ mod test { 0, Some(wgpu::Origin3d { x: 0, y: 0, z: i }), ); - let pixels = copied_buffer.pixels(ctx.get_device()); + let pixels = copied_buffer.pixels(ctx.get_device()).unwrap(); let pixels = bytemuck::cast_slice::(pixels.as_slice()) .iter() .map(|p| half::f16::from_bits(*p).to_f32()) @@ -708,7 +710,7 @@ mod test { mip_level, Some(wgpu::Origin3d { x: 0, y: 0, z: i }), ); - let pixels = copied_buffer.pixels(ctx.get_device()); + let pixels = copied_buffer.pixels(ctx.get_device()).unwrap(); let pixels = bytemuck::cast_slice::(pixels.as_slice()) .iter() .map(|p| half::f16::from_bits(*p).to_f32()) @@ -740,7 +742,7 @@ mod test { let brdf_lut = Skybox::create_precomputed_brdf_texture(&r); assert_eq!(wgpu::TextureFormat::Rg16Float, brdf_lut.texture.format()); let copied_buffer = Texture::read(&r, &brdf_lut.texture, 512, 512, 2, 2); - let pixels = copied_buffer.pixels(r.get_device()); + let pixels = copied_buffer.pixels(r.get_device()).unwrap(); let pixels: Vec = bytemuck::cast_slice::(pixels.as_slice()) .iter() .copied() diff --git a/crates/renderling/src/stage.rs b/crates/renderling/src/stage.rs index 2df876d6..486f7f4e 100644 --- a/crates/renderling/src/stage.rs +++ b/crates/renderling/src/stage.rs @@ -81,7 +81,7 @@ impl Skin { /// For more info on morph targets, see /// #[derive(Clone, Copy, Default, PartialEq, SlabItem)] -#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] +#[cfg_attr(cpu, derive(Debug))] pub struct MorphTarget { pub position: Vec3, pub normal: Vec3, @@ -183,6 +183,19 @@ impl Vertex { .alt_norm_or_zero() .extend(n_cross_t_dot_s_sign) } + + #[cfg(cpu)] + /// A triangle list mesh of points. + pub fn cube_mesh() -> [Vertex; 36] { + let mut mesh = [Vertex::default(); 36]; + let unit_cube = crate::math::unit_cube(); + debug_assert_eq!(36, unit_cube.len()); + for (i, (position, normal)) in unit_cube.into_iter().enumerate() { + mesh[i].position = position; + mesh[i].normal = normal; + } + mesh + } } /// A draw call used to render some geometry. @@ -403,8 +416,12 @@ pub fn renderlet_fragment( #[spirv(storage_buffer, descriptor_set = 0, binding = 10)] light_slab: &[u32], #[spirv(descriptor_set = 0, binding = 11)] shadow_map: &Image!(2D, type=f32, sampled, arrayed), #[spirv(descriptor_set = 0, binding = 12)] shadow_map_sampler: &Sampler, + #[cfg(feature = "debug-slab")] + #[spirv(storage_buffer, descriptor_set = 0, binding = 13)] + debug_slab: &mut [u32], #[spirv(flat)] renderlet_id: Id, + #[spirv(frag_coord)] frag_coord: Vec4, in_color: Vec4, in_uv0: Vec2, in_uv1: Vec2, @@ -430,6 +447,7 @@ pub fn renderlet_fragment( material_slab, light_slab, renderlet_id, + frag_coord, in_color, in_uv0, in_uv1, @@ -460,132 +478,6 @@ pub fn test_i8_i16_extraction( } } -#[cfg(feature = "test_spirv_atomics")] -#[spirv(compute(threads(32)))] -pub fn test_atomic_i_increment( - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] global_index: &mut u32, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] times: &u32, -) { - let mut i = 0u32; - loop { - if i >= *times { - break; - } - let _ = unsafe { - spirv_std::arch::atomic_i_increment::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::NONE.bits() }, - >(global_index) - }; - i += 1; - } -} - -#[cfg(feature = "test_spirv_atomics")] -#[spirv(compute(threads(32)))] -pub fn test_atomic_load_and_store( - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] global_index: &mut u32, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] times: &u32, -) { - for _ in 0..*times { - let loaded = unsafe { - spirv_std::arch::atomic_load::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::NONE.bits() }, - >(global_index) - }; - unsafe { - spirv_std::arch::atomic_store::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::NONE.bits() }, - >(global_index, loaded + 2) - }; - } -} - -#[cfg(feature = "test_spirv_atomics")] -#[spirv(compute(threads(32)))] -pub fn test_atomic_exchange( - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] global_index: &mut u32, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] times: &u32, -) { - let mut n = 0u32; - for _ in 0..*times { - n += unsafe { - spirv_std::arch::atomic_exchange::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::NONE.bits() }, - >(global_index, n) - }; - } -} - -#[cfg(feature = "test_spirv_atomics")] -#[spirv(compute(threads(32)))] -pub fn test_atomic_compare_exchange( - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] global_index: &mut u32, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] times: &u32, -) { - for n in 0..*times { - unsafe { - spirv_std::arch::atomic_compare_exchange::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() }, - { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() }, - >(global_index, n, 3) - }; - } -} - -#[cfg(feature = "test_spirv_atomics")] -#[spirv(compute(threads(32)))] -pub fn test_atomic_i_decrement( - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] global_index: &mut u32, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output: &mut [u32], -) { - loop { - let i = unsafe { - spirv_std::arch::atomic_i_decrement::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::NONE.bits() }, - >(global_index) - }; - output[i as usize] = i; - if i == 0 { - break; - } - } -} - -#[cfg(feature = "test_spirv_atomics")] -#[spirv(compute(threads(32)))] -pub fn test_atomic_i_add_sub( - #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] global_index: &mut u32, - #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output: &mut [u32], -) { - let i = unsafe { - spirv_std::arch::atomic_i_add::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::NONE.bits() }, - >(global_index, 2) - }; - - output[i as usize] = unsafe { - spirv_std::arch::atomic_i_sub::< - u32, - { spirv_std::memory::Scope::Workgroup as u32 }, - { spirv_std::memory::Semantics::NONE.bits() }, - >(global_index, i) - }; -} - #[cfg(test)] mod test { use craballoc::{prelude::SlabAllocator, runtime::CpuRuntime}; @@ -629,7 +521,7 @@ mod test { } #[expect(clippy::needless_borrows_for_generic_args, reason = "riffraff")] - let slab = SlabAllocator::::new(&CpuRuntime, ()); + let slab = SlabAllocator::::new(&CpuRuntime, "transform", ()); let a = NestedTransform::new(&slab); a.set(Transform { translation: Vec3::splat(100.0), diff --git a/crates/renderling/src/stage/cpu.rs b/crates/renderling/src/stage/cpu.rs index 93bed9ce..1b70cb9a 100644 --- a/crates/renderling/src/stage/cpu.rs +++ b/crates/renderling/src/stage/cpu.rs @@ -17,8 +17,8 @@ use crate::{ draw::DrawCalls, geometry::Geometry, light::{ - AnalyticalLightBundle, Light, LightDetails, Lighting, LightingBindGroupLayoutEntries, - LightingError, ShadowMap, + AnalyticalLight, Light, LightDetails, LightTiling, LightTilingConfig, Lighting, + LightingBindGroupLayoutEntries, LightingError, ShadowMap, }, material::Materials, pbr::debug::DebugChannel, @@ -96,6 +96,30 @@ impl StageCommitResult { .max() .unwrap_or_default() } + + pub fn should_invalidate(&self, previous_creation_time: usize) -> bool { + let mut should = false; + if self.geometry_buffer.is_new_this_commit() { + log::trace!("geometry buffer is new this frame"); + should = true; + } + if self.materials_buffer.is_new_this_commit() { + log::trace!("materials buffer is new this frame"); + should = true; + } + if self.lighting_buffer.is_new_this_commit() { + log::trace!("lighting buffer is new this frame"); + should = true; + } + let current = self.latest_creation_time(); + if current > previous_creation_time { + log::trace!( + "current latest buffer creation time {current} > previous {previous_creation_time}" + ); + should = true; + } + should + } } struct RenderletBindGroup<'a> { @@ -200,12 +224,13 @@ impl StageRendering<'_> { current_renderlet_bind_group_creation_time, std::sync::atomic::Ordering::Relaxed, ); - let should_invalidate_renderlet_bind_group = current_renderlet_bind_group_creation_time - > previous_renderlet_bind_group_creation_time; + let should_invalidate_renderlet_bind_group = + commit_result.should_invalidate(previous_renderlet_bind_group_creation_time); let renderlet_bind_group = self.stage .renderlet_bind_group .get(should_invalidate_renderlet_bind_group, || { + log::trace!("recreating renderlet bind group"); let atlas_texture = self.stage.materials.atlas().get_texture(); let skybox = self.stage.skybox.read().unwrap(); let shadow_map = self.stage.lighting.shadow_map_atlas.get_texture(); @@ -291,7 +316,7 @@ impl<'a> RenderletBuilder<'a, ()> { } impl<'a, T: Bundle> RenderletBuilder<'a, T> { - fn suffix(self, element: S) -> RenderletBuilder<'a, T::Suffixed> { + pub fn suffix(self, element: S) -> RenderletBuilder<'a, T::Suffixed> { RenderletBuilder { data: self.data, resources: self.resources.suffix(element), @@ -482,7 +507,7 @@ impl Stage { self.geometry.new_camera(camera) } - /// Set the given camera as the default one to use when rendering. + /// Use the given camera when rendering. pub fn use_camera(&self, camera: impl AsRef>) { self.geometry.use_camera(camera); } @@ -654,18 +679,13 @@ impl Stage { /// - [`DirectionalLightDescriptor`](crate::light::DirectionalLightDescriptor) /// - [`SpotLightDescriptor`](crate::light::SpotLightDescriptor) /// - [`PointLightDescriptor`](crate::light::PointLightDescriptor) - pub fn new_analytical_light( - &self, - light_descriptor: T, - nested_transform: Option, - ) -> AnalyticalLightBundle + pub fn new_analytical_light(&self, light_descriptor: T) -> AnalyticalLight where T: Clone + Copy + SlabItem + Send + Sync, Light: From>, LightDetails: From>, { - self.lighting - .new_analytical_light(light_descriptor, nested_transform) + self.lighting.new_analytical_light(light_descriptor) } /// Add an [`AnalyticalLightBundle`] to the internal list of lights. @@ -674,7 +694,7 @@ impl Stage { /// /// This can be used to add the light back to the scene after using /// [`Stage::remove_light`]. - pub fn add_light(&self, bundle: &AnalyticalLightBundle) { + pub fn add_light(&self, bundle: &AnalyticalLight) { self.lighting.add_light(bundle) } @@ -683,15 +703,27 @@ impl Stage { /// Use this to exclude a light from rendering, without dropping the light. /// /// After calling this function you can include the light again using [`Stage::add_light`]. - pub fn remove_light(&self, bundle: &AnalyticalLightBundle) { + pub fn remove_light(&self, bundle: &AnalyticalLight) { self.lighting.remove_light(bundle); } /// Enable shadow mapping for the given [`AnalyticalLightBundle`], creating /// a new [`ShadowMap`]. + /// + /// ## Tips for making a good shadow map + /// + /// 1. Make sure the map is big enough. + /// Using a big map can fix some peter panning issues, even before + /// messing with bias in the [`ShadowMapDescriptor`]. + /// The bigger the map, the cleaner the shadows will be. This can + /// also solve PCF problems. + /// 2. Don't set PCF samples too high in the [`ShadowMapDescriptor`], as + /// this can _cause_ peter panning. + /// 3. Ensure the **znear** and **zfar** parameters make sense, as the + /// shadow map uses these to determine how much of the scene to cover. pub fn new_shadow_map( &self, - analytical_light_bundle: &AnalyticalLightBundle, + analytical_light_bundle: &AnalyticalLight, // Size of the shadow map size: UVec2, // Distance to the near plane of the shadow map's frustum. @@ -707,6 +739,19 @@ impl Stage { .lighting .new_shadow_map(analytical_light_bundle, size, z_near, z_far)?) } + + /// Enable light tiling, creating a new [`LightTiling`]. + pub fn new_light_tiling(&self, config: LightTilingConfig) -> LightTiling { + let lighting = self.as_ref(); + let multisampled = self.get_msaa_sample_count() > 1; + let depth_texture_size = self.get_depth_texture().size(); + LightTiling::new( + lighting, + multisampled, + UVec2::new(depth_texture_size.width, depth_texture_size.height), + config, + ) + } } impl Stage { @@ -923,13 +968,16 @@ impl Stage { let runtime = ctx.runtime(); let device = &runtime.device; let resolution @ UVec2 { x: w, y: h } = ctx.get_size(); - let atlas_size = *ctx.atlas_size.read().unwrap(); + let stage_config = *ctx.stage_config.read().unwrap(); let geometry = Geometry::new( ctx, resolution, - UVec2::new(atlas_size.width, atlas_size.height), + UVec2::new( + stage_config.atlas_size.width, + stage_config.atlas_size.height, + ), ); - let materials = Materials::new(runtime, atlas_size); + let materials = Materials::new(runtime, stage_config.atlas_size); let multisample_count = 1; let hdr_texture = Arc::new(RwLock::new(Texture::create_hdr_texture( device, @@ -953,13 +1001,13 @@ impl Stage { multisample_count, ); let geometry_buffer = geometry.slab_allocator().commit(); - let lighting = Lighting::new(&geometry); + let lighting = Lighting::new(stage_config.shadow_map_atlas_size, &geometry); Self { materials, draw_calls: Arc::new(RwLock::new(DrawCalls::new( ctx, - true, + ctx.get_use_direct_draw(), &geometry_buffer, &depth_texture, ))), @@ -1006,6 +1054,12 @@ impl Stage { self } + /// Return the multisample count. + pub fn get_msaa_sample_count(&self) -> u32 { + self.msaa_sample_count + .load(std::sync::atomic::Ordering::Relaxed) + } + /// Set the MSAA multisample count. /// /// Set to `1` to disable MSAA. Setting to `0` will be treated the same as @@ -1403,12 +1457,14 @@ impl Stage { ops: mk_ops(wgpu::StoreOp::Discard), view: msaa_view, resolve_target: Some(&hdr_texture.view), + depth_slice: None, } } else { wgpu::RenderPassColorAttachment { ops: mk_ops(wgpu::StoreOp::Store), view: &hdr_texture.view, resolve_target: None, + depth_slice: None, } }; @@ -1493,7 +1549,7 @@ impl Stage { /// Only available on CPU. #[derive(Clone)] pub struct NestedTransform { - global_transform: Ct::Container, + pub(crate) global_transform: Ct::Container, local_transform: Arc>, children: Arc>>, parent: Arc>>, @@ -1554,18 +1610,7 @@ impl NestedTransform { nested } - /// Moves the inner `Gpu` of the global transform to a different - /// slab. - /// - /// This is used by the GLTF parser to move light's node transforms to the - /// light slab after they are created, while keeping any geometry's node - /// transforms untouched. - pub(crate) fn move_gpu_to_slab(&mut self, slab: &SlabAllocator) { - self.global_transform = slab.new_value(Transform::default()); - self.mark_dirty(); - } - - pub fn get_notifier_index(&self) -> usize { + pub fn get_notifier_index(&self) -> SourceId { self.global_transform.notifier_index() } @@ -1597,10 +1642,12 @@ impl NestedTransform { }); } + /// Returns the local transform. pub fn get(&self) -> Transform { *self.local_transform.read().unwrap() } + /// Returns the global transform. pub fn get_global_transform(&self) -> Transform { let maybe_parent_guard = self.parent.read().unwrap(); let transform = self.get(); @@ -1611,6 +1658,16 @@ impl NestedTransform { Transform::from(Mat4::from(parent_transform) * Mat4::from(transform)) } + /// Get a vector containing all the transforms up to the root. + pub fn get_all_transforms(&self) -> Vec { + let mut transforms = vec![]; + if let Some(parent) = self.parent() { + transforms.extend(parent.get_all_transforms()); + } + transforms.push(self.get()); + transforms + } + pub fn global_transform_id(&self) -> Id { self.global_transform.id() } @@ -1687,7 +1744,7 @@ mod test { clippy::needless_borrows_for_generic_args, reason = "This is just riff-raff, as it doesn't compile without the borrow." )] - let slab = SlabAllocator::::new(&CpuRuntime, ()); + let slab = SlabAllocator::::new(&CpuRuntime, "transform", ()); // Setup a hierarchy of transforms log::info!("new"); let root = NestedTransform::new(&slab); diff --git a/crates/renderling/src/stage/gltf_support.rs b/crates/renderling/src/stage/gltf_support.rs index 9bab4c25..f71f21f6 100644 --- a/crates/renderling/src/stage/gltf_support.rs +++ b/crates/renderling/src/stage/gltf_support.rs @@ -11,7 +11,7 @@ use crate::{ atlas::{AtlasError, AtlasImage, AtlasTexture, TextureAddressMode, TextureModes}, camera::Camera, light::{ - AnalyticalLightBundle, DirectionalLightDescriptor, LightStyle, PointLightDescriptor, + AnalyticalLight, DirectionalLightDescriptor, LightStyle, PointLightDescriptor, SpotLightDescriptor, }, pbr::Material, @@ -315,7 +315,7 @@ pub struct GltfPrimitive { impl GltfPrimitive { pub fn from_gltf( - stage: &mut Stage, + stage: &Stage, primitive: gltf::Primitive, buffer_data: &[gltf::buffer::Data], materials: &HybridArray, @@ -581,7 +581,7 @@ pub struct GltfMesh { impl GltfMesh { fn from_gltf( - stage: &mut Stage, + stage: &Stage, buffer_data: &[gltf::buffer::Data], materials: &HybridArray, mesh: gltf::Mesh, @@ -616,7 +616,7 @@ impl AsRef> for GltfCamera { } impl GltfCamera { - fn new(stage: &mut Stage, gltf_camera: gltf::Camera<'_>, transform: &NestedTransform) -> Self { + fn new(stage: &Stage, gltf_camera: gltf::Camera<'_>, transform: &NestedTransform) -> Self { log::debug!("camera: {}", gltf_camera.name().unwrap_or("unknown")); log::debug!(" transform: {:#?}", transform.get_global_transform()); let projection = match gltf_camera.projection() { @@ -711,7 +711,7 @@ pub struct GltfSkin { impl GltfSkin { pub fn from_gltf( - stage: &mut Stage, + stage: &Stage, buffer_data: &[gltf::buffer::Data], nodes: &[GltfNode], skin: gltf::Skin, @@ -774,7 +774,7 @@ pub struct GltfDocument { pub default_scene: Option, pub extensions: Option, pub textures: Vec>, - pub lights: Vec, + pub lights: Vec, pub meshes: Vec, pub nodes: Vec, pub materials: HybridArray, @@ -787,7 +787,7 @@ pub struct GltfDocument { impl GltfDocument { pub fn from_gltf( - stage: &mut Stage, + stage: &Stage, document: &gltf::Document, buffer_data: Vec, images: Vec, @@ -901,12 +901,11 @@ impl GltfDocument { fn transform_for_node( nesting_level: usize, - stage: &mut Stage, + stage: &Stage, cache: &mut HashMap, node: &gltf::Node, ) -> NestedTransform { - let padding = std::iter::repeat(" ") - .take(nesting_level * 2) + let padding = std::iter::repeat_n(" ", nesting_level * 2) .collect::>() .join(""); let nt = if let Some(nt) = cache.get(&node.index()) { @@ -1007,62 +1006,61 @@ impl GltfDocument { }, )?; - let mut node_transform = node_transforms + let node_transform = node_transforms .get(&node_index) .context(MissingNodeSnafu { index: node_index })? .clone(); - node_transform.move_gpu_to_slab(stage.lighting.slab_allocator()); let color = Vec3::from(gltf_light.color()).extend(1.0); let intensity = gltf_light.intensity(); - let light_bundle = match gltf_light.kind() { + let mut light_bundle = match gltf_light.kind() { gltf::khr_lights_punctual::Kind::Directional => { - stage.new_analytical_light( - DirectionalLightDescriptor { - direction: Vec3::NEG_Z, - color, - // TODO: Set a unit for lighting. - // We don't yet use a unit for our lighting, and we should. - // https://www.realtimerendering.com/blog/physical-units-for-lights/ - // - // NOTE: - // glTF spec [1] says directional light is in lux, whereas spot and point are - // in candelas. I haven't really set a unit, it's implicit in the shader, but it seems we - // can roughly get candelas from lux by dividing by 683 [2]. - // 1. https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md - // 2. https://depts.washington.edu/mictech/optics/me557/Radiometry.pdf - // 3. https://projects.blender.org/blender/blender-addons/commit/9d903a93f03b - intensity: intensity / 683.0, - }, - Some(node_transform), - ) + stage.new_analytical_light(DirectionalLightDescriptor { + direction: Vec3::NEG_Z, + color, + // TODO: Set a unit for lighting. + // We don't yet use a unit for our lighting, and we should. + // https://www.realtimerendering.com/blog/physical-units-for-lights/ + // + // NOTE: + // glTF spec [1] says directional light is in lux, whereas spot and point are + // in candelas. I haven't really set a unit, it's implicit in the shader, but it seems we + // can roughly get candelas from lux by dividing by 683 [2]. + // 1. https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md + // 2. https://depts.washington.edu/mictech/optics/me557/Radiometry.pdf + // 3. https://projects.blender.org/blender/blender-addons/commit/9d903a93f03b + intensity: intensity / 683.0, + }) } - gltf::khr_lights_punctual::Kind::Point => stage.new_analytical_light( - PointLightDescriptor { + gltf::khr_lights_punctual::Kind::Point => { + stage.new_analytical_light(PointLightDescriptor { position: Vec3::ZERO, color, intensity: intensity / 683.0, - }, - Some(node_transform), - ), + }) + } gltf::khr_lights_punctual::Kind::Spot { inner_cone_angle, outer_cone_angle, - } => stage.new_analytical_light( - SpotLightDescriptor { - position: Vec3::ZERO, - direction: Vec3::NEG_Z, - inner_cutoff: inner_cone_angle, - outer_cutoff: outer_cone_angle, - color, - intensity: intensity / (683.0 * 4.0 * std::f32::consts::PI), - }, - Some(node_transform), - ), + } => stage.new_analytical_light(SpotLightDescriptor { + position: Vec3::ZERO, + direction: Vec3::NEG_Z, + inner_cutoff: inner_cone_angle, + outer_cutoff: outer_cone_angle, + color, + intensity: intensity / (683.0 * 4.0 * std::f32::consts::PI), + }), }; + log::trace!( + " linking light {:?} with node transform {:?}: {:#?}", + light_bundle.light().id(), + node_transform.global_transform_id(), + node_transform.get_global_transform() + ); + light_bundle.link_node_transform(&node_transform); lights.push(light_bundle); } } @@ -1176,7 +1174,7 @@ impl GltfDocument { impl Stage { pub fn load_gltf_document_from_path( - &mut self, + &self, path: impl AsRef, ) -> Result { let (document, buffers, images) = gltf::import(path)?; @@ -1184,7 +1182,7 @@ impl Stage { } pub fn load_gltf_document_from_bytes( - &mut self, + &self, bytes: impl AsRef<[u8]>, ) -> Result { let (document, buffers, images) = gltf::import_slice(bytes)?; @@ -1223,7 +1221,7 @@ mod test { let projection = crate::camera::perspective(100.0, 50.0); let position = Vec3::new(1.0, 0.5, 1.5); let view = crate::camera::look_at(position, Vec3::new(1.0, 0.5, 0.0), Vec3::Y); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(false) .with_bloom(false) @@ -1243,7 +1241,7 @@ mod test { // Ensures we can read a minimal gltf file with a simple triangle mesh. fn minimal_mesh() { let ctx = Context::headless(20, 20); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(false) .with_bloom(false) @@ -1270,7 +1268,7 @@ mod test { // This ensures we are decoding images correctly. fn gltf_images() { let ctx = Context::headless(100, 100); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(false) .with_background_color(Vec4::splat(1.0)); @@ -1319,7 +1317,7 @@ mod test { fn simple_texture() { let size = 100; let ctx = Context::headless(size, size); - let mut stage = ctx + let stage = ctx .new_stage() .with_background_color(Vec3::splat(0.0).extend(1.0)) // There are no lights in the scene and the material isn't marked as "unlit", so @@ -1345,30 +1343,15 @@ mod test { // Demonstrates how to load and render a gltf file containing lighting and a // normal map. fn normal_mapping_brick_sphere() { - let size = 600; - let ctx = Context::headless(size, size); - let mut stage = ctx + let ctx = Context::headless(1920, 1080); + let stage = ctx .new_stage() .with_lighting(true) - .with_background_color(Vec4::ONE); + .with_background_color(Vec4::new(0.01, 0.01, 0.01, 1.0)); - let doc = stage - .load_gltf_document_from_path("../../gltf/red_brick_03_1k.glb") + let _doc = stage + .load_gltf_document_from_path("../../gltf/normal_mapping_brick_sphere.glb") .unwrap(); - let camera = doc.cameras.first().unwrap(); - stage.use_camera(camera); - // A change to the lighting units for directional lights causes this test to fail. - // - // Instead of changing the saved picture, we'll adjust the intensity. - // - // See for more info. - doc.lights.iter().for_each(|bundle| { - if let crate::light::LightDetails::Directional(d) = &bundle.light_details { - d.modify(|dir| { - dir.intensity *= 683.0; - }); - } - }); let frame = ctx.get_next_frame().unwrap(); stage.render(&frame.view()); @@ -1379,7 +1362,7 @@ mod test { #[test] fn rigged_fox() { let ctx = Context::headless(256, 256); - let mut stage = ctx + let stage = ctx .new_stage() .with_lighting(false) .with_vertex_skinning(false) @@ -1463,7 +1446,7 @@ mod test { // taking into account that the gltf files may have been // saved with Y up, or with Z up let ctx = Context::headless(100, 100); - let mut stage = ctx.new_stage(); + let stage = ctx.new_stage(); let doc = stage .load_gltf_document_from_path( crate::test::workspace_dir() diff --git a/crates/renderling/src/stage/gltf_support/anime.rs b/crates/renderling/src/stage/gltf_support/anime.rs index 4617a948..79bdb001 100644 --- a/crates/renderling/src/stage/gltf_support/anime.rs +++ b/crates/renderling/src/stage/gltf_support/anime.rs @@ -776,7 +776,7 @@ mod test { #[test] fn gltf_simple_animation() { let ctx = Context::headless(16, 16); - let mut stage = ctx + let stage = ctx .new_stage() .with_bloom(false) .with_background_color(Vec3::ZERO.extend(1.0)); @@ -807,7 +807,7 @@ mod test { let frame = ctx.get_next_frame().unwrap(); stage.render(&frame.view()); let img = frame.read_image().unwrap(); - img_diff::save(&format!("animation/triangle{i}.png"), img); + img_diff::save(format!("animation/triangle{i}.png"), img); frame.present(); } } diff --git a/crates/renderling/src/sync.rs b/crates/renderling/src/sync.rs new file mode 100644 index 00000000..9ee1b692 --- /dev/null +++ b/crates/renderling/src/sync.rs @@ -0,0 +1,70 @@ +//! GPU locks and atomics. + +use crabslab::Id; + +/// Perform an [atomic_i_increment](spirv_std::arch::atomic_i_increment) operation. +/// +/// ## Note +/// This is **not** atomic on CPU. +pub fn atomic_i_increment( + slab: &mut [u32], + id: Id, +) -> u32 { + #[cfg(gpu)] + { + let ptr = &mut slab[id.index()]; + unsafe { spirv_std::arch::atomic_i_increment::(ptr) } + } + #[cfg(cpu)] + { + let prev = slab[id.index()]; + slab[id.index()] = prev + 1; + prev + } +} + +/// Perform an [atomic_u_min](spirv_std::arch::atomic_u_min) operation. +/// +/// ## Note +/// This is **not** atomic on CPU. +pub fn atomic_u_min( + slab: &mut [u32], + id: Id, + val: u32, +) -> u32 { + #[cfg(gpu)] + { + let ptr = &mut slab[id.index()]; + unsafe { spirv_std::arch::atomic_u_min::(ptr, val) } + } + #[cfg(cpu)] + { + let prev = slab[id.index()]; + let new = prev.min(val); + slab[id.index()] = new; + prev + } +} + +/// Perform an [atomic_u_max](spirv_std::arch::atomic_u_max) operation. +/// +/// ## Note +/// This is **not** atomic on CPU. +pub fn atomic_u_max( + slab: &mut [u32], + id: Id, + val: u32, +) -> u32 { + #[cfg(gpu)] + { + let ptr = &mut slab[id.index()]; + unsafe { spirv_std::arch::atomic_u_max::(ptr, val) } + } + #[cfg(cpu)] + { + let prev = slab[id.index()]; + let new = prev.max(val); + slab[id.index()] = new; + prev + } +} diff --git a/crates/renderling/src/texture.rs b/crates/renderling/src/texture.rs index c844abbe..3fc4e5c6 100644 --- a/crates/renderling/src/texture.rs +++ b/crates/renderling/src/texture.rs @@ -8,7 +8,7 @@ use std::{ use craballoc::runtime::WgpuRuntime; use glam::UVec2; use image::{ - load_from_memory, DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageError, + load_from_memory, DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageError, Luma, PixelWithColorType, Rgba32FImage, }; use mips::MipMapGenerator; @@ -41,6 +41,9 @@ pub enum TextureError { #[snafu(display("Unsupported format"))] UnsupportedFormat, + + #[snafu(display("Driver poll error: {source}"))] + Poll { source: wgpu::PollError }, } type Result = std::result::Result; @@ -681,7 +684,7 @@ impl Texture { 2, ); - let pixels = copied.pixels(&runtime.device); + let pixels = copied.pixels(&runtime.device)?; let pixels = bytemuck::cast_slice::(pixels.as_slice()) .iter() .map(|p| half::f16::from_bits(*p).to_f32()) @@ -764,9 +767,9 @@ pub fn read_depth_texture_to_image( width: usize, height: usize, texture: &wgpu::Texture, -) -> Option { +) -> Result> { let depth_copied_buffer = Texture::read(runtime.as_ref(), texture, width, height, 1, 4); - let pixels = depth_copied_buffer.pixels(&runtime.as_ref().device); + let pixels = depth_copied_buffer.pixels(&runtime.as_ref().device)?; let pixels = bytemuck::cast_slice::(&pixels) .iter() .copied() @@ -775,8 +778,27 @@ pub fn read_depth_texture_to_image( (255.0 * f) as u8 }) .collect::>(); - let img_buffer = image::GrayImage::from_raw(width as u32, height as u32, pixels)?; - Some(img_buffer) + Ok(image::GrayImage::from_raw( + width as u32, + height as u32, + pixels, + )) +} + +pub fn read_depth_texture_f32( + runtime: impl AsRef, + width: usize, + height: usize, + texture: &wgpu::Texture, +) -> Result, Vec>>> { + let depth_copied_buffer = Texture::read(runtime.as_ref(), texture, width, height, 1, 4); + let pixels = depth_copied_buffer.pixels(&runtime.as_ref().device)?; + let pixels = bytemuck::cast_slice::(&pixels).to_vec(); + Ok(image::ImageBuffer::from_raw( + width as u32, + height as u32, + pixels, + )) } /// A depth texture. @@ -822,7 +844,7 @@ impl DepthTexture { /// ## Panics /// This may panic if the depth texture has a multisample count greater than /// 1. - pub fn read_image(&self) -> Option { + pub fn read_image(&self) -> Result> { // TODO: impl AsRef read_depth_texture_to_image( &self.runtime, @@ -866,10 +888,10 @@ pub struct CopiedTextureBuffer { impl CopiedTextureBuffer { /// Access the raw unpadded pixels of the buffer. - pub fn pixels(&self, device: &wgpu::Device) -> Vec { + pub fn pixels(&self, device: &wgpu::Device) -> Result> { let buffer_slice = self.buffer.slice(..); buffer_slice.map_async(wgpu::MapMode::Read, |_| {}); - device.poll(wgpu::Maintain::Wait); + device.poll(wgpu::PollType::Wait).context(PollSnafu)?; let padded_buffer = buffer_slice.get_mapped_range(); let mut unpadded_buffer = vec![]; @@ -878,7 +900,7 @@ impl CopiedTextureBuffer { for chunk in padded_buffer.chunks(self.dimensions.padded_bytes_per_row) { unpadded_buffer.extend_from_slice(&chunk[..self.dimensions.unpadded_bytes_per_row]); } - unpadded_buffer + Ok(unpadded_buffer) } /// Convert the post render buffer into an RgbaImage. @@ -940,7 +962,7 @@ impl CopiedTextureBuffer { P: image::Pixel, image::DynamicImage: From>>, { - let pixels = self.pixels(device); + let pixels = self.pixels(device)?; let coerced_pixels: &[Sp] = bytemuck::cast_slice(&pixels); let img_buffer: image::ImageBuffer> = image::ImageBuffer::from_raw( self.dimensions.width as u32, @@ -953,7 +975,7 @@ impl CopiedTextureBuffer { /// Convert the post render buffer into an internal-format [`AtlasImage`]. pub fn into_atlas_image(self, device: &wgpu::Device) -> Result { - let pixels = self.pixels(device); + let pixels = self.pixels(device)?; let img = AtlasImage { pixels, size: UVec2::new(self.dimensions.width as u32, self.dimensions.height as u32), @@ -1097,7 +1119,7 @@ mod test { 0, None, ); - let pixels = copied_buffer.pixels(r.get_device()); + let pixels = copied_buffer.pixels(r.get_device()).unwrap(); assert_eq!((mip_width * mip_height * 4) as usize, pixels.len()); let img: image::RgbaImage = image::ImageBuffer::from_vec(mip_width, mip_height, pixels).unwrap(); diff --git a/crates/renderling/src/texture/mips.rs b/crates/renderling/src/texture/mips.rs index a327fb37..b4d83be5 100644 --- a/crates/renderling/src/texture/mips.rs +++ b/crates/renderling/src/texture/mips.rs @@ -181,6 +181,7 @@ impl MipMapGenerator { load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() diff --git a/crates/renderling/src/tonemapping/cpu.rs b/crates/renderling/src/tonemapping/cpu.rs index 18e0f7fd..4dd0edf8 100644 --- a/crates/renderling/src/tonemapping/cpu.rs +++ b/crates/renderling/src/tonemapping/cpu.rs @@ -100,11 +100,7 @@ impl Tonemapping { frame_texture_format: wgpu::TextureFormat, hdr_texture: &Texture, ) -> Self { - let slab = SlabAllocator::new_with_label( - runtime, - wgpu::BufferUsages::empty(), - Some("tonemapping-slab"), - ); + let slab = SlabAllocator::new(runtime, "tonemapping-slab", wgpu::BufferUsages::empty()); let config = slab.new_value(TonemapConstants::default()); let label = Some("tonemapping"); @@ -203,6 +199,7 @@ impl Tonemapping { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, + depth_slice: None, })], depth_stencil_attachment: None, ..Default::default() diff --git a/crates/renderling/src/ui.rs b/crates/renderling/src/ui.rs new file mode 100644 index 00000000..3b37eb21 --- /dev/null +++ b/crates/renderling/src/ui.rs @@ -0,0 +1,27 @@ +//! User interface rendering. +//! +//! # Getting Started +//! First we create a context, then we create a [`Ui`], which we can use to +//! "stage" our paths, text, etc: +//! +//! ```rust +//! use renderling::ui::prelude::*; +//! use glam::Vec2; +//! +//! let ctx = Context::headless(100, 100); +//! let mut ui = Ui::new(&ctx); +//! +//! let _path = ui +//! .new_path() +//! .with_stroke_color([1.0, 1.0, 0.0, 1.0]) +//! .with_rectangle(Vec2::splat(10.0), Vec2::splat(60.0)) +//! .stroke(); +//! +//! let frame = ctx.get_next_frame().unwrap(); +//! ui.render(&frame.view()); +//! frame.present(); +//! ``` +#[cfg(cpu)] +mod cpu; +#[cfg(cpu)] +pub use cpu::*; diff --git a/crates/renderling-ui/src/lib.rs b/crates/renderling/src/ui/cpu.rs similarity index 88% rename from crates/renderling-ui/src/lib.rs rename to crates/renderling/src/ui/cpu.rs index 388eb8a5..a9cc3af8 100644 --- a/crates/renderling-ui/src/lib.rs +++ b/crates/renderling/src/ui/cpu.rs @@ -1,37 +1,8 @@ -//! `renderling-ui` is a "GPU driven" 2d renderer with a focus on simplicity and -//! ease of use. -//! -//! This library is meant to be used with its parent [`renderling`]. -//! -//! # Getting Started -//! First we create a context, then we create a [`Ui`], which we can use to -//! "stage" our paths, text, etc: -//! -//! ```rust -//! use renderling::{math::Vec2, Context}; -//! use renderling_ui::Ui; -//! -//! let ctx = Context::headless(100, 100); -//! let mut ui = Ui::new(&ctx); -//! -//! let _path = ui -//! .new_path() -//! .with_stroke_color([1.0, 1.0, 0.0, 1.0]) -//! .with_rectangle(Vec2::splat(10.0), Vec2::splat(60.0)) -//! .stroke(); -//! -//! let frame = ctx.get_next_frame().unwrap(); -//! ui.render(&frame.view()); -//! frame.present(); -//! ``` -//! -//! Happy hacking! +//! CPU part of ui. + use std::sync::{Arc, RwLock}; -use craballoc::prelude::Hybrid; -use crabslab::Id; -use glyph_brush::ab_glyph; -use renderling::{ +use crate::{ atlas::AtlasTexture, camera::Camera, geometry::Geometry, @@ -40,6 +11,9 @@ use renderling::{ transform::Transform, Context, }; +use craballoc::prelude::{Hybrid, SourceId}; +use crabslab::Id; +use glyph_brush::ab_glyph; use rustc_hash::FxHashMap; use snafu::{prelude::*, ResultExt}; @@ -51,6 +25,23 @@ pub use path::*; mod text; pub use text::*; +pub mod prelude { + //! A prelude for user interface development, meant to be glob-imported. + + #[cfg(cpu)] + pub extern crate craballoc; + pub extern crate glam; + + #[cfg(cpu)] + pub use craballoc::prelude::*; + pub use crabslab::{Array, Id}; + + #[cfg(cpu)] + pub use crate::context::*; + + pub use super::*; +} + #[derive(Debug, Snafu)] pub enum UiError { #[snafu(display("{source}"))] @@ -65,9 +56,7 @@ pub enum UiError { Image { source: image::ImageError }, #[snafu(display("{source}"))] - Stage { - source: renderling::stage::StageError, - }, + Stage { source: crate::stage::StageError }, } /// An image identifier. @@ -116,7 +105,7 @@ impl UiTransform { self.transform .get() .rotation - .to_euler(renderling::math::EulerRot::XYZ) + .to_euler(crate::math::EulerRot::XYZ) .2 } @@ -133,7 +122,7 @@ impl UiTransform { #[derive(Clone)] #[repr(transparent)] -struct UiImage(Hybrid); +pub struct UiImage(Hybrid); /// A 2d user interface renderer. /// @@ -150,7 +139,7 @@ pub struct Ui { // // The `usize` key here is the update source notifier index, which is needed // to re-order after any transform performs an update. - transforms: Arc>>, + transforms: Arc>>, default_stroke_options: Arc>, default_fill_options: Arc>, } @@ -295,8 +284,8 @@ impl Ui { .pop() .unwrap(); entry.modify(|t| { - t.modes.s = renderling::atlas::TextureAddressMode::Repeat; - t.modes.t = renderling::atlas::TextureAddressMode::Repeat; + t.modes.s = crate::atlas::TextureAddressMode::Repeat; + t.modes.t = crate::atlas::TextureAddressMode::Repeat; }); let mut guard = self.images.write().unwrap(); let id = guard.len(); @@ -352,8 +341,8 @@ impl Ui { } #[cfg(test)] -mod test { - use renderling::{color::rgb_hex_color, math::Vec4}; +pub(crate) mod test { + use crate::{color::rgb_hex_color, math::Vec4}; #[ctor::ctor] fn init_logging() { diff --git a/crates/renderling-ui/src/path.rs b/crates/renderling/src/ui/cpu/path.rs similarity index 98% rename from crates/renderling-ui/src/path.rs rename to crates/renderling/src/ui/cpu/path.rs index ddd4ca1d..4dbe3fa4 100644 --- a/crates/renderling-ui/src/path.rs +++ b/crates/renderling/src/ui/cpu/path.rs @@ -1,6 +1,11 @@ //! Path and builder. //! //! Path colors are sRGB. +use crate::{ + math::{Vec2, Vec3, Vec3Swizzles, Vec4}, + pbr::Material, + stage::{Renderlet, Vertex}, +}; use craballoc::prelude::{GpuArray, Hybrid}; use crabslab::Id; use lyon::{ @@ -9,13 +14,8 @@ use lyon::{ BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers, }, }; -use renderling::{ - math::{Vec2, Vec3, Vec3Swizzles, Vec4}, - pbr::Material, - stage::{Renderlet, Vertex}, -}; -use crate::{ImageId, Ui, UiTransform}; +use super::{ImageId, Ui, UiTransform}; pub use lyon::tessellation::{LineCap, LineJoin}; pub struct UiPath { @@ -323,7 +323,7 @@ impl UiPathBuilder { pub fn set_fill_color(&mut self, color: impl Into) -> &mut Self { let mut color = color.into(); - renderling::color::linear_xfer_vec4(&mut color); + crate::color::linear_xfer_vec4(&mut color); self.attributes.fill_color = color; self } @@ -335,7 +335,7 @@ impl UiPathBuilder { pub fn set_stroke_color(&mut self, color: impl Into) -> &mut Self { let mut color = color.into(); - renderling::color::linear_xfer_vec4(&mut color); + crate::color::linear_xfer_vec4(&mut color); self.attributes.stroke_color = color; self } @@ -523,16 +523,15 @@ impl UiPathBuilder { #[cfg(test)] mod test { - use renderling::{ + use crate::{ math::{hex_to_vec4, Vec2}, + ui::{ + test::{cute_beach_palette, Colors}, + Ui, + }, Context, }; - use crate::{ - test::{cute_beach_palette, Colors}, - Ui, - }; - use super::*; /// Generates points for a star shape. @@ -708,9 +707,9 @@ mod test { ui.render(&frame.view()); let mut img = frame.read_srgb_image().unwrap(); img.pixels_mut().for_each(|p| { - renderling::color::opto_xfer_u8(&mut p.0[0]); - renderling::color::opto_xfer_u8(&mut p.0[1]); - renderling::color::opto_xfer_u8(&mut p.0[2]); + crate::color::opto_xfer_u8(&mut p.0[0]); + crate::color::opto_xfer_u8(&mut p.0[1]); + crate::color::opto_xfer_u8(&mut p.0[2]); }); img_diff::assert_img_eq("ui/path/fill_image.png", img); } diff --git a/crates/renderling-ui/src/text.rs b/crates/renderling/src/ui/cpu/text.rs similarity index 99% rename from crates/renderling-ui/src/text.rs rename to crates/renderling/src/ui/cpu/text.rs index dbaa8c94..56f3b070 100644 --- a/crates/renderling-ui/src/text.rs +++ b/crates/renderling/src/ui/cpu/text.rs @@ -14,15 +14,15 @@ use glyph_brush::*; pub use ab_glyph::FontArc; pub use glyph_brush::{Section, Text}; -use image::{DynamicImage, GenericImage, ImageBuffer, Luma, Rgba}; -use renderling::{ +use crate::{ atlas::AtlasTexture, math::{Vec2, Vec4}, pbr::Material, stage::{Renderlet, Vertex}, }; +use image::{DynamicImage, GenericImage, ImageBuffer, Luma, Rgba}; -use crate::{Ui, UiTransform}; +use super::{Ui, UiTransform}; // TODO: make UiText able to be updated without fully destroying it #[derive(Debug)] @@ -330,10 +330,8 @@ impl GlyphCache { #[cfg(test)] mod test { + use crate::{ui::Ui, Context}; use glyph_brush::Section; - use renderling::Context; - - use crate::Ui; use super::*; @@ -436,7 +434,7 @@ mod test { ui.render(&frame.view()); let img = frame.read_image().unwrap(); img_diff::assert_img_eq("ui/text/overlay.png", img); - let depth_img = ui.stage.get_depth_texture().read_image().unwrap(); + let depth_img = ui.stage.get_depth_texture().read_image().unwrap().unwrap(); img_diff::assert_img_eq("ui/text/overlay_depth.png", depth_img); } diff --git a/crates/sandbox/src/main.rs b/crates/sandbox/src/main.rs index 8691cc7e..36377619 100644 --- a/crates/sandbox/src/main.rs +++ b/crates/sandbox/src/main.rs @@ -36,7 +36,7 @@ impl Example { } winit::event::WindowEvent::RedrawRequested => { - self.ctx.get_device().poll(wgpu::Maintain::Wait); + self.ctx.get_device().poll(wgpu::PollType::Wait).unwrap(); } _ => {} } diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 96342f0e..59e85cc0 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -11,6 +11,10 @@ enum Command { #[clap(long)] wgsl: bool, + /// Only generate linkage for the given shader function. + #[clap(long)] + only_fn_with_name: Option, + /// Print cargo output as if called from cargo (this is for testing). #[clap(long)] from_cargo: bool, @@ -46,10 +50,14 @@ fn main() { panic!("Building shaders failed :("); } } - Command::GenerateLinkage { wgsl, from_cargo } => { + Command::GenerateLinkage { + wgsl, + from_cargo, + only_fn_with_name, + } => { log::info!("Generating linkage for shaders"); let paths = renderling_build::RenderlingPaths::new().unwrap(); - paths.generate_linkage(from_cargo, wgsl); + paths.generate_linkage(from_cargo, wgsl, only_fn_with_name); } } } diff --git a/gltf/light_tiling_test.glb b/gltf/light_tiling_test.glb new file mode 100644 index 00000000..742296f8 Binary files /dev/null and b/gltf/light_tiling_test.glb differ diff --git a/gltf/red_brick_03_1k.glb b/gltf/normal_mapping_brick_sphere.glb similarity index 99% rename from gltf/red_brick_03_1k.glb rename to gltf/normal_mapping_brick_sphere.glb index 2ec25a93..8a99e749 100644 Binary files a/gltf/red_brick_03_1k.glb and b/gltf/normal_mapping_brick_sphere.glb differ diff --git a/gltf/pedestal.glb b/gltf/pedestal.glb new file mode 100644 index 00000000..fc47e6e8 Binary files /dev/null and b/gltf/pedestal.glb differ diff --git a/test_img/bvol/bounding_box/get_mesh.png b/test_img/bvol/bounding_box/get_mesh.png new file mode 100644 index 00000000..b437f0c6 Binary files /dev/null and b/test_img/bvol/bounding_box/get_mesh.png differ diff --git a/test_img/gltf/normal_mapping_brick_sphere.png b/test_img/gltf/normal_mapping_brick_sphere.png index dd06ce70..0d503927 100644 Binary files a/test_img/gltf/normal_mapping_brick_sphere.png and b/test_img/gltf/normal_mapping_brick_sphere.png differ diff --git a/test_img/light/pedestal/directional.png b/test_img/light/pedestal/directional.png new file mode 100644 index 00000000..af980008 Binary files /dev/null and b/test_img/light/pedestal/directional.png differ diff --git a/test_img/light/pedestal/point.png b/test_img/light/pedestal/point.png new file mode 100644 index 00000000..20c742c3 Binary files /dev/null and b/test_img/light/pedestal/point.png differ diff --git a/test_img/light/pedestal/spot.png b/test_img/light/pedestal/spot.png new file mode 100644 index 00000000..e3f2a11e Binary files /dev/null and b/test_img/light/pedestal/spot.png differ diff --git a/test_img/lights/spot_lights/frame.png b/test_img/light/spot_lights/frame.png similarity index 100% rename from test_img/lights/spot_lights/frame.png rename to test_img/light/spot_lights/frame.png diff --git a/test_img/lights/spot_lights/one.png b/test_img/light/spot_lights/one.png similarity index 100% rename from test_img/lights/spot_lights/one.png rename to test_img/light/spot_lights/one.png diff --git a/test_img/light/tiling/bins/1-scene.png b/test_img/light/tiling/bins/1-scene.png new file mode 100644 index 00000000..7e82bc4d Binary files /dev/null and b/test_img/light/tiling/bins/1-scene.png differ diff --git a/test_img/light/tiling/bins/2-lights.png b/test_img/light/tiling/bins/2-lights.png new file mode 100644 index 00000000..5999f3f4 Binary files /dev/null and b/test_img/light/tiling/bins/2-lights.png differ diff --git a/test_img/light/tiling/clear_tiles/1-lights.png b/test_img/light/tiling/clear_tiles/1-lights.png new file mode 100644 index 00000000..655a54db Binary files /dev/null and b/test_img/light/tiling/clear_tiles/1-lights.png differ diff --git a/test_img/light/tiling/clear_tiles/1-maxs.png b/test_img/light/tiling/clear_tiles/1-maxs.png new file mode 100644 index 00000000..96bb66fa Binary files /dev/null and b/test_img/light/tiling/clear_tiles/1-maxs.png differ diff --git a/test_img/light/tiling/clear_tiles/1-mins.png b/test_img/light/tiling/clear_tiles/1-mins.png new file mode 100644 index 00000000..bc3e7d76 Binary files /dev/null and b/test_img/light/tiling/clear_tiles/1-mins.png differ diff --git a/test_img/light/tiling/clear_tiles/2-lights.png b/test_img/light/tiling/clear_tiles/2-lights.png new file mode 100644 index 00000000..d916bcd3 Binary files /dev/null and b/test_img/light/tiling/clear_tiles/2-lights.png differ diff --git a/test_img/light/tiling/clear_tiles/2-maxs.png b/test_img/light/tiling/clear_tiles/2-maxs.png new file mode 100644 index 00000000..d916bcd3 Binary files /dev/null and b/test_img/light/tiling/clear_tiles/2-maxs.png differ diff --git a/test_img/light/tiling/clear_tiles/2-mins.png b/test_img/light/tiling/clear_tiles/2-mins.png new file mode 100644 index 00000000..049aab3b Binary files /dev/null and b/test_img/light/tiling/clear_tiles/2-mins.png differ diff --git a/test_img/light/tiling/e2e/4-scene-no-tiling.png b/test_img/light/tiling/e2e/4-scene-no-tiling.png new file mode 100644 index 00000000..77b06148 Binary files /dev/null and b/test_img/light/tiling/e2e/4-scene-no-tiling.png differ diff --git a/test_img/light/tiling/e2e/6-scene-16-32-lights-0-0.1-min-lux.png b/test_img/light/tiling/e2e/6-scene-16-32-lights-0-0.1-min-lux.png new file mode 100644 index 00000000..58f513b0 Binary files /dev/null and b/test_img/light/tiling/e2e/6-scene-16-32-lights-0-0.1-min-lux.png differ diff --git a/test_img/light/tiling/min_max_depth/1-scene.png b/test_img/light/tiling/min_max_depth/1-scene.png new file mode 100644 index 00000000..7e82bc4d Binary files /dev/null and b/test_img/light/tiling/min_max_depth/1-scene.png differ diff --git a/test_img/light/tiling/min_max_depth/2-maxs.png b/test_img/light/tiling/min_max_depth/2-maxs.png new file mode 100644 index 00000000..09049e6e Binary files /dev/null and b/test_img/light/tiling/min_max_depth/2-maxs.png differ diff --git a/test_img/light/tiling/min_max_depth/2-mins.png b/test_img/light/tiling/min_max_depth/2-mins.png new file mode 100644 index 00000000..cca48a56 Binary files /dev/null and b/test_img/light/tiling/min_max_depth/2-mins.png differ diff --git a/test_img/shadows/shadow_mapping_sanity/depth.png b/test_img/shadows/shadow_mapping_sanity/depth.png deleted file mode 100644 index c25bd548..00000000 Binary files a/test_img/shadows/shadow_mapping_sanity/depth.png and /dev/null differ diff --git a/test_img/shadows/shadow_mapping_sanity/light_pov_depth.png b/test_img/shadows/shadow_mapping_sanity/light_pov_depth.png deleted file mode 100644 index 6cf43cb6..00000000 Binary files a/test_img/shadows/shadow_mapping_sanity/light_pov_depth.png and /dev/null differ diff --git a/test_img/shadows/shadow_mapping_sanity/scene_light_pov.png b/test_img/shadows/shadow_mapping_sanity/scene_light_pov.png deleted file mode 100644 index e58ba2a1..00000000 Binary files a/test_img/shadows/shadow_mapping_sanity/scene_light_pov.png and /dev/null differ diff --git a/test_img/shadows/shadow_mapping_sanity/shadows_update_texture.png b/test_img/shadows/shadow_mapping_sanity/shadows_update_texture.png deleted file mode 100644 index d344ca27..00000000 Binary files a/test_img/shadows/shadow_mapping_sanity/shadows_update_texture.png and /dev/null differ diff --git a/test_img/shadows/shadow_mapping_sanity/stage_render.png b/test_img/shadows/shadow_mapping_sanity/stage_render.png index 2353e296..65b083ad 100644 Binary files a/test_img/shadows/shadow_mapping_sanity/stage_render.png and b/test_img/shadows/shadow_mapping_sanity/stage_render.png differ diff --git a/test_img/stage/resize_100.png b/test_img/stage/resize_100.png new file mode 100644 index 00000000..33651b99 Binary files /dev/null and b/test_img/stage/resize_100.png differ diff --git a/test_img/stage/resize_200.png b/test_img/stage/resize_200.png new file mode 100644 index 00000000..b131904e Binary files /dev/null and b/test_img/stage/resize_200.png differ