diff --git a/Cargo.lock b/Cargo.lock index dc8f811c..6b0ba22a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" +checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -68,7 +68,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys 0.6.0+11769913", + "ndk-sys 0.6.0+12185904", "num_enum", "thiserror", ] @@ -116,9 +116,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", @@ -131,43 +131,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "arrayref" @@ -220,7 +220,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" dependencies = [ - "bit-vec", + "bit-vec 0.7.0", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -229,6 +238,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -264,22 +279,22 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -296,9 +311,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "calloop" @@ -340,9 +355,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.24" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", @@ -375,9 +390,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -385,9 +400,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -401,10 +416,10 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -434,9 +449,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "com" @@ -693,9 +708,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" dependencies = [ "simd-adler32", ] @@ -708,7 +723,7 @@ checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "libredox 0.1.3", + "libredox", "windows-sys 0.59.0", ] @@ -749,7 +764,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -769,9 +784,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-intrusive" @@ -841,6 +856,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glutin_wgl_sys" version = "0.6.0" @@ -879,7 +906,19 @@ dependencies = [ "presser", "thiserror", "winapi", - "windows", + "windows 0.52.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror", + "windows 0.58.0", ] [[package]] @@ -964,15 +1003,6 @@ dependencies = [ "vello", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -1005,9 +1035,9 @@ checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] name = "image" -version = "0.25.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" dependencies = [ "bytemuck", "byteorder-lite", @@ -1103,9 +1133,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -1171,9 +1201,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libloading" @@ -1185,17 +1215,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "libredox" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" -dependencies = [ - "bitflags 2.6.0", - "libc", - "redox_syscall 0.4.1", -] - [[package]] name = "libredox" version = "0.1.3" @@ -1296,6 +1315,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "naga" +version = "22.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9#aeac0f29fed2ae4240869c8ff419ea93523d32d9" +dependencies = [ + "arrayvec", + "bit-set 0.8.0", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap 2.6.0", + "log", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + [[package]] name = "naga" version = "22.1.0" @@ -1303,7 +1342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.6.0", "bitflags 2.6.0", "cfg_aliases 0.1.1", "codespan-reporting", @@ -1320,13 +1359,12 @@ dependencies = [ [[package]] name = "ndk" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +source = "git+https://github.com/rust-mobile/ndk/?rev=d6eaac2df982f091dde061f8c3e0f36cb495e788#d6eaac2df982f091dde061f8c3e0f36cb495e788" dependencies = [ "bitflags 2.6.0", "jni-sys", "log", - "ndk-sys 0.6.0+11769913", + "ndk-sys 0.6.0+12185904", "num_enum", "raw-window-handle", "thiserror", @@ -1349,13 +1387,24 @@ dependencies = [ [[package]] name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +version = "0.6.0+12185904" +source = "git+https://github.com/rust-mobile/ndk/?rev=d6eaac2df982f091dde061f8c3e0f36cb495e788#d6eaac2df982f091dde061f8c3e0f36cb495e788" dependencies = [ "jni-sys", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + [[package]] name = "notify" version = "6.1.1" @@ -1413,7 +1462,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -1648,27 +1697,24 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "orbclient" -version = "0.3.47" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" dependencies = [ - "libredox 0.0.2", + "libredox", ] [[package]] name = "owned_ttf_parser" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" dependencies = [ "ttf-parser", ] @@ -1726,29 +1772,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pkg-config" @@ -1790,12 +1836,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1822,18 +1862,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", "tracing", @@ -1841,12 +1881,12 @@ dependencies = [ [[package]] name = "profiling-procmacros" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -1911,9 +1951,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "read-fonts" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb94d9ac780fdcf9b6b252253f7d8f221379b84bd3573131139b383df69f85e1" +checksum = "4a04b892cb6f91951f144c33321843790c8574c825aafdb16d815fd7183b5229" dependencies = [ "bytemuck", "font-types", @@ -1939,9 +1979,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1999,9 +2039,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -2097,29 +2137,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -2287,9 +2327,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", @@ -2320,22 +2360,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -2410,7 +2450,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] @@ -2451,9 +2491,9 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" +checksum = "5902c5d130972a0000f60860bfbf46f7ca3db5391eddfedd1b8728bd9dc96c0e" [[package]] name = "unicode-ident" @@ -2506,7 +2546,7 @@ dependencies = [ "thiserror", "vello_encoding", "vello_shaders", - "wgpu", + "wgpu 22.0.0", "wgpu-profiler", ] @@ -2521,12 +2561,24 @@ dependencies = [ "smallvec", ] +[[package]] +name = "vello_pacing" +version = "0.3.0" +dependencies = [ + "ash", + "ndk", + "nix", + "tracing", + "vello", + "wgpu 22.0.0", +] + [[package]] name = "vello_shaders" version = "0.3.0" dependencies = [ "bytemuck", - "naga", + "naga 22.1.0", "thiserror", "vello_encoding", ] @@ -2569,9 +2621,9 @@ dependencies = [ [[package]] name = "walrus" -version = "0.21.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501ace8ec3492754a9b3c4b59eac7159ceff8414f9e43a05029fe8ef43b9218f" +checksum = "d68aa3c7b80be75c8458fc087453e5a31a226cfffede2e9b932393b2ea1c624a" dependencies = [ "anyhow", "gimli", @@ -2585,14 +2637,14 @@ dependencies = [ [[package]] name = "walrus-macro" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7" +checksum = "439ad39ff894c43c9649fa724cdde9a6fc50b855d517ef071a93e5df82fe51d3" dependencies = [ - "heck 0.3.3", + "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.85", ] [[package]] @@ -2603,9 +2655,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -2614,29 +2666,30 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-cli-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7f49ca6e7da74d53d6d716b868828a47d1c3808a8f676e2104fadd3b9874bb" +checksum = "d6b21cf9e89b196d78ff95c442a8076cc417171001e75dc062fe2c421cd2a0f9" dependencies = [ "anyhow", "base64", "log", "rustc-demangle", + "serde", "serde_json", "tempfile", "unicode-ident", @@ -2651,9 +2704,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-externref-xform" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1f37f2705f4177cc87e5b2763115d078d39e4843e351438b34b085d53a8930" +checksum = "c9f5eaefdb356e4266ca53d76526f5500e1e3b0961da8ee932193f08ada25ec7" dependencies = [ "anyhow", "walrus", @@ -2662,9 +2715,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -2674,9 +2727,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2684,22 +2737,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-multi-value-xform" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2864e3f221fef3869992b541b238e55f53f99b43c4ce220422a6e1ec9458dc21" +checksum = "c22689c6bbdc7f3a9110f1aa21873398a7ab2c50474ba9a45595c9ffde49c0cd" dependencies = [ "anyhow", "walrus", @@ -2708,15 +2761,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-bindgen-threads-xform" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9ca60ee029d64cf6f63a630050935360c3877844f6de38e8287afb8f1d2715" +checksum = "17d3c6389a7c8fa49ee4f55847d01b74c640a60387598952cdf3211b8499520c" dependencies = [ "anyhow", "walrus", @@ -2725,9 +2778,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-wasm-conventions" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e429e00149de60ffc768e6e1f0563778a237e7c11cc01801edf9734bff8161f" +checksum = "f7b4d6bf2704173b57d7f319316593a35f2102e18bc92251d96909f89e7d4595" dependencies = [ "anyhow", "leb128", @@ -2738,9 +2791,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-wasm-interpreter" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771c49db324f195f221796bf3868247dd91cca4a85604dcb3729213e439abe59" +checksum = "b123244c6bf9e7abbb6ad9aa98ed86f927b55ee9d95b5552dd9346910341d5e2" dependencies = [ "anyhow", "log", @@ -2787,9 +2840,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", "rustix", @@ -2810,9 +2863,9 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.6" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" +checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" dependencies = [ "rustix", "wayland-client", @@ -2821,9 +2874,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.4" +version = "0.32.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -2833,9 +2886,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0a41a6875e585172495f7a96dfa42ca7e0213868f4f15c313f7c33221a7eff" +checksum = "9b31cab548ee68c7eb155517f2212049dc151f7cd7910c2b66abfd31c3ee12bd" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -2846,9 +2899,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -2882,9 +2935,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2900,6 +2953,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "wgpu" +version = "22.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9#aeac0f29fed2ae4240869c8ff419ea93523d32d9" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga 22.0.0", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 22.0.0", + "wgpu-hal 22.0.0 (git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9)", + "wgpu-types 22.0.0 (git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9)", +] + [[package]] name = "wgpu" version = "22.1.0" @@ -2911,7 +2988,7 @@ dependencies = [ "document-features", "js-sys", "log", - "naga", + "naga 22.1.0", "parking_lot", "profiling", "raw-window-handle", @@ -2920,9 +2997,33 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", + "wgpu-core 22.1.0", + "wgpu-hal 22.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wgpu-types 22.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wgpu-core" +version = "22.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9#aeac0f29fed2ae4240869c8ff419ea93523d32d9" +dependencies = [ + "arrayvec", + "bit-vec 0.8.0", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap 2.6.0", + "log", + "naga 22.0.0", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "wgpu-hal 22.0.0 (git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9)", + "wgpu-types 22.0.0 (git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9)", ] [[package]] @@ -2932,13 +3033,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", - "bit-vec", + "bit-vec 0.7.0", "bitflags 2.6.0", "cfg_aliases 0.1.1", "document-features", "indexmap 2.6.0", "log", - "naga", + "naga 22.1.0", "once_cell", "parking_lot", "profiling", @@ -2946,8 +3047,8 @@ dependencies = [ "rustc-hash", "smallvec", "thiserror", - "wgpu-hal", - "wgpu-types", + "wgpu-hal 22.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wgpu-types 22.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2959,16 +3060,16 @@ dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.6.0", "bitflags 2.6.0", "block", "cfg_aliases 0.1.1", "core-graphics-types", "d3d12", - "glow", + "glow 0.13.1", "glutin_wgl_sys", "gpu-alloc", - "gpu-allocator", + "gpu-allocator 0.26.0", "gpu-descriptor", "hassle-rs", "js-sys", @@ -2977,7 +3078,7 @@ dependencies = [ "libloading", "log", "metal", - "naga", + "naga 22.1.0", "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", @@ -2991,10 +3092,53 @@ dependencies = [ "thiserror", "wasm-bindgen", "web-sys", - "wgpu-types", + "wgpu-types 22.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi", ] +[[package]] +name = "wgpu-hal" +version = "22.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9#aeac0f29fed2ae4240869c8ff419ea93523d32d9" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.8.0", + "bitflags 2.6.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "glow 0.14.2", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator 0.27.0", + "gpu-descriptor", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga 22.0.0", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types 22.0.0 (git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9)", + "windows 0.58.0", + "windows-core 0.58.0", +] + [[package]] name = "wgpu-profiler" version = "0.18.2" @@ -3003,7 +3147,7 @@ checksum = "06b2cee91fdc885ff0d3d714c59810cc72c6d84b81b0eaa48bab8ff2ad54fb5b" dependencies = [ "parking_lot", "thiserror", - "wgpu", + "wgpu 22.1.0", ] [[package]] @@ -3017,6 +3161,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wgpu-types" +version = "22.0.0" +source = "git+https://github.com/gfx-rs/wgpu?rev=aeac0f29fed2ae4240869c8ff419ea93523d32d9#aeac0f29fed2ae4240869c8ff419ea93523d32d9" +dependencies = [ + "bitflags 2.6.0", + "js-sys", + "web-sys", +] + [[package]] name = "widestring" version = "1.1.0" @@ -3060,7 +3214,17 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core", + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -3073,6 +3237,60 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.85", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3354,12 +3572,14 @@ version = "0.0.0" dependencies = [ "android_logger", "anyhow", + "ash", "clap", "console_error_panic_hook", "console_log", "env_logger", "getrandom", "log", + "ndk", "notify-debouncer-mini", "pollster", "profiling", @@ -3457,7 +3677,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.85", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 624d1781..1b374e0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "vello", "vello_encoding", + "vello_pacing", "vello_shaders", "vello_tests", @@ -52,7 +53,7 @@ static_assertions = "1.1.0" thiserror = "1.0.64" # NOTE: Make sure to keep this in sync with the version badge in README.md and vello/README.md -wgpu = { version = "22.1.0" } +wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "aeac0f29fed2ae4240869c8ff419ea93523d32d9" } log = "0.4.22" image = { version = "0.25.2", default-features = false } @@ -63,3 +64,8 @@ pollster = "0.3.0" web-time = "1.1.0" wgpu-profiler = "0.18.2" scenes = { path = "examples/scenes" } + +[patch.crates-io] +# "Choreographer" branch: https://github.com/rust-mobile/ndk/tree/choreographer +ndk = { git = "https://github.com/rust-mobile/ndk/", rev = "d6eaac2df982f091dde061f8c3e0f36cb495e788" } +ndk-sys = { git = "https://github.com/rust-mobile/ndk/", rev = "d6eaac2df982f091dde061f8c3e0f36cb495e788" } diff --git a/examples/with_winit/Cargo.toml b/examples/with_winit/Cargo.toml index 7cdd3181..848c0222 100644 --- a/examples/with_winit/Cargo.toml +++ b/examples/with_winit/Cargo.toml @@ -12,7 +12,7 @@ name = "with_winit" crate-type = ["cdylib", "lib"] [features] -default = ["wgpu-profiler"] +default = [] # Enable the use of wgpu-profiler. This is an optional feature for times when we use a git dependency on # wgpu (which means the dependency used in wgpu-profiler would be incompatible) wgpu-profiler = ["dep:wgpu-profiler", "vello/wgpu-profiler"] @@ -41,6 +41,7 @@ log = { workspace = true } # We're still using env-logger, but we want to use tracing spans to allow using # tracing_android_trace tracing = { version = "0.1.40", features = ["log-always"] } +ash = { version = "0.38.0", default-features = false } [target.'cfg(not(target_os = "android"))'.dependencies] # We use android_logger on Android @@ -62,6 +63,7 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features = "registry", ] } profiling = { version = "1.0.15", features = ["profile-with-tracing"] } +ndk = { version = "0.9", features = ["api-level-33", "nativewindow"] } [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.7" @@ -70,3 +72,10 @@ wasm-bindgen-futures = "0.4.43" web-sys = { version = "0.3.70", features = ["HtmlCollection", "Text"] } web-time = { workspace = true } getrandom = { version = "0.2.15", features = ["js"] } + +[package.metadata.android.application] +debuggable = true + +[package.metadata.android.sdk] +target_sdk_version = 33 +min_sdk_version = 33 diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs index e3771e19..f2762007 100644 --- a/examples/with_winit/src/lib.rs +++ b/examples/with_winit/src/lib.rs @@ -6,6 +6,7 @@ use std::collections::HashSet; use std::num::NonZeroUsize; +use std::rc::Rc; use std::sync::Arc; #[cfg(not(target_arch = "wasm32"))] @@ -16,6 +17,8 @@ use web_time::Instant; use winit::application::ApplicationHandler; use winit::event::*; use winit::keyboard::*; +use winit::raw_window_handle::HasWindowHandle; +use winit::window::WindowId; #[cfg(all(feature = "wgpu-profiler", not(target_arch = "wasm32")))] use std::time::Duration; @@ -107,6 +110,10 @@ const AA_CONFIGS: [AaConfig; 1] = [AaConfig::Area]; struct VelloApp<'s> { context: RenderContext, renderers: Vec>, + + google_display_timing_ext_devices: Vec>, + present_id: u32, + state: Option>, // Whilst suspended, we drop `render_state`, but need to keep the same window. // If render_state exists, we must store the window in it, to maintain drop order @@ -164,6 +171,9 @@ struct VelloApp<'s> { modifiers: ModifiersState, debug: DebugLayers, + choreographer: Option>, + animation_in_flight: bool, + proxy: winit::event_loop::EventLoopProxy, } impl<'s> ApplicationHandler for VelloApp<'s> { @@ -172,6 +182,8 @@ impl<'s> ApplicationHandler for VelloApp<'s> { #[cfg(not(target_arch = "wasm32"))] fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + use vello::wgpu::hal::vulkan; + let Option::None = self.state else { return; }; @@ -194,6 +206,8 @@ impl<'s> ApplicationHandler for VelloApp<'s> { let render_state = RenderState { window, surface }; self.renderers .resize_with(self.context.devices.len(), || None); + self.google_display_timing_ext_devices + .resize_with(self.context.devices.len(), || None); let id = render_state.surface.dev_id; self.renderers[id].get_or_insert_with(|| { let start = Instant::now(); @@ -224,6 +238,29 @@ impl<'s> ApplicationHandler for VelloApp<'s> { .expect("Not setting max_num_pending_frames"); renderer }); + let display_timing_ext_device = &mut self.google_display_timing_ext_devices[id]; + let device_handle = &self.context.devices[id]; + if display_timing_ext_device.is_none() + && device_handle + .device + .features() + .contains(wgpu::Features::VULKAN_GOOGLE_DISPLAY_TIMING) + { + *display_timing_ext_device = unsafe { + device_handle + .device + .as_hal::(|device| { + let device = device?; + let instance = self.context.instance.as_hal::()?; + Some(ash::google::display_timing::Device::new( + instance.shared_instance().raw_instance(), + device.raw_device(), + )) + }) + .flatten() + }; + } + Some(render_state) }; } @@ -370,13 +407,45 @@ impl<'s> ApplicationHandler for VelloApp<'s> { // in a touch context (i.e. Windows/Linux/MacOS with a touch screen could // also be using mouse/keyboard controls) // Note that winit's rendering is y-down - if let Some(RenderState { surface, .. }) = &self.state { + if let Some(RenderState { surface, window }) = &self.state { if touch.location.y > surface.config.height as f64 * 2. / 3. { self.navigation_fingers.insert(touch.id); // The left third of the navigation zone navigates backwards if touch.location.x < surface.config.width as f64 / 3. { + if let wgpu::rwh::RawWindowHandle::AndroidNdk( + android_ndk_window_handle, + ) = window.window_handle().unwrap().as_raw() + { + let window = unsafe { + ndk::native_window::NativeWindow::clone_from_ptr( + android_ndk_window_handle.a_native_window.cast(), + ) + }; + window + .set_frame_rate( + 60., + ndk::native_window::FrameRateCompatibility::Default, + ) + .unwrap(); + } self.scene_ix = self.scene_ix.saturating_sub(1); } else if touch.location.x > 2. * surface.config.width as f64 / 3. { + if let wgpu::rwh::RawWindowHandle::AndroidNdk( + android_ndk_window_handle, + ) = window.window_handle().unwrap().as_raw() + { + let window = unsafe { + ndk::native_window::NativeWindow::clone_from_ptr( + android_ndk_window_handle.a_native_window.cast(), + ) + }; + window + .set_frame_rate( + 90., + ndk::native_window::FrameRateCompatibility::Default, + ) + .unwrap(); + } self.scene_ix = self.scene_ix.saturating_add(1); } } @@ -438,12 +507,13 @@ impl<'s> ApplicationHandler for VelloApp<'s> { self.prior_position = Some(position); } WindowEvent::RedrawRequested => { + if self.animation_in_flight { + return; + } let _rendering_span = tracing::trace_span!("Actioning Requested Redraw").entered(); let encoding_span = tracing::trace_span!("Encoding scene").entered(); - render_state.window.request_redraw(); - - let Some(RenderState { surface, window }) = &self.state else { + let Some(RenderState { surface, window }) = &mut self.state else { return; }; let width = surface.config.width; @@ -541,6 +611,44 @@ impl<'s> ApplicationHandler for VelloApp<'s> { .get_current_texture() .expect("failed to get surface texture"); + let present_id = self.present_id; + self.present_id = self.present_id.wrapping_add(1); + if device_handle + .device + .features() + .contains(wgpu::Features::VULKAN_GOOGLE_DISPLAY_TIMING) + { + unsafe { + let swc = surface + .surface + .as_hal::(|surface| { + if let Some(surface) = surface { + surface.set_next_present_time(ash::vk::PresentTimeGOOGLE { + desired_present_time: 0, + present_id, + }); + Some(surface.raw_swapchain()) + } else { + None + } + }) + .flatten() + .flatten(); + if let Some(swc) = swc { + let display_timing = self.google_display_timing_ext_devices + [surface.dev_id] + .as_ref() + .unwrap(); + // let result = display_timing.get_refresh_cycle_duration(swc); + // eprintln!("Refresh duration: {result:?}"); + if present_id % 5 == 0 { + // let result = display_timing.get_past_presentation_timing(swc); + // eprintln!("Display timings: {result:?}"); + // eprintln!("Most recent present id: {}", present_id); + } + } + } + } drop(texture_span); let render_span = tracing::trace_span!("Dispatching render").entered(); // Note: we don't run the async/"robust" pipeline, as @@ -589,6 +697,33 @@ impl<'s> ApplicationHandler for VelloApp<'s> { frame_time_us: (new_time - self.frame_start_time).as_micros() as u64, }); self.frame_start_time = new_time; + + if let Some(choreographer) = self.choreographer.as_ref() { + let proxy = self.proxy.clone(); + // choreographer.post_vsync_callback(Box::new(move |frame| { + // eprintln!("New frame"); + // let frame_time = frame.frame_time(); + // let preferred_index = frame.preferred_frame_timeline_index(); + // for timeline in 0..(frame.frame_timelines_length().min(3)) { + // eprintln!( + // "{:?} {}", + // frame.frame_timeline_deadline(timeline) - frame_time, + // if timeline == preferred_index { + // "(Preferred)" + // } else { + // "" + // } + // ); + // } + // eprintln!("{frame:?}"); + // // proxy + // // .send_event(UserEvent::ChoreographerFrame(window_id)) + // // .unwrap(); + // })); + window.request_redraw(); + } else { + window.request_redraw(); + } } _ => {} } @@ -607,12 +742,14 @@ impl<'s> ApplicationHandler for VelloApp<'s> { * self.transform; } - if let Some(render_state) = &mut self.state { - render_state.window.request_redraw(); - } + // if let Some(render_state) = &mut self.state { + // if !self.animation_in_flight { + // render_state.window.request_redraw(); + // } + // } } - fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) { + fn user_event(&mut self, event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) { match event { #[cfg(not(any(target_arch = "wasm32", target_os = "android")))] UserEvent::HotReload => { @@ -632,6 +769,10 @@ impl<'s> ApplicationHandler for VelloApp<'s> { Err(e) => log::error!("Failed to reload shaders: {e}"), } } + UserEvent::ChoreographerFrame(window_id) => { + self.animation_in_flight = false; + self.window_event(event_loop, window_id, WindowEvent::RedrawRequested); + } } } @@ -702,6 +843,8 @@ fn run( let debug = DebugLayers::none(); let mut app = VelloApp { + present_id: 0, + google_display_timing_ext_devices: vec![None; renderers.len()], context: render_cx, renderers, state: render_state, @@ -746,7 +889,34 @@ fn run( prev_scene_ix: 0, modifiers: ModifiersState::default(), debug, + // We know looper is active since we have the `EventLoop` + choreographer: ndk::choreographer::Choreographer::instance().map(Rc::new), + proxy: event_loop.create_proxy(), + animation_in_flight: false, }; + if let Some(choreographer) = app.choreographer.as_ref() { + fn post_callback(choreographer: &Rc) { + let new_choreographer = Rc::clone(choreographer); + choreographer.post_vsync_callback(Box::new(move |frame| { + eprintln!("New frame"); + // The vsync point + let frame_time = frame.frame_time(); + for timeline in frame.frame_timelines().take(3) { + eprintln!( + "{:?} to present {:?} later", + timeline.deadline() - frame_time, + timeline.expected_presentation_time() - timeline.deadline() + ); + } + post_callback(&new_choreographer); + })); + } + // post_callback(choreographer); + choreographer.register_refresh_rate_callback(Box::new(|value| { + let span = tracing::info_span!("Getting a new refresh rate", ?value).entered(); + eprintln!("New refresh rate Testing: {value:?}; {}", value.as_nanos()); + })); + } event_loop.run_app(&mut app).expect("run to completion"); } @@ -784,6 +954,7 @@ fn window_attributes() -> WindowAttributes { enum UserEvent { #[cfg(not(any(target_arch = "wasm32", target_os = "android")))] HotReload, + ChoreographerFrame(WindowId), } #[cfg(target_arch = "wasm32")] diff --git a/vello/src/util.rs b/vello/src/util.rs index 5a508dbb..58d39f63 100644 --- a/vello/src/util.rs +++ b/vello/src/util.rs @@ -138,7 +138,8 @@ impl RenderContext { let features = adapter.features(); let limits = Limits::default(); #[allow(unused_mut)] - let mut maybe_features = wgpu::Features::CLEAR_TEXTURE; + let mut maybe_features = + wgpu::Features::CLEAR_TEXTURE | wgpu::Features::VULKAN_GOOGLE_DISPLAY_TIMING; #[cfg(feature = "wgpu-profiler")] { maybe_features |= wgpu_profiler::GpuProfiler::ALL_WGPU_TIMER_FEATURES; diff --git a/vello/src/wgpu_engine.rs b/vello/src/wgpu_engine.rs index 549bfa58..c7b04a5d 100644 --- a/vello/src/wgpu_engine.rs +++ b/vello/src/wgpu_engine.rs @@ -331,7 +331,7 @@ impl WgpuEngine { layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module, - entry_point: vertex_main, + entry_point: Some(vertex_main), buffers: vertex_buffer .as_ref() .map(core::slice::from_ref) @@ -340,7 +340,7 @@ impl WgpuEngine { }, fragment: Some(wgpu::FragmentState { module, - entry_point: fragment_main, + entry_point: Some(fragment_main), targets: &[Some(color_attachment)], compilation_options: PipelineCompilationOptions::default(), }), @@ -827,7 +827,7 @@ impl WgpuEngine { label: Some(label), layout: Some(&compute_pipeline_layout), module: &shader_module, - entry_point: "main", + entry_point: None, compilation_options: PipelineCompilationOptions { zero_initialize_workgroup_memory: false, ..Default::default() diff --git a/vello_pacing/Cargo.toml b/vello_pacing/Cargo.toml new file mode 100644 index 00000000..076ea144 --- /dev/null +++ b/vello_pacing/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "vello_pacing" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +wgpu.workspace = true +vello.workspace = true +ash = { version = "0.38.0", default-features = false } +tracing = "0.1.40" +ndk = { version = "0.9", features = ["api-level-33"] } +nix = { default-features = false, features = ["time"], version = "0.29.0" } + +[lints] +workspace = true diff --git a/vello_pacing/README.md b/vello_pacing/README.md new file mode 100644 index 00000000..70809e49 --- /dev/null +++ b/vello_pacing/README.md @@ -0,0 +1,9 @@ +# Vello Pacing + +A frame pacing library for wgpu, optimised for 2d graphics use cases. + +In particular, special care is taken to handle animation use cases correctly. + +## Backends + +This library currently only targets Android, but has working fallbacks for other platforms. diff --git a/vello_pacing/src/choreographed.rs b/vello_pacing/src/choreographed.rs new file mode 100644 index 00000000..9ab8f27b --- /dev/null +++ b/vello_pacing/src/choreographed.rs @@ -0,0 +1,340 @@ +#![allow(unused)] +#![warn(unused_variables)] + +use std::{ + cell::RefCell, + mem::ManuallyDrop, + ops::Mul, + os::fd::{AsFd, AsRawFd}, + rc::{self, Rc}, + sync::mpsc::{self, Receiver}, + time::Duration, +}; + +use ndk::{ + choreographer::{self, Choreographer}, + looper::{self, FdEvent, ForeignLooper}, +}; +use nix::{ + sys::timerfd::{self, TimerFd}, + time::ClockId, +}; +use vello::Scene; + +/// A slightly tweaked version of the thinking, now that we have an understanding of `AChoreographer`. +/// +/// Observations: +/// 1) A frame drop can happen because CPU or GPU work overruns. +/// 2) GPU work starts ~a fixed time after the end of the [`wgpu::Queue::submit`] call. +/// 3) We can predict a frame drop due to CPU time problems due to this case. +/// 4) Just dropping a frame is better than stuttering when we do so. +/// +/// We need to think about three cases: +/// +/// 1) The CPU-side work to prepare a scene is longer than one vsync. We ignore this case due to parallel command construction. +/// 2) The GPU-side work to prepare a frame is longer than one vsync. +/// 3) The total GPU+CPU side work is longer than one vsync (but individually each is shorter) +/// 4) Neither is the case. +/// +/// For the first draft of this feature, we focus only on the third of these. +/// I.e. both the CPU and GPU side work of a frame are relatively cheap. +/// N.B. we do `Scene` preparation on the main thread, but it is included in +/// this CPU-side work. +/// +/// The rendering loop goes as follows: +/// +/// 1) We submit frame A. +/// 2) We start the CPU side work for frame B at the estimated start time. +/// 3) We race wait until the *deadline* for frame A with the CPU side work for frame B. +/// 4) If the deadline happened first (normal case, GPU work less than 1 frame), we compare +/// the timestamp of the end of the blit pass with the deadline. +/// +/// For the moment, we ignore the possibility of a frame failing. +/// That doesn't actually change any behaviour here, because we render with [`Mailbox`](wgpu::PresentMode::Mailbox). +pub struct Thinking; + +enum PacingCommand {} + +/// The controller for a frame pacing thread. +pub struct PacingChannel { + waker: ForeignLooper, + channel: ManuallyDrop>, +} + +impl PacingChannel { + fn send_command(&self, command: PacingCommand) { + self.channel.send(command); + // We add to the channel before waking, so that the event will be received by the right wake. + self.waker.wake(); + } +} + +impl Drop for PacingChannel { + fn drop(&mut self) { + // Safety: We don't use `self.channel` after this line. + // We drop the value before performing the wake, so that we instantly know that the drop has happened. + unsafe { ManuallyDrop::drop(&mut self.channel) }; + self.waker.wake(); + } +} + +// We generally want to be thinking about two frames at a time, except for some statistical modelling. + +/// A timestamp in `CLOCK_MONOTONIC` +/// +/// For simplicity, all timestamps are treated as happening in the `CLOCK_MONOTONIC` timebase. +/// We'll validate that GPU performance counter timestamps meet this expectation as it becomes relevant. +/// +/// This might not actually be true - the timebase of the return values from [`choreographer::ChoreographerFrameCallbackData`] +/// aren't documented by anything to be `CLOCK_MONOTONIC`, and I suspect we'll need to use [`ash::khr::calibrated_timestamps`] to get +/// the proper results. +struct Timestamp(i64); + +impl Mul for Timestamp { + type Output = Self; + + fn mul(self, rhs: i64) -> Self::Output { + Self(self.0 * rhs) + } +} + +impl Timestamp { + const fn from_nanos(nanos: i64) -> Self { + Self(nanos) + } + + const fn from_micros(micros: i64) -> Self { + Self::from_nanos(micros * 1_000) + } + + const fn from_millis(millis: i64) -> Self { + Self::from_nanos(millis * 1_000_000) + } + + /// Get the current time in `CLOCK_MONOTONIC`. + fn now() -> Self { + let spec = nix::time::clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap(); + Self(spec.tv_sec() * 1_000_000_000 + spec.tv_nsec()) + } +} + +/// A margin of latency which we *always* render against for safety. +const DEADLINE_MARGIN: Timestamp = Timestamp::from_millis(3); + +/// A margin of latency before the deadline, which if we aren't before, we assume that the +/// frame probably missed the deadline. +/// +/// In those cases, we bring future frames forward to try and avoid a cascading stutter +/// (and instead maintain only a dropped frame). +/// +/// Note that this frame might still have technically counted as hitting the deadline. +/// However, we think a prolonged timing mismatch is worse than one dropped frame. +const DEADLINE_ASSUME_FAILED: Timestamp = Timestamp::from_micros(500); + +/// The time within which we expect our estimations to be correct. +const EXPECTED_CONSISTENCY: Timestamp = Timestamp::from_millis(3); + +struct OngoingFrame { + /// The [present time][choreographer::FrameTimeline::expected_presentation_time] we + /// expect this frame to be displayed at. + target_present_time: Timestamp, + /// The [vsync id][choreographer::FrameTimeline::vsync_id] we're aiming for. + target_vsync_id: i64, + /// The [deadline][choreographer::FrameTimeline::deadline] which this frame needs to meet to be rendered at `target_present_time`. + /// + /// We aim for a time [`DEADLINE_MARGIN`] before the deadline. + target_deadline: Timestamp, + + /// The time at which we wanted to start this frame. + /// + /// `cpu_start_time` should try to be `requested_cpu_start_time - EPSILON`, + /// but if this is far off, we know early that we might drop this frame (and so should request + /// the next frame super early). + /// If this is significantly off, then we will likely drop this frame to avoid stuttering. + requested_cpu_start_time: Timestamp, + + /// The time at which `Scene` [rendering](`vello::Renderer::render_to_texture`) began. + /// + /// TODO: Does this include `Scene` construction time? + cpu_start_time: Timestamp, + + /// The time at which [`wgpu::Queue::submit`] finished for this frame. + /// + /// If this is "much" later than + cpu_submit_time: Timestamp, + + /// The time at which work on the GPU started. + gpu_start_time: Timestamp, + /// The time at which work on the GPU finished. + /// + /// This should be before `target_deadline`. + /// `gpu_finish_time` - `cpu_start_time` is used to estimate how long a total frame takes + /// (and `gpu_finished_time` - `cpu_submit_time`) is used to estimate if a submission has + /// missed a deadline. + /// + /// There is some really interesting trickery we can do here; the *next* frame + /// on the GPU can definitely know this value, and can compare it against the deadline. + /// If we know that the submitted frame will miss the deadline, then we can. + gpu_finish_time: Timestamp, +} + +struct VelloPacingController { + choreographer: Choreographer, + command_rx: Receiver, + /// The duration of each frame, as reported by the system. + /// + /// In most cases, this can be inferred from the active frame timelines. + /// However, for a short time after the refresh rate changes, `AChoreographer` is + /// giving us incorrect future vsyncs. + /// In particular, when we get a refresh rate callback, we know that frames after the + /// next vsync will be this duration long. + /// + /// In those cases, we choose to render based on the fastest of the new or old. + /// refresh rates until the updated refresh rate is being used. + /// In some cases, this means that we will get inconsistent latency/apparent frame times; however it avoids + /// dropping frames around a refresh rate window. + refresh_rate: Option, + looper: looper::ThreadLooper, + timer: TimerFd, +} + +/// We need to use a shared pacing controller here because of callback based APIs. +type SharedPacing = Rc>; + +enum GpuCommand { + Render(Scene), + Resize(u32, u32, Scene), +} + +pub fn launch_pacing() -> PacingChannel { + let (channel_tx, channel_rx) = std::sync::mpsc::sync_channel(0); + // TODO: Give thread a name + std::thread::spawn(|| { + let looper = looper::ThreadLooper::prepare(); + let waker = looper.as_foreign().clone(); + let (command_tx, command_rx) = std::sync::mpsc::channel(); + channel_tx.send(PacingChannel { + waker, + channel: ManuallyDrop::new(command_tx), + }); + drop(channel_tx); + let choreographer = Choreographer::instance().expect("We just made the `Looper`"); + + let timer = TimerFd::new( + timerfd::ClockId::CLOCK_MONOTONIC, + timerfd::TimerFlags::TFD_CLOEXEC, + ) + // Something is much more badly wrong if this fails + .expect("Could create a timer file descriptor"); + looper + .as_foreign() + .add_fd(timer.as_fd(), 0, FdEvent::INPUT, std::ptr::null_mut()); + + let state = VelloPacingController { + choreographer, + command_rx, + refresh_rate: None, + looper: looper::ThreadLooper::for_thread().unwrap(), + timer, + }; + let state = Rc::new(RefCell::new(state)); + { + // Since our pointer will not be deallocated/freed unless unregister is called, + // and we can't call unregister, we use a Weak. + // Note that if `state` has been dropped, this thread will be ending, + // so the looper will never be polled again. + let callback_state = Rc::downgrade(&state); + let state = state.borrow(); + state + .choreographer + .register_refresh_rate_callback(Box::new(move |rate| { + if let Some(state) = callback_state.upgrade() { + let mut state = state.borrow_mut(); + state.refresh_rate = Some(rate); + } else { + tracing::error!( + "Kept getting refresh rate callbacks from Android despite thread ending." + ); + } + })); + } + { + let callback_state = Rc::downgrade(&state); + let state = state.borrow(); + fn vsync_callback( + data: &choreographer::ChoreographerFrameCallbackData, + callback_state: rc::Weak>, + ) { + if let Some(state) = callback_state.upgrade() { + let mut state = state.borrow_mut(); + state + .choreographer + .post_vsync_callback(Box::new(move |data| { + vsync_callback(data, callback_state); + })); + } + } + state + .choreographer + .post_vsync_callback(Box::new(move |data| vsync_callback(data, callback_state))); + } + let (gpu_tx, gpu_rx) = std::sync::mpsc::channel::(); + // TODO: Give thread a name + std::thread::spawn(move || { + // We perform all rendering work on this thread + // Since submitting work and polling the GPU are mutually exclusive, + // we do them on the same thread? + loop { + let command = gpu_rx.recv_timeout(Duration::from_millis(5)); + } + }); + + loop { + // TODO: Ideally, we'd have the GPU polling happen through this looper. + let poll = looper.poll_once().expect("'Unrecoverable' error"); + // Outside of the looper polling operation, so no chance of overlap. + let state = state.borrow_mut(); + match poll { + // Fallthrough to checking the command channel + looper::Poll::Wake | looper::Poll::Callback => {} + looper::Poll::Timeout => { + unreachable!("Timeout reached, but we didn't set a timeout") + } + looper::Poll::Event { + ident, + fd, + events, + data: _, + } => { + if fd.as_raw_fd() == state.timer.as_fd().as_raw_fd() { + // TODO: A Hangup might be expected for a timer? + if events.contains(FdEvent::ERROR | FdEvent::INVALID | FdEvent::HANGUP) { + panic!("Got an error from the timer file descriptor {events:?}") + } + assert!(ident == 0); + // Clear out the existing timer value, so that we won't immediately retrigger. + state.timer.wait().unwrap(); + // We should now do whatever we set the timer for. + // Presumably, that is start rendering? + } + } + } + + match state.command_rx.try_recv() { + Ok(command) => { + // Action `command` + match command {} + } + Err(mpsc::TryRecvError::Disconnected) => { + return; + } + Err(mpsc::TryRecvError::Empty) => { + // Continue + } + } + } + }); + channel_rx + .recv_timeout(Duration::from_millis(100)) + .expect("Could not create pacing controller") +} diff --git a/vello_pacing/src/lib.rs b/vello_pacing/src/lib.rs new file mode 100644 index 00000000..3fb675ec --- /dev/null +++ b/vello_pacing/src/lib.rs @@ -0,0 +1,573 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +mod choreographed; + +use std::{ + collections::VecDeque, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::{Receiver, RecvTimeoutError, Sender}, + Arc, + }, + time::{Duration, Instant}, +}; + +use vello::Scene; +use wgpu::{ + hal::vulkan, Buffer, Instance, SubmissionIndex, Surface, SurfaceConfiguration, SurfaceTarget, + SurfaceTexture, TextureFormat, +}; + +/// Docs used to type out how this is being reasoned about. +/// +/// We manage six clocks: +/// 1) The CPU side clock. This is treated as an opaque nanosecond value, used only(?) for sleeping. +/// 2) The GPU "render complete clock". We wait for rendering work to complete to schedule the "present" of the *next* frame, to avoid stuttering. +/// On Android, we calibrate this to the "latching" clock using `presentMargin`. +/// Additionally, if a frame "failed", we choose not to present the next frame. +/// 3) The GPU render start clock, used to get a quick estimate for likely stutters. +/// 4) The GPU "latching" clock. This is only implied by `presentMargin` in relation to the render complete clock. +/// 5) The display "present clock". We assume that this is a potentially variable, but generally fixed amount of +/// time from the "latching" clock (due to compositing time). +/// 6) The CPU side present clock, which is the time we think presentation will happen. +/// These are always a fixed number of nanoseconds away from the "true presentation time" (this difference can be +/// calculated using +/// and the true presentation time). +/// +/// There are three cases for when we want to render: +/// 1) Active rendering +/// - Active Passive, e.g. playing an animation (/a video) +/// - Active Active, e.g. scrolling using a touch screen +/// - These have slightly different characteristics, because animation smoothness in in the passive case is more important. +/// Whereas latency reduction is more important in the active case. +/// 2) Rendering "once", i.e. in response to a keypress. Note that scrolling will likely be treated +/// as animation, and is an important case for latency minimisation. +/// 3) Stopping active rendering, i.e. the last frame. +/// +/// Note also that we need to be aware of in this design. +/// +/// We observe that `earliest_present_time` can be earlier than `actual_present_time` because of earlier presents "getting in the way". +/// +/// In the rendering once case, we just render as soon as possible. +/// It *could* be possible to coalesce these updates. +/// There are two phases to the multiple renders case: +/// 1) Up-to-date control loop +/// 2) Outdated control loop. +/// +/// The outdated control loop case is due to the Presentation Time API having ~5 frames of latency. +/// (see , +/// the "Android 8.0, presentation timing latency" heading). +/// +/// At the start of rendering, we are in the outdated control loop case, because of the above mentioned latency. That involves: +/// 1) We render the first frame immediately. This uses a best-effort estimated present time. +/// This does *not* have an requestedPresentationTime. +/// 2) We then set about rendering the second frame, which uses an estimated present time *one* interval +/// of display refresh rate after the first presentation. We start the rendering work for this frame +/// (tunable) ~20% sooner than one refresh rate interval after the start of rendering work for the first frame. +/// - If work has already finished on the first frame, then +/// - A potential option here is to cancel this render (GPU side) if the previous frame took (significantly) longer +/// than one refresh interval. It is future work to reason about this. +/// We foresee this as potentially advantageous because the CPU side work would be a relatively small part of +/// a frame, and so it would probably lead to "better" animation smoothness. +/// 3) Once the first frame finishes rendering, we present the second frame. We use "render end time" - "render start time" (`T_R`) to +/// estimate how many refresh durations (`N`) this render took, with a (tunable) ~25% "grace reduction" in the value. +/// This grace amount is to account for a likely ramping up of GPU power states, so future frames will probably be faster. +/// We also use `T_R` to estimate how many refresh durations the second frame will take to render (`N_2`), for scheduling the +/// present of the second frame. This does not use the grace reduction, but does have a grace subtraction of ~45% of the refresh cycle to +/// count as one frame. This grace period is to account for the fact that we don't know when in the refresh cycle we started rendering, +/// so there's a chance that even if both frames took longer than one refresh duration to render, we can "fake" smoothness. +/// Additionally, this slightly helps account for the GPU power state ramp-up. +/// 4) We present the second frame with a time which is "render end time" + previous "present clock - latching clock" + `N_2` * refresh durations. +/// 5) We start rendering on the third frame, either `N_2 + N` or `2*N` (TBD) refresh durations after the first frame rendering started. +/// This will have an estimated present time of the same `N_2 + N` or `2*N` refresh durations from the estimated present time. +/// +/// Using a statistical model for the variable time from starting rendering from event `1` to `2`. +/// +/// TODO: Reason about multi-window/multi-display? +pub struct Thinking; + +pub struct FrameRenderRequest { + pub scene: vello::Scene, + pub frame: FrameId, + pub expected_present: u64, + // In general, if touch is held, will need the next frame. + pub needs_next_frame: bool, // TODO: No, LatencyOptimised, ConsistencyOptimised? + pub present_immediately: bool, + pub paint_start: Instant, +} + +pub enum VelloControl { + Frame(FrameRenderRequest), + /// A resize request. This might skip rendering a previous frame + /// if it arrives before that frame is presented. + /// + /// The second parameter is (width, height). + Resize(FrameRenderRequest, (u32, u32), Sender), +} + +pub struct VelloPacingConfiguration { + queue: Arc, + device: Arc, + surface: Surface<'static>, + adapter: Arc, + config: SurfaceConfiguration, +} + +pub fn launch( + instance: &wgpu::Instance, + mut params: VelloPacingConfiguration, +) -> Sender { + let (tx, rx) = std::sync::mpsc::channel(); + let display_timing = if params + .device + .features() + .contains(wgpu::Features::VULKAN_GOOGLE_DISPLAY_TIMING) + { + unsafe { + params + .device + // Safety: + // We do not destroy the device handle we are given. + .as_hal::(|device| { + let device = device?; + // Safety: + // We do not destroy the instance we are given. + let instance = instance.as_hal::()?; + Some(ash::google::display_timing::Device::new( + instance.shared_instance().raw_instance(), + device.raw_device(), + )) + }) + .flatten() + } + } else { + None + }; + // Ensure that the surface is compatible with this device. + params.surface.configure(¶ms.device, ¶ms.config); + let refresh_rate = display_timing + .as_ref() + .and_then(|display_timing| unsafe { + // Safety: We don't manually destroy the surface + params + .surface + .as_hal::(|surface| { + let surface = surface?; + // Safety: We know the device and the swapchain are compatible, based on the above configure + Some( + display_timing + .get_refresh_cycle_duration(surface.raw_swapchain()?) + .ok()? + .refresh_duration, + ) + }) + .flatten() + }) + .unwrap_or(0); + + let pacing = VelloPacing { + rx, + queue: params.queue, + device: params.device, + google_display_timing_ext_device: display_timing, + adapter: params.adapter, + surface: params.surface, + surface_config: params.config, + stats: Default::default(), + refresh_rate, + mapped_unused_download_buffers: Default::default(), + mapped_unused_download_buffers_scratch: Default::default(), + free_download_buffers: Default::default(), + presenting_frame: None, + gpu_working_frame: None, + }; + pacing.start(); + tx +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FrameId(u32); + +impl FrameId { + pub fn next(self) -> Self { + Self(self.0.wrapping_add(1)) + } + + pub fn raw(self) -> u32 { + self.0 + } +} + +struct FrameStats { + /// When the rendering work started on the GPU, in nanoseconds. + /// + /// Used to estimate how long frames are taking to render, to get + /// faster feedback on whether we should request a slower (or faster) display mode. + /// (We might choose to be more conservative in requesting a faster display mode, but + /// act quickly on requesting a slower one?) + render_start: u64, + /// When the rendering work finished on the GPU, in nanoseconds. + /// + /// Used to: + /// - estimate the amount of time compositing takes + /// - for estimating the expected presentation times before up-to-date timestamps become available + render_end: u64, + /// The time we told the application that this frame would be rendered at. + /// + /// In early frames, this was a best-effort guess, but we should be consistent in the use of this. + /// + /// For most users, the estimated present is only useful in terms of "how long between presents". + /// However, an absolute timestamp is useful for calibrating Audio with Video + estimated_present: u64, + /// The time which we started the entire painting for this frame, including scene construction, etc. + /// + /// This is used for: + /// 1) Providing the time at which rendering should start, generally + /// 1 refresh duration later than the previous frame +- some margin. + paint_start: Instant, + /// Whether the source paint request wanted there to be a next frame. + next_frame_continued: bool, + // /// The time at which the frame pacing controller received the frame to be rendered. + // /// + // /// This is used to predict a possible frame deadline miss early + // paint_end: Instant, + /// The information we received from `SurfaceFlinger`, which is severely outdated by design. + /// + /// We might not get this information, so should be ready to work without it. + presentation_time: Option, +} + +struct InFlightFrame { + download_map_buffer: Buffer, + work_complete: Arc, + paint_start: Instant, + id: FrameId, + submission_index: SubmissionIndex, + width: u32, + height: u32, + /// Information needed to perform a presentation, and not before. + required_to_present: Option<(SurfaceTexture, Scene)>, + estimated_present: u64, + next_frame_expected: bool, +} + +/// The state of the frame pacing controller thread. +struct VelloPacing { + rx: Receiver, + queue: Arc, + device: Arc, + adapter: Arc, + surface: wgpu::Surface<'static>, + + google_display_timing_ext_device: Option, + surface_config: SurfaceConfiguration, + /// Stats from previous frames, stored in a ring buffer (max capacity ~10?). + stats: VecDeque<(FrameId, FrameStats)>, + /// The refresh rate reported "by the system". + refresh_rate: u64, + mapped_unused_download_buffers: Vec<(Buffer, Arc)>, + mapped_unused_download_buffers_scratch: Vec<(Buffer, Arc)>, + // TODO: Smallvec? + free_download_buffers: Vec<(Buffer, Arc)>, + /// Details about the previous frame, which has already been presented. + presenting_frame: Option, + /// Details about the frame whose work has been submitted to the GPU, but not yet presented. + gpu_working_frame: Option, +} + +/// A sketch of the expected API. +impl VelloPacing { + fn start(self) { + std::thread::spawn(move || self.run()); + } + + fn run(mut self) { + loop { + let timeout = if self.frame_in_flight() { + Duration::from_millis(4) + } else { + // If there is no frame in flight, then we can + // keep ticking the device, but it isn't really needed + Duration::from_millis(100) + }; + match self.rx.recv_timeout(timeout) { + Ok(command) => { + match command { + VelloControl::Frame(request) => { + self.poll_frame(); + // TODO: Error handling + let texture = self.surface.get_current_texture().unwrap(); + self.paint_frame(request.scene, texture); + self.poll_frame(); + } + VelloControl::Resize(request, (width, height), done) => { + // Cancel the frame which hasn't been scheduled for presentation. + if let Some(mut old_frame) = self.gpu_working_frame.take() { + // This frame will never be presented + drop(old_frame.required_to_present.take()); + self.abandon(old_frame); + } + // Make sure any easily detectable needed reallocation happens + // TODO: What do we want to do if: + // 1) The previous frame didn't succeed + // 2) We are trying to resize + // We choose not to address this presently, because it is a. + if let Some(old_presenting) = self.presenting_frame.take() { + self.abandon(old_presenting); + } + self.surface_config.width = width; + self.surface_config.height = height; + self.surface.configure(&self.device, &self.surface_config); + + let texture = self.surface.get_current_texture().unwrap(); + let frame = self.paint_frame_inner(&request.scene, &texture); + texture.present(); + self.presenting_frame = Some(frame); + if let Err(e) = done.send(request.frame) { + tracing::error!("Failed to send present result {e}"); + }; + } + } + + continue; + } + Err(RecvTimeoutError::Disconnected) => { + // TODO: Is this an error, or the right way to signal to stop rendering? + if let Some(mut old_frame) = self.gpu_working_frame.take() { + // This frame will never be presented + drop(old_frame.required_to_present.take()); + } + // What do we need to be careful about dropping? + // Do we need to run the GPU the completion? + // self.device.poll(wgpu::MaintainBase::Wait); + break; + } + Err(RecvTimeoutError::Timeout) => { + // Fall through intentionally + } + } + self.poll_frame(); + } + } + + fn abandon(&mut self, frame: InFlightFrame) { + // This is accurate since we never make a `Weak` for the `work_complete` buffers + // If that became untrue, the only risk is that the buffer and value would be dropped instead of reused. + if Arc::strong_count(&frame.work_complete) == 1 { + Self::handle_completed_mapping_finished( + &mut self.free_download_buffers, + frame.download_map_buffer, + frame.work_complete, + ); + } else { + self.mapped_unused_download_buffers + .push((frame.download_map_buffer, frame.work_complete)); + } + } + + fn paint_frame(&mut self, scene: Scene, to: SurfaceTexture) { + // TODO: It could happen that frames get sent not in response to our handling code (e.g. as an immediate frame) + // Do we just always want to abandon the current working frame? + assert!(self.gpu_working_frame.is_none()); + let mut res = self.paint_frame_inner(&scene, &to); + res.required_to_present = Some((to, scene)); + self.gpu_working_frame = Some(res); + } + + #[must_use] + #[expect(unused_variables, reason = "Not yet implemented")] + fn paint_frame_inner(&mut self, scene: &Scene, to: &SurfaceTexture) -> InFlightFrame { + // Prepare command buffers, etc. + + todo!(); + } + + fn poll_frame(&mut self) { + self.device.poll(wgpu::Maintain::Poll); + let mut failed = false; + let mut desired_present_time: Option = None; + if let Some(presenting) = &mut self.presenting_frame { + if let Some(value) = Arc::get_mut(&mut presenting.work_complete) { + // Reset the value to false, because we're about to reuse it. + if std::mem::take(value.get_mut()) { + let presenting = self + .presenting_frame + .take() + .expect("We know this value is present"); + + { + let map_result = + presenting.download_map_buffer.slice(..).get_mapped_range(); + eprintln!("Downloaded: {map_result:?}"); + // Will be calculated from `map_result`, as part of https://github.com/linebender/vello/pull/606 + failed = false; + if failed { + // Perform reallocation. Will be part of https://github.com/linebender/vello/pull/606. + } + // The time which the "next" frame (the `gpu_working_frame`) should not present before. + // If `map_result` indicates we really badly overflowed the available time, + // then this will be later than otherwise expected, to avoid a stutter. + // See https://developer.android.com/games/sdk/frame-pacing + desired_present_time = None; + self.stats.push_back(( + presenting.id, + FrameStats { + // TODO: Make optional because some backends don't support timestamp queries? + render_start: 0, + render_end: 0, + estimated_present: presenting.estimated_present, + paint_start: presenting.paint_start, + presentation_time: None, + next_frame_continued: presenting.next_frame_expected, + }, + )); + } + Self::handle_completed_mapping_finished( + &mut self.free_download_buffers, + presenting.download_map_buffer, + presenting.work_complete, + ); + } else { + unreachable!( + "Buffer mapping/work complete callback dropped without being called." + ) + } + } else { + // The presenting frame's work isn't complete. Probably nothing to do? + } + } + if self.presenting_frame.is_none() { + if let Some(mut working_frame) = self.gpu_working_frame.take() { + let (texture, scene) = working_frame.required_to_present.take().unwrap(); + if failed { + // Then redo the paint; we know the previous attempted frame will have been cancalled, so won't be expensive. + // We choose to present here immediately, to minimise likely latency costs. + // We know that the. + let new_inner = self.paint_frame_inner(&scene, &texture); + let old_working = std::mem::replace(&mut working_frame, new_inner); + self.abandon(old_working); + } else if working_frame.work_complete.load(Ordering::Relaxed) { + // I don't think there's actually anything interesting to do here, but might need to be reasoned about. + }; + // We run a display timing request after the present occurs, but we only need to get the Vulkan swapchain once. + let mut swc = None; + if let Some(desired_present_time) = + // Pseudocode for the or-else case. This is to handle the scenario where the previous frame finished + // *before* we. + desired_present_time + .or_else(|| Some(self.stats.front()?.1.estimated_present)) + { + swc = unsafe { + // SAFETY: We do not "manually drop" the raw handle, i.e. call "vkDestroySurfaceKHR" + self.surface + .as_hal::(|surface| { + if let Some(surface) = surface { + surface.set_next_present_time(ash::vk::PresentTimeGOOGLE { + desired_present_time, + present_id: working_frame.id.0, + }); + Some(surface.raw_swapchain()) + } else { + None + } + }) + .flatten() + .flatten() + }; + } + texture.present(); + if let Some(swc) = swc { + // We load the past present timing information, because the timings we get access to were updated + // in response to `present`. + // This is because https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/vulkan/libvulkan/swapchain.cpp#2394 + // is called inside `QueuePresentKHR` + if let Some(device) = self.google_display_timing_ext_device.as_ref() { + // Safety: We validated that the swapchain came from the same device. + let timing_info = unsafe { device.get_past_presentation_timing(swc) }; + match timing_info { + Ok(timing_info) => { + 'outer: for info in timing_info { + for (frame_id, stats) in &mut self.stats { + if frame_id.0 == info.present_id { + // TODO: Can there be multiple of these for the same present id? + stats.presentation_time = Some(info); + continue 'outer; + } + } + // TODO: This would be possible if we clear out items which are too old, in additional to those which are too far behind. + if false { + // Maybe we should warn once here? + tracing::warn!( + "Got present timing information for unknown frame '{}'", + info.present_id + ); + } + } + } + // All the possible errors are pretty fatal + Err(err) => tracing::error!( + "Got {err} whilst trying to get presentation timing results" + ), + } + // Safety: We validated that the swapchain came from the same device. + let refresh_rate = unsafe { device.get_refresh_cycle_duration(swc) }; + self.refresh_rate = refresh_rate + .ok() + .map(|it| it.refresh_duration) + .unwrap_or(self.refresh_rate); + } + } + if working_frame.next_frame_expected { + // Do the maths for when we should ask the main thread for the next frame + // TODO: Do we need our own timer thread for just that, or should something else happen? + } + self.presenting_frame = Some(working_frame); + } + } + assert!(self.mapped_unused_download_buffers_scratch.is_empty()); + for (buffer, work_complete) in self.mapped_unused_download_buffers.drain(..) { + // We know that the `work_complete` will not change again. + if Arc::strong_count(&work_complete) == 1 { + Self::handle_completed_mapping_finished( + &mut self.free_download_buffers, + buffer, + work_complete, + ); + } else { + self.mapped_unused_download_buffers_scratch + .push((buffer, work_complete)); + } + } + std::mem::swap( + &mut self.mapped_unused_download_buffers_scratch, + &mut self.mapped_unused_download_buffers, + ); + } + + fn frame_in_flight(&self) -> bool { + self.gpu_working_frame.is_some() + } + + /// Handle a completed buffer mapping for reuse. + /// `buffer` should be finished with, but mapped. + /// + /// Associated function for borrow checker purposes + fn handle_completed_mapping_finished( + unmapped_download_buffers: &mut Vec<(Buffer, Arc)>, + buffer: Buffer, + mut work_complete: Arc, + ) { + if let Some(value) = Arc::get_mut(&mut work_complete) { + let value = value.get_mut(); + if !*value { + tracing::error!("Tried to unmap buffer which was never assigned for mapping?"); + } else { + buffer.unmap(); + } + *value = false; + if unmapped_download_buffers.len() < 4 { + unmapped_download_buffers.push((buffer, work_complete)); + } + } + } +}