Added benchmarks and fixed a parallelism bug
This commit is contained in:
1 parent
d61552c33c
commit
d08bc33e8d
19 files changed
+758
-27
No files matched your search
Generated
+299
@@ -8,6 +8,24 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloca"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -23,6 +41,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anes"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anise"
|
name = "anise"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -226,6 +250,22 @@ version = "1.12.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cast"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -238,6 +278,33 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||||
|
dependencies = [
|
||||||
|
"ciborium-io",
|
||||||
|
"ciborium-ll",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-io"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ciborium-ll"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||||
|
dependencies = [
|
||||||
|
"ciborium-io",
|
||||||
|
"half",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -325,6 +392,66 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "criterion"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
|
||||||
|
dependencies = [
|
||||||
|
"alloca",
|
||||||
|
"anes",
|
||||||
|
"cast",
|
||||||
|
"ciborium",
|
||||||
|
"clap",
|
||||||
|
"criterion-plot",
|
||||||
|
"itertools",
|
||||||
|
"num-traits",
|
||||||
|
"oorandom",
|
||||||
|
"page_size",
|
||||||
|
"plotters",
|
||||||
|
"rayon",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tinytemplate",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "criterion-plot"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
|
||||||
|
dependencies = [
|
||||||
|
"cast",
|
||||||
|
"itertools",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
@@ -380,6 +507,12 @@ dependencies = [
|
|||||||
"litrs",
|
"litrs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -395,6 +528,12 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.10"
|
version = "1.1.10"
|
||||||
@@ -643,6 +782,15 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -1099,6 +1247,12 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oorandom"
|
||||||
|
version = "11.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
version = "5.4.0"
|
version = "5.4.0"
|
||||||
@@ -1121,6 +1275,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "page_size"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "papergrid"
|
name = "papergrid"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -1167,6 +1331,34 @@ version = "0.3.34"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"plotters-backend",
|
||||||
|
"plotters-svg",
|
||||||
|
"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-svg"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||||
|
dependencies = [
|
||||||
|
"plotters-backend",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
@@ -1289,6 +1481,26 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -1298,6 +1510,35 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "renderdoc-sys"
|
name = "renderdoc-sys"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -1312,6 +1553,7 @@ dependencies = [
|
|||||||
"approx",
|
"approx",
|
||||||
"astrokit",
|
"astrokit",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"criterion",
|
||||||
"gltf",
|
"gltf",
|
||||||
"hifitime",
|
"hifitime",
|
||||||
"image",
|
"image",
|
||||||
@@ -1362,6 +1604,15 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -1420,6 +1671,12 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simba"
|
name = "simba"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -1597,6 +1854,16 @@ dependencies = [
|
|||||||
"syn 3.0.9",
|
"syn 3.0.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinytemplate"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.53.3"
|
version = "1.53.3"
|
||||||
@@ -1687,6 +1954,16 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.129"
|
version = "0.2.129"
|
||||||
@@ -1958,6 +2235,22 @@ dependencies = [
|
|||||||
"safe_arch",
|
"safe_arch",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
@@ -1967,6 +2260,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.62.2"
|
version = "0.62.2"
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ fn render(
|
|||||||
},
|
},
|
||||||
appearance: file.appearance(),
|
appearance: file.appearance(),
|
||||||
read_radiance: false,
|
read_radiance: false,
|
||||||
|
profile: timings.is_some(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@@ -121,6 +122,7 @@ fn render(
|
|||||||
let loading = start.elapsed();
|
let loading = start.elapsed();
|
||||||
|
|
||||||
let mut frame_times = Vec::new();
|
let mut frame_times = Vec::new();
|
||||||
|
let mut pass_times = Vec::new();
|
||||||
let mut frame = None;
|
let mut frame = None;
|
||||||
for _ in 0..timings.unwrap_or(1).max(1) {
|
for _ in 0..timings.unwrap_or(1).max(1) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
@@ -129,6 +131,7 @@ fn render(
|
|||||||
.and_then(|p| p.wait())
|
.and_then(|p| p.wait())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
frame_times.push(start.elapsed().as_secs_f64() * 1e3);
|
frame_times.push(start.elapsed().as_secs_f64() * 1e3);
|
||||||
|
pass_times.extend(rendered.timings);
|
||||||
frame = Some(rendered);
|
frame = Some(rendered);
|
||||||
}
|
}
|
||||||
let frame = frame.expect("at least one frame");
|
let frame = frame.expect("at least one frame");
|
||||||
@@ -153,6 +156,23 @@ fn render(
|
|||||||
warm.len()
|
warm.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// GPU time per pass, median over the frames after the first.
|
||||||
|
if pass_times.len() > 1 {
|
||||||
|
let median = |f: fn(&resolved::gpu::profile::PassTimings) -> f64| {
|
||||||
|
let mut v: Vec<f64> = pass_times[1..].iter().map(f).collect();
|
||||||
|
v.sort_by(f64::total_cmp);
|
||||||
|
v[v.len() / 2]
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"gpu bodies {:.3} ms, mesh {:.3} ms, sensor {:.3} ms, first to last {:.3} ms (medians)",
|
||||||
|
median(|t| t.bodies),
|
||||||
|
median(|t| t.mesh),
|
||||||
|
median(|t| t.sensor),
|
||||||
|
median(|t| t.total)
|
||||||
|
);
|
||||||
|
} else if pass_times.is_empty() {
|
||||||
|
println!("gpu no pass timings: this adapter has no timestamp queries");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let detector = &renderer.camera().detector;
|
let detector = &renderer.camera().detector;
|
||||||
if let Some(out) = out {
|
if let Some(out) = out {
|
||||||
|
|||||||
@@ -35,3 +35,8 @@ stl_io = "0.11"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
approx = "0.5"
|
approx = "0.5"
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
|
criterion = "0.8"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "render"
|
||||||
|
harness = false
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
//! End-to-end frame times: `submit` then `wait`, including reading the DN image back.
|
||||||
|
//!
|
||||||
|
//! Every frame shows a model in the example scenes' composition: an isometric view
|
||||||
|
//! from low orbit with Earth's limb 2 degrees above the image center. Models are the
|
||||||
|
//! box-wing from the repository and, if `scripts/fetch-models.sh` has been run,
|
||||||
|
//! Landsat 8 (85k triangles) and ICESat-2 (752k triangles). Each runs on the GPU and
|
||||||
|
//! on lavapipe at 1024 x 1024 and 1920 x 1080.
|
||||||
|
//!
|
||||||
|
//! Per-pass GPU times are not here; `resolved render --timings N` reports them.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
|
use resolved::gpu::AdapterChoice;
|
||||||
|
use resolved::nalgebra::{Matrix3, Rotation3, UnitQuaternion, Vector3};
|
||||||
|
use resolved::radiometry::AU;
|
||||||
|
use resolved::{CameraConfig, LoadOptions, Mesh, Pose, Renderer, RendererOptions, Scene, Target};
|
||||||
|
|
||||||
|
const EARTH_RADIUS: f64 = 6_378_137.0;
|
||||||
|
const ORBIT: f64 = 7.0e6;
|
||||||
|
|
||||||
|
fn examples() -> PathBuf {
|
||||||
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/models")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The models to bench, skipping NASA models that have not been fetched.
|
||||||
|
fn models() -> Vec<(&'static str, Mesh)> {
|
||||||
|
let mut models = vec![(
|
||||||
|
"boxwing",
|
||||||
|
Mesh::load(&examples().join("boxwing.gltf"), &LoadOptions::default()).unwrap(),
|
||||||
|
)];
|
||||||
|
for name in ["landsat8", "icesat2"] {
|
||||||
|
let path = examples().join("nasa").join(format!("{name}.glb"));
|
||||||
|
if path.is_file() {
|
||||||
|
models.push((name, Mesh::load(&path, &LoadOptions::default()).unwrap()));
|
||||||
|
} else {
|
||||||
|
eprintln!("skipping {name}: run scripts/fetch-models.sh to bench it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
models
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scene and observer for `mesh`, as in `scripts/make-example-scenes.py`.
|
||||||
|
fn limb_view(mesh: &Mesh, model: resolved::ModelId) -> (Scene, Pose) {
|
||||||
|
let (mut lo, mut hi) = (Vector3::repeat(f64::MAX), Vector3::repeat(f64::MIN));
|
||||||
|
for p in &mesh.positions {
|
||||||
|
let p = Vector3::from(*p).cast::<f64>();
|
||||||
|
lo = lo.inf(&p);
|
||||||
|
hi = hi.sup(&p);
|
||||||
|
}
|
||||||
|
let center = (lo + hi) / 2.0;
|
||||||
|
let radius = (hi - lo).norm() / 2.0;
|
||||||
|
|
||||||
|
let limb = (EARTH_RADIUS / ORBIT).asin();
|
||||||
|
let theta = limb - 2f64.to_radians();
|
||||||
|
let look = Vector3::new(ORBIT, 0.0, 0.0);
|
||||||
|
let boresight = Vector3::new(-theta.cos(), theta.sin(), 0.0);
|
||||||
|
// Body (1, 1, 1) toward the camera, body +Z toward the local vertical.
|
||||||
|
let frame = |a: Vector3<f64>, b: Vector3<f64>| {
|
||||||
|
let t1 = a.normalize();
|
||||||
|
let t2 = a.cross(&b).normalize();
|
||||||
|
Matrix3::from_columns(&[t1, t2, t1.cross(&t2)])
|
||||||
|
};
|
||||||
|
let rotation =
|
||||||
|
frame(-boresight, Vector3::x()) * frame(Vector3::repeat(1.0), Vector3::z()).transpose();
|
||||||
|
let attitude = UnitQuaternion::from_rotation_matrix(&Rotation3::from_matrix(&rotation));
|
||||||
|
let distance = radius / 2.8f64.to_radians().tan();
|
||||||
|
let observer = Pose::looking_at(look - boresight * distance, look, Vector3::x()).unwrap();
|
||||||
|
let scene = Scene {
|
||||||
|
earth_attitude: UnitQuaternion::identity(),
|
||||||
|
sun: Vector3::new(0.743, -0.669, 0.017).normalize() * AU,
|
||||||
|
moon: Vector3::new(0.0, 0.0, -3.844e8),
|
||||||
|
targets: vec![Target {
|
||||||
|
model,
|
||||||
|
pose: Pose::new(look - attitude * center, attitude),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
(scene, observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn camera(width: u32, height: u32) -> CameraConfig {
|
||||||
|
let mut camera = CameraConfig::example();
|
||||||
|
camera.width = width;
|
||||||
|
camera.height = height;
|
||||||
|
camera
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frames(c: &mut Criterion) {
|
||||||
|
let models = models();
|
||||||
|
for (device, adapter) in [("gpu", AdapterChoice::Gpu), ("cpu", AdapterChoice::Cpu)] {
|
||||||
|
let mut group = c.benchmark_group(format!("frame/{device}"));
|
||||||
|
if device == "cpu" {
|
||||||
|
group.sample_size(20);
|
||||||
|
}
|
||||||
|
for (width, height) in [(1024, 1024), (1920, 1080)] {
|
||||||
|
let options = RendererOptions {
|
||||||
|
adapter: adapter.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut renderer = Renderer::new(camera(width, height), options).unwrap();
|
||||||
|
for (name, mesh) in &models {
|
||||||
|
let model = renderer.load_model(mesh).unwrap();
|
||||||
|
let (scene, observer) = limb_view(mesh, model);
|
||||||
|
let id = BenchmarkId::new(format!("{width}x{height}"), name);
|
||||||
|
group.bench_function(id, |b| {
|
||||||
|
b.iter(|| renderer.submit(&scene, &observer).unwrap().wait().unwrap())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("setup");
|
||||||
|
group.sample_size(10);
|
||||||
|
// Create and drop as one cycle. Timing creation alone depends on when criterion
|
||||||
|
// drops the previous renderer, and with the driver unloading in between it came out
|
||||||
|
// bimodal (16 or 160 ms) before the Vulkan instance was shared.
|
||||||
|
group.bench_function("renderer/gpu/1920x1080", |b| {
|
||||||
|
b.iter(|| drop(Renderer::new(camera(1920, 1080), RendererOptions::default()).unwrap()))
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, frames, setup);
|
||||||
|
criterion_main!(benches);
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
use nalgebra::Vector3;
|
use nalgebra::Vector3;
|
||||||
|
|
||||||
|
use super::profile::PassTimestamps;
|
||||||
use super::targets::{FrameTargets, DEPTH_COMPARE, DEPTH_FORMAT, RADIANCE_FORMAT};
|
use super::targets::{FrameTargets, DEPTH_COMPARE, DEPTH_FORMAT, RADIANCE_FORMAT};
|
||||||
use super::GpuContext;
|
use super::GpuContext;
|
||||||
use crate::bodies::{Appearance, EARTH_EQUATORIAL_RADIUS, EARTH_POLAR_RADIUS, MOON_RADIUS};
|
use crate::bodies::{Appearance, EARTH_EQUATORIAL_RADIUS, EARTH_POLAR_RADIUS, MOON_RADIUS};
|
||||||
@@ -186,13 +187,15 @@ impl BodiesPass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uploads this frame's uniforms and records the pass into `encoder`.
|
/// Uploads this frame's uniforms and records the pass into `encoder`, writing
|
||||||
|
/// `timestamps` around it if given.
|
||||||
pub fn encode(
|
pub fn encode(
|
||||||
&self,
|
&self,
|
||||||
ctx: &GpuContext,
|
ctx: &GpuContext,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
targets: &FrameTargets,
|
targets: &FrameTargets,
|
||||||
uniforms: &BodiesUniforms,
|
uniforms: &BodiesUniforms,
|
||||||
|
timestamps: Option<PassTimestamps>,
|
||||||
) {
|
) {
|
||||||
ctx.queue
|
ctx.queue
|
||||||
.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
|
.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
|
||||||
@@ -217,6 +220,7 @@ impl BodiesPass {
|
|||||||
}),
|
}),
|
||||||
stencil_ops: None,
|
stencil_ops: None,
|
||||||
}),
|
}),
|
||||||
|
timestamp_writes: timestamps.map(PassTimestamps::render),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
|||||||
@@ -41,25 +41,32 @@ impl std::fmt::Debug for GpuContext {
|
|||||||
|
|
||||||
/// Every adapter this machine offers, in the order the drivers report them.
|
/// Every adapter this machine offers, in the order the drivers report them.
|
||||||
pub fn adapters() -> Vec<wgpu::AdapterInfo> {
|
pub fn adapters() -> Vec<wgpu::AdapterInfo> {
|
||||||
let instance = instance();
|
pollster::block_on(instance().enumerate_adapters(BACKENDS))
|
||||||
pollster::block_on(instance.enumerate_adapters(BACKENDS))
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|a| a.get_info())
|
.map(|a| a.get_info())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instance() -> wgpu::Instance {
|
/// The one wgpu instance this process uses, created on first use and never dropped.
|
||||||
wgpu::Instance::new(wgpu::InstanceDescriptor {
|
///
|
||||||
backends: BACKENDS,
|
/// One per renderer would be simpler, but creating and destroying Vulkan instances on
|
||||||
..wgpu::InstanceDescriptor::new_without_display_handle_from_env()
|
/// several threads at once crashes the Vulkan loader (1.4.363 here): when the last
|
||||||
|
/// instance goes, it unloads the drivers, and an instance being created on another
|
||||||
|
/// thread at that moment calls into a driver that is no longer loaded.
|
||||||
|
fn instance() -> &'static wgpu::Instance {
|
||||||
|
static INSTANCE: std::sync::OnceLock<wgpu::Instance> = std::sync::OnceLock::new();
|
||||||
|
INSTANCE.get_or_init(|| {
|
||||||
|
wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
|
backends: BACKENDS,
|
||||||
|
..wgpu::InstanceDescriptor::new_without_display_handle_from_env()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuContext {
|
impl GpuContext {
|
||||||
/// Opens a device on the adapter `choice` selects.
|
/// Opens a device on the adapter `choice` selects.
|
||||||
pub fn new(choice: &AdapterChoice) -> Result<Self, Error> {
|
pub fn new(choice: &AdapterChoice) -> Result<Self, Error> {
|
||||||
let instance = instance();
|
let candidates = pollster::block_on(instance().enumerate_adapters(BACKENDS));
|
||||||
let candidates = pollster::block_on(instance.enumerate_adapters(BACKENDS));
|
|
||||||
let infos: Vec<_> = candidates.iter().map(|a| a.get_info()).collect();
|
let infos: Vec<_> = candidates.iter().map(|a| a.get_info()).collect();
|
||||||
let kinds: Vec<_> = infos
|
let kinds: Vec<_> = infos
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
use nalgebra::{Matrix3, Vector3};
|
use nalgebra::{Matrix3, Vector3};
|
||||||
|
|
||||||
use super::bodies::MAX_CHANNELS;
|
use super::bodies::MAX_CHANNELS;
|
||||||
|
use super::profile::PassTimestamps;
|
||||||
use super::targets::{FrameTargets, DEPTH_COMPARE, DEPTH_FORMAT, RADIANCE_FORMAT};
|
use super::targets::{FrameTargets, DEPTH_COMPARE, DEPTH_FORMAT, RADIANCE_FORMAT};
|
||||||
use super::GpuContext;
|
use super::GpuContext;
|
||||||
use crate::camera::Intrinsics;
|
use crate::camera::Intrinsics;
|
||||||
@@ -284,6 +285,7 @@ impl MeshPass {
|
|||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
targets: &FrameTargets,
|
targets: &FrameTargets,
|
||||||
draws: &[(&GpuMesh, MeshDraw)],
|
draws: &[(&GpuMesh, MeshDraw)],
|
||||||
|
timestamps: Option<PassTimestamps>,
|
||||||
) {
|
) {
|
||||||
if draws.len() > self.capacity {
|
if draws.len() > self.capacity {
|
||||||
self.capacity = draws.len().next_power_of_two();
|
self.capacity = draws.len().next_power_of_two();
|
||||||
@@ -318,6 +320,7 @@ impl MeshPass {
|
|||||||
}),
|
}),
|
||||||
stencil_ops: None,
|
stencil_ops: None,
|
||||||
}),
|
}),
|
||||||
|
timestamp_writes: timestamps.map(PassTimestamps::render),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
pub mod bodies;
|
pub mod bodies;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
|
pub mod profile;
|
||||||
pub mod sensor;
|
pub mod sensor;
|
||||||
pub mod targets;
|
pub mod targets;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
//! GPU timestamps around each pass, for seeing where a frame's time goes.
|
||||||
|
//!
|
||||||
|
//! Each pass writes a timestamp when it starts and when it ends. After the frame the
|
||||||
|
//! queries are resolved into a buffer and read back with the image. Needs the
|
||||||
|
//! adapter's `TIMESTAMP_QUERY` feature; without it there is nothing to measure and
|
||||||
|
//! [`Profiler::new`] returns `None`.
|
||||||
|
|
||||||
|
use super::GpuContext;
|
||||||
|
|
||||||
|
/// The passes timed, in the order they run.
|
||||||
|
pub const PASSES: [&str; 3] = ["bodies", "mesh", "sensor"];
|
||||||
|
|
||||||
|
/// Where one pass writes its begin and end timestamps.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PassTimestamps<'a> {
|
||||||
|
set: &'a wgpu::QuerySet,
|
||||||
|
begin: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PassTimestamps<'a> {
|
||||||
|
/// For a render pass descriptor.
|
||||||
|
pub fn render(self) -> wgpu::RenderPassTimestampWrites<'a> {
|
||||||
|
wgpu::RenderPassTimestampWrites {
|
||||||
|
query_set: self.set,
|
||||||
|
beginning_of_pass_write_index: Some(self.begin),
|
||||||
|
end_of_pass_write_index: Some(self.begin + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For a compute pass descriptor.
|
||||||
|
pub fn compute(self) -> wgpu::ComputePassTimestampWrites<'a> {
|
||||||
|
wgpu::ComputePassTimestampWrites {
|
||||||
|
query_set: self.set,
|
||||||
|
beginning_of_pass_write_index: Some(self.begin),
|
||||||
|
end_of_pass_write_index: Some(self.begin + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GPU time spent in each pass of one frame, in milliseconds.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct PassTimings {
|
||||||
|
/// Earth, Moon and Sun.
|
||||||
|
pub bodies: f64,
|
||||||
|
/// Target meshes.
|
||||||
|
pub mesh: f64,
|
||||||
|
/// Radiance to DN.
|
||||||
|
pub sensor: f64,
|
||||||
|
/// From the start of the first pass to the end of the last, gaps included.
|
||||||
|
pub total: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A timestamp query set with the buffers to read it back.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Profiler {
|
||||||
|
set: wgpu::QuerySet,
|
||||||
|
resolve: wgpu::Buffer,
|
||||||
|
/// Read back with the frame; the renderer maps it alongside the image.
|
||||||
|
pub staging: wgpu::Buffer,
|
||||||
|
/// Nanoseconds per timestamp tick.
|
||||||
|
period: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
const QUERIES: u32 = 2 * PASSES.len() as u32;
|
||||||
|
|
||||||
|
impl Profiler {
|
||||||
|
/// A profiler, if the device was opened with timestamp queries.
|
||||||
|
pub fn new(ctx: &GpuContext) -> Option<Self> {
|
||||||
|
if !ctx
|
||||||
|
.device
|
||||||
|
.features()
|
||||||
|
.contains(wgpu::Features::TIMESTAMP_QUERY)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let size = QUERIES as u64 * 8;
|
||||||
|
let buffer = |label, usage| {
|
||||||
|
ctx.device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
size,
|
||||||
|
usage,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
set: ctx.device.create_query_set(&wgpu::QuerySetDescriptor {
|
||||||
|
label: Some("pass timestamps"),
|
||||||
|
ty: wgpu::QueryType::Timestamp,
|
||||||
|
count: QUERIES,
|
||||||
|
}),
|
||||||
|
resolve: buffer(
|
||||||
|
"timestamp resolve",
|
||||||
|
wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
|
||||||
|
),
|
||||||
|
staging: buffer(
|
||||||
|
"timestamp staging",
|
||||||
|
wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||||
|
),
|
||||||
|
period: ctx.queue.get_timestamp_period() as f64,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The timestamps for pass `index` of [`PASSES`].
|
||||||
|
pub fn pass(&self, index: usize) -> PassTimestamps<'_> {
|
||||||
|
PassTimestamps {
|
||||||
|
set: &self.set,
|
||||||
|
begin: 2 * index as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records resolving the queries into the staging buffer, after the last pass.
|
||||||
|
pub fn resolve(&self, encoder: &mut wgpu::CommandEncoder) {
|
||||||
|
encoder.resolve_query_set(&self.set, 0..QUERIES, &self.resolve, 0);
|
||||||
|
encoder.copy_buffer_to_buffer(&self.resolve, 0, &self.staging, 0, self.resolve.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts mapped staging contents to milliseconds.
|
||||||
|
pub fn timings(&self, ticks: &[u64]) -> PassTimings {
|
||||||
|
let ms = |a: u64, b: u64| b.wrapping_sub(a) as f64 * self.period * 1e-6;
|
||||||
|
PassTimings {
|
||||||
|
bodies: ms(ticks[0], ticks[1]),
|
||||||
|
mesh: ms(ticks[2], ticks[3]),
|
||||||
|
sensor: ms(ticks[4], ticks[5]),
|
||||||
|
total: ms(ticks[0], ticks[5]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
//! writes [`FrameTargets::dn`]. No noise yet (M3); the result for each pixel is
|
//! writes [`FrameTargets::dn`]. No noise yet (M3); the result for each pixel is
|
||||||
//! [`Detector::dn`](crate::camera::Detector::dn) of [`Radiometry::electrons`].
|
//! [`Detector::dn`](crate::camera::Detector::dn) of [`Radiometry::electrons`].
|
||||||
|
|
||||||
|
use super::profile::PassTimestamps;
|
||||||
use super::targets::FrameTargets;
|
use super::targets::FrameTargets;
|
||||||
use super::GpuContext;
|
use super::GpuContext;
|
||||||
use crate::camera::CameraConfig;
|
use crate::camera::CameraConfig;
|
||||||
@@ -88,6 +89,7 @@ impl SensorPass {
|
|||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
targets: &FrameTargets,
|
targets: &FrameTargets,
|
||||||
uniforms: &SensorUniforms,
|
uniforms: &SensorUniforms,
|
||||||
|
timestamps: Option<PassTimestamps>,
|
||||||
) {
|
) {
|
||||||
ctx.queue
|
ctx.queue
|
||||||
.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
|
.write_buffer(&self.uniforms, 0, bytemuck::bytes_of(uniforms));
|
||||||
@@ -115,7 +117,7 @@ impl SensorPass {
|
|||||||
let (x, y) = (groups.min(65_535), groups.div_ceil(65_535));
|
let (x, y) = (groups.min(65_535), groups.div_ceil(65_535));
|
||||||
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||||
label: Some("sensor"),
|
label: Some("sensor"),
|
||||||
timestamp_writes: None,
|
timestamp_writes: timestamps.map(PassTimestamps::compute),
|
||||||
});
|
});
|
||||||
pass.set_pipeline(&self.pipeline);
|
pass.set_pipeline(&self.pipeline);
|
||||||
pass.set_bind_group(0, &bind_group, &[]);
|
pass.set_bind_group(0, &bind_group, &[]);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use crate::camera::CameraConfig;
|
|||||||
use crate::eclipse::sun_visible_fraction;
|
use crate::eclipse::sun_visible_fraction;
|
||||||
use crate::gpu::bodies::{BodiesPass, BodiesUniforms, BodyRadiometry};
|
use crate::gpu::bodies::{BodiesPass, BodiesUniforms, BodyRadiometry};
|
||||||
use crate::gpu::mesh::{GpuMesh, MeshDraw, MeshPass};
|
use crate::gpu::mesh::{GpuMesh, MeshDraw, MeshPass};
|
||||||
|
use crate::gpu::profile::{PassTimings, Profiler};
|
||||||
use crate::gpu::sensor::{SensorPass, SensorUniforms};
|
use crate::gpu::sensor::{SensorPass, SensorUniforms};
|
||||||
use crate::gpu::targets::FrameTargets;
|
use crate::gpu::targets::FrameTargets;
|
||||||
use crate::gpu::{AdapterChoice, GpuContext};
|
use crate::gpu::{AdapterChoice, GpuContext};
|
||||||
@@ -33,6 +34,9 @@ pub struct RendererOptions {
|
|||||||
/// Also read back the channel radiance image each frame. Costs another 16 bytes per
|
/// Also read back the channel radiance image each frame. Costs another 16 bytes per
|
||||||
/// pixel of transfer; for debugging and tests.
|
/// pixel of transfer; for debugging and tests.
|
||||||
pub read_radiance: bool,
|
pub read_radiance: bool,
|
||||||
|
/// Time each pass on the GPU and report it in [`Frame::timings`]. Needs an adapter
|
||||||
|
/// with timestamp queries; without one, timings are `None`.
|
||||||
|
pub profile: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One rendered image.
|
/// One rendered image.
|
||||||
@@ -48,6 +52,9 @@ pub struct Frame {
|
|||||||
pub dn: Vec<u16>,
|
pub dn: Vec<u16>,
|
||||||
/// Channel radiance, if [`RendererOptions::read_radiance`] was set.
|
/// Channel radiance, if [`RendererOptions::read_radiance`] was set.
|
||||||
pub radiance: Option<Vec<[f32; 4]>>,
|
pub radiance: Option<Vec<[f32; 4]>>,
|
||||||
|
/// GPU time per pass, if [`RendererOptions::profile`] was set and the adapter
|
||||||
|
/// supports timestamps.
|
||||||
|
pub timings: Option<PassTimings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
@@ -58,6 +65,11 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Renders frames for one camera.
|
/// Renders frames for one camera.
|
||||||
|
///
|
||||||
|
/// Create and drop renderers from one thread at a time. With current drivers
|
||||||
|
/// (NVIDIA 615.71 and Mesa's lavapipe, measured on mainframe), creating or dropping
|
||||||
|
/// Vulkan devices on several threads at once occasionally crashes or hangs inside the
|
||||||
|
/// driver. Rendering on several renderers at once, once they exist, is not affected.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
ctx: GpuContext,
|
ctx: GpuContext,
|
||||||
@@ -72,6 +84,7 @@ pub struct Renderer {
|
|||||||
models: Vec<GpuMesh>,
|
models: Vec<GpuMesh>,
|
||||||
dn_staging: wgpu::Buffer,
|
dn_staging: wgpu::Buffer,
|
||||||
radiance_staging: Option<(wgpu::Buffer, u32)>,
|
radiance_staging: Option<(wgpu::Buffer, u32)>,
|
||||||
|
profiler: Option<Profiler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
@@ -104,6 +117,11 @@ impl Renderer {
|
|||||||
row,
|
row,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
let profiler = if options.profile {
|
||||||
|
Profiler::new(&ctx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
bodies: BodiesPass::new(&ctx),
|
bodies: BodiesPass::new(&ctx),
|
||||||
meshes: MeshPass::new(&ctx),
|
meshes: MeshPass::new(&ctx),
|
||||||
@@ -117,6 +135,7 @@ impl Renderer {
|
|||||||
models: Vec::new(),
|
models: Vec::new(),
|
||||||
dn_staging,
|
dn_staging,
|
||||||
radiance_staging,
|
radiance_staging,
|
||||||
|
profiler,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,11 +195,21 @@ impl Renderer {
|
|||||||
|
|
||||||
let ctx = &self.ctx;
|
let ctx = &self.ctx;
|
||||||
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
||||||
|
let stamps = |i| self.profiler.as_ref().map(|p| p.pass(i));
|
||||||
self.bodies
|
self.bodies
|
||||||
.encode(ctx, &mut encoder, &self.targets, &bodies);
|
.encode(ctx, &mut encoder, &self.targets, &bodies, stamps(0));
|
||||||
self.meshes.encode(ctx, &mut encoder, &self.targets, &draws);
|
self.meshes
|
||||||
self.sensor
|
.encode(ctx, &mut encoder, &self.targets, &draws, stamps(1));
|
||||||
.encode(ctx, &mut encoder, &self.targets, &self.sensor_uniforms);
|
self.sensor.encode(
|
||||||
|
ctx,
|
||||||
|
&mut encoder,
|
||||||
|
&self.targets,
|
||||||
|
&self.sensor_uniforms,
|
||||||
|
stamps(2),
|
||||||
|
);
|
||||||
|
if let Some(profiler) = &self.profiler {
|
||||||
|
profiler.resolve(&mut encoder);
|
||||||
|
}
|
||||||
encoder.copy_buffer_to_buffer(
|
encoder.copy_buffer_to_buffer(
|
||||||
&self.targets.dn,
|
&self.targets.dn,
|
||||||
0,
|
0,
|
||||||
@@ -211,9 +240,7 @@ impl Renderer {
|
|||||||
|
|
||||||
// Ask for the staging buffers now; the mapping completes when the GPU finishes.
|
// Ask for the staging buffers now; the mapping completes when the GPU finishes.
|
||||||
let (sender, mapped) = mpsc::channel();
|
let (sender, mapped) = mpsc::channel();
|
||||||
let staged =
|
for buffer in self.staging_buffers() {
|
||||||
std::iter::once(&self.dn_staging).chain(self.radiance_staging.as_ref().map(|(b, _)| b));
|
|
||||||
for buffer in staged {
|
|
||||||
let sender = sender.clone();
|
let sender = sender.clone();
|
||||||
buffer
|
buffer
|
||||||
.slice(..)
|
.slice(..)
|
||||||
@@ -225,6 +252,13 @@ impl Renderer {
|
|||||||
mapped,
|
mapped,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every buffer read back after a frame.
|
||||||
|
fn staging_buffers(&self) -> impl Iterator<Item = &wgpu::Buffer> {
|
||||||
|
std::iter::once(&self.dn_staging)
|
||||||
|
.chain(self.radiance_staging.as_ref().map(|(b, _)| b))
|
||||||
|
.chain(self.profiler.as_ref().map(|p| &p.staging))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A frame the GPU is working on. [`Pending::wait`] returns it.
|
/// A frame the GPU is working on. [`Pending::wait`] returns it.
|
||||||
@@ -271,12 +305,28 @@ impl Pending<'_> {
|
|||||||
Some(texels)
|
Some(texels)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let timings = match &r.profiler {
|
||||||
|
None => None,
|
||||||
|
Some(profiler) => {
|
||||||
|
let timings = {
|
||||||
|
let view = profiler
|
||||||
|
.staging
|
||||||
|
.slice(..)
|
||||||
|
.get_mapped_range()
|
||||||
|
.map_err(gpu_error)?;
|
||||||
|
profiler.timings(bytemuck::cast_slice::<u8, u64>(&view))
|
||||||
|
};
|
||||||
|
profiler.staging.unmap();
|
||||||
|
Some(timings)
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(Frame {
|
Ok(Frame {
|
||||||
width: r.camera.width,
|
width: r.camera.width,
|
||||||
height: r.camera.height,
|
height: r.camera.height,
|
||||||
channels: r.camera.channels.len() as u32,
|
channels: r.camera.channels.len() as u32,
|
||||||
dn,
|
dn,
|
||||||
radiance,
|
radiance,
|
||||||
|
timings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,8 +343,7 @@ impl Pending<'_> {
|
|||||||
timeout: None,
|
timeout: None,
|
||||||
})
|
})
|
||||||
.map_err(gpu_error)?;
|
.map_err(gpu_error)?;
|
||||||
let buffers = 1 + usize::from(r.radiance_staging.is_some());
|
for _ in r.staging_buffers() {
|
||||||
for _ in 0..buffers {
|
|
||||||
self.mapped.recv().map_err(gpu_error)?.map_err(gpu_error)?;
|
self.mapped.recv().map_err(gpu_error)?.map_err(gpu_error)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -304,8 +353,7 @@ impl Pending<'_> {
|
|||||||
impl Drop for Pending<'_> {
|
impl Drop for Pending<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.index.is_some() && self.block().is_ok() {
|
if self.index.is_some() && self.block().is_ok() {
|
||||||
self.renderer.dn_staging.unmap();
|
for buffer in self.renderer.staging_buffers() {
|
||||||
if let Some((buffer, _)) = &self.renderer.radiance_staging {
|
|
||||||
buffer.unmap();
|
buffer.unmap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ fn render_with(
|
|||||||
&body_radiometry(),
|
&body_radiometry(),
|
||||||
);
|
);
|
||||||
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
||||||
pass.encode(ctx, &mut encoder, &targets, &uniforms);
|
pass.encode(ctx, &mut encoder, &targets, &uniforms, None);
|
||||||
ctx.queue.submit([encoder.finish()]);
|
ctx.queue.submit([encoder.finish()]);
|
||||||
let radiance = targets.read_radiance(ctx);
|
let radiance = targets.read_radiance(ctx);
|
||||||
common::save(name, &ctx.info, &radiance);
|
common::save(name, &ctx.info, &radiance);
|
||||||
|
|||||||
@@ -15,6 +15,16 @@ use resolved::radiometry::{Radiometry, AU};
|
|||||||
use resolved::CameraConfig;
|
use resolved::CameraConfig;
|
||||||
use wgpu::AdapterInfo;
|
use wgpu::AdapterInfo;
|
||||||
|
|
||||||
|
/// Held by any test that creates or drops a renderer, so only one does at a time.
|
||||||
|
///
|
||||||
|
/// Creating and dropping Vulkan devices on several threads at once crashes or hangs
|
||||||
|
/// the drivers on mainframe (NVIDIA 615.71, lavapipe): measured 4 crashes and 2 hangs
|
||||||
|
/// in 150 runs of the renderer tests in parallel, none in 100 run one at a time.
|
||||||
|
pub fn device_lock() -> std::sync::MutexGuard<'static, ()> {
|
||||||
|
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||||
|
LOCK.lock().unwrap_or_else(|e| e.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
/// A hardware GPU and lavapipe, opened once per test binary and shared by its tests.
|
/// A hardware GPU and lavapipe, opened once per test binary and shared by its tests.
|
||||||
pub fn contexts() -> &'static [GpuContext; 2] {
|
pub fn contexts() -> &'static [GpuContext; 2] {
|
||||||
static CONTEXTS: OnceLock<[GpuContext; 2]> = OnceLock::new();
|
static CONTEXTS: OnceLock<[GpuContext; 2]> = OnceLock::new();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
//! Needs a Vulkan GPU and Mesa's lavapipe (`vulkan-swrast` on Arch,
|
//! Needs a Vulkan GPU and Mesa's lavapipe (`vulkan-swrast` on Arch,
|
||||||
//! `mesa-vulkan-drivers` on Debian and Ubuntu).
|
//! `mesa-vulkan-drivers` on Debian and Ubuntu).
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
use resolved::gpu::{AdapterChoice, GpuContext};
|
use resolved::gpu::{AdapterChoice, GpuContext};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ fn double_plus_one(ctx: &GpuContext, input: &[f32]) -> Vec<f32> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check(choice: AdapterChoice, expect_cpu: bool) {
|
fn check(choice: AdapterChoice, expect_cpu: bool) {
|
||||||
|
let _devices = common::device_lock();
|
||||||
let ctx = GpuContext::new(&choice).unwrap();
|
let ctx = GpuContext::new(&choice).unwrap();
|
||||||
assert_eq!(ctx.is_cpu(), expect_cpu, "{choice:?} picked {:?}", ctx.info);
|
assert_eq!(ctx.is_cpu(), expect_cpu, "{choice:?} picked {:?}", ctx.info);
|
||||||
let input: Vec<f32> = (0..1000).map(|i| i as f32 * 0.5).collect();
|
let input: Vec<f32> = (0..1000).map(|i| i as f32 * 0.5).collect();
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ fn render(ctx: &GpuContext, name: &str, scene: &Scene, camera: &Pose, mesh: &Mes
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
||||||
bodies.encode(ctx, &mut encoder, &targets, &uniforms);
|
bodies.encode(ctx, &mut encoder, &targets, &uniforms, None);
|
||||||
meshes.encode(ctx, &mut encoder, &targets, &draws);
|
meshes.encode(ctx, &mut encoder, &targets, &draws, None);
|
||||||
ctx.queue.submit([encoder.finish()]);
|
ctx.queue.submit([encoder.finish()]);
|
||||||
let radiance = targets.read_radiance(ctx);
|
let radiance = targets.read_radiance(ctx);
|
||||||
common::save(name, &ctx.info, &radiance);
|
common::save(name, &ctx.info, &radiance);
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ fn cube_target(model: ModelId) -> Target {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sunlit_cube_matches_the_cpu_chain() {
|
fn sunlit_cube_matches_the_cpu_chain() {
|
||||||
|
let _devices = common::device_lock();
|
||||||
let config = CameraConfig::example();
|
let config = CameraConfig::example();
|
||||||
let r = Radiometry::new(&config);
|
let r = Radiometry::new(&config);
|
||||||
let ray = config.intrinsics().ray(&Vector2::new(960.5, 540.5));
|
let ray = config.intrinsics().ray(&Vector2::new(960.5, 540.5));
|
||||||
@@ -108,6 +109,7 @@ fn sunlit_cube_matches_the_cpu_chain() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn camera_mount_is_rigid() {
|
fn camera_mount_is_rigid() {
|
||||||
|
let _devices = common::device_lock();
|
||||||
// Mounting the camera rotated on the observer, and turning the observer the other
|
// Mounting the camera rotated on the observer, and turning the observer the other
|
||||||
// way, must give the same image as no mount at all.
|
// way, must give the same image as no mount at all.
|
||||||
let turn = UnitQuaternion::from_euler_angles(0.3, -0.2, 1.1);
|
let turn = UnitQuaternion::from_euler_angles(0.3, -0.2, 1.1);
|
||||||
@@ -155,6 +157,7 @@ fn camera_mount_is_rigid() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn frames_are_independent() {
|
fn frames_are_independent() {
|
||||||
|
let _devices = common::device_lock();
|
||||||
for mut renderer in renderers(false) {
|
for mut renderer in renderers(false) {
|
||||||
let model = renderer.load_model(&cube()).unwrap();
|
let model = renderer.load_model(&cube()).unwrap();
|
||||||
let with = scene(vec![cube_target(model)]);
|
let with = scene(vec![cube_target(model)]);
|
||||||
@@ -185,8 +188,44 @@ fn frames_are_independent() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_model_is_an_error() {
|
fn unknown_model_is_an_error() {
|
||||||
|
let _devices = common::device_lock();
|
||||||
let mut renderer = Renderer::new(CameraConfig::example(), Default::default()).unwrap();
|
let mut renderer = Renderer::new(CameraConfig::example(), Default::default()).unwrap();
|
||||||
assert!(renderer
|
assert!(renderer
|
||||||
.submit(&scene(vec![cube_target(ModelId(0))]), &observer())
|
.submit(&scene(vec![cube_target(ModelId(0))]), &observer())
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn profiling_reports_pass_times() {
|
||||||
|
let _devices = common::device_lock();
|
||||||
|
for profile in [false, true] {
|
||||||
|
for adapter in [AdapterChoice::Gpu, AdapterChoice::Cpu] {
|
||||||
|
let options = RendererOptions {
|
||||||
|
adapter,
|
||||||
|
profile,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut renderer = Renderer::new(CameraConfig::example(), options).unwrap();
|
||||||
|
let model = renderer.load_model(&cube()).unwrap();
|
||||||
|
let frame = renderer
|
||||||
|
.submit(&scene(vec![cube_target(model)]), &observer())
|
||||||
|
.unwrap()
|
||||||
|
.wait()
|
||||||
|
.unwrap();
|
||||||
|
let name = &renderer.adapter_info().name;
|
||||||
|
match (profile, frame.timings) {
|
||||||
|
(false, timings) => assert!(timings.is_none(), "{name}"),
|
||||||
|
(true, None) => {
|
||||||
|
panic!("{name}: no timings; both mainframe adapters support timestamps")
|
||||||
|
}
|
||||||
|
(true, Some(t)) => {
|
||||||
|
for pass in [t.bodies, t.mesh, t.sensor] {
|
||||||
|
assert!(pass > 0.0 && pass <= t.total, "{name}: {t:?}");
|
||||||
|
}
|
||||||
|
// A frame takes under a second even on lavapipe.
|
||||||
|
assert!(t.total < 1000.0, "{name}: {t:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ fn sense(ctx: &GpuContext, name: &str, radiance: &[[f32; 4]]) -> Vec<u16> {
|
|||||||
let pass = SensorPass::new(ctx);
|
let pass = SensorPass::new(ctx);
|
||||||
let uniforms = SensorUniforms::new(&config, &Radiometry::new(&config));
|
let uniforms = SensorUniforms::new(&config, &Radiometry::new(&config));
|
||||||
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
||||||
pass.encode(ctx, &mut encoder, &targets, &uniforms);
|
pass.encode(ctx, &mut encoder, &targets, &uniforms, None);
|
||||||
ctx.queue.submit([encoder.finish()]);
|
ctx.queue.submit([encoder.finish()]);
|
||||||
let dn = targets.read_dn(ctx);
|
let dn = targets.read_dn(ctx);
|
||||||
common::save_dn(name, &ctx.info, &dn);
|
common::save_dn(name, &ctx.info, &dn);
|
||||||
@@ -160,8 +160,8 @@ fn sunlit_earth_through_the_whole_chain() {
|
|||||||
for ctx in common::contexts() {
|
for ctx in common::contexts() {
|
||||||
let targets = FrameTargets::new(ctx, config.width, config.height, 3);
|
let targets = FrameTargets::new(ctx, config.width, config.height, 3);
|
||||||
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
let mut encoder = ctx.device.create_command_encoder(&Default::default());
|
||||||
BodiesPass::new(ctx).encode(ctx, &mut encoder, &targets, &bodies);
|
BodiesPass::new(ctx).encode(ctx, &mut encoder, &targets, &bodies, None);
|
||||||
SensorPass::new(ctx).encode(ctx, &mut encoder, &targets, &sensor);
|
SensorPass::new(ctx).encode(ctx, &mut encoder, &targets, &sensor, None);
|
||||||
ctx.queue.submit([encoder.finish()]);
|
ctx.queue.submit([encoder.finish()]);
|
||||||
let dn = targets.read_dn(ctx);
|
let dn = targets.read_dn(ctx);
|
||||||
common::save_dn("sensor-earth", &ctx.info, &dn);
|
common::save_dn("sensor-earth", &ctx.info, &dn);
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -18,6 +18,30 @@ Rendering goes through wgpu. On a machine without a GPU, the same shaders run
|
|||||||
on the CPU through Mesa's lavapipe (`vulkan-swrast` on Arch,
|
on the CPU through Mesa's lavapipe (`vulkan-swrast` on Arch,
|
||||||
`mesa-vulkan-drivers` on Debian and Ubuntu).
|
`mesa-vulkan-drivers` on Debian and Ubuntu).
|
||||||
|
|
||||||
|
## Performance and fidelity
|
||||||
|
|
||||||
|
Lighting and camera fidelity are chosen separately: lighting tiers L0 to L3,
|
||||||
|
camera tiers C0 to C3. A row is added here as each tier lands, with its
|
||||||
|
measured cost and a sample image from the same scene, so the cost of each step
|
||||||
|
up is visible at a glance.
|
||||||
|
|
||||||
|
| Tier | Lighting | Camera | RTX 3060 | lavapipe (CPU) | Sample |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| L0 / C0 | Lambertian surfaces, Earth's and the Moon's limbs exact, eclipse by Earth and the Moon, no shadows on the target, no light from Earth | Pinhole, absolute radiometry to 12-bit DN, no noise or blur | 1.8–2.1 ms | 8–65 ms | [](docs/images/l0-c0.png) |
|
||||||
|
|
||||||
|
Times are one 1920 x 1080 RGB frame end to end (`submit` to `wait`, readback
|
||||||
|
of the DN image included), median of criterion runs, over models from 12 to
|
||||||
|
752,000 triangles: the box-wing, Landsat 8 and ICESat-2. The GPU's own work is
|
||||||
|
0.3 to 0.6 ms of that; reading 12 MB back and copying it out is most of the
|
||||||
|
rest. lavapipe ran on the Intel Core Ultra 9 285K's 24 cores. The run-to-run
|
||||||
|
noise is about 3 %. The sample is the Landsat 8 example scene
|
||||||
|
(`examples/scenes/landsat8.toml`). `cargo bench -p resolved` reproduces the
|
||||||
|
numbers, and `resolved render --timings N` shows the per-pass split.
|
||||||
|
|
||||||
|
Planned tiers: L1 adds self-shadowing, Earthshine and albedo maps (M2); C1
|
||||||
|
and C2 add lens distortion, blur and detector noise (M3); L2 adds measured
|
||||||
|
materials and the atmosphere (M4); L3 is path traced (M5).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -149,6 +173,17 @@ them needs a Vulkan driver for the GPU and lavapipe installed. Set
|
|||||||
`WGPU_BACKEND`, `WGPU_ADAPTER_NAME` and the other standard wgpu environment
|
`WGPU_BACKEND`, `WGPU_ADAPTER_NAME` and the other standard wgpu environment
|
||||||
variables to steer adapter discovery.
|
variables to steer adapter discovery.
|
||||||
|
|
||||||
|
Benchmarks are end-to-end frame times (`submit` then `wait`, readback
|
||||||
|
included) on the GPU and on lavapipe, at two resolutions, for the box-wing and,
|
||||||
|
once fetched, Landsat 8 and ICESat-2:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo bench -p resolved
|
||||||
|
```
|
||||||
|
|
||||||
|
For where a frame's time goes, `resolved render --timings N` adds GPU
|
||||||
|
timestamps around each pass (`RendererOptions::profile` in the library).
|
||||||
|
|
||||||
To see what the GPU tests render, set `RESOLVED_TEST_IMAGES=1`. Each test then
|
To see what the GPU tests render, set `RESOLVED_TEST_IMAGES=1`. Each test then
|
||||||
writes a PNG per device to `target/tmp/test-images/`, scaled so a white
|
writes a PNG per device to `target/tmp/test-images/`, scaled so a white
|
||||||
surface facing the Sun is full white:
|
surface facing the Sun is full white:
|
||||||
|
|||||||
Reference in new issue
Block a user