From f08ef13fe85d72fe5c3d27cdb774fb0b48ecea34 Mon Sep 17 00:00:00 2001 From: Karim Date: Thu, 2 Jul 2026 00:27:46 +0200 Subject: [PATCH] Nativer 2D-wgpu-Renderer (render2d): Ear-Clipping-Fuellungen, gehrte Papier-mm-Linien, GPU-Pan/Zoom Eigenstaendige Crate (render/window-Feature-Stufung), serde-only Tessellier- schicht headless testbar. Linien als EIN gehrter Streifen (Miter-Bisektor + 1/cos-Laengenfaktor) statt Butt-Cap-Quads pro Segment -> saubere Ecken. Standalone-Spike-Fenster via winit (cargo run --features window --bin spike). --- src-tauri/render2d/.gitignore | 1 + src-tauri/render2d/Cargo.lock | 2605 ++++++++++++++++++++++++++ src-tauri/render2d/Cargo.toml | 53 + src-tauri/render2d/src/bin/spike.rs | 271 +++ src-tauri/render2d/src/gpu.rs | 400 ++++ src-tauri/render2d/src/lib.rs | 174 ++ src-tauri/render2d/src/ortho.rs | 89 + src-tauri/render2d/src/shaders.rs | 103 + src-tauri/render2d/src/tessellate.rs | 414 ++++ src-tauri/render2d/src/types.rs | 82 + 10 files changed, 4192 insertions(+) create mode 100644 src-tauri/render2d/.gitignore create mode 100644 src-tauri/render2d/Cargo.lock create mode 100644 src-tauri/render2d/Cargo.toml create mode 100644 src-tauri/render2d/src/bin/spike.rs create mode 100644 src-tauri/render2d/src/gpu.rs create mode 100644 src-tauri/render2d/src/lib.rs create mode 100644 src-tauri/render2d/src/ortho.rs create mode 100644 src-tauri/render2d/src/shaders.rs create mode 100644 src-tauri/render2d/src/tessellate.rs create mode 100644 src-tauri/render2d/src/types.rs diff --git a/src-tauri/render2d/.gitignore b/src-tauri/render2d/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/src-tauri/render2d/.gitignore @@ -0,0 +1 @@ +/target diff --git a/src-tauri/render2d/Cargo.lock b/src-tauri/render2d/Cargo.lock new file mode 100644 index 0000000..10f5585 --- /dev/null +++ b/src-tauri/render2d/Cargo.lock @@ -0,0 +1,2605 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +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 = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.0", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 2.0.18", +] + +[[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 = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +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 = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "d3d12" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" +dependencies = [ + "bitflags 2.13.0", + "libloading", + "winapi", +] + +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" +dependencies = [ + "bitflags 2.13.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.13.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.13.0", + "com", + "libc", + "libloading", + "thiserror 1.0.69", + "widestring", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +dependencies = [ + "defmt", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.118", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "bitflags 2.13.0", + "libc", + "plain", + "redox_syscall 0.9.0", +] + +[[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.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.13.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "naga" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.0", + "jni-sys 0.3.1", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[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 = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +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 = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "redox_syscall" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "render2d" +version = "0.1.0" +dependencies = [ + "bytemuck", + "env_logger", + "naga", + "pollster", + "serde", + "serde_json", + "wgpu", + "winit", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.13.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.13.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.118", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.13.0", + "rustix 1.1.4", + "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.13.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +dependencies = [ + "arrayvec", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.13.0", + "cfg_aliases 0.1.1", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.13.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +dependencies = [ + "bitflags 2.13.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[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.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.13.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "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.4", + "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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/src-tauri/render2d/Cargo.toml b/src-tauri/render2d/Cargo.toml new file mode 100644 index 0000000..37378b5 --- /dev/null +++ b/src-tauri/render2d/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "render2d" +version = "0.1.0" +edition = "2021" +description = "Nativer wgpu-2D-Renderer fuer den CAD-Grundriss (Tessellierung + Pipelines)" + +# Bewusst NICHT Teil des src-tauri-Workspaces: die Tessellierungs-Bibliothek soll +# unabhaengig von Tauri baubar/testbar bleiben (headless, ohne Webview-Toolchain). +# Der leere [workspace]-Block schliesst die Crate aus einem uebergeordneten +# Workspace aus, sodass `cargo test`/`cargo build` hier eigenstaendig laufen. +[workspace] + +[features] +# Standard: nur die reine Tessellierung + Szene + Ortho-Matrix + WGSL-Quellen. +# Diese Schicht ist serde-only, hat keine GPU-Abhaengigkeit und ist headless +# vollstaendig per `cargo test` pruefbar. +default = [] + +# GPU-Pipelines (wgpu). Baut auch headless (wgpu laedt Vulkan/GL erst zur +# Laufzeit dynamisch), rendert aber nur mit einer aktiven Grafik-Session. +render = ["dep:wgpu", "dep:bytemuck", "dep:pollster"] + +# Fenster-Spike (winit) inkl. lauffaehigem Binary. Braucht Wayland/X11 + eine +# Display-Session zur visuellen Verifikation. +window = ["render", "dep:winit", "dep:env_logger"] + +[dependencies] +serde = { version = "1", features = ["derive"] } + +# --- GPU (nur mit Feature "render") ------------------------------------------ +# Standard-Features von wgpu (wgsl, webgpu, dx12, metal). Die Linux-Backends +# Vulkan/GLES aktiviert wgpu selbst ueber seine target-spezifischen Defaults — +# es gibt in wgpu 22 keine expliziten `vulkan`/`gles`-Feature-Namen. +wgpu = { version = "22", optional = true } +bytemuck = { version = "1", optional = true, features = ["derive"] } +pollster = { version = "0.3", optional = true } + +# --- Fenster (nur mit Feature "window") -------------------------------------- +winit = { version = "0.30", optional = true } +env_logger = { version = "0.11", optional = true } + +[dev-dependencies] +serde_json = "1" +# naga validiert die WGSL-Quellen headless (Parser + Validator) im Test, ohne +# GPU/Display. Version an wgpu 22 gekoppelt (transitiv ohnehin vorhanden). +naga = { version = "22", features = ["wgsl-in"] } + +# Der Fenster-Spike ist nur mit Feature "window" verfuegbar; ohne das Feature +# wird das Binary aus dem Build ausgeschlossen (required-features). +[[bin]] +name = "spike" +path = "src/bin/spike.rs" +required-features = ["window"] diff --git a/src-tauri/render2d/src/bin/spike.rs b/src-tauri/render2d/src/bin/spike.rs new file mode 100644 index 0000000..776f533 --- /dev/null +++ b/src-tauri/render2d/src/bin/spike.rs @@ -0,0 +1,271 @@ +// Standalone-Fenster-Spike (Feature "window"): oeffnet ein winit-Fenster mit +// eigener wgpu-Surface und zeichnet eine Demo-Szene (gefuellte Polygone + Striche +// in echter Papier-mm-Breite). Pan (Ziehen mit linker Maustaste) und Zoom (Rad) +// veraendern NUR die Ortho-Matrix — kein Re-Tessellieren. +// +// Das ist bewusst Option 2 aus dem Briefing: das Rendering entkoppelt von der +// Tauri/Webview-Integration verifizieren. Die Anbindung unter die Webview +// (raw-window-handle) folgt in M2 (siehe docs/design/wgpu-integration-findings.md). +// +// Start: cargo run --features window --bin spike +// (braucht eine aktive Wayland-/X11-Session; headless nicht sichtbar verifizierbar). + +use std::sync::Arc; + +use render2d::gpu::Renderer; +use render2d::types::{FillPolygon, Line, Outline, Scene, ViewBox}; +use render2d::PX_PER_M; + +use winit::application::ApplicationHandler; +use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowId}; + +/// Demo-Szene in Modell-Metern: ein L-foermiger Wand-Poche (konkav!), ein Raum +/// und ein paar Striche — genug, um Fuellung, Umriss und Papier-mm-Linien zu sehen. +fn demo_scene() -> Scene { + let wall_grey: [f32; 4] = [0.55, 0.55, 0.55, 1.0]; + let room_blue: [f32; 4] = [0.20, 0.45, 0.85, 0.18]; + let ink: [f32; 4] = [0.10, 0.10, 0.10, 1.0]; + + // Konkaves L (Wandflaeche). + let l = vec![ + [0.0, 0.0], + [4.0, 0.0], + [4.0, 2.0], + [2.0, 2.0], + [2.0, 4.0], + [0.0, 4.0], + ]; + // Ein transluzenter Raum daneben. + let room = vec![[5.0, 0.0], [9.0, 0.0], [9.0, 4.0], [5.0, 4.0]]; + + Scene { + fills: vec![ + FillPolygon { + pts: l.clone(), + color: wall_grey, + }, + FillPolygon { + pts: room.clone(), + color: room_blue, + }, + ], + outlines: vec![ + Outline { + pts: l, + color: ink, + width_mm: 0.35, + }, + Outline { + pts: room, + color: ink, + width_mm: 0.18, + }, + ], + lines: vec![Line { + a: [0.0, -1.0], + b: [9.0, -1.0], + color: ink, + width_mm: 0.25, + }], + } +} + +/// viewBox so, dass die Szene mittig einpasst (Bildschirm-Raum = Meter*PX_PER_M). +fn initial_view_box() -> ViewBox { + // Szene ~ x[0..9], y[-1..4] in Meter -> Bildschirm x[0..810], y[-360..90]. + // Etwas Rand drumherum. + let pad = 90.0; + ViewBox::new( + -pad, + -4.0 * PX_PER_M - pad, + 9.0 * PX_PER_M + 2.0 * pad, + 5.0 * PX_PER_M + 2.0 * pad, + ) +} + +struct GpuState { + surface: wgpu::Surface<'static>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + renderer: Renderer, + window: Arc, +} + +impl GpuState { + fn new(window: Arc) -> Self { + let size = window.inner_size(); + let instance = wgpu::Instance::default(); + let surface = instance + .create_surface(window.clone()) + .expect("Surface erstellen"); + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + force_fallback_adapter: false, + compatible_surface: Some(&surface), + })) + .expect("kein passender GPU-Adapter"); + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: Some("2d.device"), + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + memory_hints: wgpu::MemoryHints::Performance, + }, + None, + )) + .expect("Device anfordern"); + + let caps = surface.get_capabilities(&adapter); + let format = caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: size.width.max(1), + height: size.height.max(1), + present_mode: caps.present_modes[0], + alpha_mode: caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + surface.configure(&device, &config); + + let mut renderer = Renderer::new(&device, format); + renderer.upload_scene(&device, &demo_scene()); + + Self { + surface, + device, + queue, + config, + renderer, + window, + } + } + + fn resize(&mut self, w: u32, h: u32) { + if w == 0 || h == 0 { + return; + } + self.config.width = w; + self.config.height = h; + self.surface.configure(&self.device, &self.config); + } + + fn render(&mut self, view_box: ViewBox) { + let frame = match self.surface.get_current_texture() { + Ok(f) => f, + Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { + self.surface.configure(&self.device, &self.config); + return; + } + Err(e) => { + eprintln!("Surface-Fehler: {e:?}"); + return; + } + }; + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + self.renderer.render( + &self.device, + &self.queue, + &view, + view_box, + (self.config.width, self.config.height), + ); + frame.present(); + } +} + +#[derive(Default)] +struct App { + state: Option, + view_box: Option, + dragging: bool, + last_cursor: (f64, f64), +} + +impl ApplicationHandler for App { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + if self.state.is_some() { + return; + } + let attrs = Window::default_attributes().with_title("render2d — Spike"); + let window = Arc::new(event_loop.create_window(attrs).expect("Fenster erstellen")); + self.view_box.get_or_insert_with(initial_view_box); + self.state = Some(GpuState::new(window)); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + _id: WindowId, + event: WindowEvent, + ) { + let Some(state) = self.state.as_mut() else { + return; + }; + let vb = self.view_box.get_or_insert_with(initial_view_box); + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Resized(size) => { + state.resize(size.width, size.height); + state.window.request_redraw(); + } + WindowEvent::MouseInput { state: s, button, .. } => { + if button == MouseButton::Left { + self.dragging = s == ElementState::Pressed; + } + } + WindowEvent::CursorMoved { position, .. } => { + if self.dragging { + // Pan: Cursor-Delta (Geraete-px) -> viewBox-Einheiten (meet-Skala). + let (vw, vh) = (state.config.width as f32, state.config.height as f32); + let meet = render2d::meet_scale(*vb, vw, vh); + let dx = (position.x - self.last_cursor.0) as f32 / meet; + let dy = (position.y - self.last_cursor.1) as f32 / meet; + vb.x -= dx; + vb.y -= dy; + state.window.request_redraw(); + } + self.last_cursor = (position.x, position.y); + } + WindowEvent::MouseWheel { delta, .. } => { + let step = match delta { + MouseScrollDelta::LineDelta(_, y) => y, + MouseScrollDelta::PixelDelta(p) => (p.y as f32) / 40.0, + }; + // Zoom um die viewBox-Mitte (Faktor pro Radschritt). + let factor = if step > 0.0 { 0.9 } else { 1.0 / 0.9 }; + let cx = vb.x + vb.w * 0.5; + let cy = vb.y + vb.h * 0.5; + vb.w *= factor; + vb.h *= factor; + vb.x = cx - vb.w * 0.5; + vb.y = cy - vb.h * 0.5; + state.window.request_redraw(); + } + WindowEvent::RedrawRequested => { + let vb_copy = *vb; + state.render(vb_copy); + } + _ => {} + } + } +} + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new().expect("Event-Loop erstellen"); + event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait); + let mut app = App::default(); + event_loop.run_app(&mut app).expect("App laufen lassen"); +} diff --git a/src-tauri/render2d/src/gpu.rs b/src-tauri/render2d/src/gpu.rs new file mode 100644 index 0000000..3f80a7e --- /dev/null +++ b/src-tauri/render2d/src/gpu.rs @@ -0,0 +1,400 @@ +// wgpu-Pipelines des nativen 2D-Renderers (nur mit Feature "render"). +// +// Zwei Pipelines analog zum WebGL-Pfad: +// - Fuell-Pipeline: gefuellte Polygone, Ortho-Matrix-Uniform. +// - Linien-Pipeline: Quad-Expansion, Breite in echten Papier-mm (Shader). +// +// Der Renderer ist fenster-/surface-agnostisch: er bekommt ein `Device`, eine +// `Queue` und ein `TextureView` (die Surface oder eine Offscreen-Textur) und +// zeichnet dort hinein. Die Fenster-Anbindung (winit) liegt separat im Spike-Bin. +// +// Pro-Batch-Uniforms ueber DYNAMISCHE Offsets: alle Globals-Bloecke (je Fuell-/ +// Linien-Batch einer) liegen hintereinander in EINEM Uniform-Puffer (auf die +// GPU-Mindest-Ausrichtung gepolstert). Vor dem Zeichnen wird der ganze Puffer +// EINMAL geschrieben; jeder Draw-Call bindet seinen Block per dynamischem Offset. +// Das umgeht die Regel "kein write_buffer auf gebundenen Puffer im offenen Pass". + +use bytemuck::{Pod, Zeroable}; +use wgpu::util::DeviceExt; + +use crate::ortho::{compute_ortho_matrix, mm_to_device_px, Mat4}; +use crate::shaders::{FILL_WGSL, LINE_WGSL}; +use crate::tessellate::{compile_scene, GpuGeometry}; +use crate::types::{Scene, ViewBox}; + +/// Uniform-Block, 1:1 zum WGSL-`Globals`-Struct. std140-kompatibel ausgelegt +/// (16-Byte-Alignment der mat4/vec4, Skalare am Ende gepackt). +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct Globals { + view_proj: [f32; 16], + color: [f32; 4], + viewport_px: [f32; 2], + stroke_px: f32, + stroke_scale: f32, +} + +impl Default for Globals { + fn default() -> Self { + Self { + view_proj: crate::ortho::identity(), + color: [0.0, 0.0, 0.0, 1.0], + viewport_px: [1.0, 1.0], + stroke_px: 0.0, + stroke_scale: 1.0, + } + } +} + +/// Rundet `size` auf das naechste Vielfache von `align` (>=1) auf. +fn align_up(size: u64, align: u64) -> u64 { + if align <= 1 { + return size; + } + ((size + align - 1) / align) * align +} + +/// GPU-seitige Puffer + Batch-Metadaten einer tessellierten Szene. +struct SceneBuffers { + fill_vbo: Option, + fill_ibo: Option, + line_vbo: Option, + line_ibo: Option, + geo: GpuGeometry, +} + +/// Der native 2D-Renderer: haelt beide Pipelines, den (dynamischen) Uniform- +/// Puffer und die aktuell hochgeladene Szene. Pan/Zoom aktualisiert nur die +/// Ortho-Matrix (kein Re-Tessellieren). +pub struct Renderer { + fill_pipeline: wgpu::RenderPipeline, + line_pipeline: wgpu::RenderPipeline, + bind_group_layout: wgpu::BindGroupLayout, + /// Ausgerichtete Groesse eines Globals-Blocks im dynamischen Uniform-Puffer. + uniform_stride: u64, + /// Aktuell allozierter Uniform-Puffer + zugehoerige Bind-Group (wachsen bei Bedarf). + uniform: Option, + scene: Option, + /// Loeschfarbe (Zeichenblatt), Default hell wie im WebGL-Pfad (#f0f0f0). + pub clear_color: wgpu::Color, + /// Papier-Massstab-Nenner (1:N) fuer echte mm-Strichbreiten. + pub paper_scale_n: f32, +} + +/// Ein Uniform-Puffer, der `capacity` Globals-Bloecke fasst, plus Bind-Group. +struct UniformArena { + buffer: wgpu::Buffer, + bind_group: wgpu::BindGroup, + capacity: u64, +} + +impl Renderer { + /// Erzeugt Pipelines + Layout fuer ein gegebenes Farbformat (Surface). + pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self { + let fill_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("fill.wgsl"), + source: wgpu::ShaderSource::Wgsl(FILL_WGSL.into()), + }); + let line_module = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("line.wgsl"), + source: wgpu::ShaderSource::Wgsl(LINE_WGSL.into()), + }); + + // Ein Uniform-Block (Globals), fuer beide Pipelines geteilt, mit dynamischem + // Offset (jeder Batch bekommt seinen eigenen Block-Slice). + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("globals.layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: wgpu::BufferSize::new( + std::mem::size_of::() as u64 + ), + }, + count: None, + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("2d.layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + // Alpha-Blending (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) wie im WebGL-Pfad. + let blend = Some(wgpu::BlendState::ALPHA_BLENDING); + let color_target = wgpu::ColorTargetState { + format: color_format, + blend, + write_mask: wgpu::ColorWrites::ALL, + }; + + // Fuell-Pipeline: ein vec2-Attribut (Bildschirm-Position). + let fill_vertex_layout = wgpu::VertexBufferLayout { + array_stride: 2 * 4, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array![0 => Float32x2], + }; + let fill_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("fill.pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &fill_module, + entry_point: "vs_main", + buffers: &[fill_vertex_layout], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &fill_module, + entry_point: "fs_main", + targets: &[Some(color_target.clone())], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + // Ohr-Clipping liefert stets CCW; wir lassen Culling aus (robuster). + cull_mode: None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + // Linien-Pipeline: [pos vec2, bisector vec2, side f32, miter f32], stride 6*4. + let line_vertex_layout = wgpu::VertexBufferLayout { + array_stride: 6 * 4, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Float32, 3 => Float32], + }; + let line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("line.pipeline"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &line_module, + entry_point: "vs_main", + buffers: &[line_vertex_layout], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &line_module, + entry_point: "fs_main", + targets: &[Some(color_target)], + compilation_options: Default::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + cull_mode: None, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + // Block-Stride = Globals auf die Dynamic-Offset-Ausrichtung des Geraets gepolstert. + let min_align = device.limits().min_uniform_buffer_offset_alignment as u64; + let uniform_stride = align_up(std::mem::size_of::() as u64, min_align.max(1)); + + Self { + fill_pipeline, + line_pipeline, + bind_group_layout, + uniform_stride, + uniform: None, + scene: None, + // #f0f0f0 (== --sheet). 0.941 entspricht dem WebGL-clearColor. + clear_color: wgpu::Color { + r: 0.941, + g: 0.941, + b: 0.941, + a: 1.0, + }, + paper_scale_n: 100.0, + } + } + + /// Tessellliert eine Szene und laedt die Puffer hoch (einmal je Szene). + pub fn upload_scene(&mut self, device: &wgpu::Device, scene: &Scene) { + let geo = compile_scene(scene); + + let mk_vbo = |data: &[f32], label: &str| -> Option { + if data.is_empty() { + return None; + } + Some(device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(label), + contents: bytemuck::cast_slice(data), + usage: wgpu::BufferUsages::VERTEX, + })) + }; + let mk_ibo = |data: &[u32], label: &str| -> Option { + if data.is_empty() { + return None; + } + Some(device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(label), + contents: bytemuck::cast_slice(data), + usage: wgpu::BufferUsages::INDEX, + })) + }; + + self.scene = Some(SceneBuffers { + fill_vbo: mk_vbo(&geo.fill_pos, "fill.vbo"), + fill_ibo: mk_ibo(&geo.fill_idx, "fill.ibo"), + line_vbo: mk_vbo(&geo.line_verts, "line.vbo"), + line_ibo: mk_ibo(&geo.line_idx, "line.ibo"), + geo, + }); + } + + /// Stellt sicher, dass der dynamische Uniform-Puffer mindestens `count` + /// Bloecke fasst; realloziert (Puffer + Bind-Group) bei Bedarf. + fn ensure_uniform_capacity(&mut self, device: &wgpu::Device, count: u64) { + let need = count.max(1); + if let Some(u) = &self.uniform { + if u.capacity >= need { + return; + } + } + let size = self.uniform_stride * need; + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("globals.buffer"), + size, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("globals.bind"), + layout: &self.bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &buffer, + offset: 0, + size: wgpu::BufferSize::new(std::mem::size_of::() as u64), + }), + }], + }); + self.uniform = Some(UniformArena { + buffer, + bind_group, + capacity: need, + }); + } + + /// Zeichnet einen Frame in `view` (Surface- oder Offscreen-Textur). + /// Pan/Zoom kommt ueber `view_box`/`viewport`; nur die Ortho-Matrix aendert sich. + pub fn render( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + view: &wgpu::TextureView, + view_box: ViewBox, + viewport: (u32, u32), + ) { + let (vw, vh) = (viewport.0 as f32, viewport.1 as f32); + let proj: Mat4 = compute_ortho_matrix(view_box, vw, vh); + let mm_px = mm_to_device_px(view_box, vw, vh, self.paper_scale_n); + + // Alle Globals-Bloecke der Reihenfolge nach (erst Fuell-, dann Linien-Batches) + // sammeln, den Puffer einmal schreiben, danach nur noch dynamisch binden. + let mut blocks: Vec = Vec::new(); + if let Some(scene) = &self.scene { + for b in &scene.geo.fill_batches { + blocks.push(Globals { + view_proj: proj, + color: b.color, + viewport_px: [vw, vh], + stroke_px: 0.0, + stroke_scale: mm_px, + }); + } + for b in &scene.geo.line_batches { + blocks.push(Globals { + view_proj: proj, + color: b.color, + viewport_px: [vw, vh], + stroke_px: b.stroke_mm, + stroke_scale: mm_px, + }); + } + } + + self.ensure_uniform_capacity(device, blocks.len() as u64); + + // Alle Bloecke in den (auf uniform_stride gepolsterten) Puffer schreiben. + if let Some(u) = &self.uniform { + let stride = self.uniform_stride as usize; + let mut bytes = vec![0u8; stride * blocks.len().max(1)]; + for (i, g) in blocks.iter().enumerate() { + let off = i * stride; + bytes[off..off + std::mem::size_of::()] + .copy_from_slice(bytemuck::bytes_of(g)); + } + if !blocks.is_empty() { + queue.write_buffer(&u.buffer, 0, &bytes); + } + } + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("2d.encoder"), + }); + { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("2d.pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(self.clear_color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + if let (Some(scene), Some(u)) = (&self.scene, &self.uniform) { + let stride = self.uniform_stride as u32; + let mut block = 0u32; // Laufindex ueber Fuell- dann Linien-Batches + + // 1) Fuellungen. + if let (Some(vbo), Some(ibo)) = (&scene.fill_vbo, &scene.fill_ibo) { + pass.set_pipeline(&self.fill_pipeline); + pass.set_vertex_buffer(0, vbo.slice(..)); + pass.set_index_buffer(ibo.slice(..), wgpu::IndexFormat::Uint32); + for b in &scene.geo.fill_batches { + pass.set_bind_group(0, &u.bind_group, &[block * stride]); + let start = b.start_index; + pass.draw_indexed(start..start + b.index_count, 0, 0..1); + block += 1; + } + } else { + block += scene.geo.fill_batches.len() as u32; + } + + // 2) Linien (Umrisse + freie Linien). + if let (Some(vbo), Some(ibo)) = (&scene.line_vbo, &scene.line_ibo) { + pass.set_pipeline(&self.line_pipeline); + pass.set_vertex_buffer(0, vbo.slice(..)); + pass.set_index_buffer(ibo.slice(..), wgpu::IndexFormat::Uint32); + for b in &scene.geo.line_batches { + pass.set_bind_group(0, &u.bind_group, &[block * stride]); + let start = b.start_index; + pass.draw_indexed(start..start + b.index_count, 0, 0..1); + block += 1; + } + } + } + } + queue.submit(std::iter::once(encoder.finish())); + } +} diff --git a/src-tauri/render2d/src/lib.rs b/src-tauri/render2d/src/lib.rs new file mode 100644 index 0000000..1f62f47 --- /dev/null +++ b/src-tauri/render2d/src/lib.rs @@ -0,0 +1,174 @@ +// Nativer wgpu-2D-Renderer fuer den CAD-Grundriss. +// +// Aufbau in Schichten (bewusst getrennt, siehe Feature-Flags in Cargo.toml): +// - `types` : serde-only Eingabe (geflachte Primitive, Szene, ViewBox). +// - `tessellate` : Ear-Clipping + Linien->Quads, Bildschirm-Raum. GPU-frei, +// headless per `cargo test` pruefbar. Kern-Port des WebGL-Pfads. +// - `ortho` : aspekt-korrekte Ortho-Matrix + Papier-mm-Strichbreiten-Formel. +// - `shaders` : WGSL-Quellen (aus den GLSL-Vorlagen uebersetzt). +// - `gpu` : wgpu-Pipelines (Feature "render"). +// - `bin/spike` : winit-Fenster fuer die visuelle Verifikation (Feature "window"). +// +// Standard-Build (`cargo test`/`cargo build` ohne Features) enthaelt nur die +// GPU-freien Schichten und ist damit unabhaengig von einer Display-Session. + +pub mod ortho; +pub mod shaders; +pub mod tessellate; +pub mod types; + +#[cfg(feature = "render")] +pub mod gpu; + +pub use ortho::{compute_ortho_matrix, meet_scale, mm_to_device_px, Mat4}; +pub use tessellate::{compile_scene, triangulate, GpuGeometry, PX_PER_M}; +pub use types::{FillPolygon, Line, Outline, Point, Rgba, Scene, ViewBox}; + +// --- Tests: Tessellierung (Muster wie glPlanCompile.test.ts) ----------------- + +#[cfg(test)] +mod tests { + use super::tessellate::{triangulate, PX_PER_M}; + use super::types::Point; + + /// Summierte Dreiecksflaeche (Betrag) aus Indizes ueber pts. + fn tri_area(pts: &[Point], idx: &[u32]) -> f32 { + let mut area = 0.0f32; + let mut i = 0; + while i < idx.len() { + let a = pts[idx[i] as usize]; + let b = pts[idx[i + 1] as usize]; + let c = pts[idx[i + 2] as usize]; + area += ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])).abs() / 2.0; + i += 3; + } + area + } + + /// Polygon-Flaeche (Shoelace, Betrag). + fn poly_area(pts: &[Point]) -> f32 { + let mut a = 0.0f32; + let n = pts.len(); + let mut j = n - 1; + for i in 0..n { + a += (pts[j][0] + pts[i][0]) * (pts[j][1] - pts[i][1]); + j = i; + } + a.abs() / 2.0 + } + + #[test] + fn quadrat_zwei_dreiecke_volle_flaeche() { + let sq: Vec = vec![[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]]; + let idx = triangulate(&sq); + assert_eq!(idx.len(), 6, "2 Dreiecke erwartet"); + assert!((tri_area(&sq, &idx) - poly_area(&sq)).abs() < 1e-4); + } + + #[test] + fn konkaves_l_flaechentreu_kein_fan() { + // L-Form: konkave Ecke bei (2,2). Ein Fan wuerde Flaeche ausserhalb des L + // erzeugen; Ear-Clipping muss flaechentreu bleiben. + let l: Vec = vec![ + [0.0, 0.0], + [4.0, 0.0], + [4.0, 2.0], + [2.0, 2.0], + [2.0, 4.0], + [0.0, 4.0], + ]; + let idx = triangulate(&l); + assert_eq!(idx.len(), 12, "6 Ecken -> 4 Dreiecke"); + assert!((tri_area(&l, &idx) - poly_area(&l)).abs() < 1e-4); + assert!((tri_area(&l, &idx) - 12.0).abs() < 1e-4, "L-Flaeche == 12"); + } + + #[test] + fn weniger_als_drei_ecken_leer() { + assert!(triangulate(&[]).is_empty()); + assert!(triangulate(&[[0.0, 0.0]]).is_empty()); + assert!(triangulate(&[[0.0, 0.0], [1.0, 1.0]]).is_empty()); + } + + #[test] + fn beide_wicklungsrichtungen_gleich() { + // CW-Quadrat. + let cw: Vec = vec![[0.0, 0.0], [0.0, 4.0], [4.0, 4.0], [4.0, 0.0]]; + let idx = triangulate(&cw); + assert_eq!(idx.len(), 6); + assert!((tri_area(&cw, &idx) - poly_area(&cw)).abs() < 1e-4); + + // CCW-Quadrat gleicher Groesse -> gleiche Gesamtflaeche. + let ccw: Vec = vec![[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]]; + let idx2 = triangulate(&ccw); + assert_eq!(idx2.len(), 6); + assert!((tri_area(&ccw, &idx2) - tri_area(&cw, &idx)).abs() < 1e-4); + } + + // --- Zusatz: Bildschirm-Raum-Abbildung + Ortho-Matrix -------------------- + + #[test] + fn to_screen_konvention() { + // sx = mx*90, sy = -my*90 (Modell-Y hoch -> Bildschirm-Y runter). + let s = super::tessellate::to_screen([2.0, 3.0]); + assert!((s[0] - 2.0 * PX_PER_M).abs() < 1e-4); + assert!((s[1] + 3.0 * PX_PER_M).abs() < 1e-4); + } + + #[test] + fn ortho_bildet_viewbox_ecken_auf_clip_ab() { + use super::ortho::compute_ortho_matrix; + use super::types::ViewBox; + // Quadratischer viewBox + quadratisches Canvas -> keine Aspekt-Dehnung. + let m = compute_ortho_matrix(ViewBox::new(0.0, 0.0, 100.0, 100.0), 100.0, 100.0); + // Spalten-Major: clip = M * (x,y,0,1). + let clip = |x: f32, y: f32| -> (f32, f32) { + ( + m[0] * x + m[4] * y + m[12], + m[1] * x + m[5] * y + m[13], + ) + }; + // Bildschirm-Punkt (0,0) = obere-linke Ecke -> Clip (-1, +1). + let tl = clip(0.0, 0.0); + assert!((tl.0 + 1.0).abs() < 1e-4); + assert!((tl.1 - 1.0).abs() < 1e-4); + // (100,100) = untere-rechte Ecke -> Clip (+1, -1). + let br = clip(100.0, 100.0); + assert!((br.0 - 1.0).abs() < 1e-4); + assert!((br.1 + 1.0).abs() < 1e-4); + } + + #[test] + fn mm_zu_device_px_35_bei_1zu100() { + use super::ortho::mm_to_device_px; + use super::types::ViewBox; + // meet=1 (viewport == viewBox), N=100: mmToPx = N/1000 * 90 * meet = 9 px/mm. + // 0.35 mm -> 3.15 px. + let vb = ViewBox::new(0.0, 0.0, 100.0, 100.0); + let mm_px = mm_to_device_px(vb, 100.0, 100.0, 100.0); + assert!((mm_px - 9.0).abs() < 1e-4, "9 px/mm bei 1:100, meet=1"); + assert!((0.35 * mm_px - 3.15).abs() < 1e-4); + } + + /// Validiert die WGSL-Quellen headless ueber naga (Parser + Validator) — + /// faengt Syntax-/Typfehler ohne GPU/Display ab. Nur mit Feature "render", + /// weil naga sonst nicht mitgebaut wird. + #[cfg(feature = "render")] + #[test] + fn wgsl_quellen_sind_valide() { + use naga::valid::{Capabilities, ValidationFlags, Validator}; + + for (name, src) in [ + ("fill", super::shaders::FILL_WGSL), + ("line", super::shaders::LINE_WGSL), + ] { + let module = naga::front::wgsl::parse_str(src) + .unwrap_or_else(|e| panic!("{name}: WGSL-Parse-Fehler: {e:?}")); + let mut validator = + Validator::new(ValidationFlags::all(), Capabilities::all()); + validator + .validate(&module) + .unwrap_or_else(|e| panic!("{name}: WGSL-Validierung fehlgeschlagen: {e:?}")); + } + } +} diff --git a/src-tauri/render2d/src/ortho.rs b/src-tauri/render2d/src/ortho.rs new file mode 100644 index 0000000..fef6ba6 --- /dev/null +++ b/src-tauri/render2d/src/ortho.rs @@ -0,0 +1,89 @@ +// Aspekt-korrekte Orthografie + Papier-mm-Strichbreiten-Formel. +// 1:1-Port von `computeOrthoMatrix` / dem strokeScale-Pfad aus +// `src/plan/glPlan/glPlanRender.ts`. +// +// WICHTIG (Konventionsunterschied WebGL vs. wgpu): +// - WebGL-Clip-Z liegt in [-1, 1]. +// - wgpu/WebGPU-Clip-Z liegt in [0, 1]. +// Fuer den reinen 2D-Fall (z=0) spielt das keine Rolle: wir setzen z konstant auf +// 0.0 im Shader; die XY-Abbildung ist identisch. Die Matrix hier ist daher exakt +// die WebGL-Ortho (Spalten-Major), unveraendert uebernehmbar. + +use crate::tessellate::PX_PER_M; +use crate::types::ViewBox; + +/// 4x4-Matrix, Spalten-Major (WebGL/wgpu-Konvention: `m[col*4 + row]`). +pub type Mat4 = [f32; 16]; + +/// Einheitsmatrix. +pub fn identity() -> Mat4 { + let mut m = [0.0f32; 16]; + m[0] = 1.0; + m[5] = 1.0; + m[10] = 1.0; + m[15] = 1.0; + m +} + +/// Orthografische Projektion [left,right] x [bottom,top] -> Clip. Spalten-Major. +/// Die Z-Zeile bildet z=0 auf 0 ab (fuer wgpu unschaedlich, siehe Modul-Kopf). +fn ortho(left: f32, right: f32, bottom: f32, top: f32) -> Mat4 { + let mut m = identity(); + let w = right - left; + let h = top - bottom; + m[0] = 2.0 / w; + m[5] = 2.0 / h; + m[10] = -1.0; + m[12] = -(right + left) / w; + m[13] = -(top + bottom) / h; + m +} + +/// Projektionsmatrix fuer einen viewBox, aspekt-korrigiert wie SVG +/// `preserveAspectRatio="xMidYMid meet"`: der viewBox wird auf das +/// Canvas-Seitenverhaeltnis gedehnt (zentriert), damit die Skalierung in X und Y +/// gleich ist (keine Verzerrung, Perpendikel bleiben senkrecht). +/// +/// Eingabe-Positionen sind bereits im BILDSCHIRM-Raum (`to_screen`), daher keine +/// zusaetzliche Meter-Skalierung. Bildschirm-Y zeigt nach unten -> top = vb.y, +/// bottom = vb.y + vb.h (kleineres Y oben -> Clip +1). +pub fn compute_ortho_matrix(view_box: ViewBox, canvas_w: f32, canvas_h: f32) -> Mat4 { + let ViewBox { + mut x, + mut y, + mut w, + mut h, + } = view_box; + if canvas_w > 0.0 && canvas_h > 0.0 && w > 0.0 && h > 0.0 { + let c_aspect = canvas_w / canvas_h; + let v_aspect = w / h; + if c_aspect > v_aspect { + // Canvas breiter -> viewBox in der Breite dehnen, X zentrieren. + let nw = h * c_aspect; + x -= (nw - w) / 2.0; + w = nw; + } else { + // Canvas hoeher -> viewBox in der Hoehe dehnen, Y zentrieren. + let nh = w / c_aspect; + y -= (nh - h) / 2.0; + h = nh; + } + } + // top = y (Bildschirm-Y oben, kleiner), bottom = y + h. + ortho(x, x + w, y + h, y) +} + +/// meet-Skala: Geraete-px je viewBox-Einheit (SVG `xMidYMid meet`). +pub fn meet_scale(view_box: ViewBox, viewport_w: f32, viewport_h: f32) -> f32 { + (viewport_w / view_box.w).min(viewport_h / view_box.h) +} + +/// Umrechnung Papier-mm -> Geraete-px, EXAKT wie der SVG-`printStrokeVb`-Pfad: +/// Breite_vb = mm * N/1000 * PX_PER_M (viewBox-Einheiten, skaliert mit Zoom) +/// Breite_px = Breite_vb * meetSkala (Geraete-px je viewBox-Einheit) +/// -> mm * (N/1000 * PX_PER_M * meet). So gilt: 0.35 mm bei 1:100 = echte 0.35 mm +/// Papier (deckungsgleich zum SVG-Renderer). +pub fn mm_to_device_px(view_box: ViewBox, viewport_w: f32, viewport_h: f32, paper_scale_n: f32) -> f32 { + let meet = meet_scale(view_box, viewport_w, viewport_h); + (paper_scale_n / 1000.0) * PX_PER_M * meet +} diff --git a/src-tauri/render2d/src/shaders.rs b/src-tauri/render2d/src/shaders.rs new file mode 100644 index 0000000..751b0af --- /dev/null +++ b/src-tauri/render2d/src/shaders.rs @@ -0,0 +1,103 @@ +// WGSL-Shader des nativen 2D-Renderers. Uebersetzt aus den GLSL-300-es-Vorlagen +// in `src/plan/glPlan/glPlanShaders.ts` (WGSL ~= GLSL). Als Konstanten hier, damit +// die Quellen auch ohne aktives GPU-Feature (headless) im Repo pruefbar bleiben. +// +// Uniform-Layout (ein gemeinsamer Struct, group(0) binding(0)): +// view_proj : mat4x4 Bildschirm-Raum -> Clip +// color : vec4 Draw-Farbe (pro Batch gesetzt) +// viewport_px : vec2 Zeichenpuffer-Groesse [w,h] in Geraete-px +// stroke_px : f32 ECHTE Papier-mm-Breite (Basis; == strokeMm) +// stroke_scale : f32 mm -> Geraete-px (== mmToDevicePx) +// +// Fuellungen ignorieren die Linien-Felder; Linien nutzen alle. + +/// Gemeinsame WGSL-Uniform-Deklaration (fuer beide Pipelines identisch). +pub const UNIFORM_WGSL: &str = r#" +struct Globals { + view_proj : mat4x4, + color : vec4, + viewport_px : vec2, + stroke_px : f32, + stroke_scale : f32, +}; +@group(0) @binding(0) var globals : Globals; +"#; + +/// Fuell-Pipeline: bildet Bildschirm-Raum-Positionen ueber `view_proj` in den +/// Clip-Raum ab und faerbt einheitlich (MVP; Schraffur folgt in M3). +pub const FILL_WGSL: &str = r#" +struct Globals { + view_proj : mat4x4, + color : vec4, + viewport_px : vec2, + stroke_px : f32, + stroke_scale : f32, +}; +@group(0) @binding(0) var globals : Globals; + +@vertex +fn vs_main(@location(0) position : vec2) -> @builtin(position) vec4 { + return globals.view_proj * vec4(position, 0.0, 1.0); +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return globals.color; +} +"#; + +/// Linien-Pipeline mit ECHTER Papier-mm-Breite. +/// +/// Idee (wie im GLSL-Original): Endpunkt und (Endpunkt + Normale) werden durch +/// `view_proj` transformiert; ihre Clip-Differenz (in Pixel skaliert) ergibt die +/// Perpendikel-Richtung. Diese wird auf `width_px/2` Pixel normiert und — je nach +/// `side` — auf die passende Seite versetzt, dann px -> Clip zurueckgerechnet. So +/// bleibt die Strichbreite zoom-unabhaengig echte Papier-mm (keine Re-Tessellierung). +/// +/// width_px = max(0.6, stroke_px * stroke_scale) (== mm * mmToDevicePx) +/// Mindestens ~0.6 px, damit feine Gewichte beim Rauszoomen sichtbar bleiben. +pub const LINE_WGSL: &str = r#" +struct Globals { + view_proj : mat4x4, + color : vec4, + viewport_px : vec2, + stroke_px : f32, + stroke_scale : f32, +}; +@group(0) @binding(0) var globals : Globals; + +struct VsIn { + @location(0) position : vec2, // Bildschirm-Raum-Endpunkt + @location(1) bisector : vec2, // Bildschirm-Raum-Miter-Bisektor (Einheit) + @location(2) side : f32, // +1.0 oder -1.0 + @location(3) miter : f32, // 1/cos(theta/2)-Laengenfaktor (Gehrung) +}; + +@vertex +fn vs_main(in : VsIn) -> @builtin(position) vec4 { + let clip_p = globals.view_proj * vec4(in.position, 0.0, 1.0); + let clip_n = globals.view_proj * vec4(in.position + in.bisector, 0.0, 1.0); + + // Bisektor-Richtung im Pixel-Raum (Clip-Differenz -> px). + let dir_px = (clip_n.xy - clip_p.xy) * globals.viewport_px * 0.5; + let len = length(dir_px); + var unit_px = vec2(0.0, 0.0); + if (len > 1e-6) { + unit_px = dir_px / len; + } + + // Echte Papierbreite in Geraete-px = mm(stroke_px) * (mm->px)(stroke_scale), + // entlang des Bisektors um den Miter-Faktor verlaengert (buendige Gehrung). + let width_px = max(0.6, globals.stroke_px * globals.stroke_scale) * in.miter; + let offset_px = unit_px * (width_px * 0.5) * in.side; + + var out = clip_p; + out = vec4(out.xy + offset_px / (globals.viewport_px * 0.5), out.z, out.w); + return out; +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return globals.color; +} +"#; diff --git a/src-tauri/render2d/src/tessellate.rs b/src-tauri/render2d/src/tessellate.rs new file mode 100644 index 0000000..4c0812a --- /dev/null +++ b/src-tauri/render2d/src/tessellate.rs @@ -0,0 +1,414 @@ +// Tessellierung: wandelt Szenen-Primitive in GPU-fertige Vertex-/Index-Puffer. +// - Polygone -> echtes Ear-Clipping (konkav-faehig), Bildschirm-Raum-Dreiecke +// - Linien -> Quad mit Normale + Seiten-Flag (Breite spaeter im Shader) +// +// 1:1-Port von `src/plan/glPlan/glPlanCompile.ts`. Koordinaten: alles wird in +// BILDSCHIRM-Raum abgelegt (wie `to_screen`): +// sx = mx * PX_PER_M, sy = -my * PX_PER_M. +// Damit ist die Projektion eine reine viewBox-Orthografie (siehe `ortho`). +// +// WARUM Ear-Clipping selbst portiert (statt earcutr/lyon): +// - Die Plan-Polygone haben wenige Ecken; O(n^2)-Ear-Clipping ist reichlich +// schnell und exakt der bereits verifizierte Web-Algorithmus (gleiche Tests). +// - Keine externe Abhaengigkeit -> die Tessellierungs-Bibliothek bleibt +// serde-only und headless testbar (entscheidend fuer die saubere Trennung +// vom GPU-Teil). earcutr/lyon zoegen zusaetzliche Crates in genau die +// Schicht, die ohne Display gruen bleiben muss. +// - Ergebnis ist byte-fuer-byte-vergleichbar mit dem WebGL-Referenzpfad. + +use crate::types::{FillPolygon, Line, Point, Rgba, Scene}; + +/// viewBox-Einheiten je Meter (identisch zu PlanView/toScreen im Web-Pfad). +pub const PX_PER_M: f32 = 90.0; + +/// Modell-Meter -> Bildschirm-Raum (identisch zu `toScreen`). +#[inline] +pub fn to_screen(p: Point) -> Point { + [p[0] * PX_PER_M, -p[1] * PX_PER_M] +} + +/// Signierte Flaeche (Shoelace); >0 = CCW (Modell-Y nach oben). +fn signed_area(pts: &[Point]) -> f32 { + let mut a = 0.0f32; + let n = pts.len(); + let mut j = n - 1; + for i in 0..n { + a += pts[j][0] * pts[i][1] - pts[i][0] * pts[j][1]; + j = i; + } + a / 2.0 +} + +/// Kreuzprodukt (b-a) x (c-a). +#[inline] +fn cross(a: Point, b: Point, c: Point) -> f32 { + (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]) +} + +/// Maximaler Miter-Laengenfaktor; darueber wird geklemmt (kein Spike an spitzen Ecken). +const MITER_LIMIT: f32 = 4.0; + +/// Einheits-Links-Normale von `from` nach `to` (Bildschirm-Raum); None bei Nulllaenge. +#[inline] +fn left_normal(from: Point, to: Point) -> Option { + let dx = to[0] - from[0]; + let dy = to[1] - from[1]; + let len = (dx * dx + dy * dy).sqrt(); + if len < 1e-6 { + None + } else { + Some([-dy / len, dx / len]) + } +} + +/// Liegt p im (a,b,c)-Dreieck? (CCW-orientiert). +fn point_in_tri(a: Point, b: Point, c: Point, p: Point) -> bool { + let d1 = cross(a, b, p); + let d2 = cross(b, c, p); + let d3 = cross(c, a, p); + let has_neg = d1 < 0.0 || d2 < 0.0 || d3 < 0.0; + let has_pos = d1 > 0.0 || d2 > 0.0 || d3 > 0.0; + !(has_neg && has_pos) +} + +/// Ear-Clipping-Triangulierung eines einfachen (lochfreien) Polygons. Robust fuer +/// konvexe UND konkave Ringe. O(n^2) — fuer Plan-Polygone (wenige Ecken) voellig +/// ausreichend. Gibt Dreiecks-Indizes (0-basiert auf `pts`) zurueck; leer bei <3 +/// Ecken oder Degeneration. +pub fn triangulate(pts: &[Point]) -> Vec { + let n = pts.len(); + if n < 3 { + return Vec::new(); + } + + // Ohr-Test unten nutzt cross>0 = konvex, was CCW voraussetzt. Bei CW-Polygonen + // die Index-Reihenfolge umdrehen (Triangulierung ist raum-affin-invariant, die + // Indizes gelten danach auch fuer die Bildschirm-Raum-Vertices). + let mut idx: Vec = (0..n).collect(); + if signed_area(pts) < 0.0 { + idx.reverse(); // <0 = CW -> auf CCW drehen + } + + let mut tris: Vec = Vec::new(); + let mut guard = 0usize; + let max_guard = n * n + 16; + + while idx.len() > 3 && guard < max_guard { + guard += 1; + let mut clipped = false; + let m = idx.len(); + for i in 0..m { + let i_prev = idx[(i + m - 1) % m]; + let i_cur = idx[i]; + let i_next = idx[(i + 1) % m]; + let a = pts[i_prev]; + let b = pts[i_cur]; + let c = pts[i_next]; + + // Konvexe Ecke? (bei CCW: cross > 0) + if cross(a, b, c) <= 0.0 { + continue; + } + + // Kein anderer Vertex im Ohr? + let mut contains = false; + for &vi in &idx { + if vi == i_prev || vi == i_cur || vi == i_next { + continue; + } + if point_in_tri(a, b, c, pts[vi]) { + contains = true; + break; + } + } + if contains { + continue; + } + + // Ohr abschneiden. + tris.push(i_prev as u32); + tris.push(i_cur as u32); + tris.push(i_next as u32); + idx.remove(i); + clipped = true; + break; + } + if !clipped { + break; // Degeneriert -> abbrechen (kein Absturz). + } + } + if idx.len() == 3 { + tris.push(idx[0] as u32); + tris.push(idx[1] as u32); + tris.push(idx[2] as u32); + } + tris +} + +/// Ein zusammenhaengender Zeichen-Batch (Index-Bereich) mit einer Farbe. +#[derive(Debug, Clone)] +pub struct FillBatch { + pub start_index: u32, + pub index_count: u32, + pub color: Rgba, +} + +/// Ein Linien-Batch: Index-Bereich mit Farbe + echter Papier-Strichbreite. +#[derive(Debug, Clone)] +pub struct LineBatch { + pub start_index: u32, + pub index_count: u32, + pub color: Rgba, + /// ECHTE Strichbreite in Papier-Millimeter. + pub stroke_mm: f32, +} + +/// GPU-fertige Geometrie: getrennte Puffer fuer Flaechen (Poche) und Striche. +/// Einmal je Szene tessellieren; danach treibt nur die Projektionsmatrix Pan/Zoom. +#[derive(Debug, Clone, Default)] +pub struct GpuGeometry { + /// Fuell-Positionen, interleaved [x,y, x,y, ...] in Bildschirm-Raum. + pub fill_pos: Vec, + pub fill_idx: Vec, + pub fill_batches: Vec, + /// Linien-Vertices, interleaved [x,y, bx,by, side, miter] in Bildschirm-Raum + /// (bx,by = Miter-Bisektor, miter = 1/cos(theta/2)-Laengenfaktor). + pub line_verts: Vec, + pub line_idx: Vec, + pub line_batches: Vec, + /// Modell-Bounds (Meter) fuer Debug/Einpassen; nicht render-kritisch. + pub bounds: [f32; 4], // [min_x, min_y, max_x, max_y] +} + +#[inline] +fn same_rgba(a: Rgba, b: Rgba) -> bool { + a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3] +} + +impl GpuGeometry { + /// Ordnungserhaltendes Batch-Merging fuer Fuellungen: aufeinanderfolgende + /// Indizes gleicher Farbe werden zu EINEM Draw-Call zusammengefasst. + fn add_fill_batch(&mut self, count: u32, color: Rgba) { + if let Some(last) = self.fill_batches.last_mut() { + if same_rgba(last.color, color) { + last.index_count += count; + return; + } + } + self.fill_batches.push(FillBatch { + start_index: self.fill_idx.len() as u32 - count, + index_count: count, + color, + }); + } + + fn add_line_batch(&mut self, count: u32, color: Rgba, stroke_mm: f32) { + if let Some(last) = self.line_batches.last_mut() { + if last.stroke_mm == stroke_mm && same_rgba(last.color, color) { + last.index_count += count; + return; + } + } + self.line_batches.push(LineBatch { + start_index: self.line_idx.len() as u32 - count, + index_count: count, + color, + stroke_mm, + }); + } + + /// Zeichnet einen zusammenhaengenden Linienzug (Modell-Meter) als EINEN + /// gehrten Streifen: an jedem Stuetzpunkt wird der Versatz entlang des Miter- + /// Bisektors verlaengert (1/cos(theta/2)), sodass benachbarte Segmente buendig + /// verschmelzen -> gehrte Ecke statt Butt-Cap-Stufe. `closed` schliesst den Ring + /// (letzter<->erster Punkt). Vertex-Layout: [x,y, bx,by, side, miter] (6 floats). + /// + /// 1:1-Port von `strokePolyline` in `src/plan/glPlan/glPlanCompile.ts`. + fn stroke_polyline( + &mut self, + pts_m: &[Point], + closed: bool, + color: Rgba, + stroke_mm: f32, + bounds: &mut Bounds, + ) { + // Auf Bildschirm-Raum abbilden + aufeinanderfolgende Duplikate entfernen. + let mut s: Vec = Vec::with_capacity(pts_m.len()); + for &p in pts_m { + let sp = to_screen(p); + if let Some(last) = s.last() { + if (last[0] - sp[0]).abs() < 1e-6 && (last[1] - sp[1]).abs() < 1e-6 { + continue; + } + } + s.push(sp); + bounds.track(p); + } + if closed && s.len() > 1 { + let f = s[0]; + let l = s[s.len() - 1]; + if (f[0] - l[0]).abs() < 1e-6 && (f[1] - l[1]).abs() < 1e-6 { + s.pop(); + } + } + let k = s.len(); + if k < 2 { + return; + } + + let base = (self.line_verts.len() / 6) as u32; + for i in 0..k { + let has_in = closed || i > 0; + let has_out = closed || i < k - 1; + let n_in = if has_in { + left_normal(s[(i + k - 1) % k], s[i]) + } else { + None + }; + let n_out = if has_out { + left_normal(s[i], s[(i + 1) % k]) + } else { + None + }; + + let (bx, by, miter): (f32, f32, f32); + match (n_in, n_out) { + (Some(a), Some(b)) => { + let sx = a[0] + b[0]; + let sy = a[1] + b[1]; + let slen = (sx * sx + sy * sy).sqrt(); + if slen < 1e-3 { + // ~180-Grad-Umkehr -> kein sinnvoller Bisektor, gerade weiter. + bx = b[0]; + by = b[1]; + miter = 1.0; + } else { + bx = sx / slen; + by = sy / slen; + let denom = bx * b[0] + by * b[1]; // cos(theta/2) + miter = if denom > 1e-3 { + (1.0 / denom).min(MITER_LIMIT) + } else { + 1.0 + }; + } + } + (Some(n), None) | (None, Some(n)) => { + bx = n[0]; + by = n[1]; + miter = 1.0; + } + (None, None) => { + // Bei k>=2 unerreichbar; sicherheitshalber gerade lassen. + bx = 0.0; + by = 0.0; + miter = 1.0; + } + } + self.line_verts + .extend_from_slice(&[s[i][0], s[i][1], bx, by, 1.0, miter]); + self.line_verts + .extend_from_slice(&[s[i][0], s[i][1], bx, by, -1.0, miter]); + } + + let segs = if closed { k } else { k - 1 }; + for i in 0..segs { + let a = base + 2 * i as u32; + let b = base + 2 * (((i + 1) % k) as u32); + self.line_idx + .extend_from_slice(&[a, a + 1, b, a + 1, b + 1, b]); + } + self.add_line_batch((segs * 6) as u32, color, stroke_mm); + } +} + +/// Hilfsstruct zum Verfolgen der Modell-Bounds. +struct Bounds { + min_x: f32, + min_y: f32, + max_x: f32, + max_y: f32, +} + +impl Bounds { + fn new() -> Self { + Self { + min_x: f32::INFINITY, + min_y: f32::INFINITY, + max_x: f32::NEG_INFINITY, + max_y: f32::NEG_INFINITY, + } + } + #[inline] + fn track(&mut self, p: Point) { + if p[0] < self.min_x { + self.min_x = p[0]; + } + if p[1] < self.min_y { + self.min_y = p[1]; + } + if p[0] > self.max_x { + self.max_x = p[0]; + } + if p[1] > self.max_y { + self.max_y = p[1]; + } + } + fn finish(self) -> [f32; 4] { + [ + if self.min_x.is_finite() { self.min_x } else { 0.0 }, + if self.min_y.is_finite() { self.min_y } else { 0.0 }, + if self.max_x.is_finite() { self.max_x } else { 1.0 }, + if self.max_y.is_finite() { self.max_y } else { 1.0 }, + ] + } +} + +/// Tessellliert eine ganze Szene zu GPU-Geometrie (gefuellte Polygone + +/// Papier-mm-Striche). Reihenfolge: Fuellungen -> Umrisse -> freie Linien. +pub fn compile_scene(scene: &Scene) -> GpuGeometry { + let mut geo = GpuGeometry::default(); + let mut bounds = Bounds::new(); + + // 1) Gefuellte Polygone. + for poly in &scene.fills { + compile_fill(&mut geo, poly, &mut bounds); + } + // 2) Umrisse (geschlossene Ringe als EIN gehrter Streifen, inkl. Schluss-Kante). + for o in &scene.outlines { + if o.width_mm > 0.0 && o.pts.len() >= 2 { + geo.stroke_polyline(&o.pts, true, o.color, o.width_mm, &mut bounds); + } + } + // 3) Freie Linien. + for l in &scene.lines { + compile_line(&mut geo, l, &mut bounds); + } + + geo.bounds = bounds.finish(); + geo +} + +fn compile_fill(geo: &mut GpuGeometry, poly: &FillPolygon, bounds: &mut Bounds) { + let tris = triangulate(&poly.pts); + if tris.is_empty() { + return; + } + let base = (geo.fill_pos.len() / 2) as u32; + for &pt in &poly.pts { + let s = to_screen(pt); + geo.fill_pos.push(s[0]); + geo.fill_pos.push(s[1]); + bounds.track(pt); + } + let count = tris.len() as u32; + for t in tris { + geo.fill_idx.push(base + t); + } + geo.add_fill_batch(count, poly.color); +} + +fn compile_line(geo: &mut GpuGeometry, l: &Line, bounds: &mut Bounds) { + let w = if l.width_mm > 0.0 { l.width_mm } else { 0.18 }; + geo.stroke_polyline(&[l.a, l.b], false, l.color, w, bounds); +} diff --git a/src-tauri/render2d/src/types.rs b/src-tauri/render2d/src/types.rs new file mode 100644 index 0000000..942640d --- /dev/null +++ b/src-tauri/render2d/src/types.rs @@ -0,0 +1,82 @@ +// Eingabe-Typen des nativen 2D-Renderers: geflachte Primitive, wie sie aus dem +// Web-Renderer (`src/plan/generatePlan.ts` -> `Plan.primitives`) heruebergereicht +// werden. Bewusst reduziert auf das, was die GPU zeichnet: gefuellte Polygone und +// Linien. Text/Schraffur bleiben (wie im WebGL-Pfad) Overlay bzw. spaeteres M3. +// +// Die Typen sind serde-only und bewusst frei von GPU-Abhaengigkeiten, damit die +// Tessellierung eigenstaendig (headless) getestet werden kann. + +use serde::{Deserialize, Serialize}; + +/// RGBA-Farbe, Komponenten in [0,1] (deckungsgleich zu `Rgba` im WebGL-Pfad). +pub type Rgba = [f32; 4]; + +/// Ein 2D-Punkt in MODELL-Metern (nicht Bildschirm-Raum). Die Konvertierung nach +/// Bildschirm-Raum passiert erst in der Tessellierung (`to_screen`). +pub type Point = [f32; 2]; + +/// Gefuelltes Polygon (Wand-Poche, Raeume, Decken). `pts` in Modell-Metern. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FillPolygon { + /// Ring-Ecken in Modell-Metern; erste != letzte (Ring wird implizit geschlossen). + pub pts: Vec, + /// Fuellfarbe (RGBA 0..1). + pub color: Rgba, +} + +/// Ein Liniensegment mit echter Papier-mm-Breite. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Line { + /// Startpunkt in Modell-Metern. + pub a: Point, + /// Endpunkt in Modell-Metern. + pub b: Point, + /// Strichfarbe (RGBA 0..1). + pub color: Rgba, + /// Strichbreite in ECHTEN Papier-Millimetern (siehe `stroke::mm_to_device_px`). + #[serde(rename = "widthMm")] + pub width_mm: f32, +} + +/// Ein geschlossenes Polygon als Umriss (nur Kanten, keine Fuellung) — bequemer +/// Weg, den Ring eines `FillPolygon` als Striche zu rendern (wie der WebGL-Pfad +/// die Poche-Umrisse zeichnet). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Outline { + /// Ring-Ecken in Modell-Metern. + pub pts: Vec, + /// Strichfarbe (RGBA 0..1). + pub color: Rgba, + /// Strichbreite in echten Papier-Millimetern. + #[serde(rename = "widthMm")] + pub width_mm: f32, +} + +/// Die vollstaendige Szene: alles, was ein Frame zeichnet. Reihenfolge ist +/// signifikant (Z-/Alpha-Ueberlagerung): erst Fuellungen, dann Umrisse, dann +/// freie Linien — analog zur Draw-Reihenfolge im WebGL-Pfad. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Scene { + #[serde(default)] + pub fills: Vec, + #[serde(default)] + pub outlines: Vec, + #[serde(default)] + pub lines: Vec, +} + +/// Rechteckiger Ausschnitt in BILDSCHIRM-Einheiten (viewBox), analog `ViewBox` +/// im WebGL-Pfad. Getrieben von Pan/Zoom in der Webview. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct ViewBox { + pub x: f32, + pub y: f32, + pub w: f32, + pub h: f32, +} + +impl ViewBox { + pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self { + Self { x, y, w, h } + } +}