diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..b736a9f --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.x86_64-unknown-linux-gnu] +rustflags = ["-Ctarget-cpu=native"] \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b05da64..1d0ba1b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,7 +31,9 @@ jobs: steps: - uses: actions/checkout@v4 - - run: rustup toolchain install $RUST_VERSION --profile minimal + - run: | + rustup toolchain add nightly-2025-10-20 --profile minimal + rustup toolchain install $RUST_VERSION --profile minimal - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -46,7 +48,9 @@ jobs: steps: - uses: actions/checkout@v4 - - run: rustup toolchain install $RUST_VERSION --profile minimal + - run: | + rustup toolchain add nightly-2025-10-20 --profile minimal + rustup toolchain install $RUST_VERSION --profile minimal - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -68,7 +72,9 @@ jobs: steps: - uses: actions/checkout@v4 - - run: rustup toolchain install $RUST_VERSION --profile minimal + - run: | + rustup toolchain add nightly-2025-10-20 --profile minimal + rustup toolchain install $RUST_VERSION --profile minimal - uses: taiki-e/install-action@v2 with: tool: cargo-shear, cargo-msrv diff --git a/Cargo.lock b/Cargo.lock index 341cba8..4faf2d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -19,9 +19,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -58,9 +58,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -123,9 +123,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -133,7 +133,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -159,9 +159,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" @@ -171,18 +171,18 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", @@ -209,9 +209,9 @@ checksum = "6236364b88b9b6d0bc181ba374cf1ab55ba3ef97a1cb6f8cddad48a273767fb5" [[package]] name = "cc" -version = "1.2.36" +version = "1.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" dependencies = [ "find-msvc-tools", "shlex", @@ -219,9 +219,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -231,9 +231,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.47" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -241,9 +241,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -253,9 +253,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "codespan-reporting" @@ -411,14 +411,14 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -435,7 +435,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" dependencies = [ - "itertools", + "itertools 0.11.0", "num-traits", ] @@ -465,12 +465,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -500,9 +500,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "fixedbitset" @@ -529,9 +529,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -616,26 +616,26 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.4+wasi-0.2.4", + "wasip2", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glam" @@ -647,6 +647,15 @@ dependencies = [ "libm", ] +[[package]] +name = "glam" +version = "0.30.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" +dependencies = [ + "libm", +] + [[package]] name = "googletest" version = "0.14.2" @@ -676,7 +685,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "gpu-alloc-types", ] @@ -686,7 +695,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -695,7 +704,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "gpu-descriptor-types", "hashbrown 0.15.5", ] @@ -706,7 +715,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -717,13 +726,14 @@ checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", "num-traits", + "zerocopy", ] [[package]] @@ -848,12 +858,12 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] @@ -867,9 +877,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -880,6 +890,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -888,9 +907,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.78" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -901,7 +920,7 @@ name = "kernel" version = "0.1.0" dependencies = [ "bytemuck", - "glam", + "glam 0.29.3", "spirv-std", ] @@ -913,18 +932,18 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -935,33 +954,32 @@ checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litrs" -version = "0.4.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -973,9 +991,9 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lsm-tree" -version = "2.10.3" +version = "2.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab73c02eadb3dc12c0024e5b61d6284e6d59064e67e74fbad77856caa56f62c7" +checksum = "799399117a2bfb37660e08be33f470958babb98386b04185288d829df362ea15" dependencies = [ "byteorder", "crossbeam-skiplist", @@ -997,9 +1015,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" [[package]] name = "matchers" @@ -1012,9 +1030,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miniz_oxide" @@ -1028,9 +1046,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd32fa8935aeadb8a8a6b6b351e40225570a37c43de67690383d87ef170cd08" +checksum = "0fbdd3d7436f8b5e892b8b7ea114271ff0fa00bc5acae845d53b07d498616ef6" dependencies = [ "num-traits", "pxfm", @@ -1038,13 +1056,13 @@ dependencies = [ [[package]] name = "naga" -version = "27.0.0" +version = "27.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b2e757b11b47345d44e7760e45458339bc490463d9548cd8651c53ae523153" +checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "codespan-reporting", @@ -1065,11 +1083,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1084,9 +1102,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1099,9 +1117,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "option-ext" @@ -1111,24 +1129,24 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "4.6.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" dependencies = [ "num-traits", ] [[package]] name = "owo-colors" -version = "4.2.2" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1136,15 +1154,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1167,9 +1185,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", @@ -1188,7 +1206,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "crc32fast", "fdeflate", "flate2", @@ -1218,9 +1236,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1233,9 +1251,9 @@ checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" [[package]] name = "proj4rs" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99f88879081958c109556f14db41e2ee45193090030d93bbbbf28819221e4f13" +checksum = "2a3c4a66ce46a8b4514d0dce1e7a39b3d2b3a6125f7968093020f96a8a5ad09b" dependencies = [ "console_log", "js-sys", @@ -1257,28 +1275,28 @@ dependencies = [ [[package]] name = "pxfm" -version = "0.1.23" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84" +checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" dependencies = [ "num-traits", ] [[package]] name = "quick_cache" -version = "0.6.16" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad6644cb07b7f3488b9f3d2fde3b4c0a7fa367cafefb39dff93a659f76eb786" +checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1323,11 +1341,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -1343,9 +1361,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1355,9 +1373,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1366,9 +1384,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "renderdoc-sys" @@ -1413,15 +1431,15 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1444,24 +1462,34 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "self_cell" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1470,14 +1498,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -1525,7 +1554,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", ] [[package]] @@ -1534,7 +1563,7 @@ version = "0.9.0" source = "git+https://github.com/Rust-GPU/rust-gpu?rev=86fc48032c4cd4afb74f1d81ae859711d20386a1#86fc48032c4cd4afb74f1d81ae859711d20386a1" dependencies = [ "bitflags 1.3.2", - "glam", + "glam 0.30.9", "libm", "num-traits", "spirv-std-macros", @@ -1559,9 +1588,9 @@ source = "git+https://github.com/Rust-GPU/rust-gpu?rev=86fc48032c4cd4afb74f1d81a [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -1583,9 +1612,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1594,15 +1623,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.21.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1616,18 +1645,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1656,10 +1685,12 @@ dependencies = [ "geojson", "googletest", "image", + "itertools 0.14.0", "kernel", "pollster", "proj4rs", "radsort", + "rayon", "serde", "serde_json", "tempfile", @@ -1741,15 +1772,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "utf8parse" @@ -1793,19 +1824,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.4+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -1814,25 +1845,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1840,31 +1857,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.101" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.78" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -1877,7 +1894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" dependencies = [ "arrayvec", - "bitflags 2.9.4", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "document-features", @@ -1896,14 +1913,14 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "27.0.1" +version = "27.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d654c0b6c6335edfca18c11bdaed964def641b8e9997d3a495a2ff4077c922" +checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" dependencies = [ "arrayvec", "bit-set", "bit-vec", - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "cfg_aliases", "document-features", @@ -1935,14 +1952,14 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "27.0.2" +version = "27.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2618a2d6b8a5964ecc1ac32a5db56cb3b1e518725fcd773fd9a782e023453f2b" +checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "cfg-if", "cfg_aliases", @@ -1972,7 +1989,7 @@ version = "27.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytemuck", "log", ] @@ -1983,7 +2000,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.0", + "windows-sys 0.61.2", ] [[package]] @@ -2033,15 +2050,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" @@ -2062,31 +2073,22 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -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.3", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -2107,19 +2109,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "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", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -2130,9 +2132,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2142,9 +2144,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2154,9 +2156,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -2166,9 +2168,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2178,9 +2180,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2190,9 +2192,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2202,9 +2204,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2214,18 +2216,38 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.45.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "xxhash-rust" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index c85e81a..b8b169d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,3 +76,11 @@ non_ascii_literal = "allow" std_instead_of_alloc = "allow" std_instead_of_core = "allow" float_arithmetic = "allow" + +# We're not worried about cryptographic code, integer +integer_division_remainder_used = "allow" + +# We're not concerned about modulo not computing negative numbers +modulo_arithmetic = "allow" + +multiple_unsafe_ops_per_block = "allow" \ No newline at end of file diff --git a/clippy.toml b/clippy.toml index a742505..7bada54 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,3 @@ allow-unwrap-in-tests = true allow-expect-in-tests = true allow-panic-in-tests = true - diff --git a/crates/total-viewsheds/Cargo.toml b/crates/total-viewsheds/Cargo.toml index b74b678..0fa2827 100644 --- a/crates/total-viewsheds/Cargo.toml +++ b/crates/total-viewsheds/Cargo.toml @@ -22,6 +22,8 @@ tracing = { version = "0.1.41" } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } wgpu = { version = "27", default-features = false, features = ["spirv", "vulkan"] } radsort = "0.1.1" +itertools = "0.14.0" +rayon = "1.11.0" [dev-dependencies] googletest = "0.14.2" diff --git a/crates/total-viewsheds/src/compute.rs b/crates/total-viewsheds/src/compute.rs index 48bb18b..1d09d89 100644 --- a/crates/total-viewsheds/src/compute.rs +++ b/crates/total-viewsheds/src/compute.rs @@ -1,5 +1,6 @@ //! The main entrypoint for running computations. +use crate::cpu; use color_eyre::{eyre::Ok, Result}; /// Handles all the computations. @@ -24,6 +25,10 @@ pub struct Compute<'compute> { pub longest_lines: Vec, } +/// `NUM_CORES` is the physical number of cores on a machine. Currently hardcoded to 8 +/// as that is what an i9900k has, and is a common configuration. +/// TODO find a good syscall for this +const NUM_CORES: usize = 8; /// Configuration for computing. pub struct ComputeConfig { /// The height of the observer that views viewsheds. @@ -80,10 +85,6 @@ impl<'compute> Compute<'compute> { ..Default::default() }; - #[expect( - clippy::if_then_some_else_none, - reason = "The `?` is hard to use in the closure" - )] let vulkan = if matches!(config.backend, crate::config::Backend::Vulkan) { let elevations = dem.elevations.clone(); dem.elevations = Vec::new(); // Free up some RAM. @@ -192,6 +193,37 @@ impl<'compute> Compute<'compute> { Vec::new() }; + if matches!(self.config.backend, crate::config::Backend::CPU) { + #[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + reason = "elevations start out as i16s, and i16 -> f32 -> i16 is lossless" + )] + let elevations = self + .dem + .elevations + .iter() + .map(|&x| x as i16) + .collect::>(); + + #[expect(clippy::as_conversions, reason = "u32 -> usize is valid")] + // TODO: third param is ring data which needs to be saved + let (surfaces, longest, _) = cpu::multithreaded_kernel( + &elevations, + self.dem.max_los_as_points as usize, + 360, + NUM_CORES, + false, + ); + + self.add_sector_surfaces_to_running_total(&surfaces); + self.increment_longest_lines(&longest); + + self.render_total_surfaces()?; + self.render_longest_lines()?; + return Ok(()); + } + for angle in 0..crate::axes::SECTOR_STEPS { self.load_or_compute_cache(angle)?; let mut sector_ring_data = vec![0; self.total_reserved_rings]; @@ -543,7 +575,7 @@ pub mod test { compute.total_surfaces, [ 2687.689, 2546.9956, 2622.3494, - 2564.7678, 3231.647, 2239.714, + 2564.7678, 3231.647, 2239.714, 2604.2551, 2186.5012, 1768.3433 ] ); diff --git a/crates/total-viewsheds/src/cpu.rs b/crates/total-viewsheds/src/cpu.rs new file mode 100644 index 0000000..98c44f7 --- /dev/null +++ b/crates/total-viewsheds/src/cpu.rs @@ -0,0 +1,932 @@ +//! `cpu` is a CPU version of the total viewshed calculation + +use itertools::izip; +use rayon::iter::ParallelIterator as _; + +#[cfg(target_feature = "avx512f")] +use std::arch::x86_64::{ + __m512, _mm256_alignr_epi32, _mm256_mask_alignr_epi32, _mm512_alignr_epi32, + _mm512_castps_si512, _mm512_castsi512_ps, _mm512_cmple_ps_mask, _mm512_max_ps, +}; + +use rayon::prelude::IntoParallelIterator as _; +use rayon::ThreadPoolBuilder; +#[cfg(all( + target_feature = "avx2", + target_feature = "avx", + target_feature = "sse", + target_feature = "sse2" +))] +use std::arch::x86_64::{ + _mm256_blend_ps, _mm256_castps_si256, _mm256_castsi256_ps, _mm256_cmp_ps, _mm256_max_ps, + _mm256_slli_si256, _mm_castps_si128, _mm_cmpge_ps, _mm_max_ps, _CMP_LE_OS, +}; +use std::iter::zip; +use std::simd::prelude::*; +use std::simd::{LaneCount, Mask, SupportedLaneCount}; +use std::sync::Mutex; +use std::time::Instant; +use std::{array, f32, mem, slice}; + +/// `EARTH_RADIUS_SQUARED` is the earth's radius squared in meters +const EARTH_RADIUS_SQUARED: f32 = 12_742_000.0; + +/// `TAN_ONE_RAD` helps normalize the fact that inner points are sampled more often +/// see the TVS paper for reasoning. +const TAN_ONE_RAD: f32 = 0.017_453_3; + +/// `Vectorized` is an empty struct to allow for specializations of the total viewhshed algorithm +/// TODO: maybe we can just use a generic struct? +struct Vectorized; + +/// `Viewshed` holds all the platform and vector-width specific methods the CPU kernel +/// needs to operate. +trait Viewshed +where + LaneCount: SupportedLaneCount, +{ + /// `gte` takes in a vector of angles and its prefix maximum returns a mask of + /// i32s which are either -1 or 0 in each lane. This way it can be used to "select" + /// which lanes of the target vector to use for further calculations + fn gte(&self, angle: Simd, prefix: Simd) -> Mask; + + /// `max` returns the lane-wise maximum of both vectors. It exists to help platform-specific + /// and potentially "unsafe" (in floating point terms) and speedier implementations + fn max(&self, lhs: Simd, rhs: Simd) -> Simd; + + /// `prefix_max` calculates a prefix maximum given all of the `angles` and stores + /// it in `prefix_max` + fn prefix_max( + &self, + angles: &[Simd], + prefix_max: &mut [Simd], + acc: Simd, + ) -> Simd; +} + +impl Viewshed<4> for Vectorized { + #[inline] + #[cfg(all(target_feature = "sse", target_feature = "sse2"))] + fn gte(&self, angle: f32x4, prefix: f32x4) -> Mask { + // safety: the caller of Viewshed<4> guarantees that -0.0 or NaN are not in the input + // thus allowing this to be non IEEE754 compliant + unsafe { + let mask = _mm_castps_si128(_mm_cmpge_ps(angle.into(), prefix.into())); + Mask::::from_int_unchecked(mask.into()) + } + } + + #[inline] + #[cfg(not(all(target_feature = "sse", target_feature = "sse2")))] + fn gte(&self, lhs: f32x4, rhs: f32x4) -> Mask { + lhs.simd_ge(rhs) + } + + #[inline] + #[cfg(all(target_feature = "sse", target_feature = "sse2"))] + fn max(&self, lhs: f32x4, rhs: f32x4) -> Simd { + // safety: the caller of Viewshed<4> guarantees that -0.0 or NaN are not in the input + // thus allowing this to be non IEEE754 compliant + unsafe { _mm_max_ps(lhs.into(), rhs.into()).into() } + } + + #[inline] + #[cfg(not(all(target_feature = "sse", target_feature = "sse2")))] + fn max(&self, lhs: f32x4, rhs: f32x4) -> Simd { + lhs.simd_max(r) + } + + #[inline] + fn prefix_max(&self, angles: &[f32x4], prefix_max: &mut [f32x4], acc: f32x4) -> f32x4 { + for (prefix, &angle) in zip(prefix_max.iter_mut(), angles.iter()) { + let mut v_prefix_max = { + let shifted = angle.shift_elements_right::<1>(-2000.0f32); + self.max(angle, shifted) + }; + + v_prefix_max = { + let shifted = v_prefix_max.shift_elements_right::<2>(-2000.0f32); + self.max(v_prefix_max, shifted) + }; + + *prefix = v_prefix_max; + } + + let mut local_acc = acc; + + // accumulate the prefix maxes for blocks, re-computing all prefix maxes + // to include the accumulated value + for prefix in prefix_max { + let cur_prefix: f32x4 = *prefix; + let cur_max: f32x4 = Simd::splat(cur_prefix[3]); + + *prefix = self.max(local_acc, cur_prefix); + local_acc = self.max(local_acc, cur_max); + } + + local_acc + } +} + +#[cfg(all(target_feature = "avx2", target_feature = "avx"))] +impl Viewshed<8> for Vectorized { + #[inline] + fn gte(&self, angle: f32x8, prefix: f32x8) -> Mask { + // safety: the caller of Viewshed<8> guarantees that -0.0 or NaN are not in the input + // thus allowing this to be non IEEE754 compliant + unsafe { + let mask = + _mm256_castps_si256(_mm256_cmp_ps::<_CMP_LE_OS>(prefix.into(), angle.into())); + Mask::::from_int_unchecked(mask.into()) + } + } + + #[inline] + fn max(&self, lhs: f32x8, rhs: f32x8) -> Simd { + // safety: the caller of Viewshed<8> guarantees that -0.0 or NaN are not in the input + // thus allowing this to be non IEEE754 compliant + unsafe { _mm256_max_ps(lhs.into(), rhs.into()).into() } + } + + #[inline] + fn prefix_max(&self, angles: &[f32x8], prefix_max: &mut [f32x8], acc: f32x8) -> f32x8 { + // Calculate the 4-wide block prefix max two at a time + for (prefix, &angle) in zip(prefix_max.iter_mut(), angles.iter()) { + // safety: all mm256 operations are avx2, and Viewshed<8> has feature guards for both + let mut v_prefix_max = unsafe { + let shifted = _mm256_slli_si256::<4>(_mm256_castps_si256(angle.into())); + let blended = _mm256_blend_ps::<0b1000_1000>( + _mm256_castsi256_ps(shifted), + Simd::splat(-2000.0f32).into(), + ); + self.max(angle, blended.into()) + }; + + // safety: all mm256 operations are avx2, and Viewshed<8> has feature guards for both + v_prefix_max = unsafe { + let shifted = _mm256_slli_si256::<8>(_mm256_castps_si256(v_prefix_max.into())); + let blended = _mm256_blend_ps::<0b1100_1100>( + _mm256_castsi256_ps(shifted), + Simd::splat(-2000.0f32).into(), + ); + + self.max(v_prefix_max, blended.into()) + }; + + *prefix = v_prefix_max; + } + + let mut local_acc = f32x4::splat(acc[3]); + + // safety: because f32x8s are aligned to exactly sizeof(f32x4) * 2 + // this is well aligned, so the cast is valid + // + // This is SUPER MEGA UBER VERY sketchy, and shouldn't be copied + // unless you _really_, _truly_ understand what the compiler will do + let single_wide_prefx: &mut [f32x4] = unsafe { + let ptr = prefix_max.as_mut_ptr(); + slice::from_raw_parts_mut(ptr.cast::(), prefix_max.len() * 2) + }; + + // accumulate the prefix maxes for blocks, re-computing all prefix maxes + // to include the accumulated value + for prefix in single_wide_prefx { + let cur_prefix: f32x4 = *prefix; + let cur_max: f32x4 = Simd::splat(cur_prefix[3]); + + *prefix = self.max(local_acc, cur_prefix); + local_acc = self.max(local_acc, cur_max); + } + + f32x8::splat(local_acc[3]) + } +} + +#[cfg(target_feature = "avx512f")] +fn _mm512_slli_si512(elem: __m512) -> __m512 +where + [(); { (16 - K) as i32 } as usize]:, +{ + unsafe { + let zero = f32x16::splat(-2000.0f32); + _mm512_castsi512_ps(_mm512_alignr_epi32::<{ (16 - K) as i32 }>( + _mm512_castps_si512(elem), + _mm512_castps_si512(zero.into()), + )) + } +} + +#[cfg(target_feature = "avx512f")] +impl Viewshed<16> for Vectorized { + #[inline] + fn gte(&self, angle: f32x16, prefix: f32x16) -> Mask { + // safety: the caller of Viewshed<8> guarantees that -0.0 or NaN are not in the input + // thus allowing this to be non IEEE754 compliant + unsafe { + let mask = _mm512_cmple_ps_mask(prefix.into(), angle.into()); + Mask::::from_bitmask(mask.into()) + } + } + + #[inline] + fn max(&self, lhs: f32x16, rhs: f32x16) -> f32x16 { + // safety: the caller of Viewshed<8> guarantees that -0.0 or NaN are not in the input + // thus allowing this to be non IEEE754 compliant + unsafe { _mm512_max_ps(lhs.into(), rhs.into()).into() } + } + + #[inline] + fn prefix_max(&self, angles: &[f32x16], prefix_max: &mut [f32x16], acc: f32x16) -> f32x16 { + // Calculate the 4-wide block prefix max two at a time + for (prefix, &angle) in zip(prefix_max.iter_mut(), angles.iter()) { + unsafe { + let mut v_prefix_max = + _mm512_max_ps(angle.into(), _mm512_slli_si512::<1>(angle.into()).into()); + v_prefix_max = _mm512_max_ps( + v_prefix_max.into(), + _mm512_slli_si512::<2>(v_prefix_max).into(), + ); + v_prefix_max = _mm512_max_ps( + v_prefix_max.into(), + _mm512_slli_si512::<4>(v_prefix_max).into(), + ); + v_prefix_max = _mm512_max_ps( + v_prefix_max.into(), + _mm512_slli_si512::<8>(v_prefix_max).into(), + ); + *prefix = v_prefix_max.into(); + } + } + + let mut local_acc = f32x16::splat(acc[0]); + + // accumulate the prefix maxes for blocks, re-computing all prefix maxes + // to include the accumulated value + for prefix in prefix_max { + let cur_prefix: f32x16 = *prefix; + let cur_max: f32x16 = Simd::splat(cur_prefix[15]); + + *prefix = self.max(local_acc, cur_prefix); + local_acc = self.max(local_acc, cur_max); + } + + f32x16::splat(local_acc[0]) + } +} + +#[inline] +/// `load_elevations` converts an array of i16 elevations into a height adjusted +fn load_elevations(elev_arr: [i16; N], pov_height: f32) -> Simd +where + LaneCount: SupportedLaneCount, +{ + let elevs = Simd::::from_array(elev_arr); + let float_elevs: Simd = elevs.cast(); + float_elevs - Simd::splat(pov_height) +} + +/// `IndexSIMD` holds the `dem_ids`/"indexes" of the current line of sight, +/// along with where they will be written out to +struct IndexSIMD<'idx, const N: usize> { + /// `indexes_in` holds a slice of a SIMD-size-wide array of `dem_ids`/"indexes" + indexes_in: &'idx [[i32; N]], + /// `indexes_out` is the buffer of a SIMD-size-wide where the visible `dem_ids`/"indexes" will be written out to + indexes_out: &'idx mut [[i32; N]], +} + +#[inline] +/// `line_of_sight` calculates a single line of sight for a given pov, which is passed in via `pov_height` +fn line_of_sight( + vs: &VS, + elevations: &[[i16; N]], + distances: &[Simd], + adjustments: &[Simd], + prefix_in: Simd, + indexes: Option>, + pov_height: f32, +) -> ([Simd; UNROLL], [Simd; UNROLL], Simd) +where + LaneCount: SupportedLaneCount, + VS: Viewshed, +{ + let mut sum_buf: [Simd; UNROLL] = [Simd::splat(0.0); UNROLL]; + let mut angle_buf: [Simd; UNROLL] = [Simd::splat(0.0); UNROLL]; + let mut longest_line_buf: [Simd; UNROLL] = [Simd::splat(0.0); UNROLL]; + let mut prefix_buf: [Simd; UNROLL] = [Simd::splat(0.0); UNROLL]; + + izip!( + angle_buf.iter_mut(), + elevations + .iter() + .map(|elev| load_elevations(*elev, pov_height)), + distances, + adjustments + ) + .for_each(|(angle, elev, dist, adjust)| { + *angle = elev / dist - adjust; + }); + + #[expect( + clippy::float_cmp, + reason = "-2000.0f32 is a sentinel value for the first time this accumlative function is run" + )] + if prefix_in[0] == -2000.0f32 { + angle_buf[0][0] = -2000.1f32; + } + + let prefix_out = vs.prefix_max(&angle_buf, &mut prefix_buf, prefix_in); + + let index_iter = indexes.map(|index_simd| { + index_simd + .indexes_in + .iter() + .map(|ind| Simd::::from_array(*ind)) + .zip(index_simd.indexes_out.iter_mut()) + }); + + izip!( + &mut sum_buf, + &mut longest_line_buf, + angle_buf.iter(), + prefix_buf.iter(), + distances.iter(), + OptionIter::new(index_iter) + ) + .for_each(|(next_sum, longest_line, &angle, &pref, &dists, inds)| { + let mask = vs.gte(angle, pref); + + if let Some((inds_in, inds_out)) = inds { + inds_in.store_select(inds_out, mask); + } + + let selected_distances = mask.select(dists, Simd::splat(0.0)); + *longest_line = vs.max(*longest_line, selected_distances); + + let selected_tans = mask.select(Simd::splat(TAN_ONE_RAD), Simd::splat(0.0)); + *next_sum = selected_distances * selected_tans; + }); + + (sum_buf, longest_line_buf, prefix_out) +} + +/// `dem_to_pov` +#[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + reason = "so long as max_los < 2^24, the following as conversions are entirely safe" +)] +#[expect( + clippy::integer_division, + reason = "i32 is constructed from (i32, i32) converting back should succeed" +)] +const fn dem_to_pov(dem_id: i32, width: usize, max_los: usize) -> i32 { + let dem_x = (dem_id / width as i32) - max_los as i32; + let dem_y = (dem_id % width as i32) - max_los as i32; + + dem_x * (max_los as i32) + dem_y +} + +/// `Indexes` holds the `dem_id`/"indexes" and the output buffer to store them in +struct Indexes<'index> { + /// `indexes_in` holds the full line of (2 or 3*`max_los`) `dem_ids`/"indexes" + indexes_in: &'index [i32], + /// `indexes_out` holds `max_los` indexes of `max_los` length. Zeroes are used if + indexes_out: &'index mut [i32], +} + +/// `OptionIter` holds the state for an optional inner iterator. +/// If passed `None`, it will repeat `None` forever. This comes in handy +/// when working with `izip!` +struct OptionIter +where + Iter: Iterator, +{ + /// `iter` holds an optional iterator state which will + /// call `next()` + iter: Option, +} + +impl OptionIter +where + Iter: Iterator, +{ + /// `new` creates a new iter from an Option of the Iter + const fn new(iter: Option) -> Self { + Self { iter } + } +} + +impl Iterator for OptionIter +where + Iter: Iterator, +{ + type Item = Option; + + #[inline] + fn next(&mut self) -> Option { + if let Some(ref mut iter) = &mut self.iter { + iter.next().map(Some) + } else { + Some(None) + } + } +} + +/// `UnrolledAngles` holds the curved earth adjustments +struct UnrolledAngles<'angle, const WIDTH: usize, const UNROLL: usize> +where + LaneCount: SupportedLaneCount, +{ + /// `adjustments` holds a slice of UNROLL sized slices used during loop unrolling, and then the "rest" portion + adjustments: ( + &'angle [[Simd; UNROLL]], + &'angle [Simd], + ), + /// `distances` holds a slice of UNROLL sized slices used during loop unrolling, and then the "rest" portion + distances: ( + &'angle [[Simd; UNROLL]], + &'angle [Simd], + ), +} + +/// `viewshed` computes the viewshed for a single pov, using its `elevation`, and `max_los` +/// and stores the results in `heatmap` and `longest_line` using the `dem_id` +#[inline] +fn viewshed( + vs: &VS, + pov_idx: usize, + elevation: i16, + dem_id: i32, + max_los: usize, + heatmap: &mut [f32], + longest_line: &mut [f32], + line: &[i16], + index_data: Option, + unrolled_angles: &UnrolledAngles, +) where + LaneCount: SupportedLaneCount, + VS: Viewshed, +{ + let result_tvs_id = dem_to_pov(dem_id, 3 * max_los, max_los); + + // if the line of sight is not within our computable points, do not consider it + if result_tvs_id < 0i32 || result_tvs_id >= (max_los * max_los) as i32 { + return; + } + + let pov_height = f32::from(elevation); + + // safety: max_los % WIDTH == 0, so [pov_idx..pov_idx+max_los) will also be WIDTH wide + let (elevations, _): (&[[i16; WIDTH]], _) = + unsafe { line.get_unchecked(pov_idx..pov_idx + max_los) }.as_chunks::(); + + let (iter, rest) = index_data.map_or_else( + || (None, None), + |data| { + // safety: pov_idx should be between [0, max_los) and len(indexes_in)==2*max_los + // thus, for any pov_idx pov_idx..pov_idx+max_los is inbounds + let (indexes, _): (&[[i32; WIDTH]], _) = + unsafe { data.indexes_in.get_unchecked(pov_idx..pov_idx + max_los) } + .as_chunks::(); + + let (indexes_out, _) = data.indexes_out.as_chunks_mut::(); + + let (chunked_indexes, rest_indexes) = indexes.as_chunks::(); + let (chunked_indexes_out, rest_indexes_out) = indexes_out.as_chunks_mut::(); + + ( + Some( + zip(chunked_indexes.iter(), chunked_indexes_out.iter_mut()).map( + |(inds_in, inds_out)| IndexSIMD { + indexes_in: inds_in, + indexes_out: inds_out, + }, + ), + ), + Some(IndexSIMD { + indexes_in: rest_indexes, + indexes_out: rest_indexes_out, + }), + ) + }, + ); + + let (chunked_elevs, rest_elevs) = elevations.as_chunks::(); + + let (chunked_distances, rest_distances) = unrolled_angles.distances; + let (chunked_adjustments, rest_adjustments) = unrolled_angles.adjustments; + + let (local_sums, local_longest, prefix) = izip!( + chunked_elevs, + chunked_distances, + chunked_adjustments, + OptionIter::new(iter), + ) + .fold( + ( + [Simd::splat(0.0); UNROLL], + [Simd::splat(0.0); UNROLL], + Simd::splat(-2000.0), + ), + |(sum, longest, prefix), (elevs, dists, adjusts, inds)| { + let (next_sum, next_longest, acc) = line_of_sight::( + vs, // elevs: &[[i16; N]], + elevs, // distances: &[Simd], + dists, // adjustments: &[Simd], + adjusts, // prefix_in: Simd, + prefix, inds, pov_height, // pov_height: f32, + ); + + let mut copied_sum = sum; + zip(copied_sum.iter_mut(), next_sum).for_each(|(old, new)| { + *old += new; + }); + + let mut copied_longest_line = longest; + zip(copied_longest_line.iter_mut(), next_longest).for_each(|(old, new)| { + *old = old.simd_max(new); + }); + + (copied_sum, copied_longest_line, acc) + }, + ); + + let mut sum = local_sums + .iter() + .fold(0.0f32, |acc, partial| acc + partial.reduce_sum()); + + let mut longest = local_longest + .iter() + .fold(0.0f32, |acc, new| acc.max(new.reduce_max())); + + let (sum_buf, longest_buf, _) = line_of_sight::( + vs, + rest_elevs, + rest_distances, + rest_adjustments, + prefix, + rest, + pov_height, + ); + + sum += sum_buf + .iter() + .fold(0.0f32, |acc, partial| acc + partial.reduce_sum()); + + longest = longest_buf + .iter() + .fold(longest, |acc, new| acc.max(new.reduce_max())); + + #[expect( + clippy::as_conversions, + clippy::cast_sign_loss, + reason = "result_idx should be in [0, 2^31]" + )] + // safety: it is guaranteed by the rotation kernel that if the index is + // greater than zero that it is in-bounds. This saves ~10% of bounds checks + unsafe { + *heatmap.get_unchecked_mut(result_tvs_id as usize) += sum; + + let old_longest: *mut f32 = longest_line.get_unchecked_mut(result_tvs_id as usize); + *old_longest = (*old_longest).max(longest); + } +} + +/// `precalculate_distances` precalculates earth curvature adjustments and +/// the distance from a particular point (which is just linear) +fn precalculate_distances( + max_los: usize, +) -> (Vec>, Vec>) +where + LaneCount: SupportedLaneCount, +{ + (0..max_los) + .step_by(WIDTH) + .map(|offset| { + #[expect( + clippy::as_conversions, + clippy::cast_possible_wrap, + clippy::cast_possible_truncation, + reason = "WIDTH < 2^31" + )] + let distance_arr: [i32; WIDTH] = array::from_fn(|i| i as i32); + let distances = Simd::from_array(distance_arr); + + #[expect( + clippy::as_conversions, + clippy::cast_possible_wrap, + clippy::cast_possible_truncation, + reason = "WIDTH < 2^31" + )] + let normalized = (distances + Simd::splat(offset as i32)) * Simd::splat(100i32); + + let floats: Simd = normalized.cast(); + + (floats, floats / Simd::splat(EARTH_RADIUS_SQUARED)) + }) + .unzip() +} + +/// `total_viewshed` computes a total viewshed heatmap for a given elevation map, +/// and corresponding indexes to store the rotated data +#[inline] +fn total_viewshed>( + vs: &V, + elevation_map: &[i16], + indexes: &[i32], + max_los: usize, + output_sector_data: bool, +) -> ViewshedAngle +where + LaneCount: SupportedLaneCount, +{ + assert_eq!( + elevation_map.len(), + 2 * max_los * max_los, + "elevations should be 2 * max_los wide, and max_los tall" + ); + + assert_eq!( + indexes.len(), + 2 * max_los * max_los, + "indexes should be 2 * max_los wide, and max_los tall" + ); + + assert_eq!( + max_los % WIDTH, + 0, + "to help the vectorizer, max_los must be a multiple of {WIDTH}" + ); + + let mut sector_data_buf = vec![0i32; if output_sector_data { max_los * max_los * max_los } else { 0 }]; + let mut heatmap = vec![0.0f32; max_los * max_los]; + let mut longest_line = vec![0.0f32; max_los * max_los]; + let mut sector_data: Option<&mut Vec> = + output_sector_data.then_some(&mut sector_data_buf); + + let width = 2 * max_los; + + // precalculate all distances and their spherical earth "adjustments". + // This saves ~33% of effort inside our hot loop + let (distances, adjustments) = precalculate_distances::(max_los); + + let unrolled_angle = UnrolledAngles { + distances: distances.as_chunks::(), + adjustments: adjustments.as_chunks::(), + }; + + for (line, line_indexes, sector_chunk) in izip!( + elevation_map.chunks_exact(width), + indexes.chunks_exact(width), + OptionIter::new(sector_data.as_mut().map(|sd| sd.chunks_exact_mut(max_los * max_los))), + ) { + for (pov, (&pov_height, &result_dem_id, line_bitmap)) in izip!( + line.iter().take(max_los), + line_indexes.iter().take(max_los), + OptionIter::new(sector_chunk.map(|chunk| chunk.chunks_exact_mut(max_los))) + ) + .enumerate() + { + viewshed( + vs, + pov, + pov_height, + result_dem_id, + max_los, + &mut heatmap, + &mut longest_line, + line, + line_bitmap.map(|bitmap| Indexes{ + indexes_in: line_indexes, + indexes_out: bitmap, + }), + &unrolled_angle, + ); + } + } + + ViewshedAngle { + heatmap, + longest_line, + sector_data: sector_data.cloned(), + } +} + +/// `generate_rotation` generates a rotation "map" for a given elevation list +/// Adapted from [this stack overflow answer](https://stackoverflow.com/a/71901621) +#[expect( + clippy::as_conversions, + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_precision_loss, + reason = "so long as max_los^2 < 2^24, the following `as` conversions are entirely safe" +)] +fn generate_rotation(elevs: &[i16], angle: f64, max_los: usize) -> (Vec, Vec) { + let width = (max_los * 3) as isize; + + #[expect(clippy::integer_division, reason = "we don't need precision here")] + { + assert_eq!( + elevs.len() as isize % width, + 0, + "Elevations array must be square {}%{width} != 0", + elevs.len(), + ); + let elevations_div_width = elevs.len() as isize / width; + assert_eq!( + elevations_div_width, + width, + "Elevations array must be square {}/{width} (={elevations_div_width}) != {width}", + elevs.len() as isize + ); + }; + + let (sin, cos) = (f64::sin(angle.to_radians()), f64::cos(angle.to_radians())); + + #[expect(clippy::integer_division, reason = "we don't need precision here")] + let (x_center, y_center) = (width / 2, width / 2); + + let mut rotation: Vec = Vec::with_capacity(2 * max_los * max_los); + + for x in (max_los as isize)..(max_los as isize) * 2 { + let x_sin = (x - x_center) as f64 * sin; + let x_cos = (x - x_center) as f64 * cos; + for y in (max_los as isize)..width { + let y_sin = (y - y_center) as f64 * sin; + let y_cos = (y - y_center) as f64 * cos; + + let x_rot = (x_cos - y_sin).round() as isize + y_center; + let y_rot = (y_cos + x_sin).round() as isize + x_center; + + let new_idx = x_rot.clamp(0, width - 1) * width + y_rot.clamp(0, width - 1); + + rotation.push(new_idx as i32); + } + } + + debug_assert_eq!( + rotation.len() as isize, + max_los as isize * (2 * max_los as isize), + "the rotation should be 2 * max_los wide, max_los tall" + ); + + // map the indexes to their elevations + let elevations = rotation + .iter() + .map(|&idx| { + if idx < 0i32 { + i16::MIN + } else { + #[expect( + clippy::as_conversions, + reason = "elevations start out as i16s, and i16 -> f32 -> i16 is lossless" + )] + #[expect(clippy::cast_sign_loss, reason = "idx < 2^31, idx >= 0")] + // safety: idx is clamped so a get will always be in-bounds + *unsafe { elevs.get_unchecked(idx as usize) } + } + }) + .collect::>(); + + (rotation, elevations) +} + +/// `ViewshedAngle` holds the cumulative result of a single angle in the `total_viewshed` algorithm +#[derive(Debug)] +struct ViewshedAngle { + /// `heatmap` contains the longest line of sight heatmap for rendering + heatmap: Vec, + /// `longest_line` contains the longest distance for a particular point + longest_line: Vec, + /// `sector_data` holds the visibility calculations for each point in row-major order. + /// elements `[0..max_los)` are for the first point, `[max_los, 2*max_los)` for the second + /// point, and so on. + /// + /// This gets absolutely massive, so we only allocate this if we know for certain we will be using it + sector_data: Option>, +} + +impl ViewshedAngle { + /// `new` creates a new buffer for viewshed results of `heatmap`, + /// `longest_line`, and `sector_data` + fn new(max_los: usize, sector_data: bool) -> Self { + Self { + heatmap: vec![0.0f32; max_los * max_los], + longest_line: vec![0.0f32; max_los * max_los], + sector_data: sector_data.then(|| Vec::with_capacity(max_los * max_los * max_los)), + } + } + + /// `acc` accumulates a single `ViewshedAngle` into another + fn acc(&mut self, other: &Self) { + zip(&mut self.heatmap, &other.heatmap).for_each(|(to, from)| { + *to += *from; + }); + + zip(&mut self.longest_line, &other.longest_line).for_each(|(to, from)| { + *to = (*to).max(*from); + }); + + if let Some(ref mut sector_data) = &mut self.sector_data { + if let Some(other_sector_data) = &other.sector_data { + sector_data.extend_from_slice(other_sector_data); + } + } + } +} + +/// `kernel` is a CPU-based total viewshed kernel. It makes use of image rotation tof +/// optimize the cache locality of all lookups for a total viewshed calculation +fn kernel(elevations: &[i16], max_los_points: usize, angle: usize) -> ViewshedAngle { + assert!(angle < 360, "angle must be [0, 360)"); + let mut start = Instant::now(); + + #[expect( + clippy::as_conversions, + clippy::cast_precision_loss, + reason = "angle is [0,360), not more than 2^54" + )] + let (indexes, rotated_elevations) = generate_rotation(elevations, angle as f64, max_los_points); + + tracing::info!( + "rotated {:?} in {:?}, calculating viewshed", + angle, + start.elapsed() + ); + + start = Instant::now(); + + let vectorized = Vectorized {}; + + let result = total_viewshed::<8, 8, Vectorized>( + &vectorized, + &rotated_elevations, + &indexes, + max_los_points, + false, + ); + tracing::info!("kernel for {} run in: {:?}", angle, start.elapsed()); + result +} + +/// `multithreaded_kernel` parallelizes CPU kernel calculations for a `core_count` and calculates +/// `num_angles` different angles +pub fn multithreaded_kernel( + elevations_original: &[i16], + max_los_points_original: usize, + num_angles: usize, + core_count: usize, + output_sector_data: bool, +) -> (Vec, Vec, Option>) { + let max_los_points = max_los_points_original.div_ceil(4) * 4; + let dem_width = max_los_points * 3; + let mut elevations_vec = elevations_original.to_vec(); + elevations_vec.resize(dem_width.pow(2), 0); + let elevations = &elevations_vec; + + if max_los_points != max_los_points_original { + tracing::warn!("LoS: {max_los_points_original} to {max_los_points}"); + } + if elevations.len() != elevations_original.len() { + tracing::warn!( + "Elevations array length resized: {} to {}", + elevations_original.len(), + elevations_vec.len() + ); + } + + #[expect( + clippy::expect_used, + reason = "threadpool must be created for program to run" + )] + let pool = ThreadPoolBuilder::new() + .num_threads(core_count) + .build() + .expect("couldn't build threadpool"); + + let mut final_angle = ViewshedAngle::new(max_los_points, output_sector_data); + let angle_mu = &Mutex::new(&mut final_angle); + + pool.install(move || { + (0..num_angles) + .into_par_iter() + .map(|angle| kernel(elevations, max_los_points, angle)) + .for_each(|vs| { + #[expect(clippy::expect_used, reason = "a poisoned mutex should crash")] + let mut angle_guard = angle_mu.lock().expect("mutex poisoned"); + + angle_guard.acc(&vs); + }); + }); + + let (heatmap, longest_line, sector) = ( + mem::take(&mut final_angle.heatmap), + mem::take(&mut final_angle.longest_line), + final_angle + .sector_data + .map(|mut sector_data| mem::take(&mut sector_data)), + ); + + (heatmap, longest_line, sector) +} diff --git a/crates/total-viewsheds/src/dem.rs b/crates/total-viewsheds/src/dem.rs index f8b4cf2..01edd8f 100644 --- a/crates/total-viewsheds/src/dem.rs +++ b/crates/total-viewsheds/src/dem.rs @@ -194,7 +194,6 @@ impl DEM { reason = "We don't want to output GBs of data!" )] impl std::fmt::Debug for DEM { - #[expect(clippy::min_ident_chars, reason = "This is from `std`")] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DEM") .field("width", &self.width) diff --git a/crates/total-viewsheds/src/main.rs b/crates/total-viewsheds/src/main.rs index b246f4d..36b8c35 100644 --- a/crates/total-viewsheds/src/main.rs +++ b/crates/total-viewsheds/src/main.rs @@ -1,6 +1,8 @@ //! Total Viewshed Calculator - +#![feature(portable_simd)] +#![feature(generic_const_exprs)] #![expect(clippy::pub_use, reason = "I admit I don't understand the other way.")] +extern crate core; use clap::Parser as _; use color_eyre::eyre::{ContextCompat as _, Result}; @@ -29,6 +31,7 @@ mod output { pub mod ring_data; pub mod viewshed; } +mod cpu; mod projection; fn main() -> Result<()> { diff --git a/init.sh b/init.sh new file mode 100644 index 0000000..a4a64a6 --- /dev/null +++ b/init.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +sudo apt install -y git clang +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +git clone https://github.com/AllTheLines/CacheTVS +cd CacheTVS && git checkout cpu-clean diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..9d26467 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2025-10-20" +components = ["rustfmt", "clippy"] \ No newline at end of file