From bdd3482b75f83f721523dd0f2d6eb7d0d53e0f25 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 7 Apr 2026 10:42:31 -0600 Subject: [PATCH] feat!: replace CPU rendering with GPU via wgpu/WGSL Drop tiny-skia and tempfile. All rendering now runs on the GPU using a WGSL fragment shader with rounded-rect SDF, gaussian glow falloff, and cursor avoidance. Add color temperature support with presets (warm, cool, studio, daylight, golden) and custom Kelvin values. Suppress MangoHUD via environment variable. --- Cargo.lock | 1151 +++++++++++++++++++++++++++++++++++++---------- Cargo.toml | 7 +- src/main.rs | 99 +++- src/render.rs | 428 +++++++++++------- src/shader.wgsl | 75 +++ src/wayland.rs | 161 ++++--- 6 files changed, 1419 insertions(+), 502 deletions(-) create mode 100644 src/shader.wgsl diff --git a/Cargo.lock b/Cargo.lock index 1adf765..6263f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,19 @@ version = 4 [[package]] -name = "adler2" -version = "2.0.1" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] [[package]] name = "anyhow" @@ -14,29 +23,82 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "cc" @@ -61,12 +123,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "crc32fast" -version = "1.5.0" +name = "codespan-reporting" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ - "cfg-if", + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", ] [[package]] @@ -91,37 +189,12 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "foldhash" version = "0.1.5" @@ -129,16 +202,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] -name = "getrandom" -version = "0.4.2" +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", - "libc", - "r-efi", - "wasip2", - "wasip3", + "crunchy", + "num-traits", + "zerocopy", ] [[package]] @@ -147,7 +315,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -155,18 +323,17 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] -name = "heck" -version = "0.5.0" +name = "hexf-parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "indexmap" @@ -176,21 +343,64 @@ checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", - "serde", - "serde_core", ] [[package]] -name = "itoa" -version = "1.0.18" +name = "jni-sys" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] [[package]] -name = "leb128fmt" -version = "0.1.0" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "libc" @@ -198,12 +408,43 @@ version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -217,13 +458,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "naga" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647" dependencies = [ - "adler2", - "simd-adler32", + "arrayvec", + "bit-set", + "bitflags", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash", + "spirv", + "thiserror", + "unicode-ident", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", ] [[package]] @@ -238,12 +504,122 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkg-config" version = "0.3.32" @@ -251,27 +627,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] -name = "png" -version = "0.18.1" +name = "pollster" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ - "bitflags", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", + "portable-atomic", ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "presser" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro2" @@ -282,6 +662,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + [[package]] name = "quick-xml" version = "0.39.2" @@ -301,23 +687,65 @@ dependencies = [ ] [[package]] -name = "r-efi" -version = "6.0.0" +name = "range-alloc" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "ringa" version = "0.1.0" dependencies = [ "anyhow", + "bytemuck", "nix", - "tempfile", - "tiny-skia", + "pollster", + "raw-window-handle", + "wayland-backend", "wayland-client", "wayland-protocols-wlr", + "wgpu", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "1.1.4" @@ -332,10 +760,22 @@ dependencies = [ ] [[package]] -name = "semver" -version = "1.0.28" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" @@ -344,6 +784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -366,19 +807,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - [[package]] name = "shlex" version = "1.3.0" @@ -386,10 +814,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "simd-adler32" -version = "0.3.9" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] [[package]] name = "smallvec" @@ -398,10 +835,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "strict-num" -version = "0.1.1" +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" @@ -415,42 +861,32 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.27.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys", + "winapi-util", ] [[package]] -name = "tiny-skia" -version = "0.12.0" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png", - "tiny-skia-path", + "thiserror-impl", ] [[package]] -name = "tiny-skia-path" -version = "0.12.0" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ - "arrayref", - "bytemuck", - "strict-num", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -460,61 +896,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unicode-width" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ - "wit-bindgen", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +name = "wasm-bindgen-futures" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ - "wit-bindgen", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wasm-encoder" -version = "0.244.0" +name = "wasm-bindgen-macro" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ - "leb128fmt", - "wasmparser", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "wasm-bindgen-macro-support" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", ] [[package]] -name = "wasmparser" -version = "0.244.0" +name = "wasm-bindgen-shared" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", + "unicode-ident", ] [[package]] @@ -526,6 +971,7 @@ dependencies = [ "cc", "downcast-rs", "rustix", + "scoped-tls", "smallvec", "wayland-sys", ] @@ -584,15 +1030,299 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ + "dlib", + "log", + "once_cell", "pkg-config", ] +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837" +dependencies = [ + "arrayvec", + "bitflags", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e80ac6cf1895df6342f87d975162108f9d98772a0d74bc404ab7304ac29469e" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43acd053312501689cd92a01a9638d37f3e41a5fd9534875efa8917ee2d11ac0" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef043bf135cc68b6f667c55ff4e345ce2b5924d75bad36a47921b0287ca4b24a" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "725d5c006a8c02967b6d93ef04f6537ec4593313e330cfe86d9d3f946eb90f28" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags", + "block2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga", + "ndk-sys", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", + "objc2-quartz-core", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4684f4410da0cf95a4cb63bb5edaac022461dedb6adf0b64d0d9b5f6890d51" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6" +dependencies = [ + "bitflags", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -603,95 +1333,36 @@ dependencies = [ ] [[package]] -name = "wit-bindgen" -version = "0.51.0" +name = "windows-threading" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "wit-bindgen-rust-macro", + "windows-link", ] [[package]] -name = "wit-bindgen-core" -version = "0.51.0" +name = "xml-rs" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ - "anyhow", - "heck", - "wit-parser", + "zerocopy-derive", ] [[package]] -name = "wit-bindgen-rust" -version = "0.51.0" +name = "zerocopy-derive" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", "proc-macro2", "quote", "syn", - "wit-bindgen-core", - "wit-bindgen-rust", ] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 1761b96..c65a9f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,11 @@ rust-version = "1.85" [dependencies] anyhow = "1.0" +bytemuck = { version = "1", features = ["derive"] } nix = { version = "0.29", features = ["signal", "process", "poll"] } -tempfile = "3" -tiny-skia = "0.12.0" +pollster = "0.4" +raw-window-handle = "0.6" +wayland-backend = { version = "0.3", features = ["client_system"] } wayland-client = "0.31" wayland-protocols-wlr = { version = "0.3", features = ["client"] } +wgpu = "29" diff --git a/src/main.rs b/src/main.rs index 19ab4c6..34fb999 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,71 +4,120 @@ mod wayland; use std::time::{Duration, Instant}; -use anyhow::{Context, Result}; -use tiny_skia::Pixmap; +use anyhow::Result; use lock::ToggleResult; +use render::GpuRenderer; use wayland::{OverlayEvent, WaylandBackend}; -const DISPLAY_DURATION: Duration = Duration::from_secs(5); +const DISPLAY_DURATION: Duration = Duration::from_secs(10); +const FRAME_INTERVAL: Duration = Duration::from_millis(16); fn main() -> Result<()> { + // Suppress MangoHUD — it hooks into Vulkan and we don't want an overlay on our overlay. + std::env::set_var("DISABLE_MANGOHUD", "1"); + match lock::try_toggle()? { ToggleResult::SignaledExisting => return Ok(()), ToggleResult::RunNew => {} } + let color = parse_color_arg(); + let _lock = lock::create_lock()?; let mut wl = WaylandBackend::new()?; let (w, h) = wl.screen_size(); - let mut pixmap = Pixmap::new(w, h).context("create pixmap")?; - let mut argb_buf = vec![0u8; (w as usize) * (h as usize) * 4]; + + let mut renderer = GpuRenderer::new( + wl.raw_display_handle()?, + wl.raw_window_handle()?, + w, + h, + color, + )?; let mut cursor_x = w as f64 / 2.0; let mut cursor_y = h as f64 / 2.0; - draw(&mut wl, &mut pixmap, &mut argb_buf, cursor_x, cursor_y)?; + renderer.render(cursor_x, cursor_y)?; let start = Instant::now(); + let mut last_draw = Instant::now(); + let mut dirty = false; loop { let elapsed = start.elapsed(); if elapsed >= DISPLAY_DURATION || lock::should_exit() { break; } + + if dirty && last_draw.elapsed() >= FRAME_INTERVAL { + renderer.render(cursor_x, cursor_y)?; + last_draw = Instant::now(); + dirty = false; + } + let remaining = DISPLAY_DURATION - elapsed; - let event = match wl.next_event(remaining)? { + let poll_timeout = if dirty { + FRAME_INTERVAL + .saturating_sub(last_draw.elapsed()) + .min(remaining) + } else { + remaining + }; + + let event = match wl.next_event(poll_timeout)? { Some(ev) => ev, - None => continue, // poll timeout — loop back to check elapsed time + None => continue, }; match event { OverlayEvent::Close => break, OverlayEvent::CursorMoved(x, y) => { cursor_x = x; cursor_y = y; - draw(&mut wl, &mut pixmap, &mut argb_buf, cursor_x, cursor_y)?; - } - OverlayEvent::Configure(new_w, new_h) => { - if new_w != w || new_h != h { - break; - } + dirty = true; } + OverlayEvent::Configure(_, _) => {} } } Ok(()) } -fn draw( - wl: &mut WaylandBackend, - pixmap: &mut Pixmap, - argb_buf: &mut [u8], - cursor_x: f64, - cursor_y: f64, -) -> Result<()> { - render::render_ring_light(pixmap, cursor_x, cursor_y); - render::pixmap_to_argb8888(pixmap, argb_buf); - let (w, h) = wl.screen_size(); - wl.present(argb_buf, w, h) +/// Parse color temperature from CLI args. +/// +/// Usage: +/// ringa → pure white (default) +/// ringa warm → 3200K (warm tungsten) +/// ringa cool → 6500K (cool daylight) +/// ringa studio → 5000K (neutral studio) +/// ringa daylight → 5600K (daylight balanced) +/// ringa golden → 2700K (golden hour) +/// ringa 4200 → custom Kelvin value +fn parse_color_arg() -> [f32; 3] { + let arg = match std::env::args().nth(1) { + Some(a) => a, + None => return [1.0, 1.0, 1.0], // pure white + }; + + let kelvin = match arg.to_lowercase().as_str() { + "warm" => 3200, + "golden" => 2700, + "studio" => 5000, + "daylight" => 5600, + "cool" => 6500, + "white" => return [1.0, 1.0, 1.0], + other => match other.parse::() { + Ok(k) if (1000..=40000).contains(&k) => k, + _ => { + eprintln!( + "usage: ringa [white|warm|golden|studio|daylight|cool|]" + ); + std::process::exit(1); + } + }, + }; + + render::kelvin_to_rgb(kelvin) } diff --git a/src/render.rs b/src/render.rs index 251168b..3998897 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,183 +1,287 @@ -//! Ring light glow rendering. -//! -//! The ring is a thick stroke centered on a single rounded rectangle SDF. -//! This guarantees uniform width everywhere, including at corners. -//! Glow bleeds outward and inward from the stroke edges. +//! GPU renderer using wgpu + WGSL fragment shader. -use tiny_skia::Pixmap; +use anyhow::{Context, Result}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; +use wgpu::util::DeviceExt; -// The rounded rect centerline is inset this far from the screen edge. -// The visible ring extends RING_HALF_WIDTH on either side of this line. -const CENTERLINE_INSET: f32 = 80.0; -// Half the stroke width. Total ring thickness = 2 * RING_HALF_WIDTH. -const RING_HALF_WIDTH: f32 = 30.0; -// Corner radius of the centerline rounded rect. -const CORNER_RADIUS: f32 = 30.0; +#[repr(C)] +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +struct Uniforms { + screen_size: [f32; 2], + cursor_pos: [f32; 2], + outer_inset: f32, + outer_corner_radius: f32, + ring_width: f32, + inner_corner_radius: f32, + glow_spread: f32, + cursor_avoid_radius: f32, + cursor_avoid_falloff: f32, + _pad: f32, + color: [f32; 3], + _pad2: f32, +} -// Soft glow spread outward and inward from the ring edges. -const GLOW_SPREAD: f32 = 50.0; -const RING_ALPHA: f32 = 1.00; +pub struct GpuRenderer { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + pipeline: wgpu::RenderPipeline, + uniform_buffer: wgpu::Buffer, + bind_group: wgpu::BindGroup, + uniforms: Uniforms, +} -// Cursor avoidance. -const CURSOR_AVOID_RADIUS: f32 = 120.0; -const CURSOR_AVOID_FALLOFF: f32 = 60.0; +impl GpuRenderer { + pub fn new( + display_handle: RawDisplayHandle, + window_handle: RawWindowHandle, + width: u32, + height: u32, + color: [f32; 3], + ) -> Result { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::VULKAN, + flags: wgpu::InstanceFlags::empty(), + backend_options: Default::default(), + memory_budget_thresholds: Default::default(), + display: None, + }); -/// Render the ring light glow into `pixmap`, avoiding the cursor at `(cx, cy)`. -pub fn render_ring_light(pixmap: &mut Pixmap, cx: f64, cy: f64) { - let w = pixmap.width() as f32; - let h = pixmap.height() as f32; - let cx = cx as f32; - let cy = cy as f32; - let wi = pixmap.width() as usize; - let hi = pixmap.height() as usize; - - let data = pixmap.data_mut(); - data.fill(0); - - let screen_cx = w / 2.0; - let screen_cy = h / 2.0; - - // Single rounded rect centerline. - let hw = w / 2.0 - CENTERLINE_INSET; - let hh = h / 2.0 - CENTERLINE_INSET; - - // The affected band: from (CENTERLINE_INSET - RING_HALF_WIDTH - GLOW_SPREAD) - // to (CENTERLINE_INSET + RING_HALF_WIDTH + GLOW_SPREAD) from each screen edge. - let band_start = (CENTERLINE_INSET - RING_HALF_WIDTH - GLOW_SPREAD) - .max(0.0) - .floor() as usize; - let band_end = (CENTERLINE_INSET + RING_HALF_WIDTH + GLOW_SPREAD).ceil() as usize + 1; - - // Top band - for py in band_start..band_end.min(hi) { - for px in 0..wi { - let a = pixel_alpha(px as f32, py as f32, screen_cx, screen_cy, hw, hh, cx, cy); - write_pixel(data, py * wi + px, a); + let surface = unsafe { + instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle { + raw_display_handle: Some(display_handle), + raw_window_handle: window_handle, + }) } + .context("create wgpu surface")?; + + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + compatible_surface: Some(&surface), + power_preference: wgpu::PowerPreference::LowPower, + ..Default::default() + })) + .context("no compatible GPU adapter")?; + + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: Some("ringa"), + ..Default::default() + }, + )) + .context("request GPU device")?; + + let caps = surface.get_capabilities(&adapter); + let format = caps + .formats + .iter() + .find(|f| f.is_srgb()) + .copied() + .or_else(|| caps.formats.first().copied()) + .context("no supported surface format")?; + + let alpha_mode = if caps + .alpha_modes + .contains(&wgpu::CompositeAlphaMode::PreMultiplied) + { + wgpu::CompositeAlphaMode::PreMultiplied + } else { + caps.alpha_modes + .first() + .copied() + .context("no supported alpha mode")? + }; + + surface.configure( + &device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width, + height, + present_mode: wgpu::PresentMode::Mailbox, + desired_maximum_frame_latency: 1, + alpha_mode, + view_formats: vec![], + }, + ); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("ring shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let uniforms = Uniforms { + screen_size: [width as f32, height as f32], + cursor_pos: [width as f32 / 2.0, height as f32 / 2.0], + outer_inset: 65.0, + outer_corner_radius: 36.0, + ring_width: 50.0, + inner_corner_radius: 100.0, + glow_spread: 50.0, + cursor_avoid_radius: 300.0, + cursor_avoid_falloff: 1500.0, + _pad: 0.0, + color, + _pad2: 0.0, + }; + + let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("uniforms"), + contents: bytemuck::bytes_of(&uniforms), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("uniforms layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("uniforms bind group"), + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("ring pipeline layout"), + bind_group_layouts: &[Some(&bind_group_layout)], + immediate_size: 0, + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("ring pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: Default::default(), + multiview_mask: None, + cache: None, + }); + + Ok(Self { + surface, + device, + queue, + pipeline, + uniform_buffer, + bind_group, + uniforms, + }) } - // Bottom band - let bottom_start = hi.saturating_sub(band_end); - let bottom_end = hi.saturating_sub(band_start); - for py in bottom_start..bottom_end.min(hi) { - if py < band_end { - continue; - } - for px in 0..wi { - let a = pixel_alpha(px as f32, py as f32, screen_cx, screen_cy, hw, hh, cx, cy); - write_pixel(data, py * wi + px, a); - } - } + pub fn render(&mut self, cx: f64, cy: f64) -> Result<()> { + self.uniforms.cursor_pos = [cx as f32, cy as f32]; + self.queue + .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&self.uniforms)); - // Left band (between top and bottom) - let vert_start = band_end.min(hi); - let vert_end = hi.saturating_sub(band_end); - for py in vert_start..vert_end { - for px in band_start..band_end.min(wi) { - let a = pixel_alpha(px as f32, py as f32, screen_cx, screen_cy, hw, hh, cx, cy); - write_pixel(data, py * wi + px, a); - } - } - - // Right band (between top and bottom) - let right_start = wi.saturating_sub(band_end); - let right_end = wi.saturating_sub(band_start); - for py in vert_start..vert_end { - for px in right_start..right_end.min(wi) { - if px < band_end { - continue; + let frame = match self.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(tex) + | wgpu::CurrentSurfaceTexture::Suboptimal(tex) => tex, + wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => { + return Ok(()) } - let a = pixel_alpha(px as f32, py as f32, screen_cx, screen_cy, hw, hh, cx, cy); - write_pixel(data, py * wi + px, a); + wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => { + anyhow::bail!("surface lost") + } + _ => anyhow::bail!("unexpected surface texture state"), + }; + + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("ring encoder"), + }); + + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("ring pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: None, + ..Default::default() + }); + + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.bind_group, &[]); + pass.draw(0..3, 0..1); } + + self.queue.submit(std::iter::once(encoder.finish())); + frame.present(); + Ok(()) } } -/// Convert a tiny-skia `Pixmap` (premultiplied RGBA) to Wayland's ARGB8888 -/// format (premultiplied BGRA in memory on little-endian). -pub fn pixmap_to_argb8888(pixmap: &Pixmap, out: &mut [u8]) { - let src = pixmap.data(); - assert_eq!(out.len(), src.len()); - for (dst, src) in out.chunks_exact_mut(4).zip(src.chunks_exact(4)) { - dst[0] = src[2]; // B - dst[1] = src[1]; // G - dst[2] = src[0]; // R - dst[3] = src[3]; // A - } -} +/// Convert color temperature in Kelvin to linear RGB. +/// Based on Tanner Helland's algorithm. +pub fn kelvin_to_rgb(kelvin: u32) -> [f32; 3] { + let temp = (kelvin as f64 / 100.0).clamp(10.0, 400.0); -// ---- Private helpers -------------------------------------------------------- - -/// Compute glow alpha for a single pixel using a single rounded rect SDF. -/// -/// The ring is all pixels where |sdf| <= RING_HALF_WIDTH (a thick stroke). -/// Glow fades from the ring edges outward/inward over GLOW_SPREAD pixels. -#[allow(clippy::too_many_arguments)] -fn pixel_alpha( - px: f32, - py: f32, - scx: f32, - scy: f32, - hw: f32, - hh: f32, - cursor_x: f32, - cursor_y: f32, -) -> u8 { - // Signed distance to the rounded rect centerline. - let sd = rounded_rect_sdf(px, py, scx, scy, hw, hh, CORNER_RADIUS); - let abs_sd = sd.abs(); - - let glow = if abs_sd <= RING_HALF_WIDTH { - // Inside the ring stroke — full brightness. - RING_ALPHA + let r = if temp <= 66.0 { + 1.0 } else { - // Distance from the nearest ring edge. - let dist = abs_sd - RING_HALF_WIDTH; - if dist >= GLOW_SPREAD { - return 0; - } - let t = dist / GLOW_SPREAD; - smoothstep(1.0 - t) * RING_ALPHA + let r = 329.698727446 * (temp - 60.0).powf(-0.1332047592); + (r / 255.0).clamp(0.0, 1.0) }; - // Cursor avoidance. - let dx = px - cursor_x; - let dy = py - cursor_y; - let cursor_dist = (dx * dx + dy * dy).sqrt(); - let cursor_factor = - smoothstep(((cursor_dist - CURSOR_AVOID_RADIUS) / CURSOR_AVOID_FALLOFF).clamp(0.0, 1.0)); + let g = if temp <= 66.0 { + let g = 99.4708025861 * temp.ln() - 161.1195681661; + (g / 255.0).clamp(0.0, 1.0) + } else { + let g = 288.1221695283 * (temp - 60.0).powf(-0.0755148492); + (g / 255.0).clamp(0.0, 1.0) + }; - let alpha = glow * cursor_factor; - (alpha * 255.0) as u8 -} + let b = if temp >= 66.0 { + 1.0 + } else if temp <= 19.0 { + 0.0 + } else { + let b = 138.5177312231 * (temp - 10.0).ln() - 305.0447927307; + (b / 255.0).clamp(0.0, 1.0) + }; -/// Signed distance field for a rounded rectangle. -/// -/// Returns negative inside, positive outside, zero on the boundary. -fn rounded_rect_sdf(px: f32, py: f32, cx: f32, cy: f32, hw: f32, hh: f32, r: f32) -> f32 { - let dx = (px - cx).abs() - (hw - r); - let dy = (py - cy).abs() - (hh - r); - let outside = dx.max(0.0); - let outside_y = dy.max(0.0); - let outside_len = (outside * outside + outside_y * outside_y).sqrt(); - let inside = dx.max(dy).min(0.0); - outside_len + inside - r -} - -/// Write a premultiplied white pixel with the given alpha. -fn write_pixel(data: &mut [u8], idx: usize, alpha: u8) { - if alpha == 0 { - return; - } - let offset = idx * 4; - data[offset] = alpha; - data[offset + 1] = alpha; - data[offset + 2] = alpha; - data[offset + 3] = alpha; -} - -/// Hermite smoothstep: 3t^2 - 2t^3 -fn smoothstep(t: f32) -> f32 { - let t = t.clamp(0.0, 1.0); - t * t * (3.0 - 2.0 * t) + [r as f32, g as f32, b as f32] } diff --git a/src/shader.wgsl b/src/shader.wgsl new file mode 100644 index 0000000..7864102 --- /dev/null +++ b/src/shader.wgsl @@ -0,0 +1,75 @@ +struct Uniforms { + screen_size: vec2f, + cursor_pos: vec2f, + // Outer rounded rect: inset from screen edge + outer_inset: f32, + outer_corner_radius: f32, + // Inner rounded rect: inset further by ring_width + ring_width: f32, + inner_corner_radius: f32, + // Glow + glow_spread: f32, + // Cursor avoidance + cursor_avoid_radius: f32, + cursor_avoid_falloff: f32, + // Color temperature (linear RGB) + _pad: f32, + color: vec3f, + _pad2: f32, +} + +@group(0) @binding(0) var u: Uniforms; + +@vertex +fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f { + let x = f32(i32(i) / 2) * 4.0 - 1.0; + let y = f32(i32(i) % 2) * 4.0 - 1.0; + return vec4f(x, y, 0.0, 1.0); +} + +// Rounded rectangle SDF (Inigo Quilez). +fn rounded_rect_sdf(p: vec2f, h: vec2f, r: f32) -> f32 { + let q = abs(p) - h + vec2f(r); + return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - r; +} + +@fragment +fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f { + let center = u.screen_size / 2.0; + let p = pos.xy - center; + + // Outer rounded rect + let outer_half = center - vec2f(u.outer_inset); + let sd_outer = rounded_rect_sdf(p, outer_half, u.outer_corner_radius); + + // Inner rounded rect + let inner_half = outer_half - vec2f(u.ring_width); + let sd_inner = rounded_rect_sdf(p, inner_half, u.inner_corner_radius); + + // Ring: inside outer AND outside inner = solid at full brightness + var alpha: f32; + if sd_outer <= 0.0 && sd_inner >= 0.0 { + alpha = 1.0; + } else if sd_outer > 0.0 { + // Gaussian glow fading outward + let t = sd_outer / u.glow_spread; + alpha = exp(-3.0 * t * t); + } else { + // Gaussian glow fading inward + let t = -sd_inner / u.glow_spread; + alpha = exp(-3.0 * t * t); + } + + // Cursor avoidance + let cursor_dist = distance(pos.xy, u.cursor_pos); + let avoid_start = u.cursor_avoid_radius; + let avoid_end = u.cursor_avoid_radius + u.cursor_avoid_falloff; + if cursor_dist < avoid_end { + let t_avoid = clamp((cursor_dist - avoid_start) / (avoid_end - avoid_start), 0.0, 1.0); + alpha *= t_avoid * t_avoid * (3.0 - 2.0 * t_avoid); + } + + // Premultiplied color output + let rgb = u.color * alpha; + return vec4f(rgb, alpha); +} diff --git a/src/wayland.rs b/src/wayland.rs index b697a41..7795706 100644 --- a/src/wayland.rs +++ b/src/wayland.rs @@ -1,12 +1,10 @@ //! Wayland backend: layer-shell overlay with keyboard and pointer tracking. //! -//! Creates a full-screen layer-shell surface for the ring light overlay. -//! Tracks pointer position for cursor avoidance and captures keyboard -//! for Escape to exit. +//! Creates a full-screen layer-shell surface. Exposes raw Wayland handles +//! for wgpu surface creation. No CPU pixel buffers — GPU renders directly. use std::collections::VecDeque; -use std::io::Write; -use std::os::fd::AsFd; +use std::ptr::NonNull; use std::time::Duration; use wayland_client::{ @@ -15,32 +13,28 @@ use wayland_client::{ wl_buffer, wl_compositor, wl_keyboard, wl_pointer, wl_region, wl_registry, wl_seat, wl_shm, wl_shm_pool, wl_surface, }, - Connection, Dispatch, EventQueue, QueueHandle, WEnum, + Connection, Dispatch, EventQueue, Proxy, QueueHandle, WEnum, }; use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_layer_surface_v1}; use anyhow::{Context, Result}; +use raw_window_handle::{ + RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, +}; -// Linux evdev key code for Escape. const KEY_ESCAPE: u32 = 1; -/// Events emitted by the Wayland backend to the main loop. +#[allow(dead_code)] pub enum OverlayEvent { - /// Escape pressed or surface closed by compositor. Close, - /// Pointer moved to (x, y) in surface-local coordinates. CursorMoved(f64, f64), - /// Surface (re-)configured with new dimensions. Configure(u32, u32), } -// =========================================================================== -// WaylandBackend — public API. -// =========================================================================== - pub struct WaylandBackend { state: WaylandState, event_queue: EventQueue, + #[allow(dead_code)] queue_handle: QueueHandle, } @@ -73,9 +67,6 @@ impl WaylandBackend { if state.compositor.is_none() { anyhow::bail!("compositor does not advertise wl_compositor"); } - if state.shm.is_none() { - anyhow::bail!("compositor does not advertise wl_shm"); - } if state.layer_shell.is_none() { anyhow::bail!( "compositor does not support wlr-layer-shell-unstable-v1. \ @@ -105,10 +96,23 @@ impl WaylandBackend { (self.state.screen_w, self.state.screen_h) } - /// Wait up to `timeout` for the next event. Returns `None` on timeout - /// or if the surface has been torn down. + /// Raw Wayland display handle for wgpu surface creation. + pub fn raw_display_handle(&self) -> Result { + let ptr = self.state.conn.backend().display_ptr() as *mut std::ffi::c_void; + let nn = NonNull::new(ptr).context("wl_display pointer is null")?; + Ok(RawDisplayHandle::Wayland(WaylandDisplayHandle::new(nn))) + } + + /// Raw Wayland surface handle for wgpu surface creation. + pub fn raw_window_handle(&self) -> Result { + let surface = self.state.surface.as_ref().context("no wl_surface")?; + let ptr = surface.id().as_ptr() as *mut std::ffi::c_void; + let nn = NonNull::new(ptr).context("wl_surface pointer is null")?; + Ok(RawWindowHandle::Wayland(WaylandWindowHandle::new(nn))) + } + + /// Wait up to `timeout` for the next event. Returns `None` on timeout. pub fn next_event(&mut self, timeout: Duration) -> Result> { - // First drain any already-buffered events. if let Some(ev) = self.state.pending_events.pop_front() { return Ok(Some(ev)); } @@ -116,62 +120,71 @@ impl WaylandBackend { return Ok(None); } - // Flush outgoing requests, then poll the fd with a timeout. self.event_queue .flush() .context("flush before poll")?; - let read_guard = self.event_queue.prepare_read().unwrap(); - let fd = read_guard.connection_fd(); - let mut pollfd = [nix::poll::PollFd::new(fd, nix::poll::PollFlags::POLLIN)]; - let millis = timeout.as_millis().min(u16::MAX as u128) as u16; - let n = nix::poll::poll(&mut pollfd, millis).context("poll")?; - - if n > 0 { - // Data available — read and dispatch. - let _ = read_guard.read(); - } else { - // Timeout — drop the read guard (cancels the read). - drop(read_guard); - return Ok(None); - } - + // Try to dispatch any already-buffered events first. self.event_queue .dispatch_pending(&mut self.state) - .context("dispatch_pending")?; + .context("dispatch_pending (pre-poll)")?; + if let Some(ev) = self.state.pending_events.pop_front() { + // Re-run coalescing below with this event re-queued. + self.state.pending_events.push_front(ev); + } else { + // No pending events — try to read from the fd with a timeout. + match self.event_queue.prepare_read() { + Some(read_guard) => { + let fd = read_guard.connection_fd(); + let mut pollfd = + [nix::poll::PollFd::new(fd, nix::poll::PollFlags::POLLIN)]; + let millis = timeout.as_millis().min(u16::MAX as u128) as u16; + let n = nix::poll::poll(&mut pollfd, millis).context("poll")?; - Ok(self.state.pending_events.pop_front()) - } + if n > 0 { + let _ = read_guard.read(); + } else { + drop(read_guard); + return Ok(None); + } - /// Copy an ARGB8888 pixel buffer to the overlay surface via wl_shm. - pub fn present(&mut self, pixels: &[u8], width: u32, height: u32) -> Result<()> { - let shm = self.state.shm.as_ref().context("wl_shm not available")?; - let surface = self - .state - .surface - .as_ref() - .context("wl_surface not available")?; + self.event_queue + .dispatch_pending(&mut self.state) + .context("dispatch_pending (post-read)")?; + } + None => { + // Another queue has events buffered — dispatch ours. + self.event_queue + .dispatch_pending(&mut self.state) + .context("dispatch_pending (no-read)")?; + } + } + } - let stride = width * 4; - let mut file = tempfile::tempfile().context("create shm tempfile")?; - file.write_all(pixels).context("write pixel buffer")?; - - let pool = shm.create_pool(file.as_fd(), pixels.len() as i32, &self.queue_handle, ()); - let buffer = pool.create_buffer( - 0, - width as i32, - height as i32, - stride as i32, - wl_shm::Format::Argb8888, - &self.queue_handle, - (), - ); - - surface.attach(Some(&buffer), 0, 0); - surface.damage_buffer(0, 0, width as i32, height as i32); - surface.commit(); - pool.destroy(); - Ok(()) + // Coalesce CursorMoved events. + let mut last_cursor: Option<(f64, f64)> = None; + let mut first_non_cursor: Option = None; + while let Some(ev) = self.state.pending_events.pop_front() { + match ev { + OverlayEvent::CursorMoved(x, y) => last_cursor = Some((x, y)), + other => { + first_non_cursor = Some(other); + break; + } + } + } + if let Some(ev) = first_non_cursor { + if let Some((x, y)) = last_cursor { + self.state + .pending_events + .push_front(OverlayEvent::CursorMoved(x, y)); + } + return Ok(Some(ev)); + } + if let Some((x, y)) = last_cursor { + return Ok(Some(OverlayEvent::CursorMoved(x, y))); + } + Ok(None) } } @@ -195,6 +208,7 @@ struct WaylandState { conn: Connection, compositor: Option, + #[allow(dead_code)] shm: Option, seat: Option, layer_shell: Option, @@ -221,6 +235,11 @@ impl WaylandState { .context("zwlr_layer_shell_v1 not available")?; let surface = compositor.create_surface(qh, ()); + + let input_region = compositor.create_region(qh, ()); + input_region.add(0, 0, i32::MAX, i32::MAX); + surface.set_input_region(Some(&input_region)); + let layer_surface = layer_shell.get_layer_surface( &surface, None, @@ -347,12 +366,8 @@ impl Dispatch for WaylandState { surface_x, surface_y, .. - } => { - state - .pending_events - .push_back(OverlayEvent::CursorMoved(surface_x, surface_y)); } - wl_pointer::Event::Motion { + | wl_pointer::Event::Motion { surface_x, surface_y, ..