From 2660dd2566ab88098a2d8a4c93f644f810f4a271 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 23 Sep 2025 21:48:39 -0700 Subject: [PATCH 01/10] [update] lambda-rs to use the latest winit and wgpu for rendering, completely deprecating the gfx-rs implementation. --- Cargo.lock | 2100 ++++++++++------- crates/lambda-rs-args/README.md | 214 +- .../docs/backend_and_adapter_providers.md | 261 ++ ..._aware_providers_and_interactive_wizard.md | 350 +++ crates/lambda-rs-args/examples/basic.rs | 30 + crates/lambda-rs-args/examples/bools.rs | 20 + crates/lambda-rs-args/examples/env_config.rs | 23 + crates/lambda-rs-args/examples/equals.rs | 22 + crates/lambda-rs-args/examples/exclusives.rs | 29 + crates/lambda-rs-args/examples/positionals.rs | 27 + crates/lambda-rs-args/examples/short_count.rs | 17 + crates/lambda-rs-args/examples/subcommands.rs | 37 + crates/lambda-rs-args/src/lib.rs | 1189 +++++++++- crates/lambda-rs-logging/src/handler.rs | 7 +- crates/lambda-rs-platform/Cargo.toml | 49 +- crates/lambda-rs-platform/src/gfx/adapter.rs | 1 - crates/lambda-rs-platform/src/gfx/api.rs | 18 - .../lambda-rs-platform/src/gfx/assembler.rs | 113 - crates/lambda-rs-platform/src/gfx/buffer.rs | 218 -- crates/lambda-rs-platform/src/gfx/command.rs | 418 ---- crates/lambda-rs-platform/src/gfx/fence.rs | 145 -- .../lambda-rs-platform/src/gfx/framebuffer.rs | 70 - crates/lambda-rs-platform/src/gfx/gpu.rs | 225 -- crates/lambda-rs-platform/src/gfx/mod.rs | 97 - crates/lambda-rs-platform/src/gfx/pipeline.rs | 197 -- .../lambda-rs-platform/src/gfx/render_pass.rs | 258 -- crates/lambda-rs-platform/src/gfx/resource.rs | 5 - crates/lambda-rs-platform/src/gfx/shader.rs | 154 -- crates/lambda-rs-platform/src/gfx/surface.rs | 291 --- crates/lambda-rs-platform/src/gfx/viewport.rs | 86 - crates/lambda-rs-platform/src/lib.rs | 4 +- crates/lambda-rs-platform/src/obj/mod.rs | 11 +- crates/lambda-rs-platform/src/rand/mod.rs | 5 +- crates/lambda-rs-platform/src/shader/mod.rs | 25 + crates/lambda-rs-platform/src/shader/naga.rs | 106 + .../mod.rs => shader/shaderc_backend.rs} | 66 +- crates/lambda-rs-platform/src/shader/types.rs | 50 + crates/lambda-rs-platform/src/shaderc.rs | 9 + crates/lambda-rs-platform/src/wgpu/mod.rs | 516 ++++ crates/lambda-rs-platform/src/winit/mod.rs | 46 +- crates/lambda-rs/Cargo.toml | 23 +- crates/lambda-rs/examples/minimal.rs | 5 +- crates/lambda-rs/examples/push_constants.rs | 68 +- crates/lambda-rs/examples/triangle.rs | 49 +- crates/lambda-rs/examples/triangles.rs | 75 +- crates/lambda-rs/src/component.rs | 10 +- crates/lambda-rs/src/events.rs | 2 +- crates/lambda-rs/src/math/matrix.rs | 16 +- crates/lambda-rs/src/render/buffer.rs | 294 ++- crates/lambda-rs/src/render/command.rs | 125 +- crates/lambda-rs/src/render/mesh.rs | 6 +- crates/lambda-rs/src/render/mod.rs | 587 ++--- crates/lambda-rs/src/render/pipeline.rs | 284 ++- crates/lambda-rs/src/render/render_pass.rs | 75 +- crates/lambda-rs/src/render/shader.rs | 10 +- crates/lambda-rs/src/render/vertex.rs | 63 +- crates/lambda-rs/src/render/viewport.rs | 62 +- crates/lambda-rs/src/render/window.rs | 5 +- crates/lambda-rs/src/runtimes/application.rs | 182 +- crates/lambda-rs/src/runtimes/mod.rs | 5 +- docs/WGPU.md | 116 + docs/feature_roadmap_and_snippets.md | 249 ++ docs/rendering.md | 215 ++ tools/obj_loader/src/main.rs | 48 +- 64 files changed, 5787 insertions(+), 4296 deletions(-) create mode 100644 crates/lambda-rs-args/docs/backend_and_adapter_providers.md create mode 100644 crates/lambda-rs-args/docs/context_aware_providers_and_interactive_wizard.md create mode 100644 crates/lambda-rs-args/examples/basic.rs create mode 100644 crates/lambda-rs-args/examples/bools.rs create mode 100644 crates/lambda-rs-args/examples/env_config.rs create mode 100644 crates/lambda-rs-args/examples/equals.rs create mode 100644 crates/lambda-rs-args/examples/exclusives.rs create mode 100644 crates/lambda-rs-args/examples/positionals.rs create mode 100644 crates/lambda-rs-args/examples/short_count.rs create mode 100644 crates/lambda-rs-args/examples/subcommands.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/adapter.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/api.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/assembler.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/buffer.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/command.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/fence.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/framebuffer.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/gpu.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/mod.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/pipeline.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/render_pass.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/resource.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/shader.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/surface.rs delete mode 100644 crates/lambda-rs-platform/src/gfx/viewport.rs create mode 100644 crates/lambda-rs-platform/src/shader/mod.rs create mode 100644 crates/lambda-rs-platform/src/shader/naga.rs rename crates/lambda-rs-platform/src/{shaderc/mod.rs => shader/shaderc_backend.rs} (67%) create mode 100644 crates/lambda-rs-platform/src/shader/types.rs create mode 100644 crates/lambda-rs-platform/src/shaderc.rs create mode 100644 crates/lambda-rs-platform/src/wgpu/mod.rs create mode 100644 docs/WGPU.md create mode 100644 docs/feature_roadmap_and_snippets.md create mode 100644 docs/rendering.md diff --git a/Cargo.lock b/Cargo.lock index fc499c1b..4bd9310a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,22 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "abscissa_core" @@ -36,11 +52,11 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74f5722bc48763cb9d81d8427ca05b6aa2842f6632cf8e4c0a29eef9baececcc" dependencies = [ - "darling 0.10.2", + "darling", "ident_case", "proc-macro2", "quote", - "syn", + "syn 1.0.102", "synstructure", ] @@ -65,6 +81,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if 1.0.0", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -74,6 +103,42 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-activity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +dependencies = [ + "android-properties", + "bitflags 2.9.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -100,26 +165,38 @@ checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" -version = "0.5.2" +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 = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] name = "ash" -version = "0.32.1" +version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ "libloading", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -140,25 +217,25 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide 0.4.4", + "miniz_oxide", "object 0.27.1", "rustc-demangle", ] [[package]] name = "bit-set" -version = "0.5.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +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 = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -166,12 +243,37 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys", + "objc2", +] + [[package]] name = "bumpalo" version = "3.8.0" @@ -180,9 +282,9 @@ checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder" @@ -190,17 +292,36 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "calloop" -version = "0.10.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ + "bitflags 2.9.0", "log", - "nix 0.24.2", - "slotmap 1.0.6", + "polling", + "rustix 0.38.44", + "slab", "thiserror", - "vec_map", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", ] [[package]] @@ -240,7 +361,7 @@ version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb04b88bd5b2036e30704f95c6ee16f3b5ca3b4ca307da2889d9006648e5c88" dependencies = [ - "petgraph 0.6.0", + "petgraph", "semver 1.0.4", "serde", "toml", @@ -271,23 +392,23 @@ dependencies = [ "gimli", "git2", "humantime-serde", - "indexmap", + "indexmap 1.9.1", "lazy_static", "libc", "memmap", - "nix 0.23.1", + "nix", "num_cpus", "object 0.28.4", "proc-macro2", "procfs", - "quick-xml", + "quick-xml 0.22.0", "quote", "regex", "rustc-demangle", "rustc_version", "serde", "serde_json", - "syn", + "syn 1.0.102", "toml", "tracing", "tracing-subscriber 0.2.25", @@ -316,6 +437,12 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "0.1.10" @@ -328,6 +455,12 @@ 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 = "chrono" version = "0.4.19" @@ -350,7 +483,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term 0.12.1", "atty", - "bitflags", + "bitflags 1.3.2", "strsim 0.8.0", "textwrap", "unicode-width", @@ -367,34 +500,13 @@ dependencies = [ ] [[package]] -name = "cocoa" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types 0.3.2", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.0" +name = "codespan-reporting" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" dependencies = [ - "bitflags", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types 0.3.2", - "libc", - "objc", + "termcolor", + "unicode-width", ] [[package]] @@ -409,16 +521,29 @@ dependencies = [ ] [[package]] -name = "copyless" -version = "0.1.5" +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -426,44 +551,31 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "libc", ] [[package]] name = "core-graphics-types" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", - "foreign-types 0.3.2", - "libc", -] - -[[package]] -name = "core-text" -version = "19.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" -dependencies = [ - "core-foundation", - "core-graphics", - "foreign-types 0.3.2", "libc", ] @@ -509,33 +621,10 @@ dependencies = [ ] [[package]] -name = "crossfont" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66b1c1979c4362323f03ab6bf7fb522902bfc418e0c37319ab347f9561d980f" -dependencies = [ - "cocoa", - "core-foundation", - "core-foundation-sys", - "core-graphics", - "core-text", - "dwrote", - "foreign-types 0.5.0", - "freetype-rs", - "libc", - "log", - "objc", - "once_cell", - "pkg-config", - "servo-fontconfig", - "winapi", -] - -[[package]] -name = "cty" -version = "0.2.2" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "curl" @@ -567,6 +656,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "cvss" version = "1.0.2" @@ -576,35 +671,14 @@ dependencies = [ "serde", ] -[[package]] -name = "d3d12" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daefd788d1e96e0a9d66dee4b828b883509bc3ea9ce30665f04c3246372690c" -dependencies = [ - "bitflags", - "libloading", - "winapi", -] - [[package]] name = "darling" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core 0.10.2", - "darling_macro 0.10.2", -] - -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", + "darling_core", + "darling_macro", ] [[package]] @@ -618,21 +692,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.9.3", - "syn", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn", + "syn 1.0.102", ] [[package]] @@ -641,20 +701,9 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core 0.10.2", - "quote", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", + "darling_core", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -668,17 +717,6 @@ dependencies = [ "gzip-header", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "difflib" version = "0.4.0" @@ -693,13 +731,22 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dlib" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading", ] +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + [[package]] name = "downcast" version = "0.11.0" @@ -712,29 +759,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" -dependencies = [ - "serde", -] - -[[package]] -name = "dwrote" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" -dependencies = [ - "lazy_static", - "libc", - "serde", - "serde_derive", - "winapi", - "wio", -] - [[package]] name = "either" version = "1.8.0" @@ -742,23 +766,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] -name = "expat-sys" -version = "2.1.6" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" -dependencies = [ - "cmake", - "pkg-config", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "external-memory" -version = "0.0.1" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4dfe8d292b014422776a8c516862d2bff8a81b223a4461dfdc45f3862dc9d39" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "bitflags", - "drm-fourcc", + "libc", + "windows-sys 0.61.0", ] [[package]] @@ -767,12 +787,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -[[package]] -name = "fixedbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" - [[package]] name = "fixedbitset" version = "0.4.0" @@ -788,7 +802,7 @@ dependencies = [ "cfg-if 1.0.0", "crc32fast", "libc", - "miniz_oxide 0.4.4", + "miniz_oxide", ] [[package]] @@ -807,13 +821,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" @@ -822,7 +833,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]] @@ -833,15 +844,9 @@ checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] -[[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" @@ -864,28 +869,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" -[[package]] -name = "freetype-rs" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb" -dependencies = [ - "bitflags", - "freetype-sys", - "libc", -] - -[[package]] -name = "freetype-sys" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" -dependencies = [ - "cmake", - "libc", - "pkg-config", -] - [[package]] name = "fs-err" version = "2.6.0" @@ -893,21 +876,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ebd3504ad6116843b8375ad70df74e7bfe83cac77a1f3fe73200c844d43bfe0" [[package]] -name = "fxhash" -version = "0.2.1" +name = "generational-arena" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" dependencies = [ - "byteorder", + "cfg-if 0.1.10", ] [[package]] -name = "generational-arena" -version = "0.2.8" +name = "gethostname" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "cfg-if 0.1.10", + "rustix 1.1.2", + "windows-targets 0.52.6", ] [[package]] @@ -918,212 +902,134 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] -name = "gfx-auxil" -version = "0.10.0" +name = "getrandom" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1694991b11d642680e82075a75c7c2bd75556b805efa7660b705689f05b1ab1c" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ - "fxhash", - "gfx-hal", - "spirv_cross", + "cfg-if 1.0.0", + "libc", + "r-efi", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] -name = "gfx-backend-dx11" -version = "0.9.0" +name = "gimli" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9e453baf3aaef2b0c354ce0b3d63d76402e406a59b64b7182d123cfa6635ae" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" dependencies = [ - "arrayvec", - "bitflags", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", - "libloading", - "log", - "parking_lot 0.11.2", - "range-alloc", - "raw-window-handle 0.3.4", - "smallvec 1.7.0", - "spirv_cross", - "thunderdome", - "winapi", - "wio", + "fallible-iterator", + "indexmap 1.9.1", + "stable_deref_trait", ] [[package]] -name = "gfx-backend-dx12" -version = "0.9.0" +name = "git2" +version = "0.13.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f09e9d8c2aa69e9a21eb83c0f5d1a286c6d37da011f796e550d180b08090ce" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" dependencies = [ - "arrayvec", - "bit-set", - "bitflags", - "d3d12", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", + "bitflags 1.3.2", + "libc", + "libgit2-sys", "log", - "parking_lot 0.11.2", - "range-alloc", - "raw-window-handle 0.3.4", - "smallvec 1.7.0", - "spirv_cross", - "thunderdome", - "winapi", + "openssl-probe", + "openssl-sys", + "url", ] [[package]] -name = "gfx-backend-empty" -version = "0.9.0" +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c8f813c47791918aa00dc9c9ddf961d23fa8c2a5d869e6cb8ea84f944820f4" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" dependencies = [ - "gfx-hal", + "khronos_api", "log", - "raw-window-handle 0.3.4", + "xml-rs", ] [[package]] -name = "gfx-backend-gl" -version = "0.9.0" +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "glow" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bae057fc3a0ab23ecf97ae51d4017d27d5ddf0aab16ee6dcb58981af88c3152" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" dependencies = [ - "arrayvec", - "bitflags", - "fxhash", - "gfx-hal", - "glow", "js-sys", - "khronos-egl", - "libloading", - "log", - "naga", - "parking_lot 0.11.2", - "raw-window-handle 0.3.4", + "slotmap", "wasm-bindgen", "web-sys", ] [[package]] -name = "gfx-backend-metal" -version = "0.9.0" +name = "glutin_wgl_sys" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76c10e91bbe274122a9bf196ac16a57dd2db67cd7c4299eeb962503aebacc013" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" dependencies = [ - "arrayvec", - "bitflags", - "block", - "cocoa-foundation", - "copyless", - "core-graphics-types", - "foreign-types 0.3.2", - "fxhash", - "gfx-hal", - "log", - "metal", - "naga", - "objc", - "parking_lot 0.11.2", - "profiling", - "range-alloc", - "raw-window-handle 0.3.4", - "storage-map", + "gl_generator", ] [[package]] -name = "gfx-backend-vulkan" -version = "0.9.0" +name = "gpu-alloc" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9861ec855acbbc65c0e4f966d761224886e811dc2c6d413a4776e9293d0e5c0" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "arrayvec", - "ash", - "byteorder", - "core-graphics-types", - "gfx-hal", - "gfx-renderdoc", - "inplace_it", - "log", - "objc", - "parking_lot 0.11.2", - "raw-window-handle 0.3.4", - "smallvec 1.7.0", - "winapi", + "bitflags 2.9.0", + "gpu-alloc-types", ] [[package]] -name = "gfx-hal" -version = "0.9.0" +name = "gpu-alloc-types" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbb575ea793dd0507b3082f4f2cde62dc9f3cebd98f5cd49ba2a4da97a976fd" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags", - "external-memory", - "naga", - "raw-window-handle 0.3.4", - "thiserror", + "bitflags 2.9.0", ] [[package]] -name = "gfx-renderdoc" -version = "0.1.0" +name = "gpu-allocator" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8027995e247e2426d3a00d13f5191dd56c314bff02dc4b54cbf727f1ba9c40a" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ - "libloading", "log", - "renderdoc-sys", -] - -[[package]] -name = "gimli" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", + "presser", + "thiserror", + "windows", ] [[package]] -name = "git2" -version = "0.13.25" +name = "gpu-descriptor" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", + "bitflags 2.9.0", + "gpu-descriptor-types", + "hashbrown 0.15.2", ] [[package]] -name = "glob" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" - -[[package]] -name = "glow" -version = "0.9.0" +name = "gpu-descriptor-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "js-sys", - "slotmap 0.4.3", - "wasm-bindgen", - "web-sys", + "bitflags 2.9.0", ] [[package]] @@ -1143,7 +1049,7 @@ checksum = "90454ce4de40b7ca6a8968b5ef367bdab48413962588d0d2b1638d60090c35d7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -1161,6 +1067,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1170,6 +1085,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1179,6 +1100,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "home" version = "0.5.3" @@ -1204,6 +1131,17 @@ dependencies = [ "serde", ] +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2", + "dispatch", + "objc2", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1228,26 +1166,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] [[package]] -name = "inplace_it" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca" - -[[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", + "equivalent", + "hashbrown 0.15.2", ] [[package]] @@ -1265,6 +1195,22 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if 1.0.0", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -1282,23 +1228,31 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +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", + "pkg-config", ] +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "lambda-obj-loader" version = "2023.1.28" @@ -1330,19 +1284,14 @@ version = "2023.1.30" name = "lambda-rs-platform" version = "2023.1.30" dependencies = [ - "cfg-if 1.0.0", - "gfx-backend-dx11", - "gfx-backend-dx12", - "gfx-backend-empty", - "gfx-backend-gl", - "gfx-backend-metal", - "gfx-backend-vulkan", - "gfx-hal", "lambda-rs-logging", "mockall", + "naga", "obj-rs", + "pollster", "rand", "shaderc", + "wgpu", "winit", ] @@ -1354,9 +1303,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.133" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libgit2-sys" @@ -1374,12 +1323,23 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if 1.0.0", - "winapi", + "windows-targets 0.52.6", +] + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.9.0", + "libc", + "redox_syscall 0.5.17", ] [[package]] @@ -1408,6 +1368,24 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + [[package]] name = "lock_api" version = "0.4.6" @@ -1480,9 +1458,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.5.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -1498,16 +1476,17 @@ dependencies = [ [[package]] name = "metal" -version = "0.23.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0514f491f4cc03632ab399ee01e2c1c1b12d3e1cf2d667c1ff5f87d6dcd2084" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags", + "bitflags 2.9.0", "block", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "log", "objc", + "paste", ] [[package]] @@ -1526,27 +1505,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - [[package]] name = "mockall" version = "0.11.3" @@ -1571,37 +1529,44 @@ dependencies = [ "cfg-if 1.0.0", "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] name = "naga" -version = "0.5.0" +version = "23.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef670817eef03d356d5a509ea275e7dd3a78ea9e24261ea3cb2dfed1abb08f64" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ + "arrayvec", "bit-set", - "bitflags", - "fxhash", + "bitflags 2.9.0", + "cfg_aliases", + "codespan-reporting", + "hexf-parse", + "indexmap 2.9.0", "log", - "num-traits", - "petgraph 0.5.1", - "rose_tree", - "spirv_headers", + "petgraph", + "pp-rs", + "rustc-hash", + "spirv", + "termcolor", "thiserror", + "unicode-xid", ] [[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", + "bitflags 2.9.0", "jni-sys", + "log", "ndk-sys", "num_enum", - "raw-window-handle 0.5.0", + "raw-window-handle", "thiserror", ] @@ -1611,40 +1576,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" -[[package]] -name = "ndk-glue" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0434fabdd2c15e0aab768ca31d5b7b333717f03cf02037d5a0a3ff3c278ed67f" -dependencies = [ - "libc", - "log", - "ndk", - "ndk-context", - "ndk-macro", - "ndk-sys", - "once_cell", - "parking_lot 0.12.1", -] - -[[package]] -name = "ndk-macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" -dependencies = [ - "darling 0.13.4", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ndk-sys" -version = "0.4.0" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21d83ec9c63ec5bf950200a8e508bdad6659972187b625469f58ef8c08e29046" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] @@ -1655,25 +1591,13 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cfg-if 1.0.0", "libc", "memoffset", ] -[[package]] -name = "nix" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "libc", - "memoffset", -] - [[package]] name = "nom" version = "7.1.0" @@ -1703,9 +1627,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1716,30 +1640,30 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] [[package]] name = "num_enum" -version = "0.5.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "085fe377a4b2805c0fbc09484415ec261174614b7f080b0e0d520456ac421a67" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ - "derivative", "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.5.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5249369707a1e07b39f78d98c8f34e00aca7dcb053812fdbb5ad7be82c1bba38" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1759,18 +1683,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "cc", + "objc-sys", + "objc2-encode", ] +[[package]] +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + [[package]] name = "object" version = "0.27.1" @@ -1792,9 +1728,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.15.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" @@ -1816,23 +1752,30 @@ dependencies = [ ] [[package]] -name = "owning_ref" -version = "0.4.1" +name = "orbclient" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" dependencies = [ - "stable_deref_trait", + "libredox", ] [[package]] -name = "parking_lot" -version = "0.11.2" +name = "owned_ttf_parser" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", + "ttf-parser", +] + +[[package]] +name = "owning_ref" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" +dependencies = [ + "stable_deref_trait", ] [[package]] @@ -1842,35 +1785,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", - "instant", "libc", - "redox_syscall", - "smallvec 1.7.0", - "winapi", + "redox_syscall 0.2.10", + "smallvec 1.15.1", + "windows-sys 0.36.1", ] [[package]] -name = "parking_lot_core" -version = "0.9.3" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall", - "smallvec 1.7.0", - "windows-sys", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1878,31 +1813,21 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -[[package]] -name = "petgraph" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" -dependencies = [ - "fixedbitset 0.2.0", - "indexmap", -] - [[package]] name = "petgraph" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ - "fixedbitset 0.4.0", - "indexmap", + "fixedbitset", + "indexmap 1.9.1", ] [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pkg-config" @@ -1920,15 +1845,32 @@ dependencies = [ ] [[package]] -name = "png" -version = "0.17.6" +name = "polling" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "bitflags", - "crc32fast", - "flate2", - "miniz_oxide 0.5.4", + "cfg-if 1.0.0", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.2", + "windows-sys 0.61.0", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "pp-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" +dependencies = [ + "unicode-xid", ] [[package]] @@ -1967,6 +1909,12 @@ dependencies = [ "termtree", ] +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro-crate" version = "1.1.0" @@ -1979,9 +1927,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -1992,7 +1940,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "chrono", "flate2", @@ -2016,15 +1964,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + [[package]] name = "quote" -version = "1.0.10" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -2052,7 +2015,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.8", ] [[package]] @@ -2063,39 +2026,35 @@ checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" [[package]] name = "raw-window-handle" -version = "0.3.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28f55143d0548dad60bb4fbdc835a3d7ac6acc3324506450c5fdd6e42903a76" -dependencies = [ - "libc", - "raw-window-handle 0.4.3", -] +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] -name = "raw-window-handle" -version = "0.4.3" +name = "redox_syscall" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ - "cty", + "bitflags 1.3.2", ] [[package]] -name = "raw-window-handle" -version = "0.5.0" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "cty", + "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -2126,18 +2085,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "renderdoc-sys" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" - -[[package]] -name = "rose_tree" -version = "0.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2" -dependencies = [ - "petgraph 0.5.1", -] +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "rustc-demangle" @@ -2145,6 +2095,12 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2154,6 +2110,32 @@ dependencies = [ "semver 1.0.4", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.0", +] + [[package]] name = "rustsec" version = "0.25.1" @@ -2177,19 +2159,16 @@ dependencies = [ ] [[package]] -name = "ryu" -version = "1.0.9" +name = "rustversion" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] -name = "safe_arch" -version = "0.5.2" +name = "ryu" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" -dependencies = [ - "bytemuck", -] +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "same-file" @@ -2224,12 +2203,13 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sctk-adwaita" -version = "0.4.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61270629cc6b4d77ec1907db1033d5c2e1a404c412743621981a871dc9c12339" +checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" dependencies = [ - "crossfont", + "ab_glyph", "log", + "memmap2", "smithay-client-toolkit", "tiny-skia", ] @@ -2271,22 +2251,32 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.132" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.132" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -2300,32 +2290,11 @@ dependencies = [ "serde", ] -[[package]] -name = "servo-fontconfig" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" -dependencies = [ - "libc", - "servo-fontconfig-sys", -] - -[[package]] -name = "servo-fontconfig-sys" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" -dependencies = [ - "expat-sys", - "freetype-sys", - "pkg-config", -] - [[package]] name = "shaderc" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58da8aaf4ad3508598cdf098567114c98d5f455de7d69b1213232ac557bc67ea" +checksum = "d6e2c757b804157350d8d79d718c756899226016486aab07a11dddf8741111a0" dependencies = [ "libc", "shaderc-sys", @@ -2333,9 +2302,9 @@ dependencies = [ [[package]] name = "shaderc-sys" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd76ec0bd25f2017a65250373485e43cdc81b5cb8fd83c6115375c8d018cdf9" +checksum = "a36f3465fce5830d33a58846b9c924f510a1e92bac181834c13b38405efe983b" dependencies = [ "cmake", "libc", @@ -2370,10 +2339,10 @@ dependencies = [ ] [[package]] -name = "slotmap" -version = "0.4.3" +name = "slab" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf34684c5767b87de9119790e92e9a1d60056be2ceeaf16a8e6ef13082aeab1" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" @@ -2395,9 +2364,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.7.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smartstring" @@ -2411,52 +2380,55 @@ dependencies = [ [[package]] name = "smithay-client-toolkit" -version = "0.16.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags", + "bitflags 2.9.0", "calloop", - "dlib", - "lazy_static", + "calloop-wayland-source", + "cursor-icon", + "libc", "log", "memmap2", - "nix 0.24.2", - "pkg-config", + "rustix 0.38.44", + "thiserror", + "wayland-backend", "wayland-client", + "wayland-csd-frame", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] -name = "socket2" -version = "0.4.2" +name = "smol_str" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ - "libc", - "winapi", + "serde", ] [[package]] -name = "spirv_cross" -version = "0.23.1" +name = "socket2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" dependencies = [ - "cc", - "js-sys", - "wasm-bindgen", + "libc", + "winapi", ] [[package]] -name = "spirv_headers" -version = "1.5.0" +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags", - "num-traits", + "bitflags 2.9.0", ] [[package]] @@ -2472,13 +2444,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "storage-map" -version = "0.3.0" +name = "strict-num" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" -dependencies = [ - "lock_api", -] +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" [[package]] name = "strsim" @@ -2493,16 +2462,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] -name = "strsim" -version = "0.10.0" +name = "syn" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "syn" -version = "1.0.102" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -2517,15 +2491,15 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", "unicode-xid", ] [[package]] name = "termcolor" -version = "1.1.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -2547,22 +2521,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -2574,12 +2548,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "thunderdome" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f685624f172cd0bde6f3363412455e81c018f2379fdf5a218e0be003f1bba642" - [[package]] name = "time" version = "0.1.43" @@ -2592,27 +2560,27 @@ dependencies = [ [[package]] name = "tiny-skia" -version = "0.7.0" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642680569bb895b16e4b9d181c60be1ed136fa0c9c7f11d004daf053ba89bf82" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if 1.0.0", - "png", - "safe_arch", + "log", "tiny-skia-path", ] [[package]] name = "tiny-skia-path" -version = "0.7.0" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c114d32f0c2ee43d585367cb013dfaba967ab9f62b90d9af0d696e955e70fa6c" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ "arrayref", "bytemuck", + "strict-num", ] [[package]] @@ -2659,7 +2627,7 @@ checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.102", ] [[package]] @@ -2711,13 +2679,19 @@ dependencies = [ "matchers", "regex", "sharded-slab", - "smallvec 1.7.0", + "smallvec 1.15.1", "thread_local", "tracing", "tracing-core", "tracing-log", ] +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -2739,6 +2713,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.9" @@ -2747,9 +2727,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "url" @@ -2778,9 +2758,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" @@ -2808,36 +2788,68 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if 1.0.0", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", - "lazy_static", "log", "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2845,104 +2857,260 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] -name = "wayland-client" -version = "0.29.5" +name = "wayland-backend" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ - "bitflags", + "cc", "downcast-rs", - "libc", - "nix 0.24.2", + "rustix 1.1.2", "scoped-tls", - "wayland-commons", - "wayland-scanner", + "smallvec 1.15.1", "wayland-sys", ] [[package]] -name = "wayland-commons" -version = "0.29.5" +name = "wayland-client" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ - "nix 0.24.2", - "once_cell", - "smallvec 1.7.0", - "wayland-sys", + "bitflags 2.9.0", + "rustix 1.1.2", + "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.0", + "cursor-icon", + "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.29.5" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "nix 0.24.2", + "rustix 1.1.2", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.5" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags", + "bitflags 2.9.0", + "wayland-backend", "wayland-client", - "wayland-commons", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.9.0", + "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 0.37.5", "quote", - "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.29.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" dependencies = [ "dlib", - "lazy_static", + "log", + "once_cell", "pkg-config", ] [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +dependencies = [ + "arrayvec", + "cfg_aliases", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec 1.15.1", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.9.0", + "bytemuck", + "cfg_aliases", + "document-features", + "indexmap 2.9.0", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec 1.15.1", + "thiserror", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.9.0", + "block", + "bytemuck", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec 1.15.1", + "thiserror", "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-types" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +dependencies = [ + "bitflags 2.9.0", + "js-sys", + "web-sys", ] [[package]] @@ -2976,90 +3144,386 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "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_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[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_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winit" -version = "0.27.5" +version = "0.29.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb796d6fbd86b2fd896c9471e6f04d39d750076ebe5680a3958f00f5ab97657c" +checksum = "4c824f11941eeae66ec71111cc2674373c772f482b58939bb4066b642aa2ffcf" dependencies = [ - "bitflags", - "cocoa", + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.9.0", + "bytemuck", + "calloop", + "cfg_aliases", "core-foundation", "core-graphics", - "dispatch", - "instant", + "cursor-icon", + "icrate", + "js-sys", "libc", "log", - "mio", + "memmap2", "ndk", - "ndk-glue", - "objc", + "ndk-sys", + "objc2", "once_cell", - "parking_lot 0.12.1", + "orbclient", "percent-encoding", - "raw-window-handle 0.4.3", - "raw-window-handle 0.5.0", + "raw-window-handle", + "redox_syscall 0.3.5", + "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", + "smol_str", + "unicode-segmentation", "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", "wayland-client", "wayland-protocols", + "wayland-protocols-plasma", "web-sys", - "windows-sys", + "web-time", + "windows-sys 0.48.0", "x11-dl", + "x11rb", + "xkbcommon-dl", ] [[package]] -name = "wio" -version = "0.2.2" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "x11-dl" @@ -3072,6 +3536,27 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.2", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "xcursor" version = "0.3.4" @@ -3081,12 +3566,51 @@ dependencies = [ "nom", ] +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "zeroize" version = "1.4.3" diff --git a/crates/lambda-rs-args/README.md b/crates/lambda-rs-args/README.md index f8e2a160..cc64de24 100644 --- a/crates/lambda-rs-args/README.md +++ b/crates/lambda-rs-args/README.md @@ -2,7 +2,215 @@ ![lambda-rs](https://img.shields.io/crates/d/lambda-rs-args) ![lambda-rs](https://img.shields.io/crates/v/lambda-rs-args) -Argument parsing for lambda-rs applications. +Lightweight, dependency-free CLI argument parsing used by lambda tools. -## Getting started -TODO +It provides a simple builder API, a non-panicking `parse` that returns rich errors, auto-generated usage/help, and pragmatic features like booleans with `--no-flag`, short-flag clusters and counts, positionals, env/config merging, subcommands, and validation helpers. + +See the examples in `crates/lambda-rs-args/examples/` for runnable snippets. + +## Quick Start + +Code: + +```rust +use args::{Argument, ArgumentParser, ArgumentType, ArgumentValue}; + +fn main() { + let parser = ArgumentParser::new("my-tool") + .with_description("Demo tool") + .with_author("you") + .with_argument(Argument::new("--name").is_required(true).with_type(ArgumentType::String)) + .with_argument(Argument::new("--count").with_type(ArgumentType::Integer).with_default_value(ArgumentValue::Integer(1))); + + let argv: Vec = std::env::args().collect(); + match parser.parse(&argv) { + Ok(parsed) => { + println!("name={}, count={}", parsed.get_string("--name").unwrap(), parsed.get_i64("--count").unwrap()); + } + Err(e) => eprintln!("{}", e), // includes --help output + } +} +``` + +CLI: + +``` +$ my-tool --help +Usage: my-tool [options] + +Demo tool + +Author(s): you + +Options: + --name + (required) + --count + [default: 1] +$ my-tool --name Alice +name=Alice, count=1 +``` + +## Features and Examples + +Each feature below shows a minimal code snippet and the CLI it enables. + +- Non‑panicking parse with help + - Code: see Quick Start (match on `Err(ArgsError::HelpRequested(usage))`). + - CLI: `--help`, `-h` prints usage and exits with error containing the usage text. + +- Booleans with presence and `--no-flag` + - Code (examples/bools.rs): + ```rust + let parser = ArgumentParser::new("bools") + .with_argument(Argument::new("--verbose").with_type(ArgumentType::Boolean)) + .with_argument(Argument::new("--dry-run").with_type(ArgumentType::Boolean)); + ``` + - CLI: + ``` + $ bools --verbose --no-dry-run + verbose=true, dry_run=false + ``` + +- `--arg=value` equals syntax + - Code (examples/equals.rs): + ```rust + let parser = ArgumentParser::new("equals") + .with_argument(Argument::new("--threshold").with_type(ArgumentType::Float)) + .with_argument(Argument::new("--title").with_type(ArgumentType::String)); + ``` + - CLI: + ``` + $ equals --threshold=0.75 --title=Demo + threshold=0.75, title=Demo + ``` + +- Short flags, clusters, and counting verbosity + - Code (examples/short_count.rs): + ```rust + let parser = ArgumentParser::new("short-count") + .with_argument(Argument::new("-v").with_aliases(&["-v"]).with_type(ArgumentType::Count)); + ``` + - CLI: + ``` + $ short-count -vvv + verbosity=3 + $ short-count -v -v + verbosity=2 + ``` + +- Aliases (short and long) + - Code: + ```rust + Argument::new("--output").with_type(ArgumentType::String).with_aliases(&["-o"]) + ``` + - CLI: + ``` + $ tool -o out.bin + ``` + +- Positional arguments and `--` terminator + - Code (examples/positionals.rs): + ```rust + let parser = ArgumentParser::new("pos") + .with_argument(Argument::new("input").as_positional().with_type(ArgumentType::String)) + .with_argument(Argument::new("output").as_positional().with_type(ArgumentType::String)); + ``` + - CLI: + ``` + $ pos -- fileA fileB + fileA -> fileB + ``` + +- Subcommands + - Code (examples/subcommands.rs): + ```rust + let root = ArgumentParser::new("tool") + .with_subcommand(ArgumentParser::new("serve").with_argument(Argument::new("--port").with_type(ArgumentType::Integer))) + .with_subcommand(ArgumentParser::new("build").with_argument(Argument::new("--release").with_type(ArgumentType::Boolean))); + // parsed.subcommand() -> Option<(name, &ParsedArgs)> + ``` + - CLI: + ``` + $ tool serve --port 8080 + serving on :8080 + $ tool build --release + building (release=true) + ``` + +- Env var merge (prefix) and simple config file + - Code (examples/env_config.rs): + ```rust + let parser = ArgumentParser::new("envcfg") + .with_env_prefix("APP") + .with_config_file("app.cfg") + .with_argument(Argument::new("--host").with_type(ArgumentType::String)) + .with_argument(Argument::new("--port").with_type(ArgumentType::Integer)); + ``` + - Env and config format: + - Env: `APP_HOST=127.0.0.1`, `APP_PORT=8080` + - Config: `app.cfg` lines like `--host=127.0.0.1` or `HOST=127.0.0.1` + - CLI: + ``` + $ APP_PORT=5000 envcfg + 0.0.0.0:5000 + ``` + +- Validation: requires and mutually exclusive + - Code (examples/exclusives.rs): + ```rust + let parser = ArgumentParser::new("exclusive") + .with_exclusive_group(&["--json", "--yaml"]).with_requires("--out", "--format") + .with_argument(Argument::new("--json").with_type(ArgumentType::Boolean)) + .with_argument(Argument::new("--yaml").with_type(ArgumentType::Boolean)) + .with_argument(Argument::new("--format").with_type(ArgumentType::String)) + .with_argument(Argument::new("--out").with_type(ArgumentType::String)); + ``` + - CLI: + ``` + $ exclusive --json --yaml + Validation error on --json, --yaml: mutually exclusive (choose one) + $ exclusive --out out.json + Validation error on --out: requires --format + ``` + +- Ignore unknown flags + - Code: + ```rust + let parser = ArgumentParser::new("tool").ignore_unknown(true); + ``` + - CLI: `tool --unknown --still-works` + +## More Useful Features (Explained) + +- Booleans and `--no-flag` + - Presence sets to true (e.g., `--verbose`), and `--no-verbose` sets to false. + - Works well for toggles and is more ergonomic than `--verbose=false`. + +- Short clusters and counting + - `-vvv` increments a `Count` argument three times; useful for verbosity. + - Also works with separated flags: `-v -v`. + +- Env + Config merging + - Handy for defaulting from environment or a checked-in config file. + - CLI always wins over env, and env wins over config. + - Uses a simple `key=value` format; both canonical names (`--host`) and uppercase keys (`HOST`) are supported. + +- Subcommands + - Let you structure CLIs (e.g., `tool serve`, `tool build`). + - Each subcommand has its own parser and arguments. Use `parsed.subcommand()` to route. + +- Validation helpers + - `.with_requires(a, b)` enforces that if `a` is provided, `b` must be too. + - `.with_exclusive_group([a, b, c])` ensures only one of them is present. + +## Design Notes + +- Non-panicking `parse` returns `Result` for predictable flows and better UX. +- `compile` remains for backward compatibility and will panic on error with friendly messages. +- Dependency-free; keeps binary sizes small and build times fast. + +## Examples + +- See all examples under `crates/lambda-rs-args/examples/`: + - `basic.rs`, `bools.rs`, `equals.rs`, `short_count.rs`, `positionals.rs`, `subcommands.rs`, `env_config.rs`, `exclusives.rs`. diff --git a/crates/lambda-rs-args/docs/backend_and_adapter_providers.md b/crates/lambda-rs-args/docs/backend_and_adapter_providers.md new file mode 100644 index 00000000..fe88b07f --- /dev/null +++ b/crates/lambda-rs-args/docs/backend_and_adapter_providers.md @@ -0,0 +1,261 @@ +# Implementing Backend and Adapter Providers + +Status: Draft + +This document shows practical, code‑level sketches for two context-aware providers proposed in the providers + interactive wizard spec: a Backend provider (chooses the best wgpu backend for the current OS/hardware) and a GPU Adapter provider (chooses among available adapters for the selected backend). These are designed to be feature-gated so core builds stay lean and headless CI remains deterministic. + +## Table of Contents +- 1. Overview and Goals +- 2. Core Provider API (Recap) +- 3. BackendProvider (wgpu) +- 4. GpuAdapterProvider (wgpu) +- 5. Parser Wiring Examples +- 6. Interactive Wizard Hook +- 7. Validation and Diagnostics +- 8. Testing Strategy +- 9. Notes and Alternatives + +--- + +## 1. Overview and Goals + +- Enumerate valid choices from the real environment at parse time (no hard-coded lists). +- Prefer sensible defaults per platform (e.g., Metal on macOS, DX12 on Windows, Vulkan on Linux). +- Remain optional and non-intrusive: strict mode for CI, interactive wizard for newcomers. + +## 2. Core Provider API (Recap) + +Add these types to `lambda-rs-args` behind a `providers` feature (or a small companion crate) and wire them into `Argument`/`ArgumentParser` as described in the spec. For quick prototyping you can also call providers manually before `.parse()`. + +```rust +pub struct ProviderContext<'a> { + pub os: &'a str, // "macos" | "linux" | "windows" | ... + pub subcommand: Option<&'a str>, + pub env: &'a std::collections::HashMap, + pub cwd: std::path::PathBuf, + pub args: &'a args::ParsedArgs, // already-parsed values (partial) +} + +#[derive(Clone, Debug)] +pub struct ProviderChoice { + pub value: T, + pub label: String, + pub description: Option, + pub score: i32, + pub supported: bool, + pub why: Option, +} + +pub trait ValueProvider: Send + Sync { + fn id(&self) -> &'static str; + fn choices(&self, ctx: &ProviderContext) -> Result>, String>; + fn default(&self, ctx: &ProviderContext) -> Option> { + self.choices(ctx).ok().and_then(|mut v| { + v.sort_by_key(|c| -c.score); + v.into_iter().find(|c| c.supported) + }) + } + fn validate(&self, ctx: &ProviderContext, value: &str) -> Result; +} +``` + +## 3. BackendProvider (wgpu) + +Enumerates supported wgpu backends for the current OS and scores likely best picks. Values are canonical strings like `wgpu/metal`, `wgpu/vulkan`, `wgpu/dx12`, `wgpu/gl`. + +```rust +#[cfg(feature = "with-wgpu")] +pub struct BackendProvider; + +#[cfg(feature = "with-wgpu")] +impl ValueProvider for BackendProvider { + fn id(&self) -> &'static str { "backend" } + + fn choices(&self, ctx: &ProviderContext) -> Result>, String> { + use wgpu::{Backends, Instance, InstanceDescriptor}; + let candidates: Vec<(&str, Backends, i32)> = match ctx.os { + "macos" => vec![("wgpu/metal", Backends::METAL, 100), ("wgpu/vulkan", Backends::VULKAN, 80), ("wgpu/gl", Backends::GL, 60)], + "linux" => vec![("wgpu/vulkan", Backends::VULKAN, 100), ("wgpu/gl", Backends::GL, 60)], + "windows" => vec![("wgpu/dx12", Backends::DX12, 100), ("wgpu/vulkan", Backends::VULKAN, 90), ("wgpu/gl", Backends::GL, 60)], + _ => vec![("wgpu/gl", Backends::GL, 50)], + }; + + let mut out = Vec::new(); + for (label, backend_flag, score) in candidates { + let instance = Instance::new(InstanceDescriptor { backends: backend_flag, ..Default::default() }); + let supported = !instance.enumerate_adapters(backend_flag).is_empty(); + out.push(ProviderChoice { + value: label.to_string(), + label: label.to_string(), + description: Some(format!("backends={:?}", backend_flag)), + score, + supported, + why: if supported { None } else { Some("no compatible adapter found".to_string()) }, + }); + } + Ok(out) + } + + fn validate(&self, _ctx: &ProviderContext, value: &str) -> Result { + let allowed = ["wgpu/metal", "wgpu/vulkan", "wgpu/dx12", "wgpu/gl"]; + if allowed.contains(&value) { Ok(value.to_string()) } else { Err(format!("unsupported backend: {}", value)) } + } +} +``` + +## 4. GpuAdapterProvider (wgpu) + +Enumerates adapters for a selected backend. Returns the adapter index as a string (e.g. `"0"`), which avoids ambiguity in case of duplicate adapter names. + +```rust +#[cfg(feature = "with-wgpu")] +pub struct GpuAdapterProvider { pub backend_arg: &'static str } + +#[cfg(feature = "with-wgpu")] +impl ValueProvider for GpuAdapterProvider { + fn id(&self) -> &'static str { "gpu_adapter" } + + fn choices(&self, ctx: &ProviderContext) -> Result>, String> { + use wgpu::{Adapter, AdapterInfo, Backends, Instance, InstanceDescriptor, DeviceType}; + + let backend = ctx + .args + .get_string(self.backend_arg) + .unwrap_or_else(|| default_backend_for_os(ctx.os)); + + let backends_flag = match backend.as_str() { + "wgpu/metal" => Backends::METAL, + "wgpu/vulkan" => Backends::VULKAN, + "wgpu/dx12" => Backends::DX12, + "wgpu/gl" => Backends::GL, + other => return Err(format!("unknown backend: {}", other)), + }; + + let instance = Instance::new(InstanceDescriptor { backends: backends_flag, ..Default::default() }); + let adapters: Vec = instance.enumerate_adapters(backends_flag).into_iter().collect(); + + let mut out = Vec::new(); + for (i, adapter) in adapters.iter().enumerate() { + let info: AdapterInfo = adapter.get_info(); + let score = match info.device_type { + DeviceType::DiscreteGpu => 100, + DeviceType::IntegratedGpu => 80, + DeviceType::VirtualGpu => 60, + DeviceType::Cpu => 10, + DeviceType::Other => 40, + }; + let label = format!("{} ({:?})", info.name, info.device_type); + out.push(ProviderChoice { + value: i.to_string(), + label, + description: Some(format!("vendor=0x{:04x} device=0x{:04x}", info.vendor, info.device)), + score, + supported: true, + why: None, + }); + } + out.sort_by_key(|c| -c.score); + Ok(out) + } + + fn validate(&self, ctx: &ProviderContext, value: &str) -> Result { + let idx: usize = value.parse().map_err(|_| format!("expected adapter index, got '{}'", value))?; + + let backend = ctx + .args + .get_string(self.backend_arg) + .unwrap_or_else(|| default_backend_for_os(ctx.os)); + + let bf = match backend.as_str() { + "wgpu/metal" => wgpu::Backends::METAL, + "wgpu/vulkan" => wgpu::Backends::VULKAN, + "wgpu/dx12" => wgpu::Backends::DX12, + "wgpu/gl" => wgpu::Backends::GL, + _ => wgpu::Backends::empty(), + }; + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { backends: bf, ..Default::default() }); + let adapters = instance.enumerate_adapters(bf); + if idx >= adapters.len() { + return Err(format!("adapter index {} out of range 0..{}", idx, adapters.len().saturating_sub(1))); + } + Ok(value.to_string()) + } +} + +fn default_backend_for_os(os: &str) -> String { + match os { + "macos" => "wgpu/metal".into(), + "linux" => "wgpu/vulkan".into(), + "windows" => "wgpu/dx12".into(), + _ => "wgpu/gl".into(), + } +} +``` + +## 5. Parser Wiring Examples + +Strict mode (no wizard), providers supply defaults and validation. If the provider hooks are not yet in `Argument`, you can run providers first to compute defaults and then pass them into your args before calling `.parse()`. + +```rust +use args::{Argument, ArgumentParser, ArgumentType}; + +// Build a context builder for providers +let ctx_builder = || { + use std::collections::HashMap; + let env: HashMap = std::env::vars().collect(); + args::ProviderContext { + os: std::env::consts::OS, + subcommand: None, + env: &env, + cwd: std::env::current_dir().unwrap(), + args: &args::ParsedArgs { values: vec![], subcommand: None }, // or from a prior pass + } +}; + +let parser = ArgumentParser::new("obj-loader") + //.with_context_builder(ctx_builder) + .with_argument(Argument::new("--backend").with_type(ArgumentType::String)) + .with_argument(Argument::new("--adapter").with_type(ArgumentType::String)); + // With hooks: .with_provider(Box::new(BackendProvider)) etc. + +let argv: Vec = std::env::args().collect(); +match parser.parse(&argv) { + Ok(parsed) => { + let backend = parsed.get_string("--backend").unwrap_or_else(|| default_backend_for_os(std::env::consts::OS)); + let adapter = parsed.get_string("--adapter").unwrap_or_else(|| "0".into()); + println!("backend={}, adapter={}", backend, adapter); + } + Err(e) => eprintln!("{}", e), +} +``` + +## 6. Interactive Wizard Hook + +Once `interactive_on_error(true)` and provider hooks exist, the parser can: +- Detect missing/invalid `--backend` or `--adapter`. +- Query provider `choices()` and launch a TUI list where unsupported items are disabled with a `why` tooltip. +- On selection, return normalized values to the parse result. + +This wizard can live in a separate optional crate (e.g., `lambda-rs-args-wizard`) so core stays dependency-free. + +## 7. Validation and Diagnostics + +- Detailed errors from providers: + - `unsupported backend: wgpu/foo (did you mean 'wgpu/vulkan'?)` + - `adapter index 3 out of range 0..1` +- Discovery JSON (`--help --format json`) should include dynamic provider choices and default selection to enable GUI wrappers. +- `--trace-providers` can print how defaults/choices were computed and why some were disabled. + +## 8. Testing Strategy + +- Mock providers implementing `ValueProvider` to return deterministic `choices()` and `default()`; test parser handling without graphics dependencies. +- Headless wizard: inject selection programmatically to bypass real TUI drawing in tests. +- Platform CI: for `wgpu` providers, either stub `wgpu::Instance`/`enumerate_adapters` or run on a known environment to exercise at least one real path. + +## 9. Notes and Alternatives + +- If provider hooks are not yet merged into `lambda-rs-args`, consumers can still “preflight” providers, compute defaults, and append them to argv/env before calling `.parse()`. +- The `Adapter` provider returns indexes as strings for simplicity; you can instead return a stable ID (vendor/device pair) if you prefer, at the cost of more complex validation. +- Everything above is feature-gated on `with-wgpu` to avoid pulling GPU deps into unrelated tools. + diff --git a/crates/lambda-rs-args/docs/context_aware_providers_and_interactive_wizard.md b/crates/lambda-rs-args/docs/context_aware_providers_and_interactive_wizard.md new file mode 100644 index 00000000..0220a120 --- /dev/null +++ b/crates/lambda-rs-args/docs/context_aware_providers_and_interactive_wizard.md @@ -0,0 +1,350 @@ +# Lambda Args: Context‑Aware Providers and Interactive Wizard Specification + +Status: Draft + +Author(s): lambda-rs team + +Target Crate: `lambda-rs-args` + +Version: 0.1.0 + +## Table of Contents +- 1. Purpose and Scope +- 2. Background and Motivation +- 3. Goals and Non‑Goals +- 4. Key Concepts and Terminology +- 5. Architecture Overview +- 6. API Specification + - 6.1 Core Traits and Types + - 6.2 Parser Integration + - 6.3 Providers Library (Built‑ins) + - 6.4 Configuration and Environment Integration + - 6.5 JSON Help/Discovery Output +- 7. Interactive Wizard (TUI) Specification + - 7.1 Modes & Triggers + - 7.2 UX Flows & Keybindings + - 7.3 Accessibility & i18n +- 8. Error Handling & Diagnostics +- 9. Security & Privacy Considerations +- 10. Performance and Caching +- 11. Compatibility, Versioning & Backwards Compatibility +- 12. Testing Strategy +- 13. Model Use Cases (lambda‑rs) + - 13.1 Backend Selection + - 13.2 GPU Adapter Selection + - 13.3 Surface/Present Mode Selection + - 13.4 Shader System Selection + - 13.5 Asset Path Discovery +- 14. Milestones & Rollout Plan +- 15. Open Questions & Future Work + +--- + +## 1. Purpose and Scope + +This document specifies a unique feature set for the `lambda-rs-args` argument +parser that differentiates it from existing CLI parsers: Context‑Aware Value +Providers with an optional Interactive Wizard (TUI) fallback. The model enables +dynamic, environment‑driven argument validation and selection for Lambda’s +graphics and tooling workflows (e.g., wgpu/gfx backend choice, adapter +enumeration, present mode filtering, shader system selection), while still +supporting strict, non‑interactive CLI workflows for CI and automation. + +Scope includes technical requirements, public APIs, internal architecture, +integration with `lambda-rs` use cases, UX specification for the wizard, +diagnostics, security, and test strategies. + +## 2. Background and Motivation + +Traditional parsers validate against static sets of values. Lambda’s domain is +hardware‑ and platform‑dependent: not all adapters, backends, present modes, or +shader systems are valid on a given machine. Hard‑coding choices leads to poor +UX and “works on my machine” pitfalls. + +Context‑Aware Providers resolve choices at parse time from the actual runtime +environment and project state. When non‑interactive parsing fails or input is +incomplete, a small optional TUI can guide users through valid choices. + +## 3. Goals and Non‑Goals + +Goals +- Provide dynamic value providers for arguments that can: + - Enumerate valid choices based on runtime environment and project state. + - Compute smart defaults and explain the rationale. + - Validate inputs and produce actionable errors/suggestions. +- Offer an optional Interactive Wizard (TUI) that activates on demand or upon + parse errors to resolve missing/invalid arguments. +- Emit machine‑readable help (JSON) including provider choices and reasons to + support GUIs and tools. +- Preserve strict, non‑interactive behavior for automation and CI. + +Non‑Goals +- Replace existing configuration systems; the feature augments CLI parsing and + integrates with existing `env/config` precedence. +- Depend on heavyweight GUI frameworks; the wizard is a lightweight TUI. + +## 4. Key Concepts and Terminology + +- Provider: A component that knows how to enumerate valid choices for an + argument, compute defaults, and validate a value in context. +- Context: Structured information available to providers: OS, environment + variables, previously parsed args, subcommand, project root, GPU/displays. +- Choice: A value plus metadata (label, score, why, supported/unsupported). +- Interactive Wizard: An optional TUI that queries providers and guides + selection. + +## 5. Architecture Overview + +At parse time, the `ArgumentParser` builds a `Context` (OS, env, subcommand, +partial args). For arguments registered with a Provider: +1. If the user supplied a value, the parser asks the provider to validate it. +2. If missing and interactive mode is off, the provider may supply a default. +3. If missing/invalid and interactive mode is on, the wizard queries the + provider for choices and guides the user to a valid selection. +4. The selection flows back into the parser’s result. + +Providers are pure Rust types implementing a trait. They must be fast, +side‑effect free (aside from discovery), and cancellable. + +## 6. API Specification + +### 6.1 Core Traits and Types + +```rust +pub struct ProviderContext<'a> { + pub os: &'a str, // e.g., "macos", "linux", "windows" + pub subcommand: Option<&'a str>, + pub env: &'a std::collections::HashMap, + pub cwd: std::path::PathBuf, + pub args: &'a crate::ParsedArgs, // already‑parsed values (partial) + // Optional domain info (feature‑gated): + pub gpu_info: Option, + pub displays: Option>, +} + +#[derive(Clone, Debug)] +pub struct ProviderChoice { + pub value: T, // canonical value to apply + pub label: String, // human‑readable label + pub description: Option, // more details + pub score: i32, // ordering hint: higher is better + pub supported: bool, // false => disabled choice in UI + pub why: Option, // why supported/unsupported +} + +pub trait ValueProvider: Send + Sync { + fn id(&self) -> &'static str; + + // Discover valid choices. Should be quick and cacheable. + fn choices(&self, ctx: &ProviderContext) -> Result>, String>; + + // Compute default choice if the user didn’t provide a value. + fn default(&self, ctx: &ProviderContext) -> Option> { + self.choices(ctx) + .ok() + .and_then(|mut v| { v.sort_by_key(|c| -c.score); v.into_iter().find(|c| c.supported) }) + } + + // Validate a user‑provided value; return Ok with normalized T or Err(reason). + fn validate(&self, ctx: &ProviderContext, value: &str) -> Result; +} +``` + +Argument registration addition: + +```rust +impl Argument { + pub fn with_provider( + self, + provider: Box>, + ) -> Self { /* store provider handle */ } +} + +impl ArgumentParser { + pub fn interactive_on_error(mut self, enable: bool) -> Self { /* ... */ self } + pub fn with_context_builder( + mut self, + builder: impl Fn() -> ProviderContext<'static> + Send + Sync + 'static, + ) -> Self { /* ... */ self } +} +``` + +### 6.2 Parser Integration + +- If an argument has a provider and the user supplies a value: + - Parser calls `provider.validate(ctx, raw)`; on `Err`, either fails (strict) + or, if interactive enabled, wizard prompts with choices. +- If missing value: + - Parser calls `provider.default(ctx)`; if `Some`, apply. + - If `None` and interactive enabled, wizard launches for selection. +- Parsed (and wizard‑resolved) values feed into `ParsedArgs`. + +### 6.3 Providers Library (Built‑ins) + +Initial built‑ins for lambda‑rs use cases (feature‑gated where applicable): + +- `BackendProvider` (wgpu/gfx): enumerate supported backends (`wgpu/metal`, + `wgpu/vulkan`, etc.). Score preferred backend per OS. +- `GpuAdapterProvider`: enumerate GPU adapters (Discrete/Integrated/Software), + with VRAM, vendor, device id; filter unsupported. +- `PresentModeProvider`: compute supported present modes for selected backend / + surface size / vsync; disable unsupported modes with `why`. +- `ShaderSystemProvider`: offer `naga` or `shaderc` based on platform/toolchain + availability; explain disabled ones. +- `AssetProvider`: discover assets by glob patterns; show previews/metadata. + +Providers should avoid long blocking calls; discoveries must be cancellable. + +### 6.4 Configuration and Environment Integration + +Precedence remains: CLI > Env > Config > Provider Default > Interactive. + +Providers should respect existing parsed values (e.g., selected backend), +allowing dependent providers (e.g., present mode filtering) to compute choices +from partial context. `ProviderContext.args` gives access to already‑resolved +arguments. + +### 6.5 JSON Help/Discovery Output + +`--help --format json` returns the static help plus a `dynamic` field with +provider choices and reasons to facilitate GUIs. + +Example schema (truncated): + +```json +{ + "name": "obj-loader", + "description": "Tool to render obj files", + "options": [ + { "name": "--backend", "type": "string", "required": false }, + { "name": "--adapter", "type": "string", "required": false } + ], + "dynamic": { + "--backend": { + "provider": "backend", + "choices": [ + { "value": "wgpu/metal", "label": "Metal", "score": 100, "supported": true }, + { "value": "wgpu/vulkan", "label": "Vulkan", "score": 80, "supported": false, "why": "Driver not present" } + ], + "default": "wgpu/metal" + } + } +} +``` + +## 7. Interactive Wizard (TUI) Specification + +### 7.1 Modes & Triggers + +- `interactive_on_error(true)`: launch wizard when a required/provider argument + is missing or invalid. +- `--wizard`: force wizard even if all args present; allow reviewing/resolving. +- `--no-wizard`: disable interactive fallback regardless of parser defaults. + +### 7.2 UX Flows & Keybindings + +Minimal flows per argument with a provider: +- List view: show `label`, `why` (if disabled), and highlight default. +- Confirm selection writes value back to parser. +- Allow “Explain unsupported” (toggle or key) to show `why`. +- Persist chosen defaults to a config file when the user opts in. + +Keybindings (suggested): +- Up/Down (j/k): navigate choices. +- Enter: select. +- Space: toggle details. +- Esc/q: cancel wizard; return to parse result (error if still missing). + +### 7.3 Accessibility & i18n +- Non‑color TUI fallback and high‑contrast mode. +- All strings sourced via a simple i18n layer to enable translation. + +## 8. Error Handling & Diagnostics + +- Rich errors: include provider `id`, value, and contextual hints. +- Trace mode: `--trace-providers` prints resolution order, defaults applied, + unsupported options with reasons, and source of final value. +- Wizard gracefully handles provider errors (shows a message, offers retry). + +## 9. Security & Privacy Considerations + +- Providers should not exfiltrate system info; any network access must be + opt‑in and documented (default providers operate offline). +- Respect sandboxing and permission boundaries. +- Do not collect PII; avoid writing outside project paths unless user confirms. + +## 10. Performance and Caching + +- Providers must return quickly; cache results per parse session. +- Expose an optional `ttl` on providers if longer‑lived caches are safe. +- Defer expensive provider queries until necessary (on demand in wizard). + +## 11. Compatibility, Versioning & Backwards Compatibility + +- Adding providers is backwards compatible. +- Existing arguments without providers continue to operate unchanged. +- Version gate provider API under a cargo feature `providers` (default on). + +## 12. Testing Strategy + +- Unit tests: mock providers to deterministically list choices/defaults and + validate different error paths. +- Integration tests: launch parser in interactive off/on modes; simulate TUI + flows by injecting selections (headless harness that bypasses UI draw loop). +- Snapshot tests for JSON help output. +- Lambda integration tests: ensure backend/adapter/present mode choices match + platform capabilities in CI images (using stubbed platform layers). + +## 13. Model Use Cases (lambda‑rs) + +### 13.1 Backend Selection +Argument: `--backend` +Provider: Enumerate `wgpu` backends supported for OS. On macOS, score `wgpu/metal` +highest; on Linux, score Vulkan when available; disable others with reason. + +### 13.2 GPU Adapter Selection +Argument: `--adapter` +Provider: Enumerate adapters (Discrete/Integrated/Software), label with vendor, +VRAM; filter based on required features. Default to the highest‑score supported. + +### 13.3 Surface/Present Mode Selection +Argument: `--present-mode` +Provider: Validate against selected backend, surface, and vsync; show only +supported modes; annotate unsupported with reason (e.g., “Mailbox unsupported”). + +### 13.4 Shader System Selection +Argument: `--shader-system` +Provider: Offer `naga` (default) and `shaderc` if toolchain available; explain +why `shaderc` may be disabled (cmake/ninja missing). + +### 13.5 Asset Path Discovery +Argument: `--asset` +Provider: Glob assets under project `assets/`; show basic metadata and resolve +relative paths; default to a common asset if present. + +## 14. Milestones & Rollout Plan + +M1 (Core API) +- Add `ValueProvider` and `ProviderContext`. +- Implement parser hooks and precedence integration. +- Add JSON help dynamic section. + +M2 (Built‑in Providers) +- Ship backend/adapter/present‑mode/shader‑system providers (feature‑gated). + +M3 (Interactive Wizard) +- Add minimal TUI; wire `interactive_on_error(true)`. Persist choices to config + upon opt‑in. + +M4 (Tooling & Tests) +- Add mocks, headless wizard harness, and CI tests. Document providers in README. + +## 15. Open Questions & Future Work + +- Provider dependency graph: allow providers to declare hard dependencies and + re‑compute choices when upstream values change. +- Network‑backed providers: opt‑in for asset registries or remote configs. +- GUI embedding: expose providers and choices via an API suitable for editors. +- Telemetry (opt‑in): aggregate anonymized reasons for unsupported options to + improve defaults. + diff --git a/crates/lambda-rs-args/examples/basic.rs b/crates/lambda-rs-args/examples/basic.rs new file mode 100644 index 00000000..82ab1ace --- /dev/null +++ b/crates/lambda-rs-args/examples/basic.rs @@ -0,0 +1,30 @@ +use args::{Argument, ArgumentParser, ArgumentType, ArgumentValue}; + +fn main() { + let parser = ArgumentParser::new("basic") + .with_description("Basic parse() example with required/optional args") + .with_author("lambda team") + .with_argument( + Argument::new("--name") + .is_required(true) + .with_type(ArgumentType::String), + ) + .with_argument( + Argument::new("--count") + .with_type(ArgumentType::Integer) + .with_default_value(ArgumentValue::Integer(1)), + ); + + let args: Vec = std::env::args().collect(); + match parser.parse(&args) { + Ok(parsed) => { + let name = parsed.get_string("--name").unwrap(); + let count = parsed.get_i64("--count").unwrap(); + println!("name={}, count={}", name, count); + } + Err(e) => { + // HelpRequested prints usage text via Display + eprintln!("{}", e); + } + } +} diff --git a/crates/lambda-rs-args/examples/bools.rs b/crates/lambda-rs-args/examples/bools.rs new file mode 100644 index 00000000..7c0436b6 --- /dev/null +++ b/crates/lambda-rs-args/examples/bools.rs @@ -0,0 +1,20 @@ +use args::{Argument, ArgumentParser, ArgumentType}; + +fn main() { + let parser = ArgumentParser::new("bools") + .with_description("Boolean flags: presence sets true; --no-flag sets false") + .with_argument(Argument::new("--verbose").with_type(ArgumentType::Boolean)) + .with_argument(Argument::new("--dry-run").with_type(ArgumentType::Boolean)); + + let args: Vec = std::env::args().collect(); + match parser.parse(&args) { + Ok(parsed) => { + let verbose = parsed.get_bool("--verbose").unwrap_or(false); + let dry_run = parsed.get_bool("--dry-run").unwrap_or(false); + println!("verbose={}, dry_run={}", verbose, dry_run); + } + Err(e) => { + eprintln!("{}", e); + } + } +} diff --git a/crates/lambda-rs-args/examples/env_config.rs b/crates/lambda-rs-args/examples/env_config.rs new file mode 100644 index 00000000..bd36fce1 --- /dev/null +++ b/crates/lambda-rs-args/examples/env_config.rs @@ -0,0 +1,23 @@ +use args::{Argument, ArgumentParser, ArgumentType}; + +fn main() { + // Reads APP_HOST and APP_PORT if set. Also reads from ./app.cfg if present + // with lines like: --host=127.0.0.1 + let parser = ArgumentParser::new("envcfg") + .with_env_prefix("APP") + .with_config_file("app.cfg") + .with_argument(Argument::new("--host").with_type(ArgumentType::String)) + .with_argument(Argument::new("--port").with_type(ArgumentType::Integer)); + + let args: Vec = std::env::args().collect(); + match parser.parse(&args) { + Ok(parsed) => { + let host = parsed + .get_string("--host") + .unwrap_or_else(|| "0.0.0.0".into()); + let port = parsed.get_i64("--port").unwrap_or(3000); + println!("{}:{}", host, port); + } + Err(e) => eprintln!("{}", e), + } +} diff --git a/crates/lambda-rs-args/examples/equals.rs b/crates/lambda-rs-args/examples/equals.rs new file mode 100644 index 00000000..0b1237ad --- /dev/null +++ b/crates/lambda-rs-args/examples/equals.rs @@ -0,0 +1,22 @@ +use args::{Argument, ArgumentParser, ArgumentType}; + +fn main() { + let parser = ArgumentParser::new("equals") + .with_description("--arg=value style parsing") + .with_argument(Argument::new("--threshold").with_type(ArgumentType::Float)) + .with_argument(Argument::new("--title").with_type(ArgumentType::String)); + + let args: Vec = std::env::args().collect(); + match parser.parse(&args) { + Ok(parsed) => { + let threshold = parsed.get_f32("--threshold").unwrap_or(0.5); + let title = parsed + .get_string("--title") + .unwrap_or_else(|| "(none)".to_string()); + println!("threshold={}, title={}", threshold, title); + } + Err(e) => { + eprintln!("{}", e); + } + } +} diff --git a/crates/lambda-rs-args/examples/exclusives.rs b/crates/lambda-rs-args/examples/exclusives.rs new file mode 100644 index 00000000..75cef83f --- /dev/null +++ b/crates/lambda-rs-args/examples/exclusives.rs @@ -0,0 +1,29 @@ +use args::{ArgsError, Argument, ArgumentParser, ArgumentType}; + +fn main() { + // --json and --yaml are mutually exclusive; --out requires --format + let parser = ArgumentParser::new("exclusive") + .with_exclusive_group(&["--json", "--yaml"]) + .with_requires("--out", "--format") + .with_argument(Argument::new("--json").with_type(ArgumentType::Boolean)) + .with_argument(Argument::new("--yaml").with_type(ArgumentType::Boolean)) + .with_argument(Argument::new("--format").with_type(ArgumentType::String)) + .with_argument(Argument::new("--out").with_type(ArgumentType::String)); + + let args: Vec = std::env::args().collect(); + match parser.parse(&args) { + Ok(parsed) => { + let fmt = parsed + .get_string("--format") + .unwrap_or_else(|| "json".into()); + let out = parsed + .get_string("--out") + .unwrap_or_else(|| "stdout".into()); + println!("fmt={}, out={}", fmt, out); + } + Err(ArgsError::InvalidValue { name, expected, .. }) => { + eprintln!("Validation error on {}: {}", name, expected); + } + Err(e) => eprintln!("{}", e), + } +} diff --git a/crates/lambda-rs-args/examples/positionals.rs b/crates/lambda-rs-args/examples/positionals.rs new file mode 100644 index 00000000..ff7ec2e5 --- /dev/null +++ b/crates/lambda-rs-args/examples/positionals.rs @@ -0,0 +1,27 @@ +use args::{Argument, ArgumentParser, ArgumentType}; + +fn main() { + let parser = ArgumentParser::new("pos") + .with_argument( + Argument::new("input") + .as_positional() + .with_type(ArgumentType::String), + ) + .with_argument( + Argument::new("output") + .as_positional() + .with_type(ArgumentType::String), + ); + + let args: Vec = std::env::args().collect(); + match parser.parse(&args) { + Ok(parsed) => { + println!( + "{} -> {}", + parsed.get_string("input").unwrap_or_default(), + parsed.get_string("output").unwrap_or_default() + ); + } + Err(e) => eprintln!("{}", e), + } +} diff --git a/crates/lambda-rs-args/examples/short_count.rs b/crates/lambda-rs-args/examples/short_count.rs new file mode 100644 index 00000000..9573cdf4 --- /dev/null +++ b/crates/lambda-rs-args/examples/short_count.rs @@ -0,0 +1,17 @@ +use args::{Argument, ArgumentParser, ArgumentType}; + +fn main() { + let parser = ArgumentParser::new("short-count").with_argument( + Argument::new("-v") + .with_aliases(&["-v"]) + .with_type(ArgumentType::Count), + ); + + let args: Vec = std::env::args().collect(); + match parser.parse(&args) { + Ok(parsed) => { + println!("verbosity={}", parsed.get_count("-v").unwrap_or(0)); + } + Err(e) => eprintln!("{}", e), + } +} diff --git a/crates/lambda-rs-args/examples/subcommands.rs b/crates/lambda-rs-args/examples/subcommands.rs new file mode 100644 index 00000000..16d87801 --- /dev/null +++ b/crates/lambda-rs-args/examples/subcommands.rs @@ -0,0 +1,37 @@ +use args::{Argument, ArgumentParser, ArgumentType}; + +fn main() { + // root + let root = + ArgumentParser::new("tool") + .with_description("Demo with subcommands: serve/build") + .with_subcommand(ArgumentParser::new("serve").with_argument( + Argument::new("--port").with_type(ArgumentType::Integer), + )) + .with_subcommand(ArgumentParser::new("build").with_argument( + Argument::new("--release").with_type(ArgumentType::Boolean), + )); + + let args: Vec = std::env::args().collect(); + let usage = root.usage(); + match root.parse(&args) { + Ok(parsed) => { + if let Some((name, sub)) = parsed.subcommand() { + match name { + "serve" => { + let port = sub.get_i64("--port").unwrap_or(8080); + println!("serving on :{}", port); + } + "build" => { + let rel = sub.get_bool("--release").unwrap_or(false); + println!("building (release={})", rel); + } + _ => {} + } + } else { + eprintln!("No subcommand provided.\n{}", usage); + } + } + Err(e) => eprintln!("{}", e), + } +} diff --git a/crates/lambda-rs-args/src/lib.rs b/crates/lambda-rs-args/src/lib.rs index 1119d03c..c33f65aa 100644 --- a/crates/lambda-rs-args/src/lib.rs +++ b/crates/lambda-rs-args/src/lib.rs @@ -3,10 +3,22 @@ //! simple to use and primarily for use in lambda command line applications. use std::collections::HashMap; +use std::fmt; pub struct ArgumentParser { name: String, + description: String, + authors: Vec, args: HashMap, + aliases: HashMap, + positionals: Vec, + env_prefix: Option, + ignore_unknown: bool, + exclusive_groups: Vec>, + requires: Vec<(String, String)>, + config_path: Option, + subcommands: HashMap, + is_subcommand: bool, } #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] @@ -16,6 +28,11 @@ pub enum ArgumentType { Float, Double, String, + Count, + StringList, + IntegerList, + FloatList, + DoubleList, } #[derive(Debug, Clone, PartialEq, PartialOrd)] @@ -75,6 +92,8 @@ pub struct Argument { required: bool, arg_type: ArgumentType, default_value: ArgumentValue, + aliases: Vec, + positional: bool, } impl Argument { @@ -86,6 +105,8 @@ impl Argument { required: false, arg_type: ArgumentType::String, default_value: ArgumentValue::None, + aliases: vec![], + positional: false, }; } @@ -113,7 +134,8 @@ impl Argument { (ArgumentType::String, ArgumentValue::String(_)) | (ArgumentType::Integer, ArgumentValue::Integer(_)) | (ArgumentType::Float, ArgumentValue::Float(_)) - | (ArgumentType::Double, ArgumentValue::Double(_)) => { + | (ArgumentType::Double, ArgumentValue::Double(_)) + | (ArgumentType::Boolean, ArgumentValue::Boolean(_)) => { self.default_value = value; } (_, _) => panic!( @@ -125,6 +147,20 @@ impl Argument { return self; } + /// Add short/long aliases (e.g., ["-o", "--output"]). + pub fn with_aliases(mut self, aliases: &[&str]) -> Self { + for a in aliases { + self.aliases.push(a.to_string()); + } + self + } + + /// Mark argument as positional (consumes tokens without leading -/--). + pub fn as_positional(mut self) -> Self { + self.positional = true; + self + } + pub fn arg_type(&self) -> ArgumentType { return self.arg_type.clone(); } @@ -140,6 +176,13 @@ impl Argument { pub fn description(&self) -> &str { return self.description.as_ref(); } + + pub fn aliases(&self) -> &Vec { + &self.aliases + } + pub fn is_positional(&self) -> bool { + self.positional + } } #[derive(Debug, Clone)] @@ -170,7 +213,18 @@ impl ArgumentParser { pub fn new(name: &str) -> Self { return ArgumentParser { name: name.to_string(), + description: String::new(), + authors: vec![], args: HashMap::new(), + aliases: HashMap::new(), + positionals: vec![], + env_prefix: None, + ignore_unknown: false, + exclusive_groups: vec![], + requires: vec![], + config_path: None, + subcommands: HashMap::new(), + is_subcommand: false, }; } @@ -184,33 +238,156 @@ impl ArgumentParser { return self.args.len(); } - pub fn with_author(mut self, author: &str) { - todo!("Implement adding authors to a command line parser") + pub fn with_author(mut self, author: &str) -> Self { + self.authors.push(author.to_string()); + self } // TODO(vmarcella): Add description to the name - pub fn with_description(mut self, description: &str) { - todo!("Implement adding a description to the command line parser.") + pub fn with_description(mut self, description: &str) -> Self { + self.description = description.to_string(); + self } pub fn with_argument(mut self, argument: Argument) -> Self { - self.args.insert( - argument.name().to_string(), - (argument, false, self.args.len()), - ); + let idx = self.args.len(); + let name = argument.name().to_string(); + if argument.is_positional() { + self.positionals.push(name.clone()); + } + for a in argument.aliases().iter() { + self.aliases.insert(a.clone(), name.clone()); + } + self.args.insert(name, (argument, false, idx)); return self; } - /// Compiles a slice of Strings into an array of Parsed Arguments. This will - /// move the parser into this function and return back the parsed arguments if - /// everything succeeds. This function assumes that the first item within args - /// is the name of the executable being run. (Which is the standard for - /// arguments passed in from std::env::args()). The ordering of the arguments - /// returned is always the same as order they're registered in with the - /// parser. - pub fn compile(mut self, args: &[String]) -> Vec { + /// Set an environment variable prefix (e.g., "OBJ_LOADER"). + pub fn with_env_prefix(mut self, prefix: &str) -> Self { + self.env_prefix = Some(prefix.to_string()); + self + } + + /// Ignore unknown arguments instead of erroring. + pub fn ignore_unknown(mut self, ignore: bool) -> Self { + self.ignore_unknown = ignore; + self + } + + /// Add a mutually exclusive group. Provide canonical names (e.g., "--a"). + pub fn with_exclusive_group(mut self, group: &[&str]) -> Self { + self + .exclusive_groups + .push(group.iter().map(|s| s.to_string()).collect()); + self + } + + /// Add a requires relationship: if `name` is present, `requires` must be. + pub fn with_requires(mut self, name: &str, requires: &str) -> Self { + self.requires.push((name.to_string(), requires.to_string())); + self + } + + /// Merge values from a simple key=value config file (optional). + pub fn with_config_file(mut self, path: &str) -> Self { + self.config_path = Some(path.to_string()); + self + } + + /// Add a subcommand parser. + pub fn with_subcommand(mut self, mut sub: ArgumentParser) -> Self { + sub.is_subcommand = true; + let key = sub.name.clone(); + self.subcommands.insert(key, sub); + self + } + + /// Generate a usage string for the parser based on registered arguments. + pub fn usage(&self) -> String { + let mut out = String::new(); + if self.subcommands.is_empty() { + out.push_str(&format!("Usage: {} [options]", self.name)); + } else { + out.push_str(&format!("Usage: {} [options]", self.name)); + } + if !self.positionals.is_empty() { + for p in &self.positionals { + out.push_str(&format!(" <{}>", normalize_name_display(p))); + } + } + out.push('\n'); + if !self.description.is_empty() { + out.push_str(&format!("\n{}\n", self.description)); + } + if !self.authors.is_empty() { + out.push_str(&format!("\nAuthor(s): {}\n", self.authors.join(", "))); + } + out.push_str("\nOptions:\n"); + // stable iteration by index order + let mut items: Vec<(&Argument, bool, usize)> = + self.args.values().map(|(a, f, i)| (a, *f, *i)).collect(); + items.sort_by_key(|(_, _, i)| *i); + for (arg, _found, _idx) in items { + let req = if arg.required { " (required)" } else { "" }; + let def = match arg.default_value() { + ArgumentValue::None => String::new(), + ArgumentValue::String(s) => format!(" [default: {}]", s), + ArgumentValue::Integer(i) => format!(" [default: {}]", i), + ArgumentValue::Float(v) => format!(" [default: {}]", v), + ArgumentValue::Double(v) => format!(" [default: {}]", v), + ArgumentValue::Boolean(b) => format!(" [default: {}]", b), + }; + let ty = match arg.arg_type() { + ArgumentType::String => "", + ArgumentType::Integer => "", + ArgumentType::Float => "", + ArgumentType::Double => "", + ArgumentType::Boolean => "", + ArgumentType::Count => "(count)", + ArgumentType::StringList => "...", + ArgumentType::IntegerList => "...", + ArgumentType::FloatList => "...", + ArgumentType::DoubleList => "...", + }; + let sep = if matches!( + arg.arg_type(), + ArgumentType::Boolean | ArgumentType::Count + ) { + String::new() + } else { + format!(" {}", ty) + }; + let desc = arg.description(); + let aliases = if arg.aliases().is_empty() { + String::new() + } else { + format!(" (aliases: {})", arg.aliases().join(", ")) + }; + out.push_str(&format!( + " {}{}\n {}{}{}\n", + arg.name(), + sep, + desc, + format!("{}{}", req, def), + aliases + )); + } + if !self.subcommands.is_empty() { + out.push_str("\nSubcommands:\n"); + let mut keys: Vec<_> = self.subcommands.keys().cloned().collect(); + keys.sort(); + for k in keys { + out.push_str(&format!(" {}\n", k)); + } + } + out + } + + /// New non-panicking parser. Prefer this over `compile`. + pub fn parse(mut self, args: &[String]) -> Result { + // Errors are returned, not panicked. let mut collecting_values = false; - let mut last_argument: Option<&mut (Argument, bool, usize)> = None; + let mut last_key: Option = None; let mut parsed_arguments = vec![]; parsed_arguments.resize( @@ -218,87 +395,943 @@ impl ArgumentParser { ParsedArgument::new("", ArgumentValue::None), ); - for arg in args.into_iter().skip(1) { - if collecting_values { - let (arg_ref, found, index) = last_argument.as_mut().unwrap(); - - let parsed_value = match arg_ref.arg_type() { - ArgumentType::String => ArgumentValue::String(arg.clone()), - ArgumentType::Float => { - ArgumentValue::Float(arg.parse().unwrap_or_else(|err| { - panic!( - "Could not convert {:?} to a float because of: {}", - arg, err - ) - })) - } - ArgumentType::Double => { - ArgumentValue::Double(arg.parse().unwrap_or_else(|err| { - panic!( - "Could not convert {:?} to a double because of: {}", - arg, err - ) - })) - } - ArgumentType::Integer => { - ArgumentValue::Integer(arg.parse().unwrap_or_else(|err| { - panic!( - "Could not convert {:?} to an integer because of: {}", - arg, err - ) - })) - } - ArgumentType::Boolean => { - ArgumentValue::Boolean(arg.parse().unwrap_or_else(|err| { - panic!( - "Could not convert {:?} to a boolean because of: {}", - arg, err - ) - })) + // Build a helper to map `--no-flag` to `--flag` when boolean + let mut name_to_bool: HashMap = HashMap::new(); + + let mut iter = args.iter(); + // skip executable name + iter.next(); + // subcommand dispatch (first non-dash token) + if let Some(token) = iter.clone().next() { + let t = token.as_str(); + if !t.starts_with('-') && self.subcommands.contains_key(t) { + // reconstruct argv for subcommand: exe, rest (exclude subcommand token) + let mut argv = Vec::new(); + argv.push(args[0].clone()); + for s in args.iter().skip(2) { + argv.push(s.clone()); + } + let sub = self.subcommands.remove(t).unwrap(); + let sub_parsed = sub.parse(&argv)?; + return Ok(ParsedArgs { + values: vec![], + subcommand: Some((t.to_string(), Box::new(sub_parsed))), + }); + } + } + while let Some(token) = iter.next() { + let arg_token = token.as_str(); + + // If first non-dash token matches a subcommand, delegate here as well. + if !arg_token.starts_with('-') && self.subcommands.contains_key(arg_token) + { + let mut argv2 = vec![args[0].clone()]; + for s in iter.clone() { + argv2.push(s.to_string()); + } + let sub = self.subcommands.remove(arg_token).unwrap(); + let sub_parsed = sub.parse(&argv2)?; + return Ok(ParsedArgs { + values: vec![], + subcommand: Some((arg_token.to_string(), Box::new(sub_parsed))), + }); + } + + if arg_token == "--help" || arg_token == "-h" { + return Err(ArgsError::HelpRequested(self.usage())); + } + + if arg_token == "--" { + for value in iter.by_ref() { + self.assign_next_positional(&mut parsed_arguments, value.as_str())?; + } + break; + } + + // Handle --no-flag + if let Some(stripped) = arg_token.strip_prefix("--no-") { + let key = match self.canonical_name(&format!("--{}", stripped)) { + Some(k) => k, + None => { + let msg = unknown_with_suggestion(arg_token, &self); + if self.ignore_unknown { + continue; + } + return Err(ArgsError::UnknownArgument(msg)); } }; + let found = self.args.get_mut(&key).unwrap(); + if !matches!(found.0.arg_type(), ArgumentType::Boolean) { + return Err(ArgsError::InvalidValue { + name: found.0.name.clone(), + expected: "boolean".to_string(), + value: "".to_string(), + }); + } + if found.1 { + return Err(ArgsError::DuplicateArgument(found.0.name.clone())); + } + let _ = name_to_bool.insert(found.0.name.clone(), false); + let index = found.2; + parsed_arguments[index] = ParsedArgument::new( + found.0.name.as_str(), + ArgumentValue::Boolean(false), + ); + found.1 = true; + collecting_values = false; + last_key = None; + continue; + } - parsed_arguments[*index] = - ParsedArgument::new(arg_ref.name.as_str(), parsed_value); + // Handle --arg=value + if let Some((key_raw, value)) = arg_token.split_once('=') { + let key = match self.canonical_name(key_raw) { + Some(k) => k, + None => { + let msg = unknown_with_suggestion(key_raw, &self); + if self.ignore_unknown { + continue; + } + return Err(ArgsError::UnknownArgument(msg)); + } + }; + let found = self.args.get_mut(&key).unwrap(); + if found.1 { + return Err(ArgsError::DuplicateArgument(found.0.name.clone())); + } + let parsed_value = parse_value(&found.0, value)?; + let index = found.2; + parsed_arguments[index] = + ParsedArgument::new(found.0.name.as_str(), parsed_value); + found.1 = true; + collecting_values = false; + last_key = None; + continue; + } + if collecting_values { + let key = last_key.as_ref().unwrap().clone(); + let found = self.args.get_mut(&key).expect("argument vanished"); + let parsed_value = parse_value(&found.0, arg_token)?; + let index = found.2; + parsed_arguments[index] = + ParsedArgument::new(found.0.name.as_str(), parsed_value); collecting_values = false; - *found = true; + found.1 = true; + last_key = None; continue; } - // Panic if the argument cannot be found inside of the registered - // arguments. - let found_argument = self.args.get_mut(arg).unwrap_or_else(|| { - panic!("Argument: {} is not a valid argument", &arg) - }); + // Handle combined short flags like -vvv + if arg_token.starts_with('-') + && !arg_token.starts_with("--") + && arg_token.len() > 2 + { + let chars: Vec = arg_token[1..].chars().collect(); + for ch in chars { + let alias = format!("-{}", ch); + let Some(canon) = self.canonical_name(&alias) else { + let msg = unknown_with_suggestion(&alias, &self); + if self.ignore_unknown { + continue; + } + return Err(ArgsError::UnknownArgument(msg)); + }; + let pre = self.args.get(&canon).unwrap(); + if matches!(pre.0.arg_type(), ArgumentType::Count) { + let index = pre.2; + let current = match &parsed_arguments[index].value { + ArgumentValue::Integer(v) => *v as i64, + _ => 0, + }; + let found = self.args.get_mut(&canon).unwrap(); + parsed_arguments[index] = ParsedArgument::new( + found.0.name.as_str(), + ArgumentValue::Integer((current + 1) as i64), + ); + found.1 = true; + } else if matches!(pre.0.arg_type(), ArgumentType::Boolean) { + let index = pre.2; + let found = self.args.get_mut(&canon).unwrap(); + parsed_arguments[index] = ParsedArgument::new( + found.0.name.as_str(), + ArgumentValue::Boolean(true), + ); + found.1 = true; + } else { + return Err(ArgsError::InvalidValue { + name: pre.0.name.clone(), + expected: "separate value (not clustered)".to_string(), + value: arg_token.to_string(), + }); + } + } + continue; + } - // If the argument has already been found, throw an error. - if found_argument.1 == true { - panic!("{} was set more than once.", found_argument.0.name.clone()); + let Some(canon_name) = self.canonical_name(arg_token) else { + let msg = unknown_with_suggestion(arg_token, &self); + if self.ignore_unknown { + continue; + } + return Err(ArgsError::UnknownArgument(msg)); + }; + let pre = self.args.get(&canon_name).unwrap(); + if pre.1 == true { + return Err(ArgsError::DuplicateArgument(pre.0.name.clone())); + } + // Boolean flags can be set by presence alone + if matches!(pre.0.arg_type(), ArgumentType::Boolean) { + let index = pre.2; + parsed_arguments[index] = ParsedArgument::new( + pre.0.name.as_str(), + ArgumentValue::Boolean(true), + ); + let found = self.args.get_mut(&canon_name).unwrap(); + found.1 = true; + continue; + } else if matches!(pre.0.arg_type(), ArgumentType::Count) { + let index = pre.2; + let found = self.args.get_mut(&canon_name).unwrap(); + let current = match &parsed_arguments[index].value { + ArgumentValue::Integer(v) => *v as i64, + _ => 0, + }; + parsed_arguments[index] = ParsedArgument::new( + found.0.name.as_str(), + ArgumentValue::Integer((current + 1) as i64), + ); + found.1 = true; + continue; } collecting_values = true; - last_argument = Some(found_argument); + last_key = Some(canon_name); + } + + // If still collecting a value, it's a missing value error + if let Some(key) = last_key { + let arg_ref = &self.args.get(&key).unwrap().0; + if !matches!(arg_ref.arg_type(), ArgumentType::Boolean) { + return Err(ArgsError::MissingValue(arg_ref.name.clone())); + } + } + + // Attempt env var merge for missing args + if let Some(prefix) = &self.env_prefix { + let mut missing: Vec = Vec::new(); + for (arg, found, _index) in self.args.values() { + if !*found { + missing.push(arg.name.clone()); + } + } + for name in missing { + if let Some(val) = read_env_var(prefix, name.as_str()) { + if let Some((arg, _found, index)) = self.args.get(name.as_str()) { + if let Ok(parsed) = parse_value(arg, &val) { + parsed_arguments[*index] = + ParsedArgument::new(arg.name.as_str(), parsed); + if let Some(entry) = self.args.get_mut(name.as_str()) { + entry.1 = true; + } + } + } + } + } + } + + // Config file merge (simple key=value) + if let Some(path) = &self.config_path { + if let Ok(file_vals) = read_config_file(path, &self) { + // collect names where not found yet + let mut not_found = std::collections::HashSet::new(); + for (_n, (arg, found, _)) in self.args.iter() { + if !*found { + not_found.insert(arg.name.clone()); + } + } + for (name, raw) in file_vals { + if !not_found.contains(&name) { + continue; + } + if let Some((arg, _found, index)) = self.args.get(&name) { + if let Ok(parsed) = parse_value(arg, &raw) { + parsed_arguments[*index] = + ParsedArgument::new(arg.name.as_str(), parsed); + if let Some(e) = self.args.get_mut(name.as_str()) { + e.1 = true; + } + } + } + } + } } - // Go through all of the registered arguments and check for forgotten flags/ - // apply default values. + // Fill defaults and check required for (arg, found, index) in self.args.values() { match (arg.required, found, arg.default_value.clone()) { - // Argument was required as user input, but not found. - (true, false, _) => panic!( - "--{} is a required argument, but was not found.", - arg.name.clone() - ), - // Argument wasn't required & wasn't found, but has a default value + (true, false, _) => { + return Err(ArgsError::MissingRequired(arg.name.clone())) + } (false, false, value) => { parsed_arguments[*index] = ParsedArgument::new(arg.name.as_str(), value); } - // Any other situation doesn't really matter and will be a noop - (_, _, _) => {} + _ => {} + } + } + + // Validate requires and exclusive groups + for (name, req) in &self.requires { + let a = self.get_present(&parsed_arguments, name); + let b = self.get_present(&parsed_arguments, req); + if a && !b { + return Err(ArgsError::InvalidValue { + name: name.clone(), + expected: format!("requires {}", req), + value: String::new(), + }); + } + } + for group in &self.exclusive_groups { + let mut count = 0; + for n in group { + if self.get_present(&parsed_arguments, n) { + count += 1; + } + } + if count > 1 { + return Err(ArgsError::InvalidValue { + name: group.join(", "), + expected: "mutually exclusive (choose one)".to_string(), + value: "multiple provided".to_string(), + }); + } + } + + Ok(ParsedArgs { + values: parsed_arguments, + subcommand: None, + }) + } + + /// Backwards-compatible panicking API. Prefer `parse` for non-panicking behavior. + pub fn compile(self, args: &[String]) -> Vec { + match self.parse(args) { + Ok(parsed) => parsed.into_vec(), + Err(err) => panic!("{}", err), + } + } +} + +fn parse_value(arg: &Argument, raw: &str) -> Result { + match arg.arg_type() { + ArgumentType::String => Ok(ArgumentValue::String(raw.to_string())), + ArgumentType::Float => raw + .parse::() + .map(ArgumentValue::Float) + .map_err(|_| ArgsError::InvalidValue { + name: arg.name.clone(), + expected: "float".to_string(), + value: raw.to_string(), + }), + ArgumentType::Double => raw + .parse::() + .map(ArgumentValue::Double) + .map_err(|_| ArgsError::InvalidValue { + name: arg.name.clone(), + expected: "double".to_string(), + value: raw.to_string(), + }), + ArgumentType::Integer => raw + .parse::() + .map(ArgumentValue::Integer) + .map_err(|_| ArgsError::InvalidValue { + name: arg.name.clone(), + expected: "integer".to_string(), + value: raw.to_string(), + }), + ArgumentType::Boolean => raw + .parse::() + .map(ArgumentValue::Boolean) + .map_err(|_| ArgsError::InvalidValue { + name: arg.name.clone(), + expected: "boolean".to_string(), + value: raw.to_string(), + }), + ArgumentType::Count => Ok(ArgumentValue::Integer(1)), + ArgumentType::StringList => Ok(ArgumentValue::String(raw.to_string())), + ArgumentType::IntegerList => raw + .parse::() + .map(ArgumentValue::Integer) + .map_err(|_| ArgsError::InvalidValue { + name: arg.name.clone(), + expected: "integer".to_string(), + value: raw.to_string(), + }), + ArgumentType::FloatList => raw + .parse::() + .map(ArgumentValue::Float) + .map_err(|_| ArgsError::InvalidValue { + name: arg.name.clone(), + expected: "float".to_string(), + value: raw.to_string(), + }), + ArgumentType::DoubleList => raw + .parse::() + .map(ArgumentValue::Double) + .map_err(|_| ArgsError::InvalidValue { + name: arg.name.clone(), + expected: "double".to_string(), + value: raw.to_string(), + }), + } +} + +#[derive(Debug)] +pub enum ArgsError { + UnknownArgument(String), + DuplicateArgument(String), + MissingValue(String), + InvalidValue { + name: String, + expected: String, + value: String, + }, + MissingRequired(String), + HelpRequested(String), +} + +impl fmt::Display for ArgsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ArgsError::UnknownArgument(a) => write!(f, "Unknown argument: {}", a), + ArgsError::DuplicateArgument(a) => write!(f, "Duplicate argument: {}", a), + ArgsError::MissingValue(a) => { + write!(f, "Missing value for argument: {}", a) + } + ArgsError::InvalidValue { + name, + expected, + value, + } => write!( + f, + "Invalid value for {}: got '{}', expected {}", + name, value, expected + ), + ArgsError::MissingRequired(a) => { + write!(f, "Missing required argument: {}", a) + } + ArgsError::HelpRequested(usage) => write!(f, "{}", usage), + } + } +} + +impl std::error::Error for ArgsError {} + +/// A parsed arguments wrapper with typed getters. +#[derive(Debug, Clone)] +pub struct ParsedArgs { + values: Vec, + subcommand: Option<(String, Box)>, +} + +impl ParsedArgs { + pub fn into_vec(self) -> Vec { + self.values + } + + pub fn has(&self, name: &str) -> bool { + self + .values + .iter() + .any(|p| p.name == name && !matches!(p.value, ArgumentValue::None)) + } + + pub fn get_string(&self, name: &str) -> Option { + self + .values + .iter() + .find(|p| p.name == name) + .and_then(|p| match &p.value { + ArgumentValue::String(s) => Some(s.clone()), + _ => None, + }) + } + + pub fn get_i64(&self, name: &str) -> Option { + self + .values + .iter() + .find(|p| p.name == name) + .and_then(|p| match &p.value { + ArgumentValue::Integer(v) => Some(*v), + _ => None, + }) + } + + pub fn get_f32(&self, name: &str) -> Option { + self + .values + .iter() + .find(|p| p.name == name) + .and_then(|p| match &p.value { + ArgumentValue::Float(v) => Some(*v), + _ => None, + }) + } + + pub fn get_f64(&self, name: &str) -> Option { + self + .values + .iter() + .find(|p| p.name == name) + .and_then(|p| match &p.value { + ArgumentValue::Double(v) => Some(*v), + _ => None, + }) + } + + pub fn get_bool(&self, name: &str) -> Option { + self + .values + .iter() + .find(|p| p.name == name) + .and_then(|p| match &p.value { + ArgumentValue::Boolean(v) => Some(*v), + _ => None, + }) + } + + pub fn get_count(&self, name: &str) -> Option { + self + .values + .iter() + .find(|p| p.name == name) + .and_then(|p| match &p.value { + ArgumentValue::Integer(v) => Some(*v), + _ => None, + }) + } + + pub fn subcommand(&self) -> Option<(&str, &ParsedArgs)> { + self + .subcommand + .as_ref() + .map(|(n, p)| (n.as_str(), p.as_ref())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn argv(args: &[&str]) -> Vec { + let mut v = vec!["prog".to_string()]; + v.extend(args.iter().map(|s| s.to_string())); + v + } + + #[test] + fn required_and_default() { + let parser = ArgumentParser::new("app") + .with_argument( + Argument::new("--name") + .is_required(true) + .with_type(ArgumentType::String), + ) + .with_argument( + Argument::new("--count") + .with_type(ArgumentType::Integer) + .with_default_value(ArgumentValue::Integer(2)), + ); + let res = parser.parse(&argv(&["--name", "lambda"])).unwrap(); + assert_eq!(res.get_string("--name").unwrap(), "lambda"); + assert_eq!(res.get_i64("--count").unwrap(), 2); + } + + #[test] + fn help_requested() { + let parser = ArgumentParser::new("app").with_description("desc"); + let err = parser.parse(&argv(&["--help"])).unwrap_err(); + match err { + ArgsError::HelpRequested(u) => assert!(u.contains("Usage: app")), + _ => panic!(), + } + } + + #[test] + fn boolean_presence_and_no() { + let parser = ArgumentParser::new("app").with_argument( + Argument::new("--verbose").with_type(ArgumentType::Boolean), + ); + let p = parser.parse(&argv(&["--verbose"])).unwrap(); + assert_eq!(p.get_bool("--verbose").unwrap(), true); + let parser2 = ArgumentParser::new("app").with_argument( + Argument::new("--verbose").with_type(ArgumentType::Boolean), + ); + let p2 = parser2.parse(&argv(&["--no-verbose"])).unwrap(); + assert_eq!(p2.get_bool("--verbose").unwrap(), false); + } + + #[test] + fn equals_syntax() { + let parser = ArgumentParser::new("app") + .with_argument(Argument::new("--title").with_type(ArgumentType::String)); + let p = parser.parse(&argv(&["--title=Demo"])).unwrap(); + assert_eq!(p.get_string("--title").unwrap(), "Demo"); + } + + #[test] + fn aliases_and_short() { + let parser = ArgumentParser::new("app").with_argument( + Argument::new("--output") + .with_type(ArgumentType::String) + .with_aliases(&["-o"]), + ); + let p = parser.parse(&argv(&["-o", "file.txt"])).unwrap(); + assert_eq!(p.get_string("--output").unwrap(), "file.txt"); + } + + #[test] + fn positional_and_terminator() { + let parser = ArgumentParser::new("app") + .with_argument( + Argument::new("input") + .as_positional() + .with_type(ArgumentType::String), + ) + .with_argument( + Argument::new("rest") + .as_positional() + .with_type(ArgumentType::String), + ); + let p = parser.parse(&argv(&["--", "a", "b"])).unwrap(); + assert_eq!(p.get_string("input").unwrap(), "a"); + assert_eq!(p.get_string("rest").unwrap(), "b"); + } + + #[test] + fn counting_and_cluster() { + let parser = ArgumentParser::new("app").with_argument( + Argument::new("-v") + .with_aliases(&["-v"]) + .with_type(ArgumentType::Count), + ); + let p = parser.parse(&argv(&["-vvv"])).unwrap(); + assert_eq!(p.get_count("-v").unwrap(), 3); + } + + #[test] + fn env_prefix() { + std::env::set_var("TEST_PATH", "/tmp/x"); + let parser = ArgumentParser::new("app") + .with_env_prefix("TEST") + .with_argument(Argument::new("--path").with_type(ArgumentType::String)); + let p = parser.parse(&argv(&[])).unwrap(); + assert_eq!(p.get_string("--path").unwrap(), "/tmp/x"); + } + + #[test] + fn usage_includes_aliases_and_types() { + let parser = ArgumentParser::new("u") + .with_argument( + Argument::new("--a") + .with_type(ArgumentType::String) + .with_aliases(&["-a"]), + ) + .with_argument(Argument::new("--b").with_type(ArgumentType::Integer)) + .with_argument(Argument::new("--c").with_type(ArgumentType::Float)) + .with_argument(Argument::new("--d").with_type(ArgumentType::Double)) + .with_argument(Argument::new("--e").with_type(ArgumentType::Boolean)) + .with_argument( + Argument::new("-v") + .with_aliases(&["-v"]) + .with_type(ArgumentType::Count), + ) + .with_argument( + Argument::new("S1") + .as_positional() + .with_type(ArgumentType::String), + ); + let u = parser.usage(); + assert!(u.contains("aliases: -a")); + assert!(u.contains("")); + assert!(u.contains("")); + assert!(u.contains("")); + assert!(u.contains("")); + assert!(u.contains("--e")); + // Count has no value placeholder in usage; ensure it exists + assert!(u.contains("-v")); + } + + #[test] + fn no_flag_non_boolean_invalid() { + let parser = ArgumentParser::new("app") + .with_argument(Argument::new("--name").with_type(ArgumentType::String)); + let err = parser.parse(&argv(&["--no-name"])).unwrap_err(); + match err { + ArgsError::InvalidValue { expected, .. } => { + assert!(expected.contains("boolean")) + } + _ => panic!(), + } + } + + #[test] + fn equals_unknown_ignore_vs_error() { + let parser = ArgumentParser::new("app"); + let err = parser.parse(&argv(&["--unknown=1"])).unwrap_err(); + match err { + ArgsError::UnknownArgument(_msg) => {} + _ => panic!(), + } + + let parser2 = ArgumentParser::new("app").ignore_unknown(true); + let ok = parser2.parse(&argv(&["--unknown=1"])).unwrap(); + assert_eq!(ok.into_vec().len(), 0); + } + + #[test] + fn cluster_invalid_for_non_boolean_or_count() { + let parser = ArgumentParser::new("app").with_argument( + Argument::new("-o") + .with_aliases(&["-o"]) + .with_type(ArgumentType::String), + ); + let err = parser.parse(&argv(&["-ov"])).unwrap_err(); + match err { + ArgsError::InvalidValue { expected, .. } => { + assert!(expected.contains("separate value")) + } + _ => panic!(), + } + } + + #[test] + fn unknown_argument_suggests() { + let parser = ArgumentParser::new("app") + .with_argument(Argument::new("--port").with_type(ArgumentType::Integer)); + let err = parser.parse(&argv(&["--portt", "1"])).unwrap_err(); + match err { + ArgsError::UnknownArgument(msg) => assert!(msg.contains("did you mean")), + _ => panic!(), + } + } + + #[test] + fn missing_value_error() { + let parser = ArgumentParser::new("app") + .with_argument(Argument::new("--name").with_type(ArgumentType::String)); + let err = parser.parse(&argv(&["--name"])).unwrap_err(); + match err { + ArgsError::MissingValue(_) | ArgsError::InvalidValue { .. } => {} + _ => panic!("unexpected: {:?}", err), + } + } + + #[test] + fn duplicate_argument_error() { + let parser = ArgumentParser::new("app") + .with_argument(Argument::new("--name").with_type(ArgumentType::String)); + let err = parser + .parse(&argv(&["--name", "a", "--name", "b"])) + .unwrap_err(); + match err { + ArgsError::DuplicateArgument(_) => {} + _ => panic!(), + } + } + + #[test] + fn env_overridden_by_cli() { + std::env::set_var("APP_PATH", "/env"); + let parser = ArgumentParser::new("app") + .with_env_prefix("APP") + .with_argument(Argument::new("--path").with_type(ArgumentType::String)); + let p = parser.parse(&argv(&["--path", "/cli"])).unwrap(); + assert_eq!(p.get_string("--path").unwrap(), "/cli"); + } + + #[test] + fn config_merge_canonical_and_uppercase() { + // Build config content + let dir = std::env::temp_dir(); + let path = dir.join("args_cfg_test.cfg"); + std::fs::write(&path, "--host=1.2.3.4\nPORT=9000\n# comment\n").unwrap(); + let parser = ArgumentParser::new("app") + .with_config_file(path.to_str().unwrap()) + .with_argument(Argument::new("--host").with_type(ArgumentType::String)) + .with_argument(Argument::new("--port").with_type(ArgumentType::Integer)); + let p = parser.parse(&argv(&[])).unwrap(); + assert_eq!(p.get_string("--host").unwrap(), "1.2.3.4"); + assert_eq!(p.get_i64("--port").unwrap(), 9000); + } + + #[test] + fn subcommand_parsing() { + let root = ArgumentParser::new("tool").with_subcommand( + ArgumentParser::new("serve").with_argument( + Argument::new("--port").with_type(ArgumentType::Integer), + ), + ); + let p = root.parse(&argv(&["serve", "--port", "8081"])).unwrap(); + let (name, sub) = p.subcommand().unwrap(); + assert_eq!(name, "serve"); + assert_eq!(sub.get_i64("--port").unwrap(), 8081); + } + + #[test] + fn assign_next_positional_error_on_extra() { + let parser = ArgumentParser::new("pos").with_argument( + Argument::new("a") + .as_positional() + .with_type(ArgumentType::String), + ); + let err = parser.parse(&argv(&["--", "x", "y"])).unwrap_err(); + match err { + ArgsError::InvalidValue { expected, .. } => { + assert!(expected.contains("no extra positional")) + } + _ => panic!(), + } + } + + #[test] + fn argumentvalue_conversions_success() { + let s: String = ArgumentValue::String("hi".into()).into(); + let i: i64 = ArgumentValue::Integer(7).into(); + let f: f32 = ArgumentValue::Float(1.5).into(); + let d: f64 = ArgumentValue::Double(2.5).into(); + assert_eq!(s, "hi"); + assert_eq!(i, 7); + assert_eq!(f, 1.5); + assert_eq!(d, 2.5); + } +} + +impl ArgumentParser { + fn canonical_name(&self, token: &str) -> Option { + if self.args.contains_key(token) { + return Some(token.to_string()); + } + if let Some(name) = self.aliases.get(token) { + return Some(name.clone()); + } + None + } + + fn assign_next_positional( + &mut self, + out: &mut Vec, + value: &str, + ) -> Result<(), ArgsError> { + for pname in self.positionals.clone() { + if let Some(entry) = self.args.get_mut(&pname) { + if entry.1 == false { + let parsed = parse_value(&entry.0, value)?; + let idx = entry.2; + out[idx] = ParsedArgument::new(entry.0.name.as_str(), parsed); + entry.1 = true; + return Ok(()); + } } } - return parsed_arguments; + Err(ArgsError::InvalidValue { + name: "".to_string(), + expected: "no extra positional arguments".to_string(), + value: value.to_string(), + }) + } + + fn get_present(&self, out: &Vec, name: &str) -> bool { + let canon = if self.args.contains_key(name) { + name.to_string() + } else if let Some(n) = self.aliases.get(name) { + n.clone() + } else { + name.to_string() + }; + if let Some((_a, _f, idx)) = self.args.get(&canon) { + return !matches!(out[*idx].value, ArgumentValue::None); + } + false + } +} + +fn normalize_name_display(name: &str) -> String { + name + .trim_start_matches('-') + .replace('-', "_") + .to_uppercase() +} + +fn read_env_var(prefix: &str, name: &str) -> Option { + let key = format!("{}_{}", prefix, normalize_name_display(name)); + std::env::var(&key).ok() +} + +fn read_config_file( + path: &str, + parser: &ArgumentParser, +) -> Result, ()> { + let content = std::fs::read_to_string(path).map_err(|_| ())?; + let mut norm: HashMap = HashMap::new(); + for (name, (_a, _f, _i)) in parser.args.iter() { + norm.insert(normalize_name_display(name), name.clone()); + } + let mut out = vec![]; + for line in content.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + let parts: Vec<&str> = line.splitn(2, '=').collect(); + if parts.len() != 2 { + continue; + } + let k = parts[0].trim(); + let v = parts[1].trim(); + let key = if parser.args.contains_key(k) { + k.to_string() + } else if let Some(c) = norm.get(&k.to_uppercase()) { + c.clone() + } else { + continue; + }; + out.push((key, v.to_string())); + } + Ok(out) +} + +fn unknown_with_suggestion(arg: &str, parser: &ArgumentParser) -> String { + let mut best: Option<(usize, String)> = None; + for key in parser.args.keys() { + let d = levenshtein(arg, key); + if best.as_ref().map(|(bd, _)| d < *bd).unwrap_or(true) { + best = Some((d, key.clone())); + } + } + if let Some((_d, name)) = best { + format!("{} (did you mean '{}'?)", arg, name) + } else { + arg.to_string() + } +} + +fn levenshtein(a: &str, b: &str) -> usize { + let mut prev: Vec = (0..=b.len()).collect(); + let mut cur: Vec = vec![0; b.len() + 1]; + for (i, ca) in a.chars().enumerate() { + cur[0] = i + 1; + for (j, cb) in b.chars().enumerate() { + let cost = if ca == cb { 0 } else { 1 }; + cur[j + 1] = std::cmp::min( + std::cmp::min(cur[j] + 1, prev[j + 1] + 1), + prev[j] + cost, + ); + } + std::mem::swap(&mut prev, &mut cur); } + prev[b.len()] } diff --git a/crates/lambda-rs-logging/src/handler.rs b/crates/lambda-rs-logging/src/handler.rs index 4e418b2e..035d8948 100644 --- a/crates/lambda-rs-logging/src/handler.rs +++ b/crates/lambda-rs-logging/src/handler.rs @@ -1,11 +1,6 @@ //! Log handling implementations for the logger. -use std::{ - fmt::Debug, - fs::OpenOptions, - io::Write, - time::SystemTime, -}; +use std::{fmt::Debug, fs::OpenOptions, io::Write, time::SystemTime}; use crate::LogLevel; diff --git a/crates/lambda-rs-platform/Cargo.toml b/crates/lambda-rs-platform/Cargo.toml index f398d13d..70f089da 100644 --- a/crates/lambda-rs-platform/Cargo.toml +++ b/crates/lambda-rs-platform/Cargo.toml @@ -11,48 +11,31 @@ name = "lambda_platform" path = "src/lib.rs" [dependencies] -gfx-hal = "=0.9.0" -winit = "=0.27.5" -shaderc = "=0.7" -cfg-if = "=1.0.0" +winit = "=0.29.10" +shaderc = { version = "=0.7", optional = true, default-features = false } +naga = { version = "=23.1.0", optional = true, default-features = false, features = ["spv-out", "glsl-in", "wgsl-in"] } rand = "=0.8.5" obj-rs = "=0.7.0" -gfx-backend-empty = "=0.9.0" - +wgpu = { version = "=23.0.1", optional = true, features = ["wgsl", "spirv"] } +pollster = { version = "=0.3.0", optional = true } lambda-rs-logging = { path = "../lambda-rs-logging", version = "2023.1.30" } -# GFX-RS backends -gfx-backend-gl = { version="=0.9.0", optional = true } -gfx-backend-metal = { version="=0.9.0", optional = true } -gfx-backend-vulkan = { version="=0.9.0", optional = true } -gfx-backend-dx11 = { version="=0.9.0", optional = true } -gfx-backend-dx12 = { version="=0.9.0", optional = true } - [dev-dependencies] mockall = "=0.11.3" [features] -default=["shaderc/build-from-source"] -detect-platform=[] -winit-windowing=[] -gfx-with-opengl=["dep:gfx-backend-gl"] -gfx-with-vulkan=["dep:gfx-backend-vulkan"] -gfx-with-metal=["dep:gfx-backend-metal"] -gfx-with-dx11=["dep:gfx-backend-dx11"] -gfx-with-dx12=["dep:gfx-backend-dx12"] +default=["wgpu", "shader-backend-naga"] + +shader-backend-naga=["dep:naga"] +shader-backend-shaderc=["dep:shaderc"] +shader-backend-shaderc-build-from-source=["shader-backend-shaderc", "shaderc/build-from-source"] + +wgpu=["dep:wgpu", "dep:pollster", "wgpu/wgsl", "wgpu/spirv", "shader-backend-naga"] +wgpu-with-vulkan=["wgpu"] +wgpu-with-metal=["wgpu", "wgpu/metal"] +wgpu-with-dx12=["wgpu", "wgpu/dx12"] +wgpu-with-gl=["wgpu", "wgpu/webgl"] [profile.dev] crate-type = ["cdylib", "rlib"] incremental = true - -[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.gfx-backend-gl] -package = "gfx-backend-gl" -version = "=0.9.0" - -[target.'cfg(all(target_os = "macos"))'.dependencies.gfx-backend-metal] -package = "gfx-backend-metal" -version = "=0.9.0" - -[target.'cfg(all(windows))'.dependencies.gfx-backend-dx12] -package = "gfx-backend-dx12" -version = "=0.9.0" diff --git a/crates/lambda-rs-platform/src/gfx/adapter.rs b/crates/lambda-rs-platform/src/gfx/adapter.rs deleted file mode 100644 index 5c5514e1..00000000 --- a/crates/lambda-rs-platform/src/gfx/adapter.rs +++ /dev/null @@ -1 +0,0 @@ -struct Adapter; diff --git a/crates/lambda-rs-platform/src/gfx/api.rs b/crates/lambda-rs-platform/src/gfx/api.rs deleted file mode 100644 index 951dc2ba..00000000 --- a/crates/lambda-rs-platform/src/gfx/api.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! GPU API exports to set the platforms primary rendering API for rendering -//! implementations to use. - -cfg_if::cfg_if! { -if #[cfg(any(feature = "gfx-with-gl", all(feature = "detect-platform", unix, not(target_os="macos")) ))] { - pub use gfx_backend_gl as RenderingAPI; -} else if #[cfg(any(feature = "gfx-with-metal", all(feature = "detect-platform", target_os="macos")))] { - pub use gfx_backend_metal as RenderingAPI; -} else if #[cfg(feature = "gfx-with-vulkan")] { - pub use gfx_backend_vulkan as RenderingAPI; -} else if #[cfg(feature = "gfx-with-dx11")] { - pub use gfx_backend_dx11 as RenderingAPI; -} else if #[cfg(any(feature = "gfx-with-dx12", all(windows, feature = "detect-platform")))] { - pub use gfx_backend_dx12 as RenderingAPI; -} else { - pub use gfx_backend_empty as RenderingAPI; - } -} diff --git a/crates/lambda-rs-platform/src/gfx/assembler.rs b/crates/lambda-rs-platform/src/gfx/assembler.rs deleted file mode 100644 index 7e2c2859..00000000 --- a/crates/lambda-rs-platform/src/gfx/assembler.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! Primitive assembly for the graphics pipeline. - -pub use gfx_hal::pso::Element as VertexElement; -use gfx_hal::pso::{ - self, - AttributeDesc, - VertexBufferDesc, -}; - -use super::{ - buffer::Buffer, - surface::ColorFormat, -}; - -/// Attributes for a vertex. -#[derive(Debug, Clone)] -pub struct VertexAttribute { - pub location: u32, - pub offset: u32, - pub element: VertexElement, -} - -/// PrimitiveAssemblerBuilder for preparing PrimitiveAssemblers to use in the -/// lambda-platform Rendering pipeline. -pub struct PrimitiveAssemblerBuilder { - buffer_descriptions: Vec, - attribute_descriptions: Vec, -} - -impl PrimitiveAssemblerBuilder { - pub fn new() -> Self { - return Self { - buffer_descriptions: Vec::new(), - attribute_descriptions: Vec::new(), - }; - } - - /// Build a primitive assembler given the lambda-platform vertex shader - /// module. Buffers & attributes do not have to be tied to - pub fn build<'shader, RenderBackend: gfx_hal::Backend>( - &'shader mut self, - vertex_shader: &'shader super::shader::ShaderModule, - buffers: Option<&Vec<&Buffer>>, - attributes: Option<&[VertexAttribute]>, - ) -> PrimitiveAssembler<'shader, RenderBackend> { - let binding = self.buffer_descriptions.len() as u32; - - match (buffers, attributes) { - (Some(buffers), Some(attributes)) => { - logging::debug!( - "Building primitive assembler with buffers and attributes" - ); - self.buffer_descriptions = buffers - .iter() - .map(|buffer| VertexBufferDesc { - binding, - stride: buffer.stride() as u32, - rate: pso::VertexInputRate::Vertex, - }) - .collect(); - - self.attribute_descriptions = attributes - .iter() - .map(|attribute| { - return AttributeDesc { - location: attribute.location, - binding, - element: attribute.element, - }; - }) - .collect(); - } - _ => {} - } - - let primitive_assembler = pso::PrimitiveAssemblerDesc::Vertex { - buffers: self.buffer_descriptions.as_slice(), - attributes: self.attribute_descriptions.as_slice(), - input_assembler: pso::InputAssemblerDesc::new( - pso::Primitive::TriangleList, - ), - vertex: pso::EntryPoint { - entry: vertex_shader.entry(), - module: super::internal::module_for(vertex_shader), - specialization: vertex_shader.specializations().clone(), - }, - tessellation: None, - geometry: None, - }; - - return PrimitiveAssembler::<'shader> { - primitive_assembler, - }; - } -} - -/// PrimitiveAssembler for used for describing how Vertex Shaders should -/// construct primitives. Each constructed Primitive Assembler should be alive -/// for as long as the shader module that created it is. -pub struct PrimitiveAssembler<'shader, RenderBackend: gfx_hal::Backend> { - primitive_assembler: pso::PrimitiveAssemblerDesc<'shader, RenderBackend>, -} - -impl<'shader, RenderBackend: gfx_hal::Backend> - PrimitiveAssembler<'shader, RenderBackend> -{ - // Get the internal primitive assembler. - pub(super) fn internal_primitive_assembler( - self, - ) -> pso::PrimitiveAssemblerDesc<'shader, RenderBackend> { - return self.primitive_assembler; - } -} diff --git a/crates/lambda-rs-platform/src/gfx/buffer.rs b/crates/lambda-rs-platform/src/gfx/buffer.rs deleted file mode 100644 index 3ceed4a8..00000000 --- a/crates/lambda-rs-platform/src/gfx/buffer.rs +++ /dev/null @@ -1,218 +0,0 @@ -use gfx_hal::{ - memory::{ - Segment, - SparseFlags, - }, - prelude::Device, - Backend, -}; - -use super::gpu::Gpu; - -// Reuse gfx-hal buffer usage & properties for now. -pub type Usage = gfx_hal::buffer::Usage; -pub type Properties = gfx_hal::memory::Properties; - -/// The type of buffers that can be allocated on the GPU. -#[derive(Debug, Clone, Copy)] -pub enum BufferType { - Vertex, - Index, - Uniform, - Storage, -} - -/// A buffer is a block of memory that can be used to store data that can be -/// accessed by the GPU. -#[derive(Debug, Clone, Copy)] -pub struct Buffer { - buffer: RenderBackend::Buffer, - memory: RenderBackend::Memory, - stride: usize, - buffer_type: BufferType, -} - -impl Buffer { - /// Destroy the buffer and all it's resources with the GPU that - /// created it. - pub fn destroy(self, gpu: &Gpu) { - unsafe { - gpu.internal_logical_device().free_memory(self.memory); - gpu.internal_logical_device().destroy_buffer(self.buffer); - } - } - - /// Size of the buffer in bytes. - pub fn stride(&self) -> usize { - return self.stride; - } -} - -impl Buffer { - /// Retrieve a reference to the internal buffer. - pub(super) fn internal_buffer(&self) -> &RenderBackend::Buffer { - return &self.buffer; - } -} - -pub struct BufferBuilder { - buffer_length: usize, - usage: Usage, - properties: Properties, - buffer_type: BufferType, -} - -impl BufferBuilder { - pub fn new() -> Self { - return Self { - buffer_length: 0, - usage: Usage::empty(), - properties: Properties::empty(), - buffer_type: BufferType::Vertex, - }; - } - - pub fn with_length(&mut self, length: usize) -> &mut Self { - self.buffer_length = length; - return self; - } - - pub fn with_usage(&mut self, usage: Usage) -> &mut Self { - self.usage = usage; - return self; - } - - pub fn with_properties(&mut self, properties: Properties) -> &mut Self { - self.properties = properties; - return self; - } - - pub fn with_buffer_type(&mut self, buffer_type: BufferType) -> &mut Self { - self.buffer_type = buffer_type; - return self; - } - - /// Builds & binds a buffer of memory to the GPU. If the buffer cannot be - /// bound to the GPU, the buffer memory is freed before the error is returned. - /// Data must represent the data that will be stored in the buffer, meaning - /// it must repr C and be the same size as the buffer length. - pub fn build( - &self, - gpu: &mut Gpu, - data: Vec, - ) -> Result, &'static str> { - use gfx_hal::{ - adapter::PhysicalDevice, - MemoryTypeId, - }; - let logical_device = gpu.internal_logical_device(); - let physical_device = gpu.internal_physical_device(); - - // TODO(vmarcella): Add the ability for the user to specify the memory - // properties (I.E. SparseFlags::SPARSE_MEMORY). - logging::debug!( - "[DEBUG] Creating buffer of length: {}", - self.buffer_length - ); - let buffer_result = unsafe { - logical_device.create_buffer( - self.buffer_length as u64, - self.usage, - SparseFlags::empty(), - ) - }; - - if buffer_result.is_err() { - logging::error!("Failed to create buffer for allocating memory."); - return Err("Failed to create buffer for allocating memory."); - } - - let mut buffer = buffer_result.unwrap(); - - let requirements = - unsafe { logical_device.get_buffer_requirements(&buffer) }; - let memory_types = physical_device.memory_properties().memory_types; - - logging::debug!("Buffer requirements: {:?}", requirements); - // Find a memory type that supports the requirements of the buffer. - let memory_type = memory_types - .iter() - .enumerate() - .find(|(id, memory_type)| { - let type_supported = requirements.type_mask & (1 << id) != 0; - type_supported && memory_type.properties.contains(self.properties) - }) - .map(|(id, _)| MemoryTypeId(id)) - .unwrap(); - - logging::debug!("Allocating memory for buffer."); - // Allocates the memory on the GPU for the buffer. - let buffer_memory_allocation = - unsafe { logical_device.allocate_memory(memory_type, requirements.size) }; - - if buffer_memory_allocation.is_err() { - logging::error!("Failed to allocate memory for buffer."); - return Err("Failed to allocate memory for buffer."); - } - - let mut buffer_memory = buffer_memory_allocation.unwrap(); - - // Bind the buffer to the GPU memory - let buffer_binding = unsafe { - logical_device.bind_buffer_memory(&buffer_memory, 0, &mut buffer) - }; - - // Destroy the buffer if we failed to bind it to memory. - if buffer_binding.is_err() { - unsafe { logical_device.destroy_buffer(buffer) }; - logging::error!("Failed to bind buffer memory."); - return Err("Failed to bind buffer memory."); - } - - // Get address of the buffer memory on the GPU so that we can write to it. - let get_mapping_to_memory = - unsafe { logical_device.map_memory(&mut buffer_memory, Segment::ALL) }; - - if get_mapping_to_memory.is_err() { - unsafe { logical_device.destroy_buffer(buffer) }; - logging::error!("Failed to map memory."); - return Err("Failed to map memory."); - } - let mapped_memory = get_mapping_to_memory.unwrap(); - - // Copy the data to the GPU memory. - unsafe { - std::ptr::copy_nonoverlapping( - data.as_ptr() as *const u8, - mapped_memory, - self.buffer_length, - ); - }; - - // Flush the data to ensure it is written to the GPU memory. - let memory_flush = unsafe { - logical_device - .flush_mapped_memory_ranges(std::iter::once(( - &buffer_memory, - Segment::ALL, - ))) - .map_err(|_| "Failed to flush memory.") - }; - - if memory_flush.is_err() { - unsafe { logical_device.destroy_buffer(buffer) }; - logging::error!("Failed to flush memory."); - return Err("No memory available on the GPU."); - } - - // Unmap the memory now that it's no longer needed by the CPU. - unsafe { logical_device.unmap_memory(&mut buffer_memory) }; - - return Ok(Buffer { - buffer, - memory: buffer_memory, - stride: std::mem::size_of::(), - buffer_type: self.buffer_type, - }); - } -} diff --git a/crates/lambda-rs-platform/src/gfx/command.rs b/crates/lambda-rs-platform/src/gfx/command.rs deleted file mode 100644 index 45f69dc6..00000000 --- a/crates/lambda-rs-platform/src/gfx/command.rs +++ /dev/null @@ -1,418 +0,0 @@ -use std::{ - borrow::Borrow, - collections::HashMap, - ops::Range, - rc::Rc, -}; - -use gfx_hal::{ - command::ClearValue, - device::Device, - pool::CommandPool as _, - prelude::CommandBuffer as GfxCommandBuffer, -}; - -use super::{ - pipeline::RenderPipeline, - viewport::ViewPort, -}; - -/// Command Pool Flag used to define optimizations/properties of the command -/// pool prior to being built. -pub enum CommandPoolFeatures { - /// Optimizes the command pool for buffers that are expected to have short - /// lifetimes (I.E. command buffers that continuously need to render data to - /// the screen) - ShortLivedBuffers, - /// Allows for buffers to be reset individually & manually by the owner of the - /// command pool. - ResetBuffersIndividually, - /// Enable no features on the CommandPool. - None, - /// Enable all features for a given CommandPool. - All, -} - -/// Features that can be used to optimize command buffers for specific -/// usages/scenarios -pub enum CommandBufferFeatures { - /// Enable this feature when you would like the command buffer to reset it's - /// contents every time its submitted for Rendering. - ResetEverySubmission, - /// Enable this feature if the command buffer lives within the lifetime of a - /// render pass. - TiedToRenderPass, - /// Enable this feature if the command buffer allows for silumtaneous - /// recording - SimultaneousRecording, - /// Enables no features. - None, - /// Enables all features. - All, -} - -/// This enum is used for specifying the type of command buffer to allocate on -/// the command pool. -pub enum CommandBufferLevel { - /// Use for allocating a top level primary command buffer on the - /// command pool. A command buffer at this level can then be used to create - /// other primaries. - Primary, - /// Used - Secondary, -} - -/// Enumeration for issuing commands to a CommandBuffer allocated on the GPU. -/// The enumerations are evaluated upon being issued to an active command buffer -/// and correspond to lower level function calls. -pub enum Command { - /// Begins recording commands to the GPU. A primary command buffer can only - /// issue this command once. - BeginRecording, - SetViewports { - start_at: u32, - viewports: Vec, - }, - SetScissors { - start_at: u32, - viewports: Vec, - }, - BeginRenderPass { - render_pass: Rc>, - surface: Rc>, - frame_buffer: Rc>, - viewport: ViewPort, - }, - /// Ends a currently active render pass. - EndRenderPass, - AttachGraphicsPipeline { - pipeline: Rc>, - }, - Draw { - vertices: Range, - }, - PushConstants { - pipeline: Rc>, - stage: super::pipeline::PipelineStage, - offset: u32, - bytes: Vec, - }, - BindVertexBuffer { - buffer: Rc>, - }, - EndRecording, -} - -/// Representation of a command buffer allocated on the GPU. The lifetime of -/// the command is constrained to the lifetime of the command pool that built -/// it to ensure that it cannot be used while -pub struct CommandBuffer<'command_pool, RenderBackend: gfx_hal::Backend> { - command_buffer: &'command_pool mut RenderBackend::CommandBuffer, - flags: gfx_hal::command::CommandBufferFlags, -} - -impl<'command_pool, RenderBackend: gfx_hal::Backend> - CommandBuffer<'command_pool, RenderBackend> -{ - /// Validates and issues a command directly to the buffer on the GPU. - /// If using a newly created Primary CommandBuffer the first and last commands - /// that should be issued are: - /// Command::BeginRecording - /// Command::EndRecording - /// Once the command buffer has stopped recording, it can be submitted to the - /// GPU to start performing work. - pub fn issue_command(&mut self, command: Command) { - use gfx_hal::command::CommandBuffer as _; - unsafe { - match command { - Command::BeginRecording => { - self.command_buffer.begin_primary(self.flags) - } - Command::SetViewports { - start_at, - viewports, - } => self.command_buffer.set_viewports( - start_at, - viewports - .into_iter() - .map(|viewport| viewport.internal_viewport()), - ), - Command::SetScissors { - start_at, - viewports, - } => self.command_buffer.set_scissors( - start_at, - viewports - .into_iter() - .map(|viewport| viewport.internal_viewport().rect), - ), - - Command::BeginRenderPass { - render_pass, - frame_buffer, - surface, - viewport, - } => self.command_buffer.begin_render_pass( - render_pass.internal_render_pass(), - frame_buffer.internal_frame_buffer(), - viewport.internal_viewport().rect, - vec![gfx_hal::command::RenderAttachmentInfo:: { - image_view: surface - .internal_surface_image() - .expect("No internal surface set when beginning the render pass.") - .borrow(), - clear_value: ClearValue { - color: gfx_hal::command::ClearColor { - float32: [0.0, 0.0, 0.0, 1.0], - }, - }, - }] - .into_iter(), - gfx_hal::command::SubpassContents::Inline, - ), - Command::AttachGraphicsPipeline { pipeline } => self - .command_buffer - .bind_graphics_pipeline(pipeline.internal_pipeline()), - Command::EndRenderPass => self.command_buffer.end_render_pass(), - Command::PushConstants { - pipeline, - stage, - offset, - bytes, - } => self.command_buffer.push_graphics_constants( - pipeline.internal_pipeline_layout(), - stage, - offset, - bytes.as_slice(), - ), - Command::Draw { vertices } => { - self.command_buffer.draw(vertices.clone(), 0..1) - } - Command::BindVertexBuffer { buffer } => { - self.command_buffer.bind_vertex_buffers( - 0, - vec![(buffer.internal_buffer(), gfx_hal::buffer::SubRange::WHOLE)] - .into_iter(), - ) - } - Command::EndRecording => self.command_buffer.finish(), - } - } - } - - /// Functions exactly like issue_command except over multiple commands at - /// once. Command execution is based on the order of commands inside the - /// vector. - pub fn issue_commands(&mut self, commands: Vec>) { - for command in commands { - self.issue_command(command); - } - } - - pub fn reset(&mut self) { - unsafe { - self.command_buffer.reset(true); - } - } -} - -/// Builder for creating a Command buffer that can issue commands directly to -/// the GPU. -pub struct CommandBufferBuilder { - flags: gfx_hal::command::CommandBufferFlags, - level: CommandBufferLevel, -} - -impl CommandBufferBuilder { - pub fn new(level: CommandBufferLevel) -> Self { - let flags = gfx_hal::command::CommandBufferFlags::empty(); - return CommandBufferBuilder { flags, level }; - } - - pub fn with_feature(mut self, feature: CommandBufferFeatures) -> Self { - let flags = match feature { - CommandBufferFeatures::ResetEverySubmission => { - gfx_hal::command::CommandBufferFlags::ONE_TIME_SUBMIT - } - CommandBufferFeatures::TiedToRenderPass => { - gfx_hal::command::CommandBufferFlags::RENDER_PASS_CONTINUE - } - CommandBufferFeatures::SimultaneousRecording => { - gfx_hal::command::CommandBufferFlags::SIMULTANEOUS_USE - } - CommandBufferFeatures::None => { - gfx_hal::command::CommandBufferFlags::empty() - } - CommandBufferFeatures::All => gfx_hal::command::CommandBufferFlags::all(), - }; - - self.flags.insert(flags); - return self; - } - - /// Build the command buffer and tie it to the lifetime of the command pool - /// that gets created. - pub fn build<'command_pool, RenderBackend: gfx_hal::Backend>( - self, - command_pool: &'command_pool mut CommandPool, - name: &str, - ) -> CommandBuffer<'command_pool, RenderBackend> { - let command_buffer = - command_pool.fetch_or_allocate_command_buffer(name, self.level); - - let flags = self.flags; - - return CommandBuffer { - command_buffer, - flags, - }; - } -} - -pub struct CommandPoolBuilder { - command_pool_flags: gfx_hal::pool::CommandPoolCreateFlags, -} - -pub mod internal { - pub fn command_buffer_for< - 'render_context, - RenderBackend: gfx_hal::Backend, - >( - command_buffer: &'render_context super::CommandBuffer< - 'render_context, - RenderBackend, - >, - ) -> &'render_context RenderBackend::CommandBuffer { - return command_buffer.command_buffer; - } -} - -impl CommandPoolBuilder { - pub fn new() -> Self { - return Self { - command_pool_flags: gfx_hal::pool::CommandPoolCreateFlags::empty(), - }; - } - - /// Attach command pool create flags to the command pool builder. - pub fn with_features(mut self, flag: CommandPoolFeatures) -> Self { - let flags = match flag { - CommandPoolFeatures::ShortLivedBuffers => { - gfx_hal::pool::CommandPoolCreateFlags::TRANSIENT - } - CommandPoolFeatures::ResetBuffersIndividually => { - gfx_hal::pool::CommandPoolCreateFlags::RESET_INDIVIDUAL - } - CommandPoolFeatures::None => { - gfx_hal::pool::CommandPoolCreateFlags::empty() - } - CommandPoolFeatures::All => gfx_hal::pool::CommandPoolCreateFlags::all(), - }; - - self.command_pool_flags.insert(flags); - return self; - } - - /// Builds a command pool. - pub fn build( - self, - gpu: &super::gpu::Gpu, - ) -> CommandPool { - let command_pool = unsafe { - gpu - .internal_logical_device() - .create_command_pool( - gpu.internal_queue_family(), - self.command_pool_flags, - ) - .expect("") - }; - - return CommandPool { - command_pool, - command_buffers: HashMap::new(), - }; - } -} - -pub struct CommandPool { - command_pool: RenderBackend::CommandPool, - command_buffers: HashMap, -} - -impl CommandPool { - /// Allocate a command buffer for lambda. - fn fetch_or_allocate_command_buffer( - &mut self, - name: &str, - level: CommandBufferLevel, - ) -> &mut RenderBackend::CommandBuffer { - if self.command_buffers.contains_key(name) { - return self.command_buffers.get_mut(name).unwrap(); - } - - let buffer = unsafe { - self - .command_pool - .allocate_one(gfx_hal::command::Level::Primary) - }; - - self.command_buffers.insert(name.to_string(), buffer); - return self.command_buffers.get_mut(name).unwrap(); - } - - /// Deallocate a command buffer - // TODO(vmarcella): This function should return a result based on the status - // of the deallocation. - pub fn deallocate_command_buffer(&mut self, name: &str) { - if self.command_buffers.contains_key(name) == false { - return; - } - - let buffer = self - .command_buffers - .remove(&name.to_string()) - .expect(format!("Command Buffer {} doesn't exist", name).as_str()); - - unsafe { self.command_pool.free(vec![buffer].into_iter()) } - } - - /// Buffers can be looked up with the same name that they're given when - /// calling `allocate_command_buffer` - pub fn get_mutable_command_buffer( - &mut self, - name: &str, - ) -> Option<&mut RenderBackend::CommandBuffer> { - return self.command_buffers.get_mut(name); - } - - /// Retrieves a command buffer that has been allocated by this command pool. - /// This function is most likely not - #[inline] - pub fn get_command_buffer( - &self, - name: &str, - ) -> Option<&RenderBackend::CommandBuffer> { - return self.command_buffers.get(name); - } - - /// Resets the command pool and all of the command buffers. - #[inline] - pub fn reset_pool(&mut self, release_resources: bool) { - unsafe { - self.command_pool.reset(release_resources); - } - } - - /// Moves the command pool into itself and destroys any command pool and - /// buffer resources allocated on the GPU. - #[inline] - pub fn destroy(mut self, gpu: &super::gpu::Gpu) { - unsafe { - self.command_pool.reset(true); - gpu - .internal_logical_device() - .destroy_command_pool(self.command_pool); - } - } -} diff --git a/crates/lambda-rs-platform/src/gfx/fence.rs b/crates/lambda-rs-platform/src/gfx/fence.rs deleted file mode 100644 index cf66ebb4..00000000 --- a/crates/lambda-rs-platform/src/gfx/fence.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! GPU synchronization Implementations that are built on top of gfx-hal and -//! are used by the lambda-platform rendering implementations to synchronize -//! GPU operations. - -use gfx_hal::device::Device; - -pub struct RenderSemaphoreBuilder {} - -impl RenderSemaphoreBuilder { - pub fn new() -> Self { - return Self {}; - } - - /// Builds a new render semaphore using the provided GPU. This semaphore can - /// only be used with the GPU that it was created with. - pub fn build( - self, - gpu: &mut super::gpu::Gpu, - ) -> RenderSemaphore { - let semaphore = gpu - .internal_logical_device() - .create_semaphore() - .expect("The GPU has no memory to allocate the semaphore"); - - return RenderSemaphore { semaphore }; - } -} - -/// Render semaphores are used to synchronize operations happening within the -/// GPU. This allows for us to tell the GPU to wait for a frame to finish -/// rendering before presenting it to the screen. -pub struct RenderSemaphore { - semaphore: RenderBackend::Semaphore, -} - -impl RenderSemaphore { - /// Destroys the semaphore using the GPU that created it. - pub fn destroy(self, gpu: &super::gpu::Gpu) { - unsafe { - gpu - .internal_logical_device() - .destroy_semaphore(self.semaphore) - } - } -} - -impl RenderSemaphore { - /// Retrieve a reference to the internal semaphore. - pub(super) fn internal_semaphore(&self) -> &RenderBackend::Semaphore { - return &self.semaphore; - } - - /// Retrieve a mutable reference to the internal semaphore. - pub(super) fn internal_semaphore_mut( - &mut self, - ) -> &mut RenderBackend::Semaphore { - return &mut self.semaphore; - } -} - -pub struct RenderSubmissionFenceBuilder { - default_render_timeout: u64, -} - -impl RenderSubmissionFenceBuilder { - /// Creates a new Render Submission Fence Builder that defaults to a 1 second - /// timeout for waiting on the fence. - pub fn new() -> Self { - return Self { - default_render_timeout: 1_000_000_000, - }; - } - - /// Provides a default render timeout in nanoseconds. This render timeout is - /// used to reset the submission fence if it's time-to-live expires. - pub fn with_render_timeout(mut self, render_timeout: u64) -> Self { - self.default_render_timeout = render_timeout; - return self; - } - - /// Builds a new submission fence using the provided GPU. This fence can only - /// be used to block operation on the GPU that created it. - pub fn build( - self, - gpu: &mut super::gpu::Gpu, - ) -> RenderSubmissionFence { - let fence = gpu - .internal_logical_device() - .create_fence(true) - .expect("There is not enough memory to create a fence on this device."); - - return RenderSubmissionFence { - fence, - default_render_timeout: self.default_render_timeout, - }; - } -} - -/// A GPU fence is used to synchronize GPU operations. It is used to ensure that -/// a GPU operation has completed before the CPU attempts to submit commands to -/// it. -pub struct RenderSubmissionFence { - fence: RenderBackend::Fence, - default_render_timeout: u64, -} - -impl RenderSubmissionFence { - /// Block a GPU until the fence is ready and then reset the fence status. - pub fn block_until_ready( - &mut self, - gpu: &mut super::gpu::Gpu, - render_timeout_override: Option, - ) { - let timeout = match render_timeout_override { - Some(render_timeout_override) => render_timeout_override, - None => self.default_render_timeout, - }; - - unsafe { - gpu.internal_logical_device() - .wait_for_fence(&self.fence, timeout) - } - .expect("The GPU ran out of memory or has become detached from the current context."); - - unsafe { gpu.internal_logical_device().reset_fence(&mut self.fence) } - .expect("The fence failed to reset."); - } - - /// Destroy this fence given the GPU that created it. - pub fn destroy(self, gpu: &super::gpu::Gpu) { - unsafe { gpu.internal_logical_device().destroy_fence(self.fence) } - } -} - -impl RenderSubmissionFence { - /// Retrieve the underlying fence. - pub fn internal_fence(&self) -> &RenderBackend::Fence { - return &self.fence; - } - - /// Retrieve a mutable reference to the underlying fence. - pub fn internal_fence_mut(&mut self) -> &mut RenderBackend::Fence { - return &mut self.fence; - } -} diff --git a/crates/lambda-rs-platform/src/gfx/framebuffer.rs b/crates/lambda-rs-platform/src/gfx/framebuffer.rs deleted file mode 100644 index 1c99d5ed..00000000 --- a/crates/lambda-rs-platform/src/gfx/framebuffer.rs +++ /dev/null @@ -1,70 +0,0 @@ -use gfx_hal::{ - device::Device, - image::Extent, -}; - -use super::{ - gpu::Gpu, - render_pass::RenderPass, - surface::Surface, -}; - -/// Framebuffer for the given render backend. -#[derive(Debug)] -pub struct Framebuffer { - frame_buffer: RenderBackend::Framebuffer, -} - -impl Framebuffer { - /// Destroys the framebuffer from the given GPU. - pub fn destroy(self, gpu: &super::gpu::Gpu) { - unsafe { - gpu - .internal_logical_device() - .destroy_framebuffer(self.frame_buffer); - } - } -} - -impl Framebuffer { - /// Retrieve a reference to the internal frame buffer. - pub(super) fn internal_frame_buffer(&self) -> &RenderBackend::Framebuffer { - return &self.frame_buffer; - } -} -pub struct FramebufferBuilder {} - -impl FramebufferBuilder { - pub fn new() -> Self { - return Self {}; - } - - /// Build a frame buffer on a given GPU for the given surface. - pub fn build( - self, - gpu: &mut Gpu, - render_pass: &RenderPass, - surface: &Surface, - ) -> Framebuffer { - let (width, height) = surface.size().expect("A surface without a swapchain cannot be used in a framebeen configured with a swapchain"); - let image = surface - .internal_frame_buffer_attachment() - .expect("A surface without a swapchain cannot be used in a frame."); - - let frame_buffer = unsafe { - gpu - .internal_logical_device() - .create_framebuffer( - render_pass.internal_render_pass(), - vec![image].into_iter(), - Extent { - width, - height, - depth: 1, - }, - ) - .expect("Failed to create a framebuffer") - }; - return Framebuffer { frame_buffer }; - } -} diff --git a/crates/lambda-rs-platform/src/gfx/gpu.rs b/crates/lambda-rs-platform/src/gfx/gpu.rs deleted file mode 100644 index c30442f9..00000000 --- a/crates/lambda-rs-platform/src/gfx/gpu.rs +++ /dev/null @@ -1,225 +0,0 @@ -use gfx_hal::{ - adapter::Adapter, - prelude::{ - PhysicalDevice, - QueueFamily, - }, - queue::{ - Queue, - QueueGroup, - }, -}; -#[cfg(test)] -use mockall::automock; - -use super::{ - command::CommandBuffer, - fence::{ - RenderSemaphore, - RenderSubmissionFence, - }, - surface, -}; - -/// GpuBuilder for constructing a GPU -pub struct GpuBuilder { - render_queue_type: RenderQueueType, -} - -impl GpuBuilder { - /// Create a new GpuBuilder to configure and build a GPU to use for rendering. - pub fn new() -> Self { - return Self { - render_queue_type: RenderQueueType::Graphical, - }; - } - - /// Set the type of queue to use for rendering. The GPU defaults to graphical. - pub fn with_render_queue_type(mut self, queue_type: RenderQueueType) -> Self { - self.render_queue_type = queue_type; - return self; - } - - /// If passing in a surface, the gpu will be built using the queue that best - /// supports both the render queue & surface. - pub fn build( - self, - instance: &mut super::Instance, - surface: Option<&surface::Surface>, - ) -> Result, String> { - match (surface, self.render_queue_type) { - (Some(surface), RenderQueueType::Graphical) => { - let adapter = instance.first_adapter(); - - let queue_family = adapter - .queue_families - .iter() - .find(|family| { - return surface.can_support_queue_family(family) - && family.queue_type().supports_graphics(); - }) - .expect("No compatible queue family found.") - .id(); - - return Ok(Gpu::new(adapter, queue_family)); - } - (Some(_surface), RenderQueueType::Compute) => { - todo!("Support a Compute based GPU.") - } - (_, _) => return Err("Failed to build GPU.".to_string()), - } - } -} - -/// Commands oriented around creating resources on & for the GPU. -pub struct Gpu { - adapter: gfx_hal::adapter::Adapter, - gpu: gfx_hal::adapter::Gpu, - queue_group: QueueGroup, -} - -/// The render queue types that the GPU can use for -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum RenderQueueType { - Compute, - Graphical, - GraphicalCompute, - Transfer, -} - -impl Gpu { - /// Instantiates a new GPU given an adapter that is implemented by the GPUs - /// current rendering backend B. A new GPU does not come with a command pool - /// unless specified. - pub(super) fn new( - adapter: Adapter, - queue_family: gfx_hal::queue::QueueFamilyId, - ) -> Self { - let queue_family = adapter - .queue_families - .iter() - .find(|family| family.id() == queue_family) - .expect("Failed to find the queue family requested for the GPU."); - - let mut gpu = unsafe { - adapter - .physical_device - .open(&[(queue_family, &[1.0])], gfx_hal::Features::empty()) - .expect("Failed to open the device.") - }; - - let queue_group = gpu.queue_groups.pop().unwrap(); - - return Self { - adapter, - gpu, - queue_group, - }; - } - - /// Submits a command buffer to the GPU. - pub fn submit_command_buffer<'render_context>( - &mut self, - command_buffer: &mut CommandBuffer, - signal_semaphores: Vec<&RenderSemaphore>, - fence: &mut RenderSubmissionFence, - ) { - let commands = - vec![super::command::internal::command_buffer_for(command_buffer)] - .into_iter(); - unsafe { - self - .queue_group - .queues - .first_mut() - .expect("Couldn't find the primary queue to submit commands to. ") - .submit( - commands, - vec![].into_iter(), - // TODO(vmarcella): This was needed to allow the push constants to - // properly render to the screen. Look into a better way to do this. - signal_semaphores.into_iter().map(|semaphore| { - return semaphore.internal_semaphore(); - }), - Some(fence.internal_fence_mut()), - ); - } - } - - /// Render to the surface and return the result from the GPU. - pub fn render_to_surface( - &mut self, - surface: &mut surface::Surface, - semaphore: &mut RenderSemaphore, - ) -> Result<(), &str> { - let (render_surface, render_image) = surface.internal_surface_and_image(); - - let result = unsafe { - self.queue_group.queues[0].present( - render_surface, - render_image, - Some(semaphore.internal_semaphore_mut()), - ) - }; - - if result.is_err() { - logging::error!( - "Failed to present to the surface: {:?}", - result.err().unwrap() - ); - surface.remove_swapchain(self); - return Err( - "Rendering failed. Swapchain for the surface needs to be reconfigured.", - ); - } - - return Ok(()); - } -} - -impl Gpu { - pub(super) fn internal_logical_device(&self) -> &RenderBackend::Device { - return &self.gpu.device; - } - - pub(super) fn internal_physical_device( - &self, - ) -> &RenderBackend::PhysicalDevice { - return &self.adapter.physical_device; - } - - pub(super) fn internal_queue_family(&self) -> gfx_hal::queue::QueueFamilyId { - return self.queue_group.family; - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_gpu_builder_default_state() { - use super::{ - GpuBuilder, - RenderQueueType, - }; - - let builder = GpuBuilder::new(); - - assert_eq!(builder.render_queue_type, RenderQueueType::Graphical); - } - - #[test] - fn test_gpu_builder_with_render_queue_type() { - use super::{ - GpuBuilder, - RenderQueueType, - }; - - let builder = - GpuBuilder::new().with_render_queue_type(RenderQueueType::Compute); - - assert_eq!(builder.render_queue_type, RenderQueueType::Compute); - } - - #[test] - fn test_gpu_builder_build() {} -} diff --git a/crates/lambda-rs-platform/src/gfx/mod.rs b/crates/lambda-rs-platform/src/gfx/mod.rs deleted file mode 100644 index c7a464d1..00000000 --- a/crates/lambda-rs-platform/src/gfx/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -// -------------------------- GFX PLATFORM EXPORTS ----------------------------- - -pub mod api; -pub mod assembler; -pub mod buffer; -pub mod command; -pub mod fence; -pub mod framebuffer; -pub mod gpu; -pub mod pipeline; -pub mod render_pass; -pub mod resource; -pub mod shader; -pub mod surface; -pub mod viewport; - -use gfx_hal::{ - Backend, - Instance as _, -}; - -// ----------------------- INSTANCE BUILDER AND INSTANCE ------------------------------- - -pub struct InstanceBuilder {} - -#[cfg(test)] -use mockall::automock; - -#[cfg_attr(test, automock)] -impl InstanceBuilder { - pub fn new() -> Self { - return Self {}; - } - - /// Builds a graphical instance for the current platform. - pub fn build( - self, - name: &str, - ) -> Instance { - return Instance::new(name); - } -} - -pub struct Instance { - gfx_hal_instance: RenderBackend::Instance, -} - -impl Instance { - /// Create a new GfxInstance connected to the current platforms primary backend. - fn new(name: &str) -> Self { - let instance = RenderBackend::Instance::create(name, 1) - .expect("gfx backend not supported by the current platform"); - - return Self { - gfx_hal_instance: instance, - }; - } -} - -impl Instance { - /// Returns a list of all available adapters. - pub(super) fn enumerate_adapters( - &self, - ) -> Vec> { - return self.gfx_hal_instance.enumerate_adapters(); - } - - pub(super) fn first_adapter( - &self, - ) -> gfx_hal::adapter::Adapter { - return self.gfx_hal_instance.enumerate_adapters().remove(0); - } - - pub(super) fn create_surface( - &self, - window_handle: &crate::winit::WindowHandle, - ) -> RenderBackend::Surface { - return unsafe { - self - .gfx_hal_instance - .create_surface(&window_handle.window_handle) - .expect("Failed to create a surface using the current instance and window handle.") - }; - } - - pub(super) fn destroy_surface(&self, surface: RenderBackend::Surface) { - unsafe { - self.gfx_hal_instance.destroy_surface(surface); - } - } -} - -// ----------------------- INTERNAL INSTANCE OPERATIONS ------------------------ - -pub mod internal { - pub use super::shader::internal::*; -} diff --git a/crates/lambda-rs-platform/src/gfx/pipeline.rs b/crates/lambda-rs-platform/src/gfx/pipeline.rs deleted file mode 100644 index a50595bc..00000000 --- a/crates/lambda-rs-platform/src/gfx/pipeline.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::ops::Range; - -/// gfx-hal imports for pipeline.rs -use gfx_hal::{ - device::Device, - pass::Subpass, - pso::{ - BlendState, - ColorBlendDesc, - ColorMask, - EntryPoint, - Face, - GraphicsPipelineDesc, - PrimitiveAssemblerDesc, - Rasterizer, - }, - Backend, -}; - -use super::{ - assembler::{ - PrimitiveAssemblerBuilder, - VertexAttribute, - }, - buffer::Buffer, - gpu::Gpu, - shader::ShaderModule, -}; - -/// Builder for a gfx-hal backed render pipeline. -pub struct RenderPipelineBuilder { - pipeline_layout: Option, - push_constants: Vec, - buffers: Vec>, - attributes: Vec, -} - -pub type PipelineStage = gfx_hal::pso::ShaderStageFlags; - -pub type PushConstantUpload = (PipelineStage, Range); - -impl RenderPipelineBuilder { - pub fn new() -> Self { - return Self { - pipeline_layout: None, - push_constants: Vec::new(), - buffers: Vec::new(), - attributes: Vec::new(), - }; - } - - pub fn with_buffer( - &mut self, - buffer: Buffer, - attributes: Vec, - ) -> &mut Self { - self.buffers.push(buffer); - self.attributes.extend(attributes); - return self; - } - - /// Adds a push constant to the render pipeline at the set PipelineStage(s) - pub fn with_push_constant( - &mut self, - stage: PipelineStage, - bytes: u32, - ) -> &mut Self { - self.push_constants.push((stage, 0..bytes)); - return self; - } - - /// Adds multiple push constants to the render pipeline at their - /// set PipelineStage(s) - pub fn with_push_constants( - mut self, - push_constants: Vec, - ) -> Self { - self.push_constants.extend(push_constants); - return self; - } - - /// Builds a render pipeline based on your builder configuration. You can - /// configure a render pipeline to be however you'd like it to be. - pub fn build( - self, - gpu: &Gpu, - render_pass: &super::render_pass::RenderPass, - vertex_shader: &ShaderModule, - fragment_shader: Option<&ShaderModule>, - buffers: &Vec<&Buffer>, - attributes: &[VertexAttribute], - ) -> RenderPipeline { - // TODO(vmarcella): The pipeline layout should be configurable through the - // RenderPipelineBuilder. - let push_constants = self.push_constants.into_iter(); - - let pipeline_layout = unsafe { - gpu - .internal_logical_device() - .create_pipeline_layout(vec![].into_iter(), push_constants) - .expect( - "The GPU does not have enough memory to allocate a pipeline layout", - ) - }; - - // TODO(vmarcella): The primitive assembler should be configurable through - // the RenderPipelineBuilder so that buffers & attributes can be bound. - let mut builder = PrimitiveAssemblerBuilder::new(); - let primitive_assembler = - builder.build(vertex_shader, Some(buffers), Some(attributes)); - - let fragment_entry = match fragment_shader { - Some(shader) => Some(EntryPoint:: { - entry: shader.entry(), - module: super::internal::module_for(shader), - specialization: shader.specializations().clone(), - }), - None => None, - }; - - let mut pipeline_desc = GraphicsPipelineDesc::new( - primitive_assembler.internal_primitive_assembler(), - Rasterizer { - cull_face: Face::BACK, - ..Rasterizer::FILL - }, - fragment_entry, - &pipeline_layout, - Subpass { - index: 0, - main_pass: render_pass.internal_render_pass(), - }, - ); - - pipeline_desc.blender.targets.push(ColorBlendDesc { - mask: ColorMask::ALL, - blend: Some(BlendState::ALPHA), - }); - - let pipeline = unsafe { - let pipeline_build_result = gpu - .internal_logical_device() - .create_graphics_pipeline(&pipeline_desc, None); - - match pipeline_build_result { - Ok(pipeline) => pipeline, - Err(e) => panic!("Failed to create graphics pipeline: {:?}", e), - } - }; - - return RenderPipeline { - pipeline_layout, - pipeline, - buffers: self.buffers, - }; - } -} - -/// Represents a render capable pipeline for graphical -#[derive(Debug)] -pub struct RenderPipeline { - pipeline_layout: RenderBackend::PipelineLayout, - pipeline: RenderBackend::GraphicsPipeline, - buffers: Vec>, -} - -impl RenderPipeline { - /// Destroys the pipeline layout and graphical pipeline - pub fn destroy(self, gpu: &super::gpu::Gpu) { - logging::debug!("Destroying render pipeline"); - unsafe { - for buffer in self.buffers { - buffer.destroy(gpu); - } - - gpu - .internal_logical_device() - .destroy_pipeline_layout(self.pipeline_layout); - - gpu - .internal_logical_device() - .destroy_graphics_pipeline(self.pipeline); - } - } -} - -impl RenderPipeline { - pub(super) fn internal_pipeline_layout( - &self, - ) -> &RenderBackend::PipelineLayout { - return &self.pipeline_layout; - } - - pub(super) fn internal_pipeline(&self) -> &RenderBackend::GraphicsPipeline { - return &self.pipeline; - } -} diff --git a/crates/lambda-rs-platform/src/gfx/render_pass.rs b/crates/lambda-rs-platform/src/gfx/render_pass.rs deleted file mode 100644 index d14e64cb..00000000 --- a/crates/lambda-rs-platform/src/gfx/render_pass.rs +++ /dev/null @@ -1,258 +0,0 @@ -use gfx_hal::device::Device; - -use super::{ - gpu::Gpu, - surface::ColorFormat, -}; - -// ----------------------- RENDER ATTACHMENT OPERATIONS ------------------------ - -#[derive(Debug)] -pub enum Operations { - DontCare, - Load, - Clear, - Store, -} - -impl Operations { - fn to_gfx_hal_load_operation(&self) -> gfx_hal::pass::AttachmentLoadOp { - match self { - Operations::DontCare => gfx_hal::pass::AttachmentLoadOp::DontCare, - Operations::Load => gfx_hal::pass::AttachmentLoadOp::Load, - Operations::Clear => gfx_hal::pass::AttachmentLoadOp::Clear, - _ => panic!("Cannot pass in {:?} as an operation for the attachment load operation!", self) - } - } - - fn to_gfx_hal_store_operation(&self) -> gfx_hal::pass::AttachmentStoreOp { - return match self { - Operations::DontCare => gfx_hal::pass::AttachmentStoreOp::DontCare, - Operations::Store => gfx_hal::pass::AttachmentStoreOp::Store, - _ => panic!( - "Cannot pass in {:?} as an operation for the attachment store operation!", - self - ), - }; - } -} - -// ----------------------------- RENDER ATTACHMENT ----------------------------- - -/// -pub struct AttachmentBuilder { - samples: u8, - color_format: Option, - load_operation: gfx_hal::pass::AttachmentLoadOp, - store_operation: gfx_hal::pass::AttachmentStoreOp, -} - -/// builder for a render attachment -impl AttachmentBuilder { - pub fn new() -> Self { - return Self { - samples: 0, - color_format: None, - load_operation: gfx_hal::pass::AttachmentLoadOp::DontCare, - store_operation: gfx_hal::pass::AttachmentStoreOp::DontCare, - }; - } - - /// Sets the number of samples to use for the attachment. - pub fn with_samples(mut self, samples: u8) -> Self { - self.samples = samples; - return self; - } - - /// Sets the color format to use for the attachment. - pub fn with_color_format(mut self, color_format: ColorFormat) -> Self { - self.color_format = Some(color_format); - return self; - } - - /// Sets the load operation for the attachment. - pub fn on_load(mut self, operation: Operations) -> Self { - self.load_operation = operation.to_gfx_hal_load_operation(); - return self; - } - - /// - pub fn on_store(mut self, operation: Operations) -> Self { - self.store_operation = operation.to_gfx_hal_store_operation(); - return self; - } - - /// Builds a render attachment that can be used within a render pass. - pub fn build(self) -> Attachment { - return Attachment { - attachment: gfx_hal::pass::Attachment { - format: self.color_format, - samples: self.samples, - ops: gfx_hal::pass::AttachmentOps::new( - self.load_operation, - self.store_operation, - ), - stencil_ops: gfx_hal::pass::AttachmentOps::DONT_CARE, - layouts: gfx_hal::image::Layout::Undefined - ..gfx_hal::image::Layout::Present, - }, - }; - } -} - -pub struct Attachment { - attachment: gfx_hal::pass::Attachment, -} - -impl Attachment { - fn gfx_hal_attachment(&self) -> gfx_hal::pass::Attachment { - return self.attachment.clone(); - } -} - -// ------------------------------ RENDER SUBPASS ------------------------------- - -pub use gfx_hal::image::Layout as ImageLayoutHint; - -pub struct SubpassBuilder { - color_attachment: Option<(usize, ImageLayoutHint)>, -} - -impl SubpassBuilder { - pub fn new() -> Self { - return Self { - color_attachment: None, - }; - } - - pub fn with_color_attachment( - mut self, - attachment_index: usize, - layout: ImageLayoutHint, - ) -> Self { - self.color_attachment = Some((attachment_index, layout)); - return self; - } - pub fn with_inputs() { - todo!("Implement input support for subpasses") - } - pub fn with_resolves() { - todo!("Implement resolving support for subpasses") - } - pub fn with_preserves() { - todo!("Implement preservation support for subpasses") - } - - pub fn build<'a>(self) -> Subpass<'a> { - return Subpass { - subpass: gfx_hal::pass::SubpassDesc { - colors: &[(0, ImageLayoutHint::ColorAttachmentOptimal)], - depth_stencil: None, - inputs: &[], - resolves: &[], - preserves: &[], - }, - }; - } -} - -pub struct Subpass<'a> { - subpass: gfx_hal::pass::SubpassDesc<'a>, -} - -impl<'a> Subpass<'a> { - fn gfx_hal_subpass(self) -> gfx_hal::pass::SubpassDesc<'a> { - return self.subpass; - } -} - -// -------------------------------- RENDER PASS -------------------------------- - -pub struct RenderPassBuilder<'builder> { - attachments: Vec, - subpasses: Vec>, -} - -impl<'builder> RenderPassBuilder<'builder> { - pub fn new() -> Self { - return Self { - attachments: vec![], - subpasses: vec![], - }; - } - - /// Adds an attachment to the render pass. Can add multiple. - pub fn add_attachment(mut self, attachment: Attachment) -> Self { - self.attachments.push(attachment); - return self; - } - - pub fn add_subpass(mut self, subpass: Subpass<'builder>) -> Self { - self.subpasses.push(subpass); - return self; - } - - pub fn build( - self, - gpu: &Gpu, - ) -> RenderPass { - // If there are no attachments, use a stub image attachment with clear and - // store operations. - let attachments = match self.attachments.is_empty() { - true => vec![AttachmentBuilder::new() - .with_samples(1) - .on_load(Operations::Clear) - .on_store(Operations::Store) - .with_color_format(ColorFormat::Rgba8Srgb) - .build() - .gfx_hal_attachment()], - false => self - .attachments - .into_iter() - .map(|attachment| attachment.gfx_hal_attachment()) - .collect(), - }; - - // If there are no subpass descriptions, use a stub subpass attachment - let subpasses = match self.subpasses.is_empty() { - true => vec![SubpassBuilder::new().build().gfx_hal_subpass()], - false => self - .subpasses - .into_iter() - .map(|subpass| subpass.gfx_hal_subpass()) - .collect(), - }; - - let render_pass = unsafe { - gpu.internal_logical_device().create_render_pass( - attachments.into_iter(), - subpasses.into_iter(), - vec![].into_iter(), - ) - } - .expect("The GPU does not have enough memory to allocate a render pass."); - - return RenderPass { render_pass }; - } -} - -#[derive(Debug)] -pub struct RenderPass { - render_pass: RenderBackend::RenderPass, -} - -impl RenderPass { - pub fn destroy(self, gpu: &Gpu) { - unsafe { - gpu - .internal_logical_device() - .destroy_render_pass(self.render_pass); - } - } -} - -impl RenderPass { - pub(super) fn internal_render_pass(&self) -> &RenderBackend::RenderPass { - return &self.render_pass; - } -} diff --git a/crates/lambda-rs-platform/src/gfx/resource.rs b/crates/lambda-rs-platform/src/gfx/resource.rs deleted file mode 100644 index 3dbf6d02..00000000 --- a/crates/lambda-rs-platform/src/gfx/resource.rs +++ /dev/null @@ -1,5 +0,0 @@ -use super::Instance; - -pub trait Resource { - fn delete(self, instance: &mut ResourceOwner); -} diff --git a/crates/lambda-rs-platform/src/gfx/shader.rs b/crates/lambda-rs-platform/src/gfx/shader.rs deleted file mode 100644 index 6b17fcee..00000000 --- a/crates/lambda-rs-platform/src/gfx/shader.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Low level shader implementations used by the lambda-platform crate to load -//! SPIR-V compiled shaders into the GPU. - -use gfx_hal::{ - device::Device, - pso::Specialization as ShaderSpecializations, -}; -#[cfg(test)] -use mockall::automock; - -use super::gpu; - -/// The type of shader that a shader module represents. Different shader types -/// are used for different operations in the rendering pipeline. -pub enum ShaderModuleType { - Vertex, - Fragment, - Compute, -} - -/// Builder class for creating a shader module. -pub struct ShaderModuleBuilder { - entry_name: String, - specializations: ShaderSpecializations<'static>, -} - -#[cfg_attr(test, automock)] -impl ShaderModuleBuilder { - pub fn new() -> Self { - return Self { - entry_name: "main".to_string(), - specializations: ShaderSpecializations::EMPTY, - }; - } - - /// Define the shader entry point (Defaults to main) - pub fn with_entry_name(mut self, entry_name: &str) -> Self { - self.entry_name = entry_name.to_string(); - return self; - } - - /// Attach specializations to the shader. - pub fn with_specializations( - mut self, - specializations: ShaderSpecializations<'static>, - ) -> Self { - self.specializations = specializations; - return self; - } - - /// Builds the shader binary into a shader module located on the GPU. - /// ShaderModules are specific to gfx-hal and can be used for building - /// RenderPipelines - pub fn build( - self, - gpu: &mut gpu::Gpu, - shader_binary: &Vec, - shader_type: ShaderModuleType, - ) -> ShaderModule { - let shader_module = unsafe { - gpu - .internal_logical_device() - .create_shader_module(&shader_binary) - .expect("Failed to create a shader module.") - }; - - return ShaderModule { - entry_name: self.entry_name, - shader_module, - specializations: self.specializations, - shader_type, - }; - } -} - -/// Shader modules are used for uploading shaders into the render pipeline. -pub struct ShaderModule { - entry_name: String, - shader_module: RenderBackend::ShaderModule, - specializations: ShaderSpecializations<'static>, - shader_type: ShaderModuleType, -} - -#[cfg_attr(test, automock)] -impl ShaderModule { - /// Destroy the shader module and free the memory on the GPU. - pub fn destroy(self, gpu: &mut gpu::Gpu) { - unsafe { - gpu - .internal_logical_device() - .destroy_shader_module(self.shader_module) - } - } - - /// Get the entry point that this shader module is using. - pub fn entry(&self) -> &str { - return self.entry_name.as_str(); - } - - /// Get the specializations being applied to the current shader module. - pub fn specializations(&self) -> &ShaderSpecializations<'static> { - return &self.specializations; - } -} - -#[cfg(test)] -mod tests { - - /// Test that we can create a shader module builder and it has the correct - /// defaults. - #[test] - fn shader_builder_initial_state() { - let shader_builder = super::ShaderModuleBuilder::new(); - assert_eq!(shader_builder.entry_name, "main"); - assert_eq!(shader_builder.specializations.data.len(), 0); - } - - /// Test that we can create a shader module builder with a custom entry point - /// & default specializations. - #[test] - fn shader_builder_with_properties() { - let shader_builder = super::ShaderModuleBuilder::new() - .with_entry_name("test") - .with_specializations(super::ShaderSpecializations::default()); - assert_eq!(shader_builder.entry_name, "test"); - assert_eq!( - shader_builder.specializations.data, - super::ShaderSpecializations::default().data - ); - } - - #[test] - fn shader_builder_builds_correctly() { - let shader_builder = super::ShaderModuleBuilder::new() - .with_entry_name("test") - .with_specializations(super::ShaderSpecializations::default()); - } -} - -/// Internal functions for the shader module. User applications most likely -/// should not use these functions directly nor should they need to. -pub mod internal { - use super::ShaderModule; - - /// Retrieve the underlying gfx-hal shader module given the lambda-platform - /// implemented shader module. Useful for creating gfx-hal entry points and - /// attaching the shader to rendering pipelines. - #[inline] - pub fn module_for( - shader_module: &ShaderModule, - ) -> &RenderBackend::ShaderModule { - return &shader_module.shader_module; - } -} diff --git a/crates/lambda-rs-platform/src/gfx/surface.rs b/crates/lambda-rs-platform/src/gfx/surface.rs deleted file mode 100644 index 0ebd4bc2..00000000 --- a/crates/lambda-rs-platform/src/gfx/surface.rs +++ /dev/null @@ -1,291 +0,0 @@ -/// ColorFormat for the surface. -pub use gfx_hal::format::Format as ColorFormat; -use gfx_hal::{ - window::{ - PresentationSurface, - Surface as _, - }, - Backend, -}; -#[cfg(test)] -use mockall::automock; - -use super::{ - gpu::Gpu, - Instance, -}; - -/// The API to use for building surfaces from a graphical instance. -#[derive(Debug, Clone)] -pub struct SurfaceBuilder { - name: Option, -} - -#[cfg_attr(test, automock)] -impl SurfaceBuilder { - pub fn new() -> Self { - return Self { name: None }; - } - - /// Set the name of the surface. - pub fn with_name(mut self, name: &str) -> Self { - self.name = Some(name.to_string()); - return self; - } - - /// Build a surface using a graphical instance & active window - pub fn build( - self, - instance: &super::Instance, - window: &crate::winit::WindowHandle, - ) -> Surface { - let gfx_hal_surface = instance.create_surface(window); - let name = match self.name { - Some(name) => name, - None => "RenderSurface".to_string(), - }; - - return Surface { - name, - extent: None, - gfx_hal_surface, - swapchain_is_valid: true, - image: None, - frame_buffer_attachment: None, - }; - } -} - -/// Defines a surface that can be rendered on to. -#[derive(Debug)] -pub struct Surface { - name: String, - gfx_hal_surface: RenderBackend::Surface, - extent: Option, - swapchain_is_valid: bool, - // TODO(vmarcella): the Image type is very large - image: Option< - >::SwapchainImage, - >, - frame_buffer_attachment: Option, -} - -#[derive(Debug, Clone)] -pub struct Swapchain { - config: gfx_hal::window::SwapchainConfig, - format: gfx_hal::format::Format, -} - -#[cfg_attr(test, automock)] -impl Surface { - /// Apply a swapchain to the current surface. This is required whenever a - /// swapchain has been invalidated (I.E. by window resizing) - pub fn apply_swapchain<'surface>( - &mut self, - gpu: &Gpu, - swapchain: Swapchain, - timeout_in_nanoseconds: u64, - ) -> Result<(), &'surface str> { - let device = gpu.internal_logical_device(); - self.extent = Some(swapchain.config.extent); - - unsafe { - self - .gfx_hal_surface - .configure_swapchain(device, swapchain.config.clone()) - .expect("Failed to configure the swapchain"); - - self.frame_buffer_attachment = - Some(swapchain.config.framebuffer_attachment()); - - let image = - match self.gfx_hal_surface.acquire_image(timeout_in_nanoseconds) { - Ok((image, _)) => Some(image), - Err(_) => { - self.swapchain_is_valid = false; - None - } - }; - - match image { - Some(image) => { - self.image = Some(image); - return Ok(()); - } - None => { - return Err("Failed to apply the swapchain."); - } - } - } - } - - pub fn needs_swapchain(&self) -> bool { - return self.swapchain_is_valid; - } - - /// Remove the swapchain configuration that this surface used on this given - /// GPU. - pub fn remove_swapchain(&mut self, gpu: &Gpu) { - logging::debug!("Removing the swapchain configuration from: {}", self.name); - unsafe { - self - .gfx_hal_surface - .unconfigure_swapchain(gpu.internal_logical_device()); - } - } - - /// Destroy the current surface and it's underlying resources. - pub fn destroy(self, instance: &Instance) { - logging::debug!("Destroying the surface: {}", self.name); - - instance.destroy_surface(self.gfx_hal_surface); - } - - /// Get the size of the surface's extent. Will only return a size if a - /// swapchain has been applied to the surface to render with. - pub fn size(&self) -> Option<(u32, u32)> { - return match self.extent { - Some(extent) => Some((extent.width, extent.height)), - None => None, - }; - } -} - -// ------------------------------ SWAPCHAIN BUILDER ---------------------------- - -pub struct SwapchainBuilder { - size: (u32, u32), -} - -impl SwapchainBuilder { - pub fn new() -> Self { - return Self { size: (480, 360) }; - } - - /// Set the size of the swapchain for the surface image. - pub fn with_size(mut self, width: u32, height: u32) -> Self { - self.size = (width, height); - return self; - } - - pub fn build( - self, - gpu: &Gpu, - surface: &Surface, - ) -> Swapchain { - let physical_device = gpu.internal_physical_device(); - let caps = surface.gfx_hal_surface.capabilities(physical_device); - let format = surface.get_first_supported_format(physical_device); - let (width, height) = self.size; - - let mut swapchain_config = gfx_hal::window::SwapchainConfig::from_caps( - &caps, - format, - gfx_hal::window::Extent2D { width, height }, - ); - - // TODO(vmarcella) Profile the performance on MacOS to see if this slows - // down frame times. - if caps.image_count.contains(&3) { - swapchain_config.image_count = 3; - } - - return Swapchain { - config: swapchain_config, - format, - }; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::gfx::MockInstanceBuilder; - - #[test] - fn test_surface_builder() { - let surface_builder = SurfaceBuilder::new(); - assert_eq!(surface_builder.name, None); - - let surface_builder = SurfaceBuilder::new().with_name("TestSurface"); - assert_eq!(surface_builder.name, Some("TestSurface".to_string())); - } - - #[test] - fn test_swapchain_builder() { - let swapchain_builder = SwapchainBuilder::new(); - assert_eq!(swapchain_builder.size, (480, 360)); - - let swapchain_builder = SwapchainBuilder::new().with_size(1920, 1080); - assert_eq!(swapchain_builder.size, (1920, 1080)); - } - - #[test] - fn test_surface_builder_e2e() { - //let instance = MockInstanceBuilder::new().build("TestInstance"); - let surface_builder = SurfaceBuilder::new().with_name("TestSurface"); - //let surface = surface_builder.build(&instance); - - //assert_eq!(surface.name, "TestSurface".to_string()); - //assert_eq!(surface.swapchain_is_valid, false); - //assert_eq!(surface.image, None); - //assert_eq!(surface.frame_buffer_attachment, None); - } -} - -impl Surface { - /// Checks the queue family if the current Surface can support the GPU. - pub(super) fn can_support_queue_family( - &self, - queue_family: &RenderBackend::QueueFamily, - ) -> bool { - return self.gfx_hal_surface.supports_queue_family(queue_family); - } - - pub(super) fn get_supported_formats( - &self, - physical_device: &RenderBackend::PhysicalDevice, - ) -> Vec { - return self - .gfx_hal_surface - .supported_formats(physical_device) - .unwrap_or(vec![]); - } - - pub(super) fn get_first_supported_format( - &self, - physical_device: &RenderBackend::PhysicalDevice, - ) -> gfx_hal::format::Format { - return self - .get_supported_formats(physical_device) - .get(0) - .unwrap_or(&gfx_hal::format::Format::Rgba8Srgb) - .clone(); - } - - pub(super) fn internal_surface_image( - &self, - ) -> Option<&>::SwapchainImage>{ - return self.image.as_ref(); - } - - pub(super) fn internal_frame_buffer_attachment( - &self, - ) -> Option { - return self.frame_buffer_attachment.clone(); - } - - pub(super) fn internal_surface_and_image( - &mut self, - ) -> ( - &mut RenderBackend::Surface, - >::SwapchainImage, - ){ - return ( - &mut self.gfx_hal_surface, - self.image.take().expect("Surface image is not present"), - ); - } -} diff --git a/crates/lambda-rs-platform/src/gfx/viewport.rs b/crates/lambda-rs-platform/src/gfx/viewport.rs deleted file mode 100644 index 37f88a23..00000000 --- a/crates/lambda-rs-platform/src/gfx/viewport.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! viewport.rs - Low level viewport management for the render context. - -/// Viewport is a rectangle that defines the area of the screen that will be -/// rendered to. For instance, if the viewport is set to x=0, y=0, width=100, -/// height=100, then only the top left 100x100 pixels of the screen will be -/// rendered to. -#[derive(Debug, Clone, PartialEq)] -pub struct ViewPort { - viewport: gfx_hal::pso::Viewport, -} - -impl ViewPort { - /// Get the internal viewport object. - pub(super) fn internal_viewport(&self) -> gfx_hal::pso::Viewport { - return self.viewport.clone(); - } -} - -/// A builder for `Viewport`. -pub struct ViewPortBuilder { - x: i16, - y: i16, -} - -impl ViewPortBuilder { - /// Create a new `ViewportBuilder`. - pub fn new() -> Self { - return ViewPortBuilder { x: 0, y: 0 }; - } - - /// Specify a viewport with specific coordinates - pub fn with_coordinates(mut self, x: i16, y: i16) -> Self { - self.x = x; - self.y = y; - return self; - } - - /// Build a viewport to use for viewing an entire surface. - pub fn build(self, width: u32, height: u32) -> ViewPort { - // The viewport currently renders to the entire size of the surface. and has - // a non-configurable depth - return ViewPort { - viewport: gfx_hal::pso::Viewport { - rect: gfx_hal::pso::Rect { - x: self.x, - y: self.y, - w: width as i16, - h: height as i16, - }, - depth: 0.0..1.0, - }, - }; - } -} - -#[cfg(test)] -pub mod tests { - /// Test the viewport builder in it's default state. - #[test] - fn viewport_builder_default_initial_state() { - let viewport_builder = super::ViewPortBuilder::new(); - assert_eq!(viewport_builder.x, 0); - assert_eq!(viewport_builder.y, 0); - } - - /// Test that the viewport builder can be configured with specific - /// coordinates. - #[test] - fn viewport_builder_with_coordinates() { - let viewport_builder = - super::ViewPortBuilder::new().with_coordinates(10, 10); - assert_eq!(viewport_builder.x, 10); - assert_eq!(viewport_builder.y, 10); - } - - #[test] - fn viewport_builder_builds_successfully() { - let viewport_builder = - super::ViewPortBuilder::new().with_coordinates(10, 10); - let viewport = viewport_builder.build(100, 100); - assert_eq!(viewport.viewport.rect.x, 10); - assert_eq!(viewport.viewport.rect.y, 10); - assert_eq!(viewport.viewport.rect.w, 100); - assert_eq!(viewport.viewport.rect.h, 100); - } -} diff --git a/crates/lambda-rs-platform/src/lib.rs b/crates/lambda-rs-platform/src/lib.rs index ab1667f1..3e7248a5 100644 --- a/crates/lambda-rs-platform/src/lib.rs +++ b/crates/lambda-rs-platform/src/lib.rs @@ -1,5 +1,7 @@ -pub mod gfx; pub mod obj; pub mod rand; +pub mod shader; pub mod shaderc; +#[cfg(feature = "wgpu")] +pub mod wgpu; pub mod winit; diff --git a/crates/lambda-rs-platform/src/obj/mod.rs b/crates/lambda-rs-platform/src/obj/mod.rs index 07aec04c..c504c320 100644 --- a/crates/lambda-rs-platform/src/obj/mod.rs +++ b/crates/lambda-rs-platform/src/obj/mod.rs @@ -1,13 +1,6 @@ -use std::{ - fs::File, - io::BufReader, -}; +use std::{fs::File, io::BufReader}; -use obj::{ - load_obj, - Obj, - TexturedVertex, -}; +use obj::{load_obj, Obj, TexturedVertex}; /// Loads a untextured obj file from the given path. Wrapper around the obj crate. pub fn load_obj_from_file(path: &str) -> Obj { diff --git a/crates/lambda-rs-platform/src/rand/mod.rs b/crates/lambda-rs-platform/src/rand/mod.rs index b0a5a14e..5e727226 100644 --- a/crates/lambda-rs-platform/src/rand/mod.rs +++ b/crates/lambda-rs-platform/src/rand/mod.rs @@ -1,7 +1,4 @@ -use rand::{ - distributions::Uniform, - Rng, -}; +use rand::Rng; /// Generate a random float within any given range. #[inline(always)] diff --git a/crates/lambda-rs-platform/src/shader/mod.rs b/crates/lambda-rs-platform/src/shader/mod.rs new file mode 100644 index 00000000..3fbd9a70 --- /dev/null +++ b/crates/lambda-rs-platform/src/shader/mod.rs @@ -0,0 +1,25 @@ +//! Abstractions for compiling shaders into SPIR-V for Lambda runtimes. + +mod types; +pub use types::{ShaderKind, VirtualShader}; + +#[cfg(feature = "shader-backend-naga")] +mod naga; + +#[cfg(feature = "shader-backend-shaderc")] +mod shaderc_backend; + +#[cfg(feature = "shader-backend-naga")] +pub use naga::{ShaderCompiler, ShaderCompilerBuilder}; + +#[cfg(all( + not(feature = "shader-backend-naga"), + feature = "shader-backend-shaderc" +))] +pub use shaderc_backend::{ShaderCompiler, ShaderCompilerBuilder}; + +#[cfg(all( + feature = "shader-backend-naga", + feature = "shader-backend-shaderc" +))] +pub use naga::{ShaderCompiler, ShaderCompilerBuilder}; diff --git a/crates/lambda-rs-platform/src/shader/naga.rs b/crates/lambda-rs-platform/src/shader/naga.rs new file mode 100644 index 00000000..5d78d1d9 --- /dev/null +++ b/crates/lambda-rs-platform/src/shader/naga.rs @@ -0,0 +1,106 @@ +use std::io::Read; + +use naga::{ + back::spv, + front::glsl, + valid::{Capabilities, ValidationFlags, Validator}, + ShaderStage, +}; + +use super::{ShaderKind, VirtualShader}; + +/// Builder for the naga-backed shader compiler. +pub struct ShaderCompilerBuilder {} + +impl ShaderCompilerBuilder { + pub fn new() -> Self { + Self {} + } + + pub fn build(self) -> ShaderCompiler { + ShaderCompiler {} + } +} + +/// A shader compiler that uses naga to translate shader sources into SPIR-V. +pub struct ShaderCompiler {} + +impl ShaderCompiler { + pub fn compile_into_binary(&mut self, shader: &VirtualShader) -> Vec { + match shader { + VirtualShader::File { + path, + kind, + name, + entry_point, + } => { + let mut file = + std::fs::File::open(path).expect("Failed to open shader file."); + let mut shader_source = String::new(); + file + .read_to_string(&mut shader_source) + .expect("Failed to read shader file."); + + self.compile_string_into_binary( + shader_source.as_str(), + name.as_str(), + entry_point.as_str(), + *kind, + ) + } + VirtualShader::Source { + source, + kind, + name, + entry_point, + } => self.compile_string_into_binary( + source.as_str(), + name.as_str(), + entry_point.as_str(), + *kind, + ), + } + } + + fn compile_string_into_binary( + &mut self, + shader_source: &str, + name: &str, + entry_point: &str, + shader_kind: ShaderKind, + ) -> Vec { + let stage = shader_kind_to_stage(shader_kind); + let mut frontend = glsl::Frontend::default(); + let module = frontend + .parse(&glsl::Options::from(stage), shader_source) + .unwrap_or_else(|err| { + panic!("Failed to compile shader {}: {:?}", name, err) + }); + + let mut validator = + Validator::new(ValidationFlags::all(), Capabilities::all()); + let module_info = validator + .validate(&module) + .expect("Failed to validate shader module."); + + let mut options = spv::Options::default(); + options.lang_version = (1, 5); + options.flags = spv::WriterFlags::empty(); + + let pipeline_options = spv::PipelineOptions { + shader_stage: stage, + entry_point: entry_point.to_string(), + }; + + spv::write_vec(&module, &module_info, &options, Some(&pipeline_options)) + .expect("Failed to translate shader module into SPIR-V.") + } +} + +fn shader_kind_to_stage(kind: ShaderKind) -> ShaderStage { + match kind { + ShaderKind::Vertex => ShaderStage::Vertex, + ShaderKind::Fragment => ShaderStage::Fragment, + ShaderKind::Compute => ShaderStage::Compute, + } +} diff --git a/crates/lambda-rs-platform/src/shaderc/mod.rs b/crates/lambda-rs-platform/src/shader/shaderc_backend.rs similarity index 67% rename from crates/lambda-rs-platform/src/shaderc/mod.rs rename to crates/lambda-rs-platform/src/shader/shaderc_backend.rs index 2cae7e46..4ee94fdf 100644 --- a/crates/lambda-rs-platform/src/shaderc/mod.rs +++ b/crates/lambda-rs-platform/src/shader/shaderc_backend.rs @@ -1,15 +1,15 @@ use std::io::Read; use shaderc; -/// Export supported shader kinds. -pub use shaderc::ShaderKind; + +use super::{ShaderKind, VirtualShader}; /// Builder for the shaderc platform shader compiler. pub struct ShaderCompilerBuilder {} impl ShaderCompilerBuilder { pub fn new() -> Self { - return Self {}; + Self {} } pub fn build(self) -> ShaderCompiler { @@ -19,10 +19,10 @@ impl ShaderCompilerBuilder { let options = shaderc::CompileOptions::new() .expect("Failed to create shaderc compile options."); - return ShaderCompiler { + ShaderCompiler { compiler, default_options: options, - }; + } } } @@ -32,40 +32,20 @@ pub struct ShaderCompiler { default_options: shaderc::CompileOptions<'static>, } -/// Meta Representations of real shaders to use for easy compilation -#[derive(Debug, Clone)] -pub enum VirtualShader { - File { - path: String, - kind: ShaderKind, - name: String, - entry_point: String, - }, - Source { - source: String, - kind: ShaderKind, - name: String, - entry_point: String, - }, -} - impl ShaderCompiler { - /// Compiles a shader into SPIR-V binary. pub fn compile_into_binary(&mut self, shader: &VirtualShader) -> Vec { - return match shader { + match shader { VirtualShader::File { path, kind, name, entry_point, - } => { - return self.compile_file_into_binary( - path.as_str(), - name.as_str(), - entry_point.as_str(), - kind.clone(), - ) - } + } => self.compile_file_into_binary( + path.as_str(), + name.as_str(), + entry_point.as_str(), + *kind, + ), VirtualShader::Source { source, kind, @@ -75,12 +55,11 @@ impl ShaderCompiler { source.as_str(), name.as_str(), entry_point.as_str(), - kind.clone(), + *kind, ), - }; + } } - /// Compiles a file at the given path into a shader and returns it as binary. fn compile_file_into_binary( &mut self, path: &str, @@ -98,16 +77,15 @@ impl ShaderCompiler { .compiler .compile_into_spirv( &shader_source, - shader_kind, + map_shader_kind(shader_kind), path, entry_point, Some(&self.default_options), ) .expect("Failed to compile the shader."); - return compiled_shader.as_binary().to_vec(); + compiled_shader.as_binary().to_vec() } - // Compiles a string into SPIR-V binary. fn compile_string_into_binary( &mut self, shader_source: &str, @@ -119,13 +97,21 @@ impl ShaderCompiler { .compiler .compile_into_spirv( shader_source, - shader_kind, + map_shader_kind(shader_kind), name, entry_point, Some(&self.default_options), ) .expect("Failed to compile the shader."); - return compiled_shader.as_binary().to_vec(); + compiled_shader.as_binary().to_vec() + } +} + +fn map_shader_kind(kind: ShaderKind) -> shaderc::ShaderKind { + match kind { + ShaderKind::Vertex => shaderc::ShaderKind::Vertex, + ShaderKind::Fragment => shaderc::ShaderKind::Fragment, + ShaderKind::Compute => shaderc::ShaderKind::Compute, } } diff --git a/crates/lambda-rs-platform/src/shader/types.rs b/crates/lambda-rs-platform/src/shader/types.rs new file mode 100644 index 00000000..795291c8 --- /dev/null +++ b/crates/lambda-rs-platform/src/shader/types.rs @@ -0,0 +1,50 @@ +//! Shared shader metadata structures used across shader backends. + +/// Supported shader stages. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ShaderKind { + Vertex, + Fragment, + Compute, +} + +/// Meta representations of real shaders to use for easy compilation. +#[derive(Debug, Clone)] +pub enum VirtualShader { + File { + path: String, + kind: ShaderKind, + name: String, + entry_point: String, + }, + Source { + source: String, + kind: ShaderKind, + name: String, + entry_point: String, + }, +} + +impl VirtualShader { + pub fn kind(&self) -> ShaderKind { + match self { + VirtualShader::File { kind, .. } => *kind, + VirtualShader::Source { kind, .. } => *kind, + } + } + + pub fn entry_point(&self) -> &str { + match self { + VirtualShader::File { entry_point, .. } + | VirtualShader::Source { entry_point, .. } => entry_point.as_str(), + } + } + + pub fn name(&self) -> &str { + match self { + VirtualShader::File { name, .. } | VirtualShader::Source { name, .. } => { + name.as_str() + } + } + } +} diff --git a/crates/lambda-rs-platform/src/shaderc.rs b/crates/lambda-rs-platform/src/shaderc.rs new file mode 100644 index 00000000..836dbcb4 --- /dev/null +++ b/crates/lambda-rs-platform/src/shaderc.rs @@ -0,0 +1,9 @@ +//! Deprecated re-exports for code that still references the legacy shaderc module. + +#[deprecated( + since = "2023.1.31", + note = "Use `lambda_platform::shader` instead of `lambda_platform::shaderc`." +)] +pub use crate::shader::{ + ShaderCompiler, ShaderCompilerBuilder, ShaderKind, VirtualShader, +}; diff --git a/crates/lambda-rs-platform/src/wgpu/mod.rs b/crates/lambda-rs-platform/src/wgpu/mod.rs new file mode 100644 index 00000000..8af6b85c --- /dev/null +++ b/crates/lambda-rs-platform/src/wgpu/mod.rs @@ -0,0 +1,516 @@ +//! Cross‑platform GPU abstraction built on top of `wgpu`. +//! +//! This module exposes a small, opinionated wrapper around core `wgpu` types +//! to make engine code concise while keeping configuration explicit. The +//! builders here (for the instance, surface, and device/queue) provide sane +//! defaults and narrow the surface area used by Lambda, without hiding +//! important handles when you need to drop down to raw `wgpu`. + +use pollster::block_on; +use wgpu::rwh::{HasDisplayHandle as _, HasWindowHandle as _}; + +use crate::winit::WindowHandle; + +pub use wgpu as types; + +#[derive(Debug, Clone)] +/// Builder for creating a `wgpu::Instance` with consistent defaults. +/// +/// - Defaults to primary backends and no special flags. +/// - All options map 1:1 to the underlying `wgpu::InstanceDescriptor`. +pub struct InstanceBuilder { + label: Option, + backends: wgpu::Backends, + flags: wgpu::InstanceFlags, + dx12_shader_compiler: wgpu::Dx12Compiler, + gles_minor_version: wgpu::Gles3MinorVersion, +} + +impl InstanceBuilder { + /// Construct a new builder with Lambda defaults. + pub fn new() -> Self { + Self { + label: None, + backends: wgpu::Backends::PRIMARY, + flags: wgpu::InstanceFlags::default(), + dx12_shader_compiler: wgpu::Dx12Compiler::default(), + gles_minor_version: wgpu::Gles3MinorVersion::default(), + } + } + + /// Attach a debug label to the instance. + pub fn with_label(mut self, label: &str) -> Self { + self.label = Some(label.to_string()); + self + } + + /// Select which graphics backends to enable. + pub fn with_backends(mut self, backends: wgpu::Backends) -> Self { + self.backends = backends; + self + } + + /// Set additional instance flags (e.g., debugging). + pub fn with_flags(mut self, flags: wgpu::InstanceFlags) -> Self { + self.flags = flags; + self + } + + /// Choose a DX12 shader compiler variant when on Windows. + pub fn with_dx12_shader_compiler( + mut self, + compiler: wgpu::Dx12Compiler, + ) -> Self { + self.dx12_shader_compiler = compiler; + self + } + + /// Configure the GLES minor version for WebGL/OpenGL ES targets. + pub fn with_gles_minor_version( + mut self, + version: wgpu::Gles3MinorVersion, + ) -> Self { + self.gles_minor_version = version; + self + } + + /// Build the `Instance` wrapper from the accumulated options. + pub fn build(self) -> Instance { + let descriptor = wgpu::InstanceDescriptor { + backends: self.backends, + flags: self.flags, + dx12_shader_compiler: self.dx12_shader_compiler, + gles_minor_version: self.gles_minor_version, + }; + + Instance { + label: self.label, + instance: wgpu::Instance::new(descriptor), + } + } +} + +#[derive(Debug)] +/// Thin wrapper over `wgpu::Instance` that preserves a user label and exposes +/// a blocking `request_adapter` convenience. +pub struct Instance { + label: Option, + instance: wgpu::Instance, +} + +impl Instance { + /// Borrow the underlying `wgpu::Instance`. + pub fn raw(&self) -> &wgpu::Instance { + &self.instance + } + + /// Return the optional label attached at construction time. + pub fn label(&self) -> Option<&str> { + self.label.as_deref() + } + + /// Request a compatible GPU adapter synchronously. + /// + /// This simply blocks on `wgpu::Instance::request_adapter` and returns + /// `None` if no suitable adapter is found. + pub fn request_adapter<'surface, 'window>( + &self, + options: &wgpu::RequestAdapterOptions<'surface, 'window>, + ) -> Option { + block_on(self.instance.request_adapter(options)) + } +} + +#[derive(Debug, Clone)] +/// Builder for creating a `Surface` bound to a `winit` window. +pub struct SurfaceBuilder { + label: Option, +} + +impl SurfaceBuilder { + /// Create a builder with no label. + pub fn new() -> Self { + Self { label: None } + } + + /// Attach a human‑readable label for debugging/profiling. + pub fn with_label(mut self, label: &str) -> Self { + self.label = Some(label.to_string()); + self + } + + /// Create a `wgpu::Surface` for the provided `WindowHandle`. + /// + /// Safety: we use `create_surface_unsafe` by forwarding raw window/display + /// handles from `winit`. Lambda guarantees the window outlives the surface + /// for the duration of the runtime. + pub fn build<'window>( + self, + instance: &Instance, + window: &'window WindowHandle, + ) -> Result, wgpu::CreateSurfaceError> { + // SAFETY: We ensure the raw window/display handles outlive the surface by + // keeping the window alive for the duration of the application runtime. + // Obtain raw handles via raw-window-handle 0.6 traits. + let raw_display_handle = window + .window_handle + .display_handle() + .expect("Failed to get display handle from window") + .as_raw(); + let raw_window_handle = window + .window_handle + .window_handle() + .expect("Failed to get window handle from window") + .as_raw(); + + let surface = unsafe { + instance.raw().create_surface_unsafe( + wgpu::SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + }, + )? + }; + + Ok(Surface { + label: self.label.unwrap_or_else(|| "Lambda Surface".to_string()), + surface, + configuration: None, + format: None, + }) + } +} + +#[derive(Debug)] +/// Presentation surface wrapper with cached configuration and format. +pub struct Surface<'window> { + label: String, + surface: wgpu::Surface<'window>, + configuration: Option, + format: Option, +} + +impl<'window> Surface<'window> { + /// Immutable label used for debugging. + pub fn label(&self) -> &str { + &self.label + } + + /// Borrow the raw `wgpu::Surface`. + pub fn surface(&self) -> &wgpu::Surface<'window> { + &self.surface + } + + /// Current configuration, if the surface has been configured. + pub fn configuration(&self) -> Option<&wgpu::SurfaceConfiguration> { + self.configuration.as_ref() + } + + /// Preferred surface format if known (set during configuration). + pub fn format(&self) -> Option { + self.format + } + + /// Configure the surface with the provided `wgpu::SurfaceConfiguration` and + /// cache the result for queries such as `format()`. + pub fn configure( + &mut self, + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + ) { + self.surface.configure(device, config); + self.configuration = Some(config.clone()); + self.format = Some(config.format); + } + + /// Configure the surface using common engine defaults: + /// - sRGB color format if available + /// - fallback present mode compatible with the platform + /// - `RENDER_ATTACHMENT` usage if requested usage is unsupported + pub fn configure_with_defaults( + &mut self, + adapter: &wgpu::Adapter, + device: &wgpu::Device, + size: (u32, u32), + present_mode: wgpu::PresentMode, + usage: wgpu::TextureUsages, + ) -> Result { + let width = size.0.max(1); + let height = size.1.max(1); + + let mut config = self + .surface + .get_default_config(adapter, width, height) + .ok_or_else(|| "Surface not supported by adapter".to_string())?; + + let capabilities = self.surface.get_capabilities(adapter); + + config.format = capabilities + .formats + .iter() + .copied() + .find(|format| format.is_srgb()) + .unwrap_or_else(|| *capabilities.formats.first().unwrap()); + + config.present_mode = if capabilities.present_modes.contains(&present_mode) + { + present_mode + } else { + capabilities + .present_modes + .iter() + .copied() + .find(|mode| { + matches!(mode, wgpu::PresentMode::Fifo | wgpu::PresentMode::AutoVsync) + }) + .unwrap_or(wgpu::PresentMode::Fifo) + }; + + if capabilities.usages.contains(usage) { + config.usage = usage; + } else { + config.usage = wgpu::TextureUsages::RENDER_ATTACHMENT; + } + + if config.view_formats.is_empty() && !config.format.is_srgb() { + config.view_formats.push(config.format.add_srgb_suffix()); + } + + self.configure(device, &config); + Ok(config) + } + + /// Resize the surface while preserving present mode and usage when possible. + pub fn resize( + &mut self, + adapter: &wgpu::Adapter, + device: &wgpu::Device, + size: (u32, u32), + ) -> Result<(), String> { + let present_mode = self + .configuration + .as_ref() + .map(|config| config.present_mode) + .unwrap_or(wgpu::PresentMode::Fifo); + let usage = self + .configuration + .as_ref() + .map(|config| config.usage) + .unwrap_or(wgpu::TextureUsages::RENDER_ATTACHMENT); + + self + .configure_with_defaults(adapter, device, size, present_mode, usage) + .map(|_| ()) + } + + /// Acquire the next swapchain texture and a default view. + pub fn acquire_next_frame(&self) -> Result { + let texture = self.surface.get_current_texture()?; + let view = texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + Ok(Frame { texture, view }) + } +} + +#[derive(Debug)] +/// A single acquired frame and its default `TextureView`. +pub struct Frame { + texture: wgpu::SurfaceTexture, + view: wgpu::TextureView, +} + +impl Frame { + /// Borrow the default view for rendering. + pub fn texture_view(&self) -> &wgpu::TextureView { + &self.view + } + + /// Consume and return the underlying parts. + pub fn into_parts(self) -> (wgpu::SurfaceTexture, wgpu::TextureView) { + (self.texture, self.view) + } + + /// Present the frame to the swapchain. + pub fn present(self) { + self.texture.present(); + } +} + +#[derive(Debug, Clone)] +/// Builder for a `Gpu` (adapter, device, queue) with feature validation. +pub struct GpuBuilder { + label: Option, + power_preference: wgpu::PowerPreference, + force_fallback_adapter: bool, + required_features: wgpu::Features, + memory_hints: wgpu::MemoryHints, +} + +impl GpuBuilder { + /// Create a builder with defaults favoring performance and push constants. + pub fn new() -> Self { + Self { + label: Some("Lambda GPU".to_string()), + power_preference: wgpu::PowerPreference::HighPerformance, + force_fallback_adapter: false, + required_features: wgpu::Features::PUSH_CONSTANTS, + memory_hints: wgpu::MemoryHints::Performance, + } + } + + /// Attach a label used for the device. + pub fn with_label(mut self, label: &str) -> Self { + self.label = Some(label.to_string()); + self + } + + /// Select the adapter power preference (e.g., LowPower for laptops). + pub fn with_power_preference( + mut self, + preference: wgpu::PowerPreference, + ) -> Self { + self.power_preference = preference; + self + } + + /// Force using a fallback adapter when a primary device is unavailable. + pub fn force_fallback(mut self, force: bool) -> Self { + self.force_fallback_adapter = force; + self + } + + /// Require `wgpu::Features` to be present on the adapter. + pub fn with_required_features(mut self, features: wgpu::Features) -> Self { + self.required_features = features; + self + } + + /// Provide memory allocation hints for the device. + pub fn with_memory_hints(mut self, hints: wgpu::MemoryHints) -> Self { + self.memory_hints = hints; + self + } + + /// Request an adapter and device/queue pair and return a `Gpu` wrapper. + /// + /// Returns an error if no adapter is available, required features are + /// missing, or device creation fails. + pub fn build<'surface, 'window>( + self, + instance: &Instance, + surface: Option<&Surface<'surface>>, + ) -> Result { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: self.power_preference, + force_fallback_adapter: self.force_fallback_adapter, + compatible_surface: surface.map(|surface| surface.surface()), + }) + .ok_or(GpuBuildError::AdapterUnavailable)?; + + let adapter_features = adapter.features(); + if !adapter_features.contains(self.required_features) { + return Err(GpuBuildError::MissingFeatures { + requested: self.required_features, + available: adapter_features, + }); + } + + let descriptor = wgpu::DeviceDescriptor { + label: self.label.as_deref(), + required_features: self.required_features, + required_limits: adapter.limits(), + memory_hints: self.memory_hints, + }; + + let (device, queue) = block_on(adapter.request_device(&descriptor, None))?; + + Ok(Gpu { + adapter, + device, + queue, + features: descriptor.required_features, + limits: descriptor.required_limits, + }) + } +} + +#[derive(Debug)] +/// Errors emitted while building a `Gpu`. +pub enum GpuBuildError { + /// No compatible adapter could be found. + AdapterUnavailable, + /// The requested features are not supported by the selected adapter. + MissingFeatures { + requested: wgpu::Features, + available: wgpu::Features, + }, + /// Wrapper for `wgpu::RequestDeviceError`. + RequestDevice(wgpu::RequestDeviceError), +} + +impl From for GpuBuildError { + fn from(error: wgpu::RequestDeviceError) -> Self { + GpuBuildError::RequestDevice(error) + } +} + +#[derive(Debug)] +/// Holds the chosen adapter along with its logical device and submission queue +/// plus immutable copies of features and limits used to create the device. +pub struct Gpu { + adapter: wgpu::Adapter, + device: wgpu::Device, + queue: wgpu::Queue, + features: wgpu::Features, + limits: wgpu::Limits, +} + +impl Gpu { + /// Borrow the adapter used to create the device. + pub fn adapter(&self) -> &wgpu::Adapter { + &self.adapter + } + + /// Borrow the logical device for resource creation. + pub fn device(&self) -> &wgpu::Device { + &self.device + } + + /// Borrow the submission queue for command submission. + pub fn queue(&self) -> &wgpu::Queue { + &self.queue + } + + /// Features that were required and enabled during device creation. + pub fn features(&self) -> wgpu::Features { + self.features + } + + /// Limits captured at device creation time. + pub fn limits(&self) -> &wgpu::Limits { + &self.limits + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn instance_builder_sets_label() { + let instance = InstanceBuilder::new().with_label("Test").build(); + assert_eq!(instance.label(), Some("Test")); + } + + #[test] + fn gpu_build_error_wraps_request_device_error() { + let error = GpuBuildError::from(wgpu::RequestDeviceError::NotFound); + assert!(matches!( + error, + GpuBuildError::RequestDevice(wgpu::RequestDeviceError::NotFound) + )); + } +} diff --git a/crates/lambda-rs-platform/src/winit/mod.rs b/crates/lambda-rs-platform/src/winit/mod.rs index 457b0a64..dff5bbdb 100644 --- a/crates/lambda-rs-platform/src/winit/mod.rs +++ b/crates/lambda-rs-platform/src/winit/mod.rs @@ -1,42 +1,25 @@ //! Winit wrapper to easily construct cross platform windows use winit::{ - dpi::{ - LogicalSize, - PhysicalSize, - }, + dpi::{LogicalSize, PhysicalSize}, event::Event, event_loop::{ - ControlFlow, - EventLoop, - EventLoopBuilder, - EventLoopProxy, + ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget, }, monitor::MonitorHandle, - window::{ - Window, - WindowBuilder, - }, + window::{Window, WindowBuilder}, }; /// Embedded module for exporting data/types from winit as minimally/controlled /// as possible. The exports from this module are not guaranteed to be stable. pub mod winit_exports { pub use winit::{ - event::{ - ElementState, - Event, - MouseButton, - VirtualKeyCode, - WindowEvent, - }, + event::{ElementState, Event, KeyEvent, MouseButton, WindowEvent}, event_loop::{ - ControlFlow, - EventLoop, - EventLoopProxy, - EventLoopWindowTarget, + ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget, }, + keyboard::{KeyCode, PhysicalKey}, }; } @@ -49,7 +32,9 @@ impl LoopBuilder { } pub fn build(self) -> Loop { - let event_loop = EventLoopBuilder::::with_user_event().build(); + let event_loop = EventLoopBuilder::::with_user_event() + .build() + .expect("Failed to build event loop"); return Loop { event_loop }; } } @@ -212,11 +197,16 @@ impl Loop { } /// Uses the winit event loop to run forever - pub fn run_forever(self, callback: Callback) + pub fn run_forever(self, mut callback: Callback) where - Callback: 'static - + FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow) -> (), + Callback: 'static + FnMut(Event, &EventLoopWindowTarget), { - self.event_loop.run(callback); + self + .event_loop + .run(move |event, target| { + target.set_control_flow(ControlFlow::Poll); + callback(event, target); + }) + .expect("Event loop terminated unexpectedly"); } } diff --git a/crates/lambda-rs/Cargo.toml b/crates/lambda-rs/Cargo.toml index b445bc89..117b996b 100644 --- a/crates/lambda-rs/Cargo.toml +++ b/crates/lambda-rs/Cargo.toml @@ -20,13 +20,22 @@ cargo-audit = "0.16.0" mockall = "0.11.3" [features] -default=["lambda-rs-platform/detect-platform"] -detect-platform=["lambda-rs-platform/detect-platform"] -with-vulkan=["lambda-rs-platform/gfx-with-vulkan"] -with-opengl=["lambda-rs-platform/gfx-with-opengl"] -with-dx11=["lambda-rs-platform/gfx-with-dx11"] -with-dx12=["lambda-rs-platform/gfx-with-dx12"] -with-metal=["lambda-rs-platform/gfx-with-metal"] +default=["with-wgpu"] +with-vulkan=["with-wgpu", "lambda-rs-platform/wgpu-with-vulkan"] +with-opengl=["with-wgpu", "lambda-rs-platform/wgpu-with-gl"] +with-dx11=["with-wgpu"] +with-dx12=["with-wgpu", "lambda-rs-platform/wgpu-with-dx12"] +with-metal=["with-wgpu", "lambda-rs-platform/wgpu-with-metal"] +with-wgpu=["lambda-rs-platform/wgpu"] +with-wgpu-vulkan=["with-wgpu", "lambda-rs-platform/wgpu-with-vulkan"] +with-wgpu-metal=["with-wgpu", "lambda-rs-platform/wgpu-with-metal"] +with-wgpu-dx12=["with-wgpu", "lambda-rs-platform/wgpu-with-dx12"] +with-wgpu-gl=["with-wgpu", "lambda-rs-platform/wgpu-with-gl"] +with-shaderc=["lambda-rs-platform/shader-backend-shaderc"] +with-shaderc-build-from-source=[ + "with-shaderc", + "lambda-rs-platform/shader-backend-shaderc-build-from-source", +] # ---------------------------- PLATFORM DEPENDENCIES --------------------------- diff --git a/crates/lambda-rs/examples/minimal.rs b/crates/lambda-rs/examples/minimal.rs index 9158c3e3..8e7524e4 100644 --- a/crates/lambda-rs/examples/minimal.rs +++ b/crates/lambda-rs/examples/minimal.rs @@ -4,10 +4,7 @@ //! applications correctly. #[macro_use] -use lambda::{ - runtime::start_runtime, - runtimes::ApplicationRuntimeBuilder, -}; +use lambda::{runtime::start_runtime, runtimes::ApplicationRuntimeBuilder}; fn main() { let runtime = ApplicationRuntimeBuilder::new("Minimal Demo application") diff --git a/crates/lambda-rs/examples/push_constants.rs b/crates/lambda-rs/examples/push_constants.rs index d146a4cd..bf2f0cce 100644 --- a/crates/lambda-rs/examples/push_constants.rs +++ b/crates/lambda-rs/examples/push_constants.rs @@ -1,48 +1,22 @@ +use lambda::render::{pipeline::PipelineStage, ColorFormat}; use lambda::{ component::Component, events::WindowEvent, logging, - math::{ - matrix, - matrix::Matrix, - vector::Vector, - }, + math::{matrix, matrix::Matrix, vector::Vector}, render::{ buffer::BufferBuilder, command::RenderCommand, - mesh::{ - Mesh, - MeshBuilder, - }, + mesh::{Mesh, MeshBuilder}, pipeline::RenderPipelineBuilder, render_pass::RenderPassBuilder, - shader::{ - Shader, - ShaderBuilder, - }, - vertex::{ - VertexAttribute, - VertexBuilder, - VertexElement, - }, - viewport, - ResourceId, + shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, + vertex::{VertexAttribute, VertexBuilder, VertexElement}, + viewport, ResourceId, }, runtime::start_runtime, runtimes::{ - application::ComponentResult, - ApplicationRuntime, - ApplicationRuntimeBuilder, - }, -}; -use lambda_platform::{ - gfx::{ - pipeline::PipelineStage, - surface::ColorFormat, - }, - shaderc::{ - ShaderKind, - VirtualShader, + application::ComponentResult, ApplicationRuntime, ApplicationRuntimeBuilder, }, }; @@ -176,6 +150,14 @@ impl Component for PushConstantsExample { offset: 0, }, }, + VertexAttribute { + location: 1, + offset: 0, + element: VertexElement { + format: ColorFormat::Rgb32Sfloat, + offset: 12, + }, + }, VertexAttribute { location: 2, offset: 0, @@ -275,6 +257,16 @@ impl Component for PushConstantsExample { .expect("No render pipeline actively set for rendering."); return vec![ + RenderCommand::BeginRenderPass { + render_pass: self + .render_pass + .expect("Cannot begin the render pass when it doesn't exist.") + .clone(), + viewport: viewport.clone(), + }, + RenderCommand::SetPipeline { + pipeline: render_pipeline.clone(), + }, RenderCommand::SetViewports { start_at: 0, viewports: vec![viewport.clone()], @@ -283,16 +275,6 @@ impl Component for PushConstantsExample { start_at: 0, viewports: vec![viewport.clone()], }, - RenderCommand::SetPipeline { - pipeline: render_pipeline.clone(), - }, - RenderCommand::BeginRenderPass { - render_pass: self - .render_pass - .expect("Cannot begin the render pass when it doesn't exist.") - .clone(), - viewport: viewport.clone(), - }, RenderCommand::BindVertexBuffer { pipeline: render_pipeline.clone(), buffer: 0, diff --git a/crates/lambda-rs/examples/triangle.rs b/crates/lambda-rs/examples/triangle.rs index 002d4028..690ba3f7 100644 --- a/crates/lambda-rs/examples/triangle.rs +++ b/crates/lambda-rs/examples/triangle.rs @@ -1,29 +1,14 @@ use lambda::{ component::Component, - events::{ - ComponentEvent, - Events, - Key, - WindowEvent, - }, + events::{ComponentEvent, Events, Key, WindowEvent}, render::{ command::RenderCommand, - pipeline, - render_pass, - shader::{ - Shader, - ShaderBuilder, - ShaderKind, - VirtualShader, - }, - viewport, - RenderContext, + pipeline, render_pass, + shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, + viewport, RenderContext, }, runtime::start_runtime, - runtimes::{ - application::ComponentResult, - ApplicationRuntimeBuilder, - }, + runtimes::{application::ComponentResult, ApplicationRuntimeBuilder}, }; pub struct DemoComponent { @@ -139,8 +124,19 @@ impl Component for DemoComponent { let viewport = viewport::ViewportBuilder::new().build(self.width, self.height); - // This array of commands will be executed in linear order + // Begin the pass first, then set pipeline/state inside return vec![ + RenderCommand::BeginRenderPass { + render_pass: self + .render_pass_id + .expect("No render pass attached to the component"), + viewport: viewport.clone(), + }, + RenderCommand::SetPipeline { + pipeline: self + .render_pipeline_id + .expect("No pipeline attached to the component"), + }, RenderCommand::SetViewports { start_at: 0, viewports: vec![viewport.clone()], @@ -149,17 +145,6 @@ impl Component for DemoComponent { start_at: 0, viewports: vec![viewport.clone()], }, - RenderCommand::SetPipeline { - pipeline: self - .render_pipeline_id - .expect("No pipeline attached to the component"), - }, - RenderCommand::BeginRenderPass { - render_pass: self - .render_pass_id - .expect("No render pass attached to the component"), - viewport: viewport.clone(), - }, RenderCommand::Draw { vertices: 0..3 }, RenderCommand::EndRenderPass, ]; diff --git a/crates/lambda-rs/examples/triangles.rs b/crates/lambda-rs/examples/triangles.rs index 0dde00e4..52bebc95 100644 --- a/crates/lambda-rs/examples/triangles.rs +++ b/crates/lambda-rs/examples/triangles.rs @@ -1,32 +1,15 @@ use lambda::{ component::Component, - events::{ - Events, - Key, - VirtualKey, - WindowEvent, - }, + events::{Events, Key, VirtualKey, WindowEvent}, render::{ command::RenderCommand, - pipeline::{ - self, - PipelineStage, - }, + pipeline::{self, PipelineStage}, render_pass, - shader::{ - Shader, - ShaderBuilder, - ShaderKind, - VirtualShader, - }, - viewport, - RenderContext, + shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, + viewport, RenderContext, }, runtime::start_runtime, - runtimes::{ - application::ComponentResult, - ApplicationRuntimeBuilder, - }, + runtimes::{application::ComponentResult, ApplicationRuntimeBuilder}, }; pub struct TrianglesComponent { @@ -113,26 +96,26 @@ impl Component for TrianglesComponent { .render_pipeline .expect("No render pipeline actively set for rendering."); - let mut commands = vec![ - RenderCommand::SetViewports { - start_at: 0, - viewports: vec![viewport.clone()], - }, - RenderCommand::SetScissors { - start_at: 0, - viewports: vec![viewport.clone()], - }, - RenderCommand::SetPipeline { - pipeline: render_pipeline.clone(), - }, - RenderCommand::BeginRenderPass { - render_pass: self - .render_pass - .expect("Cannot begin the render pass when it doesn't exist.") - .clone(), - viewport: viewport.clone(), - }, - ]; + // All state setting must be inside the render pass + let mut commands = vec![RenderCommand::BeginRenderPass { + render_pass: self + .render_pass + .expect("Cannot begin the render pass when it doesn't exist.") + .clone(), + viewport: viewport.clone(), + }]; + + commands.push(RenderCommand::SetPipeline { + pipeline: render_pipeline.clone(), + }); + commands.push(RenderCommand::SetViewports { + start_at: 0, + viewports: vec![viewport.clone()], + }); + commands.push(RenderCommand::SetScissors { + start_at: 0, + viewports: vec![viewport.clone()], + }); // Upload triangle data into the the GPU at the vertex stage of the pipeline // before requesting to draw each triangle. @@ -175,16 +158,16 @@ impl Component for TrianglesComponent { scan_code, virtual_key, } => match virtual_key { - Some(VirtualKey::W) => { + Some(VirtualKey::KeyW) => { self.position.1 -= 0.01; } - Some(VirtualKey::S) => { + Some(VirtualKey::KeyS) => { self.position.1 += 0.01; } - Some(VirtualKey::A) => { + Some(VirtualKey::KeyA) => { self.position.0 -= 0.01; } - Some(VirtualKey::D) => { + Some(VirtualKey::KeyD) => { self.position.0 += 0.01; } _ => {} diff --git a/crates/lambda-rs/src/component.rs b/crates/lambda-rs/src/component.rs index cdd3d1b0..292741d4 100644 --- a/crates/lambda-rs/src/component.rs +++ b/crates/lambda-rs/src/component.rs @@ -1,14 +1,8 @@ -use std::{ - fmt::Debug, - time::Duration, -}; +use std::{fmt::Debug, time::Duration}; use crate::{ events::Events, - render::{ - command::RenderCommand, - RenderContext, - }, + render::{command::RenderCommand, RenderContext}, }; /// The Component Interface for allowing Component based data structures diff --git a/crates/lambda-rs/src/events.rs b/crates/lambda-rs/src/events.rs index d5b070ce..0c711088 100644 --- a/crates/lambda-rs/src/events.rs +++ b/crates/lambda-rs/src/events.rs @@ -26,7 +26,7 @@ pub enum RuntimeEvent { } /// Exports the winit virtual key codes to this namespace for convenience. -pub use lambda_platform::winit::winit_exports::VirtualKeyCode as VirtualKey; +pub use lambda_platform::winit::winit_exports::KeyCode as VirtualKey; /// Keyboard events are generated in response to keyboard events coming from /// the windowing system. diff --git a/crates/lambda-rs/src/math/matrix.rs b/crates/lambda-rs/src/math/matrix.rs index 4bb37eb6..269cfc77 100644 --- a/crates/lambda-rs/src/math/matrix.rs +++ b/crates/lambda-rs/src/math/matrix.rs @@ -2,10 +2,7 @@ use lambda_platform::rand::get_uniformly_random_floats_between; -use super::{ - turns_to_radians, - vector::Vector, -}; +use super::{turns_to_radians, vector::Vector}; // -------------------------------- MATRIX ------------------------------------- @@ -392,16 +389,9 @@ where mod tests { use super::{ - filled_matrix, - perspective_matrix, - rotate_matrix, - submatrix, - Matrix, - }; - use crate::math::{ - matrix::translation_matrix, - turns_to_radians, + filled_matrix, perspective_matrix, rotate_matrix, submatrix, Matrix, }; + use crate::math::{matrix::translation_matrix, turns_to_radians}; #[test] fn square_matrix_add() { diff --git a/crates/lambda-rs/src/render/buffer.rs b/crates/lambda-rs/src/render/buffer.rs index bf11549c..8125d9d9 100644 --- a/crates/lambda-rs/src/render/buffer.rs +++ b/crates/lambda-rs/src/render/buffer.rs @@ -1,166 +1,228 @@ //! Buffers for allocating memory on the GPU. -mod internal { - // Placed these in an internal module to avoid a name collision with the - // high level Buffer & BufferBuilder types in the parent module. - pub use lambda_platform::gfx::buffer::{ - Buffer, - BufferBuilder, - }; +use std::rc::Rc; + +use lambda_platform::wgpu::types::{self as wgpu, util::DeviceExt}; + +use super::{mesh::Mesh, vertex::Vertex, RenderContext}; + +#[derive(Clone, Copy, Debug)] +/// High‑level classification for buffers created by the engine. +/// +/// The type guides default usage flags and how a buffer is bound during +/// encoding (e.g., as a vertex or index buffer). +pub enum BufferType { + Vertex, + Index, + Uniform, + Storage, } -use std::rc::Rc; +#[derive(Clone, Copy, Debug)] +/// A thin newtype for `wgpu::BufferUsages` that supports bitwise ops while +/// keeping explicit construction points in the API surface. +pub struct Usage(wgpu::BufferUsages); + +impl Usage { + /// Mark buffer usable as a vertex buffer. + pub const VERTEX: Usage = Usage(wgpu::BufferUsages::VERTEX); + /// Mark buffer usable as an index buffer. + pub const INDEX: Usage = Usage(wgpu::BufferUsages::INDEX); + /// Mark buffer usable as a uniform buffer. + pub const UNIFORM: Usage = Usage(wgpu::BufferUsages::UNIFORM); + /// Mark buffer usable as a storage buffer. + pub const STORAGE: Usage = Usage(wgpu::BufferUsages::STORAGE); + + /// Extract the inner `wgpu` flags. + pub fn to_wgpu(self) -> wgpu::BufferUsages { + self.0 + } +} + +impl std::ops::BitOr for Usage { + type Output = Usage; + + fn bitor(self, rhs: Usage) -> Usage { + Usage(self.0 | rhs.0) + } +} + +impl Default for Usage { + fn default() -> Self { + Usage::VERTEX + } +} -// publicly use Properties and Usage from buffer.rs -pub use lambda_platform::gfx::buffer::{ - BufferType, - Properties, - Usage, -}; -use logging; - -use super::{ - mesh::Mesh, - vertex::Vertex, - RenderContext, -}; - -/// Buffer for storing vertex data on the GPU. +#[derive(Clone, Copy, Debug)] +/// Buffer allocation properties that control residency and CPU visibility. +pub struct Properties { + cpu_visible: bool, +} + +impl Properties { + /// Allocate in CPU‑visible memory (upload/streaming friendly). + pub const CPU_VISIBLE: Properties = Properties { cpu_visible: true }; + /// Allocate in device‑local memory (prefer GPU residency/perf). + pub const DEVICE_LOCAL: Properties = Properties { cpu_visible: false }; + + /// Whether the buffer should be writable from the CPU. + pub fn cpu_visible(self) -> bool { + self.cpu_visible + } +} + +impl Default for Properties { + fn default() -> Self { + Properties::CPU_VISIBLE + } +} + +/// Buffer for storing data on the GPU. +/// +/// Wraps a `wgpu::Buffer` and tracks the element stride and logical type used +/// when binding to pipeline inputs. #[derive(Debug)] pub struct Buffer { - buffer: Rc>, + buffer: Rc, + stride: wgpu::BufferAddress, buffer_type: BufferType, } -/// Public interface for a buffer. impl Buffer { /// Destroy the buffer and all it's resources with the render context that - /// created it. - pub fn destroy(self, render_context: &RenderContext) { - Rc::try_unwrap(self.buffer) - .expect("Failed to get inside buffer") - .destroy(render_context.internal_gpu()); + /// created it. Dropping the buffer will release GPU resources. + pub fn destroy(self, _render_context: &RenderContext) {} + + pub(super) fn raw(&self) -> &wgpu::Buffer { + self.buffer.as_ref() } -} -/// Internal interface for working with buffers. -impl Buffer { - /// Retrieve a reference to the internal buffer. - pub(super) fn internal_buffer_rc( - &self, - ) -> Rc> { - return self.buffer.clone(); + pub(super) fn raw_rc(&self) -> Rc { + self.buffer.clone() } - pub(super) fn internal_buffer( - &self, - ) -> &internal::Buffer { - return &self.buffer; + pub(super) fn stride(&self) -> wgpu::BufferAddress { + self.stride + } + + /// The logical buffer type used by the engine (e.g., Vertex). + pub fn buffer_type(&self) -> BufferType { + self.buffer_type } } -/// A buffer is a block of memory that can be used to store data that can be -/// accessed by the GPU. The buffer is created with a length, usage, and -/// properties that determine how the buffer can be used. +/// Builder for creating `Buffer` objects with explicit usage and properties. +/// +/// A buffer is a block of memory the GPU can access. You supply a total byte +/// length, usage flags, and residency properties; the builder will initialize +/// the buffer with provided contents and add `COPY_DST` when CPU visibility is +/// requested. pub struct BufferBuilder { - buffer_builder: internal::BufferBuilder, + buffer_length: usize, + usage: Usage, + properties: Properties, buffer_type: BufferType, + label: Option, } impl BufferBuilder { /// Creates a new buffer builder of type vertex. pub fn new() -> Self { - return Self { - buffer_builder: internal::BufferBuilder::new(), + Self { + buffer_length: 0, + usage: Usage::VERTEX, + properties: Properties::CPU_VISIBLE, buffer_type: BufferType::Vertex, - }; - } - - /// Builds a buffer from a given mesh and allocates it's memory on to the GPU. - pub fn build_from_mesh( - mesh: &Mesh, - render_context: &mut RenderContext, - ) -> Result { - let mut buffer_builder = Self::new(); - - // Allocate a buffer with the size of the mesh's vertices. - let internal_buffer = buffer_builder - .buffer_builder - .with_length(mesh.vertices().len() * std::mem::size_of::()) - .with_usage(Usage::VERTEX) - .with_properties(Properties::CPU_VISIBLE) - .build( - render_context.internal_mutable_gpu(), - mesh.vertices().to_vec(), - ); - - match internal_buffer { - Ok(internal_buffer) => { - return Ok(Buffer { - buffer: Rc::new(internal_buffer), - buffer_type: BufferType::Vertex, - }); - } - Err(_) => { - return Err("Failed to create buffer from mesh."); - } + label: None, } } - /// Sets the length of the buffer (In bytes). + /// Set the length of the buffer in bytes. Defaults to the size of `data`. pub fn with_length(&mut self, size: usize) -> &mut Self { - self.buffer_builder.with_length(size); - return self; + self.buffer_length = size; + self } - /// Sets the type of buffer to create. + /// Set the logical type of buffer to be created (vertex/index/...). pub fn with_buffer_type(&mut self, buffer_type: BufferType) -> &mut Self { self.buffer_type = buffer_type; - self.buffer_builder.with_buffer_type(buffer_type); - return self; + self } - /// Sets the usage of the buffer. + /// Set `wgpu` usage flags (bit‑or `Usage` values). pub fn with_usage(&mut self, usage: Usage) -> &mut Self { - self.buffer_builder.with_usage(usage); - return self; + self.usage = usage; + self } - /// Sets the properties of the buffer. + /// Control CPU visibility and residency preferences. pub fn with_properties(&mut self, properties: Properties) -> &mut Self { - self.buffer_builder.with_properties(properties); - return self; + self.properties = properties; + self } - /// Build a buffer utilizing the current render context - pub fn build( + /// Attach a human‑readable label for debugging/profiling. + pub fn with_label(&mut self, label: &str) -> &mut Self { + self.label = Some(label.to_string()); + self + } + + /// Create a buffer initialized with the provided `data`. + /// + /// Returns an error if the resolved length would be zero. + pub fn build( &self, render_context: &mut RenderContext, data: Vec, ) -> Result { - let buffer_allocation = self - .buffer_builder - .build(render_context.internal_mutable_gpu(), data); - - match buffer_allocation { - Ok(buffer) => { - logging::debug!( - "Buffer allocation for {:?} succeeded.", - self.buffer_type - ); - return Ok(Buffer { - buffer: Rc::new(buffer), - buffer_type: self.buffer_type, - }); - } - Err(error) => { - logging::error!( - "Buffer allocation for {:?} failed with error: {:?}", - self.buffer_type, - error - ); - return Err(error); - } + let device = render_context.device(); + let element_size = std::mem::size_of::(); + let buffer_length = if self.buffer_length == 0 { + element_size * data.len() + } else { + self.buffer_length + }; + + if buffer_length == 0 { + return Err("Attempted to create a buffer with zero length."); } + + let bytes = unsafe { + std::slice::from_raw_parts( + data.as_ptr() as *const u8, + element_size * data.len(), + ) + }; + + let mut usage = self.usage.to_wgpu(); + if self.properties.cpu_visible() { + usage |= wgpu::BufferUsages::COPY_DST; + } + + let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: self.label.as_deref(), + contents: bytes, + usage, + }); + + Ok(Buffer { + buffer: Rc::new(buffer), + stride: element_size as wgpu::BufferAddress, + buffer_type: self.buffer_type, + }) + } + + /// Convenience: create a vertex buffer from a `Mesh`'s vertices. + pub fn build_from_mesh( + mesh: &Mesh, + render_context: &mut RenderContext, + ) -> Result { + let mut builder = Self::new(); + builder.with_length(mesh.vertices().len() * std::mem::size_of::()); + builder.with_usage(Usage::VERTEX); + builder.with_properties(Properties::CPU_VISIBLE); + builder.with_buffer_type(BufferType::Vertex); + + builder.build(render_context, mesh.vertices().to_vec()) } } diff --git a/crates/lambda-rs/src/render/command.rs b/crates/lambda-rs/src/render/command.rs index ec850ddc..75d94eb1 100644 --- a/crates/lambda-rs/src/render/command.rs +++ b/crates/lambda-rs/src/render/command.rs @@ -2,140 +2,43 @@ use std::ops::Range; -use lambda_platform::gfx::viewport::ViewPort as PlatformViewPort; +use super::{pipeline::PipelineStage, viewport::Viewport}; -use super::{ - PlatformRenderCommand, - RenderContext, -}; - -/// Commands that are used to render a frame within the RenderContext. +/// Commands recorded and executed by the `RenderContext` to produce a frame. #[derive(Debug, Clone)] pub enum RenderCommand { - /// sets the viewports for the render context. + /// Set one or more viewports starting at `start_at` slot. SetViewports { start_at: u32, - viewports: Vec, + viewports: Vec, }, - /// sets the scissor rectangles for the render context. + /// Set one or more scissor rectangles matching the current viewports. SetScissors { start_at: u32, - viewports: Vec, + viewports: Vec, }, - /// Sets the pipeline to use for the render context. + /// Bind a previously attached graphics pipeline by id. SetPipeline { pipeline: super::ResourceId }, - /// Begins the render pass. + /// Begin a render pass that targets the swapchain. BeginRenderPass { render_pass: super::ResourceId, - viewport: super::viewport::Viewport, + viewport: Viewport, }, - /// Ends the render pass. + /// End the current render pass. EndRenderPass, - /// Sets the push constants for the render pipeline. + /// Upload push constants for the active pipeline/stage at `offset`. PushConstants { pipeline: super::ResourceId, - stage: super::pipeline::PipelineStage, + stage: PipelineStage, offset: u32, bytes: Vec, }, - /// Binds a vertex buffer to the render pipeline. + /// Bind a vertex buffer by index as declared on the pipeline. BindVertexBuffer { pipeline: super::ResourceId, buffer: u32, }, - /// Draws a graphical primitive. + /// Issue a non‑indexed draw for the provided vertex range. Draw { vertices: Range }, } - -impl RenderCommand { - /// Converts the RenderCommand into a platform compatible render command. - pub(super) fn into_platform_command( - &self, - render_context: &mut RenderContext, - ) -> PlatformRenderCommand { - return match self { - RenderCommand::SetViewports { - start_at, - viewports, - } => PlatformRenderCommand::SetViewports { - start_at: *start_at, - viewports: viewports - .into_iter() - .map(|viewport| viewport.clone_gfx_viewport()) - .collect::>(), - }, - RenderCommand::SetScissors { - start_at, - viewports, - } => PlatformRenderCommand::SetScissors { - start_at: *start_at, - viewports: viewports - .into_iter() - .map(|viewport| viewport.clone_gfx_viewport()) - .collect::>(), - }, - RenderCommand::BeginRenderPass { - render_pass, - viewport, - } => { - let surface = render_context.internal_surface(); - let frame_buffer = render_context.allocate_and_get_frame_buffer( - render_context - .get_render_pass(*render_pass) - .into_gfx_render_pass() - .as_ref(), - ); - - PlatformRenderCommand::BeginRenderPass { - render_pass: render_context - .get_render_pass(*render_pass) - .into_gfx_render_pass(), - surface: surface.clone(), - frame_buffer: frame_buffer.clone(), - viewport: viewport.clone_gfx_viewport(), - } - } - RenderCommand::EndRenderPass => PlatformRenderCommand::EndRenderPass, - RenderCommand::SetPipeline { pipeline } => { - PlatformRenderCommand::AttachGraphicsPipeline { - pipeline: render_context - .render_pipelines - .get(*pipeline) - .unwrap() - .into_platform_render_pipeline(), - } - } - RenderCommand::PushConstants { - pipeline, - stage, - offset, - bytes, - } => PlatformRenderCommand::PushConstants { - pipeline: render_context - .render_pipelines - .get(*pipeline) - .unwrap() - .into_platform_render_pipeline(), - stage: *stage, - offset: *offset, - bytes: bytes.clone(), - }, - RenderCommand::BindVertexBuffer { pipeline, buffer } => { - PlatformRenderCommand::BindVertexBuffer { - buffer: render_context - .render_pipelines - .get(*pipeline) - .unwrap() - .buffers() - .get(*buffer as usize) - .unwrap() - .internal_buffer_rc(), - } - } - RenderCommand::Draw { vertices } => PlatformRenderCommand::Draw { - vertices: vertices.clone(), - }, - }; - } -} diff --git a/crates/lambda-rs/src/render/mesh.rs b/crates/lambda-rs/src/render/mesh.rs index d549fb1c..3d15a43d 100644 --- a/crates/lambda-rs/src/render/mesh.rs +++ b/crates/lambda-rs/src/render/mesh.rs @@ -3,11 +3,7 @@ use lambda_platform::obj::load_textured_obj_from_file; use super::{ - vertex::{ - Vertex, - VertexAttribute, - VertexElement, - }, + vertex::{Vertex, VertexAttribute, VertexElement}, ColorFormat, }; diff --git a/crates/lambda-rs/src/render/mod.rs b/crates/lambda-rs/src/render/mod.rs index 27cd0c22..56147d29 100644 --- a/crates/lambda-rs/src/render/mod.rs +++ b/crates/lambda-rs/src/render/mod.rs @@ -12,113 +12,104 @@ pub mod vertex; pub mod viewport; pub mod window; -use std::{ - mem::swap, - rc::Rc, -}; +pub use vertex::ColorFormat; + +use std::iter; -/// ColorFormat is a type alias for the color format used by the surface and -/// vertex buffers. They denote the size of the color channels and the number of -/// channels being used. -pub use lambda_platform::gfx::surface::ColorFormat; -use lambda_platform::gfx::{ - command::{ - Command, - CommandBufferBuilder, - CommandBufferFeatures, - CommandBufferLevel, - }, - framebuffer::FramebufferBuilder, - surface::SwapchainBuilder, +use lambda_platform::wgpu::{ + types as wgpu, Gpu, GpuBuilder, Instance, InstanceBuilder, Surface, + SurfaceBuilder, }; +use logging; use self::{ - command::RenderCommand, - pipeline::RenderPipeline, - render_pass::RenderPass, + command::RenderCommand, pipeline::RenderPipeline, render_pass::RenderPass, }; -/// A RenderContext is a localized rendering context that can be used to render -/// to a window. It is localized to a single window at the moment. +/// Builder for configuring a `RenderContext` tied to a single window. +/// +/// The builder wires up a `wgpu::Instance`, `Surface`, and `Gpu` using the +/// cross‑platform platform layer, then configures the surface with reasonable +/// defaults. Use this when setting up rendering for an application window. pub struct RenderContextBuilder { name: String, - render_timeout: u64, + _render_timeout: u64, } impl RenderContextBuilder { - /// Create a new localized RenderContext with the given name. + /// Create a new builder tagged with a human‑readable `name` used in labels. pub fn new(name: &str) -> Self { - return Self { + Self { name: name.to_string(), - render_timeout: 1_000_000_000, - }; + _render_timeout: 1_000_000_000, + } } - /// The time rendering has to complete before a timeout occurs. + /// Set how long rendering may take before timing out (nanoseconds). pub fn with_render_timeout(mut self, render_timeout: u64) -> Self { - self.render_timeout = render_timeout; - return self; + self._render_timeout = render_timeout; + self } - /// Builds a RenderContext and injects it into the application window. - /// Currently only supports building a Rendering Context utilizing the - /// systems primary GPU. + /// Build a `RenderContext` for the provided `window` and configure the + /// presentation surface. pub fn build(self, window: &window::Window) -> RenderContext { - let RenderContextBuilder { - name, - render_timeout, - } = self; - - let mut instance = internal::InstanceBuilder::new() - .build::(name.as_str()); - let surface = Rc::new( - internal::SurfaceBuilder::new().build(&instance, window.window_handle()), - ); - - // Build a GPU with a Graphical Render queue that can render to our surface. - let mut gpu = internal::GpuBuilder::new() - .with_render_queue_type(internal::RenderQueueType::Graphical) - .build(&mut instance, Some(&surface)) - .expect("Failed to build a GPU with a graphical render queue."); - - // Build command pool and allocate a single buffer named Primary - let command_pool = internal::CommandPoolBuilder::new().build(&gpu); - - // Build our rendering submission fence and semaphore. - let submission_fence = internal::RenderSubmissionFenceBuilder::new() - .with_render_timeout(render_timeout) - .build(&mut gpu); - - let render_semaphore = - internal::RenderSemaphoreBuilder::new().build(&mut gpu); - - return RenderContext { - name, + let RenderContextBuilder { name, .. } = self; + + let instance = InstanceBuilder::new() + .with_label(&format!("{} Instance", name)) + .build(); + + let mut surface = SurfaceBuilder::new() + .with_label(&format!("{} Surface", name)) + .build(&instance, window.window_handle()) + .expect("Failed to create rendering surface"); + + let gpu = GpuBuilder::new() + .with_label(&format!("{} Device", name)) + .build(&instance, Some(&surface)) + .expect("Failed to create GPU device"); + + let size = window.dimensions(); + let config = surface + .configure_with_defaults( + gpu.adapter(), + gpu.device(), + size, + wgpu::PresentMode::Fifo, + wgpu::TextureUsages::RENDER_ATTACHMENT, + ) + .expect("Failed to configure surface"); + + RenderContext { + label: name, instance, + surface, gpu, - surface: surface.clone(), - frame_buffer: None, - submission_fence: Some(submission_fence), - render_semaphore: Some(render_semaphore), - command_pool: Some(command_pool), + config, + present_mode: wgpu::PresentMode::Fifo, + texture_usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + size, render_passes: vec![], render_pipelines: vec![], - }; + } } } -/// Generic Rendering API setup to use the current platforms primary -/// Rendering Backend +/// High‑level rendering context backed by `wgpu` for a single window. +/// +/// The context owns the `Instance`, presentation `Surface`, and `Gpu` device +/// objects and maintains a set of attached render passes and pipelines used +/// while encoding command streams for each frame. pub struct RenderContext { - name: String, - instance: internal::Instance, - gpu: internal::Gpu, - surface: Rc>, - frame_buffer: Option>>, - submission_fence: - Option>, - render_semaphore: Option>, - command_pool: Option>, + label: String, + instance: Instance, + surface: Surface<'static>, + gpu: Gpu, + config: wgpu::SurfaceConfiguration, + present_mode: wgpu::PresentMode, + texture_usage: wgpu::TextureUsages, + size: (u32, u32), render_passes: Vec, render_pipelines: Vec, } @@ -126,255 +117,267 @@ pub struct RenderContext { pub type ResourceId = usize; impl RenderContext { - /// Permanently transfer a render pipeline to the render context in exchange - /// for a resource ID that you can use in render commands. + /// Attach a render pipeline and return a handle for use in commands. pub fn attach_pipeline(&mut self, pipeline: RenderPipeline) -> ResourceId { - let index = self.render_pipelines.len(); + let id = self.render_pipelines.len(); self.render_pipelines.push(pipeline); - return index; + id } - /// Permanently transfer a render pipeline to the render context in exchange - /// for a resource ID that you can use in render commands. + /// Attach a render pass and return a handle for use in commands. pub fn attach_render_pass(&mut self, render_pass: RenderPass) -> ResourceId { - let index = self.render_passes.len(); + let id = self.render_passes.len(); self.render_passes.push(render_pass); - return index; + id } - /// destroys the RenderContext and all associated resources. - pub fn destroy(mut self) { - logging::debug!("{} will now start destroying resources.", self.name); - - // Destroy the submission fence and rendering semaphore. - self - .submission_fence - .take() - .expect( - "Couldn't take the submission fence from the context and destroy it.", - ) - .destroy(&self.gpu); - self - .render_semaphore - .take() - .expect("Couldn't take the rendering semaphore from the context and destroy it.") - .destroy(&self.gpu); + /// Explicitly destroy the context. Dropping also releases resources. + pub fn destroy(self) { + drop(self); + } - self - .command_pool - .as_mut() - .unwrap() - .deallocate_command_buffer("primary"); + /// Render a list of commands. No‑ops when the list is empty. + /// + /// Errors are logged and do not panic; see `RenderError` for cases. + pub fn render(&mut self, commands: Vec) { + if commands.is_empty() { + return; + } - self - .command_pool - .take() - .expect("Couldn't take the command pool from the context and destroy it.") - .destroy(&self.gpu); + if let Err(err) = self.render_internal(commands) { + logging::error!("Render error: {:?}", err); + } + } - // Destroy render passes. - let mut render_passes = vec![]; - swap(&mut self.render_passes, &mut render_passes); + /// Resize the surface and update surface configuration. + pub fn resize(&mut self, width: u32, height: u32) { + if width == 0 || height == 0 { + return; + } - for render_pass in render_passes { - render_pass.destroy(&self); + self.size = (width, height); + if let Err(err) = self.reconfigure_surface(self.size) { + logging::error!("Failed to resize surface: {:?}", err); } + } - // Destroy render pipelines. - let mut render_pipelines = vec![]; - swap(&mut self.render_pipelines, &mut render_pipelines); + /// Borrow a previously attached render pass by id. + pub fn get_render_pass(&self, id: ResourceId) -> &RenderPass { + &self.render_passes[id] + } - for render_pipeline in render_pipelines { - render_pipeline.destroy(&self); - } + /// Borrow a previously attached render pipeline by id. + pub fn get_render_pipeline(&self, id: ResourceId) -> &RenderPipeline { + &self.render_pipelines[id] + } - // Takes the inner surface and destroys it. - let mut surface = Rc::try_unwrap(self.surface) - .expect("Couldn't obtain the surface from the context."); + pub(crate) fn device(&self) -> &wgpu::Device { + self.gpu.device() + } - surface.remove_swapchain(&self.gpu); - surface.destroy(&self.instance); + pub(crate) fn queue(&self) -> &wgpu::Queue { + self.gpu.queue() } - pub fn allocate_and_get_frame_buffer( - &mut self, - render_pass: &internal::RenderPass, - ) -> Rc> - { - let frame_buffer = FramebufferBuilder::new().build( - &mut self.gpu, - &render_pass, - self.surface.as_ref(), - ); - - // TODO(vmarcella): Update the framebuffer allocation to not be so hacky. - // FBAs can only be allocated once a render pass has begun, but must be - // cleaned up after commands have been submitted forcing us - self.frame_buffer = Some(Rc::new(frame_buffer)); - return self.frame_buffer.as_ref().unwrap().clone(); + pub(crate) fn surface_format(&self) -> wgpu::TextureFormat { + self.config.format } - /// Allocates a command buffer and records commands to the GPU. This is the - /// primary entry point for submitting commands to the GPU and where rendering - /// will occur. - pub fn render(&mut self, commands: Vec) { - let (width, height) = self - .surface - .size() - .expect("Surface has no size configured."); - - let swapchain = SwapchainBuilder::new() - .with_size(width, height) - .build(&self.gpu, &self.surface); - - if self.surface.needs_swapchain() { - Rc::get_mut(&mut self.surface) - .expect("Failed to get mutable reference to surface.") - .apply_swapchain(&self.gpu, swapchain, 1_000_000_000) - .expect("Failed to apply the swapchain to the surface."); + /// Encode and submit GPU work for a single frame. + fn render_internal( + &mut self, + commands: Vec, + ) -> Result<(), RenderError> { + if self.size.0 == 0 || self.size.1 == 0 { + return Ok(()); } - self - .submission_fence - .as_mut() - .expect("Failed to get the submission fence.") - .block_until_ready(&mut self.gpu, None); - - let platform_command_list = commands - .into_iter() - .map(|command| command.into_platform_command(self)) - .collect(); - - let mut command_buffer = - CommandBufferBuilder::new(CommandBufferLevel::Primary) - .with_feature(CommandBufferFeatures::ResetEverySubmission) - .build( - self - .command_pool - .as_mut() - .expect("No command pool to create a buffer from"), - "primary", - ); - - // Start recording commands, issue the high level render commands - // that came from an application, and then submit the commands to the GPU - // for rendering. - command_buffer.issue_command(PlatformRenderCommand::BeginRecording); - command_buffer.issue_commands(platform_command_list); - command_buffer.issue_command(PlatformRenderCommand::EndRecording); - - self.gpu.submit_command_buffer( - &mut command_buffer, - vec![self.render_semaphore.as_ref().unwrap()], - self.submission_fence.as_mut().unwrap(), - ); + let mut frame = match self.surface.acquire_next_frame() { + Ok(frame) => frame, + Err(wgpu::SurfaceError::Lost) | Err(wgpu::SurfaceError::Outdated) => { + self.reconfigure_surface(self.size)?; + self + .surface + .acquire_next_frame() + .map_err(RenderError::Surface)? + } + Err(err) => return Err(RenderError::Surface(err)), + }; - self - .gpu - .render_to_surface( - Rc::get_mut(&mut self.surface) - .expect("Failed to obtain a surface to render on."), - self.render_semaphore.as_mut().unwrap(), - ) - .expect("Failed to render to the surface"); - - // Destroys the frame buffer after the commands have been submitted and the - // frame buffer is no longer needed. - match self.frame_buffer { - Some(_) => { - Rc::try_unwrap(self.frame_buffer.take().unwrap()) - .expect("Failed to unwrap the frame buffer.") - .destroy(&self.gpu); + let view = frame.texture_view(); + let mut encoder = + self + .device() + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("lambda-render-command-encoder"), + }); + + let mut command_iter = commands.into_iter(); + while let Some(command) = command_iter.next() { + match command { + RenderCommand::BeginRenderPass { + render_pass, + viewport, + } => { + let pass = self.render_passes.get(render_pass).ok_or_else(|| { + RenderError::Configuration(format!( + "Unknown render pass {render_pass}" + )) + })?; + + let color_attachment = wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: pass.color_ops(), + }; + let color_attachments = [Some(color_attachment)]; + let mut pass_encoder = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: pass.label(), + color_attachments: &color_attachments, + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + self.encode_pass(&mut pass_encoder, viewport, &mut command_iter)?; + } + other => { + logging::warn!( + "Ignoring render command outside of a render pass: {:?}", + other + ); + } } - None => {} } - } - pub fn resize(&mut self, width: u32, height: u32) { - let swapchain = SwapchainBuilder::new() - .with_size(width, height) - .build(&self.gpu, &self.surface); - - if self.surface.needs_swapchain() { - Rc::get_mut(&mut self.surface) - .expect("Failed to get mutable reference to surface.") - .apply_swapchain(&self.gpu, swapchain, 1_000_000_000) - .expect("Failed to apply the swapchain to the surface."); - } + self.queue().submit(iter::once(encoder.finish())); + frame.present(); + Ok(()) } - /// Get the render pass with the resource ID that was provided upon - /// attachment. - pub fn get_render_pass(&self, id: ResourceId) -> &RenderPass { - return &self.render_passes[id]; - } + /// Encode a single render pass and consume commands until `EndRenderPass`. + fn encode_pass( + &mut self, + pass: &mut wgpu::RenderPass<'_>, + initial_viewport: viewport::Viewport, + commands: &mut I, + ) -> Result<(), RenderError> + where + I: Iterator, + { + Self::apply_viewport(pass, &initial_viewport); + + while let Some(command) = commands.next() { + match command { + RenderCommand::EndRenderPass => return Ok(()), + RenderCommand::SetPipeline { pipeline } => { + let pipeline_ref = + self.render_pipelines.get(pipeline).ok_or_else(|| { + RenderError::Configuration(format!("Unknown pipeline {pipeline}")) + })?; + pass.set_pipeline(pipeline_ref.pipeline()); + } + RenderCommand::SetViewports { viewports, .. } => { + for viewport in viewports { + Self::apply_viewport(pass, &viewport); + } + } + RenderCommand::SetScissors { viewports, .. } => { + for viewport in viewports { + let (x, y, width, height) = viewport.scissor_u32(); + pass.set_scissor_rect(x, y, width, height); + } + } + RenderCommand::BindVertexBuffer { pipeline, buffer } => { + let pipeline_ref = + self.render_pipelines.get(pipeline).ok_or_else(|| { + RenderError::Configuration(format!("Unknown pipeline {pipeline}")) + })?; + let buffer_ref = + pipeline_ref.buffers().get(buffer as usize).ok_or_else(|| { + RenderError::Configuration(format!( + "Vertex buffer index {buffer} not found for pipeline {pipeline}" + )) + })?; + pass.set_vertex_buffer(buffer as u32, buffer_ref.raw().slice(..)); + } + RenderCommand::PushConstants { + pipeline, + stage, + offset, + bytes, + } => { + let _ = self.render_pipelines.get(pipeline).ok_or_else(|| { + RenderError::Configuration(format!("Unknown pipeline {pipeline}")) + })?; + let slice = unsafe { + std::slice::from_raw_parts( + bytes.as_ptr() as *const u8, + bytes.len() * std::mem::size_of::(), + ) + }; + pass.set_push_constants(stage.to_wgpu(), offset, slice); + } + RenderCommand::Draw { vertices } => { + pass.draw(vertices, 0..1); + } + RenderCommand::BeginRenderPass { .. } => { + return Err(RenderError::Configuration( + "Nested render passes are not supported.".to_string(), + )); + } + } + } - /// Get the render pipeline with the resource ID that was provided upon - /// attachment. - pub fn get_render_pipeline(&mut self, id: ResourceId) -> &RenderPipeline { - return &self.render_pipelines[id]; + Err(RenderError::Configuration( + "Render pass did not terminate with EndRenderPass".to_string(), + )) } -} -impl RenderContext { - /// Internal access to the RenderContext's GPU. - pub(super) fn internal_gpu(&self) -> &internal::Gpu { - return &self.gpu; + /// Apply both viewport and scissor state to the active pass. + fn apply_viewport( + pass: &mut wgpu::RenderPass<'_>, + viewport: &viewport::Viewport, + ) { + let (x, y, width, height, min_depth, max_depth) = viewport.viewport_f32(); + pass.set_viewport(x, y, width, height, min_depth, max_depth); + let (sx, sy, sw, sh) = viewport.scissor_u32(); + pass.set_scissor_rect(sx, sy, sw, sh); } - /// Internal mutable access to the RenderContext's GPU. - pub(super) fn internal_mutable_gpu( + /// Reconfigure the presentation surface using current present mode/usage. + fn reconfigure_surface( &mut self, - ) -> &mut internal::Gpu { - return &mut self.gpu; - } + size: (u32, u32), + ) -> Result<(), RenderError> { + let config = self + .surface + .configure_with_defaults( + self.gpu.adapter(), + self.gpu.device(), + size, + self.present_mode, + self.texture_usage, + ) + .map_err(RenderError::Configuration)?; - pub(super) fn internal_surface( - &self, - ) -> Rc> { - return self.surface.clone(); + self.present_mode = config.present_mode; + self.texture_usage = config.usage; + self.config = config; + Ok(()) } } -type PlatformRenderCommand = Command; - -pub(crate) mod internal { - - use lambda_platform::gfx::api::RenderingAPI as RenderContext; - pub type RenderBackend = RenderContext::Backend; - - pub use lambda_platform::{ - gfx::{ - command::{ - CommandBuffer, - CommandBufferBuilder, - CommandPool, - CommandPoolBuilder, - }, - fence::{ - RenderSemaphore, - RenderSemaphoreBuilder, - RenderSubmissionFence, - RenderSubmissionFenceBuilder, - }, - framebuffer::Framebuffer, - gpu::{ - Gpu, - GpuBuilder, - RenderQueueType, - }, - pipeline::RenderPipelineBuilder, - render_pass::{ - RenderPass, - RenderPassBuilder, - }, - surface::{ - Surface, - SurfaceBuilder, - }, - Instance, - InstanceBuilder, - }, - shaderc::ShaderKind, - }; +#[derive(Debug)] +/// Errors that can occur while preparing or presenting a frame. +pub enum RenderError { + Surface(wgpu::SurfaceError), + Configuration(String), +} + +impl From for RenderError { + fn from(error: wgpu::SurfaceError) -> Self { + RenderError::Surface(error) + } } diff --git a/crates/lambda-rs/src/render/pipeline.rs b/crates/lambda-rs/src/render/pipeline.rs index 5bde6290..9838453d 100644 --- a/crates/lambda-rs/src/render/pipeline.rs +++ b/crates/lambda-rs/src/render/pipeline.rs @@ -1,169 +1,239 @@ //! Render pipeline builders and definitions for lambda runtimes and //! applications. -use std::rc::Rc; -use lambda_platform::gfx::shader::{ - ShaderModuleBuilder, - ShaderModuleType, -}; +use std::{borrow::Cow, ops::Range, rc::Rc}; + +use lambda_platform::wgpu::types as wgpu; use super::{ - buffer::Buffer, - internal::RenderBackend, - shader::Shader, - RenderContext, + buffer::Buffer, render_pass::RenderPass, shader::Shader, + vertex::VertexAttribute, RenderContext, }; #[derive(Debug)] +/// A created graphics pipeline and the vertex buffers it expects. pub struct RenderPipeline { - pipeline: Rc< - lambda_platform::gfx::pipeline::RenderPipeline< - super::internal::RenderBackend, - >, - >, + pipeline: Rc, buffers: Vec>, } impl RenderPipeline { /// Destroy the render pipeline with the render context that created it. - pub fn destroy(self, render_context: &RenderContext) { - logging::trace!("Destroying render pipeline"); - Rc::try_unwrap(self.pipeline) - .expect("Failed to destroy render pipeline") - .destroy(render_context.internal_gpu()); - - logging::trace!("Destroying buffers"); - for buffer in self.buffers { - Rc::try_unwrap(buffer) - .expect("Failed to get high level buffer.") - .destroy(render_context); - } + pub fn destroy(self, _render_context: &RenderContext) {} - logging::info!("Render pipeline & all attached buffers destroyed"); + pub(super) fn buffers(&self) -> &Vec> { + &self.buffers + } + + pub(super) fn pipeline(&self) -> &wgpu::RenderPipeline { + self.pipeline.as_ref() } } -impl RenderPipeline { - pub(super) fn buffers(&self) -> &Vec> { - return &self.buffers; +#[derive(Clone, Copy, Debug)] +/// Bitflag wrapper for shader stages used by push constants. +pub struct PipelineStage(wgpu::ShaderStages); + +impl PipelineStage { + /// Vertex stage. + pub const VERTEX: PipelineStage = PipelineStage(wgpu::ShaderStages::VERTEX); + /// Fragment stage. + pub const FRAGMENT: PipelineStage = + PipelineStage(wgpu::ShaderStages::FRAGMENT); + /// Compute stage. + pub const COMPUTE: PipelineStage = PipelineStage(wgpu::ShaderStages::COMPUTE); + + pub(crate) fn to_wgpu(self) -> wgpu::ShaderStages { + self.0 + } +} + +impl std::ops::BitOr for PipelineStage { + type Output = PipelineStage; + + fn bitor(self, rhs: PipelineStage) -> PipelineStage { + PipelineStage(self.0 | rhs.0) } +} - pub(super) fn into_platform_render_pipeline( - &self, - ) -> Rc> { - return self.pipeline.clone(); +impl std::ops::BitOrAssign for PipelineStage { + fn bitor_assign(&mut self, rhs: PipelineStage) { + self.0 |= rhs.0; } } -use lambda_platform::gfx::pipeline::PushConstantUpload; -pub use lambda_platform::gfx::{ - assembler::VertexAttribute, - pipeline::PipelineStage, -}; +/// Convenience alias for uploading push constants: stage and byte range. +pub type PushConstantUpload = (PipelineStage, Range); +struct BufferBinding { + buffer: Rc, + attributes: Vec, +} + +/// Builder for creating a graphics `RenderPipeline`. pub struct RenderPipelineBuilder { push_constants: Vec, - buffers: Vec>, - attributes: Vec, + bindings: Vec, + label: Option, } impl RenderPipelineBuilder { /// Creates a new render pipeline builder. pub fn new() -> Self { - return Self { + Self { push_constants: Vec::new(), - buffers: Vec::new(), - attributes: Vec::new(), - }; + bindings: Vec::new(), + label: None, + } } - /// Adds a buffer to the render pipeline. + /// Declare a vertex buffer and the vertex attributes consumed by the shader. pub fn with_buffer( mut self, buffer: Buffer, attributes: Vec, ) -> Self { - self.buffers.push(Rc::new(buffer)); - self.attributes.extend(attributes); - return self; + self.bindings.push(BufferBinding { + buffer: Rc::new(buffer), + attributes, + }); + self } - /// Adds a push constant to the render pipeline at the given stage - /// with the given size in bytes. + /// Declare a push constant range for a shader stage in bytes. pub fn with_push_constant( mut self, stage: PipelineStage, bytes: u32, ) -> Self { self.push_constants.push((stage, 0..bytes)); - return self; + self + } + + /// Attach a debug label to the pipeline. + pub fn with_label(mut self, label: &str) -> Self { + self.label = Some(label.to_string()); + self } - /// Builds a render pipeline based on your builder configuration. + /// Build a graphics pipeline using the provided shader modules and + /// previously registered vertex inputs and push constants. pub fn build( self, render_context: &mut RenderContext, - render_pass: &super::render_pass::RenderPass, + _render_pass: &RenderPass, vertex_shader: &Shader, fragment_shader: Option<&Shader>, ) -> RenderPipeline { - logging::debug!("Building render pipeline"); - - logging::debug!("Building vertex shader... "); - let vertex_shader_module = ShaderModuleBuilder::new().build( - render_context.internal_mutable_gpu(), - &vertex_shader.as_binary(), - ShaderModuleType::Vertex, - ); - - logging::debug!( - "\tDone. (Vertex shader: {} bytes)", - vertex_shader.as_binary().len() - ); - - logging::debug!("Building fragment shader... "); - let fragment_shader_module = match fragment_shader { - Some(shader) => Some(ShaderModuleBuilder::new().build( - render_context.internal_mutable_gpu(), - &shader.as_binary(), - ShaderModuleType::Fragment, - )), - None => None, - }; - - logging::debug!( - "\tDone. (Fragment shader: {} bytes)", - fragment_shader.map(|s| s.as_binary().len()).unwrap_or(0) - ); + let device = render_context.device(); + let surface_format = render_context.surface_format(); + + let vertex_shader_module = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("lambda-vertex-shader"), + source: wgpu::ShaderSource::SpirV(Cow::Owned( + vertex_shader.as_binary(), + )), + }); + + let fragment_shader_module = fragment_shader.map(|shader| { + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("lambda-fragment-shader"), + source: wgpu::ShaderSource::SpirV(Cow::Owned(shader.as_binary())), + }) + }); + + let push_constant_ranges: Vec = self + .push_constants + .iter() + .map(|(stage, range)| wgpu::PushConstantRange { + stages: stage.to_wgpu(), + range: range.clone(), + }) + .collect(); + + let pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("lambda-pipeline-layout"), + bind_group_layouts: &[], + push_constant_ranges: &push_constant_ranges, + }); + + let mut attribute_storage: Vec> = + Vec::with_capacity(self.bindings.len()); + let mut vertex_buffer_layouts: Vec> = + Vec::with_capacity(self.bindings.len()); + let mut buffers = Vec::with_capacity(self.bindings.len()); + + // First, collect attributes and buffers + for binding in &self.bindings { + let attributes: Vec = binding + .attributes + .iter() + .map(|attribute| wgpu::VertexAttribute { + shader_location: attribute.location, + offset: (attribute.offset + attribute.element.offset) as u64, + format: attribute.element.format.to_vertex_format(), + }) + .collect(); + attribute_storage.push(attributes.into_boxed_slice()); + buffers.push(binding.buffer.clone()); + } - let builder = lambda_platform::gfx::pipeline::RenderPipelineBuilder::new(); + // Then, build layouts referencing the stable storage + for (i, binding) in self.bindings.iter().enumerate() { + let attributes_slice = attribute_storage[i].as_ref(); + vertex_buffer_layouts.push(wgpu::VertexBufferLayout { + array_stride: binding.buffer.stride(), + step_mode: wgpu::VertexStepMode::Vertex, + attributes: attributes_slice, + }); + } - let buffers = self.buffers; - let internal_buffers = buffers - .iter() - .map(|b| b.internal_buffer()) - .collect::>(); - - let render_pipeline = builder - .with_push_constants(self.push_constants.clone()) - .build( - render_context.internal_gpu(), - render_pass.internal_render_pass(), - &vertex_shader_module, - fragment_shader_module.as_ref(), - &internal_buffers, - self.attributes.as_slice(), - ); - - // Clean up shader modules. - vertex_shader_module.destroy(render_context.internal_mutable_gpu()); - if let Some(fragment_shader_module) = fragment_shader_module { - fragment_shader_module.destroy(render_context.internal_mutable_gpu()); + // Stable storage for color targets to satisfy borrow checker + let mut color_targets: Vec> = Vec::new(); + if fragment_shader_module.is_some() { + color_targets.push(Some(wgpu::ColorTargetState { + format: surface_format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })); } - return RenderPipeline { - pipeline: Rc::new(render_pipeline), - buffers, + let fragment = + fragment_shader_module + .as_ref() + .map(|module| wgpu::FragmentState { + module, + entry_point: Some("main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: color_targets.as_slice(), + }); + + let vertex_state = wgpu::VertexState { + module: &vertex_shader_module, + entry_point: Some("main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + buffers: vertex_buffer_layouts.as_slice(), + }; + + let pipeline_descriptor = wgpu::RenderPipelineDescriptor { + label: self.label.as_deref(), + layout: Some(&pipeline_layout), + vertex: vertex_state, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + fragment, + multiview: None, + cache: None, }; + + let pipeline = device.create_render_pipeline(&pipeline_descriptor); + + RenderPipeline { + pipeline: Rc::new(pipeline), + buffers, + } } } diff --git a/crates/lambda-rs/src/render/render_pass.rs b/crates/lambda-rs/src/render/render_pass.rs index 3fbae925..6dc39652 100644 --- a/crates/lambda-rs/src/render/render_pass.rs +++ b/crates/lambda-rs/src/render/render_pass.rs @@ -1,57 +1,64 @@ //! Render pass builders and definitions for lambda runtimes and applications. -use std::rc::Rc; -use lambda_platform::gfx::render_pass; +use lambda_platform::wgpu::types as wgpu; use super::RenderContext; -#[derive(Debug)] +#[derive(Debug, Clone)] +/// Immutable parameters used when beginning a render pass. pub struct RenderPass { - render_pass: Rc>, + clear_color: wgpu::Color, + label: Option, } impl RenderPass { - /// Destroy the render pass with the render context that created it. - pub fn destroy(self, render_context: &RenderContext) { - Rc::try_unwrap(self.render_pass) - .expect("Failed to destroy render pass. Is something holding a reference to it?") - .destroy(render_context.internal_gpu()); - logging::debug!("Render pass destroyed"); - } -} + /// Destroy the pass. Kept for symmetry with other resources. + pub fn destroy(self, _render_context: &RenderContext) {} -/// Internal Renderpass functions for lambda. -impl RenderPass { - /// Retrieve a reference to the lower level render pass. - pub(super) fn internal_render_pass( - &self, - ) -> &Rc> { - return &self.render_pass; + pub(crate) fn color_ops(&self) -> wgpu::Operations { + wgpu::Operations { + load: wgpu::LoadOp::Clear(self.clear_color), + store: wgpu::StoreOp::Store, + } } - /// Converts - pub(super) fn into_gfx_render_pass( - &self, - ) -> Rc> { - return self.render_pass.clone(); + pub(crate) fn label(&self) -> Option<&str> { + self.label.as_deref() } } -pub struct RenderPassBuilder {} +/// Builder for a `RenderPass` description. +pub struct RenderPassBuilder { + clear_color: wgpu::Color, + label: Option, +} impl RenderPassBuilder { /// Creates a new render pass builder. pub fn new() -> Self { - return Self {}; + Self { + clear_color: wgpu::Color::BLACK, + label: None, + } + } + + /// Specify the clear color used for the first color attachment. + pub fn with_clear_color(mut self, color: wgpu::Color) -> Self { + self.clear_color = color; + self + } + + /// Attach a label to the render pass for debugging/profiling. + pub fn with_label(mut self, label: &str) -> Self { + self.label = Some(label.to_string()); + self } - /// Builds a render pass that can be used for defining - pub fn build(self, render_context: &RenderContext) -> RenderPass { - let render_pass = - lambda_platform::gfx::render_pass::RenderPassBuilder::new() - .build(render_context.internal_gpu()); - return RenderPass { - render_pass: Rc::new(render_pass), - }; + /// Build the description used when beginning a render pass. + pub fn build(self, _render_context: &RenderContext) -> RenderPass { + RenderPass { + clear_color: self.clear_color, + label: self.label, + } } } diff --git a/crates/lambda-rs/src/render/shader.rs b/crates/lambda-rs/src/render/shader.rs index e7492d1b..6fce5551 100644 --- a/crates/lambda-rs/src/render/shader.rs +++ b/crates/lambda-rs/src/render/shader.rs @@ -1,13 +1,11 @@ //! A module for compiling shaders into SPIR-V binary. -// Expose some lower level shader -pub use lambda_platform::shaderc::{ - ShaderCompiler, - ShaderCompilerBuilder, - ShaderKind, - VirtualShader, +// Expose the platform shader compiler abstraction +pub use lambda_platform::shader::{ + ShaderCompiler, ShaderCompilerBuilder, ShaderKind, VirtualShader, }; +/// Reusable compiler for turning virtual shaders into SPIR‑V modules. pub struct ShaderBuilder { compiler: ShaderCompiler, } diff --git a/crates/lambda-rs/src/render/vertex.rs b/crates/lambda-rs/src/render/vertex.rs index cda7178b..d2d7164a 100644 --- a/crates/lambda-rs/src/render/vertex.rs +++ b/crates/lambda-rs/src/render/vertex.rs @@ -1,9 +1,44 @@ //! Vertex data structures. -pub use lambda_platform::gfx::assembler::{ - VertexAttribute, - VertexElement, -}; +use lambda_platform::wgpu::types as wgpu; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// Canonical color/attribute formats used by engine pipelines. +pub enum ColorFormat { + Rgb32Sfloat, + Rgba8Srgb, +} + +impl ColorFormat { + pub(crate) fn to_texture_format(self) -> wgpu::TextureFormat { + match self { + ColorFormat::Rgb32Sfloat => wgpu::TextureFormat::Rgba32Float, + ColorFormat::Rgba8Srgb => wgpu::TextureFormat::Rgba8UnormSrgb, + } + } + + pub(crate) fn to_vertex_format(self) -> wgpu::VertexFormat { + match self { + ColorFormat::Rgb32Sfloat => wgpu::VertexFormat::Float32x3, + ColorFormat::Rgba8Srgb => wgpu::VertexFormat::Unorm8x4, + } + } +} + +#[derive(Clone, Copy, Debug)] +/// A single vertex element (format + byte offset). +pub struct VertexElement { + pub format: ColorFormat, + pub offset: u32, +} + +#[derive(Clone, Copy, Debug)] +/// Vertex attribute bound to a shader `location` plus relative offsets. +pub struct VertexAttribute { + pub location: u32, + pub offset: u32, + pub element: VertexElement, +} /// Vertex data structure with position, normal, and color. #[repr(C)] @@ -14,7 +49,7 @@ pub struct Vertex { pub color: [f32; 3], } -/// Construction for +/// Builder for constructing a `Vertex` instance incrementally. #[derive(Clone, Copy, Debug)] pub struct VertexBuilder { pub position: [f32; 3], @@ -25,46 +60,48 @@ pub struct VertexBuilder { impl VertexBuilder { /// Creates a new vertex builder. pub fn new() -> Self { - return Self { + Self { position: [0.0, 0.0, 0.0], normal: [0.0, 0.0, 0.0], color: [0.0, 0.0, 0.0], - }; + } } /// Set the position of the vertex. pub fn with_position(&mut self, position: [f32; 3]) -> &mut Self { self.position = position; - return self; + self } /// Set the normal of the vertex. pub fn with_normal(&mut self, normal: [f32; 3]) -> &mut Self { self.normal = normal; - return self; + self } /// Set the color of the vertex. pub fn with_color(&mut self, color: [f32; 3]) -> &mut Self { self.color = color; - return self; + self } /// Build the vertex. pub fn build(&self) -> Vertex { - return Vertex { + Vertex { position: self.position, normal: self.normal, color: self.color, - }; + } } } #[cfg(test)] mod test { + use super::*; + #[test] fn vertex_building() { - let mut vertex = super::VertexBuilder::new(); + let mut vertex = VertexBuilder::new(); assert_eq!(vertex.position, [0.0, 0.0, 0.0]); assert_eq!(vertex.normal, [0.0, 0.0, 0.0]); diff --git a/crates/lambda-rs/src/render/viewport.rs b/crates/lambda-rs/src/render/viewport.rs index f8a1e66f..175a54c3 100644 --- a/crates/lambda-rs/src/render/viewport.rs +++ b/crates/lambda-rs/src/render/viewport.rs @@ -1,38 +1,68 @@ //! Viewport for rendering a frame within the RenderContext. -use lambda_platform::gfx; - #[derive(Debug, Clone, PartialEq)] +/// Viewport/scissor rectangle applied during rendering. pub struct Viewport { - viewport: gfx::viewport::ViewPort, + pub x: u32, + pub y: u32, + pub width: u32, + pub height: u32, + pub min_depth: f32, + pub max_depth: f32, } impl Viewport { - /// Convert the viewport into a gfx platform viewport. - // TODO(vmarcella): implement this using Into - pub(crate) fn clone_gfx_viewport(&self) -> gfx::viewport::ViewPort { - return self.viewport.clone(); + pub(crate) fn viewport_f32(&self) -> (f32, f32, f32, f32, f32, f32) { + ( + self.x as f32, + self.y as f32, + self.width as f32, + self.height as f32, + self.min_depth, + self.max_depth, + ) + } + + pub(crate) fn scissor_u32(&self) -> (u32, u32, u32, u32) { + (self.x, self.y, self.width, self.height) } } /// Builder for viewports that are used to render a frame within the RenderContext. pub struct ViewportBuilder { - x: i16, - y: i16, + x: i32, + y: i32, + min_depth: f32, + max_depth: f32, } impl ViewportBuilder { /// Creates a new viewport builder. pub fn new() -> Self { - return Self { x: 0, y: 0 }; + Self { + x: 0, + y: 0, + min_depth: 0.0, + max_depth: 1.0, + } } - /// Builds a viewport that can be used for defining - pub fn build(self, width: u32, height: u32) -> Viewport { - let viewport = gfx::viewport::ViewPortBuilder::new() - .with_coordinates(self.x, self.y) - .build(width, height); + /// Set the top‑left coordinates for the viewport and scissor. + pub fn with_coordinates(mut self, x: i32, y: i32) -> Self { + self.x = x; + self.y = y; + self + } - return Viewport { viewport }; + /// Builds a viewport. + pub fn build(self, width: u32, height: u32) -> Viewport { + Viewport { + x: self.x.max(0) as u32, + y: self.y.max(0) as u32, + width, + height, + min_depth: self.min_depth, + max_depth: self.max_depth, + } } } diff --git a/crates/lambda-rs/src/render/window.rs b/crates/lambda-rs/src/render/window.rs index 9d20ad1e..c153803c 100644 --- a/crates/lambda-rs/src/render/window.rs +++ b/crates/lambda-rs/src/render/window.rs @@ -1,10 +1,7 @@ //! Window implementation for rendering applications. use lambda_platform::winit::{ - Loop, - WindowHandle, - WindowHandleBuilder, - WindowProperties, + Loop, WindowHandle, WindowHandleBuilder, WindowProperties, }; use crate::events::Events; diff --git a/crates/lambda-rs/src/runtimes/application.rs b/crates/lambda-rs/src/runtimes/application.rs index 5bd3dbb4..2f0069a2 100644 --- a/crates/lambda-rs/src/runtimes/application.rs +++ b/crates/lambda-rs/src/runtimes/application.rs @@ -6,34 +6,22 @@ use std::time::Instant; use lambda_platform::winit::{ winit_exports::{ - ElementState, - Event as WinitEvent, - MouseButton, + ElementState, Event as WinitEvent, KeyCode as WinitKeyCode, + KeyEvent as WinitKeyEvent, MouseButton, PhysicalKey as WinitPhysicalKey, WindowEvent as WinitWindowEvent, }, - Loop, - LoopBuilder, + Loop, LoopBuilder, }; use logging; use crate::{ component::Component, events::{ - Button, - ComponentEvent, - Events, - Key, - Mouse, - RuntimeEvent, - WindowEvent, + Button, ComponentEvent, Events, Key, Mouse, RuntimeEvent, WindowEvent, }, render::{ - window::{ - Window, - WindowBuilder, - }, - RenderContext, - RenderContextBuilder, + window::{Window, WindowBuilder}, + RenderContext, RenderContextBuilder, }, runtime::Runtime, }; @@ -107,20 +95,12 @@ impl ApplicationRuntimeBuilder { /// component stack that allows components to be dynamically pushed into the /// Kernel to receive events & render access. pub fn build(self) -> ApplicationRuntime { - let name = self.app_name; - let mut event_loop = LoopBuilder::new().build(); - let window = self.window_builder.build(&mut event_loop); - - let component_stack = self.components; - let render_context = self.render_context_builder.build(&window); - - return ApplicationRuntime { - name, - event_loop, - window, - render_context, - component_stack, - }; + ApplicationRuntime { + name: self.app_name, + render_context_builder: self.render_context_builder, + window_builder: self.window_builder, + component_stack: self.components, + } } } @@ -128,10 +108,9 @@ impl ApplicationRuntimeBuilder { /// scene on the primary GPU across Windows, MacOS, and Linux. pub struct ApplicationRuntime { name: String, - event_loop: Loop, - window: Window, + render_context_builder: RenderContextBuilder, + window_builder: WindowBuilder, component_stack: Vec>>, - render_context: RenderContext, } impl ApplicationRuntime {} @@ -142,16 +121,11 @@ impl Runtime<(), String> for ApplicationRuntime { /// of all components, the windowing the render context, and anything /// else relevant to the runtime. fn run(self) -> Result<(), String> { - // Decompose Runtime components to transfer ownership from the runtime to - // the event loop closure which will run until the app is closed. - let ApplicationRuntime { - window, - mut event_loop, - mut component_stack, - name, - render_context, - } = self; - + let name = self.name; + let mut event_loop = LoopBuilder::new().build(); + let window = self.window_builder.build(&mut event_loop); + let mut component_stack = self.component_stack; + let mut render_context = self.render_context_builder.build(&window); let mut active_render_context = Some(render_context); let publisher = event_loop.create_event_publisher(); @@ -163,12 +137,12 @@ impl Runtime<(), String> for ApplicationRuntime { let mut current_frame = Instant::now(); let mut runtime_result: Box> = Box::new(Ok(())); - event_loop.run_forever(move |event, _, control_flow| { + event_loop.run_forever(move |event, target| { let mapped_event: Option = match event { WinitEvent::WindowEvent { event, .. } => match event { WinitWindowEvent::CloseRequested => { // Issue a Shutdown event to deallocate resources and clean up. - control_flow.set_exit(); + target.exit(); Some(Events::Runtime { event: RuntimeEvent::Shutdown, issued_at: Instant::now(), @@ -188,56 +162,51 @@ impl Runtime<(), String> for ApplicationRuntime { issued_at: Instant::now(), }) } - WinitWindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - active_render_context - .as_mut() - .unwrap() - .resize(new_inner_size.width, new_inner_size.height); - - Some(Events::Window { - event: WindowEvent::Resize { - width: new_inner_size.width, - height: new_inner_size.height, - }, - issued_at: Instant::now(), - }) - } + WinitWindowEvent::ScaleFactorChanged { .. } => None, WinitWindowEvent::Moved(_) => None, WinitWindowEvent::Destroyed => None, WinitWindowEvent::DroppedFile(_) => None, WinitWindowEvent::HoveredFile(_) => None, WinitWindowEvent::HoveredFileCancelled => None, - WinitWindowEvent::ReceivedCharacter(_) => None, + // Character input is delivered via IME; ignore here for now WinitWindowEvent::Focused(_) => None, WinitWindowEvent::KeyboardInput { - device_id: _, - input, + event: key_event, is_synthetic, - } => match (input.state, is_synthetic) { - (ElementState::Pressed, false) => Some(Events::Keyboard { - event: Key::Pressed { - scan_code: input.scancode, - virtual_key: input.virtual_keycode, - }, - issued_at: Instant::now(), - }), - (ElementState::Released, false) => Some(Events::Keyboard { - event: Key::Released { - scan_code: input.scancode, - virtual_key: input.virtual_keycode, - }, - issued_at: Instant::now(), - }), - _ => { - logging::warn!("Unhandled synthetic keyboard event: {:?}", input); - None + .. + } => match (key_event.state, is_synthetic) { + (ElementState::Pressed, false) => { + let (scan_code, virtual_key) = match key_event.physical_key { + WinitPhysicalKey::Code(code) => (0, Some(code)), + _ => (0, None), + }; + Some(Events::Keyboard { + event: Key::Pressed { + scan_code, + virtual_key, + }, + issued_at: Instant::now(), + }) + } + (ElementState::Released, false) => { + let (scan_code, virtual_key) = match key_event.physical_key { + WinitPhysicalKey::Code(code) => (0, Some(code)), + _ => (0, None), + }; + Some(Events::Keyboard { + event: Key::Released { + scan_code, + virtual_key, + }, + issued_at: Instant::now(), + }) } + _ => None, }, WinitWindowEvent::ModifiersChanged(_) => None, WinitWindowEvent::CursorMoved { - device_id, + device_id: _, position, - modifiers, } => Some(Events::Mouse { event: Mouse::Moved { x: position.x, @@ -248,30 +217,30 @@ impl Runtime<(), String> for ApplicationRuntime { }, issued_at: Instant::now(), }), - WinitWindowEvent::CursorEntered { device_id } => { + WinitWindowEvent::CursorEntered { device_id: _ } => { Some(Events::Mouse { event: Mouse::EnteredWindow { device_id: 0 }, issued_at: Instant::now(), }) } - WinitWindowEvent::CursorLeft { device_id } => Some(Events::Mouse { - event: Mouse::LeftWindow { device_id: 0 }, - issued_at: Instant::now(), - }), + WinitWindowEvent::CursorLeft { device_id: _ } => { + Some(Events::Mouse { + event: Mouse::LeftWindow { device_id: 0 }, + issued_at: Instant::now(), + }) + } WinitWindowEvent::MouseWheel { - device_id, - delta, - phase, - modifiers, + device_id: _, + delta: _, + phase: _, } => Some(Events::Mouse { event: Mouse::Scrolled { device_id: 0 }, issued_at: Instant::now(), }), WinitWindowEvent::MouseInput { - device_id, + device_id: _, state, button, - modifiers, } => { // Map winit button to our button type let button = match button { @@ -279,6 +248,8 @@ impl Runtime<(), String> for ApplicationRuntime { MouseButton::Right => Button::Right, MouseButton::Middle => Button::Middle, MouseButton::Other(other) => Button::Other(other), + MouseButton::Back => Button::Other(8), + MouseButton::Forward => Button::Other(9), }; let event = match state { @@ -301,21 +272,13 @@ impl Runtime<(), String> for ApplicationRuntime { issued_at: Instant::now(), }) } - WinitWindowEvent::TouchpadPressure { - device_id, - pressure, - stage, - } => None, - WinitWindowEvent::AxisMotion { - device_id, - axis, - value, - } => None, + WinitWindowEvent::TouchpadPressure { .. } => None, + WinitWindowEvent::AxisMotion { .. } => None, WinitWindowEvent::Touch(_) => None, WinitWindowEvent::ThemeChanged(_) => None, _ => None, }, - WinitEvent::MainEventsCleared => { + WinitEvent::AboutToWait => { let last_frame = current_frame.clone(); current_frame = Instant::now(); let duration = ¤t_frame.duration_since(last_frame); @@ -345,7 +308,7 @@ impl Runtime<(), String> for ApplicationRuntime { None } - WinitEvent::RedrawRequested(_) => None, + // Redraw requests are handled implicitly when AboutToWait fires; ignore explicit requests WinitEvent::NewEvents(_) => None, WinitEvent::DeviceEvent { device_id, event } => None, WinitEvent::UserEvent(lambda_event) => match lambda_event { @@ -376,8 +339,9 @@ impl Runtime<(), String> for ApplicationRuntime { }, WinitEvent::Suspended => None, WinitEvent::Resumed => None, - WinitEvent::RedrawEventsCleared => None, - WinitEvent::LoopDestroyed => { + WinitEvent::MemoryWarning => None, + // No RedrawEventsCleared in winit 0.29 + WinitEvent::LoopExiting => { active_render_context .take() .expect("[ERROR] The render API has been already taken.") diff --git a/crates/lambda-rs/src/runtimes/mod.rs b/crates/lambda-rs/src/runtimes/mod.rs index eb459f59..103b6c11 100644 --- a/crates/lambda-rs/src/runtimes/mod.rs +++ b/crates/lambda-rs/src/runtimes/mod.rs @@ -1,5 +1,2 @@ pub mod application; -pub use application::{ - ApplicationRuntime, - ApplicationRuntimeBuilder, -}; +pub use application::{ApplicationRuntime, ApplicationRuntimeBuilder}; diff --git a/docs/WGPU.md b/docs/WGPU.md new file mode 100644 index 00000000..8043d9c8 --- /dev/null +++ b/docs/WGPU.md @@ -0,0 +1,116 @@ +# WGPU Support Overview + +Lambda now ships with an experimental, opt-in rendering path implemented on top of [`wgpu`](https://github.com/gfx-rs/wgpu). This document walks through the changes introduced, how to enable the new feature, and provides code samples for building wgpu-powered experiences. + +## Feature Summary +- Added `wgpu` and `pollster` as optional dependencies of `lambda-rs-platform`, plus new feature flags (`with-wgpu`, `with-wgpu-vulkan`, `with-wgpu-metal`, `with-wgpu-dx12`, `with-wgpu-gl`). +- Introduced `lambda_platform::wgpu`, a thin wrapper that mirrors the existing gfx-hal builders (instance, surface, GPU, frame acquisition) and exposes the `wgpu` types when the feature is enabled. +- Exposed `lambda::render::wgpu`, providing a high-level `ContextBuilder` and `RenderContext` so applications can render by issuing closures instead of manually managing swapchains. +- Added `lambda::runtimes::wgpu::WgpuRuntimeBuilder`, a runtime implementation that integrates the new context with Lambda's existing event loop and component model. +- Shipped a `wgpu_clear` example demonstrating a minimal runtime-driven wgpu application. + +## Enabling WGPU +1. Opt into the feature on the `lambda` crate: + ```bash + cargo run --example wgpu_clear --features lambda-rs/with-wgpu + ``` + You can also add the feature in your own `Cargo.toml`: + ```toml + [dependencies] + lambda = { version = "2023.1.30", features = ["with-wgpu"] } + ``` +2. Choose a backend feature if you need more control (e.g. `with-wgpu-metal` on macOS). +3. Recent versions of `wgpu` depend on building `shaderc`; ensure CMake ≥ 3.5 and Ninja are installed. On macOS: + ```bash + brew install cmake ninja + ``` + +## Key APIs +### Platform Builders +```rust +use lambda_platform::wgpu::{InstanceBuilder, SurfaceBuilder, GpuBuilder}; + +let instance = InstanceBuilder::new() + .with_label("Lambda Instance") + .build(); + +let mut surface = SurfaceBuilder::new() + .with_label("Lambda Surface") + .build(&instance, window_handle)?; + +let gpu = GpuBuilder::new() + .with_label("Lambda Device") + .build(&instance, Some(&surface))?; +``` + +### Render Context +```rust +use lambda::render::wgpu::ContextBuilder; + +let mut context = ContextBuilder::new() + .with_present_mode(wgpu::types::PresentMode::Fifo) + .with_texture_usage(wgpu::types::TextureUsages::RENDER_ATTACHMENT) + .build(&window)?; + +context.render(|device, queue, view, encoder| { + let mut pass = encoder.begin_render_pass(&wgpu::types::RenderPassDescriptor { + label: Some("lambda-wgpu-pass"), + color_attachments: &[Some(wgpu::types::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::types::Operations { + load: wgpu::types::LoadOp::Clear(wgpu::types::Color::BLACK), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + drop(pass); +}); +``` + +### Runtime Integration +```rust +use lambda::{ + runtime::start_runtime, + runtimes::wgpu::WgpuRuntimeBuilder, +}; + +let runtime = WgpuRuntimeBuilder::new("Lambda WGPU App") + .with_window_configured_as(|builder| builder.with_dimensions(960, 600)) + .with_render_callback(|_, _, view, encoder| { + let mut pass = encoder.begin_render_pass(&wgpu::types::RenderPassDescriptor { + label: Some("lambda-wgpu-clear"), + color_attachments: &[Some(wgpu::types::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::types::Operations { + load: wgpu::types::LoadOp::Clear(wgpu::types::Color { + r: 0.15, + g: 0.25, + b: 0.45, + a: 1.0, + }), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + drop(pass); + Ok(()) + }) + .build()?; + +start_runtime(runtime); +``` + +## Testing & Examples +- Unit tests cover builder defaults and error conversions for both platform and render layers (`cargo test --features lambda-rs/with-wgpu`). +- Run the sample: `cargo run --example wgpu_clear --features lambda-rs/with-wgpu`. +- If `cargo` fails while compiling `shaderc`, install/update CMake ≥ 3.5 or use a prebuilt shaderc toolchain. + +## Migration Notes +- Existing gfx-hal paths remain the default; no code changes are required for current applications unless opting into wgpu. +- The feature set targets wgpu 23.x to align with the current `shaderc` toolchain. Future upgrades may require revisiting dependency versions or enabling wgpu's native shader translation features. + +For further questions or suggestions, please open an issue in the repository. diff --git a/docs/feature_roadmap_and_snippets.md b/docs/feature_roadmap_and_snippets.md new file mode 100644 index 00000000..d2e5b7a0 --- /dev/null +++ b/docs/feature_roadmap_and_snippets.md @@ -0,0 +1,249 @@ +# Lambda RS: Immediate Feature Ideas + Example APIs + +This document proposes high‑impact features to add next to the Lambda RS +rendering layer and shows what each would look like from user code. + +The proposed APIs follow existing builder/command patterns and keep the +render‑pass‑centric model intact. + +## 1) Uniform Buffers and Bind Groups + +Why: Push constants are convenient but limited. Uniform buffers support larger +constant data, structured layouts, and compatibility with all adapters. + +API sketch: + +```rust +// New types +use lambda::render::{ + bind::{BindGroupLayoutBuilder, BindGroupBuilder, Binding}, + buffer::{BufferBuilder, Usage, Properties}, +}; + +// Layout: set(0) has a uniform buffer at binding(0) +let layout = BindGroupLayoutBuilder::new() + .with_uniform(binding = 0, visibility = PipelineStage::VERTEX) + .build(&mut rc); + +// Create and upload a uniform buffer +let ubo = BufferBuilder::new() + .with_length(std::mem::size_of::()) + .with_usage(Usage::UNIFORM) + .with_properties(Properties::CPU_VISIBLE) + .with_label("globals") + .build(&mut rc, vec![initial_globals])?; + +// Bind group that points the layout(0)@binding(0) to our UBO +let group0 = BindGroupBuilder::new() + .with_layout(&layout) + .with_uniform(binding = 0, &ubo) + .build(&mut rc); + +// Pipeline accepts optional bind group layouts +let pipe = RenderPipelineBuilder::new() + .with_layouts(&[&layout]) + .with_buffer(vbo, attributes) + .build(&mut rc, &pass, &vs, Some(&fs)); + +// Commands inside a render pass +RC::SetPipeline { pipeline: pipe_id }, +RC::BindGroup { set: 0, group: group0_id, offsets: &[] }, +RC::Draw { vertices: 0..3 }, +``` + +## 2) Textures + Samplers (sampling in fragment shader) + +Why: Texture rendering is foundational (images, sprites, materials). + +API sketch: + +```rust +use lambda::render::texture::{TextureBuilder, SamplerBuilder, TextureFormat}; + +let texture = TextureBuilder::new_2d(TextureFormat::Rgba8UnormSrgb) + .with_size(512, 512) + .with_data(&pixels) + .with_label("albedo") + .build(&mut rc); + +let sampler = SamplerBuilder::new() + .linear_clamp() + .build(&mut rc); + +// Layout: binding(0) uniform buffer, binding(1) sampled texture, binding(2) sampler +let layout = BindGroupLayoutBuilder::new() + .with_uniform(0, PipelineStage::VERTEX | PipelineStage::FRAGMENT) + .with_sampled_texture(1) + .with_sampler(2) + .build(&mut rc); + +let group = BindGroupBuilder::new() + .with_layout(&layout) + .with_uniform(0, &ubo) + .with_texture(1, &texture) + .with_sampler(2, &sampler) + .build(&mut rc); + +RC::BindGroup { set: 0, group: group_id, offsets: &[] }, +``` + +## 3) Depth/Stencil and MSAA + +Why: 3D scenes and high‑quality rasterization. + +API sketch: + +```rust +use lambda_platform::wgpu::types as wgpu; + +let pass = RenderPassBuilder::new() + .with_clear_color(wgpu::Color::BLACK) + .with_depth_stencil( + depth_format = wgpu::TextureFormat::Depth32Float, + depth_clear = 1.0, + depth_write = true, + depth_compare = wgpu::CompareFunction::Less, + ) + .with_msaa(samples = 4) + .build(&rc); + +let pipe = RenderPipelineBuilder::new() + .with_msaa(samples = 4) + .with_depth_format(wgpu::TextureFormat::Depth32Float) + .build(&mut rc, &pass, &vs, Some(&fs)); +``` + +## 4) Indexed Draw + Multiple Vertex Buffers + +Why: Standard practice for meshes. + +API sketch: + +```rust +// New commands +RC::BindIndexBuffer { buffer: ibo_id, format: IndexFormat::Uint32 }, +RC::DrawIndexed { indices: 0..index_count, base_vertex: 0, instances: 0..1 }, + +// Pipeline builder already accepts multiple vertex buffers; extend examples to show slot 1,2… +``` + +## 5) Multi‑pass: Offscreen (Render‑to‑Texture) + +Why: Post‑processing, shadow maps, deferred rendering. + +API sketch: + +```rust +use lambda::render::target::{RenderTargetBuilder}; + +let offscreen = RenderTargetBuilder::new() + .with_color(TextureFormat::Rgba8UnormSrgb, width, height) + .with_depth(TextureFormat::Depth32Float) + .with_label("offscreen") + .build(&mut rc); + +// Pass 1: draw scene into `offscreen` +let p1 = RenderPassBuilder::new().with_target(&offscreen).build(&rc); + +// Pass 2: sample offscreen color into swapchain +let p2 = RenderPassBuilder::new().build(&rc); + +// Commands +RC::BeginRenderPass { render_pass: p1_id, viewport }, +// ... draw scene ... +RC::EndRenderPass, +RC::BeginRenderPass { render_pass: p2_id, viewport }, +RC::BindGroup { set: 0, group: fullscreen_group, offsets: &[] }, +RC::Draw { vertices: 0..3 }, +RC::EndRenderPass, +``` + +## 6) Compute Pipelines + +Why: GPU compute for general processing. + +API sketch: + +```rust +use lambda::render::compute::{ComputePipelineBuilder, ComputeCommand}; + +let cs = compile_shader("…", ShaderKind::Compute); +let pipe = ComputePipelineBuilder::new().build(&mut rc, &cs); + +// Dispatch after binding resources +let cmds = vec![ + ComputeCommand::Begin, + ComputeCommand::BindGroup { set: 0, group: g0, offsets: &[] }, + ComputeCommand::Dispatch { x: 64, y: 1, z: 1 }, + ComputeCommand::End, +]; +``` + +## 7) WGSL Support in Shader Builder + +Why: Native shader language for wgpu; fewer translation pitfalls. + +API sketch: + +```rust +let wgsl = VirtualShader::Source { + source: include_str!("shaders/triangle.wgsl").into(), + kind: ShaderKind::Vertex, // or a new `ShaderKind::Wgsl` variant + name: "triangle".into(), + entry_point: "vs_main".into(), +}; +let vs = ShaderBuilder::new().build(wgsl); +``` + +## 8) Shader Hot‑Reloading + +Why: Faster iteration during development. + +API sketch: + +```rust +let mut watcher = ShaderWatcher::new().watch_file("shaders/triangle.vert"); +if watcher.changed() { + let new_vs = shader_builder.build(VirtualShader::File { path: …, kind: …, name: …, entry_point: … }); + pipeline.replace_vertex_shader(&mut rc, new_vs); +} +``` + +## 9) Cameras and Transforms Helpers + +Why: Common math boilerplate for 2D/3D. + +API sketch: + +```rust +let camera = Camera::perspective( + fov_radians = 60f32.to_radians(), aspect = width as f32 / height as f32, + near = 0.1, far = 100.0, +).look_at(eye, center, up); + +let ubo = Globals { view_proj: camera.view_proj() }; +``` + +## 10) Input Mapping Layer + +Why: Stable input names independent of layouts. + +API sketch: + +```rust +if input.pressed(Action::MoveForward) { position.z -= speed; } + +// Configure once +InputMap::new() + .bind(Action::MoveForward, KeyCode::KeyW) + .bind(Action::MoveLeft, KeyCode::KeyA) + .install(); +``` + +--- + +These proposals aim to keep Lambda’s surface area small while unlocking common +workflows (texturing, uniforms, depth/MSAA, compute, multipass). I can begin +implementing any of them next; uniform buffers/bind groups and depth/MSAA are +usually the quickest wins for examples and demos. + diff --git a/docs/rendering.md b/docs/rendering.md new file mode 100644 index 00000000..9bc0644a --- /dev/null +++ b/docs/rendering.md @@ -0,0 +1,215 @@ +# Lambda RS Rendering Guide (wgpu backend) + +This guide shows how to build windows, compile shaders, create pipelines, +upload vertex data, and submit draw commands using Lambda RS with the wgpu +backend (default). + +The examples below mirror the code in `crates/lambda-rs/examples/` and are +intended as a concise reference. + +## Prerequisites + +- Rust 1.70+ +- Run `scripts/setup.sh` once (git hooks, git-lfs) +- Build: `cargo build --workspace` +- Test: `cargo test --workspace` + +## Features and Backends + +Lambda uses `wgpu` by default. You can select specific platform backends via +crate features: + +- `with-wgpu` (default) +- `with-wgpu-vulkan`, `with-wgpu-metal`, `with-wgpu-dx12`, `with-wgpu-gl` +- Optional shader backends: `with-shaderc`, + `with-shaderc-build-from-source` + +In your `Cargo.toml` (consumer project): + +```toml +[dependencies] +lambda-rs = { path = "crates/lambda-rs", features = ["with-wgpu-metal"] } +``` + +## Runtime + Window + +Create a windowed runtime and start it: + +```rust +use lambda::{runtime::start_runtime, runtimes::ApplicationRuntimeBuilder}; + +fn main() { + let runtime = ApplicationRuntimeBuilder::new("Minimal App") + .with_window_configured_as(|w| w.with_dimensions(800, 600).with_name("Window")) + .build(); + + start_runtime(runtime); +} +``` + +## Components: lifecycle and events + +Implement `Component` to receive events and render access: + +```rust +use lambda::{ + component::Component, + events::{Events, WindowEvent}, + render::{command::RenderCommand, RenderContext}, +}; + +#[derive(Default)] +struct MyComponent { width: u32, height: u32 } + +impl Component<(), String> for MyComponent { + fn on_attach(&mut self, _rc: &mut RenderContext) -> Result<(), String> { Ok(()) } + fn on_detach(&mut self, _rc: &mut RenderContext) -> Result<(), String> { Ok(()) } + + fn on_event(&mut self, e: Events) -> Result<(), String> { + if let Events::Window { event: WindowEvent::Resize { width, height }, .. } = e { + self.width = width; self.height = height; + } + Ok(()) + } + + fn on_update(&mut self, _dt: &std::time::Duration) -> Result<(), String> { Ok(()) } + + fn on_render(&mut self, _rc: &mut RenderContext) -> Vec { + Vec::new() + } +} +``` + +Attach your component via the builder’s `with_component` method. + +## Shaders (GLSL via Naga) + +Compile GLSL into SPIR-V at runtime using the default Naga backend: + +```rust +use lambda::render::shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}; + +let vs_src = VirtualShader::Source { + source: include_str!("../assets/shaders/triangle.vert").to_string(), + kind: ShaderKind::Vertex, + name: "triangle".into(), + entry_point: "main".into(), +}; +let fs_src = VirtualShader::Source { + source: include_str!("../assets/shaders/triangle.frag").to_string(), + kind: ShaderKind::Fragment, + name: "triangle".into(), + entry_point: "main".into(), +}; + +let mut shader_builder = ShaderBuilder::new(); +let vs: Shader = shader_builder.build(vs_src); +let fs: Shader = shader_builder.build(fs_src); +``` + +## Render Pass + +Create a pass with a clear color: + +```rust +use lambda::render::render_pass::RenderPassBuilder; +use lambda_platform::wgpu::types as wgpu; + +let pass = RenderPassBuilder::new() + .with_clear_color(wgpu::Color { r: 0.02, g: 0.02, b: 0.06, a: 1.0 }) + .build(&render_context); +``` + +## Vertex Data: Mesh and Buffer + +Build a mesh and upload to GPU: + +```rust +use lambda::render::{ + buffer::BufferBuilder, + mesh::MeshBuilder, + vertex::{VertexAttribute, VertexElement}, + ColorFormat, +}; + +let mut mesh = MeshBuilder::new(); +// Push vertices by builder; positions/colors shown +// ... populate mesh ... + +let attrs = vec![ + VertexAttribute { location: 0, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 0 }}, + VertexAttribute { location: 2, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 24}}, +]; +mesh = mesh.with_attributes(attrs).build(); + +let vbo = BufferBuilder::build_from_mesh(&mesh, &mut render_context) + .expect("failed to create VBO"); +``` + +## Pipeline + Push Constants + +Create a pipeline, with optional push constants: + +```rust +use lambda::render::pipeline::{RenderPipelineBuilder, PipelineStage}; + +let pipeline = RenderPipelineBuilder::new() + .with_push_constant(PipelineStage::VERTEX, 64) // size in bytes + .with_buffer(vbo, mesh.attributes().to_vec()) + .build(&mut render_context, &pass, &vs, Some(&fs)); + +let pipeline_id = render_context.attach_pipeline(pipeline); +let pass_id = render_context.attach_render_pass(pass); +``` + +## Viewport and Scissor + +```rust +use lambda::render::viewport::ViewportBuilder; + +let vp = ViewportBuilder::new().build(width, height); +``` + +## Submitting Draw Commands + +Important: All state (pipeline, viewport, scissors, buffers, push constants, +draw) must be recorded inside a render pass. + +```rust +use lambda::render::command::RenderCommand as RC; + +let cmds = vec![ + RC::BeginRenderPass { render_pass: pass_id, viewport: vp.clone() }, + RC::SetPipeline { pipeline: pipeline_id }, + RC::SetViewports { start_at: 0, viewports: vec![vp.clone()] }, + RC::SetScissors { start_at: 0, viewports: vec![vp.clone()] }, + RC::BindVertexBuffer { pipeline: pipeline_id, buffer: 0 }, + RC::PushConstants { pipeline: pipeline_id, stage: PipelineStage::VERTEX, offset: 0, bytes: vec![0u32; 16] }, + RC::Draw { vertices: 0..3 }, + RC::EndRenderPass, +]; + +render_context.render(cmds); +``` + +## Resizing + +When you receive a window resize event, the runtime already reconfigures the +surface; your component can track width/height for viewport setup: + +```rust +if let Events::Window { event: WindowEvent::Resize { width, height }, .. } = e { + self.width = width; self.height = height; +} +``` + +## Notes and Tips + +- The engine enables `wgpu::Features::PUSH_CONSTANTS` for convenience. + If your adapter doesn’t support push constants, consider a uniform buffer + fallback. +- Shaders are compiled from GLSL via Naga; you can switch to `shaderc` with + the feature flag `with-shaderc`. +- All examples live under `crates/lambda-rs/examples/` and are runnable via: + `cargo run -p lambda-rs --example ` + diff --git a/tools/obj_loader/src/main.rs b/tools/obj_loader/src/main.rs index 6801ca74..d0a76abc 100644 --- a/tools/obj_loader/src/main.rs +++ b/tools/obj_loader/src/main.rs @@ -1,55 +1,25 @@ use std::env; use args::{ - Argument, - ArgumentParser, - ArgumentType, - ArgumentValue, - ParsedArgument, + Argument, ArgumentParser, ArgumentType, ArgumentValue, ParsedArgument, }; use lambda::{ component::Component, - events::{ - ComponentEvent, - Events, - WindowEvent, - }, + events::{ComponentEvent, Events, WindowEvent}, logging, - math::matrix::{ - self, - Matrix, - }, + math::matrix::{self, Matrix}, render::{ buffer::BufferBuilder, command::RenderCommand, - mesh::{ - Mesh, - MeshBuilder, - }, - pipeline::{ - PipelineStage, - RenderPipelineBuilder, - }, + mesh::{Mesh, MeshBuilder}, + pipeline::{PipelineStage, RenderPipelineBuilder}, render_pass::RenderPassBuilder, - shader::{ - Shader, - ShaderBuilder, - ShaderKind, - VirtualShader, - }, - vertex::{ - Vertex, - VertexAttribute, - VertexElement, - }, - viewport, - ResourceId, + shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, + vertex::{Vertex, VertexAttribute, VertexElement}, + viewport, ResourceId, }, runtime::start_runtime, - runtimes::{ - application::ComponentResult, - ApplicationRuntimeBuilder, - }, + runtimes::{application::ComponentResult, ApplicationRuntimeBuilder}, }; // ------------------------------ SHADER SOURCE -------------------------------- From 4d0c7b1ee72669dbdd1c5c00a95b1c69d41a92dd Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 23 Sep 2025 22:11:40 -0700 Subject: [PATCH 02/10] [update] workflows to run tests using wgpu & to build examples. --- .github/workflows/compile_lambda_rs.yml | 49 +++++++++++----------- .github/workflows/lambda-repo-security.yml | 24 ++++++----- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/.github/workflows/compile_lambda_rs.yml b/.github/workflows/compile_lambda_rs.yml index fc6851d2..11a7f9e1 100644 --- a/.github/workflows/compile_lambda_rs.yml +++ b/.github/workflows/compile_lambda_rs.yml @@ -1,4 +1,4 @@ -name: compile & test lambda-rs +name: compile & test lambda-rs (wgpu) on: push: @@ -21,53 +21,52 @@ jobs: matrix: include: - os: ubuntu-latest - rustup-toolchain: "stable" - features: "lambda-rs/with-opengl" - - os: ubuntu-latest - rustup-toolchain: "stable" - features: "lambda-rs/with-vulkan" - - os: windows-latest rustup-toolchain: "stable" features: "lambda-rs/with-vulkan" - - os: windows-latest - rustup-toolchain: "stable" - features: "lambda-rs/with-dx11" - os: windows-latest rustup-toolchain: "stable" features: "lambda-rs/with-dx12" - - os: macos-latest - rustup-toolchain: "stable" - features: "lambda-rs/with-opengl" - os: macos-latest rustup-toolchain: "stable" features: "lambda-rs/with-metal" steps: - name: Checkout Repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 + + - name: Cache cargo builds + uses: Swatinem/rust-cache@v2 - name: Run the projects setup. run: ./scripts/setup.sh --within-ci true - - name: Obtain Xorg for building on Ubuntu. + - name: Install Linux deps for winit/wgpu if: ${{ matrix.os == 'ubuntu-latest' }} - run: sudo apt-get update && sudo apt-get install xorg-dev - - - name: Add msbuild to PATH - if: ${{ matrix.os == 'windows-latest' }} - uses: microsoft/setup-msbuild@v1.0.2 + run: | + sudo apt-get update + sudo apt-get install -y \ + pkg-config libx11-dev libxcb1-dev libxcb-render0-dev \ + libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \ + libwayland-dev libudev-dev libvulkan-dev - - name: Install ninja 1.10.2 on windows. - if: ${{ matrix.os == 'windows-latest' }} - run: choco install ninja + # Windows runners already include the required toolchain for DX12 builds. - name: Obtain rust toolchain for ${{ matrix.rustup-toolchain }} run: | rustup toolchain install ${{ matrix.rustup-toolchain }} rustup default ${{ matrix.rustup-toolchain }} - - name: Build Lambda & other default workspace members. - run: cargo test --all --features ${{ matrix.features }} --no-default-features + - name: Check formatting + run: cargo fmt --all --check + + - name: Build workspace + run: cargo build --workspace --features ${{ matrix.features }} --no-default-features + + - name: Build examples (lambda-rs) + run: cargo build -p lambda-rs --examples --features ${{ matrix.features }} --no-default-features + + - name: Test workspace + run: cargo test --workspace --features ${{ matrix.features }} --no-default-features - uses: actions/setup-ruby@v1 - name: Send Webhook Notification for build status. diff --git a/.github/workflows/lambda-repo-security.yml b/.github/workflows/lambda-repo-security.yml index a78ab27b..e60250d1 100644 --- a/.github/workflows/lambda-repo-security.yml +++ b/.github/workflows/lambda-repo-security.yml @@ -7,7 +7,7 @@ # More details at https://github.com/rust-lang/rust-clippy # and https://rust-lang.github.io/rust-clippy/ -name: rust-clippy analyze +name: rust fmt + clippy analyze on: push: @@ -28,28 +28,30 @@ jobs: actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 + + - name: Cache cargo builds + uses: Swatinem/rust-cache@v2 - name: Install Rust toolchain - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable components: clippy - override: true - name: Install dependencies for converting clippy output to SARIF run: cargo install clippy-sarif sarif-fmt + - name: Check formatting + run: cargo fmt --all --check + - name: Run rust-clippy - run: - cargo clippy - --all-features - --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt + run: | + cargo clippy --workspace --all-targets --all-features --message-format=json -- -D warnings \ + | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt continue-on-error: true - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: rust-clippy-results.sarif wait-for-processing: true From 2e7a3abcf60a780fa6bf089ca8a6f4124e60f660 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 23 Sep 2025 22:49:22 -0700 Subject: [PATCH 03/10] [update] resolver version and do not remove default features. --- .github/workflows/compile_lambda_rs.yml | 6 +++--- Cargo.toml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compile_lambda_rs.yml b/.github/workflows/compile_lambda_rs.yml index 11a7f9e1..a909810d 100644 --- a/.github/workflows/compile_lambda_rs.yml +++ b/.github/workflows/compile_lambda_rs.yml @@ -60,13 +60,13 @@ jobs: run: cargo fmt --all --check - name: Build workspace - run: cargo build --workspace --features ${{ matrix.features }} --no-default-features + run: cargo build --workspace --features ${{ matrix.features }} - name: Build examples (lambda-rs) - run: cargo build -p lambda-rs --examples --features ${{ matrix.features }} --no-default-features + run: cargo build -p lambda-rs --examples --features ${{ matrix.features }} - name: Test workspace - run: cargo test --workspace --features ${{ matrix.features }} --no-default-features + run: cargo test --workspace --features ${{ matrix.features }} - uses: actions/setup-ruby@v1 - name: Send Webhook Notification for build status. diff --git a/Cargo.toml b/Cargo.toml index f8a4c452..4776ace2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" # Sub packages provided by lambda. members = [ From 23b10190731dcfe8735cfeeea82c79579f840000 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 26 Sep 2025 13:01:21 -0700 Subject: [PATCH 04/10] [update] roadmap documentation --- docs/game_roadmap_and_prototype.md | 225 +++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 docs/game_roadmap_and_prototype.md diff --git a/docs/game_roadmap_and_prototype.md b/docs/game_roadmap_and_prototype.md new file mode 100644 index 00000000..769334d9 --- /dev/null +++ b/docs/game_roadmap_and_prototype.md @@ -0,0 +1,225 @@ +--- +title: "Lambda RS: Gaps, Roadmap, and Prototype Plan" +document_id: "game-roadmap-2025-09-24" +status: "living" +created: "2025-09-24T05:09:25Z" +last_updated: "2025-09-26T19:37:55Z" +version: "0.2.0" +engine_workspace_version: "2023.1.30" +wgpu_version: "26.0.1" +shader_backend_default: "naga" +winit_version: "0.29.10" +repo_commit: "2e7a3abcf60a780fa6bf089ca8a6f4124e60f660" +owners: ["lambda-sh"] +reviewers: ["engine", "rendering"] +tags: ["roadmap","games","2d","3d","desktop"] +--- + +# Lambda RS: Gaps, Roadmap, and Prototype Plan + +This document outlines current engine capabilities, the gaps to address for 2D/3D games and desktop apps, concrete API additions, and a step‑by‑step plan to deliver a playable 2D prototype first, followed by a small 3D scene. Code sketches follow Lambda’s existing builder/command style. + +## Architecture Today + +Key modules: windowing/events (winit), GPU (wgpu), render context, runtime loop, and GLSL→SPIR‑V shader compilation (naga). + +Frame flow: +``` +App Components --> ApplicationRuntime --> RenderContext --> wgpu (Device/Queue/Surface) + | | | | + | V V V + | Events/Loop Pass/Pipeline Adapter/Swapchain + V + RenderCommand stream per frame +``` + +Currently supported commands: Begin/EndRenderPass, SetPipeline, SetViewports, SetScissors, BindVertexBuffer, PushConstants, Draw. + +## Gaps to Ship Games + +- Bind groups + uniform/storage buffers (beyond push constants) +- Textures + samplers (images, sprites, materials) +- Depth/stencil and MSAA +- Index buffers + DrawIndexed; multiple vertex buffers; instancing +- Offscreen render targets (multipass) +- 2D layer: sprite batching, atlas loader, ortho camera, text, input mapping +- 3D layer: cameras, transforms, glTF load, basic materials/lighting +- Desktop apps: egui integration; dialogs/clipboard as needed + +## Targeted API Additions (sketches) + +Bind groups and uniforms (value: larger, structured GPU data; portable across adapters; enables cameras/materials): +```rust +// Layout with one uniform buffer at set(0) binding(0) +let layout = BindGroupLayoutBuilder::new() + .with_uniform(0, PipelineStage::VERTEX) + .build(&mut rc); + +let ubo = BufferBuilder::new() + .with_length(std::mem::size_of::()) + .with_usage(Usage::UNIFORM) + .with_properties(Properties::CPU_VISIBLE) + .build(&mut rc, vec![initial_globals])?; + +let group = BindGroupBuilder::new(&layout) + .with_uniform(0, &ubo) + .build(&mut rc); + +let pipe = RenderPipelineBuilder::new() + .with_layouts(&[&layout]) + .with_buffer(vbo, attrs) + .build(&mut rc, &pass, &vs, Some(&fs)); + +// Commands inside a pass +RC::SetPipeline { pipeline: pipe_id }; +RC::SetBindGroup { set: 0, group: group_id, dynamic_offsets: vec![] }; +RC::Draw { vertices: 0..3 }; +``` + +Notes +- UBO vs push constants: UBOs scale to KBs and are supported widely; use for view/projection and per‑frame data. +- Dynamic offsets (optional later) let you pack many small structs into one UBO. + +Textures and samplers (value: sprites, materials, UI images; sRGB correctness): +```rust +let tex = TextureBuilder::new_2d(TextureFormat::Rgba8UnormSrgb) + .with_size(w, h) + .with_data(&pixels) + .build(&mut rc); +let samp = SamplerBuilder::linear_clamp().build(&mut rc); + +let tex_layout = BindGroupLayoutBuilder::new() + .with_sampled_texture(0) + .with_sampler(1) + .build(&mut rc); +let tex_group = BindGroupBuilder::new(&tex_layout) + .with_texture(0, &tex) + .with_sampler(1, &samp) + .build(&mut rc); + +// In fragment shader, sample with: sampler2D + UVs; ensure vertex inputs provide UVs. +// Upload path should convert source assets to sRGB formats when appropriate. +``` + +Index draw and instancing (value: reduce vertex duplication; batch many objects in one draw): +```rust +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 0 }; +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 1 }; // instances +RC::BindIndexBuffer { buffer: ibo_id, format: IndexFormat::Uint16 }; +RC::DrawIndexed { indices: 0..index_count, base_vertex: 0, instances: 0..instance_count }; +``` + +Instance buffer attributes example +```rust +// slot 1: per-instance mat3x2 (2D) packed as 3x vec2, plus tint color +let instance_attrs = vec![ + // location 4..6 for rows + VertexAttribute { location: 4, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 0 }}, + VertexAttribute { location: 5, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 8 }}, + VertexAttribute { location: 6, offset: 0, element: VertexElement { format: ColorFormat::Rgb32Sfloat, offset: 16 }}, + // location 7 tint (RGBA8) + VertexAttribute { location: 7, offset: 0, element: VertexElement { format: ColorFormat::Rgba8Srgb, offset: 24 }}, +]; +``` + +Depth/MSAA (value: correct 3D visibility and improved edge quality): +```rust +let pass = RenderPassBuilder::new() + .with_clear_color(wgpu::Color::BLACK) + .with_depth_stencil(wgpu::TextureFormat::Depth32Float, 1.0, true, wgpu::CompareFunction::Less) + .with_msaa(4) + .build(&rc); + +let pipe = RenderPipelineBuilder::new() + .with_depth_format(wgpu::TextureFormat::Depth32Float) + .build(&mut rc, &pass, &vs, Some(&fs)); +``` + +Notes +- Use reversed‑Z (Greater) later for precision, but start with Less. +- MSAA sample count must match between pass and pipeline. + +Offscreen render targets (value: post‑processing, shadow maps, UI composition, picking): +```rust +let offscreen = RenderTargetBuilder::new() + .with_color(TextureFormat::Rgba8UnormSrgb, width, height) + .with_depth(TextureFormat::Depth32Float) + .build(&mut rc); + +let pass1 = RenderPassBuilder::new().with_target(&offscreen).build(&rc); +let pass2 = RenderPassBuilder::new().build(&rc); // backbuffer + +// Pass 1: draw scene +RC::BeginRenderPass { render_pass: pass1_id, viewport }; +// ... draw 3D scene ... +RC::EndRenderPass; + +// Pass 2: fullscreen triangle sampling offscreen.color +RC::BeginRenderPass { render_pass: pass2_id, viewport }; +RC::SetPipeline { pipeline: post_pipe }; +RC::SetBindGroup { set: 0, group: offscreen_group, dynamic_offsets: vec![] }; +RC::Draw { vertices: 0..3 }; +RC::EndRenderPass; +``` + +WGSL support (value: first‑class wgpu shader language, fewer translation pitfalls): +```rust +let vs = VirtualShader::WgslSource { source: include_str!("shaders/quad.wgsl").into(), name: "quad".into(), entry_point: "vs_main".into() }; +let fs = VirtualShader::WgslSource { source: include_str!("shaders/quad.wgsl").into(), name: "quad".into(), entry_point: "fs_main".into() }; +``` + +Shader hot‑reload (value: faster iteration; no rebuild): watch file timestamps and recompile shaders when changed; swap pipeline modules safely between frames. + +## 2D Prototype (Asteroids‑like) + +Goals: sprite batching via instancing; atlas textures; ortho camera; input mapping; text HUD. Target 60 FPS with 10k sprites (mid‑range GPU). + +Core draw: +```rust +RC::BeginRenderPass { render_pass: pass_id, viewport }; +RC::SetPipeline { pipeline: pipe_id }; +RC::SetBindGroup { set: 0, group: globals_gid, dynamic_offsets: vec![] }; +RC::SetBindGroup { set: 1, group: atlas_gid, dynamic_offsets: vec![] }; +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 0 }; // quad +RC::BindVertexBuffer { pipeline: pipe_id, buffer: 1 }; // instances +RC::BindIndexBuffer { buffer: ibo_id, format: IndexFormat::Uint16 }; +RC::DrawIndexed { indices: 0..6, base_vertex: 0, instances: 0..sprite_count }; +RC::EndRenderPass; +``` + +Building instance data each frame (value: dynamic transforms with minimal overhead): +```rust +// CPU side: update transforms and pack into a Vec +queue.write_buffer(instance_vbo.raw(), 0, bytemuck::cast_slice(&instances)); +``` + +Text rendering options (value: legible UI/HUD): +- Bitmap font atlas: simplest path; pack glyphs into the sprite pipeline. +- glyphon/glyph_brush integration: high‑quality layout; more deps; implement later. + +## 3D Prototype (Orbit Camera + glTF) + +Goals: depth test/write, indexed mesh, textured material, simple lighting; orbit camera. + +Core draw mirrors 2D but with depth enabled and mesh buffers. + +Camera helpers (value: reduce boilerplate and bugs): +```rust +let proj = matrix::perspective_matrix(60f32.to_radians(), width as f32 / height as f32, 0.1, 100.0); +let view = matrix::translation_matrix([0.0, 0.0, -5.0]); // or look_at helper later +let view_proj = proj.multiply(&view); +``` + +glTF loading (value: standard asset path): map glTF meshes/materials/textures to VBO/IBO and bind groups; start with positions/normals/UVs and a single texture. + +## Milestones & Estimates + +- M1 Rendering Foundations (2–3 weeks): bind groups/UBO, textures/samplers, depth/MSAA, indexed draw, instancing. +- M2 2D Systems (2–3 weeks): sprite batching, atlas, 2D camera, input, text; ship prototype. +- M3 3D Systems (3–5 weeks): cameras, glTF, materials/lighting; ship scene. +- M4 Desktop UI (1–2 weeks): egui integration + example app. + +## Changelog + +- 2025-09-26 (v0.2.0) — Expanded examples, added value rationale per feature, offscreen/post, WGSL, and iteration tips. +- 2025-09-24 (v0.1.0) — Initial draft with gaps, roadmap, and prototype plan. From 6fc8f51f221ef37abe711dfad04353a15d91389f Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 26 Sep 2025 13:02:05 -0700 Subject: [PATCH 05/10] [update] wgpu and naga to v26 & update APIs. --- Cargo.lock | 391 +++++++++++++++------- crates/lambda-rs-platform/Cargo.toml | 4 +- crates/lambda-rs-platform/src/wgpu/mod.rs | 25 +- crates/lambda-rs/src/render/mod.rs | 1 + 4 files changed, 287 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bd9310a..0dbea6b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" version = "0.5.2" @@ -119,9 +125,9 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -203,9 +209,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -248,6 +254,9 @@ name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] [[package]] name = "block" @@ -285,6 +294,20 @@ name = "bytemuck" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] [[package]] name = "byteorder" @@ -309,7 +332,7 @@ dependencies = [ "polling", "rustix 0.38.44", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -352,7 +375,7 @@ dependencies = [ "rustsec", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -361,7 +384,7 @@ version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb04b88bd5b2036e30704f95c6ee16f3b5ca3b4ca307da2889d9006648e5c88" dependencies = [ - "petgraph", + "petgraph 0.6.0", "semver 1.0.4", "serde", "toml", @@ -461,6 +484,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.19" @@ -501,10 +530,11 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ + "serde", "termcolor", "unicode-width", ] @@ -549,6 +579,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -562,8 +602,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "foreign-types", "libc", ] @@ -575,7 +615,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.1", "libc", ] @@ -626,6 +677,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "curl" version = "0.4.41" @@ -793,6 +850,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.0.22" @@ -962,9 +1025,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "glow" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -1008,15 +1071,15 @@ checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", - "thiserror", + "thiserror 1.0.69", "windows", ] [[package]] name = "gpu-descriptor" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.0", "gpu-descriptor-types", @@ -1061,6 +1124,17 @@ dependencies = [ "crc32fast", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if 1.0.0", + "crunchy", + "num-traits", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1073,6 +1147,8 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -1206,7 +1282,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -1331,6 +1407,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "libredox" version = "0.1.10" @@ -1388,21 +1470,19 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "malloc_buf" @@ -1476,13 +1556,13 @@ dependencies = [ [[package]] name = "metal" -version = "0.29.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ "bitflags 2.9.0", "block", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types", "log", "objc", @@ -1534,25 +1614,30 @@ dependencies = [ [[package]] name = "naga" -version = "23.1.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" +checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" dependencies = [ "arrayvec", "bit-set", "bitflags 2.9.0", - "cfg_aliases", + "cfg-if 1.0.0", + "cfg_aliases 0.2.1", "codespan-reporting", + "half", + "hashbrown 0.15.2", "hexf-parse", "indexmap 2.9.0", + "libm", "log", - "petgraph", + "num-traits", + "once_cell", + "petgraph 0.8.2", "pp-rs", "rustc-hash", "spirv", - "termcolor", - "thiserror", - "unicode-xid", + "thiserror 2.0.16", + "unicode-ident", ] [[package]] @@ -1564,10 +1649,10 @@ dependencies = [ "bitflags 2.9.0", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1585,6 +1670,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 = "nix" version = "0.23.1" @@ -1632,6 +1726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1760,6 +1855,15 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-float" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -1780,9 +1884,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1790,15 +1894,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.5.17", "smallvec 1.15.1", - "windows-sys 0.36.1", + "windows-targets 0.52.6", ] [[package]] @@ -1819,10 +1923,21 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.0", "indexmap 1.9.1", ] +[[package]] +name = "petgraph" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.2", + "indexmap 2.9.0", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1864,6 +1979,21 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "portable-atomic" +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 = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "pp-rs" version = "0.2.1" @@ -1921,7 +2051,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" dependencies = [ - "thiserror", + "thiserror 1.0.69", "toml", ] @@ -2030,15 +2160,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -2153,7 +2274,7 @@ dependencies = [ "platforms", "semver 1.0.4", "serde", - "thiserror", + "thiserror 1.0.69", "toml", "url", ] @@ -2392,7 +2513,7 @@ dependencies = [ "log", "memmap2", "rustix 0.38.44", - "thiserror", + "thiserror 1.0.69", "wayland-backend", "wayland-client", "wayland-csd-frame", @@ -2525,7 +2646,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", ] [[package]] @@ -2539,6 +2669,17 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -3008,17 +3149,21 @@ dependencies = [ [[package]] name = "wgpu" -version = "23.0.1" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" +checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" dependencies = [ "arrayvec", - "cfg_aliases", + "bitflags 2.9.0", + "cfg-if 1.0.0", + "cfg_aliases 0.2.1", "document-features", + "hashbrown 0.15.2", "js-sys", "log", "naga", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "smallvec 1.15.1", @@ -3033,35 +3178,78 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "23.0.1" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" +checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9" dependencies = [ "arrayvec", + "bit-set", "bit-vec", "bitflags 2.9.0", "bytemuck", - "cfg_aliases", + "cfg_aliases 0.2.1", "document-features", + "hashbrown 0.15.2", "indexmap 2.9.0", "log", "naga", "once_cell", "parking_lot", + "portable-atomic", "profiling", "raw-window-handle", "rustc-hash", "smallvec 1.15.1", - "thiserror", + "thiserror 2.0.16", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", + "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", ] +[[package]] +name = "wgpu-core-deps-apple" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-wasm" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03b9f9e1a50686d315fc6debe4980cc45cd37b0e919351917df494e8fdc8885" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14" +dependencies = [ + "wgpu-hal", +] + [[package]] name = "wgpu-hal" -version = "23.0.1" +version = "26.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" +checksum = "7df2c64ac282a91ad7662c90bc4a77d4a2135bc0b2a2da5a4d4e267afc034b9e" dependencies = [ "android_system_properties", "arrayvec", @@ -3070,13 +3258,15 @@ dependencies = [ "bitflags 2.9.0", "block", "bytemuck", - "cfg_aliases", - "core-graphics-types", + "cfg-if 1.0.0", + "cfg_aliases 0.2.1", + "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", + "hashbrown 0.15.2", "js-sys", "khronos-egl", "libc", @@ -3084,17 +3274,18 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "objc", - "once_cell", + "ordered-float", "parking_lot", + "portable-atomic", + "portable-atomic-util", "profiling", "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", "smallvec 1.15.1", - "thiserror", + "thiserror 2.0.16", "wasm-bindgen", "web-sys", "wgpu-types", @@ -3104,12 +3295,15 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "23.0.0" +version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" +checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" dependencies = [ "bitflags 2.9.0", + "bytemuck", "js-sys", + "log", + "thiserror 2.0.16", "web-sys", ] @@ -3214,19 +3408,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -3327,12 +3508,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3351,12 +3526,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3381,12 +3550,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3405,12 +3568,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3447,12 +3604,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3483,8 +3634,8 @@ dependencies = [ "bitflags 2.9.0", "bytemuck", "calloop", - "cfg_aliases", - "core-foundation", + "cfg_aliases 0.1.1", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "icrate", @@ -3493,7 +3644,7 @@ dependencies = [ "log", "memmap2", "ndk", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "objc2", "once_cell", "orbclient", diff --git a/crates/lambda-rs-platform/Cargo.toml b/crates/lambda-rs-platform/Cargo.toml index 70f089da..b7670f82 100644 --- a/crates/lambda-rs-platform/Cargo.toml +++ b/crates/lambda-rs-platform/Cargo.toml @@ -13,10 +13,10 @@ path = "src/lib.rs" [dependencies] winit = "=0.29.10" shaderc = { version = "=0.7", optional = true, default-features = false } -naga = { version = "=23.1.0", optional = true, default-features = false, features = ["spv-out", "glsl-in", "wgsl-in"] } +naga = { version = "=26.0.0", optional = true, default-features = false, features = ["spv-out", "glsl-in", "wgsl-in"] } rand = "=0.8.5" obj-rs = "=0.7.0" -wgpu = { version = "=23.0.1", optional = true, features = ["wgsl", "spirv"] } +wgpu = { version = "=26.0.1", optional = true, features = ["wgsl", "spirv"] } pollster = { version = "=0.3.0", optional = true } lambda-rs-logging = { path = "../lambda-rs-logging", version = "2023.1.30" } diff --git a/crates/lambda-rs-platform/src/wgpu/mod.rs b/crates/lambda-rs-platform/src/wgpu/mod.rs index 8af6b85c..90a87364 100644 --- a/crates/lambda-rs-platform/src/wgpu/mod.rs +++ b/crates/lambda-rs-platform/src/wgpu/mod.rs @@ -22,8 +22,8 @@ pub struct InstanceBuilder { label: Option, backends: wgpu::Backends, flags: wgpu::InstanceFlags, - dx12_shader_compiler: wgpu::Dx12Compiler, - gles_minor_version: wgpu::Gles3MinorVersion, + backend_options: wgpu::BackendOptions, + memory_budget_thresholds: wgpu::MemoryBudgetThresholds, } impl InstanceBuilder { @@ -33,8 +33,8 @@ impl InstanceBuilder { label: None, backends: wgpu::Backends::PRIMARY, flags: wgpu::InstanceFlags::default(), - dx12_shader_compiler: wgpu::Dx12Compiler::default(), - gles_minor_version: wgpu::Gles3MinorVersion::default(), + backend_options: wgpu::BackendOptions::default(), + memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(), } } @@ -61,7 +61,7 @@ impl InstanceBuilder { mut self, compiler: wgpu::Dx12Compiler, ) -> Self { - self.dx12_shader_compiler = compiler; + self.backend_options.dx12.shader_compiler = compiler; self } @@ -70,7 +70,7 @@ impl InstanceBuilder { mut self, version: wgpu::Gles3MinorVersion, ) -> Self { - self.gles_minor_version = version; + self.backend_options.gl.gles_minor_version = version; self } @@ -79,13 +79,13 @@ impl InstanceBuilder { let descriptor = wgpu::InstanceDescriptor { backends: self.backends, flags: self.flags, - dx12_shader_compiler: self.dx12_shader_compiler, - gles_minor_version: self.gles_minor_version, + memory_budget_thresholds: self.memory_budget_thresholds, + backend_options: self.backend_options, }; Instance { label: self.label, - instance: wgpu::Instance::new(descriptor), + instance: wgpu::Instance::new(&descriptor), } } } @@ -116,7 +116,7 @@ impl Instance { pub fn request_adapter<'surface, 'window>( &self, options: &wgpu::RequestAdapterOptions<'surface, 'window>, - ) -> Option { + ) -> Result { block_on(self.instance.request_adapter(options)) } } @@ -408,7 +408,7 @@ impl GpuBuilder { force_fallback_adapter: self.force_fallback_adapter, compatible_surface: surface.map(|surface| surface.surface()), }) - .ok_or(GpuBuildError::AdapterUnavailable)?; + .map_err(|_| GpuBuildError::AdapterUnavailable)?; let adapter_features = adapter.features(); if !adapter_features.contains(self.required_features) { @@ -423,9 +423,10 @@ impl GpuBuilder { required_features: self.required_features, required_limits: adapter.limits(), memory_hints: self.memory_hints, + trace: wgpu::Trace::Off, }; - let (device, queue) = block_on(adapter.request_device(&descriptor, None))?; + let (device, queue) = block_on(adapter.request_device(&descriptor))?; Ok(Gpu { adapter, diff --git a/crates/lambda-rs/src/render/mod.rs b/crates/lambda-rs/src/render/mod.rs index 56147d29..9945f6c1 100644 --- a/crates/lambda-rs/src/render/mod.rs +++ b/crates/lambda-rs/src/render/mod.rs @@ -227,6 +227,7 @@ impl RenderContext { let color_attachment = wgpu::RenderPassColorAttachment { view, + depth_slice: None, resolve_target: None, ops: pass.color_ops(), }; From 740284482e34eb5073199c6c710f143cdf040853 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 26 Sep 2025 13:09:15 -0700 Subject: [PATCH 06/10] [update] rustup to install cargo fmt. --- .github/workflows/compile_lambda_rs.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/compile_lambda_rs.yml b/.github/workflows/compile_lambda_rs.yml index a909810d..9ab709d3 100644 --- a/.github/workflows/compile_lambda_rs.yml +++ b/.github/workflows/compile_lambda_rs.yml @@ -2,10 +2,10 @@ name: compile & test lambda-rs (wgpu) on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] workflow_dispatch: @@ -20,15 +20,15 @@ jobs: strategy: matrix: include: - - os: ubuntu-latest - rustup-toolchain: "stable" - features: "lambda-rs/with-vulkan" - - os: windows-latest - rustup-toolchain: "stable" - features: "lambda-rs/with-dx12" - - os: macos-latest - rustup-toolchain: "stable" - features: "lambda-rs/with-metal" + - os: ubuntu-latest + rustup-toolchain: "stable" + features: "lambda-rs/with-vulkan" + - os: windows-latest + rustup-toolchain: "stable" + features: "lambda-rs/with-dx12" + - os: macos-latest + rustup-toolchain: "stable" + features: "lambda-rs/with-metal" steps: - name: Checkout Repository @@ -54,6 +54,7 @@ jobs: - name: Obtain rust toolchain for ${{ matrix.rustup-toolchain }} run: | rustup toolchain install ${{ matrix.rustup-toolchain }} + rustup component add rustfmt rustup default ${{ matrix.rustup-toolchain }} - name: Check formatting From dd3f17d922865bb86cc2384ad9abd877d05cae62 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 26 Sep 2025 14:56:08 -0700 Subject: [PATCH 07/10] [update] formatting. --- crates/lambda-rs-args/examples/basic.rs | 7 ++- crates/lambda-rs-args/examples/bools.rs | 6 ++- crates/lambda-rs-args/examples/env_config.rs | 6 ++- crates/lambda-rs-args/examples/equals.rs | 6 ++- crates/lambda-rs-args/examples/exclusives.rs | 7 ++- crates/lambda-rs-args/examples/positionals.rs | 6 ++- crates/lambda-rs-args/examples/short_count.rs | 6 ++- crates/lambda-rs-args/examples/subcommands.rs | 6 ++- crates/lambda-rs-args/src/lib.rs | 6 ++- crates/lambda-rs-logging/src/handler.rs | 7 ++- crates/lambda-rs-platform/src/obj/mod.rs | 11 ++++- crates/lambda-rs-platform/src/shader/mod.rs | 26 ++++++---- crates/lambda-rs-platform/src/shader/naga.rs | 11 ++++- .../src/shader/shaderc_backend.rs | 5 +- crates/lambda-rs-platform/src/shaderc.rs | 5 +- crates/lambda-rs-platform/src/wgpu/mod.rs | 8 ++-- crates/lambda-rs-platform/src/winit/mod.rs | 33 ++++++++++--- crates/lambda-rs/examples/minimal.rs | 5 +- crates/lambda-rs/examples/push_constants.rs | 38 +++++++++++---- crates/lambda-rs/examples/triangle.rs | 25 ++++++++-- crates/lambda-rs/examples/triangles.rs | 27 +++++++++-- crates/lambda-rs/src/component.rs | 10 +++- crates/lambda-rs/src/math/matrix.rs | 16 +++++-- crates/lambda-rs/src/render/buffer.rs | 13 +++-- crates/lambda-rs/src/render/command.rs | 5 +- crates/lambda-rs/src/render/mesh.rs | 6 ++- crates/lambda-rs/src/render/mod.rs | 14 ++++-- crates/lambda-rs/src/render/pipeline.rs | 13 +++-- crates/lambda-rs/src/render/shader.rs | 5 +- crates/lambda-rs/src/render/window.rs | 5 +- crates/lambda-rs/src/runtimes/application.rs | 27 ++++++++--- crates/lambda-rs/src/runtimes/mod.rs | 5 +- tools/obj_loader/src/main.rs | 48 +++++++++++++++---- 33 files changed, 336 insertions(+), 88 deletions(-) diff --git a/crates/lambda-rs-args/examples/basic.rs b/crates/lambda-rs-args/examples/basic.rs index 82ab1ace..28b89253 100644 --- a/crates/lambda-rs-args/examples/basic.rs +++ b/crates/lambda-rs-args/examples/basic.rs @@ -1,4 +1,9 @@ -use args::{Argument, ArgumentParser, ArgumentType, ArgumentValue}; +use args::{ + Argument, + ArgumentParser, + ArgumentType, + ArgumentValue, +}; fn main() { let parser = ArgumentParser::new("basic") diff --git a/crates/lambda-rs-args/examples/bools.rs b/crates/lambda-rs-args/examples/bools.rs index 7c0436b6..ae0b8979 100644 --- a/crates/lambda-rs-args/examples/bools.rs +++ b/crates/lambda-rs-args/examples/bools.rs @@ -1,4 +1,8 @@ -use args::{Argument, ArgumentParser, ArgumentType}; +use args::{ + Argument, + ArgumentParser, + ArgumentType, +}; fn main() { let parser = ArgumentParser::new("bools") diff --git a/crates/lambda-rs-args/examples/env_config.rs b/crates/lambda-rs-args/examples/env_config.rs index bd36fce1..ab9df5f2 100644 --- a/crates/lambda-rs-args/examples/env_config.rs +++ b/crates/lambda-rs-args/examples/env_config.rs @@ -1,4 +1,8 @@ -use args::{Argument, ArgumentParser, ArgumentType}; +use args::{ + Argument, + ArgumentParser, + ArgumentType, +}; fn main() { // Reads APP_HOST and APP_PORT if set. Also reads from ./app.cfg if present diff --git a/crates/lambda-rs-args/examples/equals.rs b/crates/lambda-rs-args/examples/equals.rs index 0b1237ad..aba21b71 100644 --- a/crates/lambda-rs-args/examples/equals.rs +++ b/crates/lambda-rs-args/examples/equals.rs @@ -1,4 +1,8 @@ -use args::{Argument, ArgumentParser, ArgumentType}; +use args::{ + Argument, + ArgumentParser, + ArgumentType, +}; fn main() { let parser = ArgumentParser::new("equals") diff --git a/crates/lambda-rs-args/examples/exclusives.rs b/crates/lambda-rs-args/examples/exclusives.rs index 75cef83f..7dd2b0e6 100644 --- a/crates/lambda-rs-args/examples/exclusives.rs +++ b/crates/lambda-rs-args/examples/exclusives.rs @@ -1,4 +1,9 @@ -use args::{ArgsError, Argument, ArgumentParser, ArgumentType}; +use args::{ + ArgsError, + Argument, + ArgumentParser, + ArgumentType, +}; fn main() { // --json and --yaml are mutually exclusive; --out requires --format diff --git a/crates/lambda-rs-args/examples/positionals.rs b/crates/lambda-rs-args/examples/positionals.rs index ff7ec2e5..e46715a6 100644 --- a/crates/lambda-rs-args/examples/positionals.rs +++ b/crates/lambda-rs-args/examples/positionals.rs @@ -1,4 +1,8 @@ -use args::{Argument, ArgumentParser, ArgumentType}; +use args::{ + Argument, + ArgumentParser, + ArgumentType, +}; fn main() { let parser = ArgumentParser::new("pos") diff --git a/crates/lambda-rs-args/examples/short_count.rs b/crates/lambda-rs-args/examples/short_count.rs index 9573cdf4..aad62aff 100644 --- a/crates/lambda-rs-args/examples/short_count.rs +++ b/crates/lambda-rs-args/examples/short_count.rs @@ -1,4 +1,8 @@ -use args::{Argument, ArgumentParser, ArgumentType}; +use args::{ + Argument, + ArgumentParser, + ArgumentType, +}; fn main() { let parser = ArgumentParser::new("short-count").with_argument( diff --git a/crates/lambda-rs-args/examples/subcommands.rs b/crates/lambda-rs-args/examples/subcommands.rs index 16d87801..4072c54a 100644 --- a/crates/lambda-rs-args/examples/subcommands.rs +++ b/crates/lambda-rs-args/examples/subcommands.rs @@ -1,4 +1,8 @@ -use args::{Argument, ArgumentParser, ArgumentType}; +use args::{ + Argument, + ArgumentParser, + ArgumentType, +}; fn main() { // root diff --git a/crates/lambda-rs-args/src/lib.rs b/crates/lambda-rs-args/src/lib.rs index c33f65aa..fcd6de44 100644 --- a/crates/lambda-rs-args/src/lib.rs +++ b/crates/lambda-rs-args/src/lib.rs @@ -2,8 +2,10 @@ //! Lambda Args is a simple argument parser for Rust. It is designed to be //! simple to use and primarily for use in lambda command line applications. -use std::collections::HashMap; -use std::fmt; +use std::{ + collections::HashMap, + fmt, +}; pub struct ArgumentParser { name: String, diff --git a/crates/lambda-rs-logging/src/handler.rs b/crates/lambda-rs-logging/src/handler.rs index 035d8948..4e418b2e 100644 --- a/crates/lambda-rs-logging/src/handler.rs +++ b/crates/lambda-rs-logging/src/handler.rs @@ -1,6 +1,11 @@ //! Log handling implementations for the logger. -use std::{fmt::Debug, fs::OpenOptions, io::Write, time::SystemTime}; +use std::{ + fmt::Debug, + fs::OpenOptions, + io::Write, + time::SystemTime, +}; use crate::LogLevel; diff --git a/crates/lambda-rs-platform/src/obj/mod.rs b/crates/lambda-rs-platform/src/obj/mod.rs index c504c320..07aec04c 100644 --- a/crates/lambda-rs-platform/src/obj/mod.rs +++ b/crates/lambda-rs-platform/src/obj/mod.rs @@ -1,6 +1,13 @@ -use std::{fs::File, io::BufReader}; +use std::{ + fs::File, + io::BufReader, +}; -use obj::{load_obj, Obj, TexturedVertex}; +use obj::{ + load_obj, + Obj, + TexturedVertex, +}; /// Loads a untextured obj file from the given path. Wrapper around the obj crate. pub fn load_obj_from_file(path: &str) -> Obj { diff --git a/crates/lambda-rs-platform/src/shader/mod.rs b/crates/lambda-rs-platform/src/shader/mod.rs index 3fbd9a70..572fdc68 100644 --- a/crates/lambda-rs-platform/src/shader/mod.rs +++ b/crates/lambda-rs-platform/src/shader/mod.rs @@ -1,7 +1,10 @@ //! Abstractions for compiling shaders into SPIR-V for Lambda runtimes. mod types; -pub use types::{ShaderKind, VirtualShader}; +pub use types::{ + ShaderKind, + VirtualShader, +}; #[cfg(feature = "shader-backend-naga")] mod naga; @@ -10,16 +13,23 @@ mod naga; mod shaderc_backend; #[cfg(feature = "shader-backend-naga")] -pub use naga::{ShaderCompiler, ShaderCompilerBuilder}; - +pub use naga::{ + ShaderCompiler, + ShaderCompilerBuilder, +}; #[cfg(all( - not(feature = "shader-backend-naga"), + feature = "shader-backend-naga", feature = "shader-backend-shaderc" ))] -pub use shaderc_backend::{ShaderCompiler, ShaderCompilerBuilder}; - +pub use naga::{ + ShaderCompiler, + ShaderCompilerBuilder, +}; #[cfg(all( - feature = "shader-backend-naga", + not(feature = "shader-backend-naga"), feature = "shader-backend-shaderc" ))] -pub use naga::{ShaderCompiler, ShaderCompilerBuilder}; +pub use shaderc_backend::{ + ShaderCompiler, + ShaderCompilerBuilder, +}; diff --git a/crates/lambda-rs-platform/src/shader/naga.rs b/crates/lambda-rs-platform/src/shader/naga.rs index 5d78d1d9..e174da4c 100644 --- a/crates/lambda-rs-platform/src/shader/naga.rs +++ b/crates/lambda-rs-platform/src/shader/naga.rs @@ -3,11 +3,18 @@ use std::io::Read; use naga::{ back::spv, front::glsl, - valid::{Capabilities, ValidationFlags, Validator}, + valid::{ + Capabilities, + ValidationFlags, + Validator, + }, ShaderStage, }; -use super::{ShaderKind, VirtualShader}; +use super::{ + ShaderKind, + VirtualShader, +}; /// Builder for the naga-backed shader compiler. pub struct ShaderCompilerBuilder {} diff --git a/crates/lambda-rs-platform/src/shader/shaderc_backend.rs b/crates/lambda-rs-platform/src/shader/shaderc_backend.rs index 4ee94fdf..f92bebce 100644 --- a/crates/lambda-rs-platform/src/shader/shaderc_backend.rs +++ b/crates/lambda-rs-platform/src/shader/shaderc_backend.rs @@ -2,7 +2,10 @@ use std::io::Read; use shaderc; -use super::{ShaderKind, VirtualShader}; +use super::{ + ShaderKind, + VirtualShader, +}; /// Builder for the shaderc platform shader compiler. pub struct ShaderCompilerBuilder {} diff --git a/crates/lambda-rs-platform/src/shaderc.rs b/crates/lambda-rs-platform/src/shaderc.rs index 836dbcb4..f375f999 100644 --- a/crates/lambda-rs-platform/src/shaderc.rs +++ b/crates/lambda-rs-platform/src/shaderc.rs @@ -5,5 +5,8 @@ note = "Use `lambda_platform::shader` instead of `lambda_platform::shaderc`." )] pub use crate::shader::{ - ShaderCompiler, ShaderCompilerBuilder, ShaderKind, VirtualShader, + ShaderCompiler, + ShaderCompilerBuilder, + ShaderKind, + VirtualShader, }; diff --git a/crates/lambda-rs-platform/src/wgpu/mod.rs b/crates/lambda-rs-platform/src/wgpu/mod.rs index 90a87364..e3a1daf4 100644 --- a/crates/lambda-rs-platform/src/wgpu/mod.rs +++ b/crates/lambda-rs-platform/src/wgpu/mod.rs @@ -7,12 +7,14 @@ //! important handles when you need to drop down to raw `wgpu`. use pollster::block_on; -use wgpu::rwh::{HasDisplayHandle as _, HasWindowHandle as _}; +pub use wgpu as types; +use wgpu::rwh::{ + HasDisplayHandle as _, + HasWindowHandle as _, +}; use crate::winit::WindowHandle; -pub use wgpu as types; - #[derive(Debug, Clone)] /// Builder for creating a `wgpu::Instance` with consistent defaults. /// diff --git a/crates/lambda-rs-platform/src/winit/mod.rs b/crates/lambda-rs-platform/src/winit/mod.rs index dff5bbdb..4f39e881 100644 --- a/crates/lambda-rs-platform/src/winit/mod.rs +++ b/crates/lambda-rs-platform/src/winit/mod.rs @@ -1,25 +1,46 @@ //! Winit wrapper to easily construct cross platform windows use winit::{ - dpi::{LogicalSize, PhysicalSize}, + dpi::{ + LogicalSize, + PhysicalSize, + }, event::Event, event_loop::{ - ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, + ControlFlow, + EventLoop, + EventLoopBuilder, + EventLoopProxy, EventLoopWindowTarget, }, monitor::MonitorHandle, - window::{Window, WindowBuilder}, + window::{ + Window, + WindowBuilder, + }, }; /// Embedded module for exporting data/types from winit as minimally/controlled /// as possible. The exports from this module are not guaranteed to be stable. pub mod winit_exports { pub use winit::{ - event::{ElementState, Event, KeyEvent, MouseButton, WindowEvent}, + event::{ + ElementState, + Event, + KeyEvent, + MouseButton, + WindowEvent, + }, event_loop::{ - ControlFlow, EventLoop, EventLoopProxy, EventLoopWindowTarget, + ControlFlow, + EventLoop, + EventLoopProxy, + EventLoopWindowTarget, + }, + keyboard::{ + KeyCode, + PhysicalKey, }, - keyboard::{KeyCode, PhysicalKey}, }; } diff --git a/crates/lambda-rs/examples/minimal.rs b/crates/lambda-rs/examples/minimal.rs index 8e7524e4..9158c3e3 100644 --- a/crates/lambda-rs/examples/minimal.rs +++ b/crates/lambda-rs/examples/minimal.rs @@ -4,7 +4,10 @@ //! applications correctly. #[macro_use] -use lambda::{runtime::start_runtime, runtimes::ApplicationRuntimeBuilder}; +use lambda::{ + runtime::start_runtime, + runtimes::ApplicationRuntimeBuilder, +}; fn main() { let runtime = ApplicationRuntimeBuilder::new("Minimal Demo application") diff --git a/crates/lambda-rs/examples/push_constants.rs b/crates/lambda-rs/examples/push_constants.rs index bf2f0cce..90d06370 100644 --- a/crates/lambda-rs/examples/push_constants.rs +++ b/crates/lambda-rs/examples/push_constants.rs @@ -1,22 +1,44 @@ -use lambda::render::{pipeline::PipelineStage, ColorFormat}; use lambda::{ component::Component, events::WindowEvent, logging, - math::{matrix, matrix::Matrix, vector::Vector}, + math::{ + matrix, + matrix::Matrix, + vector::Vector, + }, render::{ buffer::BufferBuilder, command::RenderCommand, - mesh::{Mesh, MeshBuilder}, - pipeline::RenderPipelineBuilder, + mesh::{ + Mesh, + MeshBuilder, + }, + pipeline::{ + PipelineStage, + RenderPipelineBuilder, + }, render_pass::RenderPassBuilder, - shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, - vertex::{VertexAttribute, VertexBuilder, VertexElement}, - viewport, ResourceId, + shader::{ + Shader, + ShaderBuilder, + ShaderKind, + VirtualShader, + }, + vertex::{ + VertexAttribute, + VertexBuilder, + VertexElement, + }, + viewport, + ColorFormat, + ResourceId, }, runtime::start_runtime, runtimes::{ - application::ComponentResult, ApplicationRuntime, ApplicationRuntimeBuilder, + application::ComponentResult, + ApplicationRuntime, + ApplicationRuntimeBuilder, }, }; diff --git a/crates/lambda-rs/examples/triangle.rs b/crates/lambda-rs/examples/triangle.rs index 690ba3f7..43d8a1ce 100644 --- a/crates/lambda-rs/examples/triangle.rs +++ b/crates/lambda-rs/examples/triangle.rs @@ -1,14 +1,29 @@ use lambda::{ component::Component, - events::{ComponentEvent, Events, Key, WindowEvent}, + events::{ + ComponentEvent, + Events, + Key, + WindowEvent, + }, render::{ command::RenderCommand, - pipeline, render_pass, - shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, - viewport, RenderContext, + pipeline, + render_pass, + shader::{ + Shader, + ShaderBuilder, + ShaderKind, + VirtualShader, + }, + viewport, + RenderContext, }, runtime::start_runtime, - runtimes::{application::ComponentResult, ApplicationRuntimeBuilder}, + runtimes::{ + application::ComponentResult, + ApplicationRuntimeBuilder, + }, }; pub struct DemoComponent { diff --git a/crates/lambda-rs/examples/triangles.rs b/crates/lambda-rs/examples/triangles.rs index 52bebc95..b8a4548d 100644 --- a/crates/lambda-rs/examples/triangles.rs +++ b/crates/lambda-rs/examples/triangles.rs @@ -1,15 +1,32 @@ use lambda::{ component::Component, - events::{Events, Key, VirtualKey, WindowEvent}, + events::{ + Events, + Key, + VirtualKey, + WindowEvent, + }, render::{ command::RenderCommand, - pipeline::{self, PipelineStage}, + pipeline::{ + self, + PipelineStage, + }, render_pass, - shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, - viewport, RenderContext, + shader::{ + Shader, + ShaderBuilder, + ShaderKind, + VirtualShader, + }, + viewport, + RenderContext, }, runtime::start_runtime, - runtimes::{application::ComponentResult, ApplicationRuntimeBuilder}, + runtimes::{ + application::ComponentResult, + ApplicationRuntimeBuilder, + }, }; pub struct TrianglesComponent { diff --git a/crates/lambda-rs/src/component.rs b/crates/lambda-rs/src/component.rs index 292741d4..cdd3d1b0 100644 --- a/crates/lambda-rs/src/component.rs +++ b/crates/lambda-rs/src/component.rs @@ -1,8 +1,14 @@ -use std::{fmt::Debug, time::Duration}; +use std::{ + fmt::Debug, + time::Duration, +}; use crate::{ events::Events, - render::{command::RenderCommand, RenderContext}, + render::{ + command::RenderCommand, + RenderContext, + }, }; /// The Component Interface for allowing Component based data structures diff --git a/crates/lambda-rs/src/math/matrix.rs b/crates/lambda-rs/src/math/matrix.rs index 269cfc77..4bb37eb6 100644 --- a/crates/lambda-rs/src/math/matrix.rs +++ b/crates/lambda-rs/src/math/matrix.rs @@ -2,7 +2,10 @@ use lambda_platform::rand::get_uniformly_random_floats_between; -use super::{turns_to_radians, vector::Vector}; +use super::{ + turns_to_radians, + vector::Vector, +}; // -------------------------------- MATRIX ------------------------------------- @@ -389,9 +392,16 @@ where mod tests { use super::{ - filled_matrix, perspective_matrix, rotate_matrix, submatrix, Matrix, + filled_matrix, + perspective_matrix, + rotate_matrix, + submatrix, + Matrix, + }; + use crate::math::{ + matrix::translation_matrix, + turns_to_radians, }; - use crate::math::{matrix::translation_matrix, turns_to_radians}; #[test] fn square_matrix_add() { diff --git a/crates/lambda-rs/src/render/buffer.rs b/crates/lambda-rs/src/render/buffer.rs index 8125d9d9..fc337866 100644 --- a/crates/lambda-rs/src/render/buffer.rs +++ b/crates/lambda-rs/src/render/buffer.rs @@ -2,9 +2,16 @@ use std::rc::Rc; -use lambda_platform::wgpu::types::{self as wgpu, util::DeviceExt}; - -use super::{mesh::Mesh, vertex::Vertex, RenderContext}; +use lambda_platform::wgpu::types::{ + self as wgpu, + util::DeviceExt, +}; + +use super::{ + mesh::Mesh, + vertex::Vertex, + RenderContext, +}; #[derive(Clone, Copy, Debug)] /// High‑level classification for buffers created by the engine. diff --git a/crates/lambda-rs/src/render/command.rs b/crates/lambda-rs/src/render/command.rs index 75d94eb1..823762e4 100644 --- a/crates/lambda-rs/src/render/command.rs +++ b/crates/lambda-rs/src/render/command.rs @@ -2,7 +2,10 @@ use std::ops::Range; -use super::{pipeline::PipelineStage, viewport::Viewport}; +use super::{ + pipeline::PipelineStage, + viewport::Viewport, +}; /// Commands recorded and executed by the `RenderContext` to produce a frame. #[derive(Debug, Clone)] diff --git a/crates/lambda-rs/src/render/mesh.rs b/crates/lambda-rs/src/render/mesh.rs index 3d15a43d..d549fb1c 100644 --- a/crates/lambda-rs/src/render/mesh.rs +++ b/crates/lambda-rs/src/render/mesh.rs @@ -3,7 +3,11 @@ use lambda_platform::obj::load_textured_obj_from_file; use super::{ - vertex::{Vertex, VertexAttribute, VertexElement}, + vertex::{ + Vertex, + VertexAttribute, + VertexElement, + }, ColorFormat, }; diff --git a/crates/lambda-rs/src/render/mod.rs b/crates/lambda-rs/src/render/mod.rs index 9945f6c1..d67ca12c 100644 --- a/crates/lambda-rs/src/render/mod.rs +++ b/crates/lambda-rs/src/render/mod.rs @@ -12,18 +12,24 @@ pub mod vertex; pub mod viewport; pub mod window; -pub use vertex::ColorFormat; - use std::iter; use lambda_platform::wgpu::{ - types as wgpu, Gpu, GpuBuilder, Instance, InstanceBuilder, Surface, + types as wgpu, + Gpu, + GpuBuilder, + Instance, + InstanceBuilder, + Surface, SurfaceBuilder, }; use logging; +pub use vertex::ColorFormat; use self::{ - command::RenderCommand, pipeline::RenderPipeline, render_pass::RenderPass, + command::RenderCommand, + pipeline::RenderPipeline, + render_pass::RenderPass, }; /// Builder for configuring a `RenderContext` tied to a single window. diff --git a/crates/lambda-rs/src/render/pipeline.rs b/crates/lambda-rs/src/render/pipeline.rs index 9838453d..464c1298 100644 --- a/crates/lambda-rs/src/render/pipeline.rs +++ b/crates/lambda-rs/src/render/pipeline.rs @@ -1,13 +1,20 @@ //! Render pipeline builders and definitions for lambda runtimes and //! applications. -use std::{borrow::Cow, ops::Range, rc::Rc}; +use std::{ + borrow::Cow, + ops::Range, + rc::Rc, +}; use lambda_platform::wgpu::types as wgpu; use super::{ - buffer::Buffer, render_pass::RenderPass, shader::Shader, - vertex::VertexAttribute, RenderContext, + buffer::Buffer, + render_pass::RenderPass, + shader::Shader, + vertex::VertexAttribute, + RenderContext, }; #[derive(Debug)] diff --git a/crates/lambda-rs/src/render/shader.rs b/crates/lambda-rs/src/render/shader.rs index 6fce5551..50cfc575 100644 --- a/crates/lambda-rs/src/render/shader.rs +++ b/crates/lambda-rs/src/render/shader.rs @@ -2,7 +2,10 @@ // Expose the platform shader compiler abstraction pub use lambda_platform::shader::{ - ShaderCompiler, ShaderCompilerBuilder, ShaderKind, VirtualShader, + ShaderCompiler, + ShaderCompilerBuilder, + ShaderKind, + VirtualShader, }; /// Reusable compiler for turning virtual shaders into SPIR‑V modules. diff --git a/crates/lambda-rs/src/render/window.rs b/crates/lambda-rs/src/render/window.rs index c153803c..9d20ad1e 100644 --- a/crates/lambda-rs/src/render/window.rs +++ b/crates/lambda-rs/src/render/window.rs @@ -1,7 +1,10 @@ //! Window implementation for rendering applications. use lambda_platform::winit::{ - Loop, WindowHandle, WindowHandleBuilder, WindowProperties, + Loop, + WindowHandle, + WindowHandleBuilder, + WindowProperties, }; use crate::events::Events; diff --git a/crates/lambda-rs/src/runtimes/application.rs b/crates/lambda-rs/src/runtimes/application.rs index 2f0069a2..d316afa8 100644 --- a/crates/lambda-rs/src/runtimes/application.rs +++ b/crates/lambda-rs/src/runtimes/application.rs @@ -6,22 +6,37 @@ use std::time::Instant; use lambda_platform::winit::{ winit_exports::{ - ElementState, Event as WinitEvent, KeyCode as WinitKeyCode, - KeyEvent as WinitKeyEvent, MouseButton, PhysicalKey as WinitPhysicalKey, + ElementState, + Event as WinitEvent, + KeyCode as WinitKeyCode, + KeyEvent as WinitKeyEvent, + MouseButton, + PhysicalKey as WinitPhysicalKey, WindowEvent as WinitWindowEvent, }, - Loop, LoopBuilder, + Loop, + LoopBuilder, }; use logging; use crate::{ component::Component, events::{ - Button, ComponentEvent, Events, Key, Mouse, RuntimeEvent, WindowEvent, + Button, + ComponentEvent, + Events, + Key, + Mouse, + RuntimeEvent, + WindowEvent, }, render::{ - window::{Window, WindowBuilder}, - RenderContext, RenderContextBuilder, + window::{ + Window, + WindowBuilder, + }, + RenderContext, + RenderContextBuilder, }, runtime::Runtime, }; diff --git a/crates/lambda-rs/src/runtimes/mod.rs b/crates/lambda-rs/src/runtimes/mod.rs index 103b6c11..eb459f59 100644 --- a/crates/lambda-rs/src/runtimes/mod.rs +++ b/crates/lambda-rs/src/runtimes/mod.rs @@ -1,2 +1,5 @@ pub mod application; -pub use application::{ApplicationRuntime, ApplicationRuntimeBuilder}; +pub use application::{ + ApplicationRuntime, + ApplicationRuntimeBuilder, +}; diff --git a/tools/obj_loader/src/main.rs b/tools/obj_loader/src/main.rs index d0a76abc..6801ca74 100644 --- a/tools/obj_loader/src/main.rs +++ b/tools/obj_loader/src/main.rs @@ -1,25 +1,55 @@ use std::env; use args::{ - Argument, ArgumentParser, ArgumentType, ArgumentValue, ParsedArgument, + Argument, + ArgumentParser, + ArgumentType, + ArgumentValue, + ParsedArgument, }; use lambda::{ component::Component, - events::{ComponentEvent, Events, WindowEvent}, + events::{ + ComponentEvent, + Events, + WindowEvent, + }, logging, - math::matrix::{self, Matrix}, + math::matrix::{ + self, + Matrix, + }, render::{ buffer::BufferBuilder, command::RenderCommand, - mesh::{Mesh, MeshBuilder}, - pipeline::{PipelineStage, RenderPipelineBuilder}, + mesh::{ + Mesh, + MeshBuilder, + }, + pipeline::{ + PipelineStage, + RenderPipelineBuilder, + }, render_pass::RenderPassBuilder, - shader::{Shader, ShaderBuilder, ShaderKind, VirtualShader}, - vertex::{Vertex, VertexAttribute, VertexElement}, - viewport, ResourceId, + shader::{ + Shader, + ShaderBuilder, + ShaderKind, + VirtualShader, + }, + vertex::{ + Vertex, + VertexAttribute, + VertexElement, + }, + viewport, + ResourceId, }, runtime::start_runtime, - runtimes::{application::ComponentResult, ApplicationRuntimeBuilder}, + runtimes::{ + application::ComponentResult, + ApplicationRuntimeBuilder, + }, }; // ------------------------------ SHADER SOURCE -------------------------------- From 7c496a714873cdd3294c81414a9ced69de418222 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 26 Sep 2025 15:04:31 -0700 Subject: [PATCH 08/10] [update] CI to use the nightly rust fmt version. --- .github/workflows/compile_lambda_rs.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile_lambda_rs.yml b/.github/workflows/compile_lambda_rs.yml index 9ab709d3..0b45ca09 100644 --- a/.github/workflows/compile_lambda_rs.yml +++ b/.github/workflows/compile_lambda_rs.yml @@ -54,11 +54,12 @@ jobs: - name: Obtain rust toolchain for ${{ matrix.rustup-toolchain }} run: | rustup toolchain install ${{ matrix.rustup-toolchain }} - rustup component add rustfmt rustup default ${{ matrix.rustup-toolchain }} - name: Check formatting - run: cargo fmt --all --check + run: | + rustup component add rustfmt --toolchain nightly-2025-09-26 + cargo +nightly-nightly-2025-09-26 fmt --all --check - name: Build workspace run: cargo build --workspace --features ${{ matrix.features }} From e38df4a7c8c37c545cebc4cf08001d41948ba01f Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 26 Sep 2025 15:07:29 -0700 Subject: [PATCH 09/10] [fix] tyo --- .github/workflows/compile_lambda_rs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compile_lambda_rs.yml b/.github/workflows/compile_lambda_rs.yml index 0b45ca09..33449125 100644 --- a/.github/workflows/compile_lambda_rs.yml +++ b/.github/workflows/compile_lambda_rs.yml @@ -59,7 +59,7 @@ jobs: - name: Check formatting run: | rustup component add rustfmt --toolchain nightly-2025-09-26 - cargo +nightly-nightly-2025-09-26 fmt --all --check + cargo +nightly-2025-09-26 fmt --all --check - name: Build workspace run: cargo build --workspace --features ${{ matrix.features }} From 964d58c0658a4c5d23a4cf33f4a8ecc12458dad6 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 26 Sep 2025 20:01:09 -0700 Subject: [PATCH 10/10] [fix] broken tests. --- crates/lambda-rs-platform/src/wgpu/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/lambda-rs-platform/src/wgpu/mod.rs b/crates/lambda-rs-platform/src/wgpu/mod.rs index e3a1daf4..058f0e80 100644 --- a/crates/lambda-rs-platform/src/wgpu/mod.rs +++ b/crates/lambda-rs-platform/src/wgpu/mod.rs @@ -510,10 +510,11 @@ mod tests { #[test] fn gpu_build_error_wraps_request_device_error() { - let error = GpuBuildError::from(wgpu::RequestDeviceError::NotFound); - assert!(matches!( - error, - GpuBuildError::RequestDevice(wgpu::RequestDeviceError::NotFound) - )); + // RequestDeviceError is opaque in wgpu 26 (no public constructors or variants). + // This test previously validated pattern matching on a specific variant; now we + // simply assert the From implementation exists by + // checking the trait bound at compile time. + fn assert_from_impl>() {} + assert_from_impl::(); } }