From 167cd61b4f1e4be69cfd5eb24492caa796488a8f Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Fri, 21 Mar 2025 13:37:05 -0300 Subject: [PATCH 01/16] First setup --- Cargo.lock | 1244 +++++++++++++++-- demos/hello_world_wgpu/Cargo.toml | 10 + demos/hello_world_wgpu/assets/game.yaml | 4 + demos/hello_world_wgpu/assets/pack.yaml | 3 + .../assets/sprite/fractal.png | Bin 0 -> 34581 bytes demos/hello_world_wgpu/src/main.rs | 114 ++ .../bones_wgpu_renderer/Cargo.toml | 24 + .../bones_wgpu_renderer/src/lib.rs | 642 +++++++++ .../bones_wgpu_renderer/src/shader.wgsl | 33 + .../bones_wgpu_renderer/src/texture.rs | 88 ++ 10 files changed, 2025 insertions(+), 137 deletions(-) create mode 100644 demos/hello_world_wgpu/Cargo.toml create mode 100644 demos/hello_world_wgpu/assets/game.yaml create mode 100644 demos/hello_world_wgpu/assets/pack.yaml create mode 100644 demos/hello_world_wgpu/assets/sprite/fractal.png create mode 100644 demos/hello_world_wgpu/src/main.rs create mode 100644 framework_crates/bones_wgpu_renderer/Cargo.toml create mode 100644 framework_crates/bones_wgpu_renderer/src/lib.rs create mode 100644 framework_crates/bones_wgpu_renderer/src/shader.wgsl create mode 100644 framework_crates/bones_wgpu_renderer/src/texture.rs diff --git a/Cargo.lock b/Cargo.lock index f7dd1cdb61..58b7e0c6d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ dependencies = [ "accesskit", "accesskit_macos", "accesskit_windows", - "winit", + "winit 0.28.7", ] [[package]] @@ -185,6 +185,27 @@ dependencies = [ "num_enum 0.6.1", ] +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.6.0", + "cc", + "cesu8", + "jni 0.21.1", + "jni-sys", + "libc", + "log", + "ndk 0.9.0", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum 0.7.3", + "thiserror 1.0.69", +] + [[package]] name = "android-properties" version = "0.2.2" @@ -312,6 +333,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -321,6 +348,15 @@ dependencies = [ "libloading 0.7.4", ] +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.5", +] + [[package]] name = "asn1-rs" version = "0.6.2" @@ -333,7 +369,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", ] @@ -559,7 +595,7 @@ dependencies = [ "js-sys", "parking_lot", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -644,7 +680,7 @@ dependencies = [ "fixedbitset", "rustc-hash 1.1.0", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", "thread_local", ] @@ -665,7 +701,7 @@ dependencies = [ "fixedbitset", "rustc-hash 1.1.0", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", "thread_local", ] @@ -761,7 +797,7 @@ dependencies = [ "bevy_math", "bevy_reflect 0.11.3", "bevy_utils 0.11.3", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -922,7 +958,7 @@ dependencies = [ "serde", "smallvec", "smol_str 0.2.2", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -937,7 +973,7 @@ dependencies = [ "downcast-rs", "erased-serde 0.3.31", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -947,7 +983,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "342a4b2d09db22c48607d23ad59a056aff1ee004549050a51d490d375ba29528" dependencies = [ "bevy_macro_utils 0.11.3", - "bit-set", + "bit-set 0.5.3", "proc-macro2", "quote", "syn 2.0.90", @@ -1001,18 +1037,18 @@ dependencies = [ "hexasphere", "image 0.24.9", "js-sys", - "naga", + "naga 0.12.3", "naga_oil", "parking_lot", "regex", "serde", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "thread_local", "wasm-bindgen", "web-sys", - "wgpu", - "wgpu-hal", + "wgpu 0.16.3", + "wgpu-hal 0.16.2", ] [[package]] @@ -1045,7 +1081,7 @@ dependencies = [ "bevy_utils 0.11.3", "ron", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", "uuid", ] @@ -1071,7 +1107,7 @@ dependencies = [ "fixedbitset", "guillotiere", "rectangle-pack", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -1113,7 +1149,7 @@ dependencies = [ "bevy_reflect 0.11.3", "bevy_utils 0.11.3", "crossbeam-channel", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -1141,7 +1177,7 @@ dependencies = [ "hashbrown 0.14.5", "instant", "petgraph", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", "uuid", ] @@ -1159,7 +1195,7 @@ dependencies = [ "instant", "nonmax", "petgraph", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", "uuid", ] @@ -1198,7 +1234,7 @@ dependencies = [ "bevy_math", "bevy_reflect 0.11.3", "bevy_utils 0.11.3", - "raw-window-handle", + "raw-window-handle 0.5.2", ] [[package]] @@ -1220,10 +1256,10 @@ dependencies = [ "bevy_utils 0.11.3", "bevy_window", "crossbeam-channel", - "raw-window-handle", + "raw-window-handle 0.5.2", "wasm-bindgen", "web-sys", - "winit", + "winit 0.28.7", ] [[package]] @@ -1261,7 +1297,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[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]] @@ -1270,6 +1315,18 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitfield-rle" version = "0.2.1" @@ -1434,7 +1491,7 @@ dependencies = [ "once_map", "paste", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -1503,7 +1560,7 @@ dependencies = [ "serde_yaml", "smallvec", "sys-locale", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", "tracing-appender", @@ -1625,6 +1682,22 @@ dependencies = [ "venial", ] +[[package]] +name = "bones_wgpu_renderer" +version = "0.4.0" +dependencies = [ + "bevy_tasks 0.11.3", + "bones_framework", + "bones_schema", + "bytemuck", + "crossbeam-channel", + "env_logger", + "image 0.24.9", + "pollster", + "wgpu 23.0.1", + "winit 0.30.9", +] + [[package]] name = "bounded-integer" version = "0.5.7" @@ -1654,9 +1727,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" dependencies = [ "bytemuck_derive", ] @@ -1690,6 +1763,32 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.6.0", + "log", + "polling 3.7.4", + "rustix", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cc" version = "1.1.18" @@ -2082,9 +2181,28 @@ checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] @@ -2095,6 +2213,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -2159,6 +2283,12 @@ dependencies = [ "phf", ] +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -2268,6 +2398,14 @@ dependencies = [ "bones_framework", ] +[[package]] +name = "demo_hello_world_wgpu" +version = "0.4.0" +dependencies = [ + "bones_framework", + "bones_wgpu_renderer", +] + [[package]] name = "demo_scripting" version = "0.4.0" @@ -2405,6 +2543,15 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.5", +] + [[package]] name = "dlopen2" version = "0.5.0" @@ -2431,6 +2578,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" + [[package]] name = "dtoa" version = "1.0.9" @@ -2590,7 +2743,7 @@ dependencies = [ "const_panic", "encase_derive", "glam 0.24.2", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -2654,6 +2807,29 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "epaint" version = "0.23.0" @@ -2702,12 +2878,12 @@ checksum = "a02a5d186d7bf1cb21f1f95e1a9cfa5c1f2dcd803a47aad454423ceec13525c5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2742,6 +2918,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.0", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "extended" version = "0.1.0" @@ -2869,7 +3060,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" dependencies = [ - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -2890,6 +3081,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -3296,6 +3493,17 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +[[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 = "glam" version = "0.24.2" @@ -3333,6 +3541,27 @@ 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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + [[package]] name = "governor" version = "0.7.0" @@ -3361,7 +3590,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" dependencies = [ "bitflags 1.3.2", - "gpu-alloc-types", + "gpu-alloc-types 0.2.0", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.6.0", + "gpu-alloc-types 0.3.0", ] [[package]] @@ -3373,6 +3612,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "gpu-allocator" version = "0.22.0" @@ -3381,11 +3629,23 @@ checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" dependencies = [ "backtrace", "log", - "thiserror 1.0.63", + "thiserror 1.0.69", "winapi", "windows 0.44.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 1.0.69", + "windows 0.58.0", +] + [[package]] name = "gpu-descriptor" version = "0.2.4" @@ -3393,10 +3653,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.6.0", - "gpu-descriptor-types", + "gpu-descriptor-types 0.1.2", "hashbrown 0.14.5", ] +[[package]] +name = "gpu-descriptor" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +dependencies = [ + "bitflags 2.6.0", + "gpu-descriptor-types 0.2.0", + "hashbrown 0.15.2", +] + [[package]] name = "gpu-descriptor-types" version = "0.1.2" @@ -3406,6 +3677,15 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "group" version = "0.13.0" @@ -3446,6 +3726,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -3472,6 +3762,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + [[package]] name = "hassle-rs" version = "0.10.0" @@ -3482,7 +3781,7 @@ dependencies = [ "com-rs", "libc", "libloading 0.7.4", - "thiserror 1.0.63", + "thiserror 1.0.69", "widestring", "winapi", ] @@ -3513,6 +3812,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -3552,7 +3857,7 @@ dependencies = [ "ipnet", "once_cell", "rand", - "thiserror 1.0.63", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -3577,7 +3882,7 @@ dependencies = [ "ipnet", "once_cell", "rand", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", "tinyvec", "tokio", @@ -3601,7 +3906,7 @@ dependencies = [ "rand", "resolv-conf", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -3873,10 +4178,12 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", + "exr", "gif", "jpeg-decoder", "num-traits", "png", + "qoi", "tiff", ] @@ -4176,7 +4483,7 @@ dependencies = [ "reqwest", "rustls 0.23.12", "surge-ping", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -4196,7 +4503,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "socket2 0.5.7", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -4214,7 +4521,7 @@ dependencies = [ "rustls 0.23.12", "rustls-platform-verifier", "slab", - "thiserror 1.0.63", + "thiserror 1.0.69", "tinyvec", "tracing", ] @@ -4324,7 +4631,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.63", + "thiserror 1.0.69", "walkdir", ] @@ -4339,7 +4646,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.63", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -4364,13 +4671,17 @@ name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -4385,6 +4696,23 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.5", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "kira" version = "0.9.5" @@ -4436,11 +4764,17 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" -version = "0.2.158" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "libloading" @@ -4642,7 +4976,7 @@ dependencies = [ "serde_bencode", "serde_bytes", "sha1_smol", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", ] @@ -4684,7 +5018,7 @@ checksum = "d8031297470465389c1349c399b927505d0cc4503be7a997c3541765bca82b4d" dependencies = [ "flume", "if-addrs", - "polling", + "polling 2.8.0", "socket2 0.5.7", ] @@ -4694,6 +5028,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + [[package]] name = "metal" version = "0.24.0" @@ -4709,12 +5052,27 @@ dependencies = [ ] [[package]] -name = "mime" -version = "0.3.17" +name = "metal" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-graphics-types", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4763,7 +5121,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -4775,7 +5133,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" dependencies = [ - "bit-set", + "bit-set 0.5.3", "bitflags 1.3.2", "codespan-reporting", "hexf-parse", @@ -4784,9 +5142,30 @@ dependencies = [ "num-traits", "pp-rs", "rustc-hash 1.1.0", - "spirv", + "spirv 0.2.0+1.5.4", "termcolor", - "thiserror 1.0.63", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "naga" +version = "23.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +dependencies = [ + "arrayvec", + "bit-set 0.8.0", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap 2.5.0", + "log", + "rustc-hash 1.1.0", + "spirv 0.3.0+sdk-1.3.268.0", + "termcolor", + "thiserror 1.0.69", "unicode-xid", ] @@ -4796,16 +5175,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be942a5c21c58b9b0bf4d9b99db3634ddb7a916f8e1d1d0b71820cc4150e56b" dependencies = [ - "bit-set", + "bit-set 0.5.3", "codespan-reporting", "data-encoding", "indexmap 1.9.3", - "naga", + "naga 0.12.3", "once_cell", "regex", "regex-syntax 0.6.29", "rustc-hash 1.1.0", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", "unicode-ident", ] @@ -4829,8 +5208,8 @@ dependencies = [ "jni-sys", "ndk-sys 0.4.1+23.1.7779620", "num_enum 0.5.11", - "raw-window-handle", - "thiserror 1.0.63", + "raw-window-handle 0.5.2", + "thiserror 1.0.69", ] [[package]] @@ -4844,7 +5223,22 @@ dependencies = [ "log", "ndk-sys 0.5.0+25.2.9519653", "num_enum 0.7.3", - "thiserror 1.0.63", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.6.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum 0.7.3", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", ] [[package]] @@ -4871,6 +5265,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + [[package]] name = "netdev" version = "0.31.0" @@ -4951,7 +5354,7 @@ dependencies = [ "anyhow", "byteorder", "paste", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -4965,7 +5368,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", ] @@ -5340,6 +5743,30 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + [[package]] name = "objc2-core-data" version = "0.2.2" @@ -5364,6 +5791,18 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation", +] + [[package]] name = "objc2-encode" version = "2.0.0-pre.2" @@ -5387,10 +5826,23 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.6.0", "block2 0.5.1", + "dispatch", "libc", "objc2 0.5.2", ] +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-metal" version = "0.2.2" @@ -5416,6 +5868,61 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -5468,9 +5975,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "once_map" @@ -5649,7 +6156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", - "thiserror 1.0.63", + "thiserror 1.0.69", "ucd-trie", ] @@ -5751,7 +6258,7 @@ dependencies = [ "gc-arena", "hashbrown 0.14.5", "rand", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -5816,7 +6323,7 @@ dependencies = [ "mainline", "self_cell 1.0.4", "simple-dns", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", "ureq", "wasm-bindgen", @@ -5923,6 +6430,27 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "poly1305" version = "0.8.0" @@ -6052,6 +6580,12 @@ dependencies = [ "ucd-parse", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -6160,6 +6694,15 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quanta" version = "0.12.3" @@ -6181,6 +6724,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.5" @@ -6194,7 +6746,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "socket2 0.5.7", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -6211,7 +6763,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "slab", - "thiserror 1.0.63", + "thiserror 1.0.69", "tinyvec", "tracing", ] @@ -6320,6 +6872,32 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "rcgen" version = "0.12.1" @@ -6386,7 +6964,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox 0.1.3", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -6578,7 +7156,7 @@ dependencies = [ "netlink-proto", "netlink-sys", "nix 0.26.4", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", ] @@ -6596,7 +7174,7 @@ dependencies = [ "netlink-proto", "netlink-sys", "nix 0.27.1", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", ] @@ -6638,15 +7216,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6820,6 +7398,19 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "sec1" version = "0.7.3" @@ -7093,6 +7684,31 @@ dependencies = [ "serde", ] +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.6.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner 0.31.6", + "xkeysym", +] + [[package]] name = "smol_str" version = "0.1.24" @@ -7156,6 +7772,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "spki" version = "0.7.3" @@ -7226,6 +7851,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "strsim" version = "0.11.1" @@ -7324,7 +7955,7 @@ dependencies = [ "pnet_packet", "rand", "socket2 0.5.7", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -7594,11 +8225,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.63", + "thiserror-impl 1.0.69", ] [[package]] @@ -7612,9 +8243,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -7684,6 +8315,31 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -7821,7 +8477,7 @@ dependencies = [ "http 1.1.0", "httparse", "js-sys", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tokio-tungstenite 0.21.0", "wasm-bindgen", @@ -7946,7 +8602,7 @@ checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "parking_lot", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", "tracing-subscriber", ] @@ -8098,7 +8754,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror 1.0.63", + "thiserror 1.0.69", "url", "utf-8", ] @@ -8117,7 +8773,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror 1.0.63", + "thiserror 1.0.69", "utf-8", ] @@ -8236,6 +8892,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.13" @@ -8244,9 +8906,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -8399,24 +9061,24 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -8425,9 +9087,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", @@ -8437,9 +9099,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8447,9 +9109,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -8460,9 +9122,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "watchable" @@ -8473,7 +9138,93 @@ dependencies = [ "event-listener 4.0.3", "futures-util", "parking_lot", - "thiserror 1.0.63", + "thiserror 1.0.69", +] + +[[package]] +name = "wayland-backend" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +dependencies = [ + "bitflags 2.6.0", + "rustix", + "wayland-backend", + "wayland-scanner 0.31.6", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.6.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" +dependencies = [ + "rustix", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner 0.31.6", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccaacc76703fefd6763022ac565b590fcade92202492381c95b2edfdf7d46b3" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner 0.31.6", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner 0.31.6", ] [[package]] @@ -8487,11 +9238,34 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[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", @@ -8519,7 +9293,7 @@ dependencies = [ "log", "ndk-context", "objc", - "raw-window-handle", + "raw-window-handle 0.5.2", "url", "web-sys", ] @@ -8549,18 +9323,43 @@ dependencies = [ "cfg-if", "js-sys", "log", - "naga", + "naga 0.12.3", "parking_lot", "profiling", - "raw-window-handle", + "raw-window-handle 0.5.2", "smallvec", "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", + "wgpu-core 0.16.1", + "wgpu-hal 0.16.2", + "wgpu-types 0.16.1", +] + +[[package]] +name = "wgpu" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga 23.1.0", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 23.0.1", + "wgpu-hal 23.0.1", + "wgpu-types 23.0.0", ] [[package]] @@ -8570,20 +9369,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" dependencies = [ "arrayvec", - "bit-vec", + "bit-vec 0.6.3", "bitflags 2.6.0", "codespan-reporting", "log", - "naga", + "naga 0.12.3", "parking_lot", "profiling", - "raw-window-handle", + "raw-window-handle 0.5.2", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "web-sys", - "wgpu-hal", - "wgpu-types", + "wgpu-hal 0.16.2", + "wgpu-types 0.16.1", +] + +[[package]] +name = "wgpu-core" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +dependencies = [ + "arrayvec", + "bit-vec 0.8.0", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap 2.5.0", + "log", + "naga 23.1.0", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal 23.0.1", + "wgpu-types 23.0.0", ] [[package]] @@ -8594,40 +9418,85 @@ checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448" dependencies = [ "android_system_properties", "arrayvec", - "ash", - "bit-set", + "ash 0.37.3+1.3.251", + "bit-set 0.5.3", "bitflags 2.6.0", "block", "core-graphics-types", "d3d12", "foreign-types 0.3.2", - "glow", - "gpu-alloc", - "gpu-allocator", - "gpu-descriptor", + "glow 0.12.3", + "gpu-alloc 0.5.4", + "gpu-allocator 0.22.0", + "gpu-descriptor 0.2.4", "hassle-rs", "js-sys", - "khronos-egl", + "khronos-egl 4.1.0", "libc", "libloading 0.8.5", "log", - "metal", - "naga", + "metal 0.24.0", + "naga 0.12.3", "objc", "parking_lot", "profiling", "range-alloc", - "raw-window-handle", + "raw-window-handle 0.5.2", "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", - "wgpu-types", + "wgpu-types 0.16.1", "winapi", ] +[[package]] +name = "wgpu-hal" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash 0.38.0+1.3.281", + "bit-set 0.8.0", + "bitflags 2.6.0", + "block", + "bytemuck", + "cfg_aliases 0.1.1", + "core-graphics-types", + "glow 0.14.2", + "glutin_wgl_sys", + "gpu-alloc 0.6.0", + "gpu-allocator 0.27.0", + "gpu-descriptor 0.3.1", + "js-sys", + "khronos-egl 6.0.0", + "libc", + "libloading 0.8.5", + "log", + "metal 0.29.0", + "naga 23.1.0", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle 0.6.2", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types 23.0.0", + "windows 0.58.0", + "windows-core 0.58.0", +] + [[package]] name = "wgpu-types" version = "0.16.1" @@ -8639,6 +9508,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wgpu-types" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +dependencies = [ + "bitflags 2.6.0", + "js-sys", + "web-sys", +] + [[package]] name = "widestring" version = "1.1.0" @@ -9076,7 +9956,7 @@ version = "0.28.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" dependencies = [ - "android-activity", + "android-activity 0.4.3", "bitflags 1.3.2", "cfg_aliases 0.1.1", "core-foundation 0.9.4", @@ -9091,15 +9971,67 @@ dependencies = [ "once_cell", "orbclient", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.5.2", "redox_syscall 0.3.5", "wasm-bindgen", - "wayland-scanner", + "wayland-scanner 0.29.5", "web-sys", "windows-sys 0.45.0", "x11-dl", ] +[[package]] +name = "winit" +version = "0.30.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" +dependencies = [ + "ahash", + "android-activity 0.6.0", + "atomic-waker", + "bitflags 2.6.0", + "block2 0.5.1", + "bytemuck", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk 0.9.0", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle 0.6.2", + "redox_syscall 0.4.1", + "rustix", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str 0.2.2", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "0.5.40" @@ -9169,7 +10101,11 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ + "as-raw-xcb-connection", "gethostname", + "libc", + "libloading 0.8.5", + "once_cell", "rustix", "x11rb-protocol", ] @@ -9193,10 +10129,35 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", ] +[[package]] +name = "xcursor" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.6.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "xml-rs" version = "0.8.21" @@ -9259,3 +10220,12 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/demos/hello_world_wgpu/Cargo.toml b/demos/hello_world_wgpu/Cargo.toml new file mode 100644 index 0000000000..38f0af244e --- /dev/null +++ b/demos/hello_world_wgpu/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "demo_hello_world_wgpu" +edition.workspace = true +version.workspace = true +license.workspace = true +publish = false + +[dependencies] +bones_framework = { path = "../../framework_crates/bones_framework" } +bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/hello_world_wgpu/assets/game.yaml b/demos/hello_world_wgpu/assets/game.yaml new file mode 100644 index 0000000000..b6ae04dc2e --- /dev/null +++ b/demos/hello_world_wgpu/assets/game.yaml @@ -0,0 +1,4 @@ +# This is our root asset file, which corresponds to our `GameMeta` struct. + +title: Assets Minimal +sprite: ./sprite/fractal.png \ No newline at end of file diff --git a/demos/hello_world_wgpu/assets/pack.yaml b/demos/hello_world_wgpu/assets/pack.yaml new file mode 100644 index 0000000000..b5c73f5291 --- /dev/null +++ b/demos/hello_world_wgpu/assets/pack.yaml @@ -0,0 +1,3 @@ +# This is the core asset pack file. It's only job is to specify +# the path to the root asset. +root: ./game.yaml diff --git a/demos/hello_world_wgpu/assets/sprite/fractal.png b/demos/hello_world_wgpu/assets/sprite/fractal.png new file mode 100644 index 0000000000000000000000000000000000000000..a96df3d53d32466f89c2e7454cfd0be54c16d396 GIT binary patch literal 34581 zcmXt91yEGo+rLXU2ugQLcS?76cXy|BBPHF^A|Wj$jigdbr?hl;^WAs8`Om#UoW28xKs~_sO{IWYrCc;pfA` zMdGv&@obDp4B^Gj5c_8b=xd|MZm`HQP5t32)DESXk#2T6j7teZ`@5T+j z%*rP;o%}Yz|LwpyVpzrnN91@pdibnaRw?Qy4LMJJ$ zYW+I|XBJz69rT|cA;QD%DO4%O65zZbyUOZ$000Zsf4>mlQW0hwb3LDRaFVB)JSsYfc?*mTPPElQ*I&tQ5qzxX>TY2}K%+cn(El73#HZl9k6u#C( zk^*g(IwS?I<&hr)%u4U3kN7YnHcCCZuYUYZ5?30-Xe6abql+W-F7wuz{}Of z=kF*e^I;9J=)pc+STjRIMn=(uvL#=OTO*5ze40W8}4zC4v z`PkfDUlIi0ejJT5rrCVeKe#1?toxu!T#x)z$2LAhn`PY%3UJ&!lYZ55SMckcGwB#N zi3>8Gh1*+2drpi#`rbR84ihpjAQ1zhBa}LIJ}2|cIem6+bfNS|&&jkgD$?FzHQvnx zU;)(A_(dIwkq6&H+PTj2zriB7f?1(-%gSZZHH#uT+>i&_1|LUjTFkIO?x*9+m;R7c zp$*_u*WtkH91Z%!7s77qSHojk=swqL-2DB^ki<8jHM100ADhymc*$Jxx*()q6Cwmk z^{LZyBgIe8^>vO#KH6#wah44RyR)F}u8y@pcq``}gs#Q=_OGDahFuCsXFDP+fAFK` zo+cJMngjLjE1E*_#?eXeM+|9rw?5})nREn=JqNaAiGm{?^=@M@!LWLh3`Z1vi8oGD zL+apLAI@HjdaGn<=IAa`qOWM565?#75$yeOR3AmrP4au-!Y?uWdG!O`P|$Y`@Nd zJv*5M8r?Ak#!QZ#SGy@Z&+=6#Zk4<46XP^djKD!G#mxJVw^JKM6!KWN&|9^D+m0~g zlZya^)DMXpWbOeyYk|TI-^Z3lPuH+uLSBFLKAM>1VeLcA(Z2XrLe}k*BgM)HfYwXL z*NClH^PO4K-~*O@409tL@GdnAxDG4BAKTRPFs0i`kzqx<_D}@IC~pC)4H91VXB|D} z9&mPUW?lbjipHOW?B?Jap-1 zo!Wh|-%Fp9T}c7?KAj}QfFWL|zqw}r@)vcjig(b3(RAF6T28S4&eIOM8aOYob^0em`QQ3cb1W_+K$%1;Q`KNk+0^tGiD9lOJh5|;H3ZSW#m&mxnYH28cF{#$#j z&sOv)f1as1+`O_yS=Wd=+P4?GDX#Qw=soaT#qbvyhk0gz?**7XLfAI}FtjRZQYo;& zgfnRkot zbYE;|))9cZS~vh)w}039JvzjHy<0Z7De&NKh3Duvyaa4t!SS>3XUgP+`LWlW+Aa9z zVLS2%*y?hmmsv^2&|X)^6a8`5dK@3E%nwr#F56NImiGK?wP?vA^XYyeo}fM}7Cj}+<#_B(~o->+1KUPULhB2R%4j1}~GKOoKYh->YVlkEQ1M5_# zS0O)1;)8}|iq3zZ6)BN$JzQ5i9@4V$1I9k&!ju)n43I@7!)aUpXl;}jE+RZ$lW`T7 zAh;8BTIq#N@coR<11}HhJr^=10QA;%ufj&*VJ@|-``?B4j|yIRyV*<0R;z-;mcCiL zd#KG7STnz1Oi>J-n{Jxl0ApR|4R=3W;7(d&AX)1}O1nWPyF*I7%o5U=X(_7RU&1Ze z>ZsnXaBRDI0Hy}WK%M=qocZ~o!QyspC^8JnKv&F>Iif@wK)C}}(;9{e_2#9+Tn6SZ zL1D|8t|~|4p>O$ZRl+^{T)|)~nu=B?ZDpNl5H^+%TSe~_?5p54TklG0%D(mfak5;5 z6>g6GK!ynsh3cu&iQ-r+ys%?FHV)OZzbqk`xQ<{Mo)0ixuERQljic9JoHqJ!^;9~s z3oZ~dx}gLOu$0Mu`1fTMea9QxUKGSXe=W<`@I4_>lpQ&{FDVfwAqe<%go3}qX&43; z@1(*Yn_BHW;eEpM&u>yKO>=8aP>;&w+5>9aDq%l^{#SFv>BA-goX*v^0Z5B{pki@!%!#Pair3O6X0#(ZmTdL^;eM}hWFD{#5io7d&hYP0k8u*!+# z@i(f?{L{oQ#0jJQO*>L9QR?(Zfq14Dv>#q_LrXd;`7ar$08^LYAu~IgA8u+$-CTRo zVX~{G4sbiahv&+g1qlxs?Va6ffM%`~LenBk+*Z^iPv2ct~kBTjgY>1IME+wB>18fzw!O=80!~> zB)SfbhT+RX2+P@kEQ620Sn5@EOK!EvI7IY4asb=|DTWd$E?*BEMJtU!9hIgmz{|JQ zD^q9{*pUS{!hnlv;ny*DOm^vLGzHyiYG!u(wrUA6wXChR3j82wPq2FIcPPUGWPMP} zI0&uPGYB(?%7|xAP>HfC@{ZJf9rh@`Wx1AlJ$+67c_v?Tn0(grS=Ca<;Mv{Twnu)eTSKJ9m(M6rcol8qn%1p%68F_869H>1Y} z_jwCC&L|2+3Mxd(_9dn+VlrL}vjLsPpNT%&*_HjVmDGmoa601hG}%{Z3&Zx1@KIlsCNViuCIi1)4fp2c4#(_{Dru;;-s( zvqyVV6~6eOy6On9m4`kClo8&$ba$hdj|wdw47(heJd6l7x!46^4A&8P&hst^4&l4V zY*w2zQ77_yAZwUSB?&ty6f~Vn3=SG_h9=Tstm5dq`V^0b_rk7z?%xw$J9BtxYvaJP zXvI&(FZW^kBanG9hydlkui%>Yw6LDUi}q@EH_EPO$s_jD8?HCVy}u1Qtop6iQMWdI zF*y^~j`Pqr=2(R0wa!lT!MYDbYqzgVIm<*SuO@4~EuOc}2bZ>%H*ow_jvsI55uv ztyym!i(*PxQfOg;n~1*UF_@52oU3Z4j3Hg^`dY6jAV5eZ;`$^uRcQ{a5U|ZV{XLN( zsv2_e;u#9^^;wVfpaNW_@aFjvPJ@a^Zy0?l`hBrezbiPT;MicrYu` zB?|649r;lob&<&T)fgbc8zAv|AAjHAo)~23j=^D+-3dBOiQ>q8-uOGWhS434=`uT! zDf?LFG9+Vsp5fbRp_W0EYv$pz8a$_p=_j~%40Shv0Y*_N@DMamvlKge^;ypS-bzKv zh4HiaeJimQELm zm#A0axmN4Ve`SGzIE#{9_;UYh=zmN0i`Z8h*ETKMJHDJrx)E0y{ab)Mnm`jPVT<98 z5dIo=!KkxjGY0xw8Dx(M;9AMtJP^kzhXR2=Q-JA81@@>pBOH^x}sT9?i8xJ0D<`bkW%-}9rTBAQ*01EO2f zCf%FhPW$RX0)D?~yBB$fno=jf$wm#~;yu#QIL@=#lbzo< z|HctZVjwmn4}AW=LNlm8!Y~+90G*s@KqNk!82(FV0nytqFL2*Io4o8y#WViR{6K9w zzbe?~B+6^T>oxYn4XmK0(aSAb)Xudn83-FTLFRP?93>vHRMMM#vIkV`FPa)hW$iO_7j*h7$4j zlBZWLlF8ro0^WG4hbwauGK1xahh29wTQ{Kdo=r>#|681ftDXP^@RZ6d#R+@GI`rr3 zM;Bl0IWW)*WXx6h0ipx@hq`Rd)6SB^CbUa;K@gAzI(XifFp-6Zl2Cl3VD(d;r`q`4 zx86mf2mwNVhnrr7m*TXF{n2+Ch7~rDOmgDI*{F%$3&fY)Z(u(kBSz2wTFqon&wrAdO5I|hwgEt3$AF1hZTU%L zO9Ie3!ulMju$5{Cu)&-9wx9you*&s}xDowCxc>2+@I7xsz=i>?@dQqfSGc|M^EMA6bD$X0xDk0nA!N;EuF*_UfYuySppW0DABz_h>`J)61eCap;PB zD6k>D=!#hSb3^P1PEI??JdJn$GV@I1TCgikP)<(<_{7L@;`!52jD9d)|acc zE#P2K>dVX3d5-&XId2~^_O7r5?fotEk=RBIDr^{;hSmEqA`m1qBo(+#MHQe6Ih?clN4#F{WOAM(^rt`95( zOS)|OY5s@!uI-v6t`GXN+7dnQL+@U#IRrLjl1kQ7?5sZ`y7$PFda8)AGN*6iAT@1{7(V-L zS!73#V-R0EB2Gr!bL$@ncQIFXsrGnlQA^G>cGZ%y|V(b@AMELfRwB%>cHk*-^c2R;Zqcp8=uIUMKR z!)d~6*@Y076a`<=fRwMyc&{+`R1qN~<5|W8hVt>vb6-27m!TvPrx?f$xf&QlenV3G ztd`%NtfGBRaO<$59~pmMS!?GswEnTXeHGRwy#9 z(idJb_IF z@rmJWU#Ib5=URd?X%wjC5qHOk>qW_q-8X+{u5<|>u0m=&!5VJasy z_$W6N9KC!Z{xq55x4j@q1oxCx=ea;Z2jmbZtGlLgXpT+M?r#0Ubp3EmkAD{tV?Vjz zpR+f2?Gw@)kD(JUyYKv3W6W68#Fzewn_6LnJJGmC8-nC*sEfcf4BQqx|G;`9723*# zV1Gxz(=I9uN78IUQ_QR=TtEFt8DLg0!jaQAPBi*%hIJPN%R7QGG&~*4vu!b6LRc>| z((`2i8{ob!JlKpaD{?Y%b;%R2kNbS^izp9Q9XYNU#V=bP_wy$;U(^S>hUeEeA+G=s zA2bf-2RC1gmEFVDBkH}DD)XNbEGJld28&PRiln%sq;H?XETpBLM-b+wkgIcbg<<~N z@Ba1^u7sFP2fSTr>HD)>5{ch&O@%jycf5wR^dx)tr2ph!}r^d9!ZzMRU%c($B)TMOD_uX$LYWm(-XP^6<~_Vpq9s z0VDMl9Nw;!0qixZ<>xZOA7yh0ptM)^gX&DlfZEH=xc~Jio*wLeGNROO$*|8jdd%S| z!yb~&IB%Y65S1HLbVf<%Va+rE*?ydam*E<~8u`q4FBaxXEz~30u&wwz7a9WZVHZgj zxj#6y>lwBuXbU>1c1j&T@tHW_3;j7eZeO_#L(%0p0|37dxqkxFg`+=>lGa`ydHf2> z;mf)x+`;qO|P2`vfQn086*7!+W)bWRa8t{kjBdim`!vph^)_QnRu4 zSUpDUz1+Bp-Deoul-gRr&RUm4Si=6&ExfjzeYl{)0HJuOCnSAM&Yl?d+BU) zv?HX+NM5c0xBjfbS>EL1+#N+w&I4&F*jgYVfdZ0YA_*!edvp*ou7^w!!X{4`Kj*gM zk%rg-8DW%r`Xn;;FHWwg``f+2sMNvCaOWU0iklW91s^}qE zBw}#)rrTQQ9r8Y?kuDx8oSqkz(FpFwN6^}}#;JDzbr1J-Z=Zj1vVFwIcF`S{xT;L< z?CKBkFqIjeP8Z(;@pueQ3Q5yha_kM1UV}CKsEjDAdh7!Nf!a#!j+Beh6nSZae7qM3 z%4san+Gff#&gX~&k)cPuQ1qK#P-Oyj7O==D?W7m)RDDRqjk1&VMWS^D( zFE2RVcL-6RIYIi7?>PYBdr5NWEz?_?n{S8M3{WV_vB|vUWbQY0Ok^_yVM2^Pk|nkc ztny&y4<3&bs|pZ1G;t+N@cI>I!70$SUs2%ZACSXkd37e-kfchB_-_}+^AO=!+I>&K zFud`t7MhOgO6%#I4wiz#rkN3Z8f4Mau6N*RQ$5=);T$D34=h?o42i1^`@PX%h-i^G zw&1$!56)vXr{|&R!X+~Bp8^gNZq_@lxGu;~nd#oW)QJEU)%_T}B45iliW5rven(%o z27UD3%6dAqu-Lge#xt7?y&Mri9_DC`;XEGEmNBobjVVY&8t8ni5g=I4Y(`x$fU&96 zfJj3&xBBI$(u&_(<6udGyTNduE{b;0&TI_XDoN5mz;^t=A?>YvFWKb$E)`qDeDoaW zJ|ec8Xg^%3>z5g!TyJmqI1v#LY8S#MT4UDVF@M*M2y!k^vBWb!1M)&a-o+XH-|l08 z*w=y)b*a{4;e_sN8pbqNwk`-N2+swVA|CbuZPG$$z!?e1+&kO1?aWulf9tjJ^Tcme zKc8okdOY`#Y*8OH~05v0fB$mlU|q zI*xHlsMk63u}Z7tjT0K+@#{u-qxz0* z^((~$V%&o2%}%q_5-V%q+>UNq)1AAQ!200mO4edM{YYXiNnC^1L&dMJX4~&-JN@aQ z`yKeTKk%=V(;}*aU)*GO`=?me6X%xz0XD5Uub`lQH&}ck zkONG8?A@Fwa4mhIN)oXuDLQTQv9yn!=l__OYLjs}$g{dYYpCn?Y5y+s%?bYYIDiAE zflAyZShwC?T8K9Jw<|-bqJk?t&pB`_o3Tz`xx4Fk_Ap+(L@g~LT%L8}Vn~aIe8@0n zHLyO1`ncETv(1XhV*lz014I?nx6a{Sf>zY%O~3`GdNji?PoW2Et!6Gnp7 zU$7f{dKunh*l{~$=nG*jbUK}5L4KgBD2tnXbK1r!QVy2annxgZ{%;V)$I0@mD(Veh5*Q>9Or$t_njoj zjwq%4Au5B>%yvqHhmtPKT~z^p%Z89n7;jtCXZi2N(Jmy|I+nTHK#cveXo8PD=i!2Q1b(>e>+h}N!USa=sa9AmhyBWO) zKx4a@#?W8LhO!lXn5Xo9*QvwDtiq{|ecKY;z;*O5zlM%86*tUbV|{gD_OoczcQMf( z_r&B5Tu?lveIGj5W~;bjPC$%PHM25>(E|!CcSIV6oBY0Q>Qh0mivxG=0W0xJAy2I_ zFR2c8OCD77e!;C=M}+=NGh4HAN@?AXj*YM9(CYAw(T&lbRCG`>o8G<4fs0^z(4yr3 zdKa`=K7#toH6);M6B7zGtgp|}sE?(|rDZrOdNwk5@N&t2)85CR2CN4MD@-tMJuFhRh$3FTVsTV+v@Styl z1ju?I5H#lqmgwrn_DXJ2n~so)IR9G1LIm-cZtguON!-kaY`8wG5@|=U9(kCF5DH+% zv$kBnzO}?5Z*oqiFIF+nYxGifuLsg+@Qur8n}j`9Nf&Kgcoi<%EjJO`5Gi|A7?$NC zS!)_X^FAI){yS=&EEm0-Rka-1X6m;|M=&G`YiKD1Ya_Tpz`~!=VJm2$|=za&K zV2~z&E{r5w-V08!%NfcPEP@|g8PeJmr+DS+JV?fKk8)yS!Irg{PNe#&SXVX}64tlzg4|9wqPIH)KYwVcG=Qq%To+*t zVX)w>hX^UU*dQ|ul2k0{B)VOmuFf_;qmlxx*qWgb>(52i+9&>0%WGg z{F6YXGoLL?rZW24uT)_bzX#a{B?W2Bv)RFeIA?sGC0Nnt3pV$ZuQfL7`AaB^sbQ8x zX0t`C4$mXwk=9}2H4^0}Fd4|&D575n#Nn>Q_Z$fym=Cw)xt-vi`97u;cE2i%6q}?e z;`=i-?kxU?Tr%P{8=!i!qmIR0%+!TmI3UIhgG61PSC2hSW@f1v^Z85JhTK^F;~esm zCoZ?U_N@l2)C8gyS{vsEiKGXIE6U)VpCgLDVEt@9KpmqHSO&otTS%gG_YDa-G1U3& z%9h2FTs~y`<8D!2vykzAw}rhj zbA2HTfl7xT3=8;Kr#xY6U8EH5vqfU0B)v)pQuVEe*JRS$XUpKTCO=-^cD-v+Lr@_k zA*cLIp#50=k*DigVwG_K5!%x5IRT0hMkD%FQ)ww=#eedqNCHihP+dgBTq`q2CBmwj z{DSJe57h;>+KGM+hBLvhlIXp2F-pRxYsSyxh<&N7k;2!GZ8q&D&3DE^+oe+e!|vfV zUO4wWcb!nU;&+)Xxy;lNFbp#VUm4(*z0KO7syyD*;v!-#Iq6jTe*%0X;^u(bk^Bz<~GQgoaNKEyM7u6IlT5((qywmoBKY+!< zH@93qy8Wi;=g@yJ9D()9!8b^2ONNI>)G#1CK!Lap3 zryHuL<81Dx1JdMPI>r99sHn^>&CTDl2~aOQOL12TG~6Rm*=_ZQiuo+VE|59mrQW92 zJx$UN&UgN;+Lf-k4#FUpJ`>Z)tuWy5%-F)i`Jkwo{8m0Av3%yIy|P3GvhCyKURk&l z<&_2dm$(1!>6!dv-_82V54?i5?ofAgRbm{UXqD1R@CU-trBMQ*Ux3K`l&9R;`r^xk z7gA|e_Z3XM#nuJ1Li3EwJcf5XIC#^YSLLAlc3N_+uW0ZP=bN_~wTO}Hlq2`1Md^Gc ztZftFc|E&o_mqQEz8)Nff1(^A8Q`g$-|99%jn=&bIx};u|GaS7cmN=c)6^bvk~Fd~ z2-{OXWI(7*eZ*+f5rf5Tzf}ZRiq`+Kcd^owizmM2l-&{}(AU7FJ92tI_-PVz5P^*+ z#0r_qxPYE^V|yBMJC#-sxw`%(xSXQh;=({sOSR^ch6D2o5|6i-L@?uVmce*;1BaX)nQv+eNuG~K{_K2aLLZxI>{BPk<3R*k@%s{U_uss5u?Ni4fnz*7 zF;YwhT+sFJm3=(9QKD#_cw)6aP=U~F4ISrl_*j;Gy9@V;1Q}hP!pGE@f`Ae2EH2&L zk0m9aN@;L|KcD{ty@UN9JaHmA-M-ksA7as;HCfzsbi~)annonV51xC^g32WKP9O&Q zuX<$#T10s6J;@$-&o3UMmMtEMi*6ii`ukgdp(N7eyf?%^H{FLjS~CI@08_s+qKrV{ zU~hf>C=7oJsYQKHp9bFh(Dd$3f<>f`K^}Aya8rLJm^|9wU9}kMhOge6YMvsL1V_Ev zpoEla*y;~p&_2q~?L`mL=>e{)%@< znu15QtcVz{8tZVd%h8<29*LIj_VlL9J1%}+8mR6OKGygdlcD9fDMenwL?v=oLitOR z>H)z2Y!Sf!_I0|`_e)dumi1uVf6g5YkPuQ9Sd8f!%8D{L!LK`Q+uno6n(JapkKWFd zcLeG>T!?iuk{JkuIx`xWT(y z{Y7$L8yv%8MWn#<&k5%NZ&8qYa*_iYIqu%QhQoHAzs9r56k`IHbq*~}Jr1b*1>qvIkqN)CvU>j@)snqp;kQ!eSU$9Z!N(+dAh+dF$7Jr9 zSn5Ur`xZ6DcAQ1C>-2w1mUO!3g$<4(-Cr@}GpzuZgUI z%mBCzxxf^nWeYpF&(taB&SuPbKKV_CyD&es-veeyiRSq727LHMYh6|G6{3rnDFR(^ z!Cw<25|M6T#YBvH%7pxAi?cHr1kzpXUBAz6Z1DMeewI_5+Z>VdZrZ5OUZ9IXHwRpg zav{Lo+1S=ubQG9^SzS#QLx6d&0j#u0U=>fh6%lB)etpr7L1utvHocs4q1QVcMvE)1 zm|2*Bi3ih?6UquGC2<|74Z1uJHvziKqmN@G?(Y)sreq4WJ!(u0knq1ne@Z<1ch2P=0fZ&zJuaKub*hBEhLHPwB4X#>1cwim^1((Yn-#!BO`m_ zEn=LSLLLd9-1u7q8Zv9Q)mPy7&CmmuulIX<%ikea1<2ggTVBmywQLwUM?R*KeOc`{ z>Be>U!%;=h5KKhmNx?I9NVyV==9WYa7b&KfxzH5?Oj+?mu;71*CSCi14DW3;ems1( zKKVT(c|Ey@4DMrk-6R^U(x|)fDO_sksXyPgy&~I)@05(VB8eU1Z{o?dttK9W1PASd z+Q%z=yP_QU2f2HF?-}=(R~|ZqgEvC)tchyM-wTFZQMN|5KH4uFSzlicP_HA1BVSEV zwyUG;8ALsbBHpsW!2#n+F(3m9CM<=~D={ouw3jr^%Aj?(>Cv6oR#^m%8@<(w#?^Pu zy2*P_(#7Ww5k9DC{MZuvTzwbtK%@%1Z{Vi@b6s{@}KH z1x$#|k<3=Q-p6~{|8!JiOOyT}jCEUW5Ov>Y{O-!ap*TY*o9}?|HI8V^7JIXwo({k# zk$Q5gtdhV;^L^IT1Jfa`p;lcD(ExdyUWQhcU5`YUKZkKNF?8_{=x%rgi6TL!*YGS> zoY&GGPjL`=q6J>8sDI4o#?RAid0ks21{;W*U~0JGttMA+S#;*J(u4+1@95`vGyPG{ zZ^8ZXYcbm8c_#P6{wI<;%wzjwtDrMTLrd#{Uq(JQ*^C)utIswxi+aT~^IF6b~E%W zBeM0p>i4NJw<|sVYpDX=Wt)bb!9x{mPg4)yi{gT_hd-0lWd_Z5m>V43Br3mqG{g;G zb)M-o;(|WnmpJE~a3Lx}Tv?S$5wq_OueEg$v>+!;-qH%G8kEVL+yf(R-2>FF?6jaH z6Qm-q9rDgR?42YdL<3dvg>Fv~hF6l9oC};w4ZFc>_SIm8gDZ`fE4^g{k?Z{XF5U+P zgCjW4V+@7tLdh7gV?Wf+2l|cLlX<_2#DK*%x6I+sD93voR-hZcLY{U{uvZ2zf&&+h zh(-!kN_?Hti&%S`YI=Pc-(q^ciw%2CV?5!D>J?cmn;e@4ct20chvGL}&1-izS zjGiB326ID!WSfsBHA9{oARri#yqWKwN+79QbHmvZvA*9Oj>IhRabEkK_%!KH{o6lKzAKBD}zQCR~cY`!iNOjLwd%WB$$z;bPfdM%0{ohh3~79S19NZZfkF#yK_o~J*aZM&*pBJpKs@U0>gPP_j^_X0S4p%Nl%7*2r-WMS!KSrT1=4qX`3TM z85WK{y%e0NC=Oqxt7uwc->1hl;nUhB)kKmW{ODc*O1n4}B`BVjU#vhg+YD7)P+KF! z2El|9!<5MK)|z}B*R3#6Z}D36?u-r=q^6(&$fuV+9+3pY5YPb1i4qgrKU_QJQ`(h- zHV%zPA=7bJYr?k`9N)-QxG%s61sLZ7#qh3tlAoT7_3|1Y#vqHzPE29w&M_%FXlmy% z84Du&>glmU?<*|R0a|A_@@#sxN+A(up{fv!+{*6UsTFRuhM)3i>$gF~IuwyzZLV+F zLuu77m)smU%U0!CH^Ha?7vk49Y!Terkyjty2w#gwJKho_qip4Wx%>QaAJ1K;`B5sI zxD-xRKT`~O7)LY)wsBv)C4-S&EbWF;Tp1 zNb_C1pirCPu1Ufq1_57$1EozzV0D$H~Xc`Gae2M46~xV45LCyN)|r8 z>S6DS#S;=)f0WbRN#Gq=hor^~J(|~Lu=B{@{79>WlXio3YN75xRlvumu?u~ApIiU) zNRRz!)Hf?$j)#8cKD1&1l>eXZX2!@kh($~sn+Qv1*QpF48&^BucRs%l-b&rDGOEB7 zT^{fyG0FL57E!$Kmq~}V)Q=CUzVXP^-{EYBFyM(8O!SYZoWEe{<>ETxUs5m%+HY)H zx5Z(alsYT@K7dq^6eNmM&}v4ROC`-PP1X?G`e?8HDb`SaL_Q$E)aQxgGS;f76$TV4 zDhwmhmcGy4{aelM5uK29{s4VVR7v113s4U7A0~@(>zZn6=;u zjlu@$`XWf>D@`vXv#GU$F789534xvzc7(X{aDyn&m=JlC219Ex_f}zlp8PmQrSpNQ zHg7MCo>6RXrpvU9qs=VXHfUyZo@Vj-l3n3AT=jVAg_FIr8Ss0=)8C@lS^u~4_0Tv09i16Lgt7oJwgRsv%F5%5ny zF9+doZfJi%8XtLk*YgWc=`i-kd&AG1X{1`Z^Zoc~*12+sda;HQ6&;Y@9favp%L)v{ zs^@t7m$F!SP5M*vpURpN{ETbA^*Ad*GqtR=y7T^|t?>K7GrYs0B!p{0<96N#&n-|A zg46$yV{&y{jw4zLm&ONhDg=r)Rn#&d+Lwl=p>fejex{YJOmFN^MZUcwu^J(;j}%7t zd6yQaTXdzEz*WvIDk(U`z>I@WOStgn;4i@-8o)ebBNon2YR(wvnsy2U5jA_sZljm^?f*qMc}7A!>qqi(jLaqfT!CRh!i) zdv{H|^%XER#v4CIt(XP?(j_vXM{ZD&{_-9Gt=LcYl$u#=CPO?rP0j~UJ7^7@^$tU7q?o4h#2{-Bs-Z?sxH!pB^DuC~t-8 zyu5B4sTQtHW8{Yhgi*u630s@6l+MaL1o^H|3yY^C59s=hmKKsH24qVCG};REiIP&x zyt&E+Dh%|m4Jksr8IiW-gTK3ra!oeS(hjE-X$zM#`KhzY`#soe{c6&W%}gs4K~1hr zMkcgV{jNrRNI9WQA^Sd5{awsg_At%E`*#hC)dJYOc_r9cGD74E2%B`}XJ+1Uqoz3U zuKQTk86S+a_I69sYjW-f0;?5OeUib2P*;_sT{P8TNYgHxfjm{YuUDm4U{bXGM_;=> z2MIt4f6*NLAjeP60H-ehmYxRvYZ{ihN0Z_(h3I@rT9SR1-n=aoTFY?VR1jm7F2(&G zjv1nHeFCGG=ejOj5LltA!ajtfYu)k{S~~Kj3ELDmDk-BiNqt-wzUc>)qG4O@f~%2w zOE$K!vMH6Hsj<02GB;0W&g{UeLkQb1$N2(Bno$4tTc=!(E!=gY_ zrroqDyOh*}F8&;KU^Ji~!V>5)`KyX2P^+n#V}c5&G9}b-qx9yb$-nZ>%bvWaDm?@R zF%8M}y=X0s6)gkH)F=No!H?e1GPO^Y{5}ZakpQy_HX(QkPpWEVwwy?pn?Sq~yRaQ*CgI{<{GLe^6D5xq>ABJ)HccB0 z3hMW4Z9{I4^`pCnD?bP1lnRE<@KHQl^#^}f2h9I8cJcL5w)iZ`Saxe z9#Tmt?#W0Xgr*vIRGS7u?1KPA;LtaYu2!PJ=V0<> zRx%LSTZKt;e4;fOR@Tb3xUPAqT;kQC5Y>9L`7AH0&&^!@T@CN`2T>mI=Q_X1dv?1d z>4{sBxH?`aH!?}s)nveLkn?GeZ~sWNHW@PljXuh%wxcriYpIB`_JUnib{;D4SCYyp zg|KX~djhzcKO;cm>?fJMe& z!b(+9t){IiH-mU$7%X7d2tKPU#yjg}CI-gGuQ|bU7s|1|57TDW2|MxbwvDcq(Lr|L z>U7bS2a*my*LqQ{S%LY-DVrYO!iH$JkykGc^Mu*6U{N#z8<-G~B}h=2BX^)3J9VbVK%h{2&MZ{`fv%re2U6M%ld^Q@4v_jysDIIKq&T^=MhdQ6YC` z!UI!&8LEx3Cps^WkdXXm(M_TRws0HyQ}kBNQXn?uyssUWfoS_w*oCqAchF@eT#^LpXB8?^w{n*^pvVTSKSu<7lH<~zS^j{qe|t6>^-)Cti_-) z1v^PUNS+^(E~l=ATv)&Olp;U;$}zRrQ-ONtn8AfbFTtDho4DnMZ7hM$tlMh z%aJsDBsM*b9igrOBXRlT%QZy51~X3{@?Xf!q5$JP32qpfqcL918hLQy zc_BS?#aC@wv8i&?L?|0PGnMt01gcW*<;oTGr@$FQcFV1y37YwKj}Wz5i)HI#CA zR8ZvbSpAmX(!m4m%8H+7P!P@WMA1r`DGv`PF!}+#a5H}ka>78tmJ_+^oZ)o=NzesAT&xN`zwctc3G}(w|6tfzAJte_l1S@Ra2+jg3rKoQ>nZ?g#A5? z6A4)qXe}hE>MZuagw*Jkx)E7@$k(Cc_K+(Sjpgyu(H-d(V{mFMRQuGPBkLDu z3s00r13Df z#H(S!HdqJpq_H3D4Kej)y@7&hQQU#jV7eEPuk=y#({B|bx{>w#H`xt5m!SLWv2N*o zPdp%^FMJsp6cDU;wI)EH6>u3Bq_?6VXniq>viar&w8#PSk1NQ4JfelUJ6FSZCr^>Y zfF#NKKm7%E1=+n>BxHLZvD7)PMdJ@FR#cc@OvtBKeG6gr7z0HZqxyw= z(=_Q`KB z9wN7#kM7fYXBXI}pv5zJXIX%YH$VDIRNW>_0R1a5xqOf4#SLgeE4m`g5EE@3uWNjX zeNexC1ed#IH96&s9pmtlp&Zg^pg3G5(x~^?=0Ek&P|xDT+aI<06py8p62GIqa9_pA ziRm{vo7XFA`#mil6~}@x%Ed;^juXHY0JtuwwK1kk#hzL#z|-9eys(REwCwARJeOd+ zVGFZyyJao;Z&kh#w zP3qr(2Z4-WxQ(t4zT3mwA(A=4LwfqvbhI#R-*Zw+)p0M1s3TcpNSws03R^kt~Bbd7(dEdd>cq1?Z16H5n`B3P55Yp!VV*#{s#>k&j42U=(rJ!~BKbo#GsH(1Q??ZQ&G)PHz zcL-7v(kO=xk?uUCbV#E}cXxLqEg%ij-AKc?pLgb);TJLNv+7>=ReOO&Pih%TPhxY0 z&Y?3%6=*>egl~>mg;Npz9g=m~UDW(>m05E{v0}c+B2K}G-qji39+VzubsjXQ*eh+^ z5m#QGdC_M&TOCJ{vjA1{2>YJrT3xct?nAD~ADBlH1&dcqPtNs+Q5g0a!UWZ(0IT8V zl>AWtQ!^i-MfM&Pfn%2(^|03;O7(ZOel_h!rMj^cr6RCt)esrk&F!TKFMH(CobO1< z0^b#oNV0?sYPCVJ z?2p1NkO|1FsMfIzoUYGio^Q#62&v5?uwk|`%LIUy5Nj)pG*B4`nnLG&MU<$x2o`1= z?(Y=;pnO3bJLCVxJzia%l+UQ^=`L)5Lq3{`2!G z9T6b%%?JXNTG0+FX?=3dC6IXQYg6{fT@e(7Cf&pFKZQXT6A#YsAz8jg3)Nh9pNJb= zdLsTZE2|ZDgGT%*@^9NP1=PKlRV^W=R?cFNzC3@eo*D$jH%$4#HX{wObQpG=^^%SX z>ru_Gzk^aW`=Ie*%NS`2STQ2dH9xSK;Huj3Bftsi$_;>Rc*s>aa~1R**5*c;s?Qzl zM!w_p2;Lof*>aWrL@8~Z)%ELV!twV$STu(g1y7; z`GH9Gu9^tGhI38Lw(;Tj3;6y|G48YIg`}WkPr$}~s7lGQhaPjmaXc&Rl4CN}PNnSKpJt-#ctV{Kw82wZ_#?QTR zaV!)V^slN*wAD^9NS?G3VJ+BM`3=eZbybx*zWWB2S3PW_0AdrS69B)$4BQ@{|gvs^7ow0w1X#2MaRc$P>7I)EL1>qs@F%=R#fgUS5 zO11y&(*0&~4q0@6f@OV;jh$i6RsULG9NJoEYz|;*MV>m28)6vZW%Q%yetC_ zur&c&h0_$>%gCy%vg4F*4{%N8zSC0-pE6REP79jy?dZva2xN~+ zeWci9?Gev%<@FrkuK81F(xm_WZmasf@zYhxLQVcpfu`;@98z(8TM!EMG+!8>B}Qsb zYhUd)5)O;cf{){4X1}E6|Aw`Gd$$o>ZWX~RfAijT*?GkD-w>%Bh|xu`nJMXyQ7jxV zsJSK5B;BhV>WVT!BsB>{-QN}se&Z^Ara?k8$5Nb~((orOrzWW2PQP+ArhF&wt-OL# z+yd@fFKVuJLl4XCn*3eN1bhtIRybWJ(=R(!9a)F*oD4gh0(nkeRO5U&m~iKAwIQHaaIr@za@R`Tng~0a@BPgszmk`$IOcbaG&xuiVmW#@Ru=-9v{vqgZojO`vsf9?vv&DHXQ()oWPb!tIU!R zv3X;G86MyTa0*8Nt|9MU6PupJKN^Tp30yilbA1N2z@RBL0*^_VbO6f}G_zv0AGvzr z=hN+tVE|A-GQw{ICpWTq%Y1xOIAaI@&V<(JDiu?opa+~U(K+%JiP;B<0hcE`wvu%Y z`~~O&gBDyhyqg@HAd&eodJSm0IM_|2-ecvl_>g_8(}SP$0;)RNLeNiCp*S)G`Prxd zJp|(FsuMg%fi*2$EW<{ z(Y7ow4+X5P77ifvSuc(gI^kNBhmyEFcBevWmHpMqPRP&hWl?fMV`VC31ay~nyLDb^ z^s`^fY9HXR*Z?2OhE2!8(P(@fFKox9A1QmE)0c!9kh`wjhLY=l1}8azZ~TDCO_S(k zQGf&tVWX;k;zHGy$Q0F%Wf$CJ13BgyKvOi4I}lIu7s4?u(=ZEUX=4Lq9pPZ6kU|!s zG$^Jb%wyIHQ3@64TKGo#+`e`&LL#cRgJ_C03=4;gZsh+@W8SsBG0!AO9 zw(uPxTsfM4BgWUx!{$4g(9PXQW-rM(nhHEb+7cem(S`eXghT*eHv|`kr=0(4A?wpl z6(>3oK;-v!;Q38HvqK6!;0PZTlhdtVbMF}yu#PXQJvn^*Jg~%FgE09gXe_Slkjk?^ zvYRQIoI0L9Y8s_FOL%?rE&D*KvZxb`gmc+5w?9*}F>o(Qbf@fhK({vcqT*Y5E)kn| z8TIc=jcSRC-{Zt$8O)~SO)Tn5eBm+gDRKWRA+ zg?T%1tjNi1oB&J`R<86#pDwySjU(!i{W(eKJfmy!*J{@wKIj_%AzJ%m(1$KQ<&KGb zUN&0ClanUI;tgW|IWq!IGsIlg0bX%f5kQ*_`|SBv4s`~C(asvTY@sl(6PVmqHiHRX zh%)@u0!k5?cA=fmRbH_jKwdFv{N<3?yby_ zdlW*rbuAhQAZde0192f@Vj%35Dj;&;W0<5Q6QNu7j#h*<1cu>pdVs4dq78>r_m>4@ z>~JwRqalwZg+Rfz;GZi!gZr{ED-OL!p)x7fbFb*P63r?xa(aN|SKWRzW@S?8Oj7Fy zkDTk*s2PZ8uc1~ua>C?ku-L~pWGYCmCZp)Db`;=yAl5DlS5tK|hAGRMsG%Xi%3=mF&kjO`7Jh3+X`lAx9_Lmw4InDRTw*w?|K?G@hFA5UFKW?6e?bBV|H>dZ|aw=+c{>`ec6k!IP2?i zYlmD;21z8Hk`1&6p_v^WoHMob=@Vm zrrz$_3U|7(Q2Pr@1vD67W?iW+*{onKswP+bhwX`afDnzKRuc%`^7K!46WJwsJIHs% ztao8yfSEy94K4iZ9o+lrDKV{KYIC?L)LIfg$qT$Kv8k+_YIacg%6+vw@${bt--HfG zyPPWFHYVbo>tQCDWC%1 z7VDL|1N1m$|DkS;3LPtL4aYsDjCkx*FfK8rf0SPoX^YIVB3k zK?7exgquBX9|?lPLJz0VUZ9kR4E3_ju$W=J#{M~srp4y!8#pfF$;K4eBg`xaj8JAFq!;Mp45H=x2dZl8Pjk`|&_5sD}- zbtjDvS$0~VsOFX&S0Jpva>a5|8rbLBaaj)&FOqr~9=wX zm}6WdT{vWEpW zr)pKW)H&Zd$nIrL+v9Lrgk=kxfe)cr3XU9dokRC2q`wzY)e`Ws;GOfFmVPJYFxrne z0q{#G4AI9NDZCiWf_)|MX9_9^Ux?qyyj#R~P&frc3F6zum4)zY!lxc(n_kZ2e67bA z$0m83Zkg91{0i_U#Nk$IrT;-8#uu{9C(*MIy~CWiBB+8GwpwE3fYmTbQJDWCM2!{} z0Sdwb(gneMxtJjPqzY|<%yztFJbVQ>5~VA(vxGgyDfpUO22_NWI#qgsLEf!+Q$-&A zGf8#f={21%7))abK0g5qoT7Xv3 zlY@OeQv}M>r5pfw9LiVBsn}}KOMY_QQi6D$b>boavqzbdIj`&rDWX@-vW5j_J`(;i zNCZ?|LXHZH$XjIvUz?!MQOBsnHkAm&!Yq0~B?kaqA{eN}okWT}vbl8m&;0(Cx9A-q z4%S}gqsd4$j7L`qM0`!Zwjq6fHR9?TLi;b<6kpqz;6f&jDo_}I?>GW`dOK_~`-P5= z@j^xm%xrjUdR4j<9YeeM%TxzY7C-{J2(UHV9JBzQitdg$P;xrsWFT!%jD)I4yzlbH zGHXAmc;JTN?aMlwGO5$%TX`iUKmnQj4vnpm-tZR>5o2Nd!#Q_lPafDhnSKXvcgz-; zZ^0n7Gb5O=-!g)lWmQtujpU33)P(@j)j=Jp$q2$#M_Qcoinm=xv~yr@2TJ0}coM`B z9$oB4qfTWy?}k`bdDd8u$jq9;)E zCq*e0RMS5;?T=nwe|(|$yd=H^g>D#abG5*(cRT6P+`K%-zkZhXj= zq8f>jYGhze5^#)aH3Q$w<8Yp;wj3&tJ>0@oPs!wxHb$d=6>;!a4Lfcx?gJ_gVxWZ> zk<^Ei73C1>i`pbOquZZ#)70{1U=W)0rp{}$zhIJNu_VbvXiuOy!ZGON0v+)-d|!h@ zN%B<*^hhVy6Ltn>H?wny$LGgT&RZ;I2#WGdhYY4{KU^S`I01A|>|pUx|4Z!X9X9AM z$A%zAD!QdWmJUx+nlz@fZnyb)f5XTO3)AGyg4M2+EIb}ZwTmp<%&>a-6TE5e432>RE8bnj)uuROOXAADGH4!a`mHUHo+4BYyV68fDGg@#Et(4fv_g= zdxG03uU2x?8$VotHF++_B{h47NcuIJBGe(Mxq7}%SRHvAbTKbC;WpI_tIKvI6@RQZ z)Unmiu+oiZvWNm5viiG%hB;b=nI=A-LFIk77jL~$*>Z$4*0D*x&__xL{IiUm`c%U) zv~k3+h+yJ#XEF!2`QRK|3DU=kfR5_wTj!ITyfaK=FFTZ{B=iKN+*~G`i5r*vs%uY| z%u3FR+FOrH=~P9YeKB#{a`J7o#fQCc_5Z|LT4D|V274Gg@XuWG9o~`vQ4|+`dq(4` zSA2E)(LUBCB|>>?4TvMJI5`NcU}rhjY8%(#0r%j2e9M6U$L~;HYGrDxgr4+Iw+IWn zVMrT2qXQ0va0)9xJR<;vcg`Q9d;T!Z-@*tJl(d3!5`DKrF8Y~@J!B50@-ETz8!o;O^`ad%DUE^>rk(0^T$60a+`PbKUc zIED%)7D+3Kc`1Zo^{ISOPYbyx0bv~?HPzM>hcD0(XLzF>be&>BZsZ(j$ty?<&p`!Y| z_So8TJ~?ecOo;JmXxL@ZCEYGLPr1h2x#pwBx9M+_7hDkJ&8&GOTUN7$ZubDqul5=2A#ckogXWEhsGV6-4U&x4brXz@sY4O@=w@6axQ3#2 zO6Frb*Z`nJEVf-1r#-6eQi>d*6AY`LB@SS6`}P!ISKut=iUgrbU~?hjlG~D8rD* zaBnz`n19R}V-(Vkbgk~B&XC@7jV)xf>7*d7VgH4ey`QULM-b{yUH7tk-k{Q7uW{Lx z6u7Z2x;s#%AW5Q}VVhAyd;)&VZDOA=fx&c)ZyH|H4Bek4HM2q?n}2SaN5=w(IktPZ zUB1#{*oqG~iYfZVlj2H-zq6PrBV^Be7J8eSFjSkWer!0j z?8xGA?5Vx4Rd?~5mKU3T6FX+(eiF^Fmbm(yJ($2xQ%m?2!q-htr_gU5++E?V{1-j$ zeHtg%Y=_7<@@jBWC0?X#H#`#3Ma&+T3iEqXIWq__^0_f4Q{s97)TJSgkzj<4O2S^a zL=2B0T}}cM7?w;BVFXudGPM8}YiyX|{q{kpwJQ>k{-=OBq`@X?xSd)EY0)n7r(anh z1YpCk>H=Q8a&y-gnzNyyG5UR={216Z4Ij6k<|2?P27d3*m&~}bkjAt1UV13Z>hMrw~vtx7Z z)34y~gex@gp)NX7hUVnwbWKpG*0&@euWNWCJt#zlHM_~^ZX(_swzbgX%y7J zIi88z;Cq8B`{twEi4(hZ5x;v4P7Kb6Mu%zd-a10I)2=J56xl@Wk#+K)s@Y7|jAHI7LzbBcDqH;SQ*&1-ky-84GqFFMo+AO| zXd*&M#vDZekopQCe)n{iS=)95FSTFx0=bJ~V_H4IuZ0vHNG=OuL;Y1-O^!6*@j)!% zhfO5YcFOi7iem5P(btcAs%Xho3eWt!oL$zuTWa0iFEOn>3d~%$xvDSY??$jTxydF9 zJYEvEtqZd)eO8Pxa>aYBA%>X=sHpDPNbir11N2p-ZdTr4VDm!jo7nA80=swpxx8%O z!?2yWc@1qdEBo}bUP-3Ww6!Ig5Vpa6XodHR2%|(l}=YJ+PQesq<{ypFQ@(|Rl z9wtFS{ddlJRY_3>HN>vdu27B)Y5wG`xnK`)fk=aaVgGE64LB?X)f;!2NRlB@AF`XtX~xqX}5*w2kvoI~;yMAGM)s8Oc<-%MdqurZElg`gbE z!vzA%K>#SZ-<@hV^GvItf>@*cXly1xhTma-5{^IHH(1VmXx%#h+WzA!IE`n6nfszb zNpIi*pv6nc#sL3wI$Sh6QnQoBc}Su+-Yh_U6L`=MX!}?r*>^yxUku3ccIIWW#a>i> zbU)N@hp{(LMhZWiBMOceL_@ZU=-$V^eRLp6V!GMO>g}b>$Tzysuz`~#L$yM)d%9|6 z^(FgRaE&?8iI({LYjs$_N~V62#XGbfq+;Fn*NINDsTSS5>Ouxa+^M#56?C#8GLJo8 z*=^}V*@!HFEpFBi2V%mDa$PHyfvnUWfu^dvA0ys&ke7?P`XXgM<_8OCIb(7_U=%EyKY6L{EU`e*$N$FgC;IkRuC-Q0}=*-VYk(VbH~F1a#d zQORq`qO>{nEj1kIdvDb_EP~Gv89u*pU9hVmhG4iXTqGmet74$1MzZu}Rk#0uAu-vH zaq}j-^KG#=p_#(@hQwaF^U&>lp7Yh~lt^YfXp^Y6>)VbAB6kTD2FRyNVR7eCyAbQ7M&kjewWxB$8T-+JHFt%`9T3cU#Y-S z8-GaBD@z^yv#n~r8eg3}&o5~H=!!^f9kU>&N@6*378U@W;3B7eHIm}yKku9^0M3QT z>Aa9<;Yf%N+6=4mP1>TL(17(F9F|x+wr!1u1i%YDQ#E|Ni_EX|83}l!<8B0JFOChn z>j4gIxmcm{it60VH5G@*fDJ2ub$K)-k^e|#1DZA88EtU7WETl1`B-{7AY$ z6I+27R~AwbXhU2Q8TF>5dL=#|lOLss(e<;r4?b&VsR;2S+W%_-;IDZ?;)wh5zY#=Y zT%@M{!V2*ERkFCn==AY#y3Qz2lSpL#_ct;BP${xaW8|;2nx}yuSG8~%vD(k!-6Y$d zVYu<|z&TZ|W0i>>QptnZtgy3G0*l-i$xmz~>l&H&y+B5FccbmGU_~(ofQiABj8ZhK zM~Sa_;XT*(_Yx64b-#NKpSE`Q;=Q5mnyAHZD`jsY`PT*`ik#QM&coy}Wd?qTr1OKa zj2fg))0HF$e-L2$j-e-Qq}D%Zbw;B5VB|>tFWE^;MqZ6}023($3!wgd^7Rf(UXRa8 zUMmVm0JeKx&7}Jk>>J-n`PrHUWMu3mm@uqNY+n!#k8L9&GmqIQNRSF7`l&vdN=fF5 z=j!GdUphtjNJSK^BbiEK1%b?sD2uQ=U}dJTYl1K7TTr;O$J6qT&pEy6Z|L+c+5n^e z$xk5ErGtISQUbU1c}m&(&<}GTnWFacx+jYrW9W0&WgvtI>2edUxF0CH*CFsd-hjDf zOXf~$6O-t!!DmwaGAz%8z$oayUk4d8lIU%JOc*!nyfUrHe3n(2LiVj|jZnoc)n;Oj zYn7QH%Kl2|>&L~3EyY>*eAn$}!TH|6#+4E?BhQN50u$-qoAJe|-AAV%8RiBX^VKrV ztvSxoA*%a2QC?HT=^1-}WZ{tEF*VORppvfu*LVIOuc6H@b4SUVO0i5iIR$AI=-6eS z{hr#i#9xVWRG+qyvsx!$N}UKdUu}Ak@J;=hW36@5)T6^u!d!YyXOGn$Pf`R2ID#St zA6^29!fPR+@qPh7mjg;*3vM}E*m72na#?$(iyn((3uV@ls;wS8FG zQ~Ye97QU1RUa5m;QO;IE5NbRrA&jfkK*S!{-!X~fskn#qocH=h)ifW^YnRJnai zo}V*Cqa6vw&g^`d@|##N3#wNAO<^V#yEr^Z0S);Y4?CNMB@zKKJsU652%whep?2_G z`EF5DbJ=pN?o;h;mhTqgpAnn_#k}znUO7=K5v5?{i(f)#dr~3kd$|X=`p!PsKj_`%URpFtoQ*fd?L@R#ShG$T`zy60%_NA?+7};T>88aN zj|&FbDoxQ(f9ne6zgVuq8E3W8VIQPP7-GofgJ1hYSa zFr_Tbe~I=9tjNd1skMtWiIiZeOnoklm82I+;?&u4ay&A3B$dJfDsE&Qcd&*tb$nRI zdtf|>=mC&fv2Nj5pG0l%^ZPdPev@U^v#ht%OxQG6WXM7!P>ZEleiA__Q_B=u_&(8U zF;o*P0{xfn6r=C^pvR%xu5C7yeA()u9HscaRtxtylhN?a%aho!Iae6|6=?zV6K57V z0>_k0S6qsH{mnuajW$cG(|~adR4s~J^2ScN-(A65f14sBB4|-?2YnWSC_*LIN!LPp zZn9b%ygNOzk|PmB!}LGLEz0!q8GpHn_*bq~0Hxu~T14yCM%p9^4vQn6PF zzvb*!)9g!<-!z#4Gyy@7NXQ~XXt;hJp0u?147Ers!uGqL>9MJNh)w*uCs<=x@S)g^ z3)V){a1?xXqa4zLzdfH6k}^%#^pWw@bZC5&(hFtzrG8FjMyjNmx@R?9X>vz+F^PWP zAoU~epPMmL{0^O0#=VoaH6f57C1Ww2kDfIWQ~IEHPEGQod9Cr>KX{m6l;XdCQmpZV zotKjUms|eB&?YiQ$kZvvOLJl1EyfI|B!r7IGMFz;@=^MSdtX1QE8(J~{2yw@`z-M` zrSA>_Z|K~QqBRRN#!rS0{9>LGqA|~YPX(*&>4IL*RNS1EMONbZ@z>om1`g+UlzjUs zzu;T>lD_5fajB63EWso5ZTx?hF$a)xa@a+gcFJ_=0LiNmX8)9rS;3yd{o$5@5cWu& zWm10kAmAYJTbX&BK=2{eNBRBT9Y0vwrz}G2 zx84cz`Rxp}iy;P9JY~}*IYkY_a?*;vD$Tj7^$B%}W8%igbKszs%>Scc8SCTBz#Iqtgb0hbkk9;1j77OU8 zMDmS2uo+p8Fi#z@UZu9F3YnJHa>}29X7TV(V#DiUWR1>b&By9RY5g0<0~oLjzWCv) zcT?ID784`!PDTOH#%0BX=;w?Cr(K&R&F;5fSv==UEsdDFVf|gRR0e;149dbGTEL_` zdN4vpS1LN?kVuZ8^_D>rW8-EsbpedvybH@y;}yxnu+_UNQdhLkXmKotj!{bGi3OcS zWksI}R~*&sXOir{^}E3zi>u5z{(1d$E9Qpj=)DF=!#pwVj?+) zwZ{eb_MTmf$$0;zPc-dLfsU7UPoae>~5N-vsP>h z8q!1eK0WeBM3XN@47|FD$0tCAGbkdZ@h)vrnbT;{>UlfDwzz8pKJx$?yt28i@Q}_*I|uV=_f?QE>YH| zJ<%_H%{%N2ovV~3dsu05#xojblhw%6<=soEB{QYfWy$`{YGsDO1av8)ao-rfhcWAbAp)d}mpbr>;w++rZFI*_U z>T>vG)#hBn7?*Uiw$`f$sQNqiNWPOEL03AT`o4rCwDZj?cKZlU)Z0yz*tV7JV2!Z8{tc@_zWGlJc^WP=0g}S->rdz98-uuvtbI{mE|oH{>7^^R z^#IRzk(XG4B9j+tyw0SGv~{8%s^Nz^OTQpQImgeC0jNZ%7__(Wu$wFuVy9;AMY5yaz*G=*d2a5aW({yijCJtfI*Q}_br%TK2>y)O`V|#P$5AX1*ImS?3*Yq`#4inw|0znf5 zCdTo4^676UF{=`y5fI z;V%n9w>3d?HybMOfYSS&MtAp5&&`G48opstmN$GGxxT`^+9D(jf}_X9S%r)T&-kc3 zkRt=>q|S>?^@n33o>;TA0AG0uw#@s`q$MiHbUxdBn9UVU9cY$c9= zMr%B-0)}zZV+fo!*YBixeP?oaP^#ZTjUicoImQsNQG!ZMo=f6R?QsUN8;Jb3*Pb8y ze@Wc*tu@3xP0iF)483q!o zSX$CewEMFh@5@)o`y$ITbNO43-!mfR=h<_vR2dD|Nvtfv1MI~#21Bapf1bAq#}lqj zBS^^%f4|GFR;qC$V1K+BkBOB(Zj?EX_&m$Vy~?&p;#J;OYQ}_G*zq~DhFDGaE1sTb zi5fgS4cA)hVxstg<4JVK^*+qrI}sg8S3WEn!Hyi5Iph3H*H9dk&qsUF***Syv=*}`3;Y85sYTc8YRWn<3JTGwN%V)I&6u+#bwOR>0 zC^d-CMFj@iF-4(TV&4|5T7SHm+OJ>?-0f(+pEcq;^7S-}j`KK)jG`e6?Qz#_RsUHi77^yt;v1*@~q=@L zE1v?p7vMWuk=GDm_}<=Io#RF}T@807hxbrJw#HoN7~IPM!zt+Z!O(qbpvt8i9050% zq*mxjtEMEz(1ZyBOqFEjsF94)Y7g%8Yi9Ihj>CpSJ}I3!f3F6B&S**>?i*b zkLD|qs&1J5p!2E8C4(Z_p;j#(N8QdmbO)Y@-9}BTdJK6>4()rrs+0B4TbxbU2)y4;I{M0=#uElcA5DN>*s@hfSiUvORLNjq4FF-AT z$aK%NMz1OA+WkO>LUk2JAfwNiPI`7GhZisuO8+CuYlK$_n7u9dRNEL+znG<;O_iqxDFTTxUzpgYo zJPg=omVkkjh7ccJ@k6wp$dyHg$|DhT)51yGr$?2lfNAek%|L|Nzi14Yg0(lYsuC++p zdoZdtfj@7ENF=W&rZ;7v1Ce5ll;chL_Al;z%VhZAJ&4QXl~)Ik>%z%uDs+3*imkCe zd$};Q94tYI?j=~B0|()sc0WcLl)ht*^f09p|IgG1F+b5g|dT!HC)$>*Z+ zZcB^iWoJ7yd{YHp2hEUgfJmO?WU%cWtZ9nLV~&}uk1F_W zcxc)iZQS|SB?yj_0!K;JGDrW!ny=Ix!997_d_J5oc7mNxR_0*qp0(?}$>_g0`cIbi z9pF$PDX#rb)i5#Qx1jJ0u-=%a^Yj(fRQ_@PCt#+e#%*{f;peo5FXLvz;&4N+&(AO^ z7>1v`6}EE3M2!b7oS!O=mO6`#Vm39&qyKUiKG}Zye(@zXog(Cay!N+|L6)pN_hr5S zwrBp#;2a5%Xsl(kKYi0Ak@$9sqi-GC2t{`^r@avR5gAOf>1P3nuu=+HxE>g1T&8W9 zk%I?6g9DK=F0h5oRPy2p;^`XI-uc^;BQGrUYD9b${wmBfN9i;`;u39)zE_7Zdvx0I zvx`$+aWhNwC1uC4`sSOL{bCGs0H1ZCS8l)szcqtMQ3R|%J}0N&{(Kt4qQIy!W(sZN z?PadpsS=#*5b5Fi`0YV;x$(+ixr5EESL7nrQd-+gwwD2g6${E8aX|x~T_+ywVUNSS zIp!;rI^Xx+Q~qOiLW*QGoWC;u2ZCfjRnE_ISAFWp(V0(7KU8S3MO`_^pL@Tv{w;GB znW9xWaXov;im7hXN&-)p6ZucXk?NJcjwg9rFeEFP&fI=a|LfWBLGqiG{S{9-50F_E z{*n*xQwG&Uvf0?U3t5reBN&1aiMvJGzaA3ib=?bHn;0altYo_9%M=VnQ;i!F zZ#<33x@ft|u`e!=veaSi{T`VXz+<#;t&bfZH?gCm<}p7Rx)sn*vOXEgi%`@bz7De) zxG=Jm^FR3UtM*ODiBj$dQd2pq!|q$#NkkJNl5ng}i|?fES?FyD@I3%fcf|mv7)`Rx zB|WCescSFEZpKSct6qH`Y5&O>^UA>#9fQJf|Agf?zZZZ1S1h+2zWKUJZu(je?|>a* zczUWsfJ5LMmvgD^bc=+@#-bYLVCw*3+^QOe%@ zCmd3iFZ+?yLqJDQz~%4u?XC3xzkX0Fhy>!w|DwnZy!Enus!}&bE#rx?)40G zAlT-@agi!u`uz<><8n3FgQ;&vGJYU_I|>I+qJ**n>~_s)oCHm-3Kd#N?6Tiq-m(a1 z3bqLA4wiY^Q38AF_T=>gX~h_GQD6z+Uo-gE7SDJklz5=Y>h8yY0B(d=<*~AMdM;17PpWypOBUWQc8CQ2L~YpTFSqyDq9lL) zW9IVRf}0Pw$w)DseX>*5eFlrzbf#pGY`+Gw9 z9Z6$-?kFdJ+xO5VO~*swGcmLd`Jf~O9|&ka#buOUaL2y2YnJfYm!m=xCWZXDr#n${ zxp{+6{c9q9qG#brENdLK0w2im>dE3=c!9~aJPR$i|0Vp1hM@QKgHL+EgME=+5Q?H^L1x| zq^3p`2N!q8KO26RGJK02W>EbSckuI*vN=@s=68pzty+GGcDt~LTiZgjdR%Ch_2=m0 zoNHl;NW*&wbT7?mGN_j5936`^#rcN*J`)4@K)9Za2k4snuVwT!%Q$=oo$|N6pA7qh zC>pADsPRXzC<58mP?MFx*F>big{$>Wbc9aD=2`AsgXn6jm(oCt;6Jsv)0jtz<-?DM z2_SfB(ihl56)*XHXu5EH>|;h+T$Qy^|L)YO>m&
7VF=fanFQRTaAm$kR~R3tqg zCkk%C^?la7{$Ae;|3byGAfNW8i!;mia^CMwWWU2#dg)nx`L6%p$!~3$<;=_4%6Cjx zUX=W(uVOR{{viw{beGZ6hk&{$F2jZjmO>Sdm%hMh{|EO3OYC{Sa{;YOdB*4|`xnF0 z$wreM#h(MSf_I#@-ppao6K$+1pX#P32D(4C=Q|UZbV>Fqd!)|^bnVl_0n;}xr-(eN z^_v3=trN(MKOL~Kc`>j7QCz62vu#&+PVCXDdqqudd#ONc;~dT7plNPpIeHcRwL7+n z&k?scSSL4}^0@i9jJRq(K78U#L5PMw zB&Jm+TGO00Sv}Vn`RXM16%h}hiwez*6W#LNnw!%Qy^O_AyCdbGCHlnf+;Vq*HYCxT zJ^_bi%xuS;If%q9gtP_3S-Ljozj0Q7QIft_|1c*UJVCIs=f5#+vN#v9b|i}Dvqg!` zLh~oDC#OJ8z4c*vBKCcrKR++u;8tIO^W@knJsB|l@iZm zJ_;H~9R26A9(eZx?ZKsQ$sP z{!UivLy=MA`uEf0s9JdcIB{NtqT9M{ZSb4THt_csz*yn{)Z{u72fIh z$U-90gZd$JKUz7AuPuhrg}u9wIR|{Zuz3NS4M>P7Q~VQHKqoEXP{#Dth+Mh}2q9%l zn|})fR^o<*Bn0xWa%1KMTOLQeixH^LoNH{AY zBoRU~DLR&>2j*F;Pap$}|5A8nX5b@3IMY9CeL(mH=qk#WgRgqeoM)MA`%3j#{_9S! zWx8|BR4Ee<)X8K_AQkoXegF4n(|-ZcOX3*Tz>$~z+&oiAd-{wlfB#1~n2mC&&zNOI zj4v+?7yZHy9{7bd<~MDQUR4AyB6rP^^2~GdAYL^cD^OC}NJk|>Dsccd9dj@7{{PH@ zwuz~w<@^^Ae`VtcwPbs`hyNz$P1#&O+o2O+7!-PYLWxoGbixfJ=bfA$uua+JH19p< zv)?DaWJmwAB~-Q=Ggdh3&B$}^KfGgKHA5w+80mBY+WYt&!8g9;J(qrwho>pB??|LxA$5q*{{ud z7b{=^dCISFGCfzyqYgeSSmpl$#{xM0`zj_;{;8KO!22)#8v8!vir?4~KXbnioqhnQ zDg8IA=vVGi5gI$e=9gM}6U`e$j{eQvmlz&!XrMCUS>qo#81`2sO z(f^=HKLC9HXH)5y+|d_6Xw8WcOEZW#6-es8=kf5*zSfBA_Y^<-G60r_=%-Hs?&1|s zxF(+P&cf}v$wVLT>gAs_B|;6QH#>0zkCqft2Soa%z{#oTvISU@4i7-t=Xq^iV8?So z+CAg1r08A|q{k;{}6UJV20zmfzW5efGXQDKb;S^RrsxN=SX%KZn-~f=>m6O0vke;hrIw%?YprIfJGtq=~Hm+u(kOLSi0a( zULRc+eVqrn3ve&!nQsPmJQpm)%K4uy-w9rPz6bpujVlj!!+$EGrnm16W{1JjRo=qg(wkWGZSiv(Jnto?;}hv+7wASvJj`$w zaX%!X2Y|Ny?*c&j6o|uc{Nccs?*uJfWx|z-C%ns|U)K}a=CPeUP_7S(!(izG`&Jlw za3uirJRZiO(3*4G%K)e$@!B(4H6@;e9_1csyh!d*<)>c)MbTFr;9#HV2Tks|hk}c* zyFjpRkBe8pzO@_&x9^UaeUN&K=fPbDfR?P+96nT(bv*ao$?=qwXgm*v9towJH;8h% zhw8lkvgqqSq%YE^plJsHz}f%O2>@%q9>4lyYxvwMfU%l4>i+{(f7p(!w~br?0000< KMNUMnLSTZin>hpk literal 0 HcmV?d00001 diff --git a/demos/hello_world_wgpu/src/main.rs b/demos/hello_world_wgpu/src/main.rs new file mode 100644 index 0000000000..1639a1fd78 --- /dev/null +++ b/demos/hello_world_wgpu/src/main.rs @@ -0,0 +1,114 @@ +use bones_framework::prelude::*; +use bones_wgpu_renderer::BonesWgpuRenderer; + +// +// NOTE: You must run this example from within the `demos/hello_world_wgpu` folder. Also, be sure to +// look at the `demos/hello_world_wgpu` folder to see the asset files for this example. +// + +/// Create our "root" asset type. +#[derive(HasSchema, Clone, Default)] +#[repr(C)] +// We must mark this as a metadata asset, and we set the type to "game". +// +// This means that any files with names like `game.yaml`, `game.yml`, `game.json`, `name.game.yaml`, +// etc. will be loaded as a `GameMeta` asset. +#[type_data(metadata_asset("game"))] +struct GameMeta { + title: String, + sprite: Handle, +} + +fn main() { + // Setup logging + setup_logs!(); + + // First create bones game. + let mut game = Game::new(); + + game + // We initialize the asset server. + .init_shared_resource::(); + + // We must register all of our asset types before they can be loaded by the asset server. This + // may be done by calling schema() on each of our types, to register them with the schema + // registry. + GameMeta::register_schema(); + + // Create a new session for the game world. Each session is it's own bones world with it's own + // plugins, systems, and entities. + let world_session = game + .sessions + .create("world") + .install_plugin(sprite_demo_plugin); + world_session + // Install the default bones_framework plugin for this session + .install_plugin(DefaultSessionPlugin); + // Add our menu system to the update stage + //.add_system_to_stage(Update, menu_system); + + BonesWgpuRenderer::new(game).run(); +} + +/// Plugin for running the sprite demo. +fn sprite_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultSessionPlugin) + .add_startup_system(sprite_demo_startup) + .add_system_to_stage(Update, move_sprite); +} + +/// System that spawns the sprite demo. +fn sprite_demo_startup( + mut entities: ResMut, + mut sprites: CompMut, + mut transforms: CompMut, + mut cameras: CompMut, + meta: Root, +) { + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + let sprite_ent = entities.create(); + transforms.insert(sprite_ent, default()); + sprites.insert( + sprite_ent, + Sprite { + image: meta.sprite, + ..default() + }, + ); +} + +fn move_sprite( + entities: Res, + sprite: Comp, + mut transforms: CompMut, + input: Res, +) { + let mut left = false; + let mut right = false; + + for input in &input.key_events { + match input.key_code { + Set(KeyCode::Right) => right = true, + Set(KeyCode::Left) => left = true, + _ => (), + } + } + + for (_ent, (_sprite, transform)) in entities.iter_with((&sprite, &mut transforms)) { + if left { + transform.translation.x -= 2.0; + } + if right { + transform.translation.x += 2.0; + } + } +} + +/// System to render the home menu. +fn _menu_system(ctx: Res) { + egui::CentralPanel::default().show(&ctx, |ui| { + ui.label("Hello World"); + }); +} diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml new file mode 100644 index 0000000000..49a1633447 --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bones_wgpu_renderer" +description = "WGPU rendering implementation for the bones_framework." +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +documentation.workspace = true +categories.workspace = true +keywords.workspace = true + +[dependencies] +bones_framework = { version = "0.4.0", path = "../bones_framework" } +bones_schema = { path = "../bones_schema" } +winit = "0.30.9" +#Needs to be 23 because of bones_bevy_renderer dependecies, we need to upgrade to 24^ later +wgpu = "23" +pollster = "0.4.0" +env_logger = "0.11.6" +bevy_tasks = "0.11.3" +image = "0.24.9" +crossbeam-channel = "0.5.14" +bytemuck = "1.22" diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs new file mode 100644 index 0000000000..e739ab8cdf --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -0,0 +1,642 @@ +use bevy_tasks::{IoTaskPool, TaskPool}; +use crossbeam_channel::{unbounded, Receiver, Sender}; +use pollster::FutureExt; +use std::{ + path::{Path, PathBuf}, + sync::Arc, + time::Instant, +}; +use texture::Texture; +use wgpu::util::DeviceExt; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, + window::{Window, WindowId}, +}; + +use bones_framework::prelude::{self as bones, BitSet, ComponentIterBitset}; + +mod texture; + +/// The prelude +pub mod prelude { + pub use crate::*; +} + +//Wgpu utils Bones types + +#[derive(bones_schema::HasSchema, Default, Clone)] +#[repr(C)] +#[schema(opaque)] +struct WgpuDevice(Option>); + +impl WgpuDevice { + fn get(&self) -> &wgpu::Device { + self.0.as_ref().unwrap() + } +} + +#[derive(bones_schema::HasSchema, Default, Clone)] +#[repr(C)] +#[schema(opaque)] +struct WgpuQueue(Option>); + +impl WgpuQueue { + fn get(&self) -> &wgpu::Queue { + self.0.as_ref().unwrap() + } +} + +// Texture sender to the wgpu thread +#[derive(bones_schema::HasSchema, Clone)] +#[repr(C)] +#[schema(opaque)] +#[schema(no_default)] +struct TextureSender(Sender); + +// Indicates that we already loaded the sprite texture +// and sent it to the wgpu thread +#[derive(bones_schema::HasSchema, Default, Clone)] +#[repr(C)] +struct TextureLoaded; + +#[derive(bones_schema::HasSchema, Default, Clone)] +#[repr(C)] +struct PixelArt(bool); + +struct App { + state: Option, + instance: Arc, + adapter: Arc, + device: Arc, + queue: Arc, + texture_bind_group_layout: Arc, + receiver: Receiver, + game: bones::Game, +} + +/// Renderer for [`bones_framework`] [`Game`][bones::Game]s using wgpu. +pub struct BonesWgpuRenderer { + /// Whether or not to load all assets on startup with a loading screen, + /// or skip straight to running the bones game immedietally. + pub preload: bool, + /// Whether or not to use nearest-neighbor sampling for textures. + pub pixel_art: bool, + /// The bones game to run. + pub game: bones::Game, + /// The version of the game, used for the asset loader. + pub game_version: bones::Version, + /// The (qualifier, organization, application) that will be used to pick a persistent storage + /// location for the game. + /// + /// For example: `("org", "fishfolk", "jumpy")` + pub app_namespace: (String, String, String), + /// The path to load assets from. + pub asset_dir: PathBuf, + /// The path to load asset packs from. + pub packs_dir: PathBuf, +} + +impl BonesWgpuRenderer { + pub fn new(game: bones::Game) -> Self { + BonesWgpuRenderer { + preload: true, + pixel_art: true, + game, + game_version: bones::Version::new(0, 1, 0), + app_namespace: ("local".into(), "developer".into(), "bones_demo_game".into()), + asset_dir: PathBuf::from("assets"), + packs_dir: PathBuf::from("packs"), + } + } + + pub fn run(mut self) { + let (instance, adapter, device, queue, texture_bind_group_layout) = async { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap(); + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor::default(), + None, // Trace path + ) + .await + .unwrap(); + let texture_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + // This should match the filterable field of the textures + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + ( + Arc::new(instance), + Arc::new(adapter), + Arc::new(device), + Arc::new(queue), + Arc::new(texture_bind_group_layout), + ) + } + .block_on(); + + self.game + .insert_shared_resource(WgpuDevice(Some(device.clone()))); + self.game + .insert_shared_resource(WgpuQueue(Some(queue.clone()))); + self.game.insert_shared_resource(PixelArt(self.pixel_art)); + + let (sender, receiver) = unbounded(); + self.game.insert_shared_resource(TextureSender(sender)); + + IoTaskPool::init(TaskPool::default); + if let Some(mut asset_server) = self.game.shared_resource_mut::() { + asset_server.set_game_version(self.game_version.clone()); + asset_server.set_io(asset_io(&self.asset_dir, &self.packs_dir)); + + if self.preload { + // Load assets + let s = asset_server.clone(); + println!("Loading Assets..."); + + // Spawn a task to load the assets + IoTaskPool::get() + .spawn(async move { + s.load_assets().await.unwrap(); + }) + .detach(); + } + + // Enable asset hot reload. + asset_server.watch_for_changes(); + } + + // Insert empty inputs that will be updated by the `insert_bones_input` system later. + self.game.init_shared_resource::(); + self.game.init_shared_resource::(); + self.game.init_shared_resource::(); + + //Insert needed systems + for (_, session) in self.game.sessions.iter_mut() { + session.add_system_to_stage(bones::First, load_sprite); + session.add_system_to_stage(bones::First, insert_bones_input); + } + + // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate. + env_logger::init(); + + let event_loop = EventLoop::builder().build().unwrap(); + + // When the current loop iteration finishes, immediately begin a new + // iteration regardless of whether or not new events are available to + // process. + event_loop.set_control_flow(ControlFlow::Poll); + + let mut app = App { + state: None, + instance, + adapter, + device, + queue, + texture_bind_group_layout, + receiver, + game: self.game, + }; + event_loop.run_app(&mut app).unwrap(); + } +} + +//TODO Fix sprite deletion +fn load_sprite( + entities: bones::Res, + sprites: bones::Comp, + transforms: bones::Comp, + assets: bones::Res, + device: bones::Res, + queue: bones::Res, + texture_sender: bones::Res, + pixel_art: bones::Res, + texture_loaded: bones::Comp +) { + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + + for entity in entities.iter_with_bitset(¬_loaded) { + let (Some(sprite), Some(_transform)) = (sprites.get(entity), transforms.get(entity)) else { + continue; + }; + + //Load and send texture + let image = assets.get(sprite.image); + if let bones::Image::Data(img) = &*image { + let texture = + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(); + texture_sender.0.send(texture).unwrap(); + } else { + unreachable!() + }; + } +} + +fn insert_bones_input() {} + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + ], + } + } +} + +struct State { + window: Arc, + device: Arc, + queue: Arc, + size: winit::dpi::PhysicalSize, + surface: wgpu::Surface<'static>, + surface_format: wgpu::TextureFormat, + render_pipeline: wgpu::RenderPipeline, + binds: Vec, +} + +impl State { + fn new( + window: Arc, + device: Arc, + queue: Arc, + instance: &wgpu::Instance, + adapter: &wgpu::Adapter, + texture_bind_group_layout: &wgpu::BindGroupLayout, + ) -> Self { + let size = window.inner_size(); + let surface = instance.create_surface(window.clone()).unwrap(); + let cap = surface.get_capabilities(adapter); + let surface_format = cap.formats[0]; + + // Configure surface for the first time + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + // Request compatibility with the sRGB-format texture view we‘re going to create later. + view_formats: vec![surface_format.add_srgb_suffix()], + alpha_mode: wgpu::CompositeAlphaMode::Auto, + width: size.width, + height: size.height, + desired_maximum_frame_latency: 2, + present_mode: wgpu::PresentMode::AutoVsync, + }; + surface.configure(&device, &surface_config); + + let surface_caps = surface.get_capabilities(adapter); + // This accounts only for Srgb surfaces + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[texture_bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent::REPLACE, + alpha: wgpu::BlendComponent::REPLACE, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE + // or Features::POLYGON_MODE_POINT + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + // If the pipeline will be used with a multiview render pass, this + // indicates how many array layers the attachments will have. + multiview: None, + // Useful for optimizing shader compilation on Android + cache: None, + }); + + State { + window, + device: device.clone(), + queue, + size, + surface, + surface_format, + render_pipeline, + binds: vec![], + } + } + + fn get_window(&self) -> &Window { + &self.window + } + + fn configure_surface(&self) { + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: self.surface_format, + // Request compatibility with the sRGB-format texture view we‘re going to create later. + view_formats: vec![self.surface_format.add_srgb_suffix()], + alpha_mode: wgpu::CompositeAlphaMode::Auto, + width: self.size.width, + height: self.size.height, + desired_maximum_frame_latency: 2, + present_mode: wgpu::PresentMode::AutoVsync, + }; + self.surface.configure(&self.device, &surface_config); + } + + fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + self.size = new_size; + + // reconfigure the surface + self.configure_surface(); + } + + fn render(&mut self) { + // Create texture view + let surface_texture = self + .surface + .get_current_texture() + .expect("failed to acquire next swapchain texture"); + let texture_view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor { + // Without add_srgb_suffix() the image we will be working with + // might not be "gamma correct". + format: Some(self.surface_format.add_srgb_suffix()), + ..Default::default() + }); + + // Renders a gray background + let gray = wgpu::Color { + r: 0.10, + g: 0.10, + b: 0.10, + ..Default::default() + }; + let mut encoder = self.device.create_command_encoder(&Default::default()); + // Create the renderpass which will clear the screen. + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(gray), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + //TODO Add sprite batching + for bind_group in &self.binds { + let vertex_buffer = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(VERTICES), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + let num_indices = INDICES.len() as u32; + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, bind_group, &[]); + render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); + render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..num_indices, 0, 0..1); + } + + // End the renderpass. + drop(render_pass); + + // Submit the command in the queue to execute + self.queue.submit([encoder.finish()]); + self.window.pre_present_notify(); + surface_texture.present(); + } +} + +const VERTICES: &[Vertex] = &[ + // Top-left vertex + Vertex { + position: [-0.5, 0.5, 0.0], + tex_coords: [0.0, 0.0], + }, + // Bottom-left vertex + Vertex { + position: [-0.5, -0.5, 0.0], + tex_coords: [0.0, 1.0], + }, + // Bottom-right vertex + Vertex { + position: [0.5, -0.5, 0.0], + tex_coords: [1.0, 1.0], + }, + // Top-right vertex + Vertex { + position: [0.5, 0.5, 0.0], + tex_coords: [1.0, 0.0], + }, +]; + +const INDICES: &[u16] = &[ + 0, 1, 2, // first triangle + 0, 2, 3, // second triangle +]; + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + // Create window object + let window = Arc::new( + event_loop + .create_window(Window::default_attributes()) + .unwrap(), + ); + + if let Some(state) = &mut self.state { + state.window = window.clone(); + state.size = window.inner_size(); + + state.surface = self.instance.create_surface(window.clone()).unwrap(); + let cap = state.surface.get_capabilities(&self.adapter); + state.surface_format = cap.formats[0]; + + state.configure_surface(); + } else { + let state = State::new( + window.clone(), + self.device.clone(), + self.queue.clone(), + &self.instance, + &self.adapter, + &self.texture_bind_group_layout, + ); + + self.state = Some(state); + } + + window.request_redraw(); + } + + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + let state = self.state.as_mut().unwrap(); + + for texture in self.receiver.try_iter() { + let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture.sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); + + state.binds.push(bind_group); + } + + match event { + WindowEvent::CloseRequested => { + //Close window + event_loop.exit(); + } + WindowEvent::RedrawRequested => { + state.render(); + // Emits a new redraw requested event. + state.get_window().request_redraw(); + + //Step bones + self.game.step(Instant::now()); + } + WindowEvent::Resized(size) => { + // Reconfigures the size of the surface. We do not re-render + // here as this event is always followed up by redraw request. + state.resize(size); + } + _ => (), + } + } +} + +/// A [`bones::AssetIo`] configured for web and local file access +pub fn asset_io(asset_dir: &Path, packs_dir: &Path) -> impl bones::AssetIo + 'static { + #[cfg(not(target_arch = "wasm32"))] + { + bones::FileAssetIo::new(asset_dir, packs_dir) + } + #[cfg(target_arch = "wasm32")] + { + let _ = asset_dir; + let _ = packs_dir; + let window = web_sys::window().unwrap(); + let path = window.location().pathname().unwrap(); + let base = path.rsplit_once('/').map(|x| x.0).unwrap_or(&path); + bones::WebAssetIo::new(&format!("{base}/assets")) + } +} diff --git a/framework_crates/bones_wgpu_renderer/src/shader.wgsl b/framework_crates/bones_wgpu_renderer/src/shader.wgsl new file mode 100644 index 0000000000..656aac8091 --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/shader.wgsl @@ -0,0 +1,33 @@ +// Vertex shader + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, +} + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + out.tex_coords = model.tex_coords; + out.clip_position = vec4(model.position, 1.0); + return out; +} + +// Fragment shader + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(t_diffuse, s_diffuse, in.tex_coords); +} \ No newline at end of file diff --git a/framework_crates/bones_wgpu_renderer/src/texture.rs b/framework_crates/bones_wgpu_renderer/src/texture.rs new file mode 100644 index 0000000000..f8d40b0f4b --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/texture.rs @@ -0,0 +1,88 @@ +use image::{GenericImageView, ImageError}; + +pub struct Texture { + #[allow(unused)] + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, + pub sampler: wgpu::Sampler, +} + +impl Texture { + #[allow(unused)] + pub fn from_bytes( + device: &wgpu::Device, + queue: &wgpu::Queue, + bytes: &[u8], + label: &str, + pixel_art: bool, + ) -> Result { + let img = image::load_from_memory(bytes)?; + Self::from_image(device, queue, &img, Some(label), pixel_art) + } + + pub fn from_image( + device: &wgpu::Device, + queue: &wgpu::Queue, + img: &image::DynamicImage, + label: Option<&str>, + pixel_art: bool, + ) -> Result { + let rgba = img.to_rgba8(); + let dimensions = img.dimensions(); + + let size = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth_or_array_layers: 1, + }; + let texture = device.create_texture(&wgpu::TextureDescriptor { + label, + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + + queue.write_texture( + //Change to TexelCopyTextureInfo when updating to wgpu 24 + wgpu::ImageCopyTexture { + aspect: wgpu::TextureAspect::All, + texture: &texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + }, + &rgba, + //Change to TexelCopyBufferLayout when updating to wgpu 24 + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(4 * dimensions.0), + rows_per_image: Some(dimensions.1), + }, + size, + ); + + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: if pixel_art { + wgpu::FilterMode::Nearest + } else { + wgpu::FilterMode::Linear + }, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + Ok(Self { + texture, + view, + sampler, + }) + } +} From 02350f12e59099f3a1964a2240742cb6851aac22 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Tue, 25 Mar 2025 17:11:56 -0300 Subject: [PATCH 02/16] Added some transform handling --- .../assets/sprite/example.png | Bin 0 -> 208 bytes .../bones_framework/src/render/transform.rs | 7 ++ .../bones_wgpu_renderer/src/lib.rs | 99 +++++++++++++----- 3 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 demos/hello_world_wgpu/assets/sprite/example.png diff --git a/demos/hello_world_wgpu/assets/sprite/example.png b/demos/hello_world_wgpu/assets/sprite/example.png new file mode 100644 index 0000000000000000000000000000000000000000..35054de3e2c56a11ae5188295e659d8196cd5977 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3jKx9jP7LeL$-D$|x;$MRLp(a) zUO&s*pup2`as4sF8%>RKdK>Q?vi-8e@DJAoK= zj~4LN1Us00nq|NMAmiA6FeZ5!H~U>VY1kh<5xGPZNISL@xKJdzdxBg zxJ9bwd~9I1c~F1&oYc&%2aDG-9lv+`>UyiMC)RU4uM=bc8>;RU^md;a&{Yhcu6{1- HoD!M Self { Self { scale, ..default() } } + + /// Converts the transform to a 4x4 matrix for rendering + pub fn to_matrix(&self) -> Mat4 { + let rotation = Quat::from_xyzw(0., 0., self.rotation.normalize().z, 1.); + + glam::Mat4::from_scale_rotation_translation(self.scale, rotation, self.translation) + } } diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index e739ab8cdf..36f06b7d94 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -15,7 +15,7 @@ use winit::{ window::{Window, WindowId}, }; -use bones_framework::prelude::{self as bones, BitSet, ComponentIterBitset}; +use bones_framework::{prelude::{self as bones, BitSet, ComponentIterBitset}, glam::*}; mod texture; @@ -53,7 +53,9 @@ impl WgpuQueue { #[repr(C)] #[schema(opaque)] #[schema(no_default)] -struct TextureSender(Sender); +struct TextureSender(Sender<(Texture, bones::Entity)>); + +type TextureReceiver = Receiver<(Texture, bones::Entity)>; // Indicates that we already loaded the sprite texture // and sent it to the wgpu thread @@ -72,7 +74,7 @@ struct App { device: Arc, queue: Arc, texture_bind_group_layout: Arc, - receiver: Receiver, + receiver: TextureReceiver, game: bones::Game, } @@ -228,19 +230,18 @@ impl BonesWgpuRenderer { fn load_sprite( entities: bones::Res, sprites: bones::Comp, - transforms: bones::Comp, assets: bones::Res, device: bones::Res, queue: bones::Res, texture_sender: bones::Res, pixel_art: bones::Res, - texture_loaded: bones::Comp + mut texture_loaded: bones::CompMut, ) { let mut not_loaded = texture_loaded.bitset().clone(); not_loaded.bit_not(); for entity in entities.iter_with_bitset(¬_loaded) { - let (Some(sprite), Some(_transform)) = (sprites.get(entity), transforms.get(entity)) else { + let Some(sprite) = sprites.get(entity) else { continue; }; @@ -249,7 +250,8 @@ fn load_sprite( if let bones::Image::Data(img) = &*image { let texture = Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(); - texture_sender.0.send(texture).unwrap(); + texture_sender.0.send((texture, entity)).unwrap(); + texture_loaded.insert(entity, TextureLoaded); } else { unreachable!() }; @@ -295,7 +297,7 @@ struct State { surface: wgpu::Surface<'static>, surface_format: wgpu::TextureFormat, render_pipeline: wgpu::RenderPipeline, - binds: Vec, + sprites: Vec<(wgpu::BindGroup, bones::Entity)>, } impl State { @@ -413,7 +415,7 @@ impl State { surface, surface_format, render_pipeline, - binds: vec![], + sprites: vec![], } } @@ -443,7 +445,7 @@ impl State { self.configure_surface(); } - fn render(&mut self) { + fn render(&mut self, sessions: &bones::Sessions) { // Create texture view let surface_texture = self .surface @@ -465,8 +467,21 @@ impl State { b: 0.10, ..Default::default() }; + + // Create the command encoder. let mut encoder = self.device.create_command_encoder(&Default::default()); - // Create the renderpass which will clear the screen. + + //Index buffer is the same for all sprites + let index_buffer = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + let num_indices = INDICES.len() as u32; + + // Create one render pass that clears the screen once. let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { @@ -482,23 +497,40 @@ impl State { occlusion_query_set: None, }); - //TODO Add sprite batching - for bind_group in &self.binds { + // Render each sprite with its own transform. + for (bind_group, entity) in &self.sprites { + // Get the transform from the ECS. + let transform = sessions + .iter() + .find_map(|(_, session)| { + session + .world + .component::() + .get(*entity) + .cloned() + }) + .unwrap_or_else(|| panic!("Missing transform for entity {:?}", entity)); + + // Get the 4×4 transform matrix. + let mat4 = transform.to_matrix(); + + // Compute transformed vertices on the CPU. + let transformed_vertices: Vec = VERTICES + .iter() + .map(|v| Vertex { + position: transform_vertex(v.position.into(), mat4, transform.translation).into(), + tex_coords: v.tex_coords, // Texture coordinates remain unchanged. + }) + .collect(); + + // Create a dynamic vertex buffer with the transformed vertices. let vertex_buffer = self .device .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(VERTICES), + label: Some("Transformed Vertex Buffer"), + contents: bytemuck::cast_slice(&transformed_vertices), usage: wgpu::BufferUsages::VERTEX, }); - let index_buffer = self - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(INDICES), - usage: wgpu::BufferUsages::INDEX, - }); - let num_indices = INDICES.len() as u32; render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, bind_group, &[]); @@ -510,7 +542,7 @@ impl State { // End the renderpass. drop(render_pass); - // Submit the command in the queue to execute + // Submit the command queue. self.queue.submit([encoder.finish()]); self.window.pre_present_notify(); surface_texture.present(); @@ -582,7 +614,7 @@ impl ApplicationHandler for App { fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { let state = self.state.as_mut().unwrap(); - for texture in self.receiver.try_iter() { + for (texture, entity) in self.receiver.try_iter() { let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.texture_bind_group_layout, entries: &[ @@ -598,7 +630,7 @@ impl ApplicationHandler for App { label: Some("diffuse_bind_group"), }); - state.binds.push(bind_group); + state.sprites.push((bind_group, entity)); } match event { @@ -607,7 +639,7 @@ impl ApplicationHandler for App { event_loop.exit(); } WindowEvent::RedrawRequested => { - state.render(); + state.render(&self.game.sessions); // Emits a new redraw requested event. state.get_window().request_redraw(); @@ -624,6 +656,19 @@ impl ApplicationHandler for App { } } +//TODO Add quaternion rotations, and move this calculation to +// wgsl code + +fn transform_vertex(pos: Vec3, transform: Mat4, pivot: Vec3) -> Vec3 { + let to_origin = Mat4::from_translation(-pivot); + let from_origin = Mat4::from_translation(pivot); + let combined = from_origin * transform * to_origin; + + let pos4 = combined * pos.extend(1.0); + pos4.truncate() / pos4.w +} + + /// A [`bones::AssetIo`] configured for web and local file access pub fn asset_io(asset_dir: &Path, packs_dir: &Path) -> impl bones::AssetIo + 'static { #[cfg(not(target_arch = "wasm32"))] From bf85a534aa52c15c8b176cfbf80eb7713b1b9322 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Mon, 7 Apr 2025 13:36:09 -0300 Subject: [PATCH 03/16] Added inputs and proper sprite rot and scale --- demos/hello_world_wgpu/assets/game.yaml | 3 +- demos/hello_world_wgpu/src/main.rs | 83 ++++++- .../bones_bevy_renderer/src/input.rs | 2 +- .../bones_framework/src/input/keyboard.rs | 2 +- .../bones_framework/src/input/mouse.rs | 4 + .../bones_framework/src/render/transform.rs | 37 ++- .../bones_wgpu_renderer/Cargo.toml | 12 +- .../bones_wgpu_renderer/src/convert.rs | 235 ++++++++++++++++++ .../bones_wgpu_renderer/src/lib.rs | 121 ++++++++- 9 files changed, 473 insertions(+), 26 deletions(-) create mode 100644 framework_crates/bones_wgpu_renderer/src/convert.rs diff --git a/demos/hello_world_wgpu/assets/game.yaml b/demos/hello_world_wgpu/assets/game.yaml index b6ae04dc2e..9e34e79f68 100644 --- a/demos/hello_world_wgpu/assets/game.yaml +++ b/demos/hello_world_wgpu/assets/game.yaml @@ -1,4 +1,5 @@ # This is our root asset file, which corresponds to our `GameMeta` struct. title: Assets Minimal -sprite: ./sprite/fractal.png \ No newline at end of file +sprite: ./sprite/fractal.png +sprite2: ./sprite/example.png \ No newline at end of file diff --git a/demos/hello_world_wgpu/src/main.rs b/demos/hello_world_wgpu/src/main.rs index 1639a1fd78..cb92a80de6 100644 --- a/demos/hello_world_wgpu/src/main.rs +++ b/demos/hello_world_wgpu/src/main.rs @@ -17,6 +17,7 @@ use bones_wgpu_renderer::BonesWgpuRenderer; struct GameMeta { title: String, sprite: Handle, + sprite2: Handle, } fn main() { @@ -77,6 +78,18 @@ fn sprite_demo_startup( ..default() }, ); + let sprite_ent = entities.create(); + transforms.insert( + sprite_ent, + Transform::from_translation(Vec3::new(0.5, 0.5, 0.0)), + ); + sprites.insert( + sprite_ent, + Sprite { + image: meta.sprite2, + ..default() + }, + ); } fn move_sprite( @@ -84,24 +97,86 @@ fn move_sprite( sprite: Comp, mut transforms: CompMut, input: Res, + input_mouse: Res, + gamepad: Res, + //mouse_position: Res ) { let mut left = false; let mut right = false; + let mut up = false; + let mut down = false; + let mut rotate_left = false; + let mut rotate_right = false; for input in &input.key_events { match input.key_code { - Set(KeyCode::Right) => right = true, - Set(KeyCode::Left) => left = true, + Set(KeyCode::D) => right = true, + Set(KeyCode::A) => left = true, + Set(KeyCode::W) => up = true, + Set(KeyCode::S) => down = true, + Set(KeyCode::Q) => rotate_left = true, + Set(KeyCode::E) => rotate_right = true, _ => (), } } for (_ent, (_sprite, transform)) in entities.iter_with((&sprite, &mut transforms)) { + for event in gamepad.gamepad_events.iter() { + match event { + GamepadEvent::Axis(axis) => match axis.axis { + GamepadAxis::LeftStickX => { + transform.translation.x += axis.value * 0.1; + } + GamepadAxis::LeftStickY => { + transform.translation.y += axis.value * 0.1; + } + GamepadAxis::RightStickX => { + transform.translation.x += axis.value * 0.1; + } + GamepadAxis::RightStickY => { + transform.translation.y += axis.value * 0.1; + } + _ => (), + }, + GamepadEvent::Button(button) => { + if button.button == GamepadButton::LeftTrigger { + transform.rotation.z += 0.1; + } + if button.button == GamepadButton::RightTrigger { + transform.rotation.z -= 0.1; + } + if button.button == GamepadButton::LeftTrigger2 { + transform.scale -= button.value * 0.05; + } + if button.button == GamepadButton::RightTrigger2 { + transform.scale += button.value * 0.05; + } + } + _ => (), + } + } + if left { - transform.translation.x -= 2.0; + transform.translation.x -= 0.1; } if right { - transform.translation.x += 2.0; + transform.translation.x += 0.1; + } + if up { + transform.translation.y += 0.1; + } + if down { + transform.translation.y -= 0.1; + } + if rotate_left { + transform.rotation.z -= 0.1; + } + if rotate_right { + transform.rotation.z += 0.1; + } + + for event in &input_mouse.wheel_events { + transform.scale += event.movement.y * 0.1; } } } diff --git a/framework_crates/bones_bevy_renderer/src/input.rs b/framework_crates/bones_bevy_renderer/src/input.rs index 77b28b1e59..4259a259b5 100644 --- a/framework_crates/bones_bevy_renderer/src/input.rs +++ b/framework_crates/bones_bevy_renderer/src/input.rs @@ -60,7 +60,7 @@ pub fn get_bones_input( key_events: keyboard_events .iter() .map(|event| bones::KeyboardEvent { - scan_code: event.scan_code, + scan_code: bones::Set(event.scan_code), key_code: event.key_code.map(|x| x.into_bones()).into(), button_state: event.state.into_bones(), }) diff --git a/framework_crates/bones_framework/src/input/keyboard.rs b/framework_crates/bones_framework/src/input/keyboard.rs index 6e055f193e..0f35e40c95 100644 --- a/framework_crates/bones_framework/src/input/keyboard.rs +++ b/framework_crates/bones_framework/src/input/keyboard.rs @@ -15,7 +15,7 @@ pub struct KeyboardInputs { #[repr(C)] pub struct KeyboardEvent { /// The scan code of the pressed key. - pub scan_code: u32, + pub scan_code: Maybe, /// The key code of the pressed key, if applicable. pub key_code: Maybe, /// The state of the keyboard button. diff --git a/framework_crates/bones_framework/src/input/mouse.rs b/framework_crates/bones_framework/src/input/mouse.rs index 76c99e9c6b..029d1d1148 100644 --- a/framework_crates/bones_framework/src/input/mouse.rs +++ b/framework_crates/bones_framework/src/input/mouse.rs @@ -52,6 +52,10 @@ pub enum MouseButton { Right, /// The middle mouse button. Middle, + /// The first extra mouse button. + Back, + /// The second extra mouse button. + Forward, /// Another mouse button with the associated number. Other(u16), } diff --git a/framework_crates/bones_framework/src/render/transform.rs b/framework_crates/bones_framework/src/render/transform.rs index 73a9d50dd5..fafeb3c60a 100644 --- a/framework_crates/bones_framework/src/render/transform.rs +++ b/framework_crates/bones_framework/src/render/transform.rs @@ -1,6 +1,7 @@ //! Transform component. use crate::prelude::*; +use std::f32::consts::PI; /// The main transform component. /// @@ -50,8 +51,40 @@ impl Transform { /// Converts the transform to a 4x4 matrix for rendering pub fn to_matrix(&self) -> Mat4 { - let rotation = Quat::from_xyzw(0., 0., self.rotation.normalize().z, 1.); + let angle = self.rotation.z.rem_euclid(2.0 * PI); + let rotation = Mat4::from_rotation_z(angle); - glam::Mat4::from_scale_rotation_translation(self.scale, rotation, self.translation) + let scale_rotation = rotation * Mat4::from_scale(self.scale); + + Mat4::from_translation(self.translation) * scale_rotation + } + + /// Converts the transform to a 4x4 matrix for rendering, + /// scaling off from the given pivot (for example, the center of the screen). + pub fn to_matrix_with_pivot(&self, pivot: Vec3) -> Mat4 { + let angle = self.rotation.z.rem_euclid(2.0 * PI); + let rotation = Mat4::from_rotation_z(angle); + + let scale_offset = Mat4::from_translation(pivot) + * Mat4::from_scale(self.scale) + * Mat4::from_translation(-pivot); + + Mat4::from_translation(self.translation) * rotation * scale_offset + } + + /// Converts the transform to a 4x4 matrix for rendering, + /// using a separate pivot for scaling and for rotation. + pub fn to_matrix_with_pivots(&self, pivot_scale: Vec3, pivot_rot: Vec3) -> Mat4 { + let scale_transform = Mat4::from_translation(pivot_scale) + * Mat4::from_scale(self.scale) + * Mat4::from_translation(-pivot_scale); + + let angle = self.rotation.z.rem_euclid(2.0 * std::f32::consts::PI); + let rotation = Mat4::from_rotation_z(angle); + + let rotation_transform = + Mat4::from_translation(pivot_rot) * rotation * Mat4::from_translation(-pivot_rot); + + Mat4::from_translation(self.translation) * rotation_transform * scale_transform } } diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml index 49a1633447..0786ca75dd 100644 --- a/framework_crates/bones_wgpu_renderer/Cargo.toml +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -15,10 +15,10 @@ bones_framework = { version = "0.4.0", path = "../bones_framework" } bones_schema = { path = "../bones_schema" } winit = "0.30.9" #Needs to be 23 because of bones_bevy_renderer dependecies, we need to upgrade to 24^ later -wgpu = "23" -pollster = "0.4.0" -env_logger = "0.11.6" -bevy_tasks = "0.11.3" -image = "0.24.9" +wgpu = "23" +pollster = "0.4.0" +env_logger = "0.11.6" +bevy_tasks = "0.11.3" +image = "0.24.9" crossbeam-channel = "0.5.14" -bytemuck = "1.22" +bytemuck = "1.22" diff --git a/framework_crates/bones_wgpu_renderer/src/convert.rs b/framework_crates/bones_wgpu_renderer/src/convert.rs new file mode 100644 index 0000000000..6397985dde --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/convert.rs @@ -0,0 +1,235 @@ +use bones_framework::glam::Vec2; +use bones_framework::prelude::{self as bones}; +use winit::event::{MouseButton, MouseScrollDelta}; +use winit::{event::ElementState, keyboard::KeyCode}; + +pub trait IntoBones { + fn into_bones(self) -> T; +} + +impl IntoBones for MouseScrollDelta { + fn into_bones(self) -> bones::MouseScrollEvent { + match self { + MouseScrollDelta::LineDelta(x, y) => bones::MouseScrollEvent { + movement: Vec2::new(x, y), + unit: bones::MouseScrollUnit::Lines, + }, + MouseScrollDelta::PixelDelta(physical_position) => bones::MouseScrollEvent { + movement: Vec2::new(physical_position.x as f32, physical_position.y as f32), + unit: bones::MouseScrollUnit::Pixels, + }, + } + } +} + +impl IntoBones for ElementState { + fn into_bones(self) -> bones::ButtonState { + match self { + ElementState::Pressed => bones::ButtonState::Pressed, + ElementState::Released => bones::ButtonState::Released, + } + } +} + +impl IntoBones for MouseButton { + fn into_bones(self) -> bones::MouseButton { + match self { + MouseButton::Left => bones::MouseButton::Left, + MouseButton::Right => bones::MouseButton::Right, + MouseButton::Middle => bones::MouseButton::Middle, + MouseButton::Back => bones::MouseButton::Back, + MouseButton::Forward => bones::MouseButton::Forward, + MouseButton::Other(i) => bones::MouseButton::Other(i), + } + } +} + +impl IntoBones for winit::keyboard::KeyCode { + fn into_bones(self) -> bones::KeyCode { + match self { + KeyCode::Digit1 => bones::KeyCode::Key1, + KeyCode::Digit2 => bones::KeyCode::Key2, + KeyCode::Digit3 => bones::KeyCode::Key3, + KeyCode::Digit4 => bones::KeyCode::Key4, + KeyCode::Digit5 => bones::KeyCode::Key5, + KeyCode::Digit6 => bones::KeyCode::Key6, + KeyCode::Digit7 => bones::KeyCode::Key7, + KeyCode::Digit8 => bones::KeyCode::Key8, + KeyCode::Digit9 => bones::KeyCode::Key9, + KeyCode::Digit0 => bones::KeyCode::Key0, + KeyCode::KeyA => bones::KeyCode::A, + KeyCode::KeyB => bones::KeyCode::B, + KeyCode::KeyC => bones::KeyCode::C, + KeyCode::KeyD => bones::KeyCode::D, + KeyCode::KeyE => bones::KeyCode::E, + KeyCode::KeyF => bones::KeyCode::F, + KeyCode::KeyG => bones::KeyCode::G, + KeyCode::KeyH => bones::KeyCode::H, + KeyCode::KeyI => bones::KeyCode::I, + KeyCode::KeyJ => bones::KeyCode::J, + KeyCode::KeyK => bones::KeyCode::K, + KeyCode::KeyL => bones::KeyCode::L, + KeyCode::KeyM => bones::KeyCode::M, + KeyCode::KeyN => bones::KeyCode::N, + KeyCode::KeyO => bones::KeyCode::O, + KeyCode::KeyP => bones::KeyCode::P, + KeyCode::KeyQ => bones::KeyCode::Q, + KeyCode::KeyR => bones::KeyCode::R, + KeyCode::KeyS => bones::KeyCode::S, + KeyCode::KeyT => bones::KeyCode::T, + KeyCode::KeyU => bones::KeyCode::U, + KeyCode::KeyV => bones::KeyCode::V, + KeyCode::KeyW => bones::KeyCode::W, + KeyCode::KeyX => bones::KeyCode::X, + KeyCode::KeyY => bones::KeyCode::Y, + KeyCode::KeyZ => bones::KeyCode::Z, + KeyCode::Escape => bones::KeyCode::Escape, + KeyCode::F1 => bones::KeyCode::F1, + KeyCode::F2 => bones::KeyCode::F2, + KeyCode::F3 => bones::KeyCode::F3, + KeyCode::F4 => bones::KeyCode::F4, + KeyCode::F5 => bones::KeyCode::F5, + KeyCode::F6 => bones::KeyCode::F6, + KeyCode::F7 => bones::KeyCode::F7, + KeyCode::F8 => bones::KeyCode::F8, + KeyCode::F9 => bones::KeyCode::F9, + KeyCode::F10 => bones::KeyCode::F10, + KeyCode::F11 => bones::KeyCode::F11, + KeyCode::F12 => bones::KeyCode::F12, + KeyCode::F13 => bones::KeyCode::F13, + KeyCode::F14 => bones::KeyCode::F14, + KeyCode::F15 => bones::KeyCode::F15, + KeyCode::F16 => bones::KeyCode::F16, + KeyCode::F17 => bones::KeyCode::F17, + KeyCode::F18 => bones::KeyCode::F18, + KeyCode::F19 => bones::KeyCode::F19, + KeyCode::F20 => bones::KeyCode::F20, + KeyCode::F21 => bones::KeyCode::F21, + KeyCode::F22 => bones::KeyCode::F22, + KeyCode::F23 => bones::KeyCode::F23, + KeyCode::F24 => bones::KeyCode::F24, + KeyCode::PrintScreen => bones::KeyCode::Snapshot, + // Normally on the same key as PrintScreen, and we are already mapping it + //KeyCode::PrintScreen => bones::KeyCode::Sysrq, + KeyCode::ScrollLock => bones::KeyCode::Scroll, + KeyCode::Pause => bones::KeyCode::Pause, + KeyCode::Insert => bones::KeyCode::Insert, + KeyCode::Home => bones::KeyCode::Home, + KeyCode::Delete => bones::KeyCode::Delete, + KeyCode::End => bones::KeyCode::End, + KeyCode::PageDown => bones::KeyCode::PageDown, + KeyCode::PageUp => bones::KeyCode::PageUp, + KeyCode::ArrowLeft => bones::KeyCode::Left, + KeyCode::ArrowUp => bones::KeyCode::Up, + KeyCode::ArrowRight => bones::KeyCode::Right, + KeyCode::ArrowDown => bones::KeyCode::Down, + KeyCode::Backspace => bones::KeyCode::Back, + KeyCode::Enter => bones::KeyCode::Return, + KeyCode::Space => bones::KeyCode::Space, + KeyCode::NumLock => bones::KeyCode::Numlock, + KeyCode::Numpad0 => bones::KeyCode::Numpad0, + KeyCode::Numpad1 => bones::KeyCode::Numpad1, + KeyCode::Numpad2 => bones::KeyCode::Numpad2, + KeyCode::Numpad3 => bones::KeyCode::Numpad3, + KeyCode::Numpad4 => bones::KeyCode::Numpad4, + KeyCode::Numpad5 => bones::KeyCode::Numpad5, + KeyCode::Numpad6 => bones::KeyCode::Numpad6, + KeyCode::Numpad7 => bones::KeyCode::Numpad7, + KeyCode::Numpad8 => bones::KeyCode::Numpad8, + KeyCode::Numpad9 => bones::KeyCode::Numpad9, + KeyCode::NumpadAdd => bones::KeyCode::NumpadAdd, + KeyCode::Equal => bones::KeyCode::Equals, + // Winit doesn't differentiate both '+' and '=', considering they are usually + // on the same physical key, and we are already mapping it + //KeyCode::Equal => bones::KeyCode::Plus, + KeyCode::Backslash => bones::KeyCode::Backslash, + // LaunchApp2 is sometimes named Calculator + KeyCode::LaunchApp2 => bones::KeyCode::Calculator, + KeyCode::CapsLock => bones::KeyCode::Capital, + KeyCode::Comma => bones::KeyCode::Comma, + KeyCode::Convert => bones::KeyCode::Convert, + KeyCode::NumpadDecimal => bones::KeyCode::NumpadDecimal, + KeyCode::NumpadDivide => bones::KeyCode::NumpadDivide, + KeyCode::Backquote => bones::KeyCode::Grave, + KeyCode::AltLeft => bones::KeyCode::AltLeft, + KeyCode::BracketLeft => bones::KeyCode::BracketLeft, + KeyCode::ControlLeft => bones::KeyCode::ControlLeft, + KeyCode::ShiftLeft => bones::KeyCode::ShiftLeft, + KeyCode::SuperLeft => bones::KeyCode::SuperLeft, + KeyCode::LaunchMail => bones::KeyCode::Mail, + KeyCode::MediaSelect => bones::KeyCode::MediaSelect, + KeyCode::MediaStop => bones::KeyCode::MediaStop, + KeyCode::Minus => bones::KeyCode::Minus, + KeyCode::NumpadMultiply => bones::KeyCode::NumpadMultiply, + KeyCode::AudioVolumeMute => bones::KeyCode::Mute, + // LaunchApp1 is sometimes named MyComputer + KeyCode::LaunchApp1 => bones::KeyCode::MyComputer, + KeyCode::MediaTrackNext => bones::KeyCode::NextTrack, + KeyCode::NumpadComma => bones::KeyCode::NumpadComma, + KeyCode::NumpadEnter => bones::KeyCode::NumpadEnter, + KeyCode::NumpadEqual => bones::KeyCode::NumpadEquals, + KeyCode::Period => bones::KeyCode::Period, + KeyCode::MediaPlayPause => bones::KeyCode::PlayPause, + KeyCode::Power => bones::KeyCode::Power, + KeyCode::MediaTrackPrevious => bones::KeyCode::PrevTrack, + KeyCode::AltRight => bones::KeyCode::AltRight, + KeyCode::BracketRight => bones::KeyCode::BracketRight, + KeyCode::ControlRight => bones::KeyCode::ControlRight, + KeyCode::ShiftRight => bones::KeyCode::ShiftRight, + KeyCode::SuperRight => bones::KeyCode::SuperRight, + KeyCode::Semicolon => bones::KeyCode::Semicolon, + KeyCode::Slash => bones::KeyCode::Slash, + KeyCode::Sleep => bones::KeyCode::Sleep, + KeyCode::NumpadSubtract => bones::KeyCode::NumpadSubtract, + KeyCode::Tab => bones::KeyCode::Tab, + KeyCode::AudioVolumeDown => bones::KeyCode::VolumeDown, + KeyCode::AudioVolumeUp => bones::KeyCode::VolumeUp, + KeyCode::WakeUp => bones::KeyCode::Wake, + KeyCode::BrowserBack => bones::KeyCode::WebBack, + KeyCode::BrowserFavorites => bones::KeyCode::WebFavorites, + KeyCode::BrowserForward => bones::KeyCode::WebForward, + KeyCode::BrowserHome => bones::KeyCode::WebHome, + KeyCode::BrowserRefresh => bones::KeyCode::WebRefresh, + KeyCode::BrowserSearch => bones::KeyCode::WebSearch, + KeyCode::BrowserStop => bones::KeyCode::WebStop, + KeyCode::IntlYen => bones::KeyCode::Yen, + KeyCode::Copy => bones::KeyCode::Copy, + KeyCode::Paste => bones::KeyCode::Paste, + KeyCode::Cut => bones::KeyCode::Cut, + // These are not on the latest winit version, + // need to figure out how to handle them. + //KeyCode:: => bones::KeyCode::AbntC1, + //KeyCode:: => bones::KeyCode::AbntC2, + //KeyCode:: => bones::KeyCode::Apostrophe, + //KeyCode:: => bones::KeyCode::Asterisk, + + // Not sure how to implement this, maybe NonConvert? + //KeyCode::NonConvert => bones::KeyCode::NoConvert, + + // Not sure how to implement this + //KeyCode:: => bones::KeyCode::At, + //KeyCode:: => bones::KeyCode::Ax, + //KeyCode:: => bones::KeyCode::Colon, + //KeyCode:: => bones::KeyCode::Kana, + //KeyCode:: => bones::KeyCode::Kanji, + //KeyCode:: => bones::KeyCode::Unlabeled, + //KeyCode:: => bones::KeyCode::Oem102, + //KeyCode:: => bones::KeyCode::Underline, + //KeyCode:: => bones::KeyCode::Stop, + + // I think this is the same as PageUp and PageDown keys, but not sure. + // tho they are already mapped + //KeyCode::PageUp => bones::KeyCode::NavigateForward, + //KeyCode::PageDown => bones::KeyCode::NavigateBackward, + + // These are named keys now on winit, need to + // figure out how to handle them. + //NamedKey::Compose => bones::KeyCode::Compose, + //NamedKey::Caret => bones::KeyCode::Caret, + + // Not sure what this is, could be MediaApps or ContextMenu, or both + //KeyCode::MediaApps | KeyCode::ContextMenu => bones::KeyCode::Apps, + _ => unimplemented!(), + } + } +} diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 36f06b7d94..b793a398b1 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -1,4 +1,5 @@ use bevy_tasks::{IoTaskPool, TaskPool}; +use convert::IntoBones; use crossbeam_channel::{unbounded, Receiver, Sender}; use pollster::FutureExt; use std::{ @@ -15,8 +16,13 @@ use winit::{ window::{Window, WindowId}, }; -use bones_framework::{prelude::{self as bones, BitSet, ComponentIterBitset}, glam::*}; +use bones_framework::{ + glam::*, + input::gilrs::process_gamepad_events, + prelude::{self as bones, BitSet, ComponentIterBitset}, +}; +mod convert; mod texture; /// The prelude @@ -195,11 +201,14 @@ impl BonesWgpuRenderer { self.game.init_shared_resource::(); self.game.init_shared_resource::(); self.game.init_shared_resource::(); + self.game + .init_shared_resource::(); + self.game + .init_shared_resource::(); //Insert needed systems for (_, session) in self.game.sessions.iter_mut() { session.add_system_to_stage(bones::First, load_sprite); - session.add_system_to_stage(bones::First, insert_bones_input); } // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate. @@ -226,7 +235,6 @@ impl BonesWgpuRenderer { } } -//TODO Fix sprite deletion fn load_sprite( entities: bones::Res, sprites: bones::Comp, @@ -258,8 +266,6 @@ fn load_sprite( } } -fn insert_bones_input() {} - #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { @@ -512,13 +518,19 @@ impl State { .unwrap_or_else(|| panic!("Missing transform for entity {:?}", entity)); // Get the 4×4 transform matrix. - let mat4 = transform.to_matrix(); + let mat4 = transform.to_matrix_with_pivots(Vec3::ZERO, Vec3::ZERO); // Compute transformed vertices on the CPU. let transformed_vertices: Vec = VERTICES .iter() .map(|v| Vertex { - position: transform_vertex(v.position.into(), mat4, transform.translation).into(), + position: transform_vertex( + v.position.into(), + mat4, + Vec3::ZERO, // Use Vec3::ZERO as the pivot point for transformations + ) + .into(), + tex_coords: v.tex_coords, // Texture coordinates remain unchanged. }) .collect(); @@ -613,6 +625,11 @@ impl ApplicationHandler for App { fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { let state = self.state.as_mut().unwrap(); + // TODO: investigate possible ways to avoid allocating vectors every frame for event lists. + // TODO: Maybe add some multithreading for the diferent fors in the function? + let mut keyboard_inputs = bones::KeyboardInputs::default(); + let mut wheel_events = Vec::new(); + let mut button_events = Vec::new(); for (texture, entity) in self.receiver.try_iter() { let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -651,23 +668,105 @@ impl ApplicationHandler for App { // here as this event is always followed up by redraw request. state.resize(size); } + WindowEvent::KeyboardInput { event, .. } => { + let ev = match event.physical_key { + winit::keyboard::PhysicalKey::Code(code) => bones::KeyboardEvent { + scan_code: bones::Unset, + key_code: bones::Set(code.into_bones()), + button_state: event.state.into_bones(), + }, + winit::keyboard::PhysicalKey::Unidentified(native_key_code) => { + let scan_code = match native_key_code { + winit::keyboard::NativeKeyCode::Android(u) => bones::Set(u), + winit::keyboard::NativeKeyCode::MacOS(u) => bones::Set(u as u32), + winit::keyboard::NativeKeyCode::Windows(u) => bones::Set(u as u32), + winit::keyboard::NativeKeyCode::Xkb(u) => bones::Set(u), + winit::keyboard::NativeKeyCode::Unidentified => bones::Unset, + }; + bones::KeyboardEvent { + scan_code, + key_code: bones::Unset, + button_state: event.state.into_bones(), + } + } + }; + keyboard_inputs.key_events.push(ev); + } + WindowEvent::MouseWheel { delta, .. } => { + let ev: bones::MouseScrollEvent = delta.into_bones(); + wheel_events.push(ev); + } + WindowEvent::MouseInput { state, button, .. } => { + let ev = bones::MouseButtonEvent { + button: button.into_bones(), + state: state.into_bones(), + }; + button_events.push(ev); + } + WindowEvent::CursorMoved { position, .. } => { + let screen_pos = Some(Vec2::new(position.x as f32, position.y as f32)); + self.game + .insert_shared_resource(bones::MouseScreenPosition(screen_pos)); + } + WindowEvent::CursorLeft { .. } => { + self.game + .insert_shared_resource(bones::MouseScreenPosition(None)); + } _ => (), } + + // Add the game inputs + //TODO: Add world position + //self.game.insert_shared_resource(MouseWorldPosition(world_pos)); + self.game + .shared_resource_mut::() + .unwrap() + .wheel_events = wheel_events; + self.game + .shared_resource_mut::() + .unwrap() + .button_events = button_events; + self.game.insert_shared_resource(keyboard_inputs); + self.game.insert_shared_resource(process_gamepad_events()); + } + + fn device_event( + &mut self, + _event_loop: &ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + let mut movement = Vec2::default(); + + if let winit::event::DeviceEvent::MouseMotion { delta } = event { + let delta = Vec2::new(delta.0 as f32, delta.1 as f32); + movement += delta; + }; + + self.game + .shared_resource_mut::() + .unwrap() + .movement = movement; } } //TODO Add quaternion rotations, and move this calculation to -// wgsl code +// wgsl code fn transform_vertex(pos: Vec3, transform: Mat4, pivot: Vec3) -> Vec3 { + // Translate the vertex to the pivot point. let to_origin = Mat4::from_translation(-pivot); let from_origin = Mat4::from_translation(pivot); + + // Apply the transformation: first translate to origin, then transform, then translate back. let combined = from_origin * transform * to_origin; - + + // Apply the transformation matrix to the position let pos4 = combined * pos.extend(1.0); - pos4.truncate() / pos4.w -} + // Return the truncated Vec3 position (ignoring the w-component) + pos4.truncate() +} /// A [`bones::AssetIo`] configured for web and local file access pub fn asset_io(asset_dir: &Path, packs_dir: &Path) -> impl bones::AssetIo + 'static { From 2752016589fb83ac921a109226d0d216eb0c0acd Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Thu, 17 Apr 2025 20:19:04 -0300 Subject: [PATCH 04/16] Added viewport and multiple cameras support --- demos/hello_world_wgpu/src/main.rs | 68 +++-- .../bones_framework/src/render/camera.rs | 8 + .../bones_framework/src/render/color.rs | 17 ++ .../bones_framework/src/render/transform.rs | 1 - .../bones_wgpu_renderer/src/lib.rs | 278 +++++++++++++----- 5 files changed, 279 insertions(+), 93 deletions(-) diff --git a/demos/hello_world_wgpu/src/main.rs b/demos/hello_world_wgpu/src/main.rs index cb92a80de6..9509e6c596 100644 --- a/demos/hello_world_wgpu/src/main.rs +++ b/demos/hello_world_wgpu/src/main.rs @@ -68,6 +68,19 @@ fn sprite_demo_startup( meta: Root, ) { spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + let second_camera = Camera { + priority: 1, + viewport: Maybe::Set(Viewport { + position: UVec2::new(10, 10), + size: UVec2::new(100, 100), + ..Default::default() + }), + background_color: Color::ORANGE, + ..Default::default() + }; + let camera_ent = entities.create(); + transforms.insert(camera_ent, default()); + cameras.insert(camera_ent, second_camera); let sprite_ent = entities.create(); transforms.insert(sprite_ent, default()); @@ -99,6 +112,7 @@ fn move_sprite( input: Res, input_mouse: Res, gamepad: Res, + camera: Comp, //mouse_position: Res ) { let mut left = false; @@ -120,7 +134,36 @@ fn move_sprite( } } - for (_ent, (_sprite, transform)) in entities.iter_with((&sprite, &mut transforms)) { + let mut i = 0; + for (_ent, (_sprite, transform)) in entities.iter_with((&camera, &mut transforms)) { + if i == 0 { + //mouse and keyboard + if left { + transform.translation.x -= 0.1; + } + if right { + transform.translation.x += 0.1; + } + if up { + transform.translation.y += 0.1; + } + if down { + transform.translation.y -= 0.1; + } + if rotate_left { + transform.rotation.z -= 0.1; + } + if rotate_right { + transform.rotation.z += 0.1; + } + + for event in &input_mouse.wheel_events { + transform.scale += event.movement.y * 0.1; + } + i += 1; + continue; + } + //gamepad for event in gamepad.gamepad_events.iter() { match event { GamepadEvent::Axis(axis) => match axis.axis { @@ -155,29 +198,6 @@ fn move_sprite( _ => (), } } - - if left { - transform.translation.x -= 0.1; - } - if right { - transform.translation.x += 0.1; - } - if up { - transform.translation.y += 0.1; - } - if down { - transform.translation.y -= 0.1; - } - if rotate_left { - transform.rotation.z -= 0.1; - } - if rotate_right { - transform.rotation.z += 0.1; - } - - for event in &input_mouse.wheel_events { - transform.scale += event.movement.y * 0.1; - } } } diff --git a/framework_crates/bones_framework/src/render/camera.rs b/framework_crates/bones_framework/src/render/camera.rs index c5d87b668a..4df73a3f73 100644 --- a/framework_crates/bones_framework/src/render/camera.rs +++ b/framework_crates/bones_framework/src/render/camera.rs @@ -11,6 +11,8 @@ use crate::prelude::*; // TODO: make repr(C) when `Option`s are supported. // We don't have `Option` support in `bones_schema` right now. // Once we do, we can make this type `#[repr(C)]` instead of `#[schema(opaque)]`. +// TODO: Support different Render Targets, for example, multiple windows, +// for now the camera will always render to the main window. #[repr(C)] pub struct Camera { /// The height of the camera in in-game pixels. @@ -27,6 +29,10 @@ pub struct Camera { pub viewport: Maybe, /// Cameras with a higher priority will be rendered on top of cameras with a lower priority. pub priority: i32, + /// The color to clear the screen to before rendering. + pub background_color: Color, + /// Whether or not the camera should draw the background color. + pub draw_background_color: bool, } /// A size setting for a camera. @@ -68,6 +74,8 @@ impl Default for Camera { viewport: Unset, priority: 0, size: default(), + background_color: Color::GRAY, + draw_background_color: true, } } } diff --git a/framework_crates/bones_framework/src/render/color.rs b/framework_crates/bones_framework/src/render/color.rs index 3daf4f6fd8..5c5fe5d7ff 100644 --- a/framework_crates/bones_framework/src/render/color.rs +++ b/framework_crates/bones_framework/src/render/color.rs @@ -300,6 +300,23 @@ impl Color { } => [red, green, blue, alpha], } } + + /// Converts a `Color` to a `[u8; 4]` from sRGB colorspace + pub fn as_rgba_u8(self: Color) -> [u8; 4] { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => [ + (red * 255.0) as u8, + (green * 255.0) as u8, + (blue * 255.0) as u8, + (alpha * 255.0) as u8, + ], + } + } } impl Default for Color { diff --git a/framework_crates/bones_framework/src/render/transform.rs b/framework_crates/bones_framework/src/render/transform.rs index fafeb3c60a..53aa339e11 100644 --- a/framework_crates/bones_framework/src/render/transform.rs +++ b/framework_crates/bones_framework/src/render/transform.rs @@ -55,7 +55,6 @@ impl Transform { let rotation = Mat4::from_rotation_z(angle); let scale_rotation = rotation * Mat4::from_scale(self.scale); - Mat4::from_translation(self.translation) * scale_rotation } diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index b793a398b1..882ea1db88 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -1,6 +1,7 @@ use bevy_tasks::{IoTaskPool, TaskPool}; use convert::IntoBones; use crossbeam_channel::{unbounded, Receiver, Sender}; +use image::{DynamicImage, RgbaImage}; use pollster::FutureExt; use std::{ path::{Path, PathBuf}, @@ -466,14 +467,6 @@ impl State { ..Default::default() }); - // Renders a gray background - let gray = wgpu::Color { - r: 0.10, - g: 0.10, - b: 0.10, - ..Default::default() - }; - // Create the command encoder. let mut encoder = self.device.create_command_encoder(&Default::default()); @@ -487,73 +480,199 @@ impl State { }); let num_indices = INDICES.len() as u32; - // Create one render pass that clears the screen once. - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &texture_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(gray), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + for (_, session) in sessions.iter() { + //Get cameras and sort them + let cameras = session.world.component::(); + let transforms = session.world.component::(); + let entities = session.world.resource::(); + + let mut bitset = cameras.bitset().clone(); + bitset.bit_and(transforms.bitset()); - // Render each sprite with its own transform. - for (bind_group, entity) in &self.sprites { - // Get the transform from the ECS. - let transform = sessions - .iter() - .find_map(|(_, session)| { - session + let mut cameras_vec = vec![]; + for ent in entities.iter_with_bitset(&bitset) { + let Some(camera) = cameras.get(ent) else { + continue; + }; + + if !camera.active { + continue; + } + + // Get the transform from the ECS. + let Some(transform) = transforms.get(ent).cloned() else { + continue; + }; + + cameras_vec.push((camera, transform)); + } + cameras_vec.sort_by(|a, b| a.0.priority.cmp(&b.0.priority)); + + for (camera, mut camera_transform) in cameras_vec { + // Create one render pass for each camera + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + render_pass.set_pipeline(&self.render_pipeline); + + let scale_ratio; + if let Some(viewport) = camera.viewport.option() { + let size = IVec2::new(self.size.width as i32, self.size.height as i32) + - viewport.position.as_ivec2(); + let size = IVec2::new( + (viewport.size.x as i32).min(size.x), + (viewport.size.y as i32).min(size.y), + ); + + if size.x <= 0 || size.y <= 0 { + continue; + } + + render_pass.set_viewport( + viewport.position.x as f32, + viewport.position.y as f32, + size.x as f32, + size.y as f32, + viewport.depth_min, + viewport.depth_max, + ); + if viewport.size.y <= viewport.size.x { + scale_ratio = Mat4::from_scale(Vec3::new( + size.x as f32 / viewport.size.x as f32, + viewport.size.y as f32 / viewport.size.x as f32 * size.y as f32 + / viewport.size.y as f32, + 1., + )) + .inverse(); + } else { + scale_ratio = Mat4::from_scale(Vec3::new( + viewport.size.x as f32 / viewport.size.y as f32 * size.x as f32 + / viewport.size.x as f32, + size.y as f32 / viewport.size.y as f32, + 1., + )) + .inverse(); + } + } else if self.size.height <= self.size.width { + scale_ratio = Mat4::from_scale(Vec3::new( + 1., + self.size.height as f32 / self.size.width as f32, + 1., + )) + .inverse(); + } else { + scale_ratio = Mat4::from_scale(Vec3::new( + self.size.width as f32 / self.size.height as f32, + 1., + 1., + )) + .inverse(); + } + + if camera.draw_background_color { + let vertex_buffer = + self.device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(&VERTICES_FULL), + usage: wgpu::BufferUsages::VERTEX, + }); + + let rgba = RgbaImage::from_pixel( + 1, + 1, + image::Rgba(camera.background_color.as_rgba_u8()), + ); + let img = DynamicImage::ImageRgba8(rgba); + let texture = Texture::from_image( + &self.device, + &self.queue, + &img, + Some("background_color"), + false, + ) + .unwrap(); + let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.render_pipeline.get_bind_group_layout(0), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture.sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); + + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); + render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..num_indices, 0, 0..1); + } + + // Render each sprite with its own transform. + for (bind_group, entity) in &self.sprites { + // Get the entity transform from the ECS. + let Some(transform) = session .world .component::() .get(*entity) .cloned() - }) - .unwrap_or_else(|| panic!("Missing transform for entity {:?}", entity)); - - // Get the 4×4 transform matrix. - let mat4 = transform.to_matrix_with_pivots(Vec3::ZERO, Vec3::ZERO); - - // Compute transformed vertices on the CPU. - let transformed_vertices: Vec = VERTICES - .iter() - .map(|v| Vertex { - position: transform_vertex( - v.position.into(), - mat4, - Vec3::ZERO, // Use Vec3::ZERO as the pivot point for transformations - ) - .into(), - - tex_coords: v.tex_coords, // Texture coordinates remain unchanged. - }) - .collect(); - - // Create a dynamic vertex buffer with the transformed vertices. - let vertex_buffer = self - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Transformed Vertex Buffer"), - contents: bytemuck::cast_slice(&transformed_vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, bind_group, &[]); - render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); - render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16); - render_pass.draw_indexed(0..num_indices, 0, 0..1); + else { + continue; + }; + + camera_transform.translation.z = 0.; + let camera_mat4 = camera_transform.to_matrix().inverse(); + + // Get the 4×4 transform matrix. + let mat4 = scale_ratio * camera_mat4 * transform.to_matrix(); + + // Compute transformed vertices on the CPU. + let transformed_vertices: Vec = VERTICES + .iter() + .map(|v: &Vertex| Vertex { + position: transform_vertex( + v.position.into(), + mat4, + Vec3::ZERO, // Use Vec3::ZERO as the pivot point for transformations + ) + .into(), + tex_coords: v.tex_coords, // Texture coordinates remain unchanged. + }) + .collect(); + + // Create a dynamic vertex buffer with the transformed vertices. + let vertex_buffer = + self.device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Transformed Vertex Buffer"), + contents: bytemuck::cast_slice(&transformed_vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + + render_pass.set_bind_group(0, bind_group, &[]); + render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); + render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..num_indices, 0, 0..1); + } + } } - // End the renderpass. - drop(render_pass); - // Submit the command queue. self.queue.submit([encoder.finish()]); self.window.pre_present_notify(); @@ -584,6 +703,29 @@ const VERTICES: &[Vertex] = &[ }, ]; +const VERTICES_FULL: &[Vertex] = &[ + // Top-left vertex + Vertex { + position: [-1.0, 1.0, 0.0], + tex_coords: [0.0, 0.0], + }, + // Bottom-left vertex + Vertex { + position: [-1.0, -1.0, 0.0], + tex_coords: [0.0, 1.0], + }, + // Bottom-right vertex + Vertex { + position: [1.0, -1.0, 0.0], + tex_coords: [1.0, 1.0], + }, + // Top-right vertex + Vertex { + position: [1.0, 1.0, 0.0], + tex_coords: [1.0, 0.0], + }, +]; + const INDICES: &[u16] = &[ 0, 1, 2, // first triangle 0, 2, 3, // second triangle From 44dd406d64de4ef6a0db65cd4e90719d43ba8c00 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Tue, 29 Apr 2025 17:56:37 -0300 Subject: [PATCH 05/16] Added atlas and changes to the GPU data from bones --- .../assets/atlas/fishy-body(2).png | Bin 0 -> 26251 bytes .../assets/atlas/fishy-body.atlas.yaml | 4 + .../assets/atlas/fishy-body.png | Bin 0 -> 26309 bytes demos/hello_world_wgpu/assets/game.yaml | 3 +- demos/hello_world_wgpu/src/main.rs | 54 +++- .../bones_wgpu_renderer/src/atlas_sprite.wgsl | 93 +++++++ .../bones_wgpu_renderer/src/lib.rs | 234 +++++++++++++++++- .../bones_wgpu_renderer/src/shader.wgsl | 33 --- 8 files changed, 374 insertions(+), 47 deletions(-) create mode 100755 demos/hello_world_wgpu/assets/atlas/fishy-body(2).png create mode 100644 demos/hello_world_wgpu/assets/atlas/fishy-body.atlas.yaml create mode 100644 demos/hello_world_wgpu/assets/atlas/fishy-body.png create mode 100644 framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl delete mode 100644 framework_crates/bones_wgpu_renderer/src/shader.wgsl diff --git a/demos/hello_world_wgpu/assets/atlas/fishy-body(2).png b/demos/hello_world_wgpu/assets/atlas/fishy-body(2).png new file mode 100755 index 0000000000000000000000000000000000000000..b092cf8d3de97d5ff28a4c041ce7c1de5eb06afe GIT binary patch literal 26251 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%0AJk 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, sprite2: Handle, + atlas: Handle, } fn main() { @@ -63,6 +64,7 @@ fn sprite_demo_plugin(session: &mut Session) { fn sprite_demo_startup( mut entities: ResMut, mut sprites: CompMut, + mut atlas_sprites: CompMut, mut transforms: CompMut, mut cameras: CompMut, meta: Root, @@ -100,6 +102,23 @@ fn sprite_demo_startup( sprite_ent, Sprite { image: meta.sprite2, + flip_x: true, + ..default() + }, + ); + + let atlas_ent = entities.create(); + transforms.insert( + atlas_ent, + Transform::from_translation(Vec3::new(1.5, 1.5, 0.0)), + ); + atlas_sprites.insert( + atlas_ent, + AtlasSprite { + atlas: meta.atlas, + index: 0, + flip_x: false, + flip_y: false, ..default() }, ); @@ -107,7 +126,8 @@ fn sprite_demo_startup( fn move_sprite( entities: Res, - sprite: Comp, + mut atlases: CompMut, + mut sprites: CompMut, mut transforms: CompMut, input: Res, input_mouse: Res, @@ -121,6 +141,10 @@ fn move_sprite( let mut down = false; let mut rotate_left = false; let mut rotate_right = false; + let mut idx_up = false; + let mut idx_down = false; + let mut flip_x = false; + let mut flip_y = false; for input in &input.key_events { match input.key_code { @@ -130,10 +154,38 @@ fn move_sprite( Set(KeyCode::S) => down = true, Set(KeyCode::Q) => rotate_left = true, Set(KeyCode::E) => rotate_right = true, + Set(KeyCode::P) => idx_up = true, + Set(KeyCode::O) => idx_down = true, + Set(KeyCode::L) => flip_y = true, + Set(KeyCode::K) => flip_x = true, _ => (), } } + for (_, atlas) in entities.iter_with(&mut atlases) { + if idx_up { + atlas.index += 1; + } + if idx_down { + atlas.index -= 1; + } + if flip_x { + atlas.flip_x = !atlas.flip_x; + } + if flip_y { + atlas.flip_y = !atlas.flip_y; + } + } + + for (_, sprite) in entities.iter_with(&mut sprites) { + if flip_x { + sprite.flip_x = !sprite.flip_x; + } + if flip_y { + sprite.flip_y = !sprite.flip_y; + } + } + let mut i = 0; for (_ent, (_sprite, transform)) in entities.iter_with((&camera, &mut transforms)) { if i == 0 { diff --git a/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl b/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl new file mode 100644 index 0000000000..ab7b9d3a8b --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl @@ -0,0 +1,93 @@ +struct AtlasSpriteUniform { + // Atlas parameters + tile_size: vec2, + image_size: vec2, + padding: vec2, + offset: vec2, + columns: u32, + index: u32, + + // State flags + use_atlas: u32, + flip_x: u32, + flip_y: u32, + + // Padding + _pad0: u32, + + color_tint: vec4, +}; + +@group(0) @binding(0) var spriteUniform: AtlasSpriteUniform; +@group(0) @binding(1) var diffuseTex: texture_2d; +@group(0) @binding(2) var texSampler: sampler; + +struct VertexInput { + @location(0) position: vec2, + @location(1) uv: vec2, +}; + +struct VertexOutput { + @builtin(position) clipPos: vec4, + @location(0) uv: vec2, +}; + +@vertex +fn vs_main(in: VertexInput) -> VertexOutput { + return VertexOutput( + vec4(in.position, 0.0, 1.0), + in.uv + ); +} + +fn compute_atlas_uv(base_uv: vec2) -> vec2 { + // Precompute normalized dimensions + let tile_scale = spriteUniform.tile_size / spriteUniform.image_size; + let padding_scale = spriteUniform.padding / spriteUniform.image_size; + let base_offset = spriteUniform.offset / spriteUniform.image_size; + + // Calculate grid position + let grid_pos = vec2( + spriteUniform.index % spriteUniform.columns, + spriteUniform.index / spriteUniform.columns + ); + + // Calculate tile origin in UV space + let tile_step = tile_scale + padding_scale; + let origin = base_offset + vec2(grid_pos) * tile_step; + + // Calculate tile center and aspect ratio correction + let tile_center = origin + tile_scale * 0.5; + let aspect_ratio = normalize(1.0 / spriteUniform.tile_size); + + // Base UV calculation with aspect correction + var uv = tile_center + (base_uv - 0.5) * tile_scale * aspect_ratio; + + // Apply flipping transformations + if (spriteUniform.flip_x == 1u) { + uv.x = tile_center.x + (0.5 - base_uv.x) * tile_scale.x * aspect_ratio.x; + } + if (spriteUniform.flip_y == 1u) { + uv.y = tile_center.y + (0.5 - base_uv.y) * tile_scale.y * aspect_ratio.y; + } + + return uv; +} + +fn apply_flip(uv: vec2) -> vec2 { + var fuv = uv; + if (spriteUniform.flip_x == 1u) { + fuv.x = 1.0 - fuv.x; + } + if (spriteUniform.flip_y == 1u) { + fuv.y = 1.0 - fuv.y; + } + return fuv; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + var uv = select(apply_flip(in.uv), compute_atlas_uv(in.uv), spriteUniform.use_atlas == 1u); + return textureSample(diffuseTex, texSampler, uv) * + select(vec4(1.0, 1.0, 1.0, 1.0), spriteUniform.color_tint, spriteUniform.use_atlas == 1u); +} \ No newline at end of file diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 882ea1db88..71bf1acf98 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -60,9 +60,9 @@ impl WgpuQueue { #[repr(C)] #[schema(opaque)] #[schema(no_default)] -struct TextureSender(Sender<(Texture, bones::Entity)>); +struct TextureSender(Sender<(Texture, bones::Entity, Arc)>); -type TextureReceiver = Receiver<(Texture, bones::Entity)>; +type TextureReceiver = Receiver<(Texture, bones::Entity, Arc)>; // Indicates that we already loaded the sprite texture // and sent it to the wgpu thread @@ -83,6 +83,7 @@ struct App { texture_bind_group_layout: Arc, receiver: TextureReceiver, game: bones::Game, + _now: Instant, } /// Renderer for [`bones_framework`] [`Game`][bones::Game]s using wgpu. @@ -139,6 +140,16 @@ impl BonesWgpuRenderer { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { multisampled: false, @@ -148,7 +159,7 @@ impl BonesWgpuRenderer { count: None, }, wgpu::BindGroupLayoutEntry { - binding: 1, + binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, // This should match the filterable field of the textures ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), @@ -210,6 +221,9 @@ impl BonesWgpuRenderer { //Insert needed systems for (_, session) in self.game.sessions.iter_mut() { session.add_system_to_stage(bones::First, load_sprite); + session.add_system_to_stage(bones::First, load_atlas_sprite); + session.add_system_to_stage(bones::First, update_atlas_uniforms); + session.add_system_to_stage(bones::First, update_sprite_uniforms); } // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate. @@ -231,11 +245,73 @@ impl BonesWgpuRenderer { texture_bind_group_layout, receiver, game: self.game, + _now: Instant::now(), }; event_loop.run_app(&mut app).unwrap(); } } +// Uniform data struct matching the WGSL `AtlasSpriteUniform` layout. +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)] +pub struct AtlasSpriteUniform { + // Atlas parameters + pub tile_size: [f32; 2], + pub image_size: [f32; 2], + pub padding: [f32; 2], //TODO Check if it really works + pub offset: [f32; 2], //TODO Check if it really works + pub columns: u32, + pub index: u32, + + // State flags + pub use_atlas: u32, + pub flip_x: u32, + pub flip_y: u32, + pub _pad: [u32; 3], // Explicit padding + + // Color tint + pub color_tint: [f32; 4], +} + +#[derive(bones_schema::HasSchema, Clone)] +#[repr(C)] +#[schema(opaque)] +#[schema(no_default)] +pub struct AtlasSpriteBuffer(Arc); + +impl AtlasSpriteUniform { + fn from_atlas_sprite(atlas_sprite: &bones::AtlasSprite, atlas: &bones::Atlas) -> Self { + let image_size = [ + atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), + atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), + ]; + + Self { + tile_size: atlas.tile_size.into(), + columns: atlas.columns, + padding: atlas.padding.into(), + offset: atlas.offset.into(), + index: atlas_sprite.index, + image_size, + use_atlas: 1, + flip_x: atlas_sprite.flip_x as u32, + flip_y: atlas_sprite.flip_y as u32, + color_tint: atlas_sprite.color.as_rgba_f32(), + ..Default::default() + } + } + + fn from_sprite(sprite: &bones::Sprite) -> Self { + Self { + color_tint: sprite.color.as_rgba_f32(), + flip_x: sprite.flip_x as u32, + flip_y: sprite.flip_y as u32, + use_atlas: 0, + ..Default::default() + } + } +} + fn load_sprite( entities: bones::Res, sprites: bones::Comp, @@ -244,14 +320,16 @@ fn load_sprite( queue: bones::Res, texture_sender: bones::Res, pixel_art: bones::Res, + mut buffers: bones::CompMut, mut texture_loaded: bones::CompMut, ) { let mut not_loaded = texture_loaded.bitset().clone(); not_loaded.bit_not(); + not_loaded.bit_and(sprites.bitset()); for entity in entities.iter_with_bitset(¬_loaded) { let Some(sprite) = sprites.get(entity) else { - continue; + unreachable!(); }; //Load and send texture @@ -259,7 +337,30 @@ fn load_sprite( if let bones::Image::Data(img) = &*image { let texture = Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(); - texture_sender.0.send((texture, entity)).unwrap(); + + let atlas_uniform = AtlasSpriteUniform { + use_atlas: 0, + flip_x: sprite.flip_x as u32, + flip_y: sprite.flip_y as u32, + color_tint: sprite.color.as_rgba_f32(), + ..Default::default() + }; + + let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[atlas_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .0 + .send((texture, entity, atlas_sprite_buffer)) + .unwrap(); texture_loaded.insert(entity, TextureLoaded); } else { unreachable!() @@ -267,6 +368,92 @@ fn load_sprite( } } +fn load_atlas_sprite( + entities: bones::Res, + atlas_sprites: bones::Comp, + assets: bones::Res, + device: bones::Res, + queue: bones::Res, + texture_sender: bones::Res, + pixel_art: bones::Res, + mut buffers: bones::CompMut, + mut texture_loaded: bones::CompMut, +) { + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(atlas_sprites.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(atlas_sprite) = atlas_sprites.get(entity) else { + unreachable!(); + }; + + //Load and send texture + let atlas = assets.get(atlas_sprite.atlas); + let image = assets.get(atlas.image); + if let bones::Image::Data(img) = &*image { + let texture = + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(); + // create and send the atlas sprite uniform along with the texture and entity + let uniform = AtlasSpriteUniform::from_atlas_sprite( + atlas_sprite, + &assets.get(atlas_sprite.atlas), + ); + + let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .0 + .send((texture, entity, atlas_sprite_buffer)) + .unwrap(); + texture_loaded.insert(entity, TextureLoaded); + } else { + unreachable!() + }; + } +} + +// System for updating atlas uniforms +fn update_atlas_uniforms( + entities: bones::Res, + atlases: bones::Comp, + mut buffers: bones::CompMut, + assets: bones::Res, + queue: bones::Res, +) { + for (_, (atlas_sprite, atlas_sprite_buffer)) in entities.iter_with((&atlases, &mut buffers)) { + let atlas = assets.get(atlas_sprite.atlas).clone(); + let uniform = AtlasSpriteUniform::from_atlas_sprite(atlas_sprite, &atlas); + queue + .get() + .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + } +} + +// System for updating sprite uniforms +fn update_sprite_uniforms( + entities: bones::Res, + sprites: bones::Comp, + mut buffers: bones::CompMut, + queue: bones::Res, +) { + for (_, (sprite, atlas_sprite_buffer)) in entities.iter_with((&sprites, &mut buffers)) { + let uniform = AtlasSpriteUniform::from_sprite(sprite); + queue + .get() + .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { @@ -356,7 +543,7 @@ impl State { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + source: wgpu::ShaderSource::Wgsl(include_str!("atlas_sprite.wgsl").into()), }); let render_pipeline_layout = @@ -381,8 +568,8 @@ impl State { targets: &[Some(wgpu::ColorTargetState { format: config.format, blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent::REPLACE, - alpha: wgpu::BlendComponent::REPLACE, + color: wgpu::BlendComponent::OVER, + alpha: wgpu::BlendComponent::OVER, }), write_mask: wgpu::ColorWrites::ALL, })], @@ -581,11 +768,12 @@ impl State { } if camera.draw_background_color { + //TODO Stop creating buffers every frame let vertex_buffer = self.device .create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(&VERTICES_FULL), + contents: bytemuck::cast_slice(VERTICES_FULL), usage: wgpu::BufferUsages::VERTEX, }); @@ -603,15 +791,30 @@ impl State { false, ) .unwrap(); + let atlas_sprite_buffer = + self.device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[AtlasSpriteUniform { + use_atlas: 0, + ..Default::default() + }]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.render_pipeline.get_bind_group_layout(0), entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), + resource: atlas_sprite_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 2, resource: wgpu::BindingResource::Sampler(&texture.sampler), }, ], @@ -773,16 +976,20 @@ impl ApplicationHandler for App { let mut wheel_events = Vec::new(); let mut button_events = Vec::new(); - for (texture, entity) in self.receiver.try_iter() { + for (texture, entity, atlas_sprite_buffer) in self.receiver.try_iter() { let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.texture_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), + resource: atlas_sprite_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 1, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 2, resource: wgpu::BindingResource::Sampler(&texture.sampler), }, ], @@ -798,6 +1005,9 @@ impl ApplicationHandler for App { event_loop.exit(); } WindowEvent::RedrawRequested => { + //println!("{}", self.now.elapsed().as_secs_f32()); + //self.now = Instant::now(); + state.render(&self.game.sessions); // Emits a new redraw requested event. state.get_window().request_redraw(); diff --git a/framework_crates/bones_wgpu_renderer/src/shader.wgsl b/framework_crates/bones_wgpu_renderer/src/shader.wgsl deleted file mode 100644 index 656aac8091..0000000000 --- a/framework_crates/bones_wgpu_renderer/src/shader.wgsl +++ /dev/null @@ -1,33 +0,0 @@ -// Vertex shader - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, -} - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - out.tex_coords = model.tex_coords; - out.clip_position = vec4(model.position, 1.0); - return out; -} - -// Fragment shader - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return textureSample(t_diffuse, s_diffuse, in.tex_coords); -} \ No newline at end of file From 82ddc9c88a14bd8236e004fef51e8c18b5a2a5dc Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Fri, 2 May 2025 18:35:58 -0300 Subject: [PATCH 06/16] Added tile rendering --- .../bones_framework/src/render/tilemap.rs | 2 + .../bones_wgpu_renderer/src/lib.rs | 126 +++++++++++++++++- 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/framework_crates/bones_framework/src/render/tilemap.rs b/framework_crates/bones_framework/src/render/tilemap.rs index 2da156303b..74e0aa6db2 100644 --- a/framework_crates/bones_framework/src/render/tilemap.rs +++ b/framework_crates/bones_framework/src/render/tilemap.rs @@ -25,6 +25,8 @@ pub struct Tile { pub flip_x: bool, /// Whether or not to flip tile vertically. pub flip_y: bool, + /// The tile's color tint + pub color: Color, } impl TileLayer { diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 71bf1acf98..f4439cf2ca 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -60,9 +60,9 @@ impl WgpuQueue { #[repr(C)] #[schema(opaque)] #[schema(no_default)] -struct TextureSender(Sender<(Texture, bones::Entity, Arc)>); +struct TextureSender(Sender<(Arc, bones::Entity, Arc)>); -type TextureReceiver = Receiver<(Texture, bones::Entity, Arc)>; +type TextureReceiver = Receiver<(Arc, bones::Entity, Arc)>; // Indicates that we already loaded the sprite texture // and sent it to the wgpu thread @@ -222,8 +222,10 @@ impl BonesWgpuRenderer { for (_, session) in self.game.sessions.iter_mut() { session.add_system_to_stage(bones::First, load_sprite); session.add_system_to_stage(bones::First, load_atlas_sprite); + session.add_system_to_stage(bones::First, load_tile_sprite); session.add_system_to_stage(bones::First, update_atlas_uniforms); session.add_system_to_stage(bones::First, update_sprite_uniforms); + session.add_system_to_stage(bones::First, update_tiles_uniforms); } // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate. @@ -301,6 +303,27 @@ impl AtlasSpriteUniform { } } + fn from_tile(tile: &bones::Tile, atlas: &bones::Atlas) -> Self { + let image_size = [ + atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), + atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), + ]; + + Self { + tile_size: atlas.tile_size.into(), + columns: atlas.columns, + padding: atlas.padding.into(), + offset: atlas.offset.into(), + index: tile.idx, + image_size, + use_atlas: 1, + flip_x: tile.flip_x as u32, + flip_y: tile.flip_y as u32, + color_tint: tile.color.as_rgba_f32(), + ..Default::default() + } + } + fn from_sprite(sprite: &bones::Sprite) -> Self { Self { color_tint: sprite.color.as_rgba_f32(), @@ -335,8 +358,9 @@ fn load_sprite( //Load and send texture let image = assets.get(sprite.image); if let bones::Image::Data(img) = &*image { - let texture = - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(); + let texture = Arc::new( + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), + ); let atlas_uniform = AtlasSpriteUniform { use_atlas: 0, @@ -392,8 +416,9 @@ fn load_atlas_sprite( let atlas = assets.get(atlas_sprite.atlas); let image = assets.get(atlas.image); if let bones::Image::Data(img) = &*image { - let texture = - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(); + let texture = Arc::new( + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), + ); // create and send the atlas sprite uniform along with the texture and entity let uniform = AtlasSpriteUniform::from_atlas_sprite( atlas_sprite, @@ -422,6 +447,68 @@ fn load_atlas_sprite( } } +fn load_tile_sprite( + entities: bones::Res, + tile_layers: bones::Comp, + tiles: bones::Comp, + assets: bones::Res, + device: bones::Res, + queue: bones::Res, + texture_sender: bones::Res, + pixel_art: bones::Res, + mut buffers: bones::CompMut, + mut texture_loaded: bones::CompMut, +) { + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(tile_layers.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(tile_layer) = tile_layers.get(entity) else { + unreachable!(); + }; + + //Load and send texture + let atlas = assets.get(tile_layer.atlas); + let image = assets.get(atlas.image); + if let bones::Image::Data(img) = &*image { + let texture = Arc::new( + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), + ); + + for tile in &tile_layer.tiles { + let Some(tile) = tile else { + continue; + }; + let Some(tile) = tiles.get(*tile) else { + panic!("Couldn't find tile entity!"); + }; + // create and send the atlas sprite uniform along with the texture and entity + let uniform = AtlasSpriteUniform::from_tile(tile, &assets.get(tile_layer.atlas)); + + let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .0 + .send((texture.clone(), entity, atlas_sprite_buffer)) + .unwrap(); + texture_loaded.insert(entity, TextureLoaded); + } + } else { + unreachable!() + }; + } +} + // System for updating atlas uniforms fn update_atlas_uniforms( entities: bones::Res, @@ -454,6 +541,33 @@ fn update_sprite_uniforms( } } +// System for updating tiles uniforms +fn update_tiles_uniforms( + entities: bones::Res, + tile_layers: bones::Comp, + tiles: bones::Comp, + mut buffers: bones::CompMut, + assets: bones::Res, + queue: bones::Res, +) { + for (_, (tile_layer, atlas_sprite_buffer)) in entities.iter_with((&tile_layers, &mut buffers)) { + let atlas = assets.get(tile_layer.atlas).clone(); + for tile in &tile_layer.tiles { + let Some(tile) = tile else { + continue; + }; + let Some(tile) = tiles.get(*tile) else { + panic!("Couldn't find tile entity!"); + }; + + let uniform = AtlasSpriteUniform::from_tile(tile, &atlas); + queue + .get() + .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct Vertex { From 77432b7fe449173e7681dec0aee8dc4d255d3d37 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Sat, 3 May 2025 01:31:18 -0300 Subject: [PATCH 07/16] Organize some code --- .../bones_wgpu_renderer/src/lib.rs | 684 +++++------------- .../bones_wgpu_renderer/src/sprite.rs | 322 +++++++++ 2 files changed, 508 insertions(+), 498 deletions(-) create mode 100644 framework_crates/bones_wgpu_renderer/src/sprite.rs diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index f4439cf2ca..5ce02f4a2a 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -24,8 +24,11 @@ use bones_framework::{ }; mod convert; +mod sprite; mod texture; +use sprite::*; + /// The prelude pub mod prelude { pub use crate::*; @@ -74,18 +77,6 @@ struct TextureLoaded; #[repr(C)] struct PixelArt(bool); -struct App { - state: Option, - instance: Arc, - adapter: Arc, - device: Arc, - queue: Arc, - texture_bind_group_layout: Arc, - receiver: TextureReceiver, - game: bones::Game, - _now: Instant, -} - /// Renderer for [`bones_framework`] [`Game`][bones::Game]s using wgpu. pub struct BonesWgpuRenderer { /// Whether or not to load all assets on startup with a loading screen, @@ -253,347 +244,183 @@ impl BonesWgpuRenderer { } } -// Uniform data struct matching the WGSL `AtlasSpriteUniform` layout. -#[repr(C)] -#[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)] -pub struct AtlasSpriteUniform { - // Atlas parameters - pub tile_size: [f32; 2], - pub image_size: [f32; 2], - pub padding: [f32; 2], //TODO Check if it really works - pub offset: [f32; 2], //TODO Check if it really works - pub columns: u32, - pub index: u32, - - // State flags - pub use_atlas: u32, - pub flip_x: u32, - pub flip_y: u32, - pub _pad: [u32; 3], // Explicit padding - - // Color tint - pub color_tint: [f32; 4], +struct App { + state: Option, + instance: Arc, + adapter: Arc, + device: Arc, + queue: Arc, + texture_bind_group_layout: Arc, + receiver: TextureReceiver, + game: bones::Game, + _now: Instant, } -#[derive(bones_schema::HasSchema, Clone)] -#[repr(C)] -#[schema(opaque)] -#[schema(no_default)] -pub struct AtlasSpriteBuffer(Arc); - -impl AtlasSpriteUniform { - fn from_atlas_sprite(atlas_sprite: &bones::AtlasSprite, atlas: &bones::Atlas) -> Self { - let image_size = [ - atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), - atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), - ]; - - Self { - tile_size: atlas.tile_size.into(), - columns: atlas.columns, - padding: atlas.padding.into(), - offset: atlas.offset.into(), - index: atlas_sprite.index, - image_size, - use_atlas: 1, - flip_x: atlas_sprite.flip_x as u32, - flip_y: atlas_sprite.flip_y as u32, - color_tint: atlas_sprite.color.as_rgba_f32(), - ..Default::default() - } - } - - fn from_tile(tile: &bones::Tile, atlas: &bones::Atlas) -> Self { - let image_size = [ - atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), - atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), - ]; - - Self { - tile_size: atlas.tile_size.into(), - columns: atlas.columns, - padding: atlas.padding.into(), - offset: atlas.offset.into(), - index: tile.idx, - image_size, - use_atlas: 1, - flip_x: tile.flip_x as u32, - flip_y: tile.flip_y as u32, - color_tint: tile.color.as_rgba_f32(), - ..Default::default() - } - } +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + // Create window object + let window = Arc::new( + event_loop + .create_window(Window::default_attributes()) + .unwrap(), + ); - fn from_sprite(sprite: &bones::Sprite) -> Self { - Self { - color_tint: sprite.color.as_rgba_f32(), - flip_x: sprite.flip_x as u32, - flip_y: sprite.flip_y as u32, - use_atlas: 0, - ..Default::default() - } - } -} + if let Some(state) = &mut self.state { + state.window = window.clone(); + state.size = window.inner_size(); -fn load_sprite( - entities: bones::Res, - sprites: bones::Comp, - assets: bones::Res, - device: bones::Res, - queue: bones::Res, - texture_sender: bones::Res, - pixel_art: bones::Res, - mut buffers: bones::CompMut, - mut texture_loaded: bones::CompMut, -) { - let mut not_loaded = texture_loaded.bitset().clone(); - not_loaded.bit_not(); - not_loaded.bit_and(sprites.bitset()); - - for entity in entities.iter_with_bitset(¬_loaded) { - let Some(sprite) = sprites.get(entity) else { - unreachable!(); - }; + state.surface = self.instance.create_surface(window.clone()).unwrap(); + let cap = state.surface.get_capabilities(&self.adapter); + state.surface_format = cap.formats[0]; - //Load and send texture - let image = assets.get(sprite.image); - if let bones::Image::Data(img) = &*image { - let texture = Arc::new( - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), + state.configure_surface(); + } else { + let state = State::new( + window.clone(), + self.device.clone(), + self.queue.clone(), + &self.instance, + &self.adapter, + &self.texture_bind_group_layout, ); - let atlas_uniform = AtlasSpriteUniform { - use_atlas: 0, - flip_x: sprite.flip_x as u32, - flip_y: sprite.flip_y as u32, - color_tint: sprite.color.as_rgba_f32(), - ..Default::default() - }; - - let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[atlas_uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }, - )); - - //Add buffer to bones so we can update it - buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + self.state = Some(state); + } - texture_sender - .0 - .send((texture, entity, atlas_sprite_buffer)) - .unwrap(); - texture_loaded.insert(entity, TextureLoaded); - } else { - unreachable!() - }; + window.request_redraw(); } -} - -fn load_atlas_sprite( - entities: bones::Res, - atlas_sprites: bones::Comp, - assets: bones::Res, - device: bones::Res, - queue: bones::Res, - texture_sender: bones::Res, - pixel_art: bones::Res, - mut buffers: bones::CompMut, - mut texture_loaded: bones::CompMut, -) { - let mut not_loaded = texture_loaded.bitset().clone(); - not_loaded.bit_not(); - not_loaded.bit_and(atlas_sprites.bitset()); - - for entity in entities.iter_with_bitset(¬_loaded) { - let Some(atlas_sprite) = atlas_sprites.get(entity) else { - unreachable!(); - }; - - //Load and send texture - let atlas = assets.get(atlas_sprite.atlas); - let image = assets.get(atlas.image); - if let bones::Image::Data(img) = &*image { - let texture = Arc::new( - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), - ); - // create and send the atlas sprite uniform along with the texture and entity - let uniform = AtlasSpriteUniform::from_atlas_sprite( - atlas_sprite, - &assets.get(atlas_sprite.atlas), - ); - let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }, - )); + fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + let state = self.state.as_mut().unwrap(); + // TODO: investigate possible ways to avoid allocating vectors every frame for event lists. + // TODO: Maybe add some multithreading for the diferent fors in the function? + let mut keyboard_inputs = bones::KeyboardInputs::default(); + let mut wheel_events = Vec::new(); + let mut button_events = Vec::new(); - //Add buffer to bones so we can update it - buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + for (texture, entity, atlas_sprite_buffer) in self.receiver.try_iter() { + let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.texture_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: atlas_sprite_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&texture.sampler), + }, + ], + label: Some("diffuse_bind_group"), + }); - texture_sender - .0 - .send((texture, entity, atlas_sprite_buffer)) - .unwrap(); - texture_loaded.insert(entity, TextureLoaded); - } else { - unreachable!() - }; - } -} + state.sprites.push((bind_group, entity)); + } -fn load_tile_sprite( - entities: bones::Res, - tile_layers: bones::Comp, - tiles: bones::Comp, - assets: bones::Res, - device: bones::Res, - queue: bones::Res, - texture_sender: bones::Res, - pixel_art: bones::Res, - mut buffers: bones::CompMut, - mut texture_loaded: bones::CompMut, -) { - let mut not_loaded = texture_loaded.bitset().clone(); - not_loaded.bit_not(); - not_loaded.bit_and(tile_layers.bitset()); - - for entity in entities.iter_with_bitset(¬_loaded) { - let Some(tile_layer) = tile_layers.get(entity) else { - unreachable!(); - }; + match event { + WindowEvent::CloseRequested => { + //Close window + event_loop.exit(); + } + WindowEvent::RedrawRequested => { + //println!("{}", self.now.elapsed().as_secs_f32()); + //self.now = Instant::now(); - //Load and send texture - let atlas = assets.get(tile_layer.atlas); - let image = assets.get(atlas.image); - if let bones::Image::Data(img) = &*image { - let texture = Arc::new( - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), - ); + state.render(&self.game.sessions); + // Emits a new redraw requested event. + state.get_window().request_redraw(); - for tile in &tile_layer.tiles { - let Some(tile) = tile else { - continue; + //Step bones + self.game.step(Instant::now()); + } + WindowEvent::Resized(size) => { + // Reconfigures the size of the surface. We do not re-render + // here as this event is always followed up by redraw request. + state.resize(size); + } + WindowEvent::KeyboardInput { event, .. } => { + let ev = match event.physical_key { + winit::keyboard::PhysicalKey::Code(code) => bones::KeyboardEvent { + scan_code: bones::Unset, + key_code: bones::Set(code.into_bones()), + button_state: event.state.into_bones(), + }, + winit::keyboard::PhysicalKey::Unidentified(native_key_code) => { + let scan_code = match native_key_code { + winit::keyboard::NativeKeyCode::Android(u) => bones::Set(u), + winit::keyboard::NativeKeyCode::MacOS(u) => bones::Set(u as u32), + winit::keyboard::NativeKeyCode::Windows(u) => bones::Set(u as u32), + winit::keyboard::NativeKeyCode::Xkb(u) => bones::Set(u), + winit::keyboard::NativeKeyCode::Unidentified => bones::Unset, + }; + bones::KeyboardEvent { + scan_code, + key_code: bones::Unset, + button_state: event.state.into_bones(), + } + } }; - let Some(tile) = tiles.get(*tile) else { - panic!("Couldn't find tile entity!"); + keyboard_inputs.key_events.push(ev); + } + WindowEvent::MouseWheel { delta, .. } => { + let ev: bones::MouseScrollEvent = delta.into_bones(); + wheel_events.push(ev); + } + WindowEvent::MouseInput { state, button, .. } => { + let ev = bones::MouseButtonEvent { + button: button.into_bones(), + state: state.into_bones(), }; - // create and send the atlas sprite uniform along with the texture and entity - let uniform = AtlasSpriteUniform::from_tile(tile, &assets.get(tile_layer.atlas)); - - let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }, - )); - - //Add buffer to bones so we can update it - buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); - - texture_sender - .0 - .send((texture.clone(), entity, atlas_sprite_buffer)) - .unwrap(); - texture_loaded.insert(entity, TextureLoaded); + button_events.push(ev); } - } else { - unreachable!() - }; - } -} - -// System for updating atlas uniforms -fn update_atlas_uniforms( - entities: bones::Res, - atlases: bones::Comp, - mut buffers: bones::CompMut, - assets: bones::Res, - queue: bones::Res, -) { - for (_, (atlas_sprite, atlas_sprite_buffer)) in entities.iter_with((&atlases, &mut buffers)) { - let atlas = assets.get(atlas_sprite.atlas).clone(); - let uniform = AtlasSpriteUniform::from_atlas_sprite(atlas_sprite, &atlas); - queue - .get() - .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); - } -} + WindowEvent::CursorMoved { position, .. } => { + let screen_pos = Some(Vec2::new(position.x as f32, position.y as f32)); + self.game + .insert_shared_resource(bones::MouseScreenPosition(screen_pos)); + } + WindowEvent::CursorLeft { .. } => { + self.game + .insert_shared_resource(bones::MouseScreenPosition(None)); + } + _ => (), + } -// System for updating sprite uniforms -fn update_sprite_uniforms( - entities: bones::Res, - sprites: bones::Comp, - mut buffers: bones::CompMut, - queue: bones::Res, -) { - for (_, (sprite, atlas_sprite_buffer)) in entities.iter_with((&sprites, &mut buffers)) { - let uniform = AtlasSpriteUniform::from_sprite(sprite); - queue - .get() - .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + // Add the game inputs + //TODO: Add world position + //self.game.insert_shared_resource(MouseWorldPosition(world_pos)); + self.game + .shared_resource_mut::() + .unwrap() + .wheel_events = wheel_events; + self.game + .shared_resource_mut::() + .unwrap() + .button_events = button_events; + self.game.insert_shared_resource(keyboard_inputs); + self.game.insert_shared_resource(process_gamepad_events()); } -} -// System for updating tiles uniforms -fn update_tiles_uniforms( - entities: bones::Res, - tile_layers: bones::Comp, - tiles: bones::Comp, - mut buffers: bones::CompMut, - assets: bones::Res, - queue: bones::Res, -) { - for (_, (tile_layer, atlas_sprite_buffer)) in entities.iter_with((&tile_layers, &mut buffers)) { - let atlas = assets.get(tile_layer.atlas).clone(); - for tile in &tile_layer.tiles { - let Some(tile) = tile else { - continue; - }; - let Some(tile) = tiles.get(*tile) else { - panic!("Couldn't find tile entity!"); - }; - - let uniform = AtlasSpriteUniform::from_tile(tile, &atlas); - queue - .get() - .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); - } - } -} + fn device_event( + &mut self, + _event_loop: &ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + let mut movement = Vec2::default(); -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -struct Vertex { - position: [f32; 3], - tex_coords: [f32; 2], -} + if let winit::event::DeviceEvent::MouseMotion { delta } = event { + let delta = Vec2::new(delta.0 as f32, delta.1 as f32); + movement += delta; + }; -impl Vertex { - fn desc() -> wgpu::VertexBufferLayout<'static> { - use std::mem; - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - wgpu::VertexAttribute { - offset: 0, - shader_location: 0, - format: wgpu::VertexFormat::Float32x3, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - }, - ], - } + self.game + .shared_resource_mut::() + .unwrap() + .movement = movement; } } @@ -997,6 +824,35 @@ impl State { } } +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct Vertex { + position: [f32; 3], + tex_coords: [f32; 2], +} + +impl Vertex { + fn desc() -> wgpu::VertexBufferLayout<'static> { + use std::mem; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + ], + } + } +} + const VERTICES: &[Vertex] = &[ // Top-left vertex Vertex { @@ -1048,174 +904,6 @@ const INDICES: &[u16] = &[ 0, 2, 3, // second triangle ]; -impl ApplicationHandler for App { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { - // Create window object - let window = Arc::new( - event_loop - .create_window(Window::default_attributes()) - .unwrap(), - ); - - if let Some(state) = &mut self.state { - state.window = window.clone(); - state.size = window.inner_size(); - - state.surface = self.instance.create_surface(window.clone()).unwrap(); - let cap = state.surface.get_capabilities(&self.adapter); - state.surface_format = cap.formats[0]; - - state.configure_surface(); - } else { - let state = State::new( - window.clone(), - self.device.clone(), - self.queue.clone(), - &self.instance, - &self.adapter, - &self.texture_bind_group_layout, - ); - - self.state = Some(state); - } - - window.request_redraw(); - } - - fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { - let state = self.state.as_mut().unwrap(); - // TODO: investigate possible ways to avoid allocating vectors every frame for event lists. - // TODO: Maybe add some multithreading for the diferent fors in the function? - let mut keyboard_inputs = bones::KeyboardInputs::default(); - let mut wheel_events = Vec::new(); - let mut button_events = Vec::new(); - - for (texture, entity, atlas_sprite_buffer) in self.receiver.try_iter() { - let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: atlas_sprite_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); - - state.sprites.push((bind_group, entity)); - } - - match event { - WindowEvent::CloseRequested => { - //Close window - event_loop.exit(); - } - WindowEvent::RedrawRequested => { - //println!("{}", self.now.elapsed().as_secs_f32()); - //self.now = Instant::now(); - - state.render(&self.game.sessions); - // Emits a new redraw requested event. - state.get_window().request_redraw(); - - //Step bones - self.game.step(Instant::now()); - } - WindowEvent::Resized(size) => { - // Reconfigures the size of the surface. We do not re-render - // here as this event is always followed up by redraw request. - state.resize(size); - } - WindowEvent::KeyboardInput { event, .. } => { - let ev = match event.physical_key { - winit::keyboard::PhysicalKey::Code(code) => bones::KeyboardEvent { - scan_code: bones::Unset, - key_code: bones::Set(code.into_bones()), - button_state: event.state.into_bones(), - }, - winit::keyboard::PhysicalKey::Unidentified(native_key_code) => { - let scan_code = match native_key_code { - winit::keyboard::NativeKeyCode::Android(u) => bones::Set(u), - winit::keyboard::NativeKeyCode::MacOS(u) => bones::Set(u as u32), - winit::keyboard::NativeKeyCode::Windows(u) => bones::Set(u as u32), - winit::keyboard::NativeKeyCode::Xkb(u) => bones::Set(u), - winit::keyboard::NativeKeyCode::Unidentified => bones::Unset, - }; - bones::KeyboardEvent { - scan_code, - key_code: bones::Unset, - button_state: event.state.into_bones(), - } - } - }; - keyboard_inputs.key_events.push(ev); - } - WindowEvent::MouseWheel { delta, .. } => { - let ev: bones::MouseScrollEvent = delta.into_bones(); - wheel_events.push(ev); - } - WindowEvent::MouseInput { state, button, .. } => { - let ev = bones::MouseButtonEvent { - button: button.into_bones(), - state: state.into_bones(), - }; - button_events.push(ev); - } - WindowEvent::CursorMoved { position, .. } => { - let screen_pos = Some(Vec2::new(position.x as f32, position.y as f32)); - self.game - .insert_shared_resource(bones::MouseScreenPosition(screen_pos)); - } - WindowEvent::CursorLeft { .. } => { - self.game - .insert_shared_resource(bones::MouseScreenPosition(None)); - } - _ => (), - } - - // Add the game inputs - //TODO: Add world position - //self.game.insert_shared_resource(MouseWorldPosition(world_pos)); - self.game - .shared_resource_mut::() - .unwrap() - .wheel_events = wheel_events; - self.game - .shared_resource_mut::() - .unwrap() - .button_events = button_events; - self.game.insert_shared_resource(keyboard_inputs); - self.game.insert_shared_resource(process_gamepad_events()); - } - - fn device_event( - &mut self, - _event_loop: &ActiveEventLoop, - _device_id: winit::event::DeviceId, - event: winit::event::DeviceEvent, - ) { - let mut movement = Vec2::default(); - - if let winit::event::DeviceEvent::MouseMotion { delta } = event { - let delta = Vec2::new(delta.0 as f32, delta.1 as f32); - movement += delta; - }; - - self.game - .shared_resource_mut::() - .unwrap() - .movement = movement; - } -} - //TODO Add quaternion rotations, and move this calculation to // wgsl code diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs new file mode 100644 index 0000000000..e70402d69c --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -0,0 +1,322 @@ +use crate::texture::Texture; +use std::sync::Arc; + +use crate::*; +use bones_framework::prelude::{self as bones, BitSet, ComponentIterBitset}; + +/// Functions used to load sprites, atlas sprites and tile sprites, and update them. + +// Uniform data struct matching the WGSL `AtlasSpriteUniform` layout. +#[repr(C)] +#[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)] +pub struct AtlasSpriteUniform { + // Atlas parameters + pub tile_size: [f32; 2], + pub image_size: [f32; 2], + pub padding: [f32; 2], //TODO Check if it really works + pub offset: [f32; 2], //TODO Check if it really works + pub columns: u32, + pub index: u32, + + // State flags + pub use_atlas: u32, + pub flip_x: u32, + pub flip_y: u32, + pub _pad: [u32; 3], // Explicit padding + + // Color tint + pub color_tint: [f32; 4], +} + +#[derive(bones_schema::HasSchema, Clone)] +#[repr(C)] +#[schema(opaque)] +#[schema(no_default)] +pub struct AtlasSpriteBuffer(Arc); + +impl AtlasSpriteUniform { + pub fn from_atlas_sprite(atlas_sprite: &bones::AtlasSprite, atlas: &bones::Atlas) -> Self { + let image_size = [ + atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), + atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), + ]; + + Self { + tile_size: atlas.tile_size.into(), + columns: atlas.columns, + padding: atlas.padding.into(), + offset: atlas.offset.into(), + index: atlas_sprite.index, + image_size, + use_atlas: 1, + flip_x: atlas_sprite.flip_x as u32, + flip_y: atlas_sprite.flip_y as u32, + color_tint: atlas_sprite.color.as_rgba_f32(), + ..Default::default() + } + } + + pub fn from_tile(tile: &bones::Tile, atlas: &bones::Atlas) -> Self { + let image_size = [ + atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), + atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), + ]; + + Self { + tile_size: atlas.tile_size.into(), + columns: atlas.columns, + padding: atlas.padding.into(), + offset: atlas.offset.into(), + index: tile.idx, + image_size, + use_atlas: 1, + flip_x: tile.flip_x as u32, + flip_y: tile.flip_y as u32, + color_tint: tile.color.as_rgba_f32(), + ..Default::default() + } + } + + pub fn from_sprite(sprite: &bones::Sprite) -> Self { + Self { + color_tint: sprite.color.as_rgba_f32(), + flip_x: sprite.flip_x as u32, + flip_y: sprite.flip_y as u32, + use_atlas: 0, + ..Default::default() + } + } +} + +pub fn load_sprite( + entities: bones::Res, + sprites: bones::Comp, + assets: bones::Res, + device: bones::Res, + queue: bones::Res, + texture_sender: bones::Res, + pixel_art: bones::Res, + mut buffers: bones::CompMut, + mut texture_loaded: bones::CompMut, +) { + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(sprites.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(sprite) = sprites.get(entity) else { + unreachable!(); + }; + + //Load and send texture + let image = assets.get(sprite.image); + if let bones::Image::Data(img) = &*image { + let texture = Arc::new( + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), + ); + + let atlas_uniform = AtlasSpriteUniform { + use_atlas: 0, + flip_x: sprite.flip_x as u32, + flip_y: sprite.flip_y as u32, + color_tint: sprite.color.as_rgba_f32(), + ..Default::default() + }; + + let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[atlas_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .0 + .send((texture, entity, atlas_sprite_buffer)) + .unwrap(); + texture_loaded.insert(entity, TextureLoaded); + } else { + unreachable!() + }; + } +} + +pub fn load_atlas_sprite( + entities: bones::Res, + atlas_sprites: bones::Comp, + assets: bones::Res, + device: bones::Res, + queue: bones::Res, + texture_sender: bones::Res, + pixel_art: bones::Res, + mut buffers: bones::CompMut, + mut texture_loaded: bones::CompMut, +) { + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(atlas_sprites.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(atlas_sprite) = atlas_sprites.get(entity) else { + unreachable!(); + }; + + //Load and send texture + let atlas = assets.get(atlas_sprite.atlas); + let image = assets.get(atlas.image); + if let bones::Image::Data(img) = &*image { + let texture = Arc::new( + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), + ); + // create and send the atlas sprite uniform along with the texture and entity + let uniform = AtlasSpriteUniform::from_atlas_sprite( + atlas_sprite, + &assets.get(atlas_sprite.atlas), + ); + + let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .0 + .send((texture, entity, atlas_sprite_buffer)) + .unwrap(); + texture_loaded.insert(entity, TextureLoaded); + } else { + unreachable!() + }; + } +} + +pub fn load_tile_sprite( + entities: bones::Res, + tile_layers: bones::Comp, + tiles: bones::Comp, + assets: bones::Res, + device: bones::Res, + queue: bones::Res, + texture_sender: bones::Res, + pixel_art: bones::Res, + mut buffers: bones::CompMut, + mut texture_loaded: bones::CompMut, +) { + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(tile_layers.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(tile_layer) = tile_layers.get(entity) else { + unreachable!(); + }; + + //Load and send texture + let atlas = assets.get(tile_layer.atlas); + let image = assets.get(atlas.image); + if let bones::Image::Data(img) = &*image { + let texture = Arc::new( + Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), + ); + + for tile in &tile_layer.tiles { + let Some(tile) = tile else { + continue; + }; + let Some(tile) = tiles.get(*tile) else { + panic!("Couldn't find tile entity!"); + }; + // create and send the atlas sprite uniform along with the texture and entity + let uniform = AtlasSpriteUniform::from_tile(tile, &assets.get(tile_layer.atlas)); + + let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .0 + .send((texture.clone(), entity, atlas_sprite_buffer)) + .unwrap(); + texture_loaded.insert(entity, TextureLoaded); + } + } else { + unreachable!() + }; + } +} + +// System for updating atlas uniforms +pub fn update_atlas_uniforms( + entities: bones::Res, + atlases: bones::Comp, + mut buffers: bones::CompMut, + assets: bones::Res, + queue: bones::Res, +) { + for (_, (atlas_sprite, atlas_sprite_buffer)) in entities.iter_with((&atlases, &mut buffers)) { + let atlas = assets.get(atlas_sprite.atlas).clone(); + let uniform = AtlasSpriteUniform::from_atlas_sprite(atlas_sprite, &atlas); + queue + .get() + .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + } +} + +// System for updating sprite uniforms +pub fn update_sprite_uniforms( + entities: bones::Res, + sprites: bones::Comp, + mut buffers: bones::CompMut, + queue: bones::Res, +) { + for (_, (sprite, atlas_sprite_buffer)) in entities.iter_with((&sprites, &mut buffers)) { + let uniform = AtlasSpriteUniform::from_sprite(sprite); + queue + .get() + .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + } +} + +// System for updating tiles uniforms +pub fn update_tiles_uniforms( + entities: bones::Res, + tile_layers: bones::Comp, + tiles: bones::Comp, + mut buffers: bones::CompMut, + assets: bones::Res, + queue: bones::Res, +) { + for (_, (tile_layer, atlas_sprite_buffer)) in entities.iter_with((&tile_layers, &mut buffers)) { + let atlas = assets.get(tile_layer.atlas).clone(); + for tile in &tile_layer.tiles { + let Some(tile) = tile else { + continue; + }; + let Some(tile) = tiles.get(*tile) else { + panic!("Couldn't find tile entity!"); + }; + + let uniform = AtlasSpriteUniform::from_tile(tile, &atlas); + queue + .get() + .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + } + } +} From 54cdd17baef116bbcf3df231ca9d12a60d3b828b Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Sat, 3 May 2025 03:29:09 -0300 Subject: [PATCH 08/16] Got egui working in game --- Cargo.lock | 144 ++++++++++++++++-- .../bones_wgpu_renderer/Cargo.toml | 4 + .../bones_wgpu_renderer/src/lib.rs | 79 +++++++++- .../bones_wgpu_renderer/src/ui.rs | 118 ++++++++++++++ 4 files changed, 328 insertions(+), 17 deletions(-) create mode 100644 framework_crates/bones_wgpu_renderer/src/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 58b7e0c6d1..f493cd13dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -737,9 +737,9 @@ checksum = "fb1c1f6ad293c60fd8559c4502cda5e832e92b0e0f3d994929b33f24d4352d70" dependencies = [ "arboard", "bevy", - "egui", + "egui 0.23.0", "thread_local", - "webbrowser", + "webbrowser 0.8.15", ] [[package]] @@ -1532,7 +1532,7 @@ dependencies = [ "csscolorparser", "directories", "document-features", - "egui", + "egui 0.23.0", "egui_plot", "either", "fluent", @@ -1691,6 +1691,9 @@ dependencies = [ "bones_schema", "bytemuck", "crossbeam-channel", + "egui 0.30.0", + "egui-wgpu", + "egui-winit", "env_logger", "image 0.24.9", "pollster", @@ -2619,6 +2622,16 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ecolor" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d72e9c39f6e11a2e922d04a34ec5e7ef522ea3f5a1acfca7a19d16ad5fe50f5" +dependencies = [ + "bytemuck", + "emath 0.30.0", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -2652,8 +2665,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd69fed5fcf4fbb8225b24e80ea6193b61e17a625db105ef0c4d71dde6eb8b7" dependencies = [ "ahash", - "epaint", + "epaint 0.23.0", + "nohash-hasher", +] + +[[package]] +name = "egui" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252d52224d35be1535d7fd1d6139ce071fb42c9097773e79f7665604f5596b5e" +dependencies = [ + "ahash", + "emath 0.30.0", + "epaint 0.30.0", + "log", "nohash-hasher", + "profiling", +] + +[[package]] +name = "egui-wgpu" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c1e821d2d8921ef6ce98b258c7e24d9d6aab2ca1f9cdf374eca997e7f67f59" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui 0.30.0", + "epaint 0.30.0", + "log", + "profiling", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu 23.0.1", +] + +[[package]] +name = "egui-winit" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e84c2919cd9f3a38a91e8f84ac6a245c19251fd95226ed9fae61d5ea564fce3" +dependencies = [ + "ahash", + "arboard", + "egui 0.30.0", + "log", + "profiling", + "raw-window-handle 0.6.2", + "smithay-clipboard", + "web-time", + "webbrowser 1.0.3", + "winit 0.30.9", ] [[package]] @@ -2662,7 +2726,7 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f33a00fe8eb1ba56535b3dbacdecc7a1365a328908a97c5f3c81bb466be72b" dependencies = [ - "egui", + "egui 0.23.0", ] [[package]] @@ -2722,6 +2786,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "emath" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4fe73c1207b864ee40aa0b0c038d6092af1030744678c60188a05c28553515d" +dependencies = [ + "bytemuck", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -2839,12 +2912,36 @@ dependencies = [ "ab_glyph", "ahash", "bytemuck", - "ecolor", - "emath", + "ecolor 0.23.0", + "emath 0.23.0", "nohash-hasher", "parking_lot", ] +[[package]] +name = "epaint" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5666f8d25236293c966fbb3635eac18b04ad1914e3bab55bc7d44b9980cafcac" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor 0.30.0", + "emath 0.30.0", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66f6ddac3e6ac6fd4c3d48bb8b1943472f8da0f43a4303bcd8a18aa594401c80" + [[package]] name = "equivalent" version = "1.0.1" @@ -6667,9 +6764,9 @@ dependencies = [ [[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" [[package]] name = "prometheus-client" @@ -7709,6 +7806,17 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol_str" version = "0.1.24" @@ -9298,6 +9406,24 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webbrowser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" +dependencies = [ + "block2 0.5.1", + "core-foundation 0.10.0", + "home", + "jni 0.21.1", + "log", + "ndk-context", + "objc2 0.5.2", + "objc2-foundation", + "url", + "web-sys", +] + [[package]] name = "webpki-roots" version = "0.26.5" diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml index 0786ca75dd..167bdc146b 100644 --- a/framework_crates/bones_wgpu_renderer/Cargo.toml +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -22,3 +22,7 @@ bevy_tasks = "0.11.3" image = "0.24.9" crossbeam-channel = "0.5.14" bytemuck = "1.22" + +egui = "0.30" +egui-wgpu = "0.30" +egui-winit = "0.30" \ No newline at end of file diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 5ce02f4a2a..59b4fbdec2 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -23,11 +23,15 @@ use bones_framework::{ prelude::{self as bones, BitSet, ComponentIterBitset}, }; +use egui_wgpu::ScreenDescriptor; + mod convert; mod sprite; mod texture; +mod ui; use sprite::*; +use ui::EguiRenderer; /// The prelude pub mod prelude { @@ -321,6 +325,11 @@ impl ApplicationHandler for App { state.sprites.push((bind_group, entity)); } + // Egui input handling + state + .egui_renderer + .handle_input(&state.get_window(), &event); + match event { WindowEvent::CloseRequested => { //Close window @@ -330,12 +339,9 @@ impl ApplicationHandler for App { //println!("{}", self.now.elapsed().as_secs_f32()); //self.now = Instant::now(); - state.render(&self.game.sessions); + state.render(&mut self.game); // Emits a new redraw requested event. state.get_window().request_redraw(); - - //Step bones - self.game.step(Instant::now()); } WindowEvent::Resized(size) => { // Reconfigures the size of the surface. We do not re-render @@ -433,6 +439,8 @@ struct State { surface_format: wgpu::TextureFormat, render_pipeline: wgpu::RenderPipeline, sprites: Vec<(wgpu::BindGroup, bones::Entity)>, + egui_renderer: EguiRenderer, + egui_scale_factor: f32, } impl State { @@ -542,6 +550,8 @@ impl State { cache: None, }); + let egui_renderer = EguiRenderer::new(&device, surface_config.format, None, 1, &window); + State { window, device: device.clone(), @@ -551,11 +561,13 @@ impl State { surface_format, render_pipeline, sprites: vec![], + egui_renderer, + egui_scale_factor: 1.0, } } - fn get_window(&self) -> &Window { - &self.window + fn get_window(&self) -> Arc { + self.window.clone() } fn configure_surface(&self) { @@ -580,7 +592,7 @@ impl State { self.configure_surface(); } - fn render(&mut self, sessions: &bones::Sessions) { + fn render(&mut self, game: &mut bones::Game) { // Create texture view let surface_texture = self .surface @@ -608,7 +620,7 @@ impl State { }); let num_indices = INDICES.len() as u32; - for (_, session) in sessions.iter() { + for (_, session) in game.sessions.iter() { //Get cameras and sort them let cameras = session.world.component::(); let transforms = session.world.component::(); @@ -817,6 +829,57 @@ impl State { } } + // Draw the egui UI + { + let screen_descriptor = ScreenDescriptor { + size_in_pixels: [self.size.width, self.size.height], + pixels_per_point: self.window.as_ref().scale_factor() as f32 + * self.egui_scale_factor, + }; + + self.egui_renderer.begin_frame(&self.window); + + //Insert the egui ctx + //game.insert_shared_resource(bones::EguiCtx(self.egui_renderer.context().clone())); + //Step bones + game.step(Instant::now()); + + egui::Window::new("winit + egui + wgpu says hello!") + .resizable(true) + .vscroll(true) + .default_open(false) + .show(self.egui_renderer.context(), |ui| { + ui.label("Label!"); + + if ui.button("Button!").clicked() { + println!("boom!") + } + + ui.separator(); + ui.horizontal(|ui| { + ui.label(format!( + "Pixels per point: {}", + self.egui_renderer.context().pixels_per_point() + )); + if ui.button("-").clicked() { + self.egui_scale_factor = (self.egui_scale_factor - 0.1).max(0.3); + } + if ui.button("+").clicked() { + self.egui_scale_factor = (self.egui_scale_factor + 0.1).min(3.0); + } + }); + }); + + self.egui_renderer.end_frame_and_draw( + &self.device, + &self.queue, + &mut encoder, + &self.window, + &texture_view, + screen_descriptor, + ); + } + // Submit the command queue. self.queue.submit([encoder.finish()]); self.window.pre_present_notify(); diff --git a/framework_crates/bones_wgpu_renderer/src/ui.rs b/framework_crates/bones_wgpu_renderer/src/ui.rs new file mode 100644 index 0000000000..6895635a2e --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/ui.rs @@ -0,0 +1,118 @@ +use egui::Context; +use egui_wgpu::wgpu::{CommandEncoder, Device, Queue, StoreOp, TextureFormat, TextureView}; +use egui_wgpu::{wgpu, Renderer, ScreenDescriptor}; +use egui_winit::State; +use winit::event::WindowEvent; +use winit::window::Window; + +pub struct EguiRenderer { + state: State, + renderer: Renderer, + frame_started: bool, +} + +impl EguiRenderer { + pub fn context(&self) -> &Context { + self.state.egui_ctx() + } + + pub fn new( + device: &Device, + output_color_format: TextureFormat, + output_depth_format: Option, + msaa_samples: u32, + window: &Window, + ) -> EguiRenderer { + let egui_context = Context::default(); + + let egui_state = egui_winit::State::new( + egui_context, + egui::viewport::ViewportId::ROOT, + &window, + Some(window.scale_factor() as f32), + None, + Some(2 * 1024), // default dimension is 2048 + ); + let egui_renderer = Renderer::new( + device, + output_color_format, + output_depth_format, + msaa_samples, + true, + ); + + EguiRenderer { + state: egui_state, + renderer: egui_renderer, + frame_started: false, + } + } + + pub fn handle_input(&mut self, window: &Window, event: &WindowEvent) { + let _ = self.state.on_window_event(window, event); + } + + pub fn ppp(&mut self, v: f32) { + self.context().set_pixels_per_point(v); + } + + pub fn begin_frame(&mut self, window: &Window) { + let raw_input = self.state.take_egui_input(window); + self.state.egui_ctx().begin_pass(raw_input); + self.frame_started = true; + } + + pub fn end_frame_and_draw( + &mut self, + device: &Device, + queue: &Queue, + encoder: &mut CommandEncoder, + window: &Window, + window_surface_view: &TextureView, + screen_descriptor: ScreenDescriptor, + ) { + if !self.frame_started { + panic!("begin_frame must be called before end_frame_and_draw can be called!"); + } + + self.ppp(screen_descriptor.pixels_per_point); + + let full_output = self.state.egui_ctx().end_pass(); + + self.state + .handle_platform_output(window, full_output.platform_output); + + let tris = self + .state + .egui_ctx() + .tessellate(full_output.shapes, self.state.egui_ctx().pixels_per_point()); + for (id, image_delta) in &full_output.textures_delta.set { + self.renderer + .update_texture(device, queue, *id, image_delta); + } + self.renderer + .update_buffers(device, queue, encoder, &tris, &screen_descriptor); + let rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: window_surface_view, + resolve_target: None, + ops: egui_wgpu::wgpu::Operations { + load: egui_wgpu::wgpu::LoadOp::Load, + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + label: Some("egui main render pass"), + occlusion_query_set: None, + }); + + self.renderer + .render(&mut rpass.forget_lifetime(), &tris, &screen_descriptor); + for x in &full_output.textures_delta.free { + self.renderer.free_texture(x) + } + + self.frame_started = false; + } +} \ No newline at end of file From a490409f6a886e75b34a0ce136a43b81223bbdab Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Mon, 12 May 2025 03:44:20 -0300 Subject: [PATCH 09/16] Egui support --- Cargo.lock | 22 +- demos/features_wgpu/Cargo.toml | 10 + .../assets/atlas/atlas-demo.yaml | 18 + .../assets/atlas/seagull.atlas.yaml | 4 + demos/features_wgpu/assets/atlas/seagull.png | Bin 0 -> 87707 bytes demos/features_wgpu/assets/audio/blink.ogg | Bin 0 -> 9311 bytes demos/features_wgpu/assets/fonts/Orbitron.ttf | Bin 0 -> 45252 bytes demos/features_wgpu/assets/game.yaml | 61 ++ .../assets/locales/en-US/locale.yaml | 3 + .../assets/locales/en-US/menu.ftl | 12 + demos/features_wgpu/assets/localization.yaml | 2 + demos/features_wgpu/assets/menu-image.png | Bin 0 -> 38787 bytes demos/features_wgpu/assets/menu.lua | 9 + demos/features_wgpu/assets/pack.yaml | 1 + demos/features_wgpu/assets/sprite/fractal.png | Bin 0 -> 34581 bytes .../features_wgpu/assets/tilemap/tilemap.yaml | 27 + .../assets/tilemap/tileset.atlas.yaml | 4 + .../features_wgpu/assets/tilemap/tileset.png | Bin 0 -> 17653 bytes demos/features_wgpu/assets/ui/button-down.png | Bin 0 -> 660 bytes .../assets/ui/button-focused.png | Bin 0 -> 243 bytes demos/features_wgpu/assets/ui/button.png | Bin 0 -> 662 bytes demos/features_wgpu/assets/ui/panel.png | Bin 0 -> 407 bytes demos/features_wgpu/src/main.rs | 578 ++++++++++++++++++ demos/hello_world_wgpu/src/main.rs | 33 +- .../bones_bevy_renderer/src/render.rs | 5 +- .../bones_bevy_renderer/src/ui.rs | 8 +- framework_crates/bones_framework/Cargo.toml | 4 +- .../bones_framework/src/render/camera.rs | 2 +- .../bones_framework/src/render/transform.rs | 4 +- .../bones_framework/src/render/ui.rs | 14 +- .../bones_framework/src/render/ui/widgets.rs | 2 +- .../src/render/ui/widgets/bordered_button.rs | 32 +- .../src/render/ui/widgets/bordered_frame.rs | 22 +- .../bones_wgpu_renderer/Cargo.toml | 19 +- .../bones_wgpu_renderer/src/lib.rs | 196 ++++-- .../bones_wgpu_renderer/src/sprite.rs | 463 ++++++++------ .../bones_wgpu_renderer/src/storage.rs | 178 ++++++ .../bones_wgpu_renderer/src/ui.rs | 45 +- 38 files changed, 1490 insertions(+), 288 deletions(-) create mode 100644 demos/features_wgpu/Cargo.toml create mode 100644 demos/features_wgpu/assets/atlas/atlas-demo.yaml create mode 100644 demos/features_wgpu/assets/atlas/seagull.atlas.yaml create mode 100644 demos/features_wgpu/assets/atlas/seagull.png create mode 100644 demos/features_wgpu/assets/audio/blink.ogg create mode 100644 demos/features_wgpu/assets/fonts/Orbitron.ttf create mode 100644 demos/features_wgpu/assets/game.yaml create mode 100644 demos/features_wgpu/assets/locales/en-US/locale.yaml create mode 100644 demos/features_wgpu/assets/locales/en-US/menu.ftl create mode 100644 demos/features_wgpu/assets/localization.yaml create mode 100644 demos/features_wgpu/assets/menu-image.png create mode 100644 demos/features_wgpu/assets/menu.lua create mode 100644 demos/features_wgpu/assets/pack.yaml create mode 100644 demos/features_wgpu/assets/sprite/fractal.png create mode 100644 demos/features_wgpu/assets/tilemap/tilemap.yaml create mode 100644 demos/features_wgpu/assets/tilemap/tileset.atlas.yaml create mode 100644 demos/features_wgpu/assets/tilemap/tileset.png create mode 100644 demos/features_wgpu/assets/ui/button-down.png create mode 100644 demos/features_wgpu/assets/ui/button-focused.png create mode 100644 demos/features_wgpu/assets/ui/button.png create mode 100644 demos/features_wgpu/assets/ui/panel.png create mode 100644 demos/features_wgpu/src/main.rs create mode 100644 framework_crates/bones_wgpu_renderer/src/storage.rs diff --git a/Cargo.lock b/Cargo.lock index f493cd13dd..5c1a675cb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1532,7 +1532,7 @@ dependencies = [ "csscolorparser", "directories", "document-features", - "egui 0.23.0", + "egui 0.30.0", "egui_plot", "either", "fluent", @@ -1691,12 +1691,16 @@ dependencies = [ "bones_schema", "bytemuck", "crossbeam-channel", + "directories", "egui 0.30.0", "egui-wgpu", "egui-winit", "env_logger", "image 0.24.9", + "log", "pollster", + "serde", + "serde_yaml", "wgpu 23.0.1", "winit 0.30.9", ] @@ -2393,6 +2397,14 @@ dependencies = [ "bones_framework", ] +[[package]] +name = "demo_features_wgpu" +version = "0.4.0" +dependencies = [ + "bones_framework", + "bones_wgpu_renderer", +] + [[package]] name = "demo_hello_world" version = "0.4.0" @@ -2722,11 +2734,13 @@ dependencies = [ [[package]] name = "egui_plot" -version = "0.23.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f33a00fe8eb1ba56535b3dbacdecc7a1365a328908a97c5f3c81bb466be72b" +checksum = "c226cae80a6ee10c4d3aaf9e33bd9e9b2f1c0116b6036bdc2a1cfc9d2d0dcc10" dependencies = [ - "egui 0.23.0", + "ahash", + "egui 0.30.0", + "emath 0.30.0", ] [[package]] diff --git a/demos/features_wgpu/Cargo.toml b/demos/features_wgpu/Cargo.toml new file mode 100644 index 0000000000..ca6b2bf066 --- /dev/null +++ b/demos/features_wgpu/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "demo_features_wgpu" +edition.workspace = true +version.workspace = true +license.workspace = true +publish = false + +[dependencies] +bones_framework = { path = "../../framework_crates/bones_framework" } +bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/features_wgpu/assets/atlas/atlas-demo.yaml b/demos/features_wgpu/assets/atlas/atlas-demo.yaml new file mode 100644 index 0000000000..0d749feefe --- /dev/null +++ b/demos/features_wgpu/assets/atlas/atlas-demo.yaml @@ -0,0 +1,18 @@ +camera_size: !FixedHeight 275 +atlas: ./seagull.atlas.yaml +fps: 10 +animation: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 diff --git a/demos/features_wgpu/assets/atlas/seagull.atlas.yaml b/demos/features_wgpu/assets/atlas/seagull.atlas.yaml new file mode 100644 index 0000000000..ce03ceb93f --- /dev/null +++ b/demos/features_wgpu/assets/atlas/seagull.atlas.yaml @@ -0,0 +1,4 @@ +tile_size: [96, 80] +columns: 14 +rows: 15 +image: ./seagull.png diff --git a/demos/features_wgpu/assets/atlas/seagull.png b/demos/features_wgpu/assets/atlas/seagull.png new file mode 100644 index 0000000000000000000000000000000000000000..4a89d187e66816db92e1781074b09909383de04b GIT binary patch literal 87707 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

+UFls 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 literal 0 HcmV?d00001 diff --git a/demos/features_wgpu/assets/fonts/Orbitron.ttf b/demos/features_wgpu/assets/fonts/Orbitron.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a0062a5af28990ccd840c2fefeac4f543940770c GIT binary patch literal 45252 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 literal 0 HcmV?d00001 diff --git a/demos/features_wgpu/assets/game.yaml b/demos/features_wgpu/assets/game.yaml new file mode 100644 index 0000000000..44bdb50d44 --- /dev/null +++ b/demos/features_wgpu/assets/game.yaml @@ -0,0 +1,61 @@ +menu_script: ./menu.lua +menu_image: ./menu-image.png +sprite_demo: ./sprite/fractal.png +atlas_demo: ./atlas/atlas-demo.yaml +tilemap_demo: ./tilemap/tilemap.yaml +audio_demo: ./audio/blink.ogg +localization: ./localization.yaml +path2d_color: red +title_font: + family: Orbitron + size: 20 + color: purple +menu_border: + image: ./ui/panel.png + image_size: [44, 44] + border_size: + left: 16 + right: 16 + top: 16 + bottom: 16 + scale: 4 +button_style: + font: + family: Orbitron + color: white + size: 10 + padding: + top: 6 + left: 6 + right: 6 + bottom: 9 + borders: + default: + image: ui/button.png + image_size: [14, 14] + border_size: + top: 5 + bottom: 5 + right: 5 + left: 5 + scale: 2 + focused: + image: ui/button-focused.png + image_size: [14, 14] + border_size: + top: 5 + bottom: 5 + right: 5 + left: 5 + scale: 2 + clicked: + image: ui/button-down.png + image_size: [14, 14] + border_size: + top: 5 + bottom: 5 + right: 5 + left: 5 + scale: 2 +fonts: + - ./fonts/Orbitron.ttf diff --git a/demos/features_wgpu/assets/locales/en-US/locale.yaml b/demos/features_wgpu/assets/locales/en-US/locale.yaml new file mode 100644 index 0000000000..93ee52ae37 --- /dev/null +++ b/demos/features_wgpu/assets/locales/en-US/locale.yaml @@ -0,0 +1,3 @@ +locales: [en-US] +resources: + - ./menu.ftl diff --git a/demos/features_wgpu/assets/locales/en-US/menu.ftl b/demos/features_wgpu/assets/locales/en-US/menu.ftl new file mode 100644 index 0000000000..573a4a7ae6 --- /dev/null +++ b/demos/features_wgpu/assets/locales/en-US/menu.ftl @@ -0,0 +1,12 @@ +title = Bones Features Demo +back-to-menu = Back to Menu +sprite-demo = Sprite Demo +atlas-demo = Atlas Demo +audio-demo = Audio Demo +storage-demo = Storage Demo +tilemap-demo = Tilemap Demo +path2d-demo = Path2D Demo +quit = Quit +save = Save +play-sound = Play Sound +persisted-text-box-content = Persisted text box content diff --git a/demos/features_wgpu/assets/localization.yaml b/demos/features_wgpu/assets/localization.yaml new file mode 100644 index 0000000000..bb8dbbab69 --- /dev/null +++ b/demos/features_wgpu/assets/localization.yaml @@ -0,0 +1,2 @@ +locales: + - ./locales/en-US/locale.yaml diff --git a/demos/features_wgpu/assets/menu-image.png b/demos/features_wgpu/assets/menu-image.png new file mode 100644 index 0000000000000000000000000000000000000000..39b5937521b30770304ddb5cbe5b6bff045c976c GIT binary patch literal 38787 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 P00000NkvXXu0mjf%6L@{ literal 0 HcmV?d00001 diff --git a/demos/features_wgpu/assets/menu.lua b/demos/features_wgpu/assets/menu.lua new file mode 100644 index 0000000000..6a94d1e838 --- /dev/null +++ b/demos/features_wgpu/assets/menu.lua @@ -0,0 +1,9 @@ +local menuData = world.resources:get(schema("MenuData")) + +-- Increment the frame counter +menuData.frame = menuData.frame + 1 + +if menuData.frame % 30 == 0 then + info(menuData) +end + diff --git a/demos/features_wgpu/assets/pack.yaml b/demos/features_wgpu/assets/pack.yaml new file mode 100644 index 0000000000..a4979b30cc --- /dev/null +++ b/demos/features_wgpu/assets/pack.yaml @@ -0,0 +1 @@ +root: ./game.yaml diff --git a/demos/features_wgpu/assets/sprite/fractal.png b/demos/features_wgpu/assets/sprite/fractal.png new file mode 100644 index 0000000000000000000000000000000000000000..a96df3d53d32466f89c2e7454cfd0be54c16d396 GIT binary patch literal 34581 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

7VF=fanFQRTaAm$kR~R3tqg zCkk%C^?la7{$Ae;|3byGAfNW8i!;mia^CMwWWU2#dg)nx`L6%p$!~3$<;=_4%6Cjx zUX=W(uVOR{{viw{beGZ6hk&{$F2jZjmO>Sdm%hMh{|EO3OYC{Sa{;YOdB*4|`xnF0 z$wreM#h(MSf_I#@-ppao6K$+1pX#P32D(4C=Q|UZbV>Fqd!)|^bnVl_0n;}xr-(eN z^_v3=trN(MKOL~Kc`>j7QCz62vu#&+PVCXDdqqudd#ONc;~dT7plNPpIeHcRwL7+n z&k?scSSL4}^0@i9jJRq(K78U#L5PMw zB&Jm+TGO00Sv}Vn`RXM16%h}hiwez*6W#LNnw!%Qy^O_AyCdbGCHlnf+;Vq*HYCxT zJ^_bi%xuS;If%q9gtP_3S-Ljozj0Q7QIft_|1c*UJVCIs=f5#+vN#v9b|i}Dvqg!` zLh~oDC#OJ8z4c*vBKCcrKR++u;8tIO^W@knJsB|l@iZm zJ_;H~9R26A9(eZx?ZKsQ$sP z{!UivLy=MA`uEf0s9JdcIB{NtqT9M{ZSb4THt_csz*yn{)Z{u72fIh z$U-90gZd$JKUz7AuPuhrg}u9wIR|{Zuz3NS4M>P7Q~VQHKqoEXP{#Dth+Mh}2q9%l zn|})fR^o<*Bn0xWa%1KMTOLQeixH^LoNH{AY zBoRU~DLR&>2j*F;Pap$}|5A8nX5b@3IMY9CeL(mH=qk#WgRgqeoM)MA`%3j#{_9S! zWx8|BR4Ee<)X8K_AQkoXegF4n(|-ZcOX3*Tz>$~z+&oiAd-{wlfB#1~n2mC&&zNOI zj4v+?7yZHy9{7bd<~MDQUR4AyB6rP^^2~GdAYL^cD^OC}NJk|>Dsccd9dj@7{{PH@ zwuz~w<@^^Ae`VtcwPbs`hyNz$P1#&O+o2O+7!-PYLWxoGbixfJ=bfA$uua+JH19p< zv)?DaWJmwAB~-Q=Ggdh3&B$}^KfGgKHA5w+80mBY+WYt&!8g9;J(qrwho>pB??|LxA$5q*{{ud z7b{=^dCISFGCfzyqYgeSSmpl$#{xM0`zj_;{;8KO!22)#8v8!vir?4~KXbnioqhnQ zDg8IA=vVGi5gI$e=9gM}6U`e$j{eQvmlz&!XrMCUS>qo#81`2sO z(f^=HKLC9HXH)5y+|d_6Xw8WcOEZW#6-es8=kf5*zSfBA_Y^<-G60r_=%-Hs?&1|s zxF(+P&cf}v$wVLT>gAs_B|;6QH#>0zkCqft2Soa%z{#oTvISU@4i7-t=Xq^iV8?So z+CAg1r08A|q{k;{}6UJV20zmfzW5efGXQDKb;S^RrsxN=SX%KZn-~f=>m6O0vke;hrIw%?YprIfJGtq=~Hm+u(kOLSi0a( zULRc+eVqrn3ve&!nQsPmJQpm)%K4uy-w9rPz6bpujVlj!!+$EGrnm16W{1JjRo=qg(wkWGZSiv(Jnto?;}hv+7wASvJj`$w zaX%!X2Y|Ny?*c&j6o|uc{Nccs?*uJfWx|z-C%ns|U)K}a=CPeUP_7S(!(izG`&Jlw za3uirJRZiO(3*4G%K)e$@!B(4H6@;e9_1csyh!d*<)>c)MbTFr;9#HV2Tks|hk}c* zyFjpRkBe8pzO@_&x9^UaeUN&K=fPbDfR?P+96nT(bv*ao$?=qwXgm*v9towJH;8h% zhw8lkvgqqSq%YE^plJsHz}f%O2>@%q9>4lyYxvwMfU%l4>i+{(f7p(!w~br?0000< KMNUMnLSTZin>hpk literal 0 HcmV?d00001 diff --git a/demos/features_wgpu/assets/tilemap/tilemap.yaml b/demos/features_wgpu/assets/tilemap/tilemap.yaml new file mode 100644 index 0000000000..95d9f2b983 --- /dev/null +++ b/demos/features_wgpu/assets/tilemap/tilemap.yaml @@ -0,0 +1,27 @@ +atlas: ./tileset.atlas.yaml +map_size: [9, 9] +tiles: + - pos: [0, 0] + idx: 0 + - pos: [1, 0] + idx: 1 + - pos: [2, 0] + idx: 1 + - pos: [3, 0] + idx: 1 + - pos: [4, 0] + idx: 1 + - pos: [5, 0] + idx: 1 + - pos: [6, 0] + idx: 1 + - pos: [7, 0] + idx: 1 + - pos: [8, 0] + idx: 2 + - pos: [4, 2] + idx: 56 + - pos: [5, 2] + idx: 58 + - pos: [6, 2] + idx: 60 diff --git a/demos/features_wgpu/assets/tilemap/tileset.atlas.yaml b/demos/features_wgpu/assets/tilemap/tileset.atlas.yaml new file mode 100644 index 0000000000..2eab7fa754 --- /dev/null +++ b/demos/features_wgpu/assets/tilemap/tileset.atlas.yaml @@ -0,0 +1,4 @@ +image: ./tileset.png +tile_size: [32, 32] +columns: 7 +rows: 9 diff --git a/demos/features_wgpu/assets/tilemap/tileset.png b/demos/features_wgpu/assets/tilemap/tileset.png new file mode 100644 index 0000000000000000000000000000000000000000..42be6234e1e25472631c4247797a1af2bb18cabe GIT binary patch literal 17653 zcmY&=WmuE%`?r8J2q>Kjq7st^g)t03zaS!#8!ZjuNFCjXGyL?p&x;+~j&0YyYxjL#*Qd^Xd-wJgJq;(#jT<-U|9h?co_N1-;|9rHDhlEw zM3vtn@s7mx{i_!@ehzbQ-neo1#(&DsbztecKAK?}|9v;zVDRD`e~qyJ^HM^jwxFm4 zBpTGN;ecLle^>XanT{R6>un(JXu2~UGL_j0LM2)G+=NpCiTbzpD37s^pjU)dg(UlLDPW?=$ zGNV77k>>4Q$w>7TG8IBhhH@#$y|?SBAuyjL(VOfhWD$plKlYz1f5K5>=Ir(3KnQqG zG|_|i4&;5*Z$!!^aXP3lPdibARYR7DBLUM|s{K z+QH7kq%Vi7C%u3*{t_g$>mDiX+oncOI_R%#)uvU~S^2Wav$}gPGzcd?oefUPljr=C z8ci8{JJO#t{Xf3M_ATWC(ZAcZBb6=N{`E7zh1DeraD~~eqQogG-H{e@xG|t??QOW} z*2K?m-JBzV9o;?8LipEK|LB=(&uDVFX#S#dUk=N#H3GAS^}`YR3bC^RmRe7`0L;cPaf5pjP_evKi-@(r3P31-wWW@3@<#i z)Fic=%aHhMWg`6HIwHtb9ZE4ZMcF`=gk!S{!|mCB2Q1YS188zJCAQRF=Cs|T-x1BY zIBBY{uMU=ZuWdiWw;g2fLXQ)z0l*4SG!%8_%JcmeKI@=Se37nk^CZL7bITUf%6*lc zNruI!^#B@Ar}e#Oso;hj@S9WU+j4uY1XM|E62md%(P4yD_DsP7#n0WHA~H}T4sVtv zS%;G`7QxZI^HKJk`@(;`dwcY#<#3;gK9kvSE98#s+X-7>dlkSp^V6rH%l>{5elmF} zk`o$c4q0i28{CnZcZHm?m;Ea#*tr-OM=@0T<=+p!g$el^h#}GntQL>;K#yN4y!H^G zuYjLH^Rq(gO)W5P6{>-qA}L9%SEFgz?i~Sj3)aOb@CxyA^7x=sO1{g3$tq=*Y$x{| zD3=sx;oaXu;qR)2QsbIK*`SG5%0U^FB(&n6GizT&+W8F{s<%>nTZp}0r1IaZlTiT`KDfnb_)qGB8SUh0+_l+H|P>eNRa9`R6 z{ETeviwu^#nx|=MjDRL`{1DK|G&FyHi=;%KV|yXV#?eww z*%DRxKmtJrW3{-q)@&%ftzzX>8eRLIA2Ktac5+hBc?mdpz|Ts>OG_q`hCLng43u4^ zdGZQ{%}_LoU2nf{x8nyeK}FDMr9cS@#ZoLL*I`r|%&_qDca>L6k;`4uwzgyD99vi0E&!=yl$mq2$qiSm}Ry<>>avAW?fdT2X9oVf{V5MnSSb ze)-v|nKzrfbHBEa7;9ViDv?O-k^256MK*X=%Ijw7QQyjfxu^`T$612P6!6>PQInZZ z^qP;>ZjNlR^Twk6qy1mxdHh=ZHCS|5mFBGuLr~vnJr0kWKbX(F0AH+u-(FPO@=NYQ zNBMTnIX7+0hL!|-=<>^f2Dlh+7n4#HfVZH^KdD>B4+S}t$U<)pt{Lugzbd@sfA|$O z8d=_3B!b@I*5xl%HSlVpQ|gV+S+QE?`7Xj|q=z=99q6rVh5xvUgj@>3Zry;?9rd;* z8ud#`OO&e!yk~IOVHC_nG^fkdmDR=c(<_>v-P%v9F6v6weIoTU(P|BN$qI<6vspc% zKz0?bcP%2s4PtuJI&QB;K73PvYrd{MQ9syn{83}m5ik&18d;IFQQMdc4x5>&o`VCu zN7`Ij{VZ#{XQ2|$p?5t9tU_YPCZPTL*8pp>sHvyv^QCDjo!ZnMBSx+6+CS$T?NY}L zXZ(aO1$THba=MS^9>Y{~Ut4LHib562kOrUv8#0%Sm9@AF;Nw8u@st(sP5+lAqZ+$xUY+13sR=L zu_JN8tai1CIhQc|^3p*wGrtQ5Y@lzC9&0cy)s;JxyKYW+oMS$EY*yTyk9$553l=HL zaT6pL>v}tiXKj7rFK{VndA=bN-J2OM+s!evBt)xOy|DXV@%8svZePAR%9Fhuk4WqM zR?C~cI|0DVksIw7%}0nni@}c^yT;5ZHlmOjrGb}GHz1B%2+Q8Dx6p&S+=Eh+?swP9 zt=+4(VhHD3kq6zS@Sb_fb^o#g1F6&!UcuVF{@h2@%6T9NA;!A1EOVYT(*SrX-L&p# zN1$sK#ZhhK4Zawbdw0M(uzHT+F^cUb7|x%_iM|?GGZgRDbX2TI*Hi9(j1npznjUT+ zzMY)q=LOq;IeVx3*goa9mI|{sKG{7qBam-}mS8zUO8>(4&!UY$&NWrU11>ng8(OCk}6XzWfJ{frVc7-*_g9qNLIW1 z{Wm%vGO4{brG)(pAr(m)BBIAm)T0<>x#i4z{nZn^_YrRSXJWYFYG;6tCiL|MUbUcV z1L_991sB95yWCXDH`?)uy4DbhlU&L3Js(@o^N7LYp(}mOBCH_t>p6(dw6L@8Yy-Sm z4L6Z>ntVH9F?LJuneWENB_b^-6ruHs2zhMtEyv}yRxEBlIsU8O`UPfM_gmIU-1N_3 zrr?B>f5MEZB@8V??vrWj!~m+F>D&jJi);8MO?bsir6aFI_h8~SLiatH+){4@w z5FWfaGB)YJ+s~5H`kdP_)NrzdDuv=xz?WOy^L6NYmDeh>kgV305XZ=KJeI4#yh07c z;!}#jOjgGS_*&c@L8ow-T%QJ?AMW`k^A2=Q-iDreiRcfc99M~dxfQ1Qm$=!54H=jOply`^OutVPQXen_oL2vcr0BhdkmLIf9mxmNlYp z;Rp7c73$?5r{V3oOCGq^q(}cyf+hTCW0(ofNgQ&GuNp2fkv8b27UStSR(*PIrun$_ zd@r5Ra;7zOer$mJ&AiB~N?{r4_s852>fD(GfT|VWV9(ZGPJSskfdjfuPD3Z zQ%;vqP?E|51zdyl);oua821Z%JA#TG2NCGn#5|G^y*Jh@$j1YFMT-s^$hV-%&xOJ3 zkbE`6E{Wa3oBHH7S`%};exnOK$Ot;J(QJbG+S8l51ZIav%gzoA5Nx3OCZx*!IK ziV%=b2;LO;oji$X$=>=}&yn1XKBq!eM_al3yRXbEY@eree;s> z`!)L<>c~AF&~e!MI%7e@**NJoh3e3@JF939Y(F*rma6P;;NLkKsKUa0WKYvo&=ji1 zJKyF|`^tV=cr%UEAl$`J0H|$h0b$i?tNAHx3s?JL2V|+xyCJC+dT3yo63?;c^~;2N z;sgvlv2lOW;QFQFgIVwgys=-WWQ`G~iC+vLj1}H&qCA|tu(v%B4F>ZKj<+1C9GECY z>p5O0>{QdR%2f*UK)rQOzy3hO)l3o7AA5Ic%fTv4j_4UcdK^y{}ywp zE0JcAELlc+S*63&7jGa5Ue3y*fE(L*H`h`-Z$!B7MkLRW0TH;bbtqdoLnhz-ZS}l1 zH?(BOwx-OCDY9;EIk;Y%BpnexJkL0bUVo>j;DZ)HO#{I~x07%?0HA9r(K zCi#2a_a=#Ie_^YlS4C%dUG)76Az?G-Y0xC~$nB2TvE5@z9;um`q|eMAx>_L1dTy1; zJ3mxri&wGC#&6a#vqZLs%qd^lJXWk95euMPD$^7x^D*2$yTg_#pQW`Z9km*l$n&Ly zKAp@rx$a9H*C>jAmD)nLyNSUa5+y2A@ut7l48^EAHR5&bJw3Q ze<)AH=e&;A_C$naJNQaLv#Ga*G`W32BRRF=UAcT!{R^ae2|7(Y!j?dXlQ^g$hwh_- z5}<|#n-zmr4bpJigqvax!B52`2{TS^XGuO^^OkR>*% zITJ8^=*U^UiAv{LR=Dp8Hm+W-%tKsA;X%b4Xrg2LKjyipID5#QHEqe8@Y(u%nD`$u z)ysOe5WR2PEcd%3svg$dWY8GaQ#^1&b6v~y*7+&hlqduk%ztmnu&S&%s-e>>9hA5_ zzCCi=(0(9N?p<)f_G^AeHXY67t%u*03L_~{B@a_nY8|8OGBRor|3NG<8IJ{GjS42w zaSZ8cl}=(PW!6)WFTVgKCW?Y|Cn*$o}xf9g5xgTjd5en9Sy*@_$(6P^lZ9ofS5NCG;#z z$tb;YUgX2VkpLFzmyTD4-r!F<;LCxV$2JHvi*7Xy6W{GF(zhiky)B+P-z)El0zug! z;|&kFJ2f}%K)$`X5^z&VkxEmNgCK3$T{Eo{2vH_c6q0EJUtmi-IrS(XqdHn&^hf$#n3=m={Cptf#+c_&9iPPo46BVjjs!tC=VLHy3Ns& ze#-yld(NcuR1;}uZ;@~Eyl1@C=&orFU%pb*AqiWQ)ud8nbnv<)2CN|+Z*2`m$xj5T zptdqMO_|6`v?i2aRSkC`eN^D{kL16vuDU{&$sUuI77-RS*z01QDP3b(KUW}X??YPr zY7g`Z(bd3mZdK_+W$9m0)w|G)X2^P3?TKQ-aDel{H#z~{&DPVXv452KsYp>N8%%i%7N+mu2gy)4patZ!_^4XInmxb;-#|uvrdcRR>42A35o`cA^FukII6AJ6uzUlEr z=|ogA6JgQsR=bfGN-xi)yC|6teI6s~YU*ywSbT^(&FTt!r3%P~-q*j|NM^uk#H67| zExBqLQvcb7-GG@)^DB`gu8}FzVE8O&m97wQUuxI4VHyN(!7_PcYeR&-cx0^4?A1h@5O-b>KPvl)7ue=ZwY7p zT-9ZOx04k(QXu*Oet$|FE%@GI!fwWys#kESAb#->n=^j1O4MQfFg!W_#M{<9T+NnRh}uJDr3T88zf`V{nr2MHQjF;>P6 zZNJVhyagG$WQ8csDK1V!>VrBAge{AA=zwhZGh%_SFjN>}b`5rTkSkFNf}+igRK43~ zM4<2h=-^EmcU-4Mm}uuT%l(8C*UD1)u%`)vNU&x?YX!V z6qT7Jxdo^USSxI|dy&1?{;eRNCzm`IwIHL%2?A_h%g zucXAq@HX7mTNGRrk*KT1?-ZYHgt|qk)adD+-JCV#$nBh^)3liN6ZH{6OrdQ4IaXISy_a9t{qtQPvNCQL-_(eR^jgeNd?Vx_RiMz;yQU~xd&+N*d!)p_#tj$Gj+6*UIul6Ge*}6(Gt*K|{dL@ucR>^pHuk;~F_2GpY zuIrVaeflA+&Do$MXG^Im`uEM6h3^+~7I*(B)R@Mxa+oZg@&=`v2d^e>=X{+cJDo~? zQWiA3fqZwN>{>IX6~o-Z`MQ@Bk1W zOG)dRaeeKa-`sgs+)OzpmZ1dtJ1056PLz*X?Kf;t8YvHv)IWj3qV5p`DvJZ%>&uie z;yx$rV+mKUxnp*?+wFiRDw&gnGD#c7>d?StcW?BEUY_mz&ee2=+}wvdv7v&$F_AVN zhX&Q6!-M_m(sv*yNV0YKS^Y3Cz*p)70K?_Bdfh3$rG}D+?X_d(ew@ClpHB)>__97c zsn&B_rd&$d-R^1R)KNt=`ep)xkp7TvI2w~lM=r#9L$AkR{;(~X>-W;VYUpzN;^>l; zAgfzUx$TX+C_NKenO)P{Kn?AeSr6}_{xBh08s)vZc|Z-_INr6z+y`qhge*k?l}nke zhx?W4S6U86oBMjWzd^8pq7FS-^(Dae{nU6Zt8b#lp8HWyusGmx&kW9%sIDL?@~Zqb~VW3<9FnjRy{X zE>`q$?01919>Bpp#Z6ZA?sT^ZBn_l zxapI5wJeDz)$Z?vOWl6g5NY}2feJCmrlH>aZQO03xs_w*i%|Rm`v;5l?uz5Cx;?O0 z$!*!spL&UBSx%q#>;W{y^GEfh=C*(7VDj-@<&}HpicNdFx{|vwlsguiG2dc(D1N}} zjAU@PodVjGv`4dM$%bV&QTimoHXi*{S?(~qp-Tgk6*AkuvE9IuF^@wuALeSrd+nK} ziJ+B$cHSo^o-*Byxgf=3Mx!`K!fnV#uAcuaskc4Jc}C`O;8{Q-DrfYCsomTEvKBe+ zB!(u|h2VFT6^Q zbmSvy`_yvB-J3*7Kg8byk-x7%AR8Ze_$6p7h^#8jx_&)db#nS%c>e0TZQcO%2lz!Oq zm5wR76^HrDQ14yC`DBsv(VnKNvzZyu-5Cdme~!Z{@%1;Val`+H9q?*$bxW9v*k&8N zHt~o7VOW^r&Y zty&Q>3{j^ah{wg>Dsv-H3RG|X_O!w8@`2=hwy8ocjc zMOghqG%TPT1?-s>2fT_jx!os;XC<3S*}m;29wx{(BCU&piQVA>s@X}=DVqp$IN?)~ z76ZL2BKeE5=_NSB4cDwi`+>v5WE-faUGad4$DmC@=Bzo*rLB4xoueM#o9FZ1-}y;- zIc-{sS*QRn!SeauGNDPLtui$|MS&YMvbtCGBU8IC&g@`sg1loq;J%3`(-J={jaI6N zJg)iztLRfb%r;pXh>Qb&AdCW zHlI6W6`r6gs@VV`trm@FVabdPi3MEopWXAN#Yyq==QCRIM6?H?Kw!K7`IR4F@(dih zusEW&Q5;(p_*wK;Y5Mi$?RRIlAwllZQGLVe?c2 z+2uYJMQKced6DAzQGR{m)xs8Pa9@o~h=7i=fJ1N}eD*~vn#$s@`AVa>WN5PPs10G+ ziq;%PClS)8*dZKq=Lz(m$!H^t%HtU)`jb=KFAaiB7pb2QY5f-7SZ+nAk3`(~StmvE zds_U1wEWeDZj?>9|l+v;L1WNym$veC946ydwlDvzOt!NB zeI!wg8buuhVDQ&9=5~_NOBSN>c!LQ0sJ5l?hYb5Zu%&P`%|~veM69z&sfKAFi`K(< z(4C-L=QQwH&ZhfOT3v<}k1q57d;ww|j;a-i3W*X@&ii#=qnLpwW5hl!u1XXeJ2*j(Pi4~k)@v|+Qcwk4CHg))cdmc zWNZqkiQhwBUmvi!c-uMZ;V*Dv|5gF(8Ywxh8sLc$(KkNah2FCDb-VH*mfz3xdBTV2 z?ymY)A%1=(rR$LEBg|eTMpFs*?N@#E*84_kt#LerPveSsu-r`x`DT)ApX-ff!`6ygYqA2Pg zV6dktV8|fKNBZ4!qPZ#~hVpzYpRD@(%jV(ZUWf_B>&}6%9)JXFm*hJ~?S#G5!vRaW z%t>dJwX0bETGY5#L+ZzV&xHCuG2yLqax(Fu{wI);an`x##Z+q?flQe{e2#&O5;RFhY?O z3UVM?@msvx)9n(wF&++^^=IHst(-S_$mJhaiynxqQfspX_^Iv)ObMLx6vpQEAcMj= z9g!SX|8%w97#T1ur9nYF#cz+0=F}LoS`$I->c;aoPa*b*=>9q-PQJ2pFmboEGDNFf zx2jMMlz1q~=EQ`(xdNOBEQhMeEdiImqegq`J; zNJ5|<3&LwWx1^dy9rv;~cHZUv#0ivp&jn45th+Sh`=3z4?$|La{7|0HwfYg#B6lYF zVl9ds)8zWga2v(9)}`9)yS-LsAu982TWHsM`^-~dm@3Q36(3e6n zl+2&XgAj?g57JCN@c`SbvHtRW|jJ zQ^i&b(WY!aTmO&5<3w*U48-?t>T&|I>qx$#Y?2bn#ut06BGORHZg5qddYO0@dF``* zfPYhgJZffVUj^)tD+_&W@ zG249$mD|Yl>m|P?xSD!;}C!nmDzcFRLy~dLTujeA3LqFB$ED zV?12pn3fC`)FdCOvFP(<(qI(443DHcQ0|^h_#Z(_uf1{TJFxZ6VQri7XkDjy`hSFqA+gw&j9$U>t6^7B8j`k<(EAOImA?#dGvr}gp`Ej__r=2qEvF+}RG#t5eg2euC!S?c`3I|3IiZ0M zpvivS^f{2@W!7;G5g_B=&BRP}Q%1@=6Z!WW0r{*kc>|)`dKs*$ry^4B{wZWt_3)xh z|A*8rl!FaOruqOxv|S3_a)4^H4g<}d&W9v%>tkE`GY{)V#A@{m@Im^cu-cJ9^jhu8 z{g37rHjP++6S57TXZzlW9?CMgXLg|UQx9@my8($q5&*9#;@;SOJ)W!s;#W91`i6aA zsZmZj43Bld@bXm*n{}{lCl1*et-1QmYuB#VbD_l5*dF*886?#O|7ft zyX;(iBY{>!tSKN+=4)f`XBsiM#l!pGwM+FmvYf0ANthHt3--%okP)u=i0GJy@7eg? zg{WKw%H$EZ6sZT=75NHKZV7&b7KsT=TLE^A#S zlbTwIZs0r5h4N855}x9|7H!5siFBhvNxf=*u45xsd{@lN`!_?&6^@tBr5#9V=)Zi- z?_9xjCSTOfERyNYn|~0j;1V%j&EZ33OGIHmB)*=Lp!WqkC(w=E@kMeE06}XU6T+WnIT6qH6{w2zWW4wUR3L*=%nT;9(ObaBkh5TG$emuN$dVsNCx>L83 z@x3MR7;>FB+=7&5!i~Qtgh5woCvM3ca;T)UjLpE+z7M$v_+#^9gVuCS2VI1 z3dYI|xIRwk(x#(!HgcfBgZR-V z=bd{;Lxxt~9lGPF#(eOzd%K7CH1Qde+-aGDHG1W}27wn(d}IMi+p+_Gwms@aAKZ zR7c35hlpyNh8VRJ{X{#WhAYi*arC9?qr>Lm9u(o$@*Vm3*s!p1odl=K`6Z{)Taqz1b`WgGYMi4mcg zT9uxhUuvJ7F=+Rx01hym`hR>x^M%27NS>91ymjD*-KqWSO%lFR?&~4>iS_gMq6X5{ z=ai>g?={w!_bpJ*lT_wC&Au&b+*R9Od=p|-=lA&ytm7*|5C4aOB}DW1tJkv#A;G1g zFsvEM#6>_}9om4fijws%5Ip+0_IsLfnHmC|`cQ%mE-i>juDv=Yc$H>b;p*B-i)9Ml zYU9++)QKe}%i@0`saT%XDZ|@iCt5w)D6P@FQT9Tg~G}~AQ z3s?5XqJdUseUT5LqFd-5$*Kh*fA{;&CV6{NjMhbY)>ZwRGSQy{Wv*(u(@ z$karbJ_je(NqY%27Y!8@KoiTYSpHt~bDAYu*23!*FnNQ*8Sq@v1@Z%-Nz+{x2r)fz zo;|O|s!N{rCG?zRon6|EP<*YN>pGRX^TVwOh^-^(lwWdRFb%5+@Z#$9w z&RODO9H6qa9rz7{$Cf5w!HA;ui{G+0Q4#%;za`3Wh39LuNzh5994ih=Dg9;9dW}-V zu(78Fj`ETskMg=;pZ~+U=f$aEvA54Ft(5D{LZy_|`Xe6PmW>M$@DJk(px?I?;6#$} z`mwi<#RJ>&)?Wadm}u*L+ojo*e}q2DG%K$9)vJG;yIX2Z+vFcqZ@)@rt+_=5;G0iY z7FYZ!N6=eZChof6CWoetV0R+H*wc9DQ-8WJ{FfspoEXhWSHH6KgPyGu$Kn`?!lHq5 zvVWkdvYcY_ddkYfygWm;w!GX>VQDR#%x?EEy~pyD4TZ(qO+o*r z=W|e*NxxGRNtSzk_+4}j;P^qp$I@e2HNv`_)xZXfCgjq$;*Cp`){nPPFM6*n(;qgp z!UQWX1wGwFwqE^YSg(ckp7{}XnUi0Ubht1+prKB?iiY-8z9+fz0}dH46~_aK!|93+ zZQdmjes?LU14*lxsF<(UVRYmn<6b@F@>0v9(2K|Dqc!vJ%j26yK|-_hLG>RZ(#w!! z`qIGMFh-B??bADPU0q)-bSD(Bx|)}8*XIu8m1ng1?Z3hl=Uy|KWTrZ1_ zyZl;hF7z`d>14OG8OUJHvA9tPv0V<{C`<)llfWYc>+7x1 ze!?{;O2<`Ymr{SN#$$rTUD%j8Fk{p&wI@HfNe0-HC>7p)Z_;J2|7|?9cgv8r9AJ`w zR~kA&G(WMYTbI$2W98m!?H#*a`XeHXoxzJ||=G z4X|OPrB@Bx^DL&nq0bJmQc>c6NYEI6{Wp3fhO!+1 zRjO6FpeZ}VhOE6bR>w=a;+xCi4&y`F0z5L*0muRwZ?y@dEV6Rq%o}pSpUK7 z(*n>|FNe0%1H=>-JLIAr!D&WR*|EY~`={DxFSe5ZEhBBjWFXM+MFZ7k+)Ij9XlK#w zXZP*f4Ue47k~*XoQ~nb|6o~Qo8rq0$z1a?VC_=BNw;W4xdh^9Ff$_y=_7LYvaX?JK*ZbK-^glyCcRdi*z)R^-Q?0 z=u7WB{}yVbE@sTYJ<|KtFM^EUgx;2Q$kvwRrRhUGq)_GSOa4x0@iqKr5z#Yrgt2yS zL}?)mCf;e`cdqjLwFeUKZ+AfH`Lw6_X3rOfjnBj3Cc;rh+f@gTnIq`v`QJE<;!Gg{ ztpBy5Z-0zxEuj4+8r`?7-v=b{CB1W3)uIqVyT z;e*+1sMj+wQ1_6botU@x zLP4hO!-TrNM#8j2tMFw$u#$^}6ZQC%=;j?P7ghH9@vkqi`2?6cUpL8y;=UdPQRZz> zH)bV3R&E6M;FuJdoNd;}959q3<3gCv$&DC(mYx#c;~cz=9rRj15?zEK8K#@6=N`_3yMp%fR98xgS< zs{E$G#IMf7SCL0r@q>S+rs^rzag> zVgEZ){tmTi-&M8@D4W+rz!<9ZZkZ%t_jd8uT8cZm?do6mw7lxiS_{t(35xfsygdN| z>UD|<1xX-IKcd#A?)v#UV~&!zt^$!Z55q-)um!NBZf>r<<;sEVl#D8$3105=HL;R_ zy{onrwiIF{y5j2M$mv>3tur05Yl3!A%ep^MJ$}Qlln!{S4ZqrG*YdA1$N%RFQ7j$! zwZ8dJ?3w9SFw|~Acsg?s^UnOCxiy%uZyT;YfC>xM&)2$Ll)s<&sxeyB;!ei1f>+C$ z>mCr{9L3=x&=b9XNx3PUxwEg^L-`x_g!C<%PY$nyn`zphQlT2rICKV%mYx~n*KF$#KGESB4vt{?Y?up;)jpz*YY9j@-p<%UR!-u~E&0IC>bz~}E=szhE}bR}o^?5s-f-Z>0qj^LK! zB|C3~iwL6M^j@i6hX&?&kzr#Io<_r*xHlAj!VRajvx8O*(UZC( zY;ggiFwR&xJOwHH-;Gs{#J|-OPE5KDnPvX_JCDzYWrJUhwC7V)Jxcy&i z;LT=%Orm%00uDvO+}QIaYHcsWUZ2K@+j+N*)xQt&xay|s&b$-;8yb#&h8j0mUl-A3 z!by}z*N#C+yq-_4adBlqCr_poN?9it`eBD)eWNsIZk25m)N%3akIH6w(9eFW zMn2#uAC-w2i#mezW_rB+PidBrsb<3wa$wA&fs(*FIed>o^~`c))+DKz9)oL`@(=ZE zDOYgsrDH3c3l1KIS%dQHi6O90emRd6Eds2-TVT7a&~DmjZS%#sAoCu?cVBw7JEdnN zG?luA#3-?dSN(q4{2{rksLdz8Q!~(guEuIWs_zM5=8&zM(lUFzY7%SvV?sLaiz+=( zj7w!@2@W2W8Pq+|n3Vs>g+!+Mly~RU+FwQv0+<|p{+4Q!&d;Hw@g>8_INAhqpD6Y)ay&d zdUGFA4;)-pNPiOzY_VB|^7suAm8O)}95r~@peqRf1t9x*Yl)1g9VYLROO^I6|HVy{ zw8+AS7W8W=1`Gs!t~0d833H~SXO?fgTWqa{L4{93eW`1g%Y{G-$(@c&UYbu%B~+qV zm_oAQCXN`P{^V2;{}$nUxmZ>JfFG3gQ+IWfGh3Nx9&p}mIfCxP0Jgy~dqKC3(LK5mECIVWL++1eomBN=SlX;TbX3%=R zt=8sNzZ=Wf61r?|&ugMOL1AnBfWLHExb51qXP!f@OC->iRDS3g<)#V^$sUR3fEgRO z`9^g9Jo&*>d-4jmE-N*eB0chI*m5qvrh#+q%L$ru;qCoV6ILA6&PRy|1_R?Ubqba? zJtdu*R0KcBj5$-(g;A zT{QJ2u8+LKt$HXY+^{?somI}?7fFIh5eJXi4PR`K+Zn6kBoMm$D&yxJN#Kzn9ML#- zBVlH`7;YuRxC-0qH80#+d8;id3CeV@7>Iw3{-!8(XmG`w84L&e=0wO$9D4uAWKICcDmJ(FAa?Y#Ai=)3d!AVv65^4m+7D;Z1CIZXe zK95b}`<}Mc$inn(I8IbDo9T0*y&rexVu|8xkWzmH9!InR&#wH%?i{>L>K$QuOK2uG zcS`q&NrB8Z)6T!U21jMspo&a=_l?iH$(vaCQKe;mn<_!$j9HQ|*xGKOrN}W?Ndw^w z8(ps=VH1roQafYwj)zx=tdUEL%?#_dfNFecB!g|O(*8Gxvb=I6%_Rc?uY@B}k}E__ z*adB!*G{3=GUc9lxk)E!X3fk(=HdT*`qP=jlvFG+LQq|}#r)5s{Ff3~KW}Bf#&;FQ zP*@c~F8=wK53&C=0G$frM_j!Z<2^CsL8ez|xivgtWq-#Z2U1s(hfSZC+(v0K+H-N6 zLeh!@2R`~a5F#~|wpiEKzqTf8f8+B(&PIt7uCs94%f01d7LYYJnV+8Uo_A{xe1Fp( zYahK9{u_pxTTlX=)*Ibn`){6$&)Y5|26Xv!!PWAnxVQ8u6R@_KNWQ6#4KdiQqB{IoWA zU-g>^ePnFE4Fq<=h{!W~fVLVvwec0#T;t)wwZc<=?5|FG3;Le!>-&;hYO;sQ6shgm z@~B!SKx+UQoeuXSKQDA-ElH}X=&FzvDb3N0tK zzI9>^a;f=<-{9h45&)umb@1#MR&i_7x!|lHQLLUvYIB@;vs?STs;2srC1&!8h!_Ym zh;K+Pr@h;dBUF|mc~n|67DiWIut2v-9edubwz3=+G_o|5mZEh7@bYHUM{uCAYx#Jf zd!i^Uv|QgXx+hDH8qzxvllAI)~WLHD(F0x##z&QZ~yY4 zpiRvju-Xm|EAz*8B=5<^{amZZ;6~>i^c71Zi4v_{52;ATx{>*0(D@V-VaAk8q4V7) z5tx0=X}lqCtINjph|wgq_c#y!Q;_=_8cf4>EAm(N{p`2(2H;9=-pG{8;&mn^3DmWS z=);?=4<0LZGSKE9eK<9C(;SgM*e89|sb6mZsOFS9=4Ai!JLFn(A*tUg)_CbqdNhiEQLJsuTi2Xjt3wUY#hLclgRK3U8~ zR7(`PeH^VK)a;439Ig9*q^_Z$?D=YuFrf?8>}Ux}VG+!Q3}4@sXl;kqC{^Fu0h{F< zH*@aaW?)rh;s~aeW0)X@yKD(bTiuRb@j+kyL|<OoTDA9U}IJwQIW z7WnuHk8i1#m=vr0^{jR&7Vze0{4JC12OiyAp%S{@%4<>)8-1Qp)+@CsW89Gu>zfTI zo4UYt!upxVYLvLeQfH-In%{9S13ib#15ep_DO~EY80tQ1&DY`7#&a|?x{|1Xea;W7 z497+y9J>QKz_#E-m3zCY$;7sI3eziXEeZb$>(4J#%~E9`ZHYvr5tgVdV4P^4CdaHf3;S8$8z52$Dn@Ruv7hwITtx3;j=?J=05fd@W|FmbGPHl^4xP<>$ZtQ|9XZ7%Y z+~R<_0k1vPD3=KQ2K+_TJ*U%8Q4v49C&pgQj5f4?t{$_!YCZCL76%gym+!trFI8{{ zXv}l)O3vFuSrhp8iQV`4YL6;ROq3xE2IU_!KYGwTHXT>(ND8PUh=}>4yVC=LWbS}< zjqBBtw5zX+%L~b4vR=2rbdTu-%Fy6fEa|e6RR$h1&2S>0j2vx}Xvji)6SE~7b!5jq zRu&(-^-ajTzC17VW>0Fib|RSZXd0OCWUkXIL9=Z=n5Rnk0(n@}>;S&GZYd}E*V=P) zP#=c6iCcP{A3OC;ZV)>CuJ(KQ$AR?w)S8wox)nFV698Wy86i+huJ8J9AB=#G0e99% zL`1mwDs@>-w!Y^ps*+DSANSKPQ2P0x=!Yd`pHQ0qE^CGhNS^3N_8;kWKN9aF;t5}N zfw*P5y5GVdzqvN_Vix7oG8wdduGIlTU1#ZwAqeSEOl|B%E)+3v%@6-Jd$ zpRQj_Pms(tM=+B=$dVN}eo%1rpl1BjU+Z_hTh9309CQYQ{omd7B5J=5+y9ODcfJ1a z_D}u)pVrU&8+&T9ZvIt?;QpwHw>z@7T$6wOX>WdQXx)#;=T{#;A^&fs%>GwS6>Z z+WpsnM>+ieykB(UF2kbI-`k}u>vk60`@Nv|*WzaNtF2jaN2ji@OZ{;BG|L+4YhP1W zd_NsB{W3>5L+P_$an6UMH{ZLH&+D|;qvC6F;->@mKm7mkUc8W@f92_ZzIh!oHWTyX z>+b3-Ru(uA&S`q+>cr#Q_4nVqy}seI()n-oGiSgb-!q{Fgs+h=BDre{r`X7-}hnea;DH#?n~su-}C>& zzS;kO-FmTdqCtS;#)&R|JqG^|oj8-ZHviA8&JM1EkXB1?~t@j0mz$c)EY1Zq6 zV(OpOpOFjOs-nyMf8VaZ(VrIE|E*|}+H*sKL&5yo_kF)ae?IKD7Z;pp|Fe7lB>P`` z?-yR4w%{cw!2QoU^n`u7lz;KNhVAypS`Xh|_S#=r+fn+u`hd~7ZX4^6uisL4G1=Yt zU*CLxe{QPA0qy*$5ywu|NWEU05qZAyj)i89reUCt58{?V#p*Q*O{ t_wr=dlrn6I9*J6S>=*pu_)zl8PKK+!D!HsCoX44$rjF6*2UngBxDUJd{N literal 0 HcmV?d00001 diff --git a/demos/features_wgpu/assets/ui/button-down.png b/demos/features_wgpu/assets/ui/button-down.png new file mode 100644 index 0000000000000000000000000000000000000000..dfe103070c8974e9af7f714099da363b29882dda GIT binary patch literal 660 zcmV;F0&D$=P)EX>4Tx04R}tkv&MmKpe$iQ>7vm2a8`gWT;LSL`B3&t5Adrp;l;o12rOibnM}+Rhls^o3o9+m@}@>SNgP%+o$`fL zomI|ToV8+^HSWn@7|iG^%Uq{9gcugF1PLM(luN1==vpcDdZ}E zkz)ZRXpmh$_#gc4*2+$d*OP)#p#8;hK8At7E>NpD&iAq7)J_2ZGjOFh{iP}}{YiSI zsfCY#?rq@Wx~VC9z~v6m|76If>`H!`LN*J$pV2p^fu38SbFJQ6V;`pvK$5zO-v9@P zz(|g=*FE0d(c0U;XBz$e0I`yCz-)D^y#N3J24YJ`L;(K){{a7>y{D4^000SaNLh0L z01FcU01FcV0GgZ_00007bV*G`2jv1A1P~2G|7BkQ0059lL_t(o!|m8H3V|#*O4EX>4Tx04R}tkv&MmKpe$iQ>7vm2a8`gWT;LSL`B3&t5Adrp;l;o12rOibnM}+Rhls^o3o9+m@}@>SNgP%+o$`fL zomI|ToV8+^HSWn@7|iG^%Uq{9gcugF1PLM(luN1==vpcDdZ}E zkz)ZRXpmh$_#gc4*2+$d*OP)#p#8;hK8At7E>NpD&iAq7)J_2ZGjOFh{iP}}{YiSI zsfCY#?rq@Wx~VC9z~v6m|76If>`H!`LN*J$pV2p^fu38SbFJQ6V;`pvK$5zO-v9@P zz(|g=*FE0d(c0U;XBz$e0I`yCz-)D^y#N3J24YJ`L;(K){{a7>y{D4^000SaNLh0L z01FcU01FcV0GgZ_00007bV*G`2jv1A1Pw24^GK%v005FnL_t(o!|mCz4S*mNMNu%; zRwQcc1P^( z^MKYRJO)(%di7tZJ7x^19w<`oHTW_w=Mzji5sgQ}B^y$5N#_1= zgs!QL$0Z4F!HfuV6P+;H77C65wgx(jC^i7*q|GtMzQWWkx*ks8{yw3u, + /// The image displayed on the menu. + menu_image: Handle, + /// The image for the sprite demo + sprite_demo: Handle, + /// Character information that will be loaded from a separate asset file. + atlas_demo: Handle, + /// The tilemap demo metadata. + tilemap_demo: Handle, + /// Audio track for the audio demo. + audio_demo: Handle, + /// The color the debug lines in the debug line demo. + path2d_color: Color, + /// Localization asset + localization: Handle, + /// The font to use for the demo title. + title_font: FontMeta, + /// The list of font files to load for the UI. + fonts: SVec>, + /// The border to use the for main menu. + menu_border: BorderImageMeta, + /// The style to use for buttons. + button_style: ButtonThemeMeta, +} + +/// Atlas information. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +#[type_data(metadata_asset("atlas-demo"))] +struct AtlasDemoMeta { + /// The size of the camera. + camera_size: CameraSize, + /// The sprite atlas for the player. + pub atlas: Handle, + /// The frames-per-second of the animation. + pub fps: f32, + /// The frames of the animation. + /// + /// Note: We use an [`SVec`] here because it implements [`HasSchema`], allowing it to be loaded + /// in a metadata asset. + pub animation: SVec, +} + +/// Tilemap info. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +#[type_data(metadata_asset("tilemap"))] +struct TilemapDemoMeta { + /// The atlas that will be used for the tilemap. + pub atlas: Handle, + /// The size of the tile map in tiles. + pub map_size: UVec2, + /// The information about each tile in the tilemap. + pub tiles: SVec, +} + +/// Tile info. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +struct TileMeta { + /// The tile position. + pos: UVec2, + /// The index of the tile in the atlas. + idx: u32, +} + +/// Struct containing data that will be persisted with the storage API. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +struct PersistedTextData(String); + +fn main() { + // Setup logging + setup_logs!(); + + // Register persistent data's schema so that it can be loaded by the storage loader. + PersistedTextData::register_schema(); + + // Create a bones bevy renderer from our bones game + let mut renderer = BonesWgpuRenderer::new(create_game()); + // Set the app namespace which will be used by the renderer to decide where to put + // persistent storage files. + renderer.app_namespace = ( + "org".into(), + "fishfolk".into(), + "bones.demo_features".into(), + ); + // Run the renderer. + renderer.run(); +} + +// Initialize the game. +pub fn create_game() -> Game { + // Create an empty game + let mut game = Game::new(); + + // Configure the asset server + game.install_plugin(DefaultGamePlugin) + .init_shared_resource::() + // Register the default asset types + .register_default_assets(); + + // Register our custom asset types + GameMeta::register_schema(); + AtlasDemoMeta::register_schema(); + TilemapDemoMeta::register_schema(); + + // Create our menu session + game.sessions.create("menu").install_plugin(menu_plugin); + + game +} + +/// Resource containing data that we will access from our menu lua script. +#[derive(HasSchema, Default, Clone)] +#[repr(C)] +struct MenuData { + /// The index of the frame that we are on. + pub frame: u32, +} + +/// Menu plugin +pub fn menu_plugin(session: &mut Session) { + // Register our menu system + session + // Install the bones_framework default plugins for this session + .install_plugin(DefaultSessionPlugin) + .world + // Initialize our menu data resource + .init_resource::(); + + // And add our systems. + session + .add_system_to_stage(Update, menu_system) + .add_startup_system(menu_startup); +} + +/// Setup the main menu. +fn menu_startup( + mut egui_settings: ResMutInit, + mut clear_color: ResMutInit, +) { + // Set the clear color + **clear_color = Color::BLACK; + // Set the egui scale + egui_settings.scale = 2.0; +} + +/// Our main menu system. +fn menu_system( + meta: Root, + ctx: Res, + mut sessions: ResMut, + mut session_options: ResMut, + mut exit_bones: Option>, + // Get the localization field from our `GameMeta` + localization: Localization, + world: &World, + lua_engine: Res, +) { + // Run our menu script. + lua_engine.run_script_system(world, meta.menu_script); + + // Render the menu. + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(&ctx, |ui| { + BorderedFrame::new(&meta.menu_border).show(ui, |ui| { + ui.vertical_centered(|ui| { + ui.add_space(20.0); + ui.label(meta.title_font.rich(localization.get("title"))); + ui.add_space(20.0); + + if BorderedButton::themed(&meta.button_style, localization.get("sprite-demo")) + .show(ui) + .clicked() + { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("sprite_demo") + .install_plugin(sprite_demo_plugin); + } + + if BorderedButton::themed(&meta.button_style, localization.get("atlas-demo")) + .show(ui) + .clicked() + { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("atlas_demo") + .install_plugin(atlas_demo_plugin); + } + + if BorderedButton::themed(&meta.button_style, localization.get("tilemap-demo")) + .show(ui) + .clicked() + { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("tilemap_demo") + .install_plugin(tilemap_demo_plugin); + } + + if BorderedButton::themed(&meta.button_style, localization.get("audio-demo")) + .show(ui) + .clicked() + { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("audio_demo") + .install_plugin(audio_demo_plugin); + } + + if BorderedButton::themed(&meta.button_style, localization.get("storage-demo")) + .show(ui) + .clicked() + { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("storage_demo") + .install_plugin(storage_demo_plugin); + } + + if BorderedButton::themed(&meta.button_style, localization.get("path2d-demo")) + .show(ui) + .clicked() + { + // Delete the menu world + session_options.delete = true; + + // Create a session for the match + sessions + .create("path2d_demo") + .install_plugin(path2d_demo_plugin); + } + + if let Some(exit_bones) = &mut exit_bones { + if BorderedButton::themed(&meta.button_style, localization.get("quit")) + .show(ui) + .clicked() + { + ***exit_bones = true; + } + } + + ui.add_space(10.0); + + // We can use the `&World` parameter to access the world and run systems to act + // as egui widgets. + // + // This makes it easier to compose widgets that have differing access to the + // bones world. + world.run_system(demo_widget, ui); + + ui.add_space(30.0); + }); + }) + }); +} + +/// Plugin for running the sprite demo. +fn sprite_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultSessionPlugin) + .add_startup_system(sprite_demo_startup) + .add_system_to_stage(Update, back_to_menu_ui) + .add_system_to_stage(Update, move_sprite); +} + +/// System that spawns the sprite demo. +fn sprite_demo_startup( + mut entities: ResMut, + mut sprites: CompMut, + mut transforms: CompMut, + mut cameras: CompMut, + meta: Root, +) { + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + let sprite_ent = entities.create(); + transforms.insert(sprite_ent, default()); + sprites.insert( + sprite_ent, + Sprite { + image: meta.sprite_demo, + ..default() + }, + ); +} + +fn move_sprite( + entities: Res, + sprite: Comp, + mut transforms: CompMut, + input: Res, + ctx: Res, +) { + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(&ctx, |ui| { + ui.label("Press left and right arrow keys to move sprite"); + }); + + let mut left = false; + let mut right = false; + + for input in &input.key_events { + match input.key_code { + Set(KeyCode::Right) => right = true, + Set(KeyCode::Left) => left = true, + _ => (), + } + } + + for (_ent, (_sprite, transform)) in entities.iter_with((&sprite, &mut transforms)) { + if left { + transform.translation.x -= 2.0; + } + if right { + transform.translation.x += 2.0; + } + } +} + +/// Plugin for running the tilemap demo. +fn tilemap_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultSessionPlugin) + .add_startup_system(tilemap_startup_system) + .add_system_to_stage(Update, back_to_menu_ui); +} + +/// System for starting up the tilemap demo. +fn tilemap_startup_system( + mut entities: ResMut, + mut transforms: CompMut, + mut tile_layers: CompMut, + mut cameras: CompMut, + mut tiles: CompMut, + meta: Root, + assets: Res, +) { + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + // Load our map and atlas info + let map_info = assets.get(meta.tilemap_demo); + let atlas = assets.get(map_info.atlas); + + // Create a new map layer + let mut layer = TileLayer::new(map_info.map_size, atlas.tile_size, map_info.atlas); + + // Load the layer up with the tiles from our metadata + for tile in &map_info.tiles { + let tile_ent = entities.create(); + tiles.insert( + tile_ent, + Tile { + idx: tile.idx, + ..default() + }, + ); + layer.set(tile.pos, Some(tile_ent)) + } + + // Spawn the layer + let layer_ent = entities.create(); + tile_layers.insert(layer_ent, layer); + transforms.insert(layer_ent, default()); +} + +/// Plugin for running the atlas demo. +fn atlas_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultSessionPlugin) + .add_startup_system(atlas_demo_startup) + .add_system_to_stage(Update, back_to_menu_ui); +} + +/// System to startup the atlas demo. +fn atlas_demo_startup( + mut entities: ResMut, + mut transforms: CompMut, + mut cameras: CompMut, + mut atlas_sprites: CompMut, + mut animated_sprites: CompMut, + mut clear_color: ResMutInit, + meta: Root, + assets: Res, +) { + // Set the clear color + **clear_color = Color::GRAY; + + // Get the atlas metadata + let demo = assets.get(meta.atlas_demo); + + // Spawn the camera + let camera_ent = spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + cameras.get_mut(camera_ent).unwrap().size = demo.camera_size; + + // Spawn the character sprite. + let sprite_ent = entities.create(); + transforms.insert(sprite_ent, default()); + atlas_sprites.insert( + sprite_ent, + AtlasSprite { + atlas: demo.atlas, + ..default() + }, + ); + animated_sprites.insert( + sprite_ent, + AnimatedSprite { + frames: demo.animation.iter().copied().collect(), + fps: demo.fps, + ..default() + }, + ); +} + +fn audio_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultSessionPlugin) + .add_system_to_stage(Update, back_to_menu_ui) + .add_system_to_stage(Update, audio_demo_ui); +} + +fn audio_demo_ui( + ctx: Res, + localization: Localization, + mut audio: ResMut, + meta: Root, + assets: Res, +) { + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(&ctx, |ui| { + ui.vertical_centered(|ui| { + ui.add_space(50.0); + if ui.button(localization.get("play-sound")).clicked() { + audio.play(&*assets.get(meta.audio_demo)).unwrap(); + } + }) + }); +} + +fn storage_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultSessionPlugin) + .add_system_to_stage(Update, storage_demo_ui) + .add_system_to_stage(Update, back_to_menu_ui); +} + +fn storage_demo_ui( + ctx: Res, + mut storage: ResMut, + localization: Localization, +) { + egui::CentralPanel::default().show(&ctx, |ui| { + ui.add_space(20.0); + + ui.vertical_centered(|ui| { + ui.set_width(300.0); + { + let data = storage.get_or_insert_default_mut::(); + egui::TextEdit::singleline(&mut data.0) + .hint_text(localization.get("persisted-text-box-content")) + .show(ui); + } + if ui.button(localization.get("save")).clicked() { + storage.save() + } + }); + }); +} + +fn path2d_demo_plugin(session: &mut Session) { + session + .install_plugin(DefaultSessionPlugin) + .add_startup_system(path2d_demo_startup) + .add_system_to_stage(Update, back_to_menu_ui); +} + +fn path2d_demo_startup( + meta: Root, + mut entities: ResMut, + mut transforms: CompMut, + mut cameras: CompMut, + mut path2ds: CompMut, +) { + spawn_default_camera(&mut entities, &mut transforms, &mut cameras); + + let ent = entities.create(); + transforms.insert(ent, default()); + const SIZE: f32 = 40.; + path2ds.insert( + ent, + Path2d { + color: meta.path2d_color, + points: vec![ + vec2(-SIZE, 0.), + vec2(0., SIZE), + vec2(SIZE, 0.), + vec2(-SIZE, 0.), + ], + thickness: 2.0, + ..default() + }, + ); +} + +/// Simple UI system that shows a button at the bottom of the screen to delete the current session +/// and go back to the main menu. +fn back_to_menu_ui( + ctx: Res, + mut sessions: ResMut, + mut session_options: ResMut, + localization: Localization, +) { + egui::TopBottomPanel::bottom("back-to-menu") + .frame(egui::Frame::none()) + .show_separator_line(false) + .show(&ctx, |ui| { + ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { + ui.add_space(20.0); + if ui.button(localization.get("back-to-menu")).clicked() { + session_options.delete = true; + sessions.create("menu").install_plugin(menu_plugin); + } + }); + }); +} + +/// This is an example widget system. +fn demo_widget( + // Widget systems must have an `In<&mut egui::Ui>` parameter as their first argument. + mut ui: In<&mut egui::Ui>, + // They can have any normal bones system parameters + meta: Root, + egui_textures: Res, + // And they may return an `egui::Response` or any other value. +) -> egui::Response { + ui.label("Demo Widget"); + // When using a bones image in egui, we have to get it's corresponding egui texture + // from the egui textures resource. + ui.image(egui::load::SizedTexture::new( + egui_textures.get(meta.menu_image), + [50., 50.], + )) +} diff --git a/demos/hello_world_wgpu/src/main.rs b/demos/hello_world_wgpu/src/main.rs index 7ce169f553..fcb4bc7bae 100644 --- a/demos/hello_world_wgpu/src/main.rs +++ b/demos/hello_world_wgpu/src/main.rs @@ -45,9 +45,10 @@ fn main() { .install_plugin(sprite_demo_plugin); world_session // Install the default bones_framework plugin for this session - .install_plugin(DefaultSessionPlugin); - // Add our menu system to the update stage - //.add_system_to_stage(Update, menu_system); + .install_plugin(DefaultSessionPlugin) + // Add our menu system to the update stage + //.add_system_to_stage(Update, menu_system) + .add_system_to_stage(Update, test); BonesWgpuRenderer::new(game).run(); } @@ -254,8 +255,32 @@ fn move_sprite( } /// System to render the home menu. -fn _menu_system(ctx: Res) { +fn menu_system(ctx: Res) { egui::CentralPanel::default().show(&ctx, |ui| { ui.label("Hello World"); }); } + +fn test(ctx: Res) { + egui::Window::new("winit + egui + wgpu + bones :0") + .resizable(true) + .vscroll(true) + .default_open(false) + .show(&ctx, |ui| { + ui.label("Label!"); + + if ui.button("Button!").clicked() { + println!("boom!") + } + + ui.separator(); + ui.horizontal(|ui| { + if ui.button("-").clicked() { + println!("Sei la"); + } + if ui.button("+").clicked() { + println!("Sei la"); + } + }); + }); +} diff --git a/framework_crates/bones_bevy_renderer/src/render.rs b/framework_crates/bones_bevy_renderer/src/render.rs index ac78d7e786..efbb061ac7 100644 --- a/framework_crates/bones_bevy_renderer/src/render.rs +++ b/framework_crates/bones_bevy_renderer/src/render.rs @@ -47,7 +47,7 @@ impl BonesImageIds { bevy_egui_textures: &mut bevy_egui::EguiUserTextures, ) { for entry in bones_assets.store.asset_ids.iter() { - let handle = entry.key(); + let handle: &bones::UntypedHandle = entry.key(); let cid = entry.value(); let mut asset = bones_assets.store.assets.get_mut(cid).unwrap(); if let Ok(image) = asset.data.try_cast_mut::() { @@ -77,7 +77,8 @@ impl BonesImageIds { if let bones::Image::Data(data) = taken_image { let handle = bevy_images.add(Image::from_dynamic(data, true)); let egui_texture = bevy_egui_textures.add_image(handle.clone()); - bones_egui_textures.insert(bones_handle, egui_texture); + // Broke when updating bones egui to 0.30 + //bones_egui_textures.insert(bones_handle, egui_texture); map.insert(*next_id, handle); *image = bones::Image::External(*next_id); *next_id += 1; diff --git a/framework_crates/bones_bevy_renderer/src/ui.rs b/framework_crates/bones_bevy_renderer/src/ui.rs index c44d85d991..85029b6441 100644 --- a/framework_crates/bones_bevy_renderer/src/ui.rs +++ b/framework_crates/bones_bevy_renderer/src/ui.rs @@ -11,7 +11,8 @@ pub fn setup_egui(world: &mut World) { }; // Insert the egui context as a shared resource - game.insert_shared_resource(bones::EguiCtx(ctx.clone())); + // Broke when updating bones egui to 0.30 + //game.insert_shared_resource(bones::EguiCtx(ctx.clone())); if let Some(bones_assets) = &game.asset_server() { update_egui_fonts(&ctx, bones_assets); @@ -34,7 +35,8 @@ pub fn egui_input_hook( if let Some(hook) = game.shared_resource_cell::() { let hook = hook.borrow().unwrap(); let mut egui_input = egui_query.get_single_mut().unwrap(); - (hook.0)(&mut game, &mut egui_input); + // Broke when updating bones egui to 0.30 + //(hook.0)(&mut game, &mut egui_input); } } @@ -57,6 +59,7 @@ pub fn update_egui_fonts(ctx: &bevy_egui::egui::Context, bones_assets: &bones::A let mut fonts = egui::FontDefinitions::default(); for entry in bones_assets.store.assets.iter() { + /*Broke when updating bones egui to 0.30 let asset = entry.value(); if let Ok(font) = asset.try_cast_ref::() { let previous = fonts @@ -75,6 +78,7 @@ pub fn update_egui_fonts(ctx: &bevy_egui::egui::Context, bones_assets: &bones::A .or_default() .push(font.family_name.to_string()); } + */ } ctx.set_fonts(fonts); diff --git a/framework_crates/bones_framework/Cargo.toml b/framework_crates/bones_framework/Cargo.toml index 9c50d09cfa..ff9b5b60e9 100644 --- a/framework_crates/bones_framework/Cargo.toml +++ b/framework_crates/bones_framework/Cargo.toml @@ -122,8 +122,8 @@ serde = { version = "1.0", features = ["derive"] } image = { version = "0.24", default-features = false } # Gui -egui = { version = "0.23", optional = true } -egui_plot = "0.23" +egui = { version = "0.30", optional = true } +egui_plot = "0.30" ttf-parser = { version = "0.24", default-features = false, optional = true } # Audio diff --git a/framework_crates/bones_framework/src/render/camera.rs b/framework_crates/bones_framework/src/render/camera.rs index 4df73a3f73..dc0b72990e 100644 --- a/framework_crates/bones_framework/src/render/camera.rs +++ b/framework_crates/bones_framework/src/render/camera.rs @@ -74,7 +74,7 @@ impl Default for Camera { viewport: Unset, priority: 0, size: default(), - background_color: Color::GRAY, + background_color: Color::BLACK, draw_background_color: true, } } diff --git a/framework_crates/bones_framework/src/render/transform.rs b/framework_crates/bones_framework/src/render/transform.rs index 53aa339e11..38549b8da6 100644 --- a/framework_crates/bones_framework/src/render/transform.rs +++ b/framework_crates/bones_framework/src/render/transform.rs @@ -50,12 +50,12 @@ impl Transform { } /// Converts the transform to a 4x4 matrix for rendering - pub fn to_matrix(&self) -> Mat4 { + pub fn to_matrix(&self, translation_scale: Vec3) -> Mat4 { let angle = self.rotation.z.rem_euclid(2.0 * PI); let rotation = Mat4::from_rotation_z(angle); let scale_rotation = rotation * Mat4::from_scale(self.scale); - Mat4::from_translation(self.translation) * scale_rotation + Mat4::from_translation(self.translation * translation_scale) * scale_rotation } /// Converts the transform to a 4x4 matrix for rendering, diff --git a/framework_crates/bones_framework/src/render/ui.rs b/framework_crates/bones_framework/src/render/ui.rs index a0f553099e..59cdfecfb5 100644 --- a/framework_crates/bones_framework/src/render/ui.rs +++ b/framework_crates/bones_framework/src/render/ui.rs @@ -35,14 +35,14 @@ impl EguiInputHook { } /// Resource that maps image handles to their associated egui textures. -#[derive(HasSchema, Clone, Debug, Default, Deref, DerefMut)] -pub struct EguiTextures(pub HashMap, egui::TextureId>); +#[derive(HasSchema, Clone, Default, Deref, DerefMut)] +pub struct EguiTextures(pub HashMap, egui::TextureHandle>); impl EguiTextures { /// Get the [`egui::TextureId`] for the given bones [`Handle`]. #[track_caller] pub fn get(&self, handle: Handle) -> egui::TextureId { - *self.0.get(&handle).unwrap() + self.0.get(&handle).unwrap().id() } } @@ -180,13 +180,13 @@ pub trait EguiContextExt { impl EguiContextExt for &egui::Context { fn clear_focus(self) { - self.memory_mut(|r| r.request_focus(egui::Id::null())); + self.memory_mut(|r| r.request_focus(egui::Id::NULL)); } fn get_state(self) -> T { - self.data_mut(|data| data.get_temp_mut_or_default::(egui::Id::null()).clone()) + self.data_mut(|data| data.get_temp_mut_or_default::(egui::Id::NULL).clone()) } fn set_state(self, value: T) { - self.data_mut(|data| *data.get_temp_mut_or_default::(egui::Id::null()) = value); + self.data_mut(|data| *data.get_temp_mut_or_default::(egui::Id::NULL) = value); } } @@ -198,7 +198,7 @@ pub trait EguiResponseExt { impl EguiResponseExt for egui::Response { fn focus_by_default(self, ui: &mut egui::Ui) -> egui::Response { - if ui.ctx().memory(|r| r.focus().is_none()) { + if ui.ctx().memory(|r| r.focused().is_none()) { ui.ctx().memory_mut(|r| r.request_focus(self.id)); self diff --git a/framework_crates/bones_framework/src/render/ui/widgets.rs b/framework_crates/bones_framework/src/render/ui/widgets.rs index 8d4aaf3eb5..8a55b3122b 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets.rs @@ -75,7 +75,7 @@ pub struct MarginMeta { pub right: f32, } -impl From for egui::style::Margin { +impl From for egui::Margin { fn from(m: MarginMeta) -> Self { Self { left: m.left, diff --git a/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs b/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs index 42df4fbd7d..de5b8971c0 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs @@ -16,8 +16,8 @@ pub struct BorderedButton<'a> { default_border: Option<&'a BorderImageMeta>, on_focus_border: Option<&'a BorderImageMeta>, on_click_border: Option<&'a BorderImageMeta>, - margin: egui::style::Margin, - padding: egui::style::Margin, + margin: egui::Margin, + padding: egui::Margin, } impl<'a> BorderedButton<'a> { @@ -78,7 +78,7 @@ impl<'a> BorderedButton<'a> { /// Set the margin. This will be applied on the outside of the border. #[must_use = "You must call .show() to render the button"] - pub fn margin(mut self, margin: impl Into) -> Self { + pub fn margin(mut self, margin: impl Into) -> Self { self.margin = margin.into(); self @@ -86,7 +86,7 @@ impl<'a> BorderedButton<'a> { /// Set the padding. This will be applied on the inside of the border. #[must_use = "You must call .show() to render the button"] - pub fn padding(mut self, padding: impl Into) -> Self { + pub fn padding(mut self, padding: impl Into) -> Self { self.padding = padding.into(); self @@ -153,13 +153,26 @@ impl<'a> Widget for BorderedButton<'a> { let total_extra = padding.sum() + margin.sum(); let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button); + let text = text.into_galley( + ui, + if let Some(wrap) = wrap { + if wrap { + Some(egui::TextWrapMode::Wrap) + } else { + None + } + } else { + None + }, + wrap_width, + TextStyle::Button, + ); let mut desired_size = text.size() + total_extra; desired_size = desired_size.at_least(egui::vec2(min_size.x, min_size.y)); let (rect, response) = ui.allocate_at_least(desired_size, sense); - response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, text.text())); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Button, true, text.text())); // Focus the button automatically when it is hovered and the mouse is moving if response.hovered() @@ -199,7 +212,7 @@ impl<'a> Widget for BorderedButton<'a> { if let Some(border) = border { let texture = ui.data(|map| { - map.get_temp::>(egui::Id::null()) + map.get_temp::>(egui::Id::NULL) .unwrap() .borrow() .unwrap() @@ -209,7 +222,10 @@ impl<'a> Widget for BorderedButton<'a> { .add(BorderedFrame::new(border).paint(texture, border_rect)); } - text.paint_with_visuals(ui.painter(), label_pos, visuals); + // OLD + //text.paint_with_visuals(ui.painter(), label_pos, visuals); + + ui.painter().galley(label_pos, text, visuals.text_color()); } response diff --git a/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs b/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs index d34f56135a..807e7b5d08 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs @@ -5,9 +5,9 @@ pub struct BorderedFrame { bg_handle: Handle, border_scale: f32, texture_size: egui::Vec2, - texture_border_size: egui::style::Margin, - padding: egui::style::Margin, - margin: egui::style::Margin, + texture_border_size: egui::Margin, + padding: egui::Margin, + margin: egui::Margin, border_only: bool, } @@ -29,7 +29,7 @@ impl BorderedFrame { /// Set the padding. This will be applied on the inside of the border. #[must_use = "You must call .show() to render the frame"] - pub fn padding(mut self, margin: impl Into) -> Self { + pub fn padding(mut self, margin: impl Into) -> Self { self.padding = margin.into(); self @@ -37,7 +37,7 @@ impl BorderedFrame { /// Set the margin. This will be applied on the outside of the border. #[must_use = "You must call .show() to render the frame"] - pub fn margin(mut self, margin: egui::style::Margin) -> Self { + pub fn margin(mut self, margin: egui::Margin) -> Self { self.margin = margin; self @@ -97,7 +97,11 @@ impl BorderedFrame { content_rect.max.x = content_rect.max.x.max(content_rect.min.x); content_rect.max.y = content_rect.max.y.max(content_rect.min.y); - let content_ui = ui.child_ui(content_rect, *ui.layout()); + let content_ui = ui.new_child(egui::UiBuilder { + layout: Some(*ui.layout()), + max_rect: Some(content_rect), + ..Default::default() + }); BorderedFramePrepared { frame: self, @@ -120,13 +124,13 @@ impl BorderedFrame { let b = self.texture_border_size; let pr = paint_rect; // UV border - let buv = egui::style::Margin { + let buv = egui::Margin { left: b.left / s.x, right: b.right / s.x, top: b.top / s.y, bottom: b.bottom / s.y, }; - let b = egui::style::Margin { + let b = egui::Margin { left: b.left * self.border_scale, right: b.right * self.border_scale, top: b.top * self.border_scale, @@ -266,7 +270,7 @@ impl BorderedFramePrepared { }; if ui.is_rect_visible(paint_rect) { let texture = ui.data(|map| { - map.get_temp::>(egui::Id::null()) + map.get_temp::>(egui::Id::NULL) .unwrap() .borrow() .unwrap() diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml index 167bdc146b..3e186f0b2b 100644 --- a/framework_crates/bones_wgpu_renderer/Cargo.toml +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -11,18 +11,25 @@ categories.workspace = true keywords.workspace = true [dependencies] -bones_framework = { version = "0.4.0", path = "../bones_framework" } -bones_schema = { path = "../bones_schema" } -winit = "0.30.9" +bones_framework = { version = "0.4.0", path = "../bones_framework" } +bones_schema = { path = "../bones_schema" } +winit = "0.30.9" #Needs to be 23 because of bones_bevy_renderer dependecies, we need to upgrade to 24^ later wgpu = "23" pollster = "0.4.0" env_logger = "0.11.6" +log = "0.4" bevy_tasks = "0.11.3" image = "0.24.9" crossbeam-channel = "0.5.14" bytemuck = "1.22" -egui = "0.30" -egui-wgpu = "0.30" -egui-winit = "0.30" \ No newline at end of file +egui = "0.30" +egui-wgpu = "0.30" +egui-winit = "0.30" + +serde = "1.0.188" +serde_yaml = "0.9" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +directories = "5.0" diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 59b4fbdec2..40dd6a4357 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -27,11 +27,12 @@ use egui_wgpu::ScreenDescriptor; mod convert; mod sprite; +mod storage; mod texture; mod ui; use sprite::*; -use ui::EguiRenderer; +use ui::{default_load_progress, EguiRenderer}; /// The prelude pub mod prelude { @@ -81,11 +82,18 @@ struct TextureLoaded; #[repr(C)] struct PixelArt(bool); +#[derive(bones_schema::HasSchema, Default, bones::Deref, bones::DerefMut)] +#[schema(no_clone)] +struct LoadingContext(pub Option); +type LoadingFunction = Box; + /// Renderer for [`bones_framework`] [`Game`][bones::Game]s using wgpu. pub struct BonesWgpuRenderer { /// Whether or not to load all assets on startup with a loading screen, /// or skip straight to running the bones game immedietally. pub preload: bool, + /// Optional field to implement your own loading screen. Does nothing if [`Self::preload`] = false + pub custom_load_progress: Option, /// Whether or not to use nearest-neighbor sampling for textures. pub pixel_art: bool, /// The bones game to run. @@ -107,6 +115,7 @@ impl BonesWgpuRenderer { pub fn new(game: bones::Game) -> Self { BonesWgpuRenderer { preload: true, + custom_load_progress: None, pixel_art: true, game, game_version: bones::Version::new(0, 1, 0), @@ -173,6 +182,7 @@ impl BonesWgpuRenderer { } .block_on(); + //Insert wgpu resources self.game .insert_shared_resource(WgpuDevice(Some(device.clone()))); self.game @@ -182,6 +192,9 @@ impl BonesWgpuRenderer { let (sender, receiver) = unbounded(); self.game.insert_shared_resource(TextureSender(sender)); + self.game + .insert_shared_resource(LoadingContext(self.custom_load_progress)); + IoTaskPool::init(TaskPool::default); if let Some(mut asset_server) = self.game.shared_resource_mut::() { asset_server.set_game_version(self.game_version.clone()); @@ -204,6 +217,18 @@ impl BonesWgpuRenderer { asset_server.watch_for_changes(); } + // Configure and load the persitent storage + let mut storage = bones::Storage::with_backend(Box::new(storage::StorageBackend::new( + &self.app_namespace.0, + &self.app_namespace.1, + &self.app_namespace.2, + ))); + storage.load(); + self.game.insert_shared_resource(storage); + self.game + .insert_shared_resource(bones::EguiTextures::default()); + self.game.insert_shared_resource(bones::ExitBones(false)); + // Insert empty inputs that will be updated by the `insert_bones_input` system later. self.game.init_shared_resource::(); self.game.init_shared_resource::(); @@ -214,14 +239,15 @@ impl BonesWgpuRenderer { .init_shared_resource::(); //Insert needed systems - for (_, session) in self.game.sessions.iter_mut() { - session.add_system_to_stage(bones::First, load_sprite); - session.add_system_to_stage(bones::First, load_atlas_sprite); - session.add_system_to_stage(bones::First, load_tile_sprite); - session.add_system_to_stage(bones::First, update_atlas_uniforms); - session.add_system_to_stage(bones::First, update_sprite_uniforms); - session.add_system_to_stage(bones::First, update_tiles_uniforms); - } + self.game.systems.add_before_system(load_sprite); + self.game.systems.add_before_system(load_atlas_sprite); + self.game.systems.add_before_system(load_tile_sprite); + self.game.systems.add_before_system(update_atlas_uniforms); + self.game.systems.add_before_system(update_sprite_uniforms); + self.game.systems.add_before_system(update_tiles_uniforms); + + self.game.systems.add_startup_system(load_egui_textures); + self.game.systems.add_startup_system(asset_load_status); // wgpu uses `log` for all of our logging, so we initialize a logger with the `env_logger` crate. env_logger::init(); @@ -245,9 +271,118 @@ impl BonesWgpuRenderer { _now: Instant::now(), }; event_loop.run_app(&mut app).unwrap(); + + app.device.poll(wgpu::Maintain::Wait); } } +fn asset_load_status(game: &mut bones::Game) { + let asset_server = game.shared_resource::().unwrap(); + let ctx = game.shared_resource::().unwrap(); + let mut function = game.shared_resource_mut::().unwrap(); + + if asset_server.load_progress.is_finished() { + return; + } + + if let Some(function) = &mut function.0 { + (function)(&asset_server, &ctx); + } else { + default_load_progress(&asset_server, &ctx); + } +} + +/// Startup system to load egui fonts and textures. +pub fn setup_egui(game: &mut bones::Game, ctx: &egui::Context) { + // Insert the egui context as a shared resource + game.insert_shared_resource(bones::EguiCtx(ctx.clone())); + + let asset_server = game.shared_resource::(); + + if let Some(bones_assets) = asset_server { + update_egui_fonts(ctx, &bones_assets); + + // Insert the bones egui textures + ctx.data_mut(|map| { + map.insert_temp( + egui::Id::NULL, + game.shared_resource_cell::().unwrap(), + ); + }); + } +} + +pub fn update_egui_fonts(ctx: &egui::Context, bones_assets: &bones::AssetServer) { + let mut fonts = egui::FontDefinitions::default(); + + for entry in bones_assets.store.assets.iter() { + let asset = entry.value(); + if let Ok(font) = asset.try_cast_ref::() { + let previous = fonts + .font_data + .insert(font.family_name.to_string(), font.data.clone().into()); + if previous.is_some() { + log::warn!( + "{} Found two fonts with the same family name, using \ + only the latest one", + font.family_name + ); + } + fonts + .families + .entry(egui::FontFamily::Name(font.family_name.clone())) + .or_default() + .push(font.family_name.to_string()); + } + } + + ctx.set_fonts(fonts); +} +//TODO Handle asset changes +fn load_egui_textures(game: &mut bones::Game) { + let asset_server = game.shared_resource::().unwrap(); + let ctx = game.shared_resource::().unwrap(); + let mut egui_textures = game.shared_resource_mut::().unwrap(); + let pixel_art = game.shared_resource::().unwrap(); + + for entry in asset_server.store.asset_ids.iter() { + let id = entry.key().typed(); + if egui_textures.contains_key(&id) { + // we already loaded this one + continue; + } + + let asset = asset_server.store.assets.get_mut(entry.value()).unwrap(); + if let Ok(image) = asset.data.try_cast_ref::() { + if let bones::Image::Data(data) = image { + let rgba: RgbaImage = data.to_rgba8(); + let (w, h) = (rgba.width() as usize, rgba.height() as usize); + let raw = rgba.into_raw(); + + let handle = ctx.load_texture( + &format!("Texture {:?}", entry.key()), + egui::ColorImage::from_rgba_unmultiplied([w, h], &raw), + egui::TextureOptions { + magnification: if pixel_art.0 { + egui::TextureFilter::Nearest + } else { + egui::TextureFilter::Linear + }, + minification: if pixel_art.0 { + egui::TextureFilter::Nearest + } else { + egui::TextureFilter::Linear + }, + ..Default::default() + }, + ); + egui_textures.insert(id, handle); + } + } + } +} + +//TODO Implement proper Drop for the app struct App { state: Option, instance: Arc, @@ -288,6 +423,8 @@ impl ApplicationHandler for App { &self.texture_bind_group_layout, ); + setup_egui(&mut self.game, &state.egui_renderer.context().clone()); + self.state = Some(state); } @@ -339,6 +476,10 @@ impl ApplicationHandler for App { //println!("{}", self.now.elapsed().as_secs_f32()); //self.now = Instant::now(); + if self.game.shared_resource::().unwrap().0 { + event_loop.exit(); + } + state.render(&mut self.game); // Emits a new redraw requested event. state.get_window().request_redraw(); @@ -792,11 +933,14 @@ impl State { continue; }; + let (w, h) = (self.size.width as f32, self.size.height as f32); + let translation_scale = Vec3::new(1. / w, 1. / h, 1.); + camera_transform.translation.z = 0.; - let camera_mat4 = camera_transform.to_matrix().inverse(); + let camera_mat4 = camera_transform.to_matrix(translation_scale).inverse(); - // Get the 4×4 transform matrix. - let mat4 = scale_ratio * camera_mat4 * transform.to_matrix(); + // Build the final matrix + let mat4 = scale_ratio * camera_mat4 * transform.to_matrix(translation_scale); // Compute transformed vertices on the CPU. let transformed_vertices: Vec = VERTICES @@ -839,37 +983,9 @@ impl State { self.egui_renderer.begin_frame(&self.window); - //Insert the egui ctx - //game.insert_shared_resource(bones::EguiCtx(self.egui_renderer.context().clone())); //Step bones game.step(Instant::now()); - egui::Window::new("winit + egui + wgpu says hello!") - .resizable(true) - .vscroll(true) - .default_open(false) - .show(self.egui_renderer.context(), |ui| { - ui.label("Label!"); - - if ui.button("Button!").clicked() { - println!("boom!") - } - - ui.separator(); - ui.horizontal(|ui| { - ui.label(format!( - "Pixels per point: {}", - self.egui_renderer.context().pixels_per_point() - )); - if ui.button("-").clicked() { - self.egui_scale_factor = (self.egui_scale_factor - 0.1).max(0.3); - } - if ui.button("+").clicked() { - self.egui_scale_factor = (self.egui_scale_factor + 0.1).min(3.0); - } - }); - }); - self.egui_renderer.end_frame_and_draw( &self.device, &self.queue, diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs index e70402d69c..53f1f60f01 100644 --- a/framework_crates/bones_wgpu_renderer/src/sprite.rs +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -88,235 +88,300 @@ impl AtlasSpriteUniform { } } -pub fn load_sprite( - entities: bones::Res, - sprites: bones::Comp, - assets: bones::Res, - device: bones::Res, - queue: bones::Res, - texture_sender: bones::Res, - pixel_art: bones::Res, - mut buffers: bones::CompMut, - mut texture_loaded: bones::CompMut, -) { - let mut not_loaded = texture_loaded.bitset().clone(); - not_loaded.bit_not(); - not_loaded.bit_and(sprites.bitset()); - - for entity in entities.iter_with_bitset(¬_loaded) { - let Some(sprite) = sprites.get(entity) else { - unreachable!(); - }; - - //Load and send texture - let image = assets.get(sprite.image); - if let bones::Image::Data(img) = &*image { - let texture = Arc::new( - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), - ); - - let atlas_uniform = AtlasSpriteUniform { - use_atlas: 0, - flip_x: sprite.flip_x as u32, - flip_y: sprite.flip_y as u32, - color_tint: sprite.color.as_rgba_f32(), - ..Default::default() +pub fn load_sprite(game: &mut bones::Game) { + let assets = game.shared_resource_cell::().unwrap(); + let device = game.shared_resource_cell::().unwrap(); + let queue = game.shared_resource_cell::().unwrap(); + let texture_sender = game.shared_resource_cell::().unwrap(); + let pixel_art = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + let entities = session.world.resource::(); + let sprites = session.world.component::(); + let mut buffers = session.world.component_mut::(); + let mut texture_loaded = session.world.component_mut::(); + + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(sprites.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(sprite) = sprites.get(entity) else { + unreachable!(); }; - let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[atlas_uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }, - )); - - //Add buffer to bones so we can update it - buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); - - texture_sender - .0 - .send((texture, entity, atlas_sprite_buffer)) - .unwrap(); - texture_loaded.insert(entity, TextureLoaded); - } else { - unreachable!() - }; - } -} + println!("Loading sprite: {:?}", sprite); + + //Load and send texture + let assets = assets.borrow().unwrap(); + let image = assets.get(sprite.image); + if let bones::Image::Data(img) = &*image { + let texture = Arc::new( + Texture::from_image( + device.borrow().unwrap().get(), + queue.borrow().unwrap().get(), + img, + None, + pixel_art.borrow().unwrap().0, + ) + .unwrap(), + ); + + let atlas_uniform = AtlasSpriteUniform { + use_atlas: 0, + flip_x: sprite.flip_x as u32, + flip_y: sprite.flip_y as u32, + color_tint: sprite.color.as_rgba_f32(), + ..Default::default() + }; -pub fn load_atlas_sprite( - entities: bones::Res, - atlas_sprites: bones::Comp, - assets: bones::Res, - device: bones::Res, - queue: bones::Res, - texture_sender: bones::Res, - pixel_art: bones::Res, - mut buffers: bones::CompMut, - mut texture_loaded: bones::CompMut, -) { - let mut not_loaded = texture_loaded.bitset().clone(); - not_loaded.bit_not(); - not_loaded.bit_and(atlas_sprites.bitset()); - - for entity in entities.iter_with_bitset(¬_loaded) { - let Some(atlas_sprite) = atlas_sprites.get(entity) else { - unreachable!(); - }; - - //Load and send texture - let atlas = assets.get(atlas_sprite.atlas); - let image = assets.get(atlas.image); - if let bones::Image::Data(img) = &*image { - let texture = Arc::new( - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), - ); - // create and send the atlas sprite uniform along with the texture and entity - let uniform = AtlasSpriteUniform::from_atlas_sprite( - atlas_sprite, - &assets.get(atlas_sprite.atlas), - ); + let atlas_sprite_buffer = + Arc::new(device.borrow().unwrap().get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[atlas_uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); - let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }, - )); - - //Add buffer to bones so we can update it - buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); - - texture_sender - .0 - .send((texture, entity, atlas_sprite_buffer)) - .unwrap(); - texture_loaded.insert(entity, TextureLoaded); - } else { - unreachable!() - }; + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .borrow() + .unwrap() + .0 + .send((texture, entity, atlas_sprite_buffer)) + .unwrap(); + texture_loaded.insert(entity, TextureLoaded); + } else { + unreachable!() + }; + } } } -pub fn load_tile_sprite( - entities: bones::Res, - tile_layers: bones::Comp, - tiles: bones::Comp, - assets: bones::Res, - device: bones::Res, - queue: bones::Res, - texture_sender: bones::Res, - pixel_art: bones::Res, - mut buffers: bones::CompMut, - mut texture_loaded: bones::CompMut, -) { - let mut not_loaded = texture_loaded.bitset().clone(); - not_loaded.bit_not(); - not_loaded.bit_and(tile_layers.bitset()); - - for entity in entities.iter_with_bitset(¬_loaded) { - let Some(tile_layer) = tile_layers.get(entity) else { - unreachable!(); - }; - - //Load and send texture - let atlas = assets.get(tile_layer.atlas); - let image = assets.get(atlas.image); - if let bones::Image::Data(img) = &*image { - let texture = Arc::new( - Texture::from_image(device.get(), queue.get(), img, None, pixel_art.0).unwrap(), - ); +pub fn load_atlas_sprite(game: &mut bones::Game) { + let assets = game.shared_resource_cell::().unwrap(); + let device = game.shared_resource_cell::().unwrap(); + let queue = game.shared_resource_cell::().unwrap(); + let texture_sender = game.shared_resource_cell::().unwrap(); + let pixel_art = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + let entities = session.world.resource::(); + let atlas_sprites = session.world.component::(); + let mut buffers = session.world.component_mut::(); + let mut texture_loaded = session.world.component_mut::(); + + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(atlas_sprites.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(atlas_sprite) = atlas_sprites.get(entity) else { + unreachable!(); + }; - for tile in &tile_layer.tiles { - let Some(tile) = tile else { - continue; - }; - let Some(tile) = tiles.get(*tile) else { - panic!("Couldn't find tile entity!"); - }; + //Load and send texture + let assets = assets.borrow().unwrap(); + let atlas = assets.get(atlas_sprite.atlas); + let image = assets.get(atlas.image); + if let bones::Image::Data(img) = &*image { + let texture = Arc::new( + Texture::from_image( + device.borrow().unwrap().get(), + queue.borrow().unwrap().get(), + img, + None, + pixel_art.borrow().unwrap().0, + ) + .unwrap(), + ); // create and send the atlas sprite uniform along with the texture and entity - let uniform = AtlasSpriteUniform::from_tile(tile, &assets.get(tile_layer.atlas)); - - let atlas_sprite_buffer = Arc::new(device.get().create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[uniform]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }, - )); + let uniform = AtlasSpriteUniform::from_atlas_sprite( + atlas_sprite, + &assets.get(atlas_sprite.atlas), + ); + + let atlas_sprite_buffer = + Arc::new(device.borrow().unwrap().get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); //Add buffer to bones so we can update it buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); texture_sender + .borrow() + .unwrap() .0 - .send((texture.clone(), entity, atlas_sprite_buffer)) + .send((texture, entity, atlas_sprite_buffer)) .unwrap(); texture_loaded.insert(entity, TextureLoaded); - } - } else { - unreachable!() - }; + } else { + unreachable!() + }; + } + } +} + +pub fn load_tile_sprite(game: &mut bones::Game) { + let assets = game.shared_resource_cell::().unwrap(); + let device = game.shared_resource_cell::().unwrap(); + let queue = game.shared_resource_cell::().unwrap(); + let texture_sender = game.shared_resource_cell::().unwrap(); + let pixel_art = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + let entities = session.world.resource::(); + let tile_layers = session.world.component::(); + let tiles = session.world.component::(); + let mut buffers = session.world.component_mut::(); + let mut texture_loaded = session.world.component_mut::(); + + let mut not_loaded = texture_loaded.bitset().clone(); + not_loaded.bit_not(); + not_loaded.bit_and(tile_layers.bitset()); + + for entity in entities.iter_with_bitset(¬_loaded) { + let Some(tile_layer) = tile_layers.get(entity) else { + unreachable!(); + }; + + //Load and send texture + let assets = assets.borrow().unwrap(); + let atlas = assets.get(tile_layer.atlas); + let image = assets.get(atlas.image); + if let bones::Image::Data(img) = &*image { + let texture = Arc::new( + Texture::from_image( + device.borrow().unwrap().get(), + queue.borrow().unwrap().get(), + img, + None, + pixel_art.borrow().unwrap().0, + ) + .unwrap(), + ); + + for tile in &tile_layer.tiles { + let Some(tile) = tile else { + continue; + }; + let Some(tile) = tiles.get(*tile) else { + panic!("Couldn't find tile entity!"); + }; + // create and send the atlas sprite uniform along with the texture and entity + let uniform = + AtlasSpriteUniform::from_tile(tile, &assets.get(tile_layer.atlas)); + + let atlas_sprite_buffer = + Arc::new(device.borrow().unwrap().get().create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Atlas Sprite Buffer"), + contents: bytemuck::cast_slice(&[uniform]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }, + )); + + //Add buffer to bones so we can update it + buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + + texture_sender + .borrow() + .unwrap() + .0 + .send((texture.clone(), entity, atlas_sprite_buffer)) + .unwrap(); + texture_loaded.insert(entity, TextureLoaded); + } + } else { + unreachable!() + }; + } } } // System for updating atlas uniforms -pub fn update_atlas_uniforms( - entities: bones::Res, - atlases: bones::Comp, - mut buffers: bones::CompMut, - assets: bones::Res, - queue: bones::Res, -) { - for (_, (atlas_sprite, atlas_sprite_buffer)) in entities.iter_with((&atlases, &mut buffers)) { - let atlas = assets.get(atlas_sprite.atlas).clone(); - let uniform = AtlasSpriteUniform::from_atlas_sprite(atlas_sprite, &atlas); - queue - .get() - .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); +pub fn update_atlas_uniforms(game: &mut bones::Game) { + let assets = game.shared_resource_cell::().unwrap(); + let queue = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + let entities = session.world.resource::(); + let atlases = session.world.component::(); + let mut buffers = session.world.component_mut::(); + + for (_, (atlas_sprite, atlas_sprite_buffer)) in entities.iter_with((&atlases, &mut buffers)) + { + let assets = assets.borrow().unwrap(); + let atlas = assets.get(atlas_sprite.atlas).clone(); + let uniform = AtlasSpriteUniform::from_atlas_sprite(atlas_sprite, &atlas); + queue.borrow().unwrap().get().write_buffer( + &atlas_sprite_buffer.0, + 0, + bytemuck::bytes_of(&uniform), + ); + } } } // System for updating sprite uniforms -pub fn update_sprite_uniforms( - entities: bones::Res, - sprites: bones::Comp, - mut buffers: bones::CompMut, - queue: bones::Res, -) { - for (_, (sprite, atlas_sprite_buffer)) in entities.iter_with((&sprites, &mut buffers)) { - let uniform = AtlasSpriteUniform::from_sprite(sprite); - queue - .get() - .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); +pub fn update_sprite_uniforms(game: &mut bones::Game) { + let queue = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + let entities = session.world.resource::(); + let sprites = session.world.component::(); + let mut buffers = session.world.component_mut::(); + + for (_, (sprite, atlas_sprite_buffer)) in entities.iter_with((&sprites, &mut buffers)) { + let uniform = AtlasSpriteUniform::from_sprite(sprite); + queue.borrow().unwrap().get().write_buffer( + &atlas_sprite_buffer.0, + 0, + bytemuck::bytes_of(&uniform), + ); + } } } // System for updating tiles uniforms -pub fn update_tiles_uniforms( - entities: bones::Res, - tile_layers: bones::Comp, - tiles: bones::Comp, - mut buffers: bones::CompMut, - assets: bones::Res, - queue: bones::Res, -) { - for (_, (tile_layer, atlas_sprite_buffer)) in entities.iter_with((&tile_layers, &mut buffers)) { - let atlas = assets.get(tile_layer.atlas).clone(); - for tile in &tile_layer.tiles { - let Some(tile) = tile else { - continue; - }; - let Some(tile) = tiles.get(*tile) else { - panic!("Couldn't find tile entity!"); - }; +pub fn update_tiles_uniforms(game: &mut bones::Game) { + let assets = game.shared_resource_cell::().unwrap(); + let queue = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + let entities = session.world.resource::(); + let tile_layers = session.world.component::(); + let tiles = session.world.component::(); + let mut buffers = session.world.component_mut::(); + + for (_, (tile_layer, atlas_sprite_buffer)) in + entities.iter_with((&tile_layers, &mut buffers)) + { + let assets = assets.borrow().unwrap(); + let atlas = assets.get(tile_layer.atlas).clone(); + for tile in &tile_layer.tiles { + let Some(tile) = tile else { + continue; + }; + let Some(tile) = tiles.get(*tile) else { + panic!("Couldn't find tile entity!"); + }; - let uniform = AtlasSpriteUniform::from_tile(tile, &atlas); - queue - .get() - .write_buffer(&atlas_sprite_buffer.0, 0, bytemuck::bytes_of(&uniform)); + let uniform = AtlasSpriteUniform::from_tile(tile, &atlas); + queue.borrow().unwrap().get().write_buffer( + &atlas_sprite_buffer.0, + 0, + bytemuck::bytes_of(&uniform), + ); + } } } } diff --git a/framework_crates/bones_wgpu_renderer/src/storage.rs b/framework_crates/bones_wgpu_renderer/src/storage.rs new file mode 100644 index 0000000000..e03bab9af9 --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/storage.rs @@ -0,0 +1,178 @@ +use bones_framework::prelude::*; +use serde::{de::Visitor, Deserialize, Serialize}; + +#[cfg(target_arch = "wasm32")] +pub use wasm::StorageBackend; +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::*; + pub struct StorageBackend { + storage_key: String, + } + + impl StorageBackend { + pub fn new(qualifier: &str, organization: &str, application: &str) -> Self { + Self { + storage_key: format!("{qualifier}.{organization}.{application}.storage"), + } + } + } + + impl StorageApi for StorageBackend { + fn save(&mut self, data: Vec) { + let mut buffer = Vec::new(); + let mut serializer = serde_yaml::Serializer::new(&mut buffer); + LoadedStorage(data) + .serialize(&mut serializer) + .expect("Failed to serialize to storage file."); + let data = String::from_utf8(buffer).unwrap(); + let window = web_sys::window().unwrap(); + let storage = window.local_storage().unwrap().unwrap(); + storage.set_item(&self.storage_key, &data).unwrap(); + } + + fn load(&mut self) -> Vec { + let window = web_sys::window().unwrap(); + let storage = window.local_storage().unwrap().unwrap(); + let Some(data) = storage.get_item(&self.storage_key).unwrap() else { + return default(); + }; + + let Ok(loaded) = serde_yaml::from_str::(&data) else { + return default(); + }; + loaded.0 + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub use native::StorageBackend; +#[cfg(not(target_arch = "wasm32"))] +mod native { + use super::*; + + pub struct StorageBackend { + storage_path: std::path::PathBuf, + } + + impl StorageBackend { + pub fn new(qualifier: &str, organization: &str, application: &str) -> Self { + let project_dirs = directories::ProjectDirs::from(qualifier, organization, application) + .expect("Identify system data dir path"); + Self { + storage_path: project_dirs.data_dir().join("storage.yml"), + } + } + } + + impl StorageApi for StorageBackend { + fn save(&mut self, data: Vec) { + let file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&self.storage_path) + .expect("Failed to open storage file"); + let mut serializer = serde_yaml::Serializer::new(file); + LoadedStorage(data) + .serialize(&mut serializer) + .expect("Failed to serialize to storage file."); + } + + fn load(&mut self) -> Vec { + if self.storage_path.exists() { + let result: anyhow::Result = (|| { + let file = std::fs::OpenOptions::new() + .read(true) + .open(&self.storage_path) + .context("Failed to open storage file")?; + let loaded: LoadedStorage = serde_yaml::from_reader(file) + .context("Failed to deserialize storage file")?; + + anyhow::Result::Ok(loaded) + })(); + match result { + Ok(loaded) => loaded.0, + Err(e) => { + log::error!( + "Error deserializing storage file, ignoring file, \ + data will be overwritten when saved: {e:?}" + ); + default() + } + } + } else { + std::fs::create_dir_all(self.storage_path.parent().unwrap()).unwrap(); + default() + } + } + } +} + +struct LoadedStorage(Vec); +impl Serialize for LoadedStorage { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let data: HashMap = self + .0 + .iter() + .map(|x| (x.schema().full_name.to_string(), x.as_ref())) + .collect(); + + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(data.len()))?; + + for (key, value) in data { + map.serialize_key(&key)?; + map.serialize_value(&SchemaSerializer(value))?; + } + + map.end() + } +} +impl<'de> Deserialize<'de> for LoadedStorage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(LoadedStorageVisitor).map(Self) + } +} +struct LoadedStorageVisitor; +impl<'de> Visitor<'de> for LoadedStorageVisitor { + type Value = Vec; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "Mapping of string type names to type data.") + } + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut data = Vec::new(); + while let Some(type_name) = map.next_key::()? { + let Some(schema) = SCHEMA_REGISTRY + .schemas + .iter() + .find(|schema| schema.full_name.as_ref() == type_name) + else { + log::error!( + "\n\nCannot find schema registration for `{}` while loading persisted \ + storage. This means you that you need to call \ + `{}::schema()` to register your persisted storage type before \ + creating the `BonesWgpuRenderer` or that there is data from an old \ + version of the app inside of the persistent storage file.\n\n", + type_name, + type_name, + ); + continue; + }; + + data.push(map.next_value_seed(SchemaDeserializer(schema))?); + } + + Ok(data) + } +} diff --git a/framework_crates/bones_wgpu_renderer/src/ui.rs b/framework_crates/bones_wgpu_renderer/src/ui.rs index 6895635a2e..b42e98ac9c 100644 --- a/framework_crates/bones_wgpu_renderer/src/ui.rs +++ b/framework_crates/bones_wgpu_renderer/src/ui.rs @@ -5,6 +5,8 @@ use egui_winit::State; use winit::event::WindowEvent; use winit::window::Window; +use crate::bones; + pub struct EguiRenderer { state: State, renderer: Renderer, @@ -115,4 +117,45 @@ impl EguiRenderer { self.frame_started = false; } -} \ No newline at end of file +} + +pub fn default_load_progress(asset_server: &bones::AssetServer, ctx: &egui::Context) { + let errored = asset_server.load_progress.errored(); + + egui::CentralPanel::default().show(ctx, |ui| { + let height = ui.available_height(); + let ctx = ui.ctx().clone(); + + let space_size = 0.03; + let spinner_size = 0.07; + let text_size = 0.034; + ui.vertical_centered(|ui| { + ui.add_space(height * 0.3); + + if errored > 0 { + ui.label( + egui::RichText::new("⚠") + .color(egui::Color32::RED) + .size(height * spinner_size), + ); + ui.add_space(height * space_size); + ui.label( + egui::RichText::new(format!( + "Error loading {errored} asset{}.", + if errored > 1 { "s" } else { "" } + )) + .color(egui::Color32::RED) + .size(height * text_size * 0.75), + ); + } else { + ui.add(egui::Spinner::new().size(height * spinner_size)); + ui.add_space(height * space_size); + ui.label(egui::RichText::new("Loading").size(height * text_size)); + } + }); + + ctx.data_mut(|d| { + d.insert_temp(ui.id(), (spinner_size, space_size, text_size)); + }) + }); +} From cc74d77823da010877ba314673af1f5a7e9dc6e0 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Mon, 12 May 2025 03:48:40 -0300 Subject: [PATCH 10/16] Cargo clippy and fmt --- .../bones_wgpu_renderer/src/lib.rs | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 40dd6a4357..940d13192f 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -227,7 +227,7 @@ impl BonesWgpuRenderer { self.game.insert_shared_resource(storage); self.game .insert_shared_resource(bones::EguiTextures::default()); - self.game.insert_shared_resource(bones::ExitBones(false)); + self.game.insert_shared_resource(bones::ExitBones(false)); // Insert empty inputs that will be updated by the `insert_bones_input` system later. self.game.init_shared_resource::(); @@ -353,31 +353,29 @@ fn load_egui_textures(game: &mut bones::Game) { } let asset = asset_server.store.assets.get_mut(entry.value()).unwrap(); - if let Ok(image) = asset.data.try_cast_ref::() { - if let bones::Image::Data(data) = image { - let rgba: RgbaImage = data.to_rgba8(); - let (w, h) = (rgba.width() as usize, rgba.height() as usize); - let raw = rgba.into_raw(); - - let handle = ctx.load_texture( - &format!("Texture {:?}", entry.key()), - egui::ColorImage::from_rgba_unmultiplied([w, h], &raw), - egui::TextureOptions { - magnification: if pixel_art.0 { - egui::TextureFilter::Nearest - } else { - egui::TextureFilter::Linear - }, - minification: if pixel_art.0 { - egui::TextureFilter::Nearest - } else { - egui::TextureFilter::Linear - }, - ..Default::default() + if let Ok(bones::Image::Data(data)) = asset.data.try_cast_ref::() { + let rgba: RgbaImage = data.to_rgba8(); + let (w, h) = (rgba.width() as usize, rgba.height() as usize); + let raw = rgba.into_raw(); + + let handle = ctx.load_texture( + format!("Texture {:?}", entry.key()), + egui::ColorImage::from_rgba_unmultiplied([w, h], &raw), + egui::TextureOptions { + magnification: if pixel_art.0 { + egui::TextureFilter::Nearest + } else { + egui::TextureFilter::Linear }, - ); - egui_textures.insert(id, handle); - } + minification: if pixel_art.0 { + egui::TextureFilter::Nearest + } else { + egui::TextureFilter::Linear + }, + ..Default::default() + }, + ); + egui_textures.insert(id, handle); } } } From ec9a6e94fc225a31358fdecb896ed2e550d5f5ea Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Mon, 12 May 2025 14:42:08 -0300 Subject: [PATCH 11/16] Fixed background color and session specific rendering --- demos/hello_world_wgpu/src/main.rs | 2 +- .../bones_framework/src/render/camera.rs | 8 ++-- .../bones_wgpu_renderer/src/lib.rs | 47 ++++++++++++------- .../bones_wgpu_renderer/src/sprite.rs | 36 +++++++++++--- 4 files changed, 66 insertions(+), 27 deletions(-) diff --git a/demos/hello_world_wgpu/src/main.rs b/demos/hello_world_wgpu/src/main.rs index fcb4bc7bae..381dbd4c78 100644 --- a/demos/hello_world_wgpu/src/main.rs +++ b/demos/hello_world_wgpu/src/main.rs @@ -78,7 +78,7 @@ fn sprite_demo_startup( size: UVec2::new(100, 100), ..Default::default() }), - background_color: Color::ORANGE, + background_color: Set(Color::ORANGE), ..Default::default() }; let camera_ent = entities.create(); diff --git a/framework_crates/bones_framework/src/render/camera.rs b/framework_crates/bones_framework/src/render/camera.rs index dc0b72990e..547ac24616 100644 --- a/framework_crates/bones_framework/src/render/camera.rs +++ b/framework_crates/bones_framework/src/render/camera.rs @@ -30,7 +30,9 @@ pub struct Camera { /// Cameras with a higher priority will be rendered on top of cameras with a lower priority. pub priority: i32, /// The color to clear the screen to before rendering. - pub background_color: Color, + /// If None and the camera is set to draw the background, we will try to use the global ClearColor, + /// If not available, we will use Black + pub background_color: Maybe, /// Whether or not the camera should draw the background color. pub draw_background_color: bool, } @@ -74,13 +76,13 @@ impl Default for Camera { viewport: Unset, priority: 0, size: default(), - background_color: Color::BLACK, + background_color: Maybe::Unset, draw_background_color: true, } } } -/// Resource for controlling the clear color. +/// Resource for controlling the global clear color. #[derive(Deref, DerefMut, Clone, Copy, HasSchema, Default)] pub struct ClearColor(pub Color); diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 940d13192f..34917dedec 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -68,9 +68,9 @@ impl WgpuQueue { #[repr(C)] #[schema(opaque)] #[schema(no_default)] -struct TextureSender(Sender<(Arc, bones::Entity, Arc)>); +struct TextureSender(Sender<(Arc, bones::Entity, Arc, bones::Ustr)>); -type TextureReceiver = Receiver<(Arc, bones::Entity, Arc)>; +type TextureReceiver = Receiver<(Arc, bones::Entity, Arc, bones::Ustr)>; // Indicates that we already loaded the sprite texture // and sent it to the wgpu thread @@ -239,9 +239,9 @@ impl BonesWgpuRenderer { .init_shared_resource::(); //Insert needed systems - self.game.systems.add_before_system(load_sprite); - self.game.systems.add_before_system(load_atlas_sprite); - self.game.systems.add_before_system(load_tile_sprite); + self.game.systems.add_after_system(load_sprite); + self.game.systems.add_after_system(load_atlas_sprite); + self.game.systems.add_after_system(load_tile_sprite); self.game.systems.add_before_system(update_atlas_uniforms); self.game.systems.add_before_system(update_sprite_uniforms); self.game.systems.add_before_system(update_tiles_uniforms); @@ -437,7 +437,7 @@ impl ApplicationHandler for App { let mut wheel_events = Vec::new(); let mut button_events = Vec::new(); - for (texture, entity, atlas_sprite_buffer) in self.receiver.try_iter() { + for (texture, entity, atlas_sprite_buffer, session_name) in self.receiver.try_iter() { let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.texture_bind_group_layout, entries: &[ @@ -457,9 +457,12 @@ impl ApplicationHandler for App { label: Some("diffuse_bind_group"), }); - state.sprites.push((bind_group, entity)); + if let Some(v) = state.sprites.get_mut(&session_name) { + v.push((bind_group, entity)); + } else { + state.sprites.insert(session_name.clone(), vec![(bind_group, entity)]); + } } - // Egui input handling state .egui_renderer @@ -577,7 +580,7 @@ struct State { surface: wgpu::Surface<'static>, surface_format: wgpu::TextureFormat, render_pipeline: wgpu::RenderPipeline, - sprites: Vec<(wgpu::BindGroup, bones::Entity)>, + sprites: bones::UstrMap>, egui_renderer: EguiRenderer, egui_scale_factor: f32, } @@ -699,7 +702,7 @@ impl State { surface, surface_format, render_pipeline, - sprites: vec![], + sprites: bones::UstrMap::default(), egui_renderer, egui_scale_factor: 1.0, } @@ -759,7 +762,11 @@ impl State { }); let num_indices = INDICES.len() as u32; - for (_, session) in game.sessions.iter() { + for (session_name, session) in game.sessions.iter() { + if !session.visible { + continue; + } + //Get cameras and sort them let cameras = session.world.component::(); let transforms = session.world.component::(); @@ -860,6 +867,14 @@ impl State { } if camera.draw_background_color { + let clear_color = session.world.get_resource::(); + + let background_color = match (camera.background_color.option(), clear_color) { + (Some(color), _) => color, + (None, Some(color)) => color.0, + (None, None) => bones::Color::BLACK, + }; + //TODO Stop creating buffers every frame let vertex_buffer = self.device @@ -869,11 +884,8 @@ impl State { usage: wgpu::BufferUsages::VERTEX, }); - let rgba = RgbaImage::from_pixel( - 1, - 1, - image::Rgba(camera.background_color.as_rgba_u8()), - ); + let rgba = + RgbaImage::from_pixel(1, 1, image::Rgba(background_color.as_rgba_u8())); let img = DynamicImage::ImageRgba8(rgba); let texture = Texture::from_image( &self.device, @@ -920,7 +932,8 @@ impl State { } // Render each sprite with its own transform. - for (bind_group, entity) in &self.sprites { + for (bind_group, entity) in &self.sprites[session_name] { + // Get the entity transform from the ECS. let Some(transform) = session .world diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs index 53f1f60f01..86fc579915 100644 --- a/framework_crates/bones_wgpu_renderer/src/sprite.rs +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -95,7 +95,11 @@ pub fn load_sprite(game: &mut bones::Game) { let texture_sender = game.shared_resource_cell::().unwrap(); let pixel_art = game.shared_resource_cell::().unwrap(); - for (_, session) in game.sessions.iter_mut() { + for (session_name, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + let entities = session.world.resource::(); let sprites = session.world.component::(); let mut buffers = session.world.component_mut::(); @@ -151,7 +155,7 @@ pub fn load_sprite(game: &mut bones::Game) { .borrow() .unwrap() .0 - .send((texture, entity, atlas_sprite_buffer)) + .send((texture, entity, atlas_sprite_buffer, *session_name)) .unwrap(); texture_loaded.insert(entity, TextureLoaded); } else { @@ -168,7 +172,11 @@ pub fn load_atlas_sprite(game: &mut bones::Game) { let texture_sender = game.shared_resource_cell::().unwrap(); let pixel_art = game.shared_resource_cell::().unwrap(); - for (_, session) in game.sessions.iter_mut() { + for (session_name, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + let entities = session.world.resource::(); let atlas_sprites = session.world.component::(); let mut buffers = session.world.component_mut::(); @@ -220,7 +228,7 @@ pub fn load_atlas_sprite(game: &mut bones::Game) { .borrow() .unwrap() .0 - .send((texture, entity, atlas_sprite_buffer)) + .send((texture, entity, atlas_sprite_buffer, *session_name)) .unwrap(); texture_loaded.insert(entity, TextureLoaded); } else { @@ -237,7 +245,11 @@ pub fn load_tile_sprite(game: &mut bones::Game) { let texture_sender = game.shared_resource_cell::().unwrap(); let pixel_art = game.shared_resource_cell::().unwrap(); - for (_, session) in game.sessions.iter_mut() { + for (session_name, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + let entities = session.world.resource::(); let tile_layers = session.world.component::(); let tiles = session.world.component::(); @@ -296,7 +308,7 @@ pub fn load_tile_sprite(game: &mut bones::Game) { .borrow() .unwrap() .0 - .send((texture.clone(), entity, atlas_sprite_buffer)) + .send((texture.clone(), entity, atlas_sprite_buffer, *session_name)) .unwrap(); texture_loaded.insert(entity, TextureLoaded); } @@ -313,6 +325,10 @@ pub fn update_atlas_uniforms(game: &mut bones::Game) { let queue = game.shared_resource_cell::().unwrap(); for (_, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + let entities = session.world.resource::(); let atlases = session.world.component::(); let mut buffers = session.world.component_mut::(); @@ -336,6 +352,10 @@ pub fn update_sprite_uniforms(game: &mut bones::Game) { let queue = game.shared_resource_cell::().unwrap(); for (_, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + let entities = session.world.resource::(); let sprites = session.world.component::(); let mut buffers = session.world.component_mut::(); @@ -357,6 +377,10 @@ pub fn update_tiles_uniforms(game: &mut bones::Game) { let queue = game.shared_resource_cell::().unwrap(); for (_, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + let entities = session.world.resource::(); let tile_layers = session.world.component::(); let tiles = session.world.component::(); From cd2dd5bfed3d266cfac082fd6c7a1686757d86ac Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Mon, 12 May 2025 16:58:01 -0300 Subject: [PATCH 12/16] Fixed multiple hash entries --- .../bones_wgpu_renderer/src/lib.rs | 17 ++++++++++++-- .../bones_wgpu_renderer/src/sprite.rs | 23 +++++++++++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 34917dedec..4ac53981e3 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -460,7 +460,9 @@ impl ApplicationHandler for App { if let Some(v) = state.sprites.get_mut(&session_name) { v.push((bind_group, entity)); } else { - state.sprites.insert(session_name.clone(), vec![(bind_group, entity)]); + state + .sprites + .insert(session_name.clone(), vec![(bind_group, entity)]); } } // Egui input handling @@ -762,7 +764,9 @@ impl State { }); let num_indices = INDICES.len() as u32; + let mut dont_delete = vec![]; for (session_name, session) in game.sessions.iter() { + dont_delete.push(session_name.clone()); if !session.visible { continue; } @@ -933,7 +937,6 @@ impl State { // Render each sprite with its own transform. for (bind_group, entity) in &self.sprites[session_name] { - // Get the entity transform from the ECS. let Some(transform) = session .world @@ -1007,6 +1010,16 @@ impl State { ); } + let keys_to_remove: Vec<_> = self + .sprites + .keys() + .filter(|session_name| !dont_delete.contains(session_name)) + .cloned() + .collect(); + for session_name in keys_to_remove { + self.sprites.remove(&session_name); + } + // Submit the command queue. self.queue.submit([encoder.finish()]); self.window.pre_present_notify(); diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs index 86fc579915..2a68ba305b 100644 --- a/framework_crates/bones_wgpu_renderer/src/sprite.rs +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -114,8 +114,6 @@ pub fn load_sprite(game: &mut bones::Game) { unreachable!(); }; - println!("Loading sprite: {:?}", sprite); - //Load and send texture let assets = assets.borrow().unwrap(); let image = assets.get(sprite.image); @@ -260,8 +258,8 @@ pub fn load_tile_sprite(game: &mut bones::Game) { not_loaded.bit_not(); not_loaded.bit_and(tile_layers.bitset()); - for entity in entities.iter_with_bitset(¬_loaded) { - let Some(tile_layer) = tile_layers.get(entity) else { + for layer_ent in entities.iter_with_bitset(¬_loaded) { + let Some(tile_layer) = tile_layers.get(layer_ent) else { unreachable!(); }; @@ -281,11 +279,11 @@ pub fn load_tile_sprite(game: &mut bones::Game) { .unwrap(), ); - for tile in &tile_layer.tiles { - let Some(tile) = tile else { + for (tile_pos_idx, tile) in tile_layer.tiles.iter().enumerate() { + let Some(tile_ent) = tile else { continue; }; - let Some(tile) = tiles.get(*tile) else { + let Some(tile) = tiles.get(*tile_ent) else { panic!("Couldn't find tile entity!"); }; // create and send the atlas sprite uniform along with the texture and entity @@ -302,19 +300,24 @@ pub fn load_tile_sprite(game: &mut bones::Game) { )); //Add buffer to bones so we can update it - buffers.insert(entity, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); + buffers.insert(*tile_ent, AtlasSpriteBuffer(atlas_sprite_buffer.clone())); texture_sender .borrow() .unwrap() .0 - .send((texture.clone(), entity, atlas_sprite_buffer, *session_name)) + .send(( + texture.clone(), + *tile_ent, + atlas_sprite_buffer, + *session_name, + )) .unwrap(); - texture_loaded.insert(entity, TextureLoaded); } } else { unreachable!() }; + texture_loaded.insert(layer_ent, TextureLoaded); } } } From 5b54d732f49305a4457301b999bd1dc43960aad2 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Wed, 21 May 2025 16:26:21 -0300 Subject: [PATCH 13/16] Added some line code --- Cargo.lock | 11 ++ .../bones_framework/src/render/camera.rs | 9 +- .../bones_wgpu_renderer/Cargo.toml | 1 + .../bones_wgpu_renderer/src/lib.rs | 21 ++-- .../bones_wgpu_renderer/src/line.rs | 100 ++++++++++++++++++ .../bones_wgpu_renderer/src/sprite.rs | 33 ++++++ 6 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 framework_crates/bones_wgpu_renderer/src/line.rs diff --git a/Cargo.lock b/Cargo.lock index 5c1a675cb1..4ed2e98b90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1698,6 +1698,7 @@ dependencies = [ "env_logger", "image 0.24.9", "log", + "lyon", "pollster", "serde", "serde_yaml", @@ -5020,6 +5021,16 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + [[package]] name = "lyon_algorithms" version = "1.0.4" diff --git a/framework_crates/bones_framework/src/render/camera.rs b/framework_crates/bones_framework/src/render/camera.rs index 547ac24616..444870007d 100644 --- a/framework_crates/bones_framework/src/render/camera.rs +++ b/framework_crates/bones_framework/src/render/camera.rs @@ -97,7 +97,14 @@ pub fn spawn_default_camera( ) -> Entity { let ent = entities.create(); cameras.insert(ent, default()); - transforms.insert(ent, Transform::from_translation(Vec3::new(0., 0., 1000.))); + transforms.insert( + ent, + Transform { + translation: Vec3::new(0.0, 0.0, 1000.0), + scale: Vec3::new(40., 40., 1.0), + ..default() + }, + ); ent } diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml index 3e186f0b2b..ccfde93c72 100644 --- a/framework_crates/bones_wgpu_renderer/Cargo.toml +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -23,6 +23,7 @@ bevy_tasks = "0.11.3" image = "0.24.9" crossbeam-channel = "0.5.14" bytemuck = "1.22" +lyon = "1.0.1" egui = "0.30" egui-wgpu = "0.30" diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 4ac53981e3..4063fd1613 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -26,6 +26,7 @@ use bones_framework::{ use egui_wgpu::ScreenDescriptor; mod convert; +mod line; mod sprite; mod storage; mod texture; @@ -936,7 +937,11 @@ impl State { } // Render each sprite with its own transform. - for (bind_group, entity) in &self.sprites[session_name] { + let Some(binds) = self.sprites.get(session_name) else { + continue; + }; + + for (bind_group, entity) in binds { // Get the entity transform from the ECS. let Some(transform) = session .world @@ -948,20 +953,24 @@ impl State { }; let (w, h) = (self.size.width as f32, self.size.height as f32); - let translation_scale = Vec3::new(1. / w, 1. / h, 1.); camera_transform.translation.z = 0.; + let camera_scale = + Vec3::new(1.0 / scale_ratio.x_axis.x, 1.0 / scale_ratio.y_axis.y, 1.0); + let translation_scale = + Vec3::new((2.0 / w) * camera_scale.x, (2.0 / h) * camera_scale.y, 1.0); + let camera_mat4 = camera_transform.to_matrix(translation_scale).inverse(); - // Build the final matrix - let mat4 = scale_ratio * camera_mat4 * transform.to_matrix(translation_scale); + let quad_scale = translation_scale * camera_transform.scale; + let mat4 = scale_ratio * camera_mat4 * transform.to_matrix(quad_scale); // Compute transformed vertices on the CPU. - let transformed_vertices: Vec = VERTICES + let transformed_vertices: Vec = VERTICES_FULL .iter() .map(|v: &Vertex| Vertex { position: transform_vertex( - v.position.into(), + Vec3::from(v.position), mat4, Vec3::ZERO, // Use Vec3::ZERO as the pivot point for transformations ) diff --git a/framework_crates/bones_wgpu_renderer/src/line.rs b/framework_crates/bones_wgpu_renderer/src/line.rs new file mode 100644 index 0000000000..acbeb60fb7 --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/line.rs @@ -0,0 +1,100 @@ +use lyon::math::Point; +use lyon::path::Path; +use lyon::tessellation::{ + StrokeOptions, StrokeTessellator, BuffersBuilder, FillVertex, StrokeVertex, VertexBuffers, +}; +use bones_framework::glam::Vec2; + +use crate::bones::Path2d; + +#[repr(C)] +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct Vertex { + position: [f32; 2], + color: [f32; 4], +} + +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + use std::mem; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x4, + }, + ], + } + } +} + +// Helper function to process line breaks +fn process_segments(path: &Path2d) -> Vec<&[Vec2]> { + let mut segments = Vec::new(); + let mut line_breaks = path.line_breaks.clone(); + line_breaks.sort_unstable(); + line_breaks.dedup(); + + let mut start = 0; + for &break_point in &line_breaks { + let end = break_point; + if start <= end && end < path.points.len() { + let segment = &path.points[start..=end]; + if segment.len() >= 2 { + segments.push(segment); + } + } + start = end + 1; + } + + if start < path.points.len() { + let segment = &path.points[start..]; + if segment.len() >= 2 { + segments.push(segment); + } + } + + segments +} + +// Tessellate path into vertex/index buffers +fn tessellate_path(path: &Path2d) -> VertexBuffers { + let segments = process_segments(path); + let mut lyon_path_builder = Path::builder(); + + for segment in segments { + let mut points = segment.iter(); + if let Some(first) = points.next() { + lyon_path_builder.begin(Point::new(first.x, first.y)); + for point in points { + lyon_path_builder.line_to(Point::new(point.x, point.y)); + } + lyon_path_builder.end(false); + } + } + + let lyon_path = lyon_path_builder.build(); + let options = StrokeOptions::default().with_line_width(path.thickness); + let mut geometry = VertexBuffers::new(); + + StrokeTessellator::new() + .tessellate_path( + &lyon_path, + &options, + &mut BuffersBuilder::new(&mut geometry, |vertex: StrokeVertex| Vertex { + position: vertex.position().to_array(), + color: path.color.as_rgba_f32(), + }), + ) + .expect("Path tessellation failed"); + + geometry +} diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs index 2a68ba305b..60e5459e74 100644 --- a/framework_crates/bones_wgpu_renderer/src/sprite.rs +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -286,6 +286,39 @@ pub fn load_tile_sprite(game: &mut bones::Game) { let Some(tile) = tiles.get(*tile_ent) else { panic!("Couldn't find tile entity!"); }; + let mut transforms = session.world.component_mut::(); + + let transform = if let Some(t) = transforms.get_mut(*tile_ent) { + t + } else { + transforms.insert(*tile_ent, bones::Transform::default()); + transforms.get_mut(*tile_ent).unwrap() + }; + + let tile_pos = tile_layer.pos(tile_pos_idx as u32); + let tile_offset = tile_pos.as_vec2() * tile_layer.tile_size; + + /*let sprite_idx = tile.idx; + let y = sprite_idx / atlas.columns; + let x = sprite_idx - (y * atlas.columns); + let cell = Vec2::new(x as f32, y as f32); + let current_padding = atlas.padding + * Vec2::new(if x > 0 { 1.0 } else { 0.0 }, if y > 0 { 1.0 } else { 0.0 }); + let min = (atlas.tile_size + current_padding) * cell + atlas.offset; + let rect = Rect { + min, + max: min + atlas.tile_size, + };*/ + + transform.translation += tile_offset.extend(0.0); + // Scale up slightly to avoid bleeding between tiles. + // TODO: Improve tile rendering + // Currently we do a small hack here, scaling up the tiles a little bit, to prevent + // visible gaps between tiles. This solution isn't perfect and we probably need to + // create a proper tile renderer. That can render multiple tiles on one quad instead + // of using a separate quad for each tile. + transform.scale += Vec3::new(0.01, 0.01, 0.0); + // create and send the atlas sprite uniform along with the texture and entity let uniform = AtlasSpriteUniform::from_tile(tile, &assets.get(tile_layer.atlas)); From 0122b8b10593d4c407634c728002dd53b2a8052d Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Sun, 27 Jul 2025 20:56:18 -0300 Subject: [PATCH 14/16] test --- Cargo.lock | 6 +- demos/features_wgpu/atlas_0.png | Bin 0 -> 456077 bytes demos/hello_world_wgpu/src/main.rs | 2 +- .../bones_framework/src/render/color.rs | 12 + .../bones_framework/src/render/transform.rs | 15 + .../bones_wgpu_renderer/Cargo.toml | 3 + .../bones_wgpu_renderer/src/atlas_pool.rs | 190 ++++ .../bones_wgpu_renderer/src/atlas_sprite.wgsl | 215 +++-- .../src/dynamic_storage.rs | 86 ++ .../bones_wgpu_renderer/src/lib.rs | 829 +++++++++--------- .../bones_wgpu_renderer/src/line.rs | 4 +- .../bones_wgpu_renderer/src/sprite.rs | 706 ++++++++++++++- .../bones_wgpu_renderer/src/test.rs | 250 ++++++ .../bones_wgpu_renderer/src/texture.rs | 4 +- .../bones_wgpu_renderer/src/texture_file.rs | 82 ++ .../bones_wgpu_renderer/src/uniforms.rs | 64 ++ 16 files changed, 1958 insertions(+), 510 deletions(-) create mode 100644 demos/features_wgpu/atlas_0.png create mode 100644 framework_crates/bones_wgpu_renderer/src/atlas_pool.rs create mode 100644 framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs create mode 100644 framework_crates/bones_wgpu_renderer/src/test.rs create mode 100644 framework_crates/bones_wgpu_renderer/src/texture_file.rs create mode 100644 framework_crates/bones_wgpu_renderer/src/uniforms.rs diff --git a/Cargo.lock b/Cargo.lock index 4ed2e98b90..4cac37bb87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -284,9 +284,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "append-only-vec" @@ -1686,6 +1686,7 @@ dependencies = [ name = "bones_wgpu_renderer" version = "0.4.0" dependencies = [ + "anyhow", "bevy_tasks 0.11.3", "bones_framework", "bones_schema", @@ -1696,6 +1697,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "env_logger", + "guillotiere", "image 0.24.9", "log", "lyon", diff --git a/demos/features_wgpu/atlas_0.png b/demos/features_wgpu/atlas_0.png new file mode 100644 index 0000000000000000000000000000000000000000..5470c32fc6ba25759ce679ada05314d5b2aea424 GIT binary patch literal 456077 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

^d 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 zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-LdcfLw(IB|I3$G&42atUmeH)-M)O~ LXAj-BcI*EDn3D>i literal 0 HcmV?d00001 diff --git a/demos/hello_world_wgpu/src/main.rs b/demos/hello_world_wgpu/src/main.rs index 381dbd4c78..6554e1b1e6 100644 --- a/demos/hello_world_wgpu/src/main.rs +++ b/demos/hello_world_wgpu/src/main.rs @@ -78,7 +78,7 @@ fn sprite_demo_startup( size: UVec2::new(100, 100), ..Default::default() }), - background_color: Set(Color::ORANGE), + //background_color: Set(Color::ORANGE), ..Default::default() }; let camera_ent = entities.create(); diff --git a/framework_crates/bones_framework/src/render/color.rs b/framework_crates/bones_framework/src/render/color.rs index 5c5fe5d7ff..5be187c2d6 100644 --- a/framework_crates/bones_framework/src/render/color.rs +++ b/framework_crates/bones_framework/src/render/color.rs @@ -301,6 +301,18 @@ impl Color { } } + /// Converts a `Color` to a `[f64; 4]` from sRGB colorspace + pub fn as_rgba_f64(self: Color) -> [f64; 4] { + match self { + Color::Rgba { + red, + green, + blue, + alpha, + } => [red as f64, green as f64, blue as f64, alpha as f64], + } + } + /// Converts a `Color` to a `[u8; 4]` from sRGB colorspace pub fn as_rgba_u8(self: Color) -> [u8; 4] { match self { diff --git a/framework_crates/bones_framework/src/render/transform.rs b/framework_crates/bones_framework/src/render/transform.rs index 38549b8da6..bb15226796 100644 --- a/framework_crates/bones_framework/src/render/transform.rs +++ b/framework_crates/bones_framework/src/render/transform.rs @@ -49,6 +49,16 @@ impl Transform { Self { scale, ..default() } } + pub fn from_matrix(matrix: Mat4) -> Self { + let (scale, rotation, translation) = matrix.to_scale_rotation_translation(); + + Self { + translation, + rotation, + scale, + } + } + /// Converts the transform to a 4x4 matrix for rendering pub fn to_matrix(&self, translation_scale: Vec3) -> Mat4 { let angle = self.rotation.z.rem_euclid(2.0 * PI); @@ -58,6 +68,11 @@ impl Transform { Mat4::from_translation(self.translation * translation_scale) * scale_rotation } + /// Converts to a matrix without translation scale + pub fn to_matrix_none(&self) -> Mat4 { + Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) + } + /// Converts the transform to a 4x4 matrix for rendering, /// scaling off from the given pivot (for example, the center of the screen). pub fn to_matrix_with_pivot(&self, pivot: Vec3) -> Mat4 { diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml index ccfde93c72..f8cbadbe20 100644 --- a/framework_crates/bones_wgpu_renderer/Cargo.toml +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -25,6 +25,9 @@ crossbeam-channel = "0.5.14" bytemuck = "1.22" lyon = "1.0.1" +guillotiere = "0.6.2" +anyhow = "1.0.98" + egui = "0.30" egui-wgpu = "0.30" egui-winit = "0.30" diff --git a/framework_crates/bones_wgpu_renderer/src/atlas_pool.rs b/framework_crates/bones_wgpu_renderer/src/atlas_pool.rs new file mode 100644 index 0000000000..cb0256f848 --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/atlas_pool.rs @@ -0,0 +1,190 @@ +use bones_framework::prelude::Entity; +use crossbeam_channel::bounded; +use guillotiere::{size2, AllocId, Allocation, AtlasAllocator}; +use std::sync::Arc; + +use crate::sprite::AtlasPoolHandle; + +/// Code for the AtlasPool and the TextureAtlas. +/// A pool of texture atlases, each capable of holding multiple sprites. + +pub struct AtlasPool { + device: Arc, + layout: Arc, + pub atlases: Vec, + next_id: usize, + max_atlases: usize, + pub atlas_size: (u32, u32), + pixel_art: bool, + //Used to remove deleted textures + pub sender: crossbeam_channel::Sender<(Entity, usize, Allocation)>, + pub receiver: crossbeam_channel::Receiver<(Entity, usize, Allocation)>, +} + +impl AtlasPool { + pub fn new( + device: &Arc, + layout: &Arc, + atlas_size: (u32, u32), + max_atlases: usize, + pixel_art: bool, + ) -> Self { + // I think 100 is a good number? + let (sender, receiver) = bounded(1000); + + AtlasPool { + device: device.clone(), + layout: layout.clone(), + atlases: Vec::new(), + next_id: 0, + max_atlases, + atlas_size, + pixel_art, + sender, + receiver, + } + } + + /// Allocate a rectangle of `size` in one of the atlases. + /// If none has room and we’re under `max_atlases`, we create a new atlas. + /// If at capacity and no atlas has space, returns an error. + pub fn allocate(&mut self, size: (i32, i32)) -> Result<(usize, Allocation), String> { + // 1) Try existing atlases + if let Some((idx, alloc)) = self + .atlases + .iter_mut() + .enumerate() + .filter_map(|(i, a)| a.allocate(size).map(|alloc| (i, alloc))) + .next() + { + return Ok((self.atlases[idx].id, alloc)); + } + + // 2) Need a new atlas? + if self.atlases.len() < self.max_atlases { + let id = self.next_id; + self.next_id += 1; + + let mut atlas = TextureAtlas::new( + &self.device, + &self.layout, + id, + self.atlas_size, + self.pixel_art, + ); + let alloc = atlas + .allocate(size) + .expect("Newly-created atlas must fit the requested sprite size"); + self.atlases.push(atlas); + return Ok((id, alloc)); + } + + // 3) All atlases full + Err(format!( + "AtlasPool: all {} atlases are full, cannot allocate size {:?}", + self.max_atlases, size + )) + } + + /// Frees the given allocation back into its atlas, making space reusable. + pub fn deallocate(&mut self, atlas_id: usize, alloc: Allocation) -> bool { + if let Some(atlas) = self.atlases.iter_mut().find(|a| a.id == atlas_id) { + atlas.deallocate(alloc.id); + true + } else { + false + } + } +} + +pub struct TextureAtlas { + pub id: usize, + pub allocator: AtlasAllocator, // guillotiere packing + pub texture: wgpu::Texture, // the GPU texture + pub view: wgpu::TextureView, + pub bind_group: wgpu::BindGroup, // for sampling it in shaders + pub allocated: usize, +} + +impl TextureAtlas { + pub fn new( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + id: usize, + size: (u32, u32), + pixel_art: bool, + ) -> Self { + // initialize allocator + let allocator = AtlasAllocator::new(size2(size.0 as i32, size.1 as i32)); + + // create GPU texture + let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some(&format!("Atlas #{}", id)), + size: wgpu::Extent3d { + width: size.0, + height: size.1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }); + + let view = texture.create_view(&Default::default()); + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: if pixel_art { + wgpu::FilterMode::Nearest + } else { + wgpu::FilterMode::Linear + }, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + // create bind group + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + label: Some(&format!("Atlas BG #{}", id)), + }); + + TextureAtlas { + id, + allocator, + texture, + view, + bind_group, + allocated: 0, + } + } + + /// Attempt to allocate; returns None if sprite doesn't fit + pub fn allocate(&mut self, size: (i32, i32)) -> Option { + self.allocated += 1; + self.allocator.allocate(size.into()) + } + + /// Free a single rectangle back into this atlas’s free-list + pub fn deallocate(&mut self, alloc_id: AllocId) { + self.allocated -= 1; + self.allocator.deallocate(alloc_id); + } +} diff --git a/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl b/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl index ab7b9d3a8b..970d29eb1e 100644 --- a/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl +++ b/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl @@ -1,93 +1,170 @@ +// Must match the Rust layout struct AtlasSpriteUniform { - // Atlas parameters - tile_size: vec2, - image_size: vec2, - padding: vec2, - offset: vec2, - columns: u32, - index: u32, - - // State flags - use_atlas: u32, - flip_x: u32, - flip_y: u32, - - // Padding - _pad0: u32, - - color_tint: vec4, + entity_type: u32, + camera_index: u32, + _pad0: u32, + _pad1: u32, + transform: mat4x4, + color_tint: vec4, + + flip_x: u32, + flip_y: u32, + uv_min: vec2, + uv_max: vec2, + + tile_size: vec2, + image_size: vec2, + padding: vec2, + offset: vec2, + + columns: u32, + index: u32, }; -@group(0) @binding(0) var spriteUniform: AtlasSpriteUniform; -@group(0) @binding(1) var diffuseTex: texture_2d; -@group(0) @binding(2) var texSampler: sampler; +// Camera uniform, one per render pass +struct CameraUniform { + transform: mat4x4, + screen_size: vec2, + _pad0: u32, + _pad1: u32, // Padding to ensure 16-byte alignment +} + +// Bindings +@group(0) @binding(0) var diffuseTex: texture_2d; +@group(0) @binding(1) var texSampler: sampler; + +// All per-instance data lives here +@group(1) @binding(0) +var sprite_data: array; +@group(2) @binding(0) +var cameras: array; + +// Quad vertex input struct VertexInput { - @location(0) position: vec2, + @location(0) position: vec3, @location(1) uv: vec2, }; +// Data we pass to the fragment stage struct VertexOutput { - @builtin(position) clipPos: vec4, - @location(0) uv: vec2, + @builtin(position) clipPos: vec4, + @location(0) atlas_uv: vec2, + @location(1) inst_index: u32, }; +// Vertex Shader @vertex -fn vs_main(in: VertexInput) -> VertexOutput { - return VertexOutput( - vec4(in.position, 0.0, 1.0), - in.uv +fn vs_main( + vert: VertexInput, + @builtin(instance_index) idx: u32 +) -> VertexOutput { + let inst = sprite_data[idx]; + var out: VertexOutput; + + // 1) compute quad size in *pixels* from your atlas‐tile: + // inst.tile_size is already [width_px, height_px] + let quad_px = vec3( + vert.position.x * inst.tile_size.x, + vert.position.y * inst.tile_size.y, + vert.position.z ); -} - -fn compute_atlas_uv(base_uv: vec2) -> vec2 { - // Precompute normalized dimensions - let tile_scale = spriteUniform.tile_size / spriteUniform.image_size; - let padding_scale = spriteUniform.padding / spriteUniform.image_size; - let base_offset = spriteUniform.offset / spriteUniform.image_size; - // Calculate grid position - let grid_pos = vec2( - spriteUniform.index % spriteUniform.columns, - spriteUniform.index / spriteUniform.columns + // 2) convert pixel‐coordinates → NDC ([-1,1]) per axis + let cam = cameras[inst.camera_index]; + let px_to_ndc = vec2( + 2.0 / cam.screen_size.x, + 2.0 / cam.screen_size.y ); - // Calculate tile origin in UV space - let tile_step = tile_scale + padding_scale; - let origin = base_offset + vec2(grid_pos) * tile_step; + // 3) build your instance matrix so that + // translation is in *pixels* → NDC, + // but rotation & scale from inst.transform stay in world‐units + let inst_mat = scale_translation( + inst.transform, + vec3(px_to_ndc.x, px_to_ndc.y, 1.0) + ); - // Calculate tile center and aspect ratio correction - let tile_center = origin + tile_scale * 0.5; - let aspect_ratio = normalize(1.0 / spriteUniform.tile_size); + // 4) finally, emit clip‐space position: + out.clipPos = cam.transform * inst_mat * vec4(quad_px, 1.0); - // Base UV calculation with aspect correction - var uv = tile_center + (base_uv - 0.5) * tile_scale * aspect_ratio; + // …then do your UV flip/remap, color_tint etc… + var base_uv = vert.uv; + if inst.flip_x == 1u { base_uv.x = 1.0 - base_uv.x; } + if inst.flip_y == 1u { base_uv.y = 1.0 - base_uv.y; } + out.atlas_uv = mix(inst.uv_min, inst.uv_max, base_uv); + out.inst_index = idx; + return out; +} - // Apply flipping transformations - if (spriteUniform.flip_x == 1u) { - uv.x = tile_center.x + (0.5 - base_uv.x) * tile_scale.x * aspect_ratio.x; - } - if (spriteUniform.flip_y == 1u) { - uv.y = tile_center.y + (0.5 - base_uv.y) * tile_scale.y * aspect_ratio.y; +// Fragment Shader +@fragment +fn fs_main( + in: VertexOutput +) -> @location(0) vec4 { + let inst = sprite_data[in.inst_index]; + + // Start with the quad’s atlas-mapped UV + var uv = in.atlas_uv; + + //If this is a tiled sheet sprite, compute the sub‐cell + if inst.entity_type == 1u { + // Normalize dimensions by image size + let ts = inst.tile_size / inst.image_size; + let ps = inst.padding / inst.image_size; + let os = inst.offset / inst.image_size; + + // Calculate tile position + let col = inst.index % inst.columns; + let row = inst.index / inst.columns; + + // Calculate step size (tile + padding) + let step = ts + ps; + + // Calculate top-left corner of tile + let tile_min = os + vec2(f32(col), f32(row)) * step; + + // Calculate bottom-right corner of tile + let tile_max = tile_min + step; + + // Map UV from [0,1] to [tile_min, tile_max] + uv = mix(tile_min, tile_max, uv); } - return uv; + // Sample the atlas at the correct UV and apply tint + let tex_col = textureSample(diffuseTex, texSampler, uv); + return tex_col * inst.color_tint; + //return inst.color_tint; } -fn apply_flip(uv: vec2) -> vec2 { - var fuv = uv; - if (spriteUniform.flip_x == 1u) { - fuv.x = 1.0 - fuv.x; - } - if (spriteUniform.flip_y == 1u) { - fuv.y = 1.0 - fuv.y; - } - return fuv; -} +fn scale_translation(matrix: mat4x4, translation_scale: vec3) -> mat4x4 { + // Extract translation from last column + let translation = matrix[3].xyz; -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - var uv = select(apply_flip(in.uv), compute_atlas_uv(in.uv), spriteUniform.use_atlas == 1u); - return textureSample(diffuseTex, texSampler, uv) * - select(vec4(1.0, 1.0, 1.0, 1.0), spriteUniform.color_tint, spriteUniform.use_atlas == 1u); -} \ No newline at end of file + // Extract scale from basis vector lengths + let scale = vec3( + length(matrix[0].xyz), + length(matrix[1].xyz), + length(matrix[2].xyz) + ); + + // Extract Z-rotation from first column + let angle = atan2(matrix[0].y, matrix[0].x); + + // Compute sin/cos once + let c = cos(angle); + let s = sin(angle); + + // Scale translation components + let tx = translation.x * translation_scale.x; + let ty = translation.y * translation_scale.y; + let tz = translation.z * translation_scale.z; + + // Rebuild matrix in column-major order + return mat4x4( + vec4(c * scale.x, s * scale.x, 0.0, 0.0), // Column 0 + vec4(-s * scale.y, c * scale.y, 0.0, 0.0), // Column 1 + vec4(0.0, 0.0, scale.z, 0.0), // Column 2 + vec4(tx, ty, tz, 1.0) // Column 3 + ); +} diff --git a/framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs b/framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs new file mode 100644 index 0000000000..1b14e9a7ac --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs @@ -0,0 +1,86 @@ +use std::sync::Arc; + +use image::buffer; + +pub struct DynamicBuffer { + pub layout: Arc, + pub buffer: wgpu::Buffer, + bind_group: wgpu::BindGroup, + pub capacity: u64, // in bytes + buffer_usage: wgpu::BufferUsages, +} + +impl DynamicBuffer { + /// Create with an initial capacity (in bytes). + pub fn new( + device: &wgpu::Device, + layout: Arc, + initial_capacity: u64, + buffer_usage: wgpu::BufferUsages, + ) -> Self { + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("dynamic_storage_buffer"), + size: initial_capacity, + usage: buffer_usage | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &*layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + label: Some("dynamic_storage_bind_group"), + }); + Self { + layout, + buffer, + bind_group, + capacity: initial_capacity, + buffer_usage, + } + } + + /// Ensure we have at least `needed` bytes; if not, reallocate & rebind. + fn ensure_capacity(&mut self, device: &wgpu::Device, needed: u64) { + if needed <= self.capacity { + return; + } + // double up (or at least `needed`) + let new_capacity = (self.capacity.max(needed)) * 2; + self.buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("dynamic_storage_buffer (resized)"), + size: new_capacity, + usage: self.buffer_usage | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &*self.layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: self.buffer.as_entire_binding(), + }], + label: Some("dynamic_storage_bind_group (resized)"), + }); + self.capacity = new_capacity; + } + + /// Write your Pod‐slice into the buffer, growing if necessary. + pub fn write_pods( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + data: &[T], + ) { + let needed_bytes = (data.len() * std::mem::size_of::()) as u64; + self.ensure_capacity(device, needed_bytes); + queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data)); + + println!("DynamicBuffer: Wrote {} bytes", needed_bytes); + } + + /// Access the bind group for binding in your render pass. + pub fn get_bind_group(&self) -> &wgpu::BindGroup { + &self.bind_group + } +} diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index 4063fd1613..a9e1d2d2b4 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -1,14 +1,12 @@ use bevy_tasks::{IoTaskPool, TaskPool}; use convert::IntoBones; -use crossbeam_channel::{unbounded, Receiver, Sender}; -use image::{DynamicImage, RgbaImage}; +use image::RgbaImage; use pollster::FutureExt; use std::{ path::{Path, PathBuf}, sync::Arc, time::Instant, }; -use texture::Texture; use wgpu::util::DeviceExt; use winit::{ application::ApplicationHandler, @@ -21,18 +19,24 @@ use bones_framework::{ glam::*, input::gilrs::process_gamepad_events, prelude::{self as bones, BitSet, ComponentIterBitset}, + render::camera, }; use egui_wgpu::ScreenDescriptor; +mod atlas_pool; mod convert; +mod dynamic_storage; mod line; mod sprite; mod storage; mod texture; +mod texture_file; mod ui; +use dynamic_storage::DynamicBuffer; use sprite::*; +use texture::Texture; use ui::{default_load_progress, EguiRenderer}; /// The prelude @@ -64,21 +68,6 @@ impl WgpuQueue { } } -// Texture sender to the wgpu thread -#[derive(bones_schema::HasSchema, Clone)] -#[repr(C)] -#[schema(opaque)] -#[schema(no_default)] -struct TextureSender(Sender<(Arc, bones::Entity, Arc, bones::Ustr)>); - -type TextureReceiver = Receiver<(Arc, bones::Entity, Arc, bones::Ustr)>; - -// Indicates that we already loaded the sprite texture -// and sent it to the wgpu thread -#[derive(bones_schema::HasSchema, Default, Clone)] -#[repr(C)] -struct TextureLoaded; - #[derive(bones_schema::HasSchema, Default, Clone)] #[repr(C)] struct PixelArt(bool); @@ -127,12 +116,14 @@ impl BonesWgpuRenderer { } pub fn run(mut self) { - let (instance, adapter, device, queue, texture_bind_group_layout) = async { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); + //Start wgpu + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); + let (adapter, device, queue) = async { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) .await .unwrap(); + let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor::default(), @@ -140,62 +131,98 @@ impl BonesWgpuRenderer { ) .await .unwrap(); - let texture_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the textures - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture_bind_group_layout"), - }); - ( - Arc::new(instance), - Arc::new(adapter), - Arc::new(device), - Arc::new(queue), - Arc::new(texture_bind_group_layout), - ) + (Arc::new(adapter), Arc::new(device), Arc::new(queue)) } .block_on(); + // Texture bind group layout (matches @group(0) in WGSL) + let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + // This should match the filterable field of the textures + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + label: Some("texture_bind_group_layout"), + }); + + // Storage buffer bind group layout (matches @group(1) in WGSL) + let storage_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("storage_bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let camera_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("camera_bind_group_layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(VERTICES), + usage: wgpu::BufferUsages::VERTEX, + }); + + let instance = Arc::new(instance); + let texture_layout = Arc::new(texture_layout); + let storage_layout = Arc::new(storage_layout); + let camera_layout = Arc::new(camera_layout); + let vertex_buffer = Arc::new(vertex_buffer); + let index_buffer = Arc::new(index_buffer); + + //This is used to store dynamically some rendering data, like transform, flip, etc + let dynamic_storage = + DynamicBuffer::new(&device, storage_layout, 1024, wgpu::BufferUsages::STORAGE); + let camera_dynamic_uniform = + DynamicBuffer::new(&device, camera_layout, 1024, wgpu::BufferUsages::STORAGE); + //Insert wgpu resources self.game .insert_shared_resource(WgpuDevice(Some(device.clone()))); self.game .insert_shared_resource(WgpuQueue(Some(queue.clone()))); self.game.insert_shared_resource(PixelArt(self.pixel_art)); - - let (sender, receiver) = unbounded(); - self.game.insert_shared_resource(TextureSender(sender)); - self.game .insert_shared_resource(LoadingContext(self.custom_load_progress)); + self.game.insert_shared_resource(Cameras(Vec::new())); + //Deal with asset server IoTaskPool::init(TaskPool::default); if let Some(mut asset_server) = self.game.shared_resource_mut::() { asset_server.set_game_version(self.game_version.clone()); @@ -226,6 +253,7 @@ impl BonesWgpuRenderer { ))); storage.load(); self.game.insert_shared_resource(storage); + self.game .insert_shared_resource(bones::EguiTextures::default()); self.game.insert_shared_resource(bones::ExitBones(false)); @@ -240,13 +268,6 @@ impl BonesWgpuRenderer { .init_shared_resource::(); //Insert needed systems - self.game.systems.add_after_system(load_sprite); - self.game.systems.add_after_system(load_atlas_sprite); - self.game.systems.add_after_system(load_tile_sprite); - self.game.systems.add_before_system(update_atlas_uniforms); - self.game.systems.add_before_system(update_sprite_uniforms); - self.game.systems.add_before_system(update_tiles_uniforms); - self.game.systems.add_startup_system(load_egui_textures); self.game.systems.add_startup_system(asset_load_status); @@ -260,16 +281,31 @@ impl BonesWgpuRenderer { // process. event_loop.set_control_flow(ControlFlow::Poll); + let bind_group_clone = texture_layout.clone(); + let device_clone = device.clone(); + let mut app = App { state: None, instance, adapter, device, queue, - texture_bind_group_layout, - receiver, + texture_layout, + storage_layout: dynamic_storage.layout.clone(), + dynamic_storage: Some(dynamic_storage), + camera_layout: camera_dynamic_uniform.layout.clone(), + camera_dynamic_uniform: Some(camera_dynamic_uniform), game: self.game, + vertex_buffer, + index_buffer, _now: Instant::now(), + atlas_pool: atlas_pool::AtlasPool::new( + &device_clone, + &bind_group_clone, + (4096, 4096), + 8, + self.pixel_art, + ), }; event_loop.run_app(&mut app).unwrap(); @@ -339,6 +375,7 @@ pub fn update_egui_fonts(ctx: &egui::Context, bones_assets: &bones::AssetServer) ctx.set_fonts(fonts); } + //TODO Handle asset changes fn load_egui_textures(game: &mut bones::Game) { let asset_server = game.shared_resource::().unwrap(); @@ -384,12 +421,18 @@ fn load_egui_textures(game: &mut bones::Game) { //TODO Implement proper Drop for the app struct App { state: Option, + atlas_pool: atlas_pool::AtlasPool, instance: Arc, adapter: Arc, device: Arc, queue: Arc, - texture_bind_group_layout: Arc, - receiver: TextureReceiver, + texture_layout: Arc, + storage_layout: Arc, + dynamic_storage: Option, + camera_layout: Arc, + camera_dynamic_uniform: Option, + vertex_buffer: Arc, + index_buffer: Arc, game: bones::Game, _now: Instant, } @@ -419,7 +462,23 @@ impl ApplicationHandler for App { self.queue.clone(), &self.instance, &self.adapter, - &self.texture_bind_group_layout, + self.texture_layout.clone(), + self.vertex_buffer.clone(), + self.index_buffer.clone(), + self.dynamic_storage.take().unwrap_or(DynamicBuffer::new( + &self.device, + self.storage_layout.clone(), + 1024, + wgpu::BufferUsages::STORAGE, + )), + self.camera_dynamic_uniform + .take() + .unwrap_or(DynamicBuffer::new( + &self.device, + self.camera_layout.clone(), + 1024, + wgpu::BufferUsages::STORAGE, + )), ); setup_egui(&mut self.game, &state.egui_renderer.context().clone()); @@ -438,34 +497,6 @@ impl ApplicationHandler for App { let mut wheel_events = Vec::new(); let mut button_events = Vec::new(); - for (texture, entity, atlas_sprite_buffer, session_name) in self.receiver.try_iter() { - let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: atlas_sprite_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); - - if let Some(v) = state.sprites.get_mut(&session_name) { - v.push((bind_group, entity)); - } else { - state - .sprites - .insert(session_name.clone(), vec![(bind_group, entity)]); - } - } // Egui input handling state .egui_renderer @@ -484,7 +515,7 @@ impl ApplicationHandler for App { event_loop.exit(); } - state.render(&mut self.game); + state.render(&mut self.game, &mut self.atlas_pool); // Emits a new redraw requested event. state.get_window().request_redraw(); } @@ -582,8 +613,12 @@ struct State { size: winit::dpi::PhysicalSize, surface: wgpu::Surface<'static>, surface_format: wgpu::TextureFormat, - render_pipeline: wgpu::RenderPipeline, - sprites: bones::UstrMap>, + opaque_render_pipeline: wgpu::RenderPipeline, + transparent_render_pipeline: wgpu::RenderPipeline, + vertex_buffer: Arc, + index_buffer: Arc, + dynamic_storage: DynamicBuffer, + camera_dynamic_uniform: DynamicBuffer, egui_renderer: EguiRenderer, egui_scale_factor: f32, } @@ -595,7 +630,11 @@ impl State { queue: Arc, instance: &wgpu::Instance, adapter: &wgpu::Adapter, - texture_bind_group_layout: &wgpu::BindGroupLayout, + texture_layout: Arc, + vertex_buffer: Arc, + index_buffer: Arc, + dynamic_storage: DynamicBuffer, + camera_dynamic_uniform: DynamicBuffer, ) -> Self { let size = window.inner_size(); let surface = instance.create_surface(window.clone()).unwrap(); @@ -643,57 +682,121 @@ impl State { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[texture_bind_group_layout], + bind_group_layouts: &[ + &texture_layout, + &dynamic_storage.layout, + &camera_dynamic_uniform.layout, + ], push_constant_ranges: &[], }); - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: Some("vs_main"), - buffers: &[Vertex::desc()], - compilation_options: Default::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent::OVER, - alpha: wgpu::BlendComponent::OVER, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: Default::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE - // or Features::POLYGON_MODE_POINT - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - // If the pipeline will be used with a multiview render pass, this - // indicates how many array layers the attachments will have. - multiview: None, - // Useful for optimizing shader compilation on Android - cache: None, - }); + let opaque_render_pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: None, //For opaque + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE + // or Features::POLYGON_MODE_POINT + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + + /*Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Rgba8UnormSrgb, + depth_write_enabled: true, //For opaque + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: Default::default(), + bias: Default::default(), + }),*/ + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + // If the pipeline will be used with a multiview render pass, this + // indicates how many array layers the attachments will have. + multiview: None, + // Useful for optimizing shader compilation on Android + cache: None, + }); + + let transparent_render_pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[Vertex::desc()], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), //For transparent + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE + // or Features::POLYGON_MODE_POINT + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + + /*Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Rgba8UnormSrgb, + depth_write_enabled: false, //For transparent + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: Default::default(), + bias: Default::default(), + }),*/ + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + // If the pipeline will be used with a multiview render pass, this + // indicates how many array layers the attachments will have. + multiview: None, + // Useful for optimizing shader compilation on Android + cache: None, + }); let egui_renderer = EguiRenderer::new(&device, surface_config.format, None, 1, &window); @@ -704,10 +807,14 @@ impl State { size, surface, surface_format, - render_pipeline, - sprites: bones::UstrMap::default(), + opaque_render_pipeline, + transparent_render_pipeline, egui_renderer, + dynamic_storage, + camera_dynamic_uniform, egui_scale_factor: 1.0, + vertex_buffer, + index_buffer, } } @@ -737,7 +844,27 @@ impl State { self.configure_surface(); } - fn render(&mut self, game: &mut bones::Game) { + fn render(&mut self, game: &mut bones::Game, atlas_pool: &mut atlas_pool::AtlasPool) { + // Create the command encoder. + let mut encoder = self.device.create_command_encoder(&Default::default()); + + //Run needed egui related function, needs to run before step + self.egui_renderer.begin_frame(&self.window); + + //Step bones + game.step(Instant::now()); + + update_atlas_pool(game, atlas_pool); + sort_sprites(game); + update_uniforms(game, &mut self.dynamic_storage); + update_cameras_uniform( + game, + &mut self.camera_dynamic_uniform, + IVec2::new(self.size.width as i32, self.size.height as i32), + ); + + let cameras_sorted = game.shared_resource::().unwrap(); + // Create texture view let surface_texture = self .surface @@ -752,56 +879,52 @@ impl State { ..Default::default() }); - // Create the command encoder. - let mut encoder = self.device.create_command_encoder(&Default::default()); + let mut camera_index = 0; + for (session_name, camera_vec) in cameras_sorted.0.iter() { + let session = game.sessions.get(*session_name).unwrap(); - //Index buffer is the same for all sprites - let index_buffer = self - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(INDICES), - usage: wgpu::BufferUsages::INDEX, - }); - let num_indices = INDICES.len() as u32; - - let mut dont_delete = vec![]; - for (session_name, session) in game.sessions.iter() { - dont_delete.push(session_name.clone()); - if !session.visible { - continue; - } - - //Get cameras and sort them + let sprite_lists = session.world.resource::(); + let atlas_handles = session.world.component::(); let cameras = session.world.component::(); - let transforms = session.world.component::(); - let entities = session.world.resource::(); - let mut bitset = cameras.bitset().clone(); - bitset.bit_and(transforms.bitset()); + for (camera_ent, camera_size) in camera_vec { + // Create render passes for each camera + let camera = cameras.get(*camera_ent).unwrap(); + + // Set the camera for the sprites + let n = self.dynamic_storage.capacity / size_of::() as u64; + for i in 0..n { + self.queue.write_buffer( + &self.dynamic_storage.buffer, + 4 + i * size_of::() as u64, + bytemuck::cast_slice(&[(camera_index as u32)]), + ); + } - let mut cameras_vec = vec![]; - for ent in entities.iter_with_bitset(&bitset) { - let Some(camera) = cameras.get(ent) else { - continue; - }; + let clear_color = session.world.get_resource::(); - if !camera.active { - continue; - } + let load = if camera.draw_background_color { + let color: bones_framework::render::prelude::Color = + match (camera.background_color.option(), clear_color) { + (Some(color), _) => color, + (None, Some(color)) => color.0, + (None, None) => bones::Color::BLACK, + }; + let color = color.as_rgba_f64(); - // Get the transform from the ECS. - let Some(transform) = transforms.get(ent).cloned() else { - continue; - }; + let color = wgpu::Color { + r: color[0], + g: color[1], + b: color[2], + a: color[3], + }; - cameras_vec.push((camera, transform)); - } - cameras_vec.sort_by(|a, b| a.0.priority.cmp(&b.0.priority)); + wgpu::LoadOp::Clear(color) + } else { + wgpu::LoadOp::Load + }; - for (camera, mut camera_transform) in cameras_vec { - // Create one render pass for each camera - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: None, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &texture_view, @@ -815,184 +938,116 @@ impl State { timestamp_writes: None, occlusion_query_set: None, }); - render_pass.set_pipeline(&self.render_pipeline); - let scale_ratio; if let Some(viewport) = camera.viewport.option() { - let size = IVec2::new(self.size.width as i32, self.size.height as i32) - - viewport.position.as_ivec2(); - let size = IVec2::new( - (viewport.size.x as i32).min(size.x), - (viewport.size.y as i32).min(size.y), - ); - - if size.x <= 0 || size.y <= 0 { - continue; - } - - render_pass.set_viewport( + pass.set_viewport( viewport.position.x as f32, viewport.position.y as f32, - size.x as f32, - size.y as f32, + camera_size.x, + camera_size.y, viewport.depth_min, viewport.depth_max, ); - if viewport.size.y <= viewport.size.x { - scale_ratio = Mat4::from_scale(Vec3::new( - size.x as f32 / viewport.size.x as f32, - viewport.size.y as f32 / viewport.size.x as f32 * size.y as f32 - / viewport.size.y as f32, - 1., - )) - .inverse(); - } else { - scale_ratio = Mat4::from_scale(Vec3::new( - viewport.size.x as f32 / viewport.size.y as f32 * size.x as f32 - / viewport.size.x as f32, - size.y as f32 / viewport.size.y as f32, - 1., - )) - .inverse(); - } - } else if self.size.height <= self.size.width { - scale_ratio = Mat4::from_scale(Vec3::new( - 1., - self.size.height as f32 / self.size.width as f32, - 1., - )) - .inverse(); - } else { - scale_ratio = Mat4::from_scale(Vec3::new( - self.size.width as f32 / self.size.height as f32, - 1., - 1., - )) - .inverse(); } - if camera.draw_background_color { - let clear_color = session.world.get_resource::(); + // set the quad vertex buffer (slot 0) + pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + // Before your draw loops, once: + pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + pass.set_bind_group(1, &*self.dynamic_storage.get_bind_group(), &[]); + pass.set_bind_group(2, &*self.camera_dynamic_uniform.get_bind_group(), &[]); + + // === OPAQUE PASS === + pass.set_pipeline(&self.opaque_render_pipeline); + + // Debug bind: use atlas 0 and instance 0 for a single test quad + + //pass.set_bind_group(0, &atlas_pool.atlases[0].bind_group, &[]); + //pass.set_bind_group(1, self.dynamic_storage.get_bind_group(), &[]); + //pass.draw_indexed(0..6, 0, 0..1); // should draw the first sprite once + + let opaque_list = &sprite_lists.opaque_list; + let transparent_list = &sprite_lists.transparent_list; + let index_of = &sprite_lists.index_of; + + // iterate through opaque_list and batch by atlas_id + let mut i = 0; + while i < opaque_list.len() { + let atlas_pool_handle = + atlas_handles.get(opaque_list[i]).unwrap_or_else(|| { + atlas_handles + .get(sprite_lists.tile_layer.get(&opaque_list[i]).unwrap().0) + .unwrap() + }); + let current_atlas = atlas_pool_handle.atlas_id; + + // find how many consecutive entries share this atlas + let start = i; + while i < opaque_list.len() + && atlas_handles + .get(opaque_list[i]) + .unwrap_or_else(|| { + atlas_handles + .get(sprite_lists.tile_layer.get(&opaque_list[i]).unwrap().0) + .unwrap() + }) + .atlas_id + == current_atlas + { + i += 1; + } + let count = (i - start) as u32; + let first_instance = index_of[&opaque_list[start]]; - let background_color = match (camera.background_color.option(), clear_color) { - (Some(color), _) => color, - (None, Some(color)) => color.0, - (None, None) => bones::Color::BLACK, - }; + // bind this atlas’s bind group (group 0) + let atlas_bg = &atlas_pool.atlases[current_atlas].bind_group; + pass.set_bind_group(0, atlas_bg, &[]); - //TODO Stop creating buffers every frame - let vertex_buffer = - self.device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(VERTICES_FULL), - usage: wgpu::BufferUsages::VERTEX, - }); - - let rgba = - RgbaImage::from_pixel(1, 1, image::Rgba(background_color.as_rgba_u8())); - let img = DynamicImage::ImageRgba8(rgba); - let texture = Texture::from_image( - &self.device, - &self.queue, - &img, - Some("background_color"), - false, - ) - .unwrap(); - let atlas_sprite_buffer = - self.device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[AtlasSpriteUniform { - use_atlas: 0, - ..Default::default() - }]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }); - - let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.render_pipeline.get_bind_group_layout(0), - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: atlas_sprite_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); - - render_pass.set_bind_group(0, &bind_group, &[]); - render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); - render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16); - render_pass.draw_indexed(0..num_indices, 0, 0..1); + // draw a quad instanced `count` times, indexing into your storage arrays + pass.draw_indexed(0..6, 0, first_instance..first_instance + count); } - // Render each sprite with its own transform. - let Some(binds) = self.sprites.get(session_name) else { - continue; - }; + // === TRANSPARENT PASS === + pass.set_pipeline(&self.transparent_render_pipeline); - for (bind_group, entity) in binds { - // Get the entity transform from the ECS. - let Some(transform) = session - .world - .component::() - .get(*entity) - .cloned() - else { - continue; - }; - - let (w, h) = (self.size.width as f32, self.size.height as f32); - - camera_transform.translation.z = 0.; - let camera_scale = - Vec3::new(1.0 / scale_ratio.x_axis.x, 1.0 / scale_ratio.y_axis.y, 1.0); - let translation_scale = - Vec3::new((2.0 / w) * camera_scale.x, (2.0 / h) * camera_scale.y, 1.0); - - let camera_mat4 = camera_transform.to_matrix(translation_scale).inverse(); + let mut j = 0; + while j < transparent_list.len() { + let atlas_pool_handle = atlas_handles.get(transparent_list[i]).unwrap_or( + atlas_handles + .get(sprite_lists.tile_layer.get(&transparent_list[i]).unwrap().0) + .unwrap(), + ); + let current_atlas = atlas_pool_handle.atlas_id; + + let start = j; + while j < transparent_list.len() + && atlas_handles + .get(transparent_list[i]) + .unwrap_or( + atlas_handles + .get( + sprite_lists + .tile_layer + .get(&transparent_list[i]) + .unwrap() + .0, + ) + .unwrap(), + ) + .atlas_id + == current_atlas + { + j += 1; + } + let count = (j - start) as u32; + let first_instance = index_of[&transparent_list[start]]; - let quad_scale = translation_scale * camera_transform.scale; - let mat4 = scale_ratio * camera_mat4 * transform.to_matrix(quad_scale); + let atlas_bg = &atlas_pool.atlases[current_atlas].bind_group; + pass.set_bind_group(0, atlas_bg, &[]); - // Compute transformed vertices on the CPU. - let transformed_vertices: Vec = VERTICES_FULL - .iter() - .map(|v: &Vertex| Vertex { - position: transform_vertex( - Vec3::from(v.position), - mat4, - Vec3::ZERO, // Use Vec3::ZERO as the pivot point for transformations - ) - .into(), - tex_coords: v.tex_coords, // Texture coordinates remain unchanged. - }) - .collect(); - - // Create a dynamic vertex buffer with the transformed vertices. - let vertex_buffer = - self.device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Transformed Vertex Buffer"), - contents: bytemuck::cast_slice(&transformed_vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - - render_pass.set_bind_group(0, bind_group, &[]); - render_pass.set_vertex_buffer(0, vertex_buffer.slice(..)); - render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint16); - render_pass.draw_indexed(0..num_indices, 0, 0..1); + pass.draw_indexed(0..6, 0, first_instance..first_instance + count); } + camera_index += 1; } } @@ -1004,11 +1059,6 @@ impl State { * self.egui_scale_factor, }; - self.egui_renderer.begin_frame(&self.window); - - //Step bones - game.step(Instant::now()); - self.egui_renderer.end_frame_and_draw( &self.device, &self.queue, @@ -1019,20 +1069,27 @@ impl State { ); } - let keys_to_remove: Vec<_> = self - .sprites - .keys() - .filter(|session_name| !dont_delete.contains(session_name)) - .cloned() - .collect(); - for session_name in keys_to_remove { - self.sprites.remove(&session_name); - } - // Submit the command queue. self.queue.submit([encoder.finish()]); self.window.pre_present_notify(); surface_texture.present(); + + /*Write atlas pool textures to png files for debugging + if !time_since_start().as_secs() < 10 { + for atlas in atlas_pool.atlases.iter() { + let path = String::from("atlas_") + &atlas.id.to_string() + ".png"; + + let result = crate::texture_file::dump_texture_to_png( + &self.device, + &self.queue, + &atlas.texture, + (atlas.texture.width(), atlas.texture.height()), + std::path::Path::new(&path), + ); + result.unwrap(); + } + std::process::exit(1); + }*/ } } @@ -1116,24 +1173,6 @@ const INDICES: &[u16] = &[ 0, 2, 3, // second triangle ]; -//TODO Add quaternion rotations, and move this calculation to -// wgsl code - -fn transform_vertex(pos: Vec3, transform: Mat4, pivot: Vec3) -> Vec3 { - // Translate the vertex to the pivot point. - let to_origin = Mat4::from_translation(-pivot); - let from_origin = Mat4::from_translation(pivot); - - // Apply the transformation: first translate to origin, then transform, then translate back. - let combined = from_origin * transform * to_origin; - - // Apply the transformation matrix to the position - let pos4 = combined * pos.extend(1.0); - - // Return the truncated Vec3 position (ignoring the w-component) - pos4.truncate() -} - /// A [`bones::AssetIo`] configured for web and local file access pub fn asset_io(asset_dir: &Path, packs_dir: &Path) -> impl bones::AssetIo + 'static { #[cfg(not(target_arch = "wasm32"))] diff --git a/framework_crates/bones_wgpu_renderer/src/line.rs b/framework_crates/bones_wgpu_renderer/src/line.rs index acbeb60fb7..cf3d8db723 100644 --- a/framework_crates/bones_wgpu_renderer/src/line.rs +++ b/framework_crates/bones_wgpu_renderer/src/line.rs @@ -1,9 +1,9 @@ +use bones_framework::glam::Vec2; use lyon::math::Point; use lyon::path::Path; use lyon::tessellation::{ - StrokeOptions, StrokeTessellator, BuffersBuilder, FillVertex, StrokeVertex, VertexBuffers, + BuffersBuilder, StrokeOptions, StrokeTessellator, StrokeVertex, VertexBuffers, }; -use bones_framework::glam::Vec2; use crate::bones::Path2d; diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs index 60e5459e74..a5d5bd3049 100644 --- a/framework_crates/bones_wgpu_renderer/src/sprite.rs +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -1,46 +1,67 @@ -use crate::texture::Texture; -use std::sync::Arc; +use crate::{atlas_pool::AtlasPool, *}; +use bones_framework::{ + prelude::{self as bones, BitSet, ComponentIterBitset, Transform, Ustr}, + render::transform, +}; +use guillotiere::Allocation; +use std::collections::HashMap; -use crate::*; -use bones_framework::prelude::{self as bones, BitSet, ComponentIterBitset}; +// Functions used to load sprites, atlas sprites and tile sprites, and update them. -/// Functions used to load sprites, atlas sprites and tile sprites, and update them. +#[repr(C, align(16))] +#[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)] +pub struct CameraTransform { + transform: [[f32; 4]; 4], + screen_size: [f32; 2], + _pad0: u32, + _pad1: u32, +} -// Uniform data struct matching the WGSL `AtlasSpriteUniform` layout. -#[repr(C)] +#[repr(C, align(16))] #[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)] pub struct AtlasSpriteUniform { + // Base parameters + pub entity_type: u32, + pub camera_index: u32, + pub _pad0: u32, + pub _pad1: u32, + pub transform: [[f32; 4]; 4], + pub color_tint: [f32; 4], + + // Sprite and Atlas parameters + pub flip_x: u32, + pub flip_y: u32, + pub uv_min: [f32; 2], + pub uv_max: [f32; 2], + // Atlas parameters pub tile_size: [f32; 2], pub image_size: [f32; 2], - pub padding: [f32; 2], //TODO Check if it really works - pub offset: [f32; 2], //TODO Check if it really works + pub padding: [f32; 2], + pub offset: [f32; 2], + pub columns: u32, pub index: u32, - - // State flags - pub use_atlas: u32, - pub flip_x: u32, - pub flip_y: u32, - pub _pad: [u32; 3], // Explicit padding - - // Color tint - pub color_tint: [f32; 4], } -#[derive(bones_schema::HasSchema, Clone)] -#[repr(C)] -#[schema(opaque)] -#[schema(no_default)] -pub struct AtlasSpriteBuffer(Arc); - impl AtlasSpriteUniform { - pub fn from_atlas_sprite(atlas_sprite: &bones::AtlasSprite, atlas: &bones::Atlas) -> Self { + pub fn from_atlas_sprite( + atlas_sprite: &bones::AtlasSprite, + atlas: &bones::Atlas, + transform: &bones::Transform, + uvs: ([f32; 2], [f32; 2]), + ) -> Self { let image_size = [ atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), ]; + if atlas_sprite.flip_x { + println!("Flipping X for sprite: {:?}", atlas_sprite.index); + } else { + println!("Not flipping X for sprite: {:?}", atlas_sprite.index); + } + Self { tile_size: atlas.tile_size.into(), columns: atlas.columns, @@ -48,46 +69,636 @@ impl AtlasSpriteUniform { offset: atlas.offset.into(), index: atlas_sprite.index, image_size, - use_atlas: 1, + entity_type: 1, flip_x: atlas_sprite.flip_x as u32, flip_y: atlas_sprite.flip_y as u32, color_tint: atlas_sprite.color.as_rgba_f32(), + transform: transform.to_matrix_none().to_cols_array_2d(), + uv_min: uvs.0, + uv_max: uvs.1, ..Default::default() } } - pub fn from_tile(tile: &bones::Tile, atlas: &bones::Atlas) -> Self { + //DONE I need to spawn sprite in a place based on its position on the tile layer array + pub fn from_tile( + tile: &bones::Tile, + atlas: &bones::Atlas, + transform: &bones::Transform, + uvs: ([f32; 2], [f32; 2]), + index: usize, + tile_layer: &bones::TileLayer, + ) -> Self { let image_size = [ atlas.offset.x + ((atlas.tile_size.x + atlas.padding.x) * atlas.columns as f32), atlas.offset.y + ((atlas.tile_size.y + atlas.padding.y) * atlas.rows as f32), ]; + let mut transform = transform.clone(); + transform.translation.x += + tile_layer.tile_size.x * (index as u32 % tile_layer.grid_size.x) as f32; + transform.translation.y += + tile_layer.tile_size.y * (index as u32 / tile_layer.grid_size.x) as f32; Self { tile_size: atlas.tile_size.into(), columns: atlas.columns, padding: atlas.padding.into(), offset: atlas.offset.into(), - index: tile.idx, + index: 0, image_size, - use_atlas: 1, + entity_type: 1, flip_x: tile.flip_x as u32, flip_y: tile.flip_y as u32, color_tint: tile.color.as_rgba_f32(), + transform: transform.to_matrix_none().to_cols_array_2d(), + uv_min: uvs.0, + uv_max: uvs.1, ..Default::default() } } - pub fn from_sprite(sprite: &bones::Sprite) -> Self { + pub fn from_sprite( + sprite: &bones::Sprite, + transform: &bones::Transform, + uvs: ([f32; 2], [f32; 2]), + image_size: Vec2, + ) -> Self { + if sprite.flip_x { + println!("Flipping X for sprite: {:?}", sprite); + } else { + println!("Not flipping X for sprite: {:?}", sprite); + } + Self { color_tint: sprite.color.as_rgba_f32(), flip_x: sprite.flip_x as u32, flip_y: sprite.flip_y as u32, - use_atlas: 0, + entity_type: 0, + transform: transform.to_matrix_none().to_cols_array_2d(), + uv_min: uvs.0, + uv_max: uvs.1, + tile_size: image_size.into(), ..Default::default() } } } +#[derive(bones_schema::HasSchema, Clone)] +#[schema(no_default)] +/// Points to the atlas and rectangle this sprite lives in. +pub struct AtlasPoolHandle { + pub atlas_id: usize, + pub alloc: Allocation, + pub uv_min: [f32; 2], // Atlas UV coordinates (min_x, min_y) + pub uv_max: [f32; 2], // Atlas UV coordinates (max_x, max_y) + pub image_size: [f32; 2], // Original image size (width, height) + + sender: crossbeam_channel::Sender<(bones::Entity, usize, Allocation)>, + entity: bones::Entity, +} + +impl Drop for AtlasPoolHandle { + fn drop(&mut self) { + // Send the entity to the atlas pool for deallocation + if let Err(e) = self.sender.send((self.entity, self.atlas_id, self.alloc)) { + eprintln!("Failed to send entity for deallocation: {:?}", e); + } + } +} + +//TODO Add tiles +pub fn update_atlas_pool(game: &mut bones::Game, atlas_pool: &mut AtlasPool) { + let assets = game.shared_resource_cell::().unwrap(); + let queue = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + if !session.active { + continue; + } + + let entities = session.world.resource::(); + let sprites = session.world.component::(); + let atlases = session.world.component::(); + let tile_layers = session.world.component::(); + let mut atlas_pool_handles = session.world.component_mut::(); + + let mut bitset = sprites.bitset().clone(); + bitset.bit_or(atlases.bitset()); + bitset.bit_or(tile_layers.bitset()); + + let mut not_atlas_handle_bitset = atlas_pool_handles.bitset().clone(); + not_atlas_handle_bitset.bit_not(); + + let mut without_handle = bitset.clone(); + without_handle.bit_and(¬_atlas_handle_bitset); + + for ent in entities.iter_with_bitset(&without_handle) { + println!("Adding sprite to atlas pool: {:?}", ent); + + let image; + if let Some(sprite) = sprites.get(ent) { + image = sprite.image; + } else if let Some(atlas_sprite) = atlases.get(ent) { + let assets = assets.borrow().unwrap(); + let atlas = assets.get(atlas_sprite.atlas); + image = atlas.image.clone(); + } else { + let tile_layer = tile_layers.get(ent).unwrap(); + let assets = assets.borrow().unwrap(); + let atlas = assets.get(tile_layer.atlas); + image = atlas.image.clone(); + } + let assets = assets.borrow().unwrap(); + let image = assets.get(image); + + if let bones::Image::Data(img) = &*image { + //Allocate in the atlas pool guiliotiere + let (atlas_id, alloc) = atlas_pool + .allocate((img.width() as i32, img.height() as i32)) + .unwrap_or_else(|_| { + panic!("Failed to allocate space for sprite image: {:?}", image); + }); + + // 5) Compute and store UVs for your sprite + let rect: guillotiere::euclid::Box2D = + alloc.rectangle; + let (atlas_w, atlas_h) = ( + atlas_pool.atlas_size.0 as f32, + atlas_pool.atlas_size.1 as f32, + ); + let uv_min = [rect.min.x as f32 / atlas_w, rect.min.y as f32 / atlas_h]; + let uv_max = [rect.max.x as f32 / atlas_w, rect.max.y as f32 / atlas_h]; + + println!("{} {}", uv_min[0], uv_min[1]); + println!("{} {}", uv_max[0], uv_max[1]); + + atlas_pool_handles.insert( + ent, + AtlasPoolHandle { + atlas_id, + alloc, + uv_min, + uv_max, + image_size: [img.width() as f32, img.height() as f32], + entity: ent, + sender: atlas_pool.sender.clone(), + }, + ); + + // 1) Convert to RGBA8 and grab the raw bytes + let rgba = img.to_rgba8(); // image::RgbaImage (Vec under the hood) + let (w, h) = (rgba.width(), rgba.height()); + let raw = rgba.into_raw(); // Vec, length = w*h*4 + + // 2) Compute the origin in the atlas texture + let origin = wgpu::Origin3d { + x: rect.min.x as u32, + y: rect.min.y as u32, + z: 0, + }; + + let unpadded_stride = (4 * w) as usize; + let padded_stride = ((unpadded_stride + 255) / 256) * 256; + let mut padded_data = vec![0u8; padded_stride * h as usize]; + + // copy each scanline into the padded buffer + for row in 0..(h as usize) { + let src_offset = row * unpadded_stride; + let dst_offset = row * padded_stride; + padded_data[dst_offset..dst_offset + unpadded_stride] + .copy_from_slice(&raw[src_offset..src_offset + unpadded_stride]); + } + + println!("Texture {:?}", atlas_pool.atlases[atlas_id].texture); + + let data_layout = wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(padded_stride as u32), + rows_per_image: Some(h), + }; + let queue = queue.borrow().unwrap(); + queue.get().write_texture( + wgpu::ImageCopyTexture { + texture: &atlas_pool.atlases[atlas_id].texture, + mip_level: 0, + origin, + aspect: wgpu::TextureAspect::All, + }, + &padded_data, + data_layout, + wgpu::Extent3d { + width: w, + height: h, + depth_or_array_layers: 1, + }, + ); + queue.get().submit([]); + } + } + + // Deallocate sprites that were removed + let removed: Vec<_> = atlas_pool.receiver.try_iter().collect(); + for (ent, atlas_id, alloc) in removed { + println!("Removing sprite from atlas pool: {:?}", ent); + atlas_pool.deallocate(atlas_id, alloc); + atlas_pool_handles.remove(ent); + } + } +} + +pub fn update_uniforms(game: &mut bones::Game, dynamic_storage: &mut DynamicBuffer) { + // Prepare instance data + //TODO put lenght + let mut instances = Vec::new(); + + let queue = game.shared_resource_cell::().unwrap(); + let device = game.shared_resource_cell::().unwrap(); + let assets = game.shared_resource_cell::().unwrap(); + + for (_, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + + let entities = session.world.resource::(); + let sprite_lists = session.world.resource::(); + + let transforms = session.world.component::(); + let sprites = session.world.component::(); + let atlases = session.world.component::(); + let tile_layers = session.world.component::(); + let tiles = session.world.component::(); + let paths = session.world.component::(); + let atlas_handles = session.world.component::(); + + for entity in sprite_lists + .opaque_list + .iter() + .chain(&sprite_lists.transparent_list) + { + let transform = transforms.get(*entity).unwrap_or_else(|| { + transforms + .get(sprite_lists.tile_layer.get(entity).unwrap().0) + .unwrap() + }); + + if let Some(sprite) = sprites.get(*entity) { + let Some(atlas_handle) = atlas_handles.get(*entity) else { + panic!("Texture not loaded in atlas pool!") + }; + let (uv_min, uv_max) = (atlas_handle.uv_min, atlas_handle.uv_max); + + // Get dynamic image size from the atlas handle (stored when the image was loaded) + let image_size = Vec2::new(atlas_handle.image_size[0], atlas_handle.image_size[1]); + + let atlas_sprite = + AtlasSpriteUniform::from_sprite(sprite, &transform, (uv_min, uv_max), image_size); + + println!("Adding atlas sprite: {:?}", atlas_sprite); + + instances.push(atlas_sprite); + } else if let Some(atlas_sprite) = atlases.get(*entity) { + let assets = assets.borrow().unwrap(); + let atlas = assets.get(atlas_sprite.atlas).clone(); + + let Some(atlas_handle) = atlas_handles.get(*entity) else { + panic!("Texture not loaded in atlas pool!") + }; + let (uv_min, uv_max) = (atlas_handle.uv_min, atlas_handle.uv_max); + + let atlas_sprite = AtlasSpriteUniform::from_atlas_sprite( + atlas_sprite, + &atlas, + &transform, + (uv_min, uv_max), + ); + + println!("Adding atlas sprite: {:?}", atlas_sprite); + + instances.push(atlas_sprite); + } else if let Some(tile) = tiles.get(*entity) { + let tile_layer_ent = sprite_lists.tile_layer.get(&entity).unwrap(); + let tile_layer = tile_layers.get(tile_layer_ent.0).unwrap(); + let assets = assets.borrow().unwrap(); + let atlas = assets.get(tile_layer.atlas).clone(); + + let Some(atlas_handle) = atlas_handles.get(tile_layer_ent.0) else { + panic!("Texture not loaded in atlas pool!") + }; + let (uv_min, uv_max) = (atlas_handle.uv_min, atlas_handle.uv_max); + + let tile_sprite = AtlasSpriteUniform::from_tile( + tile, + &atlas, + &transform, + (uv_min, uv_max), + tile_layer_ent.1, + tile_layer, + ); + + instances.push(tile_sprite); + } else { + unreachable!() + } + } + + for (entity, path) in entities.iter_with(&paths) { + let Some(transform) = transforms.get(entity) else { + unreachable!() + }; + + instances.push(AtlasSpriteUniform { + entity_type: 2, + transform: transform.to_matrix_none().to_cols_array_2d(), + color_tint: path.color.as_rgba_f32(), + ..Default::default() + }); + } + } + + let device = device.borrow().unwrap(); + let queue = queue.borrow().unwrap(); + + println!("{}: {:?}", instances.len(), instances); + + // Update buffers (with dynamic resizing) + dynamic_storage.write_pods(&device.get(), queue.get(), &instances); +} + +#[derive(bones_schema::HasSchema, Default, Clone)] +/// Holds sorted sprite indices grouped by transparency for rendering order: +pub struct SpriteLists { + pub transparent_list: Vec, + pub opaque_list: Vec, + pub index_of: HashMap, + pub tile_layer: HashMap, +} + +pub fn sort_sprites(game: &mut bones::Game) { + for (_, session) in game.sessions.iter_mut() { + if !session.visible { + continue; + } + + { + let mut sprite_lists = session.world.init_resource::(); + + sprite_lists.transparent_list.clear(); + sprite_lists.opaque_list.clear(); + } + + // Now safe to borrow entities immutably + let entities = session.world.resource::(); + + let atlas_handles = session.world.component::(); + let transforms = session.world.component::(); + + let sprites = session.world.component::(); + let atlases = session.world.component::(); + let tile_layers = session.world.component::(); + let tiles = session.world.component::(); + + //Get entities with atlas handle, and that have sprite or atlas sprite + let mut sprites_atlases = sprites.bitset().clone(); + sprites_atlases.bit_or(atlases.bitset()); + sprites_atlases.bit_or(tile_layers.bitset()); + let mut bitset = atlas_handles.bitset().clone(); + bitset.bit_and(&sprites_atlases); + + // Pre‐frame (CPU) work: + let mut opaque_list: Vec = Vec::new(); + let mut transparent_list: Vec = Vec::new(); + + let mut tile_layer_hash = HashMap::with_capacity(tiles.bitset().len()); + + //TODO This is wrong! When adding sprite we should also check if we find a transparent pixel! + //And add a flag, but its fine for now i guess + for ent in entities.iter_with_bitset(&bitset) { + println!("Sorting sprite: {:?}", ent); + + if let Some(sprite) = sprites.get(ent) { + let color = sprite.color; + + if color.a() != 1.0 { + transparent_list.push(ent); + } else { + opaque_list.push(ent); + } + } else if let Some(atlas_sprite) = atlases.get(ent) { + let color = atlas_sprite.color; + + if color.a() != 1.0 { + transparent_list.push(ent); + } else { + opaque_list.push(ent); + } + } else if let Some(tile_layer) = tile_layers.get(ent) { + for (i, tile) in tile_layer.tiles.iter().enumerate() { + if let Some(tile_ent) = tile { + let tile = tiles.get(*tile_ent).unwrap(); + if tile.color.a() != 1.0 { + transparent_list.push(*tile_ent); + } else { + opaque_list.push(*tile_ent); + } + tile_layer_hash.insert(*tile_ent, (ent, i)); + } + } + } + } + + // sort opaque front‐to‐back by layer, then atlas + opaque_list.sort_by_key(|ent| { + let atlas_handle = atlas_handles.get(*ent).unwrap_or_else(|| { + atlas_handles + .get(tile_layer_hash.get(ent).unwrap().0) + .unwrap() + }); + + let layer = transforms + .get(*ent) + .unwrap_or_else(|| transforms.get(tile_layer_hash.get(ent).unwrap().0).unwrap()) + .translation + .z; + + (layer.to_bits(), atlas_handle.atlas_id) + }); + // sort transparent back‐to‐front, then atlas + transparent_list.sort_by_key(|ent| { + let atlas_handle = atlas_handles.get(*ent).unwrap_or_else(|| { + atlas_handles + .get(tile_layer_hash.get(ent).unwrap().0) + .unwrap() + }); + + let layer = transforms + .get(*ent) + .unwrap_or_else(|| transforms.get(tile_layer_hash.get(ent).unwrap().0).unwrap()) + .translation + .z; + + (!layer.to_bits(), atlas_handle.atlas_id) + }); + + /* + println!( + "Opaque sprites: {}, Transparent sprites: {}", + opaque_list.len(), + transparent_list.len() + ); + */ + + // Create index map for fast lookup + let mut index_of = HashMap::with_capacity(opaque_list.len() + transparent_list.len()); + for (i, &e) in opaque_list.iter().chain(&transparent_list).enumerate() { + index_of.insert(e, i as u32); + } + + let mut sprite_lists = session.world.resource_mut::(); + sprite_lists.index_of = index_of; + + sprite_lists.opaque_list = opaque_list; + sprite_lists.transparent_list = transparent_list; + sprite_lists.tile_layer = tile_layer_hash; + } +} + +pub fn update_cameras_uniform( + game: &mut bones::Game, + dynamic_uniform: &mut DynamicBuffer, + mut window_size: IVec2, +) { + let mut instances: Vec<(&Ustr, Vec<(i32, CameraTransform, bones::Entity, Vec2)>)> = Vec::new(); + + let queue = game.shared_resource_cell::().unwrap(); + let device = game.shared_resource_cell::().unwrap(); + + for (session_name, session) in game.sessions.iter() { + if !session.visible { + continue; + } + + let mut session_instances = Vec::new(); + + let entities = session.world.resource::(); + let cameras = session.world.component::(); + let transforms = session.world.component::(); + + let mut bitset = cameras.bitset().clone(); + bitset.bit_and(transforms.bitset()); + + for ent in entities.iter_with_bitset(&bitset) { + let Some(transform) = transforms.get(ent) else { + unreachable!() + }; + + let Some(camera) = cameras.get(ent) else { + unreachable!() + }; + + if !camera.active { + continue; + } + + let scale_ratio; + if let Some(viewport) = camera.viewport.option() { + //Get the viewport size, cropping it if needed + window_size -= viewport.position.as_ivec2(); + window_size = IVec2::new( + (viewport.size.x as i32).min(window_size.x), + (viewport.size.y as i32).min(window_size.y), + ); + + if window_size.x <= 0 || window_size.y <= 0 { + continue; + } + + if viewport.size.y <= viewport.size.x { + scale_ratio = Mat4::from_scale(Vec3::new( + window_size.x as f32 / viewport.size.x as f32, + viewport.size.y as f32 / viewport.size.x as f32 * window_size.y as f32 + / viewport.size.y as f32, + 1., + )) + .inverse(); + } else { + scale_ratio = Mat4::from_scale(Vec3::new( + viewport.size.x as f32 / viewport.size.y as f32 * window_size.x as f32 + / viewport.size.x as f32, + window_size.y as f32 / viewport.size.y as f32, + 1., + )) + .inverse(); + } + } else if window_size.x <= 0 || window_size.y <= 0 { + continue; + } else if window_size.y <= window_size.x { + scale_ratio = Mat4::from_scale(Vec3::new( + 1., + window_size.y as f32 / window_size.x as f32, + 1., + )) + .inverse(); + } else { + scale_ratio = Mat4::from_scale(Vec3::new( + window_size.x as f32 / window_size.y as f32, + 1., + 1., + )) + .inverse(); + } + + let pixel_to_clip_space = Mat4::from_scale(Vec3::new( + 2.0 / window_size.x as f32, + -2.0 / window_size.y as f32, + 1.0, + )) * Mat4::from_translation(Vec3::new(-1.0, 1.0, 0.0)); + + session_instances.push(( + camera.priority, + CameraTransform{ + transform: (/*Mat4::IDENTITYtransform.to_matrix((1.0 / window_size.as_vec2()).extend(1.0)) */ scale_ratio) + .to_cols_array_2d(), + screen_size: window_size.as_vec2().into(), ..Default::default() + }, + ent, + window_size.as_vec2() + )); + } + + session_instances.sort_by(|a, b| a.0.cmp(&b.0)); + instances.push((session_name, session_instances)); + } + + instances.sort_by(|a, b| a.0.cmp(&b.0)); + + let device = device.borrow().unwrap(); + let queue = queue.borrow().unwrap(); + let mut cameras = game.shared_resource_mut::().unwrap(); + + cameras.0 = Vec::new(); + let mut transforms = Vec::new(); + instances + .iter() + .for_each(|(session_name, session_instances)| { + let mut entities = Vec::new(); + for (_, transform, entity, window_size) in session_instances { + transforms.push(transform.clone()); + entities.push((*entity, *window_size)); + } + + cameras.0.push((**session_name, entities)); + }); + + dynamic_uniform.write_pods(&device.get(), queue.get(), &transforms); +} + +#[derive(bones_schema::HasSchema, Clone)] +#[schema(no_default)] +/// Camera entities sorted by `update_cameras_uniform` with their respective sizes. +pub struct Cameras(pub Vec<(Ustr, Vec<(bones::Entity, Vec2)>)>); + +/* OLD pub fn load_sprite(game: &mut bones::Game) { let assets = game.shared_resource_cell::().unwrap(); let device = game.shared_resource_cell::().unwrap(); @@ -104,6 +715,7 @@ pub fn load_sprite(game: &mut bones::Game) { let sprites = session.world.component::(); let mut buffers = session.world.component_mut::(); let mut texture_loaded = session.world.component_mut::(); + let transforms = session.world.component::(); let mut not_loaded = texture_loaded.bitset().clone(); not_loaded.bit_not(); @@ -113,6 +725,9 @@ pub fn load_sprite(game: &mut bones::Game) { let Some(sprite) = sprites.get(entity) else { unreachable!(); }; + let Some(transform) = transforms.get(entity) else { + panic!("No transform found!"); + }; //Load and send texture let assets = assets.borrow().unwrap(); @@ -129,19 +744,20 @@ pub fn load_sprite(game: &mut bones::Game) { .unwrap(), ); - let atlas_uniform = AtlasSpriteUniform { - use_atlas: 0, - flip_x: sprite.flip_x as u32, - flip_y: sprite.flip_y as u32, - color_tint: sprite.color.as_rgba_f32(), - ..Default::default() + let base = BaseInstance { + color: sprite.color.as_rgba_f32(), + entity_type: 0, + transform: transform.to_matrix_none().to_cols_array_2d(), + }; + let sprite_flags = SpriteFlags { + flip: [sprite.flip_x as u32, sprite.flip_y as u32] }; let atlas_sprite_buffer = Arc::new(device.borrow().unwrap().get().create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Atlas Sprite Buffer"), - contents: bytemuck::cast_slice(&[atlas_uniform]), + contents: bytemuck::cast_slice(&[base]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }, )); @@ -394,10 +1010,19 @@ pub fn update_sprite_uniforms(game: &mut bones::Game) { let entities = session.world.resource::(); let sprites = session.world.component::(); - let mut buffers = session.world.component_mut::(); + let mut buffers = session.world.component_mut::(); + let transform = session.world.component::(); for (_, (sprite, atlas_sprite_buffer)) in entities.iter_with((&sprites, &mut buffers)) { - let uniform = AtlasSpriteUniform::from_sprite(sprite); + let base = BaseInstance { + color: sprite.color.as_rgba_f32(), + entity_type: 0, + transform: transform.to_matrix_none().to_cols_array_2d(), + }; + let sprite_flags = SpriteFlags { + flip: [sprite.flip_x as u32, sprite.flip_y as u32], + }; + queue.borrow().unwrap().get().write_buffer( &atlas_sprite_buffer.0, 0, @@ -445,3 +1070,4 @@ pub fn update_tiles_uniforms(game: &mut bones::Game) { } } } +*/ diff --git a/framework_crates/bones_wgpu_renderer/src/test.rs b/framework_crates/bones_wgpu_renderer/src/test.rs new file mode 100644 index 0000000000..70b9f8de1b --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/test.rs @@ -0,0 +1,250 @@ +use wgpu::util::DeviceExt; +use guillotiere::{AtlasAllocator, size2, Allocation}; + +// Your per-sprite instance data (with layer) +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct SpriteInstance { + model: [[f32; 4]; 4], + uv_min: [f32; 2], + uv_max: [f32; 2], + color: [f32; 4], + layer: f32, +} + +// Simplified sprite struct +struct Sprite { + atlas_id: usize, + allocation: Allocation, + layer: f32, + model: [[f32; 4]; 4], + uv_min: [f32; 2], + uv_max: [f32; 2], + color: [f32; 4], + is_transparent: bool, +} + +impl Sprite { + fn to_instance(&self) -> SpriteInstance { + SpriteInstance { + model: self.model, + uv_min: self.uv_min, + uv_max: self.uv_max, + color: self.color, + layer: self.layer, + } + } +} + +// --- 1. Pipeline setup --- + +// Depth format +const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus; + +// Create two pipelines: opaque and transparent +fn create_pipelines( + device: &wgpu::Device, + layout: &wgpu::BindGroupLayout, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + sc_format: wgpu::TextureFormat, +) -> (wgpu::RenderPipeline, wgpu::RenderPipeline) { + let common = |depth_write: bool, blend: Option| { + wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[layout], + push_constant_ranges: &[], + })), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "vs_main", + buffers: &[ + // quad vertex buffer (slot 0) + wgpu::VertexBufferLayout { /* pos+uv */ ..Default::default() }, + // instance buffer (slot 1) + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as _, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ + // model matrix (locations 2–5), uv_min(6), uv_max(7), color(8), layer(9) + wgpu::VertexAttribute { + offset: 16*4, // after model + shader_location: 6, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: 16*4 + 2*4, + shader_location: 7, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: 16*4 + 4*4, + shader_location: 8, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: 16*4 + 8*4, + shader_location: 9, + format: wgpu::VertexFormat::Float32, + }, + ], + }, + ], + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: sc_format, + blend, + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: Some(wgpu::DepthStencilState { + format: DEPTH_FORMAT, + depth_write_enabled: depth_write, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: Default::default(), + bias: Default::default(), + }), + multisample: Default::default(), + multiview: None, + } + }; + + // Opaque: depth-write ON, no blending + let opaque = device.create_render_pipeline(&common(true, None)); + + // Transparent: depth-write OFF, alpha blending + let alpha_blend = wgpu::BlendState::ALPHA_BLENDING; + let transparent = device.create_render_pipeline(&common(false, Some(alpha_blend))); + + (opaque, transparent) +} + +// --- 2. Render loop --- + +fn render( + encoder: &mut wgpu::CommandEncoder, + view: &wgpu::TextureView, + depth_view: &wgpu::TextureView, + pipeline_opaque: &wgpu::RenderPipeline, + pipeline_transparent: &wgpu::RenderPipeline, + sprites: &mut Vec, + instance_buf: &wgpu::Buffer, + atlas_pool: &AtlasPool, + queue: &wgpu::Queue, +) { + // Separate lists + let mut opaque_sprites: Vec<_> = sprites.iter_mut().filter(|s| !s.is_transparent).collect(); + let mut transparent_sprites: Vec<_> = sprites.iter_mut().filter(|s| s.is_transparent).collect(); + + // === OPAQUE PASS === + { + // sort only by layer first (secondary atlas to reduce binds if you like) + opaque_sprites.sort_by(|a, b| { + a.layer.partial_cmp(&b.layer).unwrap() + .then(a.atlas_id.cmp(&b.atlas_id)) + }); + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Opaque Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations::default(), + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: depth_view, + depth_ops: Some(wgpu::Operations::default()), + stencil_ops: None, + }), + }); + pass.set_pipeline(pipeline_opaque); + pass.set_vertex_buffer(0, /* your quad VB */); + // Batch by atlas + let mut last_atlas = None; + let mut batch = Vec::new(); + for sprite in opaque_sprites { + if Some(sprite.atlas_id) != last_atlas { + if let Some(id) = last_atlas { + // flush + queue.write_buffer(instance_buf, 0, bytemuck::cast_slice(&batch)); + let atlas = atlas_pool.get(id); + pass.set_bind_group(0, &atlas.bind_group, &[]); + pass.set_vertex_buffer(1, instance_buf.slice(..)); + pass.draw_indexed(0..6, 0, 0..batch.len() as u32); + } + batch.clear(); + last_atlas = Some(sprite.atlas_id); + } + batch.push(sprite.to_instance()); + } + // flush last + if let Some(id) = last_atlas { + queue.write_buffer(instance_buf, 0, bytemuck::cast_slice(&batch)); + let atlas = atlas_pool.get(id); + pass.set_bind_group(0, &atlas.bind_group, &[]); + pass.set_vertex_buffer(1, instance_buf.slice(..)); + pass.draw_indexed(0..6, 0, 0..batch.len() as u32); + } + } + + // === TRANSPARENT PASS === + { + // sort back-to-front: higher layer first + transparent_sprites.sort_by(|a, b| { + b.layer.partial_cmp(&a.layer).unwrap() + .then(a.atlas_id.cmp(&b.atlas_id)) + }); + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Transparent Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: false, // important: no depth write + }), + stencil_ops: None, + }), + }); + pass.set_pipeline(pipeline_transparent); + pass.set_vertex_buffer(0, /* your quad VB */); + + let mut last_atlas = None; + let mut batch = Vec::new(); + for sprite in transparent_sprites { + if Some(sprite.atlas_id) != last_atlas { + if let Some(id) = last_atlas { + queue.write_buffer(instance_buf, 0, bytemuck::cast_slice(&batch)); + let atlas = atlas_pool.get(id); + pass.set_bind_group(0, &atlas.bind_group, &[]); + pass.set_vertex_buffer(1, instance_buf.slice(..)); + pass.draw_indexed(0..6, 0, 0..batch.len() as u32); + } + batch.clear(); + last_atlas = Some(sprite.atlas_id); + } + batch.push(sprite.to_instance()); + } + if let Some(id) = last_atlas { + queue.write_buffer(instance_buf, 0, bytemuck::cast_slice(&batch)); + let atlas = atlas_pool.get(id); + pass.set_bind_group(0, &atlas.bind_group, &[]); + pass.set_vertex_buffer(1, instance_buf.slice(..)); + pass.draw_indexed(0..6, 0, 0..batch.len() as u32); + } + } +} diff --git a/framework_crates/bones_wgpu_renderer/src/texture.rs b/framework_crates/bones_wgpu_renderer/src/texture.rs index f8d40b0f4b..1b4d96858e 100644 --- a/framework_crates/bones_wgpu_renderer/src/texture.rs +++ b/framework_crates/bones_wgpu_renderer/src/texture.rs @@ -42,7 +42,9 @@ impl Texture { sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::COPY_SRC, view_formats: &[], }); diff --git a/framework_crates/bones_wgpu_renderer/src/texture_file.rs b/framework_crates/bones_wgpu_renderer/src/texture_file.rs new file mode 100644 index 0000000000..788f9f9e5d --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/texture_file.rs @@ -0,0 +1,82 @@ +use image::{ImageBuffer, Rgba}; + +#[allow(unused)] +pub fn dump_texture_to_png( + device: &wgpu::Device, + queue: &wgpu::Queue, + texture: &wgpu::Texture, + size: (u32, u32), + path: &std::path::Path, +) -> anyhow::Result<()> { + let mut encoder = device.create_command_encoder(&Default::default()); + + let (width, height) = size; + + // 1) Create a buffer large enough to hold the RGBA8 data, + // with COPY_DST so we can copy into it + let pixel_size = 4; // RGBA8 + let padded_bytes_per_row = { + // GPU requires rows aligned to 256 bytes: + let unpadded = pixel_size * width as usize; + ((unpadded + 255) / 256) * 256 + }; + let output_buffer_size = (padded_bytes_per_row * height as usize) as u64; + + let dst_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("texture-readback-buffer"), + size: output_buffer_size, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ, + mapped_at_creation: false, + }); + + // 2) Copy the texture to the buffer + encoder.copy_texture_to_buffer( + wgpu::ImageCopyTexture { + texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + wgpu::ImageCopyBuffer { + buffer: &dst_buffer, + layout: wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: Some(padded_bytes_per_row as u32), + rows_per_image: Some(height), + }, + }, + wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + ); + + // 3) Submit the copy command and wait for it to finish + queue.submit(Some(encoder.finish())); + device.poll(wgpu::Maintain::Wait); + + // 4) Map the buffer and read its contents + let slice = dst_buffer.slice(..); + slice.map_async(wgpu::MapMode::Read, |res| res.unwrap()); + // wait for the mapping to finish + device.poll(wgpu::Maintain::Wait); + let data = slice.get_mapped_range(); + + // 5) Demultiplex rows (drop the padding) into a contiguous Vec + let mut pixels = Vec::with_capacity((pixel_size * width as usize * height as usize) as usize); + for row in 0..height as usize { + let offset = row * padded_bytes_per_row; + pixels.extend_from_slice(&data[offset..offset + (pixel_size * width as usize)]); + } + // Unmap so the buffer can be reused/dropped + drop(data); + dst_buffer.unmap(); + + // 6) Encode & save with the `image` crate + let img: ImageBuffer, _> = ImageBuffer::from_raw(width, height, pixels) + .expect("buffer size should match width*height*4"); + img.save(path)?; + + Ok(()) +} diff --git a/framework_crates/bones_wgpu_renderer/src/uniforms.rs b/framework_crates/bones_wgpu_renderer/src/uniforms.rs new file mode 100644 index 0000000000..40e70c1617 --- /dev/null +++ b/framework_crates/bones_wgpu_renderer/src/uniforms.rs @@ -0,0 +1,64 @@ +#[derive(bones_schema::HasSchema)] +#[repr(C)] +#[schema(opaque, no_default, no_clone)] +pub struct RenderBuffers { + // Instance data (updated per entity) + pub base: wgpu::Buffer, + pub sprite_flags: wgpu::Buffer, + pub atlas_data: wgpu::Buffer, +} + +impl RenderBuffers { + pub fn new(device: &wgpu::Device) -> Self { + // Initialize with empty data + let initial_size = 1024; // Starting capacity + let buffer_desc = |usage| wgpu::BufferDescriptor { + label: None, + size: initial_size as u64, + usage, + mapped_at_creation: false, + }; + + Self { + base: device.create_buffer(&buffer_desc( + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + )), + sprite_flags: device.create_buffer(&buffer_desc( + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + )), + atlas_data: device.create_buffer(&buffer_desc( + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + )), + } + } +} + +// Common to ALL renderable types +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct BaseInstance { + pub transform: [[f32; 4]; 4], // Mat4 + pub entity_type: u32, // 0 = sprite, 1 = atlas, 2 = path2d + pub color: [f32; 4], +} + +// Sprite/Atlas-specific extensions +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct SpriteFlags { + pub flip: [u32; 2], // Packed as bits: 0x1 = flip_x, 0x2 = flip_y + pub uv_min: [f32; 2], // Atlas UV coordinates (min_x, min_y) + pub uv_max: [f32; 2], // Atlas UV coordinates (max_x, max_y) +} + +// Atlas-specific data +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct AtlasData { + pub tile_size: [f32; 2], + pub image_size: [f32; 2], + pub padding: [f32; 2], + pub offset: [f32; 2], + pub columns: u32, + pub index: u32, +} From 27b0d949baf2a4a6f8c3713afeb6a149e941a868 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Tue, 29 Jul 2025 18:01:02 -0300 Subject: [PATCH 15/16] Fixed sprite placement --- Cargo.lock | 1907 ++--------------- .../CHANGELOG.md | 0 .../Cargo.toml | 0 .../src/convert.rs | 0 .../src/debug.rs | 0 .../src/input.rs | 0 .../src/lib.rs | 0 .../src/render.rs | 0 .../src/rumble.rs | 0 .../src/storage.rs | 0 .../src/ui.rs | 0 demos/asset_packs/Cargo.toml | 2 +- demos/asset_packs/src/main.rs | 4 +- demos/assets_minimal/Cargo.toml | 2 +- demos/assets_minimal/src/main.rs | 4 +- demos/features/Cargo.toml | 2 +- demos/features/src/main.rs | 8 +- demos/hello_world/Cargo.toml | 2 +- demos/hello_world/src/main.rs | 4 +- demos/scripting/Cargo.toml | 2 +- demos/scripting/src/main.rs | 6 +- .../bones_wgpu_renderer/Cargo.toml | 3 +- .../bones_wgpu_renderer/src/atlas_sprite.wgsl | 61 +- .../bones_wgpu_renderer/src/sprite.rs | 2 +- 24 files changed, 250 insertions(+), 1759 deletions(-) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/CHANGELOG.md (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/Cargo.toml (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/convert.rs (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/debug.rs (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/input.rs (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/lib.rs (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/render.rs (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/rumble.rs (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/storage.rs (100%) rename {framework_crates/bones_bevy_renderer => bones_bevy_renderer}/src/ui.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 4cac37bb87..21372fc031 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,59 +18,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" -[[package]] -name = "accesskit" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" - -[[package]] -name = "accesskit_consumer" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bb4d9e4772fe0d47df57d0d5dbe5d85dd05e2f37ae1ddb6b105e76be58fb00" -dependencies = [ - "accesskit", -] - -[[package]] -name = "accesskit_macos" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" -dependencies = [ - "accesskit", - "accesskit_consumer", - "objc2 0.3.0-beta.3.patch-leaks.3", - "once_cell", -] - -[[package]] -name = "accesskit_windows" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31" -dependencies = [ - "accesskit", - "accesskit_consumer", - "arrayvec", - "once_cell", - "paste", - "windows 0.48.0", -] - -[[package]] -name = "accesskit_winit" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825d23acee1bd6d25cbaa3ca6ed6e73faf24122a774ec33d52c5c86c6ab423c0" -dependencies = [ - "accesskit", - "accesskit_macos", - "accesskit_windows", - "winit 0.28.7", -] - [[package]] name = "acto" version = "0.7.1" @@ -152,7 +99,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.6.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -167,24 +114,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "android-activity" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" -dependencies = [ - "android-properties", - "bitflags 1.3.2", - "cc", - "jni-sys", - "libc", - "log", - "ndk 0.7.0", - "ndk-context", - "ndk-sys 0.4.1+23.1.7779620", - "num_enum 0.6.1", -] - [[package]] name = "android-activity" version = "0.6.0" @@ -192,7 +121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.6.0", + "bitflags 2.9.1", "cc", "cesu8", "jni 0.21.1", @@ -202,7 +131,7 @@ dependencies = [ "ndk 0.9.0", "ndk-context", "ndk-sys 0.6.0+11769913", - "num_enum 0.7.3", + "num_enum", "thiserror 1.0.69", ] @@ -218,12 +147,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" -[[package]] -name = "android_log-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -294,15 +217,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d9f7083455f1a474276ccd32374958d2cb591024aac45101c7623b10271347" -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - [[package]] name = "arboard" version = "3.4.0" @@ -310,14 +224,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" dependencies = [ "clipboard-win", - "core-graphics 0.23.2", - "image 0.25.2", "log", - "objc2 0.5.2", + "objc2", "objc2-app-kit", "objc2-foundation", "parking_lot", - "windows-sys 0.48.0", "x11rb", ] @@ -339,22 +250,13 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" -[[package]] -name = "ash" -version = "0.37.3+1.3.251" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" -dependencies = [ - "libloading 0.7.4", -] - [[package]] name = "ash" version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.8.5", + "libloading", ] [[package]] @@ -518,12 +420,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -536,154 +432,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bevy" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c6d3ec4f89e85294dc97334c5b271ddc301fdf67ac9bb994fe44d9273e6ed7" -dependencies = [ - "bevy_internal", -] - -[[package]] -name = "bevy_a11y" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132c9e35a77c5395951f6d25fa2c52ee92296353426df4f961e60f3ff47e2e42" -dependencies = [ - "accesskit", - "bevy_app", - "bevy_derive", - "bevy_ecs 0.11.3", -] - -[[package]] -name = "bevy_app" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557a7d59e1e16892d7544fc37316506ee598cb5310ef0365125a30783c11531" -dependencies = [ - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "downcast-rs", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "bevy_asset" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9714af523da4cdf58c42a317e5ed40349708ad954a18533991fd64c8ae0a6f68" -dependencies = [ - "anyhow", - "async-channel", - "bevy_app", - "bevy_diagnostic", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "bevy_winit", - "crossbeam-channel", - "downcast-rs", - "fastrand 1.9.0", - "js-sys", - "parking_lot", - "serde", - "thiserror 1.0.69", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "bevy_core" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5272321be5fcf5ce2fb16023bc825bb10dfcb71611117296537181ce950f48" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "bytemuck", -] - -[[package]] -name = "bevy_core_pipeline" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67382fa9c96ce4f4e5833ed7cedd9886844a8f3284b4a717bd4ac738dcdea0c3" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "bitflags 2.6.0", - "radsort", - "serde", -] - -[[package]] -name = "bevy_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44e4e2784a81430199e4157e02903a987a32127c773985506f020e7d501b62e" -dependencies = [ - "bevy_macro_utils 0.11.3", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "bevy_diagnostic" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6babb230dc383c98fdfc9603e3a7a2a49e1e2879dbe8291059ef37dca897932e" -dependencies = [ - "bevy_app", - "bevy_core", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_time", - "bevy_utils 0.11.3", - "sysinfo", -] - -[[package]] -name = "bevy_ecs" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266144b36df7e834d5198049e037ecdf2a2310a76ce39ed937d1b0a6a2c4e8c6" -dependencies = [ - "async-channel", - "bevy_ecs_macros 0.11.3", - "bevy_ptr 0.11.3", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "downcast-rs", - "event-listener 2.5.3", - "fixedbitset", - "rustc-hash 1.1.0", - "serde", - "thiserror 1.0.69", - "thread_local", -] - [[package]] name = "bevy_ecs" version = "0.12.1" @@ -691,11 +439,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709fbd22f81fb681534cd913c41e1cd18b17143368743281195d7f024b61aea" dependencies = [ "async-channel", - "bevy_ecs_macros 0.12.1", - "bevy_ptr 0.12.1", - "bevy_reflect 0.12.1", + "bevy_ecs_macros", + "bevy_ptr", + "bevy_reflect", "bevy_tasks 0.12.1", - "bevy_utils 0.12.1", + "bevy_utils", "downcast-rs", "event-listener 2.5.3", "fixedbitset", @@ -705,162 +453,18 @@ dependencies = [ "thread_local", ] -[[package]] -name = "bevy_ecs_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7157a9c3be038d5008ee3f114feb6cf6b39c1d3d32ee21a7cacb8f81fccdfa80" -dependencies = [ - "bevy_macro_utils 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "bevy_ecs_macros" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8843aa489f159f25cdcd9fee75cd7d221a7098a71eaa72cb2d6b40ac4e3f1ba" dependencies = [ - "bevy_macro_utils 0.12.1", + "bevy_macro_utils", "proc-macro2", "quote", "syn 2.0.90", ] -[[package]] -name = "bevy_egui" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1c1f6ad293c60fd8559c4502cda5e832e92b0e0f3d994929b33f24d4352d70" -dependencies = [ - "arboard", - "bevy", - "egui 0.23.0", - "thread_local", - "webbrowser 0.8.15", -] - -[[package]] -name = "bevy_encase_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ac0f55ad6bca1be7b0f35bbd5fc95ed3d31e4e9db158fee8e5327f59006001" -dependencies = [ - "bevy_macro_utils 0.11.3", - "encase_derive_impl", -] - -[[package]] -name = "bevy_gizmos" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e286a3e7276431963f4aa29165ea5429fa7dbbc6d5c5ba0c531e7dd44ecc88a2" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_core_pipeline", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_sprite", - "bevy_transform", - "bevy_utils 0.11.3", -] - -[[package]] -name = "bevy_hierarchy" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103f8f58416ac6799b8c7f0b418f1fac9eba44fa924df3b0e16b09256b897e3d" -dependencies = [ - "bevy_app", - "bevy_core", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "smallvec", -] - -[[package]] -name = "bevy_input" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbd935401101ac8003f3c3aea70788c65ad03f7a32716a10608bedda7a648bc" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "thiserror 1.0.69", -] - -[[package]] -name = "bevy_internal" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e35a9b2bd29aa784b3cc416bcbf2a298f69f00ca51fd042ea39d9af7fad37e" -dependencies = [ - "bevy_a11y", - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_core_pipeline", - "bevy_derive", - "bevy_diagnostic", - "bevy_ecs 0.11.3", - "bevy_gizmos", - "bevy_hierarchy", - "bevy_input", - "bevy_log", - "bevy_math", - "bevy_pbr", - "bevy_ptr 0.11.3", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_scene", - "bevy_sprite", - "bevy_tasks 0.11.3", - "bevy_time", - "bevy_transform", - "bevy_utils 0.11.3", - "bevy_window", - "bevy_winit", -] - -[[package]] -name = "bevy_log" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dcc615ff4f617b06c3f9522fca3c55d56f9644db293318f8ab68fcdea5d4fe" -dependencies = [ - "android_log-sys", - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_utils 0.11.3", - "console_error_panic_hook", - "tracing-log 0.1.4", - "tracing-subscriber", - "tracing-wasm", -] - -[[package]] -name = "bevy_macro_utils" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ddc18d489b4e57832d4958cde7cd2f349f0ad91e5892ac9e2f2ee16546b981" -dependencies = [ - "quote", - "rustc-hash 1.1.0", - "syn 2.0.90", - "toml_edit 0.19.15", -] - [[package]] name = "bevy_macro_utils" version = "0.12.1" @@ -874,242 +478,40 @@ dependencies = [ "toml_edit 0.20.7", ] -[[package]] -name = "bevy_math" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78286a81fead796dc4b45ab14f4f02fe29a94423d3587bcfef872b2a8e0a474b" -dependencies = [ - "glam 0.24.2", - "serde", -] - -[[package]] -name = "bevy_mikktspace" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfc2a21ea47970a9b1f0f4735af3256a8f204815bd756110051d10f9d909497" -dependencies = [ - "glam 0.24.2", -] - -[[package]] -name = "bevy_pbr" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ca796a619e61cd43a0a3b11fde54644f7f0732a1fba1eef5d406248c6eba85" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core_pipeline", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "bevy_window", - "bitflags 2.6.0", - "bytemuck", - "naga_oil", - "radsort", -] - -[[package]] -name = "bevy_prototype_lyon" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e347c16caede05dc5f774ba388cefeef0ab558a5601fc6b5ffd6606bef77308" -dependencies = [ - "bevy", - "lyon_algorithms", - "lyon_tessellation", - "svgtypes", -] - -[[package]] -name = "bevy_ptr" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c7586401a46f7d8e436028225c1df5288f2e0082d066b247a82466fea155c6" - [[package]] name = "bevy_ptr" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77ec20c8fafcdc196508ef5ccb4f0400a8d193cb61f7b14a36ed9a25ad423cf" -[[package]] -name = "bevy_reflect" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0778197a1eb3e095a71417c74b7152ede02975cdc95b5ea4ddc5251ed00a2eb5" -dependencies = [ - "bevy_math", - "bevy_ptr 0.11.3", - "bevy_reflect_derive 0.11.3", - "bevy_utils 0.11.3", - "downcast-rs", - "erased-serde 0.3.31", - "glam 0.24.2", - "once_cell", - "parking_lot", - "serde", - "smallvec", - "smol_str 0.2.2", - "thiserror 1.0.69", -] - [[package]] name = "bevy_reflect" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7921f15fc944c9c8ad01d7dbcea6505b8909c6655cd9382bab1407181556038" dependencies = [ - "bevy_ptr 0.12.1", - "bevy_reflect_derive 0.12.1", - "bevy_utils 0.12.1", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", "downcast-rs", "erased-serde 0.3.31", "serde", "thiserror 1.0.69", ] -[[package]] -name = "bevy_reflect_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342a4b2d09db22c48607d23ad59a056aff1ee004549050a51d490d375ba29528" -dependencies = [ - "bevy_macro_utils 0.11.3", - "bit-set 0.5.3", - "proc-macro2", - "quote", - "syn 2.0.90", - "uuid", -] - [[package]] name = "bevy_reflect_derive" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4a8c5475f216e751ef4452a1306b00711f33d2d04d9f149e4c845dfeb6753a0" dependencies = [ - "bevy_macro_utils 0.12.1", + "bevy_macro_utils", "proc-macro2", "quote", "syn 2.0.90", "uuid", ] -[[package]] -name = "bevy_render" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39df4824b760928c27afc7b00fb649c7a63c9d76661ab014ff5c86537ee906cb" -dependencies = [ - "anyhow", - "async-channel", - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_encase_derive", - "bevy_hierarchy", - "bevy_log", - "bevy_math", - "bevy_mikktspace", - "bevy_reflect 0.11.3", - "bevy_render_macros", - "bevy_tasks 0.11.3", - "bevy_time", - "bevy_transform", - "bevy_utils 0.11.3", - "bevy_window", - "bitflags 2.6.0", - "bytemuck", - "codespan-reporting", - "downcast-rs", - "encase", - "futures-lite 1.13.0", - "hexasphere", - "image 0.24.9", - "js-sys", - "naga 0.12.3", - "naga_oil", - "parking_lot", - "regex", - "serde", - "smallvec", - "thiserror 1.0.69", - "thread_local", - "wasm-bindgen", - "web-sys", - "wgpu 0.16.3", - "wgpu-hal 0.16.2", -] - -[[package]] -name = "bevy_render_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd08c740aac73363e32fb45af869b10cec65bcb76fe3e6cd0f8f7eebf4c36c9" -dependencies = [ - "bevy_macro_utils 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "bevy_scene" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd47e1263506153bef3a8be97fe2d856f206d315668c4f97510ca6cc181d9681" -dependencies = [ - "anyhow", - "bevy_app", - "bevy_asset", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_hierarchy", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "ron", - "serde", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "bevy_sprite" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a8ca824fad75c6ef74cfbbba0a4ce3ccc435fa23d6bf3f003f260548813397" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core_pipeline", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "bitflags 2.6.0", - "bytemuck", - "fixedbitset", - "guillotiere", - "rectangle-pack", - "thiserror 1.0.69", -] - [[package]] name = "bevy_tasks" version = "0.11.3" @@ -1138,50 +540,6 @@ dependencies = [ "wasm-bindgen-futures", ] -[[package]] -name = "bevy_time" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d58d6dbae9c8225d8c0e0f04d2c5dbb71d22adc01ecd5ab3cebc364139e4a6d" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "crossbeam-channel", - "thiserror 1.0.69", -] - -[[package]] -name = "bevy_transform" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b0ac0149a57cd846cb357a35fc99286f9848e53d4481954608ac9552ed2d4" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_hierarchy", - "bevy_math", - "bevy_reflect 0.11.3", -] - -[[package]] -name = "bevy_utils" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d9484e32434ea84dc548cff246ce0c6f756c1336f5ea03f24ac120a48595c7" -dependencies = [ - "ahash", - "bevy_utils_proc_macros 0.11.3", - "getrandom", - "hashbrown 0.14.5", - "instant", - "petgraph", - "thiserror 1.0.69", - "tracing", - "uuid", -] - [[package]] name = "bevy_utils" version = "0.12.1" @@ -1189,7 +547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7915222f4a08ccc782e08d10b751b42e5f9d786e697d0cb3fd09333cb7e8b6ea" dependencies = [ "ahash", - "bevy_utils_proc_macros 0.12.1", + "bevy_utils_proc_macros", "getrandom", "hashbrown 0.14.5", "instant", @@ -1200,17 +558,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "bevy_utils_proc_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5391b242c36f556db01d5891444730c83aa9dd648b6a8fd2b755d22cb3bddb57" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "bevy_utils_proc_macros" version = "0.12.1" @@ -1222,46 +569,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "bevy_window" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd584c0da7c4ada6557b09f57f30fb7cff21ccedc641473fc391574b4c9b7944" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_input", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "raw-window-handle 0.5.2", -] - -[[package]] -name = "bevy_winit" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdc044abdb95790c20053e6326760f0a2985f0dcd78613d397bf35f16039d53" -dependencies = [ - "accesskit_winit", - "approx", - "bevy_a11y", - "bevy_app", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_hierarchy", - "bevy_input", - "bevy_math", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "bevy_window", - "crossbeam-channel", - "raw-window-handle 0.5.2", - "wasm-bindgen", - "web-sys", - "winit 0.28.7", -] - [[package]] name = "bincode" version = "1.3.3" @@ -1277,7 +584,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools", @@ -1291,30 +598,15 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[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", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -1345,12 +637,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -dependencies = [ - "serde", -] +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bitset-core" @@ -1398,32 +687,13 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-sys" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" -dependencies = [ - "objc-sys 0.2.0-beta.2", -] - -[[package]] -name = "block2" -version = "0.2.0-alpha.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" -dependencies = [ - "block-sys", - "objc2-encode 2.0.0-pre.2", -] - [[package]] name = "block2" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2 0.5.2", + "objc2", ] [[package]] @@ -1460,22 +730,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "bones_bevy_renderer" -version = "0.4.0" -dependencies = [ - "anyhow", - "bevy", - "bevy_egui", - "bevy_prototype_lyon", - "bones_framework", - "directories", - "glam 0.24.2", - "serde", - "serde_yaml", - "web-sys", -] - [[package]] name = "bones_ecs" version = "0.4.0" @@ -1498,7 +752,7 @@ dependencies = [ name = "bones_ecs_macros" version = "0.4.0" dependencies = [ - "bevy_ecs 0.12.1", + "bevy_ecs", "bones_ecs", "bones_ecs_macros_core", "bones_schema", @@ -1532,7 +786,7 @@ dependencies = [ "csscolorparser", "directories", "document-features", - "egui 0.30.0", + "egui", "egui_plot", "either", "fluent", @@ -1542,7 +796,7 @@ dependencies = [ "gilrs", "glam 0.24.2", "hex", - "image 0.24.9", + "image", "instant", "intl-memoizer", "iroh", @@ -1693,19 +947,19 @@ dependencies = [ "bytemuck", "crossbeam-channel", "directories", - "egui 0.30.0", + "egui", "egui-wgpu", "egui-winit", "env_logger", "guillotiere", - "image 0.24.9", + "image", "log", "lyon", "pollster", "serde", "serde_yaml", - "wgpu 23.0.1", - "winit 0.30.9", + "wgpu", + "winit", ] [[package]] @@ -1761,12 +1015,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.7.1" @@ -1779,7 +1027,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "log", "polling 3.7.4", "rustix", @@ -1886,7 +1134,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading 0.8.5", + "libloading", ] [[package]] @@ -1966,12 +1214,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" -[[package]] -name = "com-rs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" - [[package]] name = "combine" version = "4.6.7" @@ -1991,49 +1233,18 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const_panic" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7782af8f90fe69a4bb41e460abe1727d493403d8b2cc43201a3a3e906b24379f" - -[[package]] -name = "const_soft_float" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" - [[package]] name = "constant_time_eq" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "constgebra" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1aaf9b65849a68662ac6c0810c8893a765c960b907dd7cfab9c4a50bf764fbc" -dependencies = [ - "const_soft_float", -] - [[package]] name = "cordyceps" version = "0.3.2" @@ -2070,19 +1281,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types 0.3.2", - "libc", -] - [[package]] name = "core-graphics" version = "0.23.2" @@ -2092,7 +1290,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -2326,17 +1524,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "d3d12" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" -dependencies = [ - "bitflags 1.3.2", - "libloading 0.7.4", - "winapi", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -2380,24 +1567,24 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" name = "demo_asset_packs" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] name = "demo_assets_minimal" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] name = "demo_features" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] @@ -2412,8 +1599,8 @@ dependencies = [ name = "demo_hello_world" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] @@ -2428,8 +1615,8 @@ dependencies = [ name = "demo_scripting" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] @@ -2567,7 +1754,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.5", + "libloading", ] [[package]] @@ -2628,15 +1815,6 @@ dependencies = [ "spki", ] -[[package]] -name = "ecolor" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf4e52dbbb615cfd30cf5a5265335c217b5fd8d669593cea74a517d9c605af" -dependencies = [ - "bytemuck", -] - [[package]] name = "ecolor" version = "0.30.0" @@ -2644,7 +1822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d72e9c39f6e11a2e922d04a34ec5e7ef522ea3f5a1acfca7a19d16ad5fe50f5" dependencies = [ "bytemuck", - "emath 0.30.0", + "emath", ] [[package]] @@ -2673,17 +1851,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "egui" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd69fed5fcf4fbb8225b24e80ea6193b61e17a625db105ef0c4d71dde6eb8b7" -dependencies = [ - "ahash", - "epaint 0.23.0", - "nohash-hasher", -] - [[package]] name = "egui" version = "0.30.0" @@ -2691,8 +1858,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "252d52224d35be1535d7fd1d6139ce071fb42c9097773e79f7665604f5596b5e" dependencies = [ "ahash", - "emath 0.30.0", - "epaint 0.30.0", + "emath", + "epaint", "log", "nohash-hasher", "profiling", @@ -2707,14 +1874,14 @@ dependencies = [ "ahash", "bytemuck", "document-features", - "egui 0.30.0", - "epaint 0.30.0", + "egui", + "epaint", "log", "profiling", "thiserror 1.0.69", "type-map", "web-time", - "wgpu 23.0.1", + "wgpu", ] [[package]] @@ -2725,14 +1892,14 @@ checksum = "1e84c2919cd9f3a38a91e8f84ac6a245c19251fd95226ed9fae61d5ea564fce3" dependencies = [ "ahash", "arboard", - "egui 0.30.0", + "egui", "log", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "smithay-clipboard", "web-time", - "webbrowser 1.0.3", - "winit 0.30.9", + "webbrowser", + "winit", ] [[package]] @@ -2742,8 +1909,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c226cae80a6ee10c4d3aaf9e33bd9e9b2f1c0116b6036bdc2a1cfc9d2d0dcc10" dependencies = [ "ahash", - "egui 0.30.0", - "emath 0.30.0", + "egui", + "emath", ] [[package]] @@ -2794,15 +1961,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "emath" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef2b29de53074e575c18b694167ccbe6e5191f7b25fe65175a0d905a32eeec0" -dependencies = [ - "bytemuck", -] - [[package]] name = "emath" version = "0.30.0" @@ -2824,38 +1982,6 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" -[[package]] -name = "encase" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fce2eeef77fd4a293a54b62aa00ac9daebfbcda4bf8998c5a815635b004aa1c" -dependencies = [ - "const_panic", - "encase_derive", - "glam 0.24.2", - "thiserror 1.0.69", -] - -[[package]] -name = "encase_derive" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e520cde08cbf4f7cc097f61573ec06ce467019803de8ae82fb2823fa1554a0e" -dependencies = [ - "encase_derive_impl", -] - -[[package]] -name = "encase_derive_impl" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "encoding_rs" version = "0.8.34" @@ -2920,21 +2046,6 @@ dependencies = [ "log", ] -[[package]] -name = "epaint" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58067b840d009143934d91d8dcb8ded054d8301d7c11a517ace0a99bb1e1595e" -dependencies = [ - "ab_glyph", - "ahash", - "bytemuck", - "ecolor 0.23.0", - "emath 0.23.0", - "nohash-hasher", - "parking_lot", -] - [[package]] name = "epaint" version = "0.30.0" @@ -2944,8 +2055,8 @@ dependencies = [ "ab_glyph", "ahash", "bytemuck", - "ecolor 0.30.0", - "emath 0.30.0", + "ecolor", + "emath", "epaint_default_fonts", "log", "nohash-hasher", @@ -3201,15 +2312,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - [[package]] name = "foreign-types" version = "0.5.0" @@ -3217,7 +2319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -3231,12 +2333,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -3623,10 +2719,6 @@ name = "glam" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" -dependencies = [ - "bytemuck", - "serde", -] [[package]] name = "glam" @@ -3643,18 +2735,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "glow" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "glow" version = "0.14.2" @@ -3697,33 +2777,14 @@ dependencies = [ "spinning_top", ] -[[package]] -name = "gpu-alloc" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" -dependencies = [ - "bitflags 1.3.2", - "gpu-alloc-types 0.2.0", -] - [[package]] name = "gpu-alloc" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.6.0", - "gpu-alloc-types 0.3.0", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" -dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", + "gpu-alloc-types", ] [[package]] @@ -3732,20 +2793,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "gpu-allocator" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" -dependencies = [ - "backtrace", - "log", - "thiserror 1.0.69", - "winapi", - "windows 0.44.0", + "bitflags 2.9.1", ] [[package]] @@ -3760,44 +2808,24 @@ dependencies = [ "windows 0.58.0", ] -[[package]] -name = "gpu-descriptor" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" -dependencies = [ - "bitflags 2.6.0", - "gpu-descriptor-types 0.1.2", - "hashbrown 0.14.5", -] - [[package]] name = "gpu-descriptor" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ - "bitflags 2.6.0", - "gpu-descriptor-types 0.2.0", + "bitflags 2.9.1", + "gpu-descriptor-types", "hashbrown 0.15.2", ] -[[package]] -name = "gpu-descriptor-types" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" -dependencies = [ - "bitflags 2.6.0", -] - [[package]] name = "gpu-descriptor-types" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] @@ -3833,7 +2861,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.5.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -3859,12 +2887,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -3885,21 +2907,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "hassle-rs" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" -dependencies = [ - "bitflags 1.3.2", - "com-rs", - "libc", - "libloading 0.7.4", - "thiserror 1.0.69", - "widestring", - "winapi", -] - [[package]] name = "heapless" version = "0.7.17" @@ -3938,16 +2945,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hexasphere" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb3df16a7bcb1b5bc092abd55e14f77ca70aea14445026e264586fc62889a10" -dependencies = [ - "constgebra", - "glam 0.24.2", -] - [[package]] name = "hexf-parse" version = "0.2.1" @@ -4301,29 +3298,6 @@ dependencies = [ "tiff", ] -[[package]] -name = "image" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" -dependencies = [ - "bytemuck", - "byteorder-lite", - "num-traits", - "png", - "tiff", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.5.0" @@ -4351,7 +3325,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "inotify-sys", "libc", ] @@ -4441,7 +3415,7 @@ checksum = "80b15215aea8d0367fefb9264521e4a251dc4e113896a3d765f530378518188f" dependencies = [ "anyhow", "backoff", - "base64 0.22.1", + "base64", "bytes", "der", "derive_more", @@ -4475,7 +3449,7 @@ dependencies = [ "netlink-packet-route 0.21.0", "netlink-sys", "netwatch", - "num_enum 0.7.3", + "num_enum", "once_cell", "parking_lot", "pin-project", @@ -4660,7 +3634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28a0d0d7317795a01caa47ffdaeec84211d1b394530bdb31dbe15f642e58bcff" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bytes", "clap", "derive_more", @@ -4682,7 +3656,7 @@ dependencies = [ "iroh-quinn", "iroh-quinn-proto", "libc", - "num_enum 0.7.3", + "num_enum", "once_cell", "parking_lot", "pin-project", @@ -4799,17 +3773,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "khronos-egl" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" -dependencies = [ - "libc", - "libloading 0.7.4", - "pkg-config", -] - [[package]] name = "khronos-egl" version = "6.0.0" @@ -4817,7 +3780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.5", + "libloading", "pkg-config", ] @@ -4890,16 +3853,6 @@ version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libloading" version = "0.8.5" @@ -4922,7 +3875,7 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "libc", "redox_syscall 0.4.1", ] @@ -4933,7 +3886,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "libc", "redox_syscall 0.5.3", ] @@ -5161,30 +4114,16 @@ dependencies = [ "libc", ] -[[package]] -name = "metal" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-graphics-types", - "foreign-types 0.3.2", - "log", - "objc", -] - [[package]] name = "metal" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "block", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "log", "objc", "paste", @@ -5251,27 +4190,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "naga" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" -dependencies = [ - "bit-set 0.5.3", - "bitflags 1.3.2", - "codespan-reporting", - "hexf-parse", - "indexmap 1.9.3", - "log", - "num-traits", - "pp-rs", - "rustc-hash 1.1.0", - "spirv 0.2.0+1.5.4", - "termcolor", - "thiserror 1.0.69", - "unicode-xid", -] - [[package]] name = "naga" version = "23.1.0" @@ -5279,40 +4197,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", - "bit-set 0.8.0", - "bitflags 2.6.0", + "bit-set", + "bitflags 2.9.1", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.5.0", + "indexmap", "log", "rustc-hash 1.1.0", - "spirv 0.3.0+sdk-1.3.268.0", + "spirv", "termcolor", "thiserror 1.0.69", "unicode-xid", ] -[[package]] -name = "naga_oil" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be942a5c21c58b9b0bf4d9b99db3634ddb7a916f8e1d1d0b71820cc4150e56b" -dependencies = [ - "bit-set 0.5.3", - "codespan-reporting", - "data-encoding", - "indexmap 1.9.3", - "naga 0.12.3", - "once_cell", - "regex", - "regex-syntax 0.6.29", - "rustc-hash 1.1.0", - "thiserror 1.0.69", - "tracing", - "unicode-ident", -] - [[package]] name = "nanorand" version = "0.7.0" @@ -5322,31 +4220,17 @@ dependencies = [ "getrandom", ] -[[package]] -name = "ndk" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys 0.4.1+23.1.7779620", - "num_enum 0.5.11", - "raw-window-handle 0.5.2", - "thiserror 1.0.69", -] - [[package]] name = "ndk" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.5.0+25.2.9519653", - "num_enum 0.7.3", + "num_enum", "thiserror 1.0.69", ] @@ -5356,12 +4240,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.6.0+11769913", - "num_enum 0.7.3", - "raw-window-handle 0.6.2", + "num_enum", + "raw-window-handle", "thiserror 1.0.69", ] @@ -5371,15 +4255,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-sys" -version = "0.4.1+23.1.7779620" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" -dependencies = [ - "jni-sys", -] - [[package]] name = "ndk-sys" version = "0.5.0+25.2.9519653" @@ -5461,7 +4336,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "483325d4bfef65699214858f097d504eb812c38ce7077d165f301ec406c3066e" dependencies = [ "anyhow", - "bitflags 2.6.0", + "bitflags 2.9.1", "byteorder", "libc", "log", @@ -5559,7 +4434,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -5570,7 +4445,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -5633,7 +4508,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -5646,15 +4521,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -5740,54 +4606,12 @@ dependencies = [ ] [[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive 0.5.11", -] - -[[package]] -name = "num_enum" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" -dependencies = [ - "num_enum_derive 0.6.1", -] - -[[package]] -name = "num_enum" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" -dependencies = [ - "num_enum_derive 0.7.3", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num_enum_derive" -version = "0.6.1" +name = "num_enum" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 2.0.90", + "num_enum_derive", ] [[package]] @@ -5796,7 +4620,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.90", @@ -5815,40 +4639,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] -[[package]] -name = "objc-sys" -version = "0.2.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" - [[package]] name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -[[package]] -name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" -dependencies = [ - "block2 0.2.0-alpha.6", - "objc-sys 0.2.0-beta.2", - "objc2-encode 2.0.0-pre.2", -] - [[package]] name = "objc2" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ - "objc-sys 0.3.5", - "objc2-encode 4.0.3", + "objc-sys", + "objc2-encode", ] [[package]] @@ -5857,10 +4663,10 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", + "bitflags 2.9.1", + "block2", "libc", - "objc2 0.5.2", + "objc2", "objc2-core-data", "objc2-core-image", "objc2-foundation", @@ -5873,9 +4679,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-core-location", "objc2-foundation", ] @@ -5886,8 +4692,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", + "block2", + "objc2", "objc2-foundation", ] @@ -5897,9 +4703,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-foundation", ] @@ -5909,8 +4715,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", + "block2", + "objc2", "objc2-foundation", "objc2-metal", ] @@ -5921,21 +4727,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", + "block2", + "objc2", "objc2-contacts", "objc2-foundation", ] -[[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" -dependencies = [ - "objc-sys 0.2.0-beta.2", -] - [[package]] name = "objc2-encode" version = "4.0.3" @@ -5948,11 +4745,11 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", + "bitflags 2.9.1", + "block2", "dispatch", "libc", - "objc2 0.5.2", + "objc2", ] [[package]] @@ -5961,8 +4758,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", + "block2", + "objc2", "objc2-app-kit", "objc2-foundation", ] @@ -5973,9 +4770,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-foundation", ] @@ -5985,9 +4782,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-foundation", "objc2-metal", ] @@ -5998,7 +4795,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "objc2 0.5.2", + "objc2", "objc2-foundation", ] @@ -6008,9 +4805,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", @@ -6029,8 +4826,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", + "block2", + "objc2", "objc2-foundation", ] @@ -6040,22 +4837,13 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-core-location", "objc2-foundation", ] -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - [[package]] name = "object" version = "0.36.4" @@ -6254,7 +5042,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.1", + "base64", "serde", ] @@ -6325,7 +5113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap", ] [[package]] @@ -6599,7 +5387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ea24e7552a28ee4a3478ae116c89080957d6816526d0a533bee6cd67048279" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bytes", "derive_more", "futures-lite 2.5.0", @@ -6608,7 +5396,7 @@ dependencies = [ "iroh-metrics", "libc", "netwatch", - "num_enum 0.7.3", + "num_enum", "rand", "serde", "smallvec", @@ -6652,15 +5440,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "pp-rs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" -dependencies = [ - "unicode-xid", -] - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -6729,16 +5508,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -6930,12 +5699,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radsort" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22" - [[package]] name = "rand" version = "0.8.5" @@ -6987,15 +5750,9 @@ version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "raw-window-handle" version = "0.6.2" @@ -7047,21 +5804,6 @@ dependencies = [ "yasna", ] -[[package]] -name = "rectangle-pack" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -7077,7 +5819,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] @@ -7153,7 +5895,7 @@ version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "futures-util", @@ -7233,18 +5975,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64 0.21.7", - "bitflags 2.6.0", - "serde", - "serde_derive", -] - [[package]] name = "rsa" version = "0.9.6" @@ -7344,7 +6074,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", @@ -7397,7 +6127,7 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -7555,7 +6285,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -7681,7 +6411,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.5.0", + "indexmap", "itoa", "ryu", "serde", @@ -7772,7 +6502,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01607fe2e61894468c6dc0b26103abb073fb08b79a3d9e4b6d76a1a341549958" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] @@ -7804,9 +6534,6 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] [[package]] name = "smithay-client-toolkit" @@ -7814,7 +6541,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -7829,7 +6556,7 @@ dependencies = [ "wayland-cursor", "wayland-protocols", "wayland-protocols-wlr", - "wayland-scanner 0.31.6", + "wayland-scanner", "xkeysym", ] @@ -7897,23 +6624,13 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spirv" -version = "0.2.0+1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" -dependencies = [ - "bitflags 1.3.2", - "num-traits", -] - [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] @@ -8055,7 +6772,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0adebf9fb8fba5c39ee34092b0383f247e4d1255b98fcffec94b4b797b85b677" dependencies = [ - "base64 0.22.1", + "base64", "bounded-integer", "byteorder", "crc", @@ -8101,15 +6818,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" -[[package]] -name = "svgtypes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" -dependencies = [ - "siphasher", -] - [[package]] name = "swarm-discovery" version = "0.2.1" @@ -8308,27 +7016,13 @@ dependencies = [ "libc", ] -[[package]] -name = "sysinfo" -version = "0.29.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "winapi", -] - [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -8545,7 +7239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3184e8e292a828dd4bca5b2a60aba830ec5ed873a66c9ebb6e65038fa649e827" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "chrono", "futures", "log", @@ -8655,24 +7349,13 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.5.0", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.5.0", + "indexmap", "toml_datetime", "winnow 0.5.40", ] @@ -8683,7 +7366,7 @@ version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.5.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -8763,17 +7446,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -8800,7 +7472,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] @@ -9073,7 +7745,7 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" dependencies = [ - "base64 0.22.1", + "base64", "flate2", "log", "once_cell", @@ -9296,10 +7968,10 @@ version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "rustix", "wayland-backend", - "wayland-scanner 0.31.6", + "wayland-scanner", ] [[package]] @@ -9308,7 +7980,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] @@ -9330,10 +8002,10 @@ version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", - "wayland-scanner 0.31.6", + "wayland-scanner", ] [[package]] @@ -9342,11 +8014,11 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ccaacc76703fefd6763022ac565b590fcade92202492381c95b2edfdf7d46b3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", - "wayland-scanner 0.31.6", + "wayland-scanner", ] [[package]] @@ -9355,22 +8027,11 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", - "wayland-scanner 0.31.6", -] - -[[package]] -name = "wayland-scanner" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" -dependencies = [ - "proc-macro2", - "quote", - "xml-rs", + "wayland-scanner", ] [[package]] @@ -9398,9 +8059,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -9416,36 +8077,19 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webbrowser" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" -dependencies = [ - "core-foundation 0.9.4", - "home", - "jni 0.21.1", - "log", - "ndk-context", - "objc", - "raw-window-handle 0.5.2", - "url", - "web-sys", -] - [[package]] name = "webbrowser" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" dependencies = [ - "block2 0.5.1", + "block2", "core-foundation 0.10.0", "home", "jni 0.21.1", "log", "ndk-context", - "objc2 0.5.2", + "objc2", "objc2-foundation", "url", "web-sys", @@ -9466,30 +8110,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "wgpu" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd" -dependencies = [ - "arrayvec", - "cfg-if", - "js-sys", - "log", - "naga 0.12.3", - "parking_lot", - "profiling", - "raw-window-handle 0.5.2", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core 0.16.1", - "wgpu-hal 0.16.2", - "wgpu-types 0.16.1", -] - [[package]] name = "wgpu" version = "23.0.1" @@ -9501,41 +8121,18 @@ dependencies = [ "document-features", "js-sys", "log", - "naga 23.1.0", + "naga", "parking_lot", "profiling", - "raw-window-handle 0.6.2", + "raw-window-handle", "smallvec", "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core 23.0.1", - "wgpu-hal 23.0.1", - "wgpu-types 23.0.0", -] - -[[package]] -name = "wgpu-core" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" -dependencies = [ - "arrayvec", - "bit-vec 0.6.3", - "bitflags 2.6.0", - "codespan-reporting", - "log", - "naga 0.12.3", - "parking_lot", - "profiling", - "raw-window-handle 0.5.2", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 1.0.69", - "web-sys", - "wgpu-hal 0.16.2", - "wgpu-types 0.16.1", + "wgpu-core", + "wgpu-hal", + "wgpu-types", ] [[package]] @@ -9545,64 +8142,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" dependencies = [ "arrayvec", - "bit-vec 0.8.0", - "bitflags 2.6.0", + "bit-vec", + "bitflags 2.9.1", "cfg_aliases 0.1.1", "document-features", - "indexmap 2.5.0", + "indexmap", "log", - "naga 23.1.0", + "naga", "once_cell", "parking_lot", "profiling", - "raw-window-handle 0.6.2", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 1.0.69", - "wgpu-hal 23.0.1", - "wgpu-types 23.0.0", -] - -[[package]] -name = "wgpu-hal" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash 0.37.3+1.3.251", - "bit-set 0.5.3", - "bitflags 2.6.0", - "block", - "core-graphics-types", - "d3d12", - "foreign-types 0.3.2", - "glow 0.12.3", - "gpu-alloc 0.5.4", - "gpu-allocator 0.22.0", - "gpu-descriptor 0.2.4", - "hassle-rs", - "js-sys", - "khronos-egl 4.1.0", - "libc", - "libloading 0.8.5", - "log", - "metal 0.24.0", - "naga 0.12.3", - "objc", - "parking_lot", - "profiling", - "range-alloc", - "raw-window-handle 0.5.2", - "renderdoc-sys", + "raw-window-handle", "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", - "wasm-bindgen", - "web-sys", - "wgpu-types 0.16.1", - "winapi", + "wgpu-hal", + "wgpu-types", ] [[package]] @@ -9613,61 +8168,50 @@ checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" dependencies = [ "android_system_properties", "arrayvec", - "ash 0.38.0+1.3.281", - "bit-set 0.8.0", - "bitflags 2.6.0", + "ash", + "bit-set", + "bitflags 2.9.1", "block", "bytemuck", "cfg_aliases 0.1.1", "core-graphics-types", - "glow 0.14.2", + "glow", "glutin_wgl_sys", - "gpu-alloc 0.6.0", - "gpu-allocator 0.27.0", - "gpu-descriptor 0.3.1", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", "js-sys", - "khronos-egl 6.0.0", + "khronos-egl", "libc", - "libloading 0.8.5", + "libloading", "log", - "metal 0.29.0", - "naga 23.1.0", + "metal", + "naga", "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", "parking_lot", "profiling", "range-alloc", - "raw-window-handle 0.6.2", + "raw-window-handle", "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", "wasm-bindgen", "web-sys", - "wgpu-types 23.0.0", + "wgpu-types", "windows 0.58.0", "windows-core 0.58.0", ] -[[package]] -name = "wgpu-types" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a" -dependencies = [ - "bitflags 2.6.0", - "js-sys", - "web-sys", -] - [[package]] name = "wgpu-types" version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "js-sys", "web-sys", ] @@ -9724,23 +8268,12 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-implement 0.48.0", - "windows-interface 0.48.0", "windows-targets 0.48.5", ] @@ -9799,24 +8332,13 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", + "windows-implement", + "windows-interface", "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] -[[package]] -name = "windows-implement" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -9828,17 +8350,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "windows-interface" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "windows-interface" version = "0.58.0" @@ -10103,36 +8614,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winit" -version = "0.28.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" -dependencies = [ - "android-activity 0.4.3", - "bitflags 1.3.2", - "cfg_aliases 0.1.1", - "core-foundation 0.9.4", - "core-graphics 0.22.3", - "dispatch", - "instant", - "libc", - "log", - "mio 0.8.11", - "ndk 0.7.0", - "objc2 0.3.0-beta.3.patch-leaks.3", - "once_cell", - "orbclient", - "percent-encoding", - "raw-window-handle 0.5.2", - "redox_syscall 0.3.5", - "wasm-bindgen", - "wayland-scanner 0.29.5", - "web-sys", - "windows-sys 0.45.0", - "x11-dl", -] - [[package]] name = "winit" version = "0.30.9" @@ -10140,30 +8621,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a809eacf18c8eca8b6635091543f02a5a06ddf3dad846398795460e6e0ae3cc0" dependencies = [ "ahash", - "android-activity 0.6.0", + "android-activity", "atomic-waker", - "bitflags 2.6.0", - "block2 0.5.1", + "bitflags 2.9.1", + "block2", "bytemuck", "calloop", "cfg_aliases 0.2.1", "concurrent-queue", "core-foundation 0.9.4", - "core-graphics 0.23.2", + "core-graphics", "cursor-icon", "dpi", "js-sys", "libc", "memmap2", "ndk 0.9.0", - "objc2 0.5.2", + "objc2", "objc2-app-kit", "objc2-foundation", "objc2-ui-kit", "orbclient", "percent-encoding", "pin-project", - "raw-window-handle 0.6.2", + "raw-window-handle", "redox_syscall 0.4.1", "rustix", "sctk-adwaita", @@ -10257,7 +8738,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.5", + "libloading", "once_cell", "rustix", "x11rb-protocol", @@ -10298,7 +8779,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "dlib", "log", "once_cell", diff --git a/framework_crates/bones_bevy_renderer/CHANGELOG.md b/bones_bevy_renderer/CHANGELOG.md similarity index 100% rename from framework_crates/bones_bevy_renderer/CHANGELOG.md rename to bones_bevy_renderer/CHANGELOG.md diff --git a/framework_crates/bones_bevy_renderer/Cargo.toml b/bones_bevy_renderer/Cargo.toml similarity index 100% rename from framework_crates/bones_bevy_renderer/Cargo.toml rename to bones_bevy_renderer/Cargo.toml diff --git a/framework_crates/bones_bevy_renderer/src/convert.rs b/bones_bevy_renderer/src/convert.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/convert.rs rename to bones_bevy_renderer/src/convert.rs diff --git a/framework_crates/bones_bevy_renderer/src/debug.rs b/bones_bevy_renderer/src/debug.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/debug.rs rename to bones_bevy_renderer/src/debug.rs diff --git a/framework_crates/bones_bevy_renderer/src/input.rs b/bones_bevy_renderer/src/input.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/input.rs rename to bones_bevy_renderer/src/input.rs diff --git a/framework_crates/bones_bevy_renderer/src/lib.rs b/bones_bevy_renderer/src/lib.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/lib.rs rename to bones_bevy_renderer/src/lib.rs diff --git a/framework_crates/bones_bevy_renderer/src/render.rs b/bones_bevy_renderer/src/render.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/render.rs rename to bones_bevy_renderer/src/render.rs diff --git a/framework_crates/bones_bevy_renderer/src/rumble.rs b/bones_bevy_renderer/src/rumble.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/rumble.rs rename to bones_bevy_renderer/src/rumble.rs diff --git a/framework_crates/bones_bevy_renderer/src/storage.rs b/bones_bevy_renderer/src/storage.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/storage.rs rename to bones_bevy_renderer/src/storage.rs diff --git a/framework_crates/bones_bevy_renderer/src/ui.rs b/bones_bevy_renderer/src/ui.rs similarity index 100% rename from framework_crates/bones_bevy_renderer/src/ui.rs rename to bones_bevy_renderer/src/ui.rs diff --git a/demos/asset_packs/Cargo.toml b/demos/asset_packs/Cargo.toml index c4b250027d..dc79658264 100644 --- a/demos/asset_packs/Cargo.toml +++ b/demos/asset_packs/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] bones_framework = { path = "../../framework_crates/bones_framework" } -bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } +bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/asset_packs/src/main.rs b/demos/asset_packs/src/main.rs index e2fb6fce95..686d9d4ae9 100644 --- a/demos/asset_packs/src/main.rs +++ b/demos/asset_packs/src/main.rs @@ -1,4 +1,4 @@ -use bones_bevy_renderer::BonesBevyRenderer; +use bones_wgpu_renderer::BonesWgpuRenderer; use bones_framework::prelude::*; // @@ -53,7 +53,7 @@ fn main() { // Add our menu system to the update stage .add_system_to_stage(Update, menu_system); - BonesBevyRenderer::new(game).app().run(); + BonesWgpuRenderer::new(game).run(); } /// System to render the home menu. diff --git a/demos/assets_minimal/Cargo.toml b/demos/assets_minimal/Cargo.toml index df333a9573..a60d02dd96 100644 --- a/demos/assets_minimal/Cargo.toml +++ b/demos/assets_minimal/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] bones_framework = { path = "../../framework_crates/bones_framework" } -bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } +bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/assets_minimal/src/main.rs b/demos/assets_minimal/src/main.rs index 092f0dd94c..d43451aff6 100644 --- a/demos/assets_minimal/src/main.rs +++ b/demos/assets_minimal/src/main.rs @@ -1,4 +1,4 @@ -use bones_bevy_renderer::BonesBevyRenderer; +use bones_wgpu_renderer::BonesWgpuRenderer; use bones_framework::prelude::*; // @@ -43,7 +43,7 @@ fn main() { // Add our menu system to the update stage .add_system_to_stage(Update, menu_system); - BonesBevyRenderer::new(game).app().run(); + BonesWgpuRenderer::new(game).run(); } /// System to render the home menu. diff --git a/demos/features/Cargo.toml b/demos/features/Cargo.toml index ec3e4425f7..85c3d4b044 100644 --- a/demos/features/Cargo.toml +++ b/demos/features/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] bones_framework = { path = "../../framework_crates/bones_framework" } -bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } +bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/features/src/main.rs b/demos/features/src/main.rs index c97a421f01..72261c5684 100644 --- a/demos/features/src/main.rs +++ b/demos/features/src/main.rs @@ -1,6 +1,6 @@ #![allow(clippy::too_many_arguments)] -use bones_bevy_renderer::{bevy::diagnostic::LogDiagnosticsPlugin, BonesBevyRenderer}; +use bones_wgpu_renderer::BonesWgpuRenderer; use bones_framework::prelude::*; /// Create our root asset type. @@ -91,7 +91,7 @@ fn main() { PersistedTextData::register_schema(); // Create a bones bevy renderer from our bones game - let mut renderer = BonesBevyRenderer::new(create_game()); + let mut renderer = BonesWgpuRenderer::new(create_game()); // Set the app namespace which will be used by the renderer to decide where to put // persistent storage files. renderer.app_namespace = ( @@ -101,9 +101,9 @@ fn main() { ); // Get a bevy app for running our game renderer - .app() + //.app() // We can add our own bevy plugins now - .add_plugins(LogDiagnosticsPlugin::default()) + //.add_plugins(LogDiagnosticsPlugin::default()) // And run the bevy app .run() } diff --git a/demos/hello_world/Cargo.toml b/demos/hello_world/Cargo.toml index 947524c651..5763fdf58f 100644 --- a/demos/hello_world/Cargo.toml +++ b/demos/hello_world/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] bones_framework = { path = "../../framework_crates/bones_framework" } -bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } +bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/hello_world/src/main.rs b/demos/hello_world/src/main.rs index 3428194cbb..9628e12cfa 100644 --- a/demos/hello_world/src/main.rs +++ b/demos/hello_world/src/main.rs @@ -1,4 +1,4 @@ -use bones_bevy_renderer::BonesBevyRenderer; +use bones_wgpu_renderer::BonesWgpuRenderer; use bones_framework::prelude::*; fn main() { @@ -17,7 +17,7 @@ fn main() { // Add our menu system to the update stage .add_system_to_stage(Update, menu_system); - BonesBevyRenderer::new(game).app().run(); + BonesWgpuRenderer::new(game).run(); } /// System to render the home menu. diff --git a/demos/scripting/Cargo.toml b/demos/scripting/Cargo.toml index 64a4142881..bc57e7a6e3 100644 --- a/demos/scripting/Cargo.toml +++ b/demos/scripting/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] bones_framework = { path = "../../framework_crates/bones_framework" } -bones_bevy_renderer = { path = "../../framework_crates/bones_bevy_renderer" } +bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/scripting/src/main.rs b/demos/scripting/src/main.rs index 24ec4f5b42..888b66c032 100644 --- a/demos/scripting/src/main.rs +++ b/demos/scripting/src/main.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use bones_bevy_renderer::BonesBevyRenderer; +use bones_wgpu_renderer::BonesWgpuRenderer; use bones_framework::prelude::*; #[derive(HasSchema, Default, Clone)] @@ -26,13 +26,13 @@ fn main() { .create("launch") .add_startup_system(launch_game_session); - let mut renderer = BonesBevyRenderer::new(game); + let mut renderer = BonesWgpuRenderer::new(game); renderer.app_namespace = ( "org".into(), "fishfolk".into(), "bones.demo_scripting".into(), ); - renderer.app().run(); + renderer.run(); } fn launch_game_session( diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml index f8cbadbe20..4750a68da3 100644 --- a/framework_crates/bones_wgpu_renderer/Cargo.toml +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -15,7 +15,7 @@ bones_framework = { version = "0.4.0", path = "../bones_framework" } bones_schema = { path = "../bones_schema" } winit = "0.30.9" #Needs to be 23 because of bones_bevy_renderer dependecies, we need to upgrade to 24^ later -wgpu = "23" +wgpu = "23.0" pollster = "0.4.0" env_logger = "0.11.6" log = "0.4" @@ -31,7 +31,6 @@ anyhow = "1.0.98" egui = "0.30" egui-wgpu = "0.30" egui-winit = "0.30" - serde = "1.0.188" serde_yaml = "0.9" diff --git a/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl b/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl index 970d29eb1e..3e0915b0e7 100644 --- a/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl +++ b/framework_crates/bones_wgpu_renderer/src/atlas_sprite.wgsl @@ -62,37 +62,49 @@ fn vs_main( let inst = sprite_data[idx]; var out: VertexOutput; - // 1) compute quad size in *pixels* from your atlas‐tile: - // inst.tile_size is already [width_px, height_px] - let quad_px = vec3( - vert.position.x * inst.tile_size.x, - vert.position.y * inst.tile_size.y, + var uv_size: vec2; + if inst.entity_type == 1u { + // For atlas/tiles: use tile_size directly + uv_size = inst.tile_size; + } else { + // For sprites: use calculated UV size multiplied by 4096 (atlas pool size) + uv_size = (inst.uv_max - inst.uv_min) * 4096.0; + } + + // Convert UV size to world size: use a fixed scale factor instead of screen size + // This prevents distortion when window is resized + let pixel_scale = 0.001; // Adjust this value to control sprite size (smaller = smaller sprites) + uv_size = uv_size * pixel_scale; + + let aspect_ratio = uv_size.x / uv_size.y; + + // Scale position by UV size (not just aspect ratio) + let scaled_position = vec3( + vert.position.x * uv_size.x, + vert.position.y * uv_size.y, vert.position.z ); - // 2) convert pixel‐coordinates → NDC ([-1,1]) per axis - let cam = cameras[inst.camera_index]; - let px_to_ndc = vec2( - 2.0 / cam.screen_size.x, - 2.0 / cam.screen_size.y - ); + // load the camera uniform + let camera = cameras[inst.camera_index]; - // 3) build your instance matrix so that - // translation is in *pixels* → NDC, - // but rotation & scale from inst.transform stay in world‐units - let inst_mat = scale_translation( - inst.transform, - vec3(px_to_ndc.x, px_to_ndc.y, 1.0) - ); + // 1) Transform should use the same pixel_scale so 1 unit = 1 pixel + // Scale transform by pixel_scale to match sprite scaling + let transform_scale = vec3(pixel_scale, pixel_scale, 1.0); + let inst_mat = scale_translation(inst.transform, transform_scale); - // 4) finally, emit clip‐space position: - out.clipPos = cam.transform * inst_mat * vec4(quad_px, 1.0); + // 2) apply camera→clip transform + out.clipPos = camera.transform * inst_mat * vec4(scaled_position, 1.0); - // …then do your UV flip/remap, color_tint etc… + // 2) Apply per‑instance flip var base_uv = vert.uv; if inst.flip_x == 1u { base_uv.x = 1.0 - base_uv.x; } if inst.flip_y == 1u { base_uv.y = 1.0 - base_uv.y; } + + // 3) Remap [0,1] → [uv_min,uv_max] of the atlas out.atlas_uv = mix(inst.uv_min, inst.uv_max, base_uv); + + // 4) Pass instance index along out.inst_index = idx; return out; } @@ -124,10 +136,9 @@ fn fs_main( // Calculate top-left corner of tile let tile_min = os + vec2(f32(col), f32(row)) * step; - // Calculate bottom-right corner of tile - let tile_max = tile_min + step; - - // Map UV from [0,1] to [tile_min, tile_max] + // Calculate bottom-right corner of tile (exclude padding) + let tile_max = tile_min + ts; + // Map UV from [0,1] to [tile_min, tile_max] (only tile area, not padding) uv = mix(tile_min, tile_max, uv); } diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs index a5d5bd3049..23a0944d51 100644 --- a/framework_crates/bones_wgpu_renderer/src/sprite.rs +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -104,7 +104,7 @@ impl AtlasSpriteUniform { columns: atlas.columns, padding: atlas.padding.into(), offset: atlas.offset.into(), - index: 0, + index: tile.idx, image_size, entity_type: 1, flip_x: tile.flip_x as u32, From 3d6ccbb06b5f470253c39bcd9aef747b475265f0 Mon Sep 17 00:00:00 2001 From: Isaac Turci <78173025+Zac8668@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:58:09 -0300 Subject: [PATCH 16/16] Updated to egui 0.31 and wgpu 0.24 --- Cargo.lock | 2672 ++++++++--------- demos/asset_packs/src/main.rs | 2 +- demos/assets_minimal/src/main.rs | 2 +- demos/features/src/main.rs | 8 +- demos/features_wgpu/Cargo.toml | 10 - .../assets/atlas/atlas-demo.yaml | 18 - .../assets/atlas/seagull.atlas.yaml | 4 - demos/features_wgpu/assets/atlas/seagull.png | Bin 87707 -> 0 bytes demos/features_wgpu/assets/audio/blink.ogg | Bin 9311 -> 0 bytes demos/features_wgpu/assets/fonts/Orbitron.ttf | Bin 45252 -> 0 bytes demos/features_wgpu/assets/game.yaml | 61 - .../assets/locales/en-US/locale.yaml | 3 - .../assets/locales/en-US/menu.ftl | 12 - demos/features_wgpu/assets/localization.yaml | 2 - demos/features_wgpu/assets/menu-image.png | Bin 38787 -> 0 bytes demos/features_wgpu/assets/menu.lua | 9 - demos/features_wgpu/assets/pack.yaml | 1 - demos/features_wgpu/assets/sprite/fractal.png | Bin 34581 -> 0 bytes .../features_wgpu/assets/tilemap/tilemap.yaml | 27 - .../assets/tilemap/tileset.atlas.yaml | 4 - .../features_wgpu/assets/tilemap/tileset.png | Bin 17653 -> 0 bytes demos/features_wgpu/assets/ui/button-down.png | Bin 660 -> 0 bytes .../assets/ui/button-focused.png | Bin 243 -> 0 bytes demos/features_wgpu/assets/ui/button.png | Bin 662 -> 0 bytes demos/features_wgpu/assets/ui/panel.png | Bin 407 -> 0 bytes demos/features_wgpu/atlas_0.png | Bin 456077 -> 0 bytes demos/features_wgpu/src/main.rs | 578 ---- demos/hello_world_wgpu/src/main.rs | 19 +- framework_crates/bones_framework/Cargo.toml | 4 +- .../bones_framework/src/render/transform.rs | 1 + .../bones_framework/src/render/ui/widgets.rs | 2 +- .../src/render/ui/widgets/bordered_button.rs | 8 +- .../src/render/ui/widgets/bordered_frame.rs | 16 +- .../bones_wgpu_renderer/Cargo.toml | 11 +- .../bones_wgpu_renderer/src/atlas_pool.rs | 2 - .../src/dynamic_storage.rs | 2 - .../bones_wgpu_renderer/src/lib.rs | 6 +- .../bones_wgpu_renderer/src/sprite.rs | 7 +- .../bones_wgpu_renderer/src/texture.rs | 6 +- .../bones_wgpu_renderer/src/texture_file.rs | 6 +- 40 files changed, 1270 insertions(+), 2233 deletions(-) delete mode 100644 demos/features_wgpu/Cargo.toml delete mode 100644 demos/features_wgpu/assets/atlas/atlas-demo.yaml delete mode 100644 demos/features_wgpu/assets/atlas/seagull.atlas.yaml delete mode 100644 demos/features_wgpu/assets/atlas/seagull.png delete mode 100644 demos/features_wgpu/assets/audio/blink.ogg delete mode 100644 demos/features_wgpu/assets/fonts/Orbitron.ttf delete mode 100644 demos/features_wgpu/assets/game.yaml delete mode 100644 demos/features_wgpu/assets/locales/en-US/locale.yaml delete mode 100644 demos/features_wgpu/assets/locales/en-US/menu.ftl delete mode 100644 demos/features_wgpu/assets/localization.yaml delete mode 100644 demos/features_wgpu/assets/menu-image.png delete mode 100644 demos/features_wgpu/assets/menu.lua delete mode 100644 demos/features_wgpu/assets/pack.yaml delete mode 100644 demos/features_wgpu/assets/sprite/fractal.png delete mode 100644 demos/features_wgpu/assets/tilemap/tilemap.yaml delete mode 100644 demos/features_wgpu/assets/tilemap/tileset.atlas.yaml delete mode 100644 demos/features_wgpu/assets/tilemap/tileset.png delete mode 100644 demos/features_wgpu/assets/ui/button-down.png delete mode 100644 demos/features_wgpu/assets/ui/button-focused.png delete mode 100644 demos/features_wgpu/assets/ui/button.png delete mode 100644 demos/features_wgpu/assets/ui/panel.png delete mode 100644 demos/features_wgpu/atlas_0.png delete mode 100644 demos/features_wgpu/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 4bbf6a3058..c500ab7e76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,59 +18,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" -[[package]] -name = "accesskit" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76eb1adf08c5bcaa8490b9851fd53cca27fa9880076f178ea9d29f05196728a8" - -[[package]] -name = "accesskit_consumer" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bb4d9e4772fe0d47df57d0d5dbe5d85dd05e2f37ae1ddb6b105e76be58fb00" -dependencies = [ - "accesskit", -] - -[[package]] -name = "accesskit_macos" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d0acf6acb667c89d3332999b1a5df4edbc8d6113910f392ebb73f2b03bb56" -dependencies = [ - "accesskit", - "accesskit_consumer", - "objc2 0.3.0-beta.3.patch-leaks.3", - "once_cell", -] - -[[package]] -name = "accesskit_windows" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31" -dependencies = [ - "accesskit", - "accesskit_consumer", - "arrayvec", - "once_cell", - "paste", - "windows 0.48.0", -] - -[[package]] -name = "accesskit_winit" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825d23acee1bd6d25cbaa3ca6ed6e73faf24122a774ec33d52c5c86c6ab423c0" -dependencies = [ - "accesskit", - "accesskit_macos", - "accesskit_windows", - "winit", -] - [[package]] name = "acto" version = "0.7.1" @@ -152,7 +99,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.6.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -169,20 +116,23 @@ dependencies = [ [[package]] name = "android-activity" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64529721f27c2314ced0890ce45e469574a73e5e6fdd6e9da1860eb29285f5e0" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 1.3.2", + "bitflags 2.9.1", "cc", + "cesu8", + "jni 0.21.1", "jni-sys", "libc", "log", - "ndk 0.7.0", + "ndk 0.9.0", "ndk-context", - "ndk-sys 0.4.1+23.1.7779620", - "num_enum 0.6.1", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", ] [[package]] @@ -197,12 +147,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" -[[package]] -name = "android_log-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -263,9 +207,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "append-only-vec" @@ -273,15 +217,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d9f7083455f1a474276ccd32374958d2cb591024aac45101c7623b10271347" -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - [[package]] name = "arboard" version = "3.4.0" @@ -289,10 +224,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" dependencies = [ "clipboard-win", - "core-graphics 0.23.2", - "image 0.25.2", + "core-graphics", + "image 0.25.6", "log", - "objc2 0.5.2", + "objc2", "objc2-app-kit", "objc2-foundation", "parking_lot", @@ -312,13 +247,19 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "ash" -version = "0.37.3+1.3.251" +version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.7.4", + "libloading", ] [[package]] @@ -333,7 +274,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", ] @@ -345,7 +286,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", "synstructure", ] @@ -357,7 +298,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -392,7 +333,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -409,7 +350,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -482,12 +423,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -500,154 +435,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bevy" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c6d3ec4f89e85294dc97334c5b271ddc301fdf67ac9bb994fe44d9273e6ed7" -dependencies = [ - "bevy_internal", -] - -[[package]] -name = "bevy_a11y" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "132c9e35a77c5395951f6d25fa2c52ee92296353426df4f961e60f3ff47e2e42" -dependencies = [ - "accesskit", - "bevy_app", - "bevy_derive", - "bevy_ecs 0.11.3", -] - -[[package]] -name = "bevy_app" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557a7d59e1e16892d7544fc37316506ee598cb5310ef0365125a30783c11531" -dependencies = [ - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "downcast-rs", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "bevy_asset" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9714af523da4cdf58c42a317e5ed40349708ad954a18533991fd64c8ae0a6f68" -dependencies = [ - "anyhow", - "async-channel", - "bevy_app", - "bevy_diagnostic", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "bevy_winit", - "crossbeam-channel", - "downcast-rs", - "fastrand 1.9.0", - "js-sys", - "parking_lot", - "serde", - "thiserror 1.0.63", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "bevy_core" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5272321be5fcf5ce2fb16023bc825bb10dfcb71611117296537181ce950f48" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "bytemuck", -] - -[[package]] -name = "bevy_core_pipeline" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67382fa9c96ce4f4e5833ed7cedd9886844a8f3284b4a717bd4ac738dcdea0c3" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "bitflags 2.6.0", - "radsort", - "serde", -] - -[[package]] -name = "bevy_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44e4e2784a81430199e4157e02903a987a32127c773985506f020e7d501b62e" -dependencies = [ - "bevy_macro_utils 0.11.3", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "bevy_diagnostic" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6babb230dc383c98fdfc9603e3a7a2a49e1e2879dbe8291059ef37dca897932e" -dependencies = [ - "bevy_app", - "bevy_core", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_time", - "bevy_utils 0.11.3", - "sysinfo", -] - -[[package]] -name = "bevy_ecs" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266144b36df7e834d5198049e037ecdf2a2310a76ce39ed937d1b0a6a2c4e8c6" -dependencies = [ - "async-channel", - "bevy_ecs_macros 0.11.3", - "bevy_ptr 0.11.3", - "bevy_reflect 0.11.3", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "downcast-rs", - "event-listener 2.5.3", - "fixedbitset", - "rustc-hash 1.1.0", - "serde", - "thiserror 1.0.63", - "thread_local", -] - [[package]] name = "bevy_ecs" version = "0.12.1" @@ -655,174 +442,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709fbd22f81fb681534cd913c41e1cd18b17143368743281195d7f024b61aea" dependencies = [ "async-channel", - "bevy_ecs_macros 0.12.1", - "bevy_ptr 0.12.1", - "bevy_reflect 0.12.1", + "bevy_ecs_macros", + "bevy_ptr", + "bevy_reflect", "bevy_tasks 0.12.1", - "bevy_utils 0.12.1", + "bevy_utils", "downcast-rs", "event-listener 2.5.3", "fixedbitset", "rustc-hash 1.1.0", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", "thread_local", ] -[[package]] -name = "bevy_ecs_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7157a9c3be038d5008ee3f114feb6cf6b39c1d3d32ee21a7cacb8f81fccdfa80" -dependencies = [ - "bevy_macro_utils 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "bevy_ecs_macros" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8843aa489f159f25cdcd9fee75cd7d221a7098a71eaa72cb2d6b40ac4e3f1ba" dependencies = [ - "bevy_macro_utils 0.12.1", + "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.90", -] - -[[package]] -name = "bevy_egui" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1c1f6ad293c60fd8559c4502cda5e832e92b0e0f3d994929b33f24d4352d70" -dependencies = [ - "arboard", - "bevy", - "egui", - "thread_local", - "webbrowser", -] - -[[package]] -name = "bevy_encase_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ac0f55ad6bca1be7b0f35bbd5fc95ed3d31e4e9db158fee8e5327f59006001" -dependencies = [ - "bevy_macro_utils 0.11.3", - "encase_derive_impl", -] - -[[package]] -name = "bevy_gizmos" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e286a3e7276431963f4aa29165ea5429fa7dbbc6d5c5ba0c531e7dd44ecc88a2" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_core_pipeline", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_sprite", - "bevy_transform", - "bevy_utils 0.11.3", -] - -[[package]] -name = "bevy_hierarchy" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103f8f58416ac6799b8c7f0b418f1fac9eba44fa924df3b0e16b09256b897e3d" -dependencies = [ - "bevy_app", - "bevy_core", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "smallvec", -] - -[[package]] -name = "bevy_input" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbd935401101ac8003f3c3aea70788c65ad03f7a32716a10608bedda7a648bc" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "thiserror 1.0.63", -] - -[[package]] -name = "bevy_internal" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e35a9b2bd29aa784b3cc416bcbf2a298f69f00ca51fd042ea39d9af7fad37e" -dependencies = [ - "bevy_a11y", - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_core_pipeline", - "bevy_derive", - "bevy_diagnostic", - "bevy_ecs 0.11.3", - "bevy_gizmos", - "bevy_hierarchy", - "bevy_input", - "bevy_log", - "bevy_math", - "bevy_pbr", - "bevy_ptr 0.11.3", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_scene", - "bevy_sprite", - "bevy_tasks 0.11.3", - "bevy_time", - "bevy_transform", - "bevy_utils 0.11.3", - "bevy_window", - "bevy_winit", -] - -[[package]] -name = "bevy_log" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dcc615ff4f617b06c3f9522fca3c55d56f9644db293318f8ab68fcdea5d4fe" -dependencies = [ - "android_log-sys", - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_utils 0.11.3", - "console_error_panic_hook", - "tracing-log 0.1.4", - "tracing-subscriber", - "tracing-wasm", -] - -[[package]] -name = "bevy_macro_utils" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ddc18d489b4e57832d4958cde7cd2f349f0ad91e5892ac9e2f2ee16546b981" -dependencies = [ - "quote", - "rustc-hash 1.1.0", - "syn 2.0.90", - "toml_edit 0.19.15", + "syn 2.0.104", ] [[package]] @@ -834,244 +477,42 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash 1.1.0", - "syn 2.0.90", + "syn 2.0.104", "toml_edit 0.20.7", ] -[[package]] -name = "bevy_math" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78286a81fead796dc4b45ab14f4f02fe29a94423d3587bcfef872b2a8e0a474b" -dependencies = [ - "glam 0.24.2", - "serde", -] - -[[package]] -name = "bevy_mikktspace" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfc2a21ea47970a9b1f0f4735af3256a8f204815bd756110051d10f9d909497" -dependencies = [ - "glam 0.24.2", -] - -[[package]] -name = "bevy_pbr" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ca796a619e61cd43a0a3b11fde54644f7f0732a1fba1eef5d406248c6eba85" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core_pipeline", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "bevy_window", - "bitflags 2.6.0", - "bytemuck", - "naga_oil", - "radsort", -] - -[[package]] -name = "bevy_prototype_lyon" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e347c16caede05dc5f774ba388cefeef0ab558a5601fc6b5ffd6606bef77308" -dependencies = [ - "bevy", - "lyon_algorithms", - "lyon_tessellation", - "svgtypes", -] - -[[package]] -name = "bevy_ptr" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c7586401a46f7d8e436028225c1df5288f2e0082d066b247a82466fea155c6" - [[package]] name = "bevy_ptr" version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ec20c8fafcdc196508ef5ccb4f0400a8d193cb61f7b14a36ed9a25ad423cf" - -[[package]] -name = "bevy_reflect" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0778197a1eb3e095a71417c74b7152ede02975cdc95b5ea4ddc5251ed00a2eb5" -dependencies = [ - "bevy_math", - "bevy_ptr 0.11.3", - "bevy_reflect_derive 0.11.3", - "bevy_utils 0.11.3", - "downcast-rs", - "erased-serde 0.3.31", - "glam 0.24.2", - "once_cell", - "parking_lot", - "serde", - "smallvec", - "smol_str 0.2.2", - "thiserror 1.0.63", -] - -[[package]] -name = "bevy_reflect" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7921f15fc944c9c8ad01d7dbcea6505b8909c6655cd9382bab1407181556038" -dependencies = [ - "bevy_ptr 0.12.1", - "bevy_reflect_derive 0.12.1", - "bevy_utils 0.12.1", - "downcast-rs", - "erased-serde 0.3.31", - "serde", - "thiserror 1.0.63", -] - -[[package]] -name = "bevy_reflect_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342a4b2d09db22c48607d23ad59a056aff1ee004549050a51d490d375ba29528" -dependencies = [ - "bevy_macro_utils 0.11.3", - "bit-set", - "proc-macro2", - "quote", - "syn 2.0.90", - "uuid", -] - -[[package]] -name = "bevy_reflect_derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a8c5475f216e751ef4452a1306b00711f33d2d04d9f149e4c845dfeb6753a0" -dependencies = [ - "bevy_macro_utils 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.90", - "uuid", -] - -[[package]] -name = "bevy_render" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39df4824b760928c27afc7b00fb649c7a63c9d76661ab014ff5c86537ee906cb" -dependencies = [ - "anyhow", - "async-channel", - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_encase_derive", - "bevy_hierarchy", - "bevy_log", - "bevy_math", - "bevy_mikktspace", - "bevy_reflect 0.11.3", - "bevy_render_macros", - "bevy_tasks 0.11.3", - "bevy_time", - "bevy_transform", - "bevy_utils 0.11.3", - "bevy_window", - "bitflags 2.6.0", - "bytemuck", - "codespan-reporting", - "downcast-rs", - "encase", - "futures-lite 1.13.0", - "hexasphere", - "image 0.24.9", - "js-sys", - "naga", - "naga_oil", - "parking_lot", - "regex", - "serde", - "smallvec", - "thiserror 1.0.63", - "thread_local", - "wasm-bindgen", - "web-sys", - "wgpu", - "wgpu-hal", -] - -[[package]] -name = "bevy_render_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd08c740aac73363e32fb45af869b10cec65bcb76fe3e6cd0f8f7eebf4c36c9" -dependencies = [ - "bevy_macro_utils 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.90", -] +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77ec20c8fafcdc196508ef5ccb4f0400a8d193cb61f7b14a36ed9a25ad423cf" [[package]] -name = "bevy_scene" -version = "0.11.3" +name = "bevy_reflect" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd47e1263506153bef3a8be97fe2d856f206d315668c4f97510ca6cc181d9681" +checksum = "d7921f15fc944c9c8ad01d7dbcea6505b8909c6655cd9382bab1407181556038" dependencies = [ - "anyhow", - "bevy_app", - "bevy_asset", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_hierarchy", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "ron", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "downcast-rs", + "erased-serde 0.3.31", "serde", - "thiserror 1.0.63", - "uuid", + "thiserror 1.0.69", ] [[package]] -name = "bevy_sprite" -version = "0.11.3" +name = "bevy_reflect_derive" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a8ca824fad75c6ef74cfbbba0a4ce3ccc435fa23d6bf3f003f260548813397" -dependencies = [ - "bevy_app", - "bevy_asset", - "bevy_core_pipeline", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_log", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_render", - "bevy_transform", - "bevy_utils 0.11.3", - "bitflags 2.6.0", - "bytemuck", - "fixedbitset", - "guillotiere", - "rectangle-pack", - "thiserror 1.0.63", +checksum = "b4a8c5475f216e751ef4452a1306b00711f33d2d04d9f149e4c845dfeb6753a0" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn 2.0.104", + "uuid", ] [[package]] @@ -1102,50 +543,6 @@ dependencies = [ "wasm-bindgen-futures", ] -[[package]] -name = "bevy_time" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d58d6dbae9c8225d8c0e0f04d2c5dbb71d22adc01ecd5ab3cebc364139e4a6d" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "crossbeam-channel", - "thiserror 1.0.63", -] - -[[package]] -name = "bevy_transform" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b0ac0149a57cd846cb357a35fc99286f9848e53d4481954608ac9552ed2d4" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_hierarchy", - "bevy_math", - "bevy_reflect 0.11.3", -] - -[[package]] -name = "bevy_utils" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d9484e32434ea84dc548cff246ce0c6f756c1336f5ea03f24ac120a48595c7" -dependencies = [ - "ahash", - "bevy_utils_proc_macros 0.11.3", - "getrandom", - "hashbrown 0.14.5", - "instant", - "petgraph", - "thiserror 1.0.63", - "tracing", - "uuid", -] - [[package]] name = "bevy_utils" version = "0.12.1" @@ -1153,28 +550,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7915222f4a08ccc782e08d10b751b42e5f9d786e697d0cb3fd09333cb7e8b6ea" dependencies = [ "ahash", - "bevy_utils_proc_macros 0.12.1", + "bevy_utils_proc_macros", "getrandom", "hashbrown 0.14.5", "instant", "nonmax", "petgraph", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", "uuid", ] -[[package]] -name = "bevy_utils_proc_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5391b242c36f556db01d5891444730c83aa9dd648b6a8fd2b755d22cb3bddb57" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "bevy_utils_proc_macros" version = "0.12.1" @@ -1183,47 +569,7 @@ checksum = "7aafecc952b6b8eb1a93c12590bd867d25df2f4ae1033a01dfdfc3c35ebccfff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", -] - -[[package]] -name = "bevy_window" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd584c0da7c4ada6557b09f57f30fb7cff21ccedc641473fc391574b4c9b7944" -dependencies = [ - "bevy_app", - "bevy_ecs 0.11.3", - "bevy_input", - "bevy_math", - "bevy_reflect 0.11.3", - "bevy_utils 0.11.3", - "raw-window-handle", -] - -[[package]] -name = "bevy_winit" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdc044abdb95790c20053e6326760f0a2985f0dcd78613d397bf35f16039d53" -dependencies = [ - "accesskit_winit", - "approx", - "bevy_a11y", - "bevy_app", - "bevy_derive", - "bevy_ecs 0.11.3", - "bevy_hierarchy", - "bevy_input", - "bevy_math", - "bevy_tasks 0.11.3", - "bevy_utils 0.11.3", - "bevy_window", - "crossbeam-channel", - "raw-window-handle", - "wasm-bindgen", - "web-sys", - "winit", + "syn 2.0.104", ] [[package]] @@ -1241,7 +587,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cexpr", "clang-sys", "itertools", @@ -1252,23 +598,29 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit_field" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitfield-rle" @@ -1288,9 +640,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -1341,32 +693,13 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-sys" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" -dependencies = [ - "objc-sys 0.2.0-beta.2", -] - -[[package]] -name = "block2" -version = "0.2.0-alpha.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" -dependencies = [ - "block-sys", - "objc2-encode 2.0.0-pre.2", -] - [[package]] name = "block2" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2 0.5.2", + "objc2", ] [[package]] @@ -1403,22 +736,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "bones_bevy_renderer" -version = "0.4.0" -dependencies = [ - "anyhow", - "bevy", - "bevy_egui", - "bevy_prototype_lyon", - "bones_framework", - "directories", - "glam 0.24.2", - "serde", - "serde_yaml", - "web-sys", -] - [[package]] name = "bones_ecs" version = "0.4.0" @@ -1434,7 +751,7 @@ dependencies = [ "once_map", "paste", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", ] @@ -1442,7 +759,7 @@ dependencies = [ name = "bones_ecs_macros" version = "0.4.0" dependencies = [ - "bevy_ecs 0.12.1", + "bevy_ecs", "bones_ecs", "bones_ecs_macros_core", "bones_schema", @@ -1456,7 +773,7 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1504,7 +821,7 @@ dependencies = [ "serde_yaml", "smallvec", "sys-locale", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", "tracing-appender", @@ -1627,6 +944,33 @@ dependencies = [ "venial", ] +[[package]] +name = "bones_wgpu_renderer" +version = "0.4.0" +dependencies = [ + "anyhow", + "bevy_tasks 0.11.3", + "bones_framework", + "bones_schema", + "bytemuck", + "crossbeam-channel", + "directories", + "egui", + "egui-wgpu", + "egui-winit", + "env_logger", + "guillotiere", + "image 0.24.9", + "log", + "lyon", + "pollster", + "profiling", + "serde", + "serde_yaml", + "wgpu", + "winit", +] + [[package]] name = "bounded-integer" version = "0.5.7" @@ -1656,9 +1000,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" dependencies = [ "bytemuck_derive", ] @@ -1671,7 +1015,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1692,6 +1036,32 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.1", + "log", + "polling 3.9.0", + "rustix 0.38.36", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.36", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cc" version = "1.1.18" @@ -1724,12 +1094,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -1779,7 +1143,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading 0.8.5", + "libloading", ] [[package]] @@ -1813,7 +1177,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -1859,12 +1223,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" -[[package]] -name = "com-rs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" - [[package]] name = "combine" version = "4.6.7" @@ -1884,49 +1242,18 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const_panic" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7782af8f90fe69a4bb41e460abe1727d493403d8b2cc43201a3a3e906b24379f" - -[[package]] -name = "const_soft_float" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" - [[package]] name = "constant_time_eq" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "constgebra" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1aaf9b65849a68662ac6c0810c8893a765c960b907dd7cfab9c4a50bf764fbc" -dependencies = [ - "const_soft_float", -] - [[package]] name = "cordyceps" version = "0.3.2" @@ -1963,19 +1290,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types", - "foreign-types 0.3.2", - "libc", -] - [[package]] name = "core-graphics" version = "0.23.2" @@ -1985,7 +1299,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -2084,9 +1398,28 @@ checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] @@ -2097,6 +1430,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -2161,6 +1500,12 @@ dependencies = [ "phf", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -2185,18 +1530,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", -] - -[[package]] -name = "d3d12" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" -dependencies = [ - "bitflags 1.3.2", - "libloading 0.7.4", - "winapi", + "syn 2.0.104", ] [[package]] @@ -2242,40 +1576,48 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" name = "demo_asset_packs" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] name = "demo_assets_minimal" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] name = "demo_features" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] name = "demo_hello_world" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", +] + +[[package]] +name = "demo_hello_world_wgpu" +version = "0.4.0" +dependencies = [ + "bones_framework", + "bones_wgpu_renderer", ] [[package]] name = "demo_scripting" version = "0.4.0" dependencies = [ - "bones_bevy_renderer", "bones_framework", + "bones_wgpu_renderer", ] [[package]] @@ -2311,7 +1653,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -2341,7 +1683,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", "unicode-xid", ] @@ -2404,7 +1746,16 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", ] [[package]] @@ -2433,6 +1784,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "dtoa" version = "1.0.9" @@ -2461,11 +1818,12 @@ dependencies = [ [[package]] name = "ecolor" -version = "0.23.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf4e52dbbb615cfd30cf5a5265335c217b5fd8d669593cea74a517d9c605af" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" dependencies = [ "bytemuck", + "emath", ] [[package]] @@ -2480,38 +1838,82 @@ dependencies = [ ] [[package]] -name = "ed25519-dalek" -version = "2.1.1" +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" +dependencies = [ + "ahash", + "bitflags 2.9.1", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", +] + +[[package]] +name = "egui-wgpu" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core", - "serde", - "sha2", - "subtle", - "zeroize", + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu", ] [[package]] -name = "egui" -version = "0.23.0" +name = "egui-winit" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd69fed5fcf4fbb8225b24e80ea6193b61e17a625db105ef0c4d71dde6eb8b7" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" dependencies = [ "ahash", - "epaint", - "nohash-hasher", + "arboard", + "bytemuck", + "egui", + "log", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", ] [[package]] name = "egui_plot" -version = "0.23.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f33a00fe8eb1ba56535b3dbacdecc7a1365a328908a97c5f3c81bb466be72b" +checksum = "1794c66fb727dac28dffed2e4b548e5118d1cccc331d368a35411d68725dde71" dependencies = [ + "ahash", "egui", + "emath", ] [[package]] @@ -2564,9 +1966,9 @@ dependencies = [ [[package]] name = "emath" -version = "0.23.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef2b29de53074e575c18b694167ccbe6e5191f7b25fe65175a0d905a32eeec0" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" dependencies = [ "bytemuck", ] @@ -2583,38 +1985,6 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" -[[package]] -name = "encase" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fce2eeef77fd4a293a54b62aa00ac9daebfbcda4bf8998c5a815635b004aa1c" -dependencies = [ - "const_panic", - "encase_derive", - "glam 0.24.2", - "thiserror 1.0.63", -] - -[[package]] -name = "encase_derive" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e520cde08cbf4f7cc097f61573ec06ce467019803de8ae82fb2823fa1554a0e" -dependencies = [ - "encase_derive_impl", -] - -[[package]] -name = "encase_derive_impl" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "encoding_rs" version = "0.8.34" @@ -2633,7 +2003,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -2653,24 +2023,56 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] name = "epaint" -version = "0.23.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58067b840d009143934d91d8dcb8ded054d8301d7c11a517ace0a99bb1e1595e" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" dependencies = [ "ab_glyph", "ahash", "bytemuck", "ecolor", "emath", + "epaint_default_fonts", + "log", "nohash-hasher", "parking_lot", + "profiling", ] +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + [[package]] name = "equivalent" version = "1.0.1" @@ -2704,12 +2106,12 @@ checksum = "a02a5d186d7bf1cb21f1f95e1a9cfa5c1f2dcd803a47aad454423ceec13525c5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -2744,6 +2146,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.0", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "extended" version = "0.1.0" @@ -2871,7 +2288,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d" dependencies = [ - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -2893,13 +2310,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "foreign-types" -version = "0.3.2" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -2908,7 +2322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -2919,15 +2333,9 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -3069,7 +2477,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -3137,7 +2545,7 @@ checksum = "c612a69f5557a11046b77a7408d2836fe77077f842171cd211c5ef504bd3cddd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", "synstructure", ] @@ -3298,15 +2706,22 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +[[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 = "glam" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" -dependencies = [ - "bytemuck", - "serde", -] [[package]] name = "glam" @@ -3325,9 +2740,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "glow" -version = "0.12.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -3335,6 +2750,15 @@ dependencies = [ "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 = "governor" version = "0.7.0" @@ -3358,54 +2782,53 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "gpu-alloc-types", ] [[package]] name = "gpu-alloc-types" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", ] [[package]] name = "gpu-allocator" -version = "0.22.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ - "backtrace", "log", - "thiserror 1.0.63", - "winapi", - "windows 0.44.0", + "presser", + "thiserror 1.0.69", + "windows 0.58.0", ] [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown 0.15.4", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] @@ -3441,13 +2864,23 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.5.0", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -3457,12 +2890,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.5" @@ -3475,18 +2902,12 @@ dependencies = [ ] [[package]] -name = "hassle-rs" -version = "0.10.0" +name = "hashbrown" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ - "bitflags 1.3.2", - "com-rs", - "libc", - "libloading 0.7.4", - "thiserror 1.0.63", - "widestring", - "winapi", + "foldhash", ] [[package]] @@ -3516,20 +2937,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] -name = "hex" -version = "0.4.3" +name = "hermit-abi" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] -name = "hexasphere" -version = "9.1.0" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb3df16a7bcb1b5bc092abd55e14f77ca70aea14445026e264586fc62889a10" -dependencies = [ - "constgebra", - "glam 0.24.2", -] +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hexf-parse" @@ -3554,7 +2971,7 @@ dependencies = [ "ipnet", "once_cell", "rand", - "thiserror 1.0.63", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -3579,7 +2996,7 @@ dependencies = [ "ipnet", "once_cell", "rand", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", "tinyvec", "tokio", @@ -3603,7 +3020,7 @@ dependencies = [ "rand", "resolv-conf", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -3875,18 +3292,20 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", + "exr", "gif", "jpeg-decoder", "num-traits", "png", + "qoi", "tiff", ] [[package]] name = "image" -version = "0.25.2" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", @@ -3895,16 +3314,6 @@ dependencies = [ "tiff", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.5.0" @@ -3932,7 +3341,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "inotify-sys", "libc", ] @@ -4022,7 +3431,7 @@ checksum = "80b15215aea8d0367fefb9264521e4a251dc4e113896a3d765f530378518188f" dependencies = [ "anyhow", "backoff", - "base64 0.22.1", + "base64", "bytes", "der", "derive_more", @@ -4056,7 +3465,7 @@ dependencies = [ "netlink-packet-route 0.21.0", "netlink-sys", "netwatch", - "num_enum 0.7.3", + "num_enum", "once_cell", "parking_lot", "pin-project", @@ -4178,7 +3587,7 @@ dependencies = [ "reqwest", "rustls 0.23.12", "surge-ping", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tokio-util", "tracing", @@ -4198,7 +3607,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "socket2 0.5.7", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -4216,7 +3625,7 @@ dependencies = [ "rustls 0.23.12", "rustls-platform-verifier", "slab", - "thiserror 1.0.63", + "thiserror 1.0.69", "tinyvec", "tracing", ] @@ -4241,7 +3650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28a0d0d7317795a01caa47ffdaeec84211d1b394530bdb31dbe15f642e58bcff" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bytes", "clap", "derive_more", @@ -4263,7 +3672,7 @@ dependencies = [ "iroh-quinn", "iroh-quinn-proto", "libc", - "num_enum 0.7.3", + "num_enum", "once_cell", "parking_lot", "pin-project", @@ -4316,6 +3725,30 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "jni" version = "0.19.0" @@ -4326,7 +3759,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.63", + "thiserror 1.0.69", "walkdir", ] @@ -4341,7 +3774,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.63", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -4366,27 +3799,37 @@ name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "khronos-egl" -version = "4.1.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.7.4", + "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 = "kira" version = "0.9.5" @@ -4439,20 +3882,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] -name = "libc" -version = "0.2.158" +name = "lebe" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] -name = "libloading" -version = "0.7.4" +name = "libc" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" @@ -4476,7 +3915,7 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "libc", "redox_syscall 0.4.1", ] @@ -4487,7 +3926,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "libc", "redox_syscall 0.5.3", ] @@ -4514,6 +3953,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litrs" version = "0.4.1" @@ -4577,6 +4022,16 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "lyon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + [[package]] name = "lyon_algorithms" version = "1.0.4" @@ -4644,7 +4099,7 @@ dependencies = [ "serde_bencode", "serde_bytes", "sha1_smol", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", ] @@ -4686,7 +4141,7 @@ checksum = "d8031297470465389c1349c399b927505d0cc4503be7a997c3541765bca82b4d" dependencies = [ "flume", "if-addrs", - "polling", + "polling 2.8.0", "socket2 0.5.7", ] @@ -4696,18 +4151,28 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +dependencies = [ + "libc", +] + [[package]] name = "metal" -version = "0.24.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "block", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "log", "objc", + "paste", ] [[package]] @@ -4765,7 +4230,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -4773,45 +4238,26 @@ dependencies = [ [[package]] name = "naga" -version = "0.12.3" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcc2e0513220fd2b598e6068608d4462db20322c0e77e47f6f488dfcfc279cb" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" dependencies = [ + "arrayvec", "bit-set", - "bitflags 1.3.2", + "bitflags 2.9.1", + "cfg_aliases", "codespan-reporting", "hexf-parse", - "indexmap 1.9.3", + "indexmap", "log", - "num-traits", - "pp-rs", "rustc-hash 1.1.0", "spirv", + "strum", "termcolor", - "thiserror 1.0.63", + "thiserror 2.0.4", "unicode-xid", ] -[[package]] -name = "naga_oil" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be942a5c21c58b9b0bf4d9b99db3634ddb7a916f8e1d1d0b71820cc4150e56b" -dependencies = [ - "bit-set", - "codespan-reporting", - "data-encoding", - "indexmap 1.9.3", - "naga", - "once_cell", - "regex", - "regex-syntax 0.6.29", - "rustc-hash 1.1.0", - "thiserror 1.0.63", - "tracing", - "unicode-ident", -] - [[package]] name = "nanorand" version = "0.7.0" @@ -4823,30 +4269,31 @@ dependencies = [ [[package]] name = "ndk" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "jni-sys", - "ndk-sys 0.4.1+23.1.7779620", - "num_enum 0.5.11", - "raw-window-handle", - "thiserror 1.0.63", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", ] [[package]] name = "ndk" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "jni-sys", "log", - "ndk-sys 0.5.0+25.2.9519653", - "num_enum 0.7.3", - "thiserror 1.0.63", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", ] [[package]] @@ -4857,18 +4304,18 @@ checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" -version = "0.4.1+23.1.7779620" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] [[package]] name = "ndk-sys" -version = "0.5.0+25.2.9519653" +version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ "jni-sys", ] @@ -4936,7 +4383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "483325d4bfef65699214858f097d504eb812c38ce7077d165f301ec406c3066e" dependencies = [ "anyhow", - "bitflags 2.6.0", + "bitflags 2.9.1", "byteorder", "libc", "log", @@ -4953,7 +4400,7 @@ dependencies = [ "anyhow", "byteorder", "paste", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -4967,7 +4414,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", ] @@ -5034,7 +4481,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -5045,9 +4492,9 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cfg-if", - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", ] @@ -5108,7 +4555,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -5121,15 +4568,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -5181,7 +4619,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -5214,55 +4652,13 @@ dependencies = [ "libm", ] -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive 0.5.11", -] - -[[package]] -name = "num_enum" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" -dependencies = [ - "num_enum_derive 0.6.1", -] - [[package]] name = "num_enum" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ - "num_enum_derive 0.7.3", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num_enum_derive" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 2.0.90", + "num_enum_derive", ] [[package]] @@ -5271,10 +4667,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -5290,40 +4686,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] -[[package]] -name = "objc-sys" -version = "0.2.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" - [[package]] name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" -[[package]] -name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" -dependencies = [ - "block2 0.2.0-alpha.6", - "objc-sys 0.2.0-beta.2", - "objc2-encode 2.0.0-pre.2", -] - [[package]] name = "objc2" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ - "objc-sys 0.3.5", - "objc2-encode 4.0.3", + "objc-sys", + "objc2-encode", ] [[package]] @@ -5332,25 +4710,49 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", + "bitflags 2.9.1", + "block2", "libc", - "objc2 0.5.2", + "objc2", "objc2-core-data", "objc2-core-image", "objc2-foundation", "objc2-quartz-core", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-data" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-foundation", ] @@ -5360,19 +4762,22 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", + "block2", + "objc2", "objc2-foundation", "objc2-metal", ] [[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" +name = "objc2-core-location" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "objc-sys 0.2.0-beta.2", + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", ] [[package]] @@ -5387,10 +4792,23 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", + "bitflags 2.9.1", + "block2", + "dispatch", "libc", - "objc2 0.5.2", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", ] [[package]] @@ -5399,9 +4817,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-foundation", ] @@ -5411,20 +4829,66 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.6.0", - "block2 0.5.1", - "objc2 0.5.2", + "bitflags 2.9.1", + "block2", + "objc2", "objc2-foundation", "objc2-metal", ] [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc2-symbols" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "cc", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", ] [[package]] @@ -5470,9 +4934,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_map" @@ -5513,6 +4977,15 @@ dependencies = [ "libredox 0.0.2", ] +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "overload" version = "0.1.1" @@ -5625,7 +5098,7 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.1", + "base64", "serde", ] @@ -5651,7 +5124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", - "thiserror 1.0.63", + "thiserror 1.0.69", "ucd-trie", ] @@ -5675,7 +5148,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -5696,7 +5169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap", ] [[package]] @@ -5729,7 +5202,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -5753,7 +5226,7 @@ dependencies = [ "gc-arena", "hashbrown 0.14.5", "rand", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -5773,7 +5246,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -5818,7 +5291,7 @@ dependencies = [ "mainline", "self_cell 1.0.4", "simple-dns", - "thiserror 1.0.63", + "thiserror 1.0.69", "tracing", "ureq", "wasm-bindgen", @@ -5872,7 +5345,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -5925,6 +5398,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.0.8", + "windows-sys 0.60.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "poly1305" version = "0.8.0" @@ -5938,9 +5431,18 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] [[package]] name = "portmapper" @@ -5949,7 +5451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ea24e7552a28ee4a3478ae116c89080957d6816526d0a533bee6cd67048279" dependencies = [ "anyhow", - "base64 0.22.1", + "base64", "bytes", "derive_more", "futures-lite 2.5.0", @@ -5958,7 +5460,7 @@ dependencies = [ "iroh-metrics", "libc", "netwatch", - "num_enum 0.7.3", + "num_enum", "rand", "serde", "smallvec", @@ -6002,15 +5504,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "pp-rs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" -dependencies = [ - "unicode-xid", -] - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -6054,6 +5547,12 @@ dependencies = [ "ucd-parse", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -6073,16 +5572,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -6126,18 +5615,31 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +dependencies = [ + "quote", + "syn 2.0.104", +] [[package]] name = "prometheus-client" @@ -6159,7 +5661,16 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", ] [[package]] @@ -6183,6 +5694,15 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.5" @@ -6196,7 +5716,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "socket2 0.5.7", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -6213,7 +5733,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "slab", - "thiserror 1.0.63", + "thiserror 1.0.69", "tinyvec", "tracing", ] @@ -6233,9 +5753,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -6256,12 +5776,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radsort" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22" - [[package]] name = "rand" version = "0.8.5" @@ -6313,14 +5827,34 @@ version = "11.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] name = "raw-window-handle" -version = "0.5.2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] [[package]] name = "rcgen" @@ -6347,21 +5881,6 @@ dependencies = [ "yasna", ] -[[package]] -name = "rectangle-pack" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -6377,7 +5896,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] @@ -6388,7 +5907,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox 0.1.3", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -6453,7 +5972,7 @@ version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "futures-util", @@ -6533,18 +6052,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64 0.21.7", - "bitflags 2.6.0", - "serde", - "serde_derive", -] - [[package]] name = "rsa" version = "0.9.6" @@ -6580,7 +6087,7 @@ dependencies = [ "netlink-proto", "netlink-sys", "nix 0.26.4", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", ] @@ -6598,7 +6105,7 @@ dependencies = [ "netlink-proto", "netlink-sys", "nix 0.27.1", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", ] @@ -6644,13 +6151,26 @@ version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + [[package]] name = "rustls" version = "0.21.12" @@ -6697,7 +6217,7 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ - "base64 0.22.1", + "base64", "rustls-pki-types", ] @@ -6822,6 +6342,19 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "sec1" version = "0.7.3" @@ -6842,7 +6375,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -6926,7 +6459,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -6968,7 +6501,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.5.0", + "indexmap", "itoa", "ryu", "serde", @@ -7059,7 +6592,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01607fe2e61894468c6dc0b26103abb073fb08b79a3d9e4b6d76a1a341549958" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", ] [[package]] @@ -7091,8 +6624,41 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "serde", + "bitflags 2.9.1", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.36", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", ] [[package]] @@ -7150,12 +6716,11 @@ dependencies = [ [[package]] name = "spirv" -version = "0.2.0+1.5.4" +version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 1.3.2", - "num-traits", + "bitflags 2.9.1", ] [[package]] @@ -7228,6 +6793,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "strsim" version = "0.11.1" @@ -7254,7 +6825,7 @@ dependencies = [ "proc-macro2", "quote", "struct_iterable_internal", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -7282,7 +6853,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -7291,7 +6862,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0adebf9fb8fba5c39ee34092b0383f247e4d1255b98fcffec94b4b797b85b677" dependencies = [ - "base64 0.22.1", + "base64", "bounded-integer", "byteorder", "crc", @@ -7326,25 +6897,16 @@ dependencies = [ "pnet_packet", "rand", "socket2 0.5.7", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "svg_fmt" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" - -[[package]] -name = "svgtypes" -version = "0.8.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" -dependencies = [ - "siphasher", -] +checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" [[package]] name = "swarm-discovery" @@ -7495,9 +7057,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -7532,7 +7094,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -7544,27 +7106,13 @@ dependencies = [ "libc", ] -[[package]] -name = "sysinfo" -version = "0.29.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "winapi", -] - [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -7596,11 +7144,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.63", + "thiserror-impl 1.0.69", ] [[package]] @@ -7614,13 +7162,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -7631,7 +7179,7 @@ checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -7686,6 +7234,31 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -7735,7 +7308,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -7756,7 +7329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3184e8e292a828dd4bca5b2a60aba830ec5ed873a66c9ebb6e65038fa649e827" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "chrono", "futures", "log", @@ -7823,7 +7396,7 @@ dependencies = [ "http 1.1.0", "httparse", "js-sys", - "thiserror 1.0.63", + "thiserror 1.0.69", "tokio", "tokio-tungstenite 0.21.0", "wasm-bindgen", @@ -7866,24 +7439,13 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.5.0", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.5.0", + "indexmap", "toml_datetime", "winnow 0.5.40", ] @@ -7894,7 +7456,7 @@ version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.5.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -7948,7 +7510,7 @@ checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", "parking_lot", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", "tracing-subscriber", ] @@ -7961,7 +7523,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -7974,17 +7536,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -8011,7 +7562,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", ] [[package]] @@ -8100,7 +7651,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror 1.0.63", + "thiserror 1.0.69", "url", "utf-8", ] @@ -8119,7 +7670,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror 1.0.63", + "thiserror 1.0.69", "utf-8", ] @@ -8238,6 +7789,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.13" @@ -8246,9 +7803,9 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -8278,7 +7835,7 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" dependencies = [ - "base64 0.22.1", + "base64", "flate2", "log", "once_cell", @@ -8401,47 +7958,48 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8449,22 +8007,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "watchable" @@ -8475,25 +8036,123 @@ dependencies = [ "event-listener 4.0.3", "futures-util", "parking_lot", - "thiserror 1.0.63", + "thiserror 1.0.69", +] + +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.0.8", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.9.1", + "rustix 1.0.8", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.9.1", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" +dependencies = [ + "rustix 1.0.8", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.29.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" dependencies = [ "proc-macro2", + "quick-xml", "quote", - "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -8511,17 +8170,18 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.15" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" +checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535" dependencies = [ - "core-foundation 0.9.4", + "block2", + "core-foundation 0.10.0", "home", "jni 0.21.1", "log", "ndk-context", - "objc", - "raw-window-handle", + "objc2", + "objc2-foundation", "url", "web-sys", ] @@ -8543,12 +8203,14 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "0.16.3" +version = "24.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c965c9306872eb6255fa55e4b4953be55a8b64d57e61d7ff840d3dcc051cd" +checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" dependencies = [ "arrayvec", - "cfg-if", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", "js-sys", "log", "naga", @@ -8567,55 +8229,60 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.16.1" +version = "24.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f478237b4bf0d5b70a39898a66fa67ca3a007d79f2520485b8b0c3dfc46f8c2" +checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.6.0", - "codespan-reporting", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "indexmap", "log", "naga", + "once_cell", "parking_lot", "profiling", "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.63", - "web-sys", + "thiserror 2.0.4", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-hal" -version = "0.16.2" +version = "24.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecb3258078e936deee14fd4e0febe1cfe9bbb5ffef165cb60218d2ee5eb4448" +checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags 2.6.0", + "bitflags 2.9.1", "block", + "bytemuck", + "cfg_aliases", "core-graphics-types", - "d3d12", - "foreign-types 0.3.2", "glow", + "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", - "hassle-rs", "js-sys", "khronos-egl", "libc", - "libloading 0.8.5", + "libloading", "log", "metal", "naga", + "ndk-sys 0.5.0+25.2.9519653", "objc", + "once_cell", + "ordered-float", "parking_lot", "profiling", "range-alloc", @@ -8623,21 +8290,23 @@ dependencies = [ "renderdoc-sys", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.63", + "thiserror 2.0.4", "wasm-bindgen", "web-sys", "wgpu-types", - "winapi", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] name = "wgpu-types" -version = "0.16.1" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c153280bb108c2979eb5c7391cb18c56642dd3c072e55f52065e13e2a1252a" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "js-sys", + "log", "web-sys", ] @@ -8693,23 +8362,12 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-implement 0.48.0", - "windows-interface 0.48.0", "windows-targets 0.48.5", ] @@ -8768,24 +8426,13 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", + "windows-implement", + "windows-interface", "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] -[[package]] -name = "windows-implement" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -8794,30 +8441,25 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] name = "windows-interface" -version = "0.48.0" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.104", ] [[package]] -name = "windows-interface" -version = "0.58.0" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" @@ -8894,6 +8536,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -8933,13 +8584,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -8958,6 +8626,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8976,6 +8650,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8994,12 +8674,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -9018,6 +8710,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -9036,6 +8734,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -9054,6 +8758,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9072,34 +8782,62 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winit" -version = "0.28.7" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ + "ahash", "android-activity", - "bitflags 1.3.2", - "cfg_aliases 0.1.1", + "atomic-waker", + "bitflags 2.9.1", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", "core-foundation 0.9.4", - "core-graphics 0.22.3", - "dispatch", - "instant", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", "libc", - "log", - "mio 0.8.11", - "ndk 0.7.0", - "objc2 0.3.0-beta.3.patch-leaks.3", - "once_cell", + "memmap2", + "ndk 0.9.0", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", "orbclient", "percent-encoding", + "pin-project", "raw-window-handle", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", + "rustix 0.38.36", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str 0.2.2", + "tracing", + "unicode-segmentation", "wasm-bindgen", - "wayland-scanner", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", "web-sys", - "windows-sys 0.45.0", + "web-time", + "windows-sys 0.52.0", "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] @@ -9171,8 +8909,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ + "as-raw-xcb-connection", "gethostname", - "rustix", + "libc", + "libloading", + "once_cell", + "rustix 0.38.36", "x11rb-protocol", ] @@ -9195,10 +8937,35 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", ] +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + [[package]] name = "xml-rs" version = "0.8.21" @@ -9253,7 +9020,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.104", ] [[package]] @@ -9261,3 +9028,12 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/demos/asset_packs/src/main.rs b/demos/asset_packs/src/main.rs index 7bdc474c29..b95fe50cee 100644 --- a/demos/asset_packs/src/main.rs +++ b/demos/asset_packs/src/main.rs @@ -65,7 +65,7 @@ fn menu_system( all_packs: AllPacksData, ) { egui::CentralPanel::default() - .frame(egui::Frame::none().outer_margin(egui::Margin::same(32.0))) + .frame(egui::Frame::new().outer_margin(egui::epaint::Marginf::same(32.0))) .show(&egui_ctx, |ui| { // Use the title that has been loaded from the asset ui.heading(&core_meta.title); diff --git a/demos/assets_minimal/src/main.rs b/demos/assets_minimal/src/main.rs index 5fdf46e7d2..a4b883ac62 100644 --- a/demos/assets_minimal/src/main.rs +++ b/demos/assets_minimal/src/main.rs @@ -54,7 +54,7 @@ fn menu_system( meta: Root, ) { egui::CentralPanel::default() - .frame(egui::Frame::none()) + .frame(egui::Frame::new()) .show(&egui_ctx, |ui| { // Use the title that has been loaded from the asset ui.heading(&meta.title); diff --git a/demos/features/src/main.rs b/demos/features/src/main.rs index 80bddb71ac..688b36b247 100644 --- a/demos/features/src/main.rs +++ b/demos/features/src/main.rs @@ -181,7 +181,7 @@ fn menu_system( // Render the menu. egui::CentralPanel::default() - .frame(egui::Frame::none()) + .frame(egui::Frame::new()) .show(&ctx, |ui| { BorderedFrame::new(&meta.menu_border).show(ui, |ui| { ui.vertical_centered(|ui| { @@ -317,7 +317,7 @@ fn move_sprite( ctx: Res, ) { egui::CentralPanel::default() - .frame(egui::Frame::none()) + .frame(egui::Frame::new()) .show(&ctx, |ui| { ui.label("Press left and right arrow keys to move sprite"); }); @@ -453,7 +453,7 @@ fn audio_demo_ui( assets: Res, ) { egui::CentralPanel::default() - .frame(egui::Frame::none()) + .frame(egui::Frame::new()) .show(&ctx, |ui| { ui.vertical_centered(|ui| { ui.add_space(50.0); @@ -538,7 +538,7 @@ fn back_to_menu_ui( localization: Localization, ) { egui::TopBottomPanel::bottom("back-to-menu") - .frame(egui::Frame::none()) + .frame(egui::Frame::new()) .show_separator_line(false) .show(&ctx, |ui| { ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { diff --git a/demos/features_wgpu/Cargo.toml b/demos/features_wgpu/Cargo.toml deleted file mode 100644 index ca6b2bf066..0000000000 --- a/demos/features_wgpu/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "demo_features_wgpu" -edition.workspace = true -version.workspace = true -license.workspace = true -publish = false - -[dependencies] -bones_framework = { path = "../../framework_crates/bones_framework" } -bones_wgpu_renderer = { path = "../../framework_crates/bones_wgpu_renderer" } diff --git a/demos/features_wgpu/assets/atlas/atlas-demo.yaml b/demos/features_wgpu/assets/atlas/atlas-demo.yaml deleted file mode 100644 index 0d749feefe..0000000000 --- a/demos/features_wgpu/assets/atlas/atlas-demo.yaml +++ /dev/null @@ -1,18 +0,0 @@ -camera_size: !FixedHeight 275 -atlas: ./seagull.atlas.yaml -fps: 10 -animation: - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - - 9 - - 10 - - 11 - - 12 - - 13 diff --git a/demos/features_wgpu/assets/atlas/seagull.atlas.yaml b/demos/features_wgpu/assets/atlas/seagull.atlas.yaml deleted file mode 100644 index ce03ceb93f..0000000000 --- a/demos/features_wgpu/assets/atlas/seagull.atlas.yaml +++ /dev/null @@ -1,4 +0,0 @@ -tile_size: [96, 80] -columns: 14 -rows: 15 -image: ./seagull.png diff --git a/demos/features_wgpu/assets/atlas/seagull.png b/demos/features_wgpu/assets/atlas/seagull.png deleted file mode 100644 index 4a89d187e66816db92e1781074b09909383de04b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87707 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

+UFls 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 diff --git a/demos/features_wgpu/assets/fonts/Orbitron.ttf b/demos/features_wgpu/assets/fonts/Orbitron.ttf deleted file mode 100644 index a0062a5af28990ccd840c2fefeac4f543940770c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45252 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 diff --git a/demos/features_wgpu/assets/game.yaml b/demos/features_wgpu/assets/game.yaml deleted file mode 100644 index 44bdb50d44..0000000000 --- a/demos/features_wgpu/assets/game.yaml +++ /dev/null @@ -1,61 +0,0 @@ -menu_script: ./menu.lua -menu_image: ./menu-image.png -sprite_demo: ./sprite/fractal.png -atlas_demo: ./atlas/atlas-demo.yaml -tilemap_demo: ./tilemap/tilemap.yaml -audio_demo: ./audio/blink.ogg -localization: ./localization.yaml -path2d_color: red -title_font: - family: Orbitron - size: 20 - color: purple -menu_border: - image: ./ui/panel.png - image_size: [44, 44] - border_size: - left: 16 - right: 16 - top: 16 - bottom: 16 - scale: 4 -button_style: - font: - family: Orbitron - color: white - size: 10 - padding: - top: 6 - left: 6 - right: 6 - bottom: 9 - borders: - default: - image: ui/button.png - image_size: [14, 14] - border_size: - top: 5 - bottom: 5 - right: 5 - left: 5 - scale: 2 - focused: - image: ui/button-focused.png - image_size: [14, 14] - border_size: - top: 5 - bottom: 5 - right: 5 - left: 5 - scale: 2 - clicked: - image: ui/button-down.png - image_size: [14, 14] - border_size: - top: 5 - bottom: 5 - right: 5 - left: 5 - scale: 2 -fonts: - - ./fonts/Orbitron.ttf diff --git a/demos/features_wgpu/assets/locales/en-US/locale.yaml b/demos/features_wgpu/assets/locales/en-US/locale.yaml deleted file mode 100644 index 93ee52ae37..0000000000 --- a/demos/features_wgpu/assets/locales/en-US/locale.yaml +++ /dev/null @@ -1,3 +0,0 @@ -locales: [en-US] -resources: - - ./menu.ftl diff --git a/demos/features_wgpu/assets/locales/en-US/menu.ftl b/demos/features_wgpu/assets/locales/en-US/menu.ftl deleted file mode 100644 index 573a4a7ae6..0000000000 --- a/demos/features_wgpu/assets/locales/en-US/menu.ftl +++ /dev/null @@ -1,12 +0,0 @@ -title = Bones Features Demo -back-to-menu = Back to Menu -sprite-demo = Sprite Demo -atlas-demo = Atlas Demo -audio-demo = Audio Demo -storage-demo = Storage Demo -tilemap-demo = Tilemap Demo -path2d-demo = Path2D Demo -quit = Quit -save = Save -play-sound = Play Sound -persisted-text-box-content = Persisted text box content diff --git a/demos/features_wgpu/assets/localization.yaml b/demos/features_wgpu/assets/localization.yaml deleted file mode 100644 index bb8dbbab69..0000000000 --- a/demos/features_wgpu/assets/localization.yaml +++ /dev/null @@ -1,2 +0,0 @@ -locales: - - ./locales/en-US/locale.yaml diff --git a/demos/features_wgpu/assets/menu-image.png b/demos/features_wgpu/assets/menu-image.png deleted file mode 100644 index 39b5937521b30770304ddb5cbe5b6bff045c976c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38787 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 P00000NkvXXu0mjf%6L@{ diff --git a/demos/features_wgpu/assets/menu.lua b/demos/features_wgpu/assets/menu.lua deleted file mode 100644 index 6a94d1e838..0000000000 --- a/demos/features_wgpu/assets/menu.lua +++ /dev/null @@ -1,9 +0,0 @@ -local menuData = world.resources:get(schema("MenuData")) - --- Increment the frame counter -menuData.frame = menuData.frame + 1 - -if menuData.frame % 30 == 0 then - info(menuData) -end - diff --git a/demos/features_wgpu/assets/pack.yaml b/demos/features_wgpu/assets/pack.yaml deleted file mode 100644 index a4979b30cc..0000000000 --- a/demos/features_wgpu/assets/pack.yaml +++ /dev/null @@ -1 +0,0 @@ -root: ./game.yaml diff --git a/demos/features_wgpu/assets/sprite/fractal.png b/demos/features_wgpu/assets/sprite/fractal.png deleted file mode 100644 index a96df3d53d32466f89c2e7454cfd0be54c16d396..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34581 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

7VF=fanFQRTaAm$kR~R3tqg zCkk%C^?la7{$Ae;|3byGAfNW8i!;mia^CMwWWU2#dg)nx`L6%p$!~3$<;=_4%6Cjx zUX=W(uVOR{{viw{beGZ6hk&{$F2jZjmO>Sdm%hMh{|EO3OYC{Sa{;YOdB*4|`xnF0 z$wreM#h(MSf_I#@-ppao6K$+1pX#P32D(4C=Q|UZbV>Fqd!)|^bnVl_0n;}xr-(eN z^_v3=trN(MKOL~Kc`>j7QCz62vu#&+PVCXDdqqudd#ONc;~dT7plNPpIeHcRwL7+n z&k?scSSL4}^0@i9jJRq(K78U#L5PMw zB&Jm+TGO00Sv}Vn`RXM16%h}hiwez*6W#LNnw!%Qy^O_AyCdbGCHlnf+;Vq*HYCxT zJ^_bi%xuS;If%q9gtP_3S-Ljozj0Q7QIft_|1c*UJVCIs=f5#+vN#v9b|i}Dvqg!` zLh~oDC#OJ8z4c*vBKCcrKR++u;8tIO^W@knJsB|l@iZm zJ_;H~9R26A9(eZx?ZKsQ$sP z{!UivLy=MA`uEf0s9JdcIB{NtqT9M{ZSb4THt_csz*yn{)Z{u72fIh z$U-90gZd$JKUz7AuPuhrg}u9wIR|{Zuz3NS4M>P7Q~VQHKqoEXP{#Dth+Mh}2q9%l zn|})fR^o<*Bn0xWa%1KMTOLQeixH^LoNH{AY zBoRU~DLR&>2j*F;Pap$}|5A8nX5b@3IMY9CeL(mH=qk#WgRgqeoM)MA`%3j#{_9S! zWx8|BR4Ee<)X8K_AQkoXegF4n(|-ZcOX3*Tz>$~z+&oiAd-{wlfB#1~n2mC&&zNOI zj4v+?7yZHy9{7bd<~MDQUR4AyB6rP^^2~GdAYL^cD^OC}NJk|>Dsccd9dj@7{{PH@ zwuz~w<@^^Ae`VtcwPbs`hyNz$P1#&O+o2O+7!-PYLWxoGbixfJ=bfA$uua+JH19p< zv)?DaWJmwAB~-Q=Ggdh3&B$}^KfGgKHA5w+80mBY+WYt&!8g9;J(qrwho>pB??|LxA$5q*{{ud z7b{=^dCISFGCfzyqYgeSSmpl$#{xM0`zj_;{;8KO!22)#8v8!vir?4~KXbnioqhnQ zDg8IA=vVGi5gI$e=9gM}6U`e$j{eQvmlz&!XrMCUS>qo#81`2sO z(f^=HKLC9HXH)5y+|d_6Xw8WcOEZW#6-es8=kf5*zSfBA_Y^<-G60r_=%-Hs?&1|s zxF(+P&cf}v$wVLT>gAs_B|;6QH#>0zkCqft2Soa%z{#oTvISU@4i7-t=Xq^iV8?So z+CAg1r08A|q{k;{}6UJV20zmfzW5efGXQDKb;S^RrsxN=SX%KZn-~f=>m6O0vke;hrIw%?YprIfJGtq=~Hm+u(kOLSi0a( zULRc+eVqrn3ve&!nQsPmJQpm)%K4uy-w9rPz6bpujVlj!!+$EGrnm16W{1JjRo=qg(wkWGZSiv(Jnto?;}hv+7wASvJj`$w zaX%!X2Y|Ny?*c&j6o|uc{Nccs?*uJfWx|z-C%ns|U)K}a=CPeUP_7S(!(izG`&Jlw za3uirJRZiO(3*4G%K)e$@!B(4H6@;e9_1csyh!d*<)>c)MbTFr;9#HV2Tks|hk}c* zyFjpRkBe8pzO@_&x9^UaeUN&K=fPbDfR?P+96nT(bv*ao$?=qwXgm*v9towJH;8h% zhw8lkvgqqSq%YE^plJsHz}f%O2>@%q9>4lyYxvwMfU%l4>i+{(f7p(!w~br?0000< KMNUMnLSTZin>hpk diff --git a/demos/features_wgpu/assets/tilemap/tilemap.yaml b/demos/features_wgpu/assets/tilemap/tilemap.yaml deleted file mode 100644 index 95d9f2b983..0000000000 --- a/demos/features_wgpu/assets/tilemap/tilemap.yaml +++ /dev/null @@ -1,27 +0,0 @@ -atlas: ./tileset.atlas.yaml -map_size: [9, 9] -tiles: - - pos: [0, 0] - idx: 0 - - pos: [1, 0] - idx: 1 - - pos: [2, 0] - idx: 1 - - pos: [3, 0] - idx: 1 - - pos: [4, 0] - idx: 1 - - pos: [5, 0] - idx: 1 - - pos: [6, 0] - idx: 1 - - pos: [7, 0] - idx: 1 - - pos: [8, 0] - idx: 2 - - pos: [4, 2] - idx: 56 - - pos: [5, 2] - idx: 58 - - pos: [6, 2] - idx: 60 diff --git a/demos/features_wgpu/assets/tilemap/tileset.atlas.yaml b/demos/features_wgpu/assets/tilemap/tileset.atlas.yaml deleted file mode 100644 index 2eab7fa754..0000000000 --- a/demos/features_wgpu/assets/tilemap/tileset.atlas.yaml +++ /dev/null @@ -1,4 +0,0 @@ -image: ./tileset.png -tile_size: [32, 32] -columns: 7 -rows: 9 diff --git a/demos/features_wgpu/assets/tilemap/tileset.png b/demos/features_wgpu/assets/tilemap/tileset.png deleted file mode 100644 index 42be6234e1e25472631c4247797a1af2bb18cabe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17653 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 diff --git a/demos/features_wgpu/assets/ui/button-down.png b/demos/features_wgpu/assets/ui/button-down.png deleted file mode 100644 index dfe103070c8974e9af7f714099da363b29882dda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 660 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

^d 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 zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{} zJW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZ zaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D< z&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK z-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-QZ{}JW_D<&@NZaP-hK-LdcfLw(IB|I3$G&42atUmeH)-M)O~ LXAj-BcI*EDn3D>i diff --git a/demos/features_wgpu/src/main.rs b/demos/features_wgpu/src/main.rs deleted file mode 100644 index 73571a856f..0000000000 --- a/demos/features_wgpu/src/main.rs +++ /dev/null @@ -1,578 +0,0 @@ -#![allow(clippy::too_many_arguments)] - -use bones_framework::prelude::*; -use bones_wgpu_renderer::BonesWgpuRenderer; - -/// Create our root asset type. -/// -/// The path to our root asset file is specified in `assets/pack.yaml`. -#[derive(HasSchema, Default, Clone)] -#[repr(C)] -// Allow asset to be loaded from "game.yaml" assets. -#[type_data(metadata_asset("game"))] -struct GameMeta { - /// A lua script that will be run every frame on the menu. - menu_script: Handle, - /// The image displayed on the menu. - menu_image: Handle, - /// The image for the sprite demo - sprite_demo: Handle, - /// Character information that will be loaded from a separate asset file. - atlas_demo: Handle, - /// The tilemap demo metadata. - tilemap_demo: Handle, - /// Audio track for the audio demo. - audio_demo: Handle, - /// The color the debug lines in the debug line demo. - path2d_color: Color, - /// Localization asset - localization: Handle, - /// The font to use for the demo title. - title_font: FontMeta, - /// The list of font files to load for the UI. - fonts: SVec>, - /// The border to use the for main menu. - menu_border: BorderImageMeta, - /// The style to use for buttons. - button_style: ButtonThemeMeta, -} - -/// Atlas information. -#[derive(HasSchema, Default, Clone)] -#[repr(C)] -#[type_data(metadata_asset("atlas-demo"))] -struct AtlasDemoMeta { - /// The size of the camera. - camera_size: CameraSize, - /// The sprite atlas for the player. - pub atlas: Handle, - /// The frames-per-second of the animation. - pub fps: f32, - /// The frames of the animation. - /// - /// Note: We use an [`SVec`] here because it implements [`HasSchema`], allowing it to be loaded - /// in a metadata asset. - pub animation: SVec, -} - -/// Tilemap info. -#[derive(HasSchema, Default, Clone)] -#[repr(C)] -#[type_data(metadata_asset("tilemap"))] -struct TilemapDemoMeta { - /// The atlas that will be used for the tilemap. - pub atlas: Handle, - /// The size of the tile map in tiles. - pub map_size: UVec2, - /// The information about each tile in the tilemap. - pub tiles: SVec, -} - -/// Tile info. -#[derive(HasSchema, Default, Clone)] -#[repr(C)] -struct TileMeta { - /// The tile position. - pos: UVec2, - /// The index of the tile in the atlas. - idx: u32, -} - -/// Struct containing data that will be persisted with the storage API. -#[derive(HasSchema, Default, Clone)] -#[repr(C)] -struct PersistedTextData(String); - -fn main() { - // Setup logging - setup_logs!(); - - // Register persistent data's schema so that it can be loaded by the storage loader. - PersistedTextData::register_schema(); - - // Create a bones bevy renderer from our bones game - let mut renderer = BonesWgpuRenderer::new(create_game()); - // Set the app namespace which will be used by the renderer to decide where to put - // persistent storage files. - renderer.app_namespace = ( - "org".into(), - "fishfolk".into(), - "bones.demo_features".into(), - ); - // Run the renderer. - renderer.run(); -} - -// Initialize the game. -pub fn create_game() -> Game { - // Create an empty game - let mut game = Game::new(); - - // Configure the asset server - game.install_plugin(DefaultGamePlugin) - .init_shared_resource::() - // Register the default asset types - .register_default_assets(); - - // Register our custom asset types - GameMeta::register_schema(); - AtlasDemoMeta::register_schema(); - TilemapDemoMeta::register_schema(); - - // Create our menu session - game.sessions.create("menu").install_plugin(menu_plugin); - - game -} - -/// Resource containing data that we will access from our menu lua script. -#[derive(HasSchema, Default, Clone)] -#[repr(C)] -struct MenuData { - /// The index of the frame that we are on. - pub frame: u32, -} - -/// Menu plugin -pub fn menu_plugin(session: &mut Session) { - // Register our menu system - session - // Install the bones_framework default plugins for this session - .install_plugin(DefaultSessionPlugin) - .world - // Initialize our menu data resource - .init_resource::(); - - // And add our systems. - session - .add_system_to_stage(Update, menu_system) - .add_startup_system(menu_startup); -} - -/// Setup the main menu. -fn menu_startup( - mut egui_settings: ResMutInit, - mut clear_color: ResMutInit, -) { - // Set the clear color - **clear_color = Color::BLACK; - // Set the egui scale - egui_settings.scale = 2.0; -} - -/// Our main menu system. -fn menu_system( - meta: Root, - ctx: Res, - mut sessions: ResMut, - mut session_options: ResMut, - mut exit_bones: Option>, - // Get the localization field from our `GameMeta` - localization: Localization, - world: &World, - lua_engine: Res, -) { - // Run our menu script. - lua_engine.run_script_system(world, meta.menu_script); - - // Render the menu. - egui::CentralPanel::default() - .frame(egui::Frame::none()) - .show(&ctx, |ui| { - BorderedFrame::new(&meta.menu_border).show(ui, |ui| { - ui.vertical_centered(|ui| { - ui.add_space(20.0); - ui.label(meta.title_font.rich(localization.get("title"))); - ui.add_space(20.0); - - if BorderedButton::themed(&meta.button_style, localization.get("sprite-demo")) - .show(ui) - .clicked() - { - // Delete the menu world - session_options.delete = true; - - // Create a session for the match - sessions - .create("sprite_demo") - .install_plugin(sprite_demo_plugin); - } - - if BorderedButton::themed(&meta.button_style, localization.get("atlas-demo")) - .show(ui) - .clicked() - { - // Delete the menu world - session_options.delete = true; - - // Create a session for the match - sessions - .create("atlas_demo") - .install_plugin(atlas_demo_plugin); - } - - if BorderedButton::themed(&meta.button_style, localization.get("tilemap-demo")) - .show(ui) - .clicked() - { - // Delete the menu world - session_options.delete = true; - - // Create a session for the match - sessions - .create("tilemap_demo") - .install_plugin(tilemap_demo_plugin); - } - - if BorderedButton::themed(&meta.button_style, localization.get("audio-demo")) - .show(ui) - .clicked() - { - // Delete the menu world - session_options.delete = true; - - // Create a session for the match - sessions - .create("audio_demo") - .install_plugin(audio_demo_plugin); - } - - if BorderedButton::themed(&meta.button_style, localization.get("storage-demo")) - .show(ui) - .clicked() - { - // Delete the menu world - session_options.delete = true; - - // Create a session for the match - sessions - .create("storage_demo") - .install_plugin(storage_demo_plugin); - } - - if BorderedButton::themed(&meta.button_style, localization.get("path2d-demo")) - .show(ui) - .clicked() - { - // Delete the menu world - session_options.delete = true; - - // Create a session for the match - sessions - .create("path2d_demo") - .install_plugin(path2d_demo_plugin); - } - - if let Some(exit_bones) = &mut exit_bones { - if BorderedButton::themed(&meta.button_style, localization.get("quit")) - .show(ui) - .clicked() - { - ***exit_bones = true; - } - } - - ui.add_space(10.0); - - // We can use the `&World` parameter to access the world and run systems to act - // as egui widgets. - // - // This makes it easier to compose widgets that have differing access to the - // bones world. - world.run_system(demo_widget, ui); - - ui.add_space(30.0); - }); - }) - }); -} - -/// Plugin for running the sprite demo. -fn sprite_demo_plugin(session: &mut Session) { - session - .install_plugin(DefaultSessionPlugin) - .add_startup_system(sprite_demo_startup) - .add_system_to_stage(Update, back_to_menu_ui) - .add_system_to_stage(Update, move_sprite); -} - -/// System that spawns the sprite demo. -fn sprite_demo_startup( - mut entities: ResMut, - mut sprites: CompMut, - mut transforms: CompMut, - mut cameras: CompMut, - meta: Root, -) { - spawn_default_camera(&mut entities, &mut transforms, &mut cameras); - - let sprite_ent = entities.create(); - transforms.insert(sprite_ent, default()); - sprites.insert( - sprite_ent, - Sprite { - image: meta.sprite_demo, - ..default() - }, - ); -} - -fn move_sprite( - entities: Res, - sprite: Comp, - mut transforms: CompMut, - input: Res, - ctx: Res, -) { - egui::CentralPanel::default() - .frame(egui::Frame::none()) - .show(&ctx, |ui| { - ui.label("Press left and right arrow keys to move sprite"); - }); - - let mut left = false; - let mut right = false; - - for input in &input.key_events { - match input.key_code { - Set(KeyCode::Right) => right = true, - Set(KeyCode::Left) => left = true, - _ => (), - } - } - - for (_ent, (_sprite, transform)) in entities.iter_with((&sprite, &mut transforms)) { - if left { - transform.translation.x -= 2.0; - } - if right { - transform.translation.x += 2.0; - } - } -} - -/// Plugin for running the tilemap demo. -fn tilemap_demo_plugin(session: &mut Session) { - session - .install_plugin(DefaultSessionPlugin) - .add_startup_system(tilemap_startup_system) - .add_system_to_stage(Update, back_to_menu_ui); -} - -/// System for starting up the tilemap demo. -fn tilemap_startup_system( - mut entities: ResMut, - mut transforms: CompMut, - mut tile_layers: CompMut, - mut cameras: CompMut, - mut tiles: CompMut, - meta: Root, - assets: Res, -) { - spawn_default_camera(&mut entities, &mut transforms, &mut cameras); - - // Load our map and atlas info - let map_info = assets.get(meta.tilemap_demo); - let atlas = assets.get(map_info.atlas); - - // Create a new map layer - let mut layer = TileLayer::new(map_info.map_size, atlas.tile_size, map_info.atlas); - - // Load the layer up with the tiles from our metadata - for tile in &map_info.tiles { - let tile_ent = entities.create(); - tiles.insert( - tile_ent, - Tile { - idx: tile.idx, - ..default() - }, - ); - layer.set(tile.pos, Some(tile_ent)) - } - - // Spawn the layer - let layer_ent = entities.create(); - tile_layers.insert(layer_ent, layer); - transforms.insert(layer_ent, default()); -} - -/// Plugin for running the atlas demo. -fn atlas_demo_plugin(session: &mut Session) { - session - .install_plugin(DefaultSessionPlugin) - .add_startup_system(atlas_demo_startup) - .add_system_to_stage(Update, back_to_menu_ui); -} - -/// System to startup the atlas demo. -fn atlas_demo_startup( - mut entities: ResMut, - mut transforms: CompMut, - mut cameras: CompMut, - mut atlas_sprites: CompMut, - mut animated_sprites: CompMut, - mut clear_color: ResMutInit, - meta: Root, - assets: Res, -) { - // Set the clear color - **clear_color = Color::GRAY; - - // Get the atlas metadata - let demo = assets.get(meta.atlas_demo); - - // Spawn the camera - let camera_ent = spawn_default_camera(&mut entities, &mut transforms, &mut cameras); - cameras.get_mut(camera_ent).unwrap().size = demo.camera_size; - - // Spawn the character sprite. - let sprite_ent = entities.create(); - transforms.insert(sprite_ent, default()); - atlas_sprites.insert( - sprite_ent, - AtlasSprite { - atlas: demo.atlas, - ..default() - }, - ); - animated_sprites.insert( - sprite_ent, - AnimatedSprite { - frames: demo.animation.iter().copied().collect(), - fps: demo.fps, - ..default() - }, - ); -} - -fn audio_demo_plugin(session: &mut Session) { - session - .install_plugin(DefaultSessionPlugin) - .add_system_to_stage(Update, back_to_menu_ui) - .add_system_to_stage(Update, audio_demo_ui); -} - -fn audio_demo_ui( - ctx: Res, - localization: Localization, - mut audio: ResMut, - meta: Root, - assets: Res, -) { - egui::CentralPanel::default() - .frame(egui::Frame::none()) - .show(&ctx, |ui| { - ui.vertical_centered(|ui| { - ui.add_space(50.0); - if ui.button(localization.get("play-sound")).clicked() { - audio.play(&*assets.get(meta.audio_demo)).unwrap(); - } - }) - }); -} - -fn storage_demo_plugin(session: &mut Session) { - session - .install_plugin(DefaultSessionPlugin) - .add_system_to_stage(Update, storage_demo_ui) - .add_system_to_stage(Update, back_to_menu_ui); -} - -fn storage_demo_ui( - ctx: Res, - mut storage: ResMut, - localization: Localization, -) { - egui::CentralPanel::default().show(&ctx, |ui| { - ui.add_space(20.0); - - ui.vertical_centered(|ui| { - ui.set_width(300.0); - { - let data = storage.get_or_insert_default_mut::(); - egui::TextEdit::singleline(&mut data.0) - .hint_text(localization.get("persisted-text-box-content")) - .show(ui); - } - if ui.button(localization.get("save")).clicked() { - storage.save() - } - }); - }); -} - -fn path2d_demo_plugin(session: &mut Session) { - session - .install_plugin(DefaultSessionPlugin) - .add_startup_system(path2d_demo_startup) - .add_system_to_stage(Update, back_to_menu_ui); -} - -fn path2d_demo_startup( - meta: Root, - mut entities: ResMut, - mut transforms: CompMut, - mut cameras: CompMut, - mut path2ds: CompMut, -) { - spawn_default_camera(&mut entities, &mut transforms, &mut cameras); - - let ent = entities.create(); - transforms.insert(ent, default()); - const SIZE: f32 = 40.; - path2ds.insert( - ent, - Path2d { - color: meta.path2d_color, - points: vec![ - vec2(-SIZE, 0.), - vec2(0., SIZE), - vec2(SIZE, 0.), - vec2(-SIZE, 0.), - ], - thickness: 2.0, - ..default() - }, - ); -} - -/// Simple UI system that shows a button at the bottom of the screen to delete the current session -/// and go back to the main menu. -fn back_to_menu_ui( - ctx: Res, - mut sessions: ResMut, - mut session_options: ResMut, - localization: Localization, -) { - egui::TopBottomPanel::bottom("back-to-menu") - .frame(egui::Frame::none()) - .show_separator_line(false) - .show(&ctx, |ui| { - ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { - ui.add_space(20.0); - if ui.button(localization.get("back-to-menu")).clicked() { - session_options.delete = true; - sessions.create("menu").install_plugin(menu_plugin); - } - }); - }); -} - -/// This is an example widget system. -fn demo_widget( - // Widget systems must have an `In<&mut egui::Ui>` parameter as their first argument. - mut ui: In<&mut egui::Ui>, - // They can have any normal bones system parameters - meta: Root, - egui_textures: Res, - // And they may return an `egui::Response` or any other value. -) -> egui::Response { - ui.label("Demo Widget"); - // When using a bones image in egui, we have to get it's corresponding egui texture - // from the egui textures resource. - ui.image(egui::load::SizedTexture::new( - egui_textures.get(meta.menu_image), - [50., 50.], - )) -} diff --git a/demos/hello_world_wgpu/src/main.rs b/demos/hello_world_wgpu/src/main.rs index 6554e1b1e6..87c14910be 100644 --- a/demos/hello_world_wgpu/src/main.rs +++ b/demos/hello_world_wgpu/src/main.rs @@ -39,22 +39,19 @@ fn main() { // Create a new session for the game world. Each session is it's own bones world with it's own // plugins, systems, and entities. - let world_session = game + game .sessions - .create("world") - .install_plugin(sprite_demo_plugin); - world_session - // Install the default bones_framework plugin for this session - .install_plugin(DefaultSessionPlugin) - // Add our menu system to the update stage - //.add_system_to_stage(Update, menu_system) - .add_system_to_stage(Update, test); + .create_with("world", |session: &mut SessionBuilder| { + session.install_plugin(sprite_demo_plugin); + session.add_system_to_stage(Update, test); + //session.add_system_to_stage(Update, menu_system); + }); BonesWgpuRenderer::new(game).run(); } /// Plugin for running the sprite demo. -fn sprite_demo_plugin(session: &mut Session) { +fn sprite_demo_plugin(session: &mut SessionBuilder) { session .install_plugin(DefaultSessionPlugin) .add_startup_system(sprite_demo_startup) @@ -255,7 +252,7 @@ fn move_sprite( } /// System to render the home menu. -fn menu_system(ctx: Res) { +fn _menu_system(ctx: Res) { egui::CentralPanel::default().show(&ctx, |ui| { ui.label("Hello World"); }); diff --git a/framework_crates/bones_framework/Cargo.toml b/framework_crates/bones_framework/Cargo.toml index 0bbd98dce3..94df3998b9 100644 --- a/framework_crates/bones_framework/Cargo.toml +++ b/framework_crates/bones_framework/Cargo.toml @@ -122,8 +122,8 @@ serde = { version = "1.0", features = ["derive"] } image = { version = "0.24", default-features = false } # Gui -egui = { version = "0.30", optional = true } -egui_plot = "0.30" +egui = { version = "0.31", optional = true } +egui_plot = "0.31" ttf-parser = { version = "0.24", default-features = false, optional = true } # Audio diff --git a/framework_crates/bones_framework/src/render/transform.rs b/framework_crates/bones_framework/src/render/transform.rs index bb15226796..428587f6ea 100644 --- a/framework_crates/bones_framework/src/render/transform.rs +++ b/framework_crates/bones_framework/src/render/transform.rs @@ -49,6 +49,7 @@ impl Transform { Self { scale, ..default() } } + /// Create a transform from a transformation matrix. pub fn from_matrix(matrix: Mat4) -> Self { let (scale, rotation, translation) = matrix.to_scale_rotation_translation(); diff --git a/framework_crates/bones_framework/src/render/ui/widgets.rs b/framework_crates/bones_framework/src/render/ui/widgets.rs index 8a55b3122b..9404589711 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets.rs @@ -75,7 +75,7 @@ pub struct MarginMeta { pub right: f32, } -impl From for egui::Margin { +impl From for egui::epaint::Marginf { fn from(m: MarginMeta) -> Self { Self { left: m.left, diff --git a/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs b/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs index de5b8971c0..64ec17a062 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets/bordered_button.rs @@ -16,8 +16,8 @@ pub struct BorderedButton<'a> { default_border: Option<&'a BorderImageMeta>, on_focus_border: Option<&'a BorderImageMeta>, on_click_border: Option<&'a BorderImageMeta>, - margin: egui::Margin, - padding: egui::Margin, + margin: egui::epaint::Marginf, + padding: egui::epaint::Marginf, } impl<'a> BorderedButton<'a> { @@ -78,7 +78,7 @@ impl<'a> BorderedButton<'a> { /// Set the margin. This will be applied on the outside of the border. #[must_use = "You must call .show() to render the button"] - pub fn margin(mut self, margin: impl Into) -> Self { + pub fn margin(mut self, margin: impl Into) -> Self { self.margin = margin.into(); self @@ -86,7 +86,7 @@ impl<'a> BorderedButton<'a> { /// Set the padding. This will be applied on the inside of the border. #[must_use = "You must call .show() to render the button"] - pub fn padding(mut self, padding: impl Into) -> Self { + pub fn padding(mut self, padding: impl Into) -> Self { self.padding = padding.into(); self diff --git a/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs b/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs index 807e7b5d08..7b98fa242d 100644 --- a/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs +++ b/framework_crates/bones_framework/src/render/ui/widgets/bordered_frame.rs @@ -5,9 +5,9 @@ pub struct BorderedFrame { bg_handle: Handle, border_scale: f32, texture_size: egui::Vec2, - texture_border_size: egui::Margin, - padding: egui::Margin, - margin: egui::Margin, + texture_border_size: egui::epaint::Marginf, + padding: egui::epaint::Marginf, + margin: egui::epaint::Marginf, border_only: bool, } @@ -29,7 +29,7 @@ impl BorderedFrame { /// Set the padding. This will be applied on the inside of the border. #[must_use = "You must call .show() to render the frame"] - pub fn padding(mut self, margin: impl Into) -> Self { + pub fn padding(mut self, margin: impl Into) -> Self { self.padding = margin.into(); self @@ -37,7 +37,7 @@ impl BorderedFrame { /// Set the margin. This will be applied on the outside of the border. #[must_use = "You must call .show() to render the frame"] - pub fn margin(mut self, margin: egui::Margin) -> Self { + pub fn margin(mut self, margin: egui::epaint::Marginf) -> Self { self.margin = margin; self @@ -124,13 +124,13 @@ impl BorderedFrame { let b = self.texture_border_size; let pr = paint_rect; // UV border - let buv = egui::Margin { + let buv = egui::epaint::Marginf { left: b.left / s.x, right: b.right / s.x, top: b.top / s.y, bottom: b.bottom / s.y, }; - let b = egui::Margin { + let b = egui::epaint::Marginf { left: b.left * self.border_scale, right: b.right * self.border_scale, top: b.top * self.border_scale, @@ -247,7 +247,7 @@ impl BorderedFrame { white, ); - egui::Shape::Mesh(mesh) + egui::Shape::Mesh(mesh.into()) } } diff --git a/framework_crates/bones_wgpu_renderer/Cargo.toml b/framework_crates/bones_wgpu_renderer/Cargo.toml index 4750a68da3..fef4aaa9d2 100644 --- a/framework_crates/bones_wgpu_renderer/Cargo.toml +++ b/framework_crates/bones_wgpu_renderer/Cargo.toml @@ -14,8 +14,8 @@ keywords.workspace = true bones_framework = { version = "0.4.0", path = "../bones_framework" } bones_schema = { path = "../bones_schema" } winit = "0.30.9" -#Needs to be 23 because of bones_bevy_renderer dependecies, we need to upgrade to 24^ later -wgpu = "23.0" +wgpu = "24" +profiling = "1.0.16" pollster = "0.4.0" env_logger = "0.11.6" log = "0.4" @@ -28,9 +28,10 @@ lyon = "1.0.1" guillotiere = "0.6.2" anyhow = "1.0.98" -egui = "0.30" -egui-wgpu = "0.30" -egui-winit = "0.30" +egui = "0.31" +egui-wgpu = "0.31" +egui-winit = "0.31" + serde = "1.0.188" serde_yaml = "0.9" diff --git a/framework_crates/bones_wgpu_renderer/src/atlas_pool.rs b/framework_crates/bones_wgpu_renderer/src/atlas_pool.rs index cb0256f848..c4ad965aba 100644 --- a/framework_crates/bones_wgpu_renderer/src/atlas_pool.rs +++ b/framework_crates/bones_wgpu_renderer/src/atlas_pool.rs @@ -3,8 +3,6 @@ use crossbeam_channel::bounded; use guillotiere::{size2, AllocId, Allocation, AtlasAllocator}; use std::sync::Arc; -use crate::sprite::AtlasPoolHandle; - /// Code for the AtlasPool and the TextureAtlas. /// A pool of texture atlases, each capable of holding multiple sprites. diff --git a/framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs b/framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs index 1b14e9a7ac..505ba970d7 100644 --- a/framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs +++ b/framework_crates/bones_wgpu_renderer/src/dynamic_storage.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use image::buffer; - pub struct DynamicBuffer { pub layout: Arc, pub buffer: wgpu::Buffer, diff --git a/framework_crates/bones_wgpu_renderer/src/lib.rs b/framework_crates/bones_wgpu_renderer/src/lib.rs index a9e1d2d2b4..6bad8c8672 100644 --- a/framework_crates/bones_wgpu_renderer/src/lib.rs +++ b/framework_crates/bones_wgpu_renderer/src/lib.rs @@ -18,8 +18,7 @@ use winit::{ use bones_framework::{ glam::*, input::gilrs::process_gamepad_events, - prelude::{self as bones, BitSet, ComponentIterBitset}, - render::camera, + prelude::{self as bones}, }; use egui_wgpu::ScreenDescriptor; @@ -36,7 +35,6 @@ mod ui; use dynamic_storage::DynamicBuffer; use sprite::*; -use texture::Texture; use ui::{default_load_progress, EguiRenderer}; /// The prelude @@ -117,7 +115,7 @@ impl BonesWgpuRenderer { pub fn run(mut self) { //Start wgpu - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default()); + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); let (adapter, device, queue) = async { let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions::default()) diff --git a/framework_crates/bones_wgpu_renderer/src/sprite.rs b/framework_crates/bones_wgpu_renderer/src/sprite.rs index 23a0944d51..3802358248 100644 --- a/framework_crates/bones_wgpu_renderer/src/sprite.rs +++ b/framework_crates/bones_wgpu_renderer/src/sprite.rs @@ -1,7 +1,6 @@ use crate::{atlas_pool::AtlasPool, *}; use bones_framework::{ - prelude::{self as bones, BitSet, ComponentIterBitset, Transform, Ustr}, - render::transform, + prelude::{self as bones, BitSet, ComponentIterBitset, Ustr}, }; use guillotiere::Allocation; use std::collections::HashMap; @@ -271,14 +270,14 @@ pub fn update_atlas_pool(game: &mut bones::Game, atlas_pool: &mut AtlasPool) { println!("Texture {:?}", atlas_pool.atlases[atlas_id].texture); - let data_layout = wgpu::ImageDataLayout { + let data_layout = wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(padded_stride as u32), rows_per_image: Some(h), }; let queue = queue.borrow().unwrap(); queue.get().write_texture( - wgpu::ImageCopyTexture { + wgpu::TexelCopyTextureInfo { texture: &atlas_pool.atlases[atlas_id].texture, mip_level: 0, origin, diff --git a/framework_crates/bones_wgpu_renderer/src/texture.rs b/framework_crates/bones_wgpu_renderer/src/texture.rs index 1b4d96858e..2998d6dc56 100644 --- a/framework_crates/bones_wgpu_renderer/src/texture.rs +++ b/framework_crates/bones_wgpu_renderer/src/texture.rs @@ -49,16 +49,14 @@ impl Texture { }); queue.write_texture( - //Change to TexelCopyTextureInfo when updating to wgpu 24 - wgpu::ImageCopyTexture { + wgpu::TexelCopyTextureInfo { aspect: wgpu::TextureAspect::All, texture: &texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, }, &rgba, - //Change to TexelCopyBufferLayout when updating to wgpu 24 - wgpu::ImageDataLayout { + wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(4 * dimensions.0), rows_per_image: Some(dimensions.1), diff --git a/framework_crates/bones_wgpu_renderer/src/texture_file.rs b/framework_crates/bones_wgpu_renderer/src/texture_file.rs index 788f9f9e5d..a5d3f6a48c 100644 --- a/framework_crates/bones_wgpu_renderer/src/texture_file.rs +++ b/framework_crates/bones_wgpu_renderer/src/texture_file.rs @@ -31,15 +31,15 @@ pub fn dump_texture_to_png( // 2) Copy the texture to the buffer encoder.copy_texture_to_buffer( - wgpu::ImageCopyTexture { + wgpu::TexelCopyTextureInfo { texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, - wgpu::ImageCopyBuffer { + wgpu::TexelCopyBufferInfo { buffer: &dst_buffer, - layout: wgpu::ImageDataLayout { + layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(padded_bytes_per_row as u32), rows_per_image: Some(height),